From ee330deef0445c51ea224379d468c3b2a967b42e Mon Sep 17 00:00:00 2001 From: SaraKaliman <62022458+SaraKaliman@users.noreply.github.com> Date: Wed, 8 Nov 2023 19:51:38 +0100 Subject: [PATCH 1/4] new cell features added to mt_legacy.py and tests for them (#13) * Add files via upload this is h5py file containing masks from fmt-hdf5_cytoshot_full-features_2023.zip and new cell features * Add files via upload tests for updated mt_legacy.py file that checks the new cell features (form the raw contour) * adding new raw cnt based features in dcnum * change casting to float32&int64 back --------- Co-authored-by: Sara Kaliman --- dcnum/feat/feat_moments/mt_legacy.py | 102 +++++++++++------- ...df5_cytoshot_extended-moments-features.zip | Bin 0 -> 6905 bytes tests/test_feat_moments_based_extended.py | 61 +++++++++++ 3 files changed, 126 insertions(+), 37 deletions(-) create mode 100644 tests/data/fmt-hdf5_cytoshot_extended-moments-features.zip create mode 100644 tests/test_feat_moments_based_extended.py diff --git a/dcnum/feat/feat_moments/mt_legacy.py b/dcnum/feat/feat_moments/mt_legacy.py index b90f59c..60e3689 100644 --- a/dcnum/feat/feat_moments/mt_legacy.py +++ b/dcnum/feat/feat_moments/mt_legacy.py @@ -11,67 +11,99 @@ def moments_based_features(mask, pixel_size): size = mask.shape[0] empty = np.full(size, np.nan, dtype=np.float64) - deform = np.copy(empty) + + # features from raw contour size_x = np.copy(empty) size_y = np.copy(empty) - pos_x = np.copy(empty) - pos_y = np.copy(empty) + aspect = np.copy(empty) area_msd = np.copy(empty) + area_um_raw = np.copy(empty) + per_um_raw = np.copy(empty) + deform_raw = np.copy(empty) area_ratio = np.copy(empty) - area_um = np.copy(empty) - aspect = np.copy(empty) - tilt = np.copy(empty) - inert_ratio_cvx = np.copy(empty) + per_ratio = np.copy(empty) inert_ratio_raw = np.copy(empty) inert_ratio_prnc = np.copy(empty) + eccentr_prnc = np.copy(empty) + tilt = np.copy(empty) + s_x = np.copy(empty) + s_y = np.copy(empty) + + # features from convex hull + pos_x = np.copy(empty) + pos_y = np.copy(empty) + area_um = np.copy(empty) + deform = np.copy(empty) + inert_ratio_cvx = np.copy(empty) # The following valid-array is not a real feature, but only # used to figure out which events need to be removed due # to invalid computed features, often due to invalid contours. valid = np.full(size, False) for ii in range(size): + # raw contour cont_raw = contour_single_opencv(mask[ii]) if len(cont_raw.shape) < 2: continue if cv2.contourArea(cont_raw) == 0: continue + mu_raw = cv2.moments(cont_raw) + arc_raw = np.float64(cv2.arcLength(cont_raw, True)) + area_raw = np.float64(mu_raw["m00"]) + event_mask = mask[ii] # convex hull cont_cvx = np.squeeze(cv2.convexHull(cont_raw)) mu_cvx = cv2.moments(cont_cvx) + arc_hull = np.float64(cv2.arcLength(cont_cvx, True)) + # circ + circ = 2 * np.sqrt(np.pi * mu_cvx["m00"]) / arc_hull + deform[ii] = 1 - circ if mu_cvx["m00"] == 0 or mu_raw["m00"] == 0: - # Contour size too small + # contour size too small continue - arc = cv2.arcLength(cont_cvx, True) + # moments of inetria + i_xx = np.float64(mu_raw["mu02"]) + i_yy = np.float64(mu_raw["mu20"]) + i_xy = np.float64(mu_raw["mu11"]) + # central moments in principal axes + i_1 = 0.5*(i_xx+i_yy + np.sqrt((i_xx - i_yy) ** 2 + 4*(i_xy ** 2))) + i_2 = 0.5*(i_xx+i_yy - np.sqrt((i_xx - i_yy) ** 2 + 4*(i_xy ** 2))) + # bounding box x, y, w, h = cv2.boundingRect(cont_raw) - # tilt - oii = 0.5 * np.arctan2(2 * mu_raw['mu11'], - mu_raw['mu02'] - mu_raw['mu20']) - # +PI/2 because relative to channel axis - tilti = oii + np.pi / 2 - # restrict to interval [0,PI/2] - tilti = np.mod(tilti, np.pi) - if tilti > np.pi / 2: - tilti -= np.pi - tilt[ii] = np.abs(tilti) - - # circ - circ = 2 * np.sqrt(np.pi * mu_cvx["m00"]) / arc - deform[ii] = 1 - circ + # tilt angle in radians + tilt[ii] = np.abs(0.5 * np.arctan2(-2*i_xy, i_yy - i_xx)) size_x[ii] = w * pixel_size size_y[ii] = h * pixel_size pos_x[ii] = mu_cvx["m10"] / mu_cvx["m00"] * pixel_size pos_y[ii] = mu_cvx["m01"] / mu_cvx["m00"] * pixel_size area_msd[ii] = mu_raw["m00"] + area_um_raw[ii] = area_raw*(pixel_size ** 2) area_ratio[ii] = mu_cvx["m00"] / mu_raw["m00"] area_um[ii] = mu_cvx["m00"] * pixel_size ** 2 aspect[ii] = w / h + per_um_raw[ii] = (arc_raw * pixel_size) + deform_raw[ii] = (1 - 2 * np.sqrt(np.pi * area_raw) / arc_raw) + per_ratio[ii] = arc_raw / arc_hull + + # b symetry ratio calculation + pos_x_pix = mu_raw["m10"] / mu_raw["m00"] + pos_y_pix = mu_raw["m01"] / mu_raw["m00"] + half_area_midd = np.sum(event_mask[:, round(pos_x_pix)]) / 2 + area_right = np.sum(event_mask[:, round(pos_x_pix)+1:]) \ + + half_area_midd + area_left = np.sum(event_mask[:, :round(pos_x_pix)]) + half_area_midd + s_x[ii] = area_left / area_right + half_area_midd = np.sum(event_mask[round(pos_y_pix), :]) / 2 + area_down = np.sum(event_mask[round(pos_y_pix)+1:, :]) + half_area_midd + area_up = np.sum(event_mask[:round(pos_y_pix), :]) + half_area_midd + s_y[ii] = area_up / area_down # inert_ratio_cvx if mu_cvx['mu02'] > 0: # defaults to zero @@ -81,20 +113,9 @@ def moments_based_features(mask, pixel_size): if mu_raw['mu02'] > 0: # defaults to zero inert_ratio_raw[ii] = np.sqrt(mu_raw['mu20'] / mu_raw['mu02']) - # inert_ratio_prnc - # rotate contour - orient = 0.5 * np.arctan2(2 * mu_raw['mu11'], - mu_raw['mu02'] - mu_raw['mu20']) - cc = np.array(cont_raw, dtype=np.float32, copy=True) # float32 [sic] - rho = np.sqrt(cc[:, 0] ** 2 + cc[:, 1] ** 2) - phi = np.arctan2(cc[:, 1], cc[:, 0]) + orient + np.pi / 2 - cc[:, 0] = rho * np.cos(phi) - cc[:, 1] = rho * np.sin(phi) - # compute inertia ratio of rotated contour - mprnc = cv2.moments(cc) - root_prnc = mprnc["mu20"] / mprnc["mu02"] - if root_prnc > 0: # defaults to zero - inert_ratio_prnc[ii] = np.sqrt(root_prnc) + # inert_ratio_prnc and eccentricity + inert_ratio_prnc[ii] = np.sqrt(i_1 / i_2) + eccentr_prnc[ii] = np.sqrt((i_1 - i_2) / i_1) valid[ii] = True return { @@ -111,5 +132,12 @@ def moments_based_features(mask, pixel_size): "inert_ratio_cvx": inert_ratio_cvx, "inert_ratio_raw": inert_ratio_raw, "inert_ratio_prnc": inert_ratio_prnc, + "area_um_raw": area_um_raw, + "per_um_raw": per_um_raw, + "deform_raw": deform_raw, + "eccentr_prnc": eccentr_prnc, + "per_ratio": per_ratio, + "s_x": s_x, + "s_y": s_y, "valid": valid, } diff --git a/tests/data/fmt-hdf5_cytoshot_extended-moments-features.zip b/tests/data/fmt-hdf5_cytoshot_extended-moments-features.zip new file mode 100644 index 0000000000000000000000000000000000000000..a1501e67c491ab4f80254910f7f7338ad76bd5f4 GIT binary patch literal 6905 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 Mh`3NfzW;guKPZzrI{*Lx literal 0 HcmV?d00001 diff --git a/tests/test_feat_moments_based_extended.py b/tests/test_feat_moments_based_extended.py new file mode 100644 index 0000000..bfc21b1 --- /dev/null +++ b/tests/test_feat_moments_based_extended.py @@ -0,0 +1,61 @@ +import pathlib + +import h5py +import numpy as np + +from dcnum.feat import feat_moments + +from helper_methods import retrieve_data + +data_path = pathlib.Path(__file__).parent / "data" + + +def test_moments_based_features(): + # This file has new cell features belonging to + # fmt-hdf5_cytoshot_full-features_2023.zip + path = retrieve_data(data_path / + "fmt-hdf5_cytoshot_extended-moments-features.zip") + + feats = [ + "area_um_raw", + "per_um_raw", + "deform_raw", + "eccentr_prnc", + "per_ratio", + "s_x", + "s_y", + ] + + # Make data available + with h5py.File(path) as h5: + data = feat_moments.moments_based_features( + mask=h5["events/mask"][:], + pixel_size=0.2645 + ) + for feat in feats: + rtol = 1e-5 + atol = 1e-8 + assert np.allclose(h5["events"][feat][:], + data[feat], + rtol=rtol, + atol=atol), f"Feature {feat} mismatch!" + + +def test_mask_2d(): + masks = np.array([ + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 1, 1, 0, 0], + [0, 0, 1, 1, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + ], dtype=bool)[np.newaxis] + data = feat_moments.moments_based_features( + mask=masks, + pixel_size=0.2645 + ) + assert data["deform_raw"].shape == (1,) + # This is the deformation of a square (compared to circle) + assert np.allclose(data["deform_raw"][0], 0.11377307454724206) + # Without moments-based computation, this would be 4*pxsize=0.066125 + assert np.allclose(data["area_um_raw"][0], 0.06996025) From 16e014415c002d21b21528e312f454d13e78e2ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paul=20M=C3=BCller?= Date: Wed, 8 Nov 2023 20:29:34 +0100 Subject: [PATCH 2/4] ref: strip unrelated features for now --- dcnum/feat/feat_moments/mt_legacy.py | 70 ++++++++---------- ...df5_cytoshot_extended-moments-features.zip | Bin 6905 -> 6340 bytes 2 files changed, 32 insertions(+), 38 deletions(-) diff --git a/dcnum/feat/feat_moments/mt_legacy.py b/dcnum/feat/feat_moments/mt_legacy.py index 60e3689..614f90f 100644 --- a/dcnum/feat/feat_moments/mt_legacy.py +++ b/dcnum/feat/feat_moments/mt_legacy.py @@ -24,10 +24,7 @@ def moments_based_features(mask, pixel_size): per_ratio = np.copy(empty) inert_ratio_raw = np.copy(empty) inert_ratio_prnc = np.copy(empty) - eccentr_prnc = np.copy(empty) tilt = np.copy(empty) - s_x = np.copy(empty) - s_y = np.copy(empty) # features from convex hull pos_x = np.copy(empty) @@ -56,54 +53,41 @@ def moments_based_features(mask, pixel_size): # convex hull cont_cvx = np.squeeze(cv2.convexHull(cont_raw)) mu_cvx = cv2.moments(cont_cvx) - arc_hull = np.float64(cv2.arcLength(cont_cvx, True)) + arc_cvx = np.float64(cv2.arcLength(cont_cvx, True)) # circ - circ = 2 * np.sqrt(np.pi * mu_cvx["m00"]) / arc_hull + circ = 2 * np.sqrt(np.pi * mu_cvx["m00"]) / arc_cvx deform[ii] = 1 - circ if mu_cvx["m00"] == 0 or mu_raw["m00"] == 0: # contour size too small continue - # moments of inetria - i_xx = np.float64(mu_raw["mu02"]) - i_yy = np.float64(mu_raw["mu20"]) - i_xy = np.float64(mu_raw["mu11"]) - # central moments in principal axes - i_1 = 0.5*(i_xx+i_yy + np.sqrt((i_xx - i_yy) ** 2 + 4*(i_xy ** 2))) - i_2 = 0.5*(i_xx+i_yy - np.sqrt((i_xx - i_yy) ** 2 + 4*(i_xy ** 2))) - # bounding box x, y, w, h = cv2.boundingRect(cont_raw) # tilt angle in radians - tilt[ii] = np.abs(0.5 * np.arctan2(-2*i_xy, i_yy - i_xx)) + oii = 0.5 * np.arctan2(2 * mu_raw['mu11'], + mu_raw['mu02'] - mu_raw['mu20']) + # +PI/2 because relative to channel axis + tilti = oii + np.pi / 2 + # restrict to interval [0,PI/2] + tilti = np.mod(tilti, np.pi) + if tilti > np.pi / 2: + tilti -= np.pi + tilt[ii] = np.abs(tilti) size_x[ii] = w * pixel_size size_y[ii] = h * pixel_size pos_x[ii] = mu_cvx["m10"] / mu_cvx["m00"] * pixel_size pos_y[ii] = mu_cvx["m01"] / mu_cvx["m00"] * pixel_size area_msd[ii] = mu_raw["m00"] - area_um_raw[ii] = area_raw*(pixel_size ** 2) + area_um_raw[ii] = area_raw * pixel_size**2 area_ratio[ii] = mu_cvx["m00"] / mu_raw["m00"] - area_um[ii] = mu_cvx["m00"] * pixel_size ** 2 + area_um[ii] = mu_cvx["m00"] * pixel_size**2 aspect[ii] = w / h - per_um_raw[ii] = (arc_raw * pixel_size) - deform_raw[ii] = (1 - 2 * np.sqrt(np.pi * area_raw) / arc_raw) - per_ratio[ii] = arc_raw / arc_hull - - # b symetry ratio calculation - pos_x_pix = mu_raw["m10"] / mu_raw["m00"] - pos_y_pix = mu_raw["m01"] / mu_raw["m00"] - half_area_midd = np.sum(event_mask[:, round(pos_x_pix)]) / 2 - area_right = np.sum(event_mask[:, round(pos_x_pix)+1:]) \ - + half_area_midd - area_left = np.sum(event_mask[:, :round(pos_x_pix)]) + half_area_midd - s_x[ii] = area_left / area_right - half_area_midd = np.sum(event_mask[round(pos_y_pix), :]) / 2 - area_down = np.sum(event_mask[round(pos_y_pix)+1:, :]) + half_area_midd - area_up = np.sum(event_mask[:round(pos_y_pix), :]) + half_area_midd - s_y[ii] = area_up / area_down + per_um_raw[ii] = arc_raw * pixel_size + deform_raw[ii] = 1 - 2 * np.sqrt(np.pi * area_raw) / arc_raw + per_ratio[ii] = arc_raw / arc_cvx # inert_ratio_cvx if mu_cvx['mu02'] > 0: # defaults to zero @@ -113,9 +97,22 @@ def moments_based_features(mask, pixel_size): if mu_raw['mu02'] > 0: # defaults to zero inert_ratio_raw[ii] = np.sqrt(mu_raw['mu20'] / mu_raw['mu02']) - # inert_ratio_prnc and eccentricity - inert_ratio_prnc[ii] = np.sqrt(i_1 / i_2) - eccentr_prnc[ii] = np.sqrt((i_1 - i_2) / i_1) + # inert_ratio_prnc + # rotate contour + orient = 0.5 * np.arctan2(2 * mu_raw['mu11'], + mu_raw['mu02'] - mu_raw['mu20']) + cc = np.array(cont_raw, dtype=np.float32, copy=True) # float32 [sic] + rho = np.sqrt(cc[:, 0] ** 2 + cc[:, 1] ** 2) + phi = np.arctan2(cc[:, 1], cc[:, 0]) + orient + np.pi / 2 + cc[:, 0] = rho * np.cos(phi) + cc[:, 1] = rho * np.sin(phi) + # compute inertia ratio of rotated contour + mprnc = cv2.moments(cc) + root_prnc = mprnc["mu20"] / mprnc["mu02"] + if root_prnc > 0: # defaults to zero + inert_ratio_prnc[ii] = np.sqrt(root_prnc) + + # specify validity valid[ii] = True return { @@ -135,9 +132,6 @@ def moments_based_features(mask, pixel_size): "area_um_raw": area_um_raw, "per_um_raw": per_um_raw, "deform_raw": deform_raw, - "eccentr_prnc": eccentr_prnc, "per_ratio": per_ratio, - "s_x": s_x, - "s_y": s_y, "valid": valid, } diff --git a/tests/data/fmt-hdf5_cytoshot_extended-moments-features.zip b/tests/data/fmt-hdf5_cytoshot_extended-moments-features.zip index a1501e67c491ab4f80254910f7f7338ad76bd5f4..49654d9238dca48c9754e2c2183edb38b27d8c83 100644 GIT binary patch literal 6340 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 Mh`3NfzW;guKPZzrI{*Lx From 8b4249dce35a67b4338f4fae772d86acc1103fe4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paul=20M=C3=BCller?= Date: Wed, 8 Nov 2023 20:32:18 +0100 Subject: [PATCH 3/4] tests: fix tests --- tests/test_feat_moments_based_extended.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/test_feat_moments_based_extended.py b/tests/test_feat_moments_based_extended.py index bfc21b1..3a78ab2 100644 --- a/tests/test_feat_moments_based_extended.py +++ b/tests/test_feat_moments_based_extended.py @@ -20,10 +20,7 @@ def test_moments_based_features(): "area_um_raw", "per_um_raw", "deform_raw", - "eccentr_prnc", "per_ratio", - "s_x", - "s_y", ] # Make data available From 2f6b3d64337f3d175ce77d45e5076697c3b8f7bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paul=20M=C3=BCller?= Date: Wed, 8 Nov 2023 20:39:31 +0100 Subject: [PATCH 4/4] cleanup --- CHANGELOG | 3 +++ dcnum/feat/feat_moments/mt_legacy.py | 20 ++++++++++---------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index a2af735..9353dec 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,6 @@ +0.12.2 + - enh: feature computation for area_um_raw, deform_raw, per_ratio, + and per_um_raw 0.12.1 - fix: avoid NaN-valued features due to invalid contours (#9) 0.12.0 diff --git a/dcnum/feat/feat_moments/mt_legacy.py b/dcnum/feat/feat_moments/mt_legacy.py index 614f90f..1f15913 100644 --- a/dcnum/feat/feat_moments/mt_legacy.py +++ b/dcnum/feat/feat_moments/mt_legacy.py @@ -13,25 +13,26 @@ def moments_based_features(mask, pixel_size): empty = np.full(size, np.nan, dtype=np.float64) # features from raw contour - size_x = np.copy(empty) - size_y = np.copy(empty) - aspect = np.copy(empty) area_msd = np.copy(empty) + area_ratio = np.copy(empty) area_um_raw = np.copy(empty) - per_um_raw = np.copy(empty) + aspect = np.copy(empty) deform_raw = np.copy(empty) - area_ratio = np.copy(empty) - per_ratio = np.copy(empty) - inert_ratio_raw = np.copy(empty) inert_ratio_prnc = np.copy(empty) + inert_ratio_raw = np.copy(empty) + per_ratio = np.copy(empty) + per_um_raw = np.copy(empty) + size_x = np.copy(empty) + size_y = np.copy(empty) tilt = np.copy(empty) # features from convex hull - pos_x = np.copy(empty) - pos_y = np.copy(empty) area_um = np.copy(empty) deform = np.copy(empty) inert_ratio_cvx = np.copy(empty) + pos_x = np.copy(empty) + pos_y = np.copy(empty) + # The following valid-array is not a real feature, but only # used to figure out which events need to be removed due # to invalid computed features, often due to invalid contours. @@ -48,7 +49,6 @@ def moments_based_features(mask, pixel_size): mu_raw = cv2.moments(cont_raw) arc_raw = np.float64(cv2.arcLength(cont_raw, True)) area_raw = np.float64(mu_raw["m00"]) - event_mask = mask[ii] # convex hull cont_cvx = np.squeeze(cv2.convexHull(cont_raw))