Spaces:
Sleeping
Sleeping
| import itertools | |
| import numpy as np | |
| import pandas as pd | |
| from skimage.measure import regionprops_table | |
| # the property keys that are supported for 2 and 3 dim | |
| _PROPERTIES = { | |
| 2: { | |
| # FIXME: The only image regionprop possible now (when compressing) is mean_intensity, | |
| # since we store a mask with the mean intensity of each detection as the image. | |
| "regionprops": ( | |
| "label", | |
| "area", | |
| "intensity_mean", | |
| "eccentricity", | |
| "solidity", | |
| "inertia_tensor", | |
| ), | |
| # faster | |
| "regionprops2": ( | |
| "label", | |
| "area", | |
| "intensity_mean", | |
| "inertia_tensor", | |
| ), | |
| "patch_regionprops": ( | |
| "label", | |
| "area", | |
| "intensity_mean", | |
| "inertia_tensor", | |
| ), | |
| }, | |
| 3: { | |
| "regionprops2": ( | |
| "label", | |
| "area", | |
| "intensity_mean", | |
| "inertia_tensor", | |
| ), | |
| "patch_regionprops": ( | |
| "label", | |
| "area", | |
| "intensity_mean", | |
| "inertia_tensor", | |
| ), | |
| }, | |
| } | |
| def extract_features_regionprops( | |
| mask: np.ndarray, | |
| img: np.ndarray, | |
| labels: np.ndarray, | |
| properties="regionprops2", | |
| ): | |
| ndim = mask.ndim | |
| assert ndim in (2, 3) | |
| assert mask.shape == img.shape | |
| prop_dict = _PROPERTIES[ndim] | |
| if properties not in prop_dict: | |
| raise ValueError(f"properties must be one of {prop_dict.keys()}") | |
| properties_tuple = prop_dict[properties] | |
| assert properties_tuple[0] == "label" | |
| labels = np.asarray(labels) | |
| # remove mask labels that are not present | |
| # not needed, remove for speed | |
| # mask[~np.isin(mask, labels)] = 0 | |
| df = pd.DataFrame( | |
| regionprops_table(mask, intensity_image=img, properties=properties_tuple) | |
| ) | |
| assert df.columns[0] == "label" | |
| assert df.columns[1] == "area" | |
| # the bnumber of inertia tensor columns depends on the dimensionality | |
| n_cols_inertia = ndim**2 | |
| assert np.all(["inertia_tensor" in col for col in df.columns[-n_cols_inertia:]]) | |
| # Hack for backwards compatibility | |
| if properties in ("regionprops", "patch_regionprops"): | |
| # Nice for conceptual clarity, but does not matter for speed | |
| # drop upper triangular part of symmetric inertia tensor | |
| for i, j in itertools.product(range(ndim), repeat=2): | |
| if i > j: | |
| df.drop(f"inertia_tensor-{i}-{j}", axis=1, inplace=True) | |
| table = df.to_numpy() | |
| table[:, 1] *= 0.001 | |
| table[:, -n_cols_inertia:] *= 0.01 | |
| # reorder according to labels | |
| features = np.zeros((len(labels), len(df.columns) - 1)) | |
| # faster than iterating over pandas dataframe | |
| for row in table: | |
| # old version with tuple indexing, slow. | |
| # n = labels.index(int(row.label)) | |
| # features[n] = row.to_numpy()[1:] | |
| # Only process regions present in the labels | |
| n = np.where(labels == int(row[0]))[0] | |
| if len(n) > 0: | |
| # Remove label column (0)! | |
| features[n[0]] = row[1:] | |
| return features | |
| def extract_features_patch( | |
| mask: np.ndarray, | |
| img: np.ndarray, | |
| coords: np.ndarray, | |
| labels: np.ndarray, | |
| width_patch: int = 16, | |
| ): | |
| """16x16 Image patch around detection.""" | |
| ndim = mask.ndim | |
| assert ndim in (2, 3) and mask.shape == img.shape | |
| if len(coords) == 0: | |
| return np.zeros((0, width_patch * width_patch)) | |
| pads = (width_patch // 2,) * ndim | |
| img = np.pad( | |
| img, | |
| tuple((p, p) for p in pads), | |
| mode="constant", | |
| ) | |
| coords = coords.astype(int) + np.array(pads) | |
| ss = tuple( | |
| tuple(slice(_c - width_patch // 2, _c + width_patch // 2) for _c in c) | |
| for c in coords | |
| ) | |
| fs = tuple(img[_s] for _s in ss) | |
| # max project along z if 3D | |
| if ndim == 3: | |
| fs = tuple(f.max(0) for f in fs) | |
| features = np.stack([f.flatten() for f in fs]) | |
| return features | |