File size: 4,011 Bytes
8f72b1f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
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