Skip to content

Commit e17d4f4

Browse files
authored
Merge pull request #61 from computational-cell-analytics/marker_export
The branch adds a script that exports marker annotations as individual binary masks (positive or negative) or a combined mask. It also features an improved intensity threshold calculation for marker annotations. When there are no overlapping IDs between "including all weak positives" and "excluding all negatives," the average of the lowest positive and highest negative intensity is used as the threshold.
2 parents bd03d5e + d070dd7 commit e17d4f4

File tree

3 files changed

+105
-33
lines changed

3 files changed

+105
-33
lines changed

flamingo_tools/segmentation/chreef_utils.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ def find_inbetween_ids(
137137
negexc_negatives = find_overlapping_masks(arr_negexc, roi_seg, label_id_base=1)
138138
allweak_positives = find_overlapping_masks(arr_allweak, roi_seg, label_id_base=2)
139139
inbetween_ids = [int(i) for i in set(negexc_negatives).intersection(set(allweak_positives))]
140-
return inbetween_ids
140+
return inbetween_ids, allweak_positives, negexc_negatives
141141

142142

143143
def get_median_intensity(file_negexc, file_allweak, center, data_seg, table):
@@ -148,9 +148,19 @@ def get_median_intensity(file_negexc, file_allweak, center, data_seg, table):
148148
roi = get_roi(center, roi_halo)
149149

150150
roi_seg = data_seg[roi]
151-
inbetween_ids = find_inbetween_ids(arr_negexc, arr_allweak, roi_seg)
151+
inbetween_ids, allweak_positives, negexc_negatives = find_inbetween_ids(arr_negexc, arr_allweak, roi_seg)
152152
if len(inbetween_ids) == 0:
153-
return None
153+
if len(allweak_positives) == 0 and len(negexc_negatives) == 0:
154+
return None
155+
156+
subset_positive = table[table["label_id"].isin(allweak_positives)]
157+
subset_negative = table[table["label_id"].isin(negexc_negatives)]
158+
lowest_positive = float(subset_positive["median"].min())
159+
highest_negative = float(subset_negative["median"].max())
160+
if np.isnan(lowest_positive) or np.isnan(highest_negative):
161+
return None
162+
163+
return np.average([lowest_positive, highest_negative])
154164

155165
subset = table[table["label_id"].isin(inbetween_ids)]
156166
intensities = list(subset["median"])
@@ -172,7 +182,7 @@ def localize_median_intensities(annotation_dir, cochlea, data_seg, table_measure
172182
median_intensity = get_median_intensity(file_neg, file_pos, center_coord, data_seg, table_measure)
173183

174184
if median_intensity is None:
175-
print(f"No inbetween IDs found for {center_str}.")
185+
print(f"No threshold identified for {center_str}.")
176186

177187
annotation_dic[center_str]["median_intensity"] = median_intensity
178188

scripts/export_lower_resolution.py

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -96,31 +96,6 @@ def filter_cochlea(
9696
dilation_iterations=dilation_iterations)
9797

9898

99-
def filter_marker_instances(cochlea, segmentation, seg_name):
100-
"""Filter segmentation with marker labels.
101-
Positive segmentation instances are set to 1, negative to 2.
102-
"""
103-
internal_path = os.path.join(cochlea, "tables", seg_name, "default.tsv")
104-
tsv_path, fs = get_s3_path(internal_path, bucket_name=BUCKET_NAME, service_endpoint=SERVICE_ENDPOINT)
105-
with fs.open(tsv_path, "r") as f:
106-
table_seg = pd.read_csv(f, sep="\t")
107-
108-
label_ids_positive = list(table_seg.loc[table_seg["marker_labels"] == 1, "label_id"])
109-
label_ids_negative = list(table_seg.loc[table_seg["marker_labels"] == 2, "label_id"])
110-
111-
label_ids_marker = label_ids_positive + label_ids_negative
112-
filter_mask = ~np.isin(segmentation, label_ids_marker)
113-
segmentation[filter_mask] = 0
114-
115-
filter_mask = np.isin(segmentation, label_ids_positive)
116-
segmentation[filter_mask] = 1
117-
filter_mask = np.isin(segmentation, label_ids_negative)
118-
segmentation[filter_mask] = 2
119-
120-
segmentation = segmentation.astype("uint16")
121-
return segmentation
122-
123-
12499
def upscale_volume(
125100
target_data: np.ndarray,
126101
downscaled_volume: np.ndarray,
@@ -186,9 +161,6 @@ def export_lower_resolution(args):
186161
if args.filter_by_components is not None:
187162
print(f"Filtering channel {channel} by components {args.filter_by_components}.")
188163
data = filter_component(fs, data, args.cochlea, channel, args.filter_by_components)
189-
if args.filter_marker_labels:
190-
print(f"Filtering marker instances for channel {channel}.")
191-
data = filter_marker_instances(args.cochlea, data, channel)
192164
if args.filter_cochlea_channels is not None:
193165
us_factor = ds_factor // (2 ** scale)
194166
upscaled_filter = upscale_volume(data, filter_volume, upscale_factor=us_factor)
@@ -213,7 +185,6 @@ def main():
213185
parser.add_argument("--filter_ihc_components", nargs="+", type=int, default=[1])
214186
parser.add_argument("--binarize", action="store_true")
215187
parser.add_argument("--filter_cochlea_channels", nargs="+", type=str, default=None)
216-
parser.add_argument("--filter_marker_labels", action="store_true")
217188
parser.add_argument("--filter_dilation_iterations", type=int, default=8)
218189
args = parser.parse_args()
219190

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import argparse
2+
import os
3+
4+
import numpy as np
5+
import pandas as pd
6+
import tifffile
7+
import zarr
8+
9+
from flamingo_tools.s3_utils import get_s3_path, BUCKET_NAME, SERVICE_ENDPOINT
10+
# from skimage.segmentation import relabel_sequential
11+
12+
13+
def filter_marker_instances(cochlea, segmentation, seg_name, group=None):
14+
"""Filter segmentation with marker labels.
15+
Positive segmentation instances are set to 1, negative to 2.
16+
"""
17+
internal_path = os.path.join(cochlea, "tables", seg_name, "default.tsv")
18+
tsv_path, fs = get_s3_path(internal_path, bucket_name=BUCKET_NAME, service_endpoint=SERVICE_ENDPOINT)
19+
with fs.open(tsv_path, "r") as f:
20+
table_seg = pd.read_csv(f, sep="\t")
21+
22+
label_ids_positive = list(table_seg.loc[table_seg["marker_labels"] == 1, "label_id"])
23+
label_ids_negative = list(table_seg.loc[table_seg["marker_labels"] == 2, "label_id"])
24+
25+
if group is None:
26+
label_ids_marker = label_ids_positive + label_ids_negative
27+
filter_mask = ~np.isin(segmentation, label_ids_marker)
28+
segmentation[filter_mask] = 0
29+
30+
filter_mask = np.isin(segmentation, label_ids_positive)
31+
segmentation[filter_mask] = 1
32+
filter_mask = np.isin(segmentation, label_ids_negative)
33+
segmentation[filter_mask] = 2
34+
elif group == "positive":
35+
filter_mask = ~np.isin(segmentation, label_ids_positive)
36+
segmentation[filter_mask] = 0
37+
filter_mask = np.isin(segmentation, label_ids_positive)
38+
segmentation[filter_mask] = 1
39+
elif group == "negative":
40+
filter_mask = ~np.isin(segmentation, label_ids_negative)
41+
segmentation[filter_mask] = 0
42+
filter_mask = np.isin(segmentation, label_ids_negative)
43+
segmentation[filter_mask] = 2
44+
else:
45+
raise ValueError("Choose either 'positive' or 'negative' as group value.")
46+
47+
segmentation = segmentation.astype("uint16")
48+
return segmentation
49+
50+
51+
def export_lower_resolution(args):
52+
53+
# iterate through exporting lower resolutions
54+
for scale in args.scale:
55+
output_folder = os.path.join(args.output_folder, args.cochlea, f"scale{scale}")
56+
os.makedirs(output_folder, exist_ok=True)
57+
58+
for group in ["positive", "negative"]:
59+
60+
input_key = f"s{scale}"
61+
for channel in args.channels:
62+
63+
out_path = os.path.join(output_folder, f"{channel}_marker_{group}.tif")
64+
if os.path.exists(out_path):
65+
continue
66+
67+
print("Exporting channel", channel)
68+
internal_path = os.path.join(args.cochlea, "images", "ome-zarr", f"{channel}.ome.zarr")
69+
s3_store, fs = get_s3_path(internal_path, bucket_name=BUCKET_NAME, service_endpoint=SERVICE_ENDPOINT)
70+
with zarr.open(s3_store, mode="r") as f:
71+
data = f[input_key][:]
72+
print("Data shape", data.shape)
73+
74+
print(f"Filtering {group} marker instances.")
75+
data = filter_marker_instances(args.cochlea, data, channel, group=group)
76+
tifffile.imwrite(out_path, data, bigtiff=True, compression="zlib")
77+
78+
79+
def main():
80+
parser = argparse.ArgumentParser()
81+
parser.add_argument("--cochlea", "-c", required=True)
82+
parser.add_argument("--scale", "-s", nargs="+", type=int, required=True)
83+
parser.add_argument("--output_folder", "-o", required=True)
84+
parser.add_argument("--channels", nargs="+", type=str, default=["PV", "VGlut3", "CTBP2"])
85+
args = parser.parse_args()
86+
87+
export_lower_resolution(args)
88+
89+
90+
if __name__ == "__main__":
91+
main()

0 commit comments

Comments
 (0)