Skip to content

Commit ea5d84e

Browse files
author
Corentin
committed
masking option for SDH
1 parent 82e3612 commit ea5d84e

File tree

5 files changed

+120
-13
lines changed

5 files changed

+120
-13
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,4 +167,5 @@ debug_data/
167167
!binary_mask.tif
168168
!binary_mask_fluo.tif
169169
!nuclei.tif
170-
!cytoplasm.tif
170+
!cytoplasm.tif
171+
!binary_mask_sdh.tif

myoquant/__main__.py

Lines changed: 77 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def sdh_analysis(
3030
),
3131
mask_path: Path = typer.Option(
3232
None,
33-
help="The path to a binary mask to hide slide region during analysis.",
33+
help="The path to a binary mask to hide slide region during analysis. It needs to be of the same resolution as input image and only pixel marked as 1 will be analyzed.",
3434
exists=True,
3535
file_okay=True,
3636
dir_okay=False,
@@ -134,6 +134,25 @@ def sdh_analysis(
134134

135135
image_ndarray_sdh = imread(image_path)
136136
console.print("Image loaded.", style="blue")
137+
138+
if mask_path is not None:
139+
console.print(f"Reading binary mask: {mask_path} and masking...", style="blue")
140+
mask_ndarray = imread(mask_path)
141+
if np.unique(mask_ndarray).shape[0] != 2:
142+
console.print(
143+
"The mask image should be a binary image with only 2 values (0 and 1).",
144+
style="red",
145+
)
146+
raise ValueError
147+
if len(image_ndarray_sdh.shape) > 2:
148+
mask_ndarray = np.repeat(
149+
mask_ndarray.reshape(mask_ndarray.shape[0], mask_ndarray.shape[1], 1),
150+
image_ndarray_sdh.shape[2],
151+
axis=2,
152+
)
153+
image_ndarray_sdh = image_ndarray_sdh * mask_ndarray
154+
console.print(f"Masking done.", style="blue")
155+
137156
console.print("Starting the Analysis. This may take a while...", style="blue")
138157
if cellpose_path is None:
139158
console.print("Running CellPose...", style="blue")
@@ -152,6 +171,11 @@ def sdh_analysis(
152171

153172
model_SDH = load_sdh_model(model_path)
154173
console.print("SDH Model loaded !", style="blue")
174+
175+
if mask_path is not None:
176+
mask_ndarray = imread(mask_path)
177+
mask_cellpose = mask_cellpose * mask_ndarray
178+
155179
result_df, full_label_map, df_cellpose_details = run_sdh_analysis(
156180
image_ndarray_sdh, model_SDH, mask_cellpose
157181
)
@@ -197,7 +221,7 @@ def sdh_analysis(
197221
def he_analysis(
198222
image_path: Path = typer.Argument(
199223
...,
200-
help="The image file path to analyse.",
224+
help="The HE image file path to analyse. If using single channel images, this will be used as cytoplasm image to run CellPose. Please use the --fluo-nuc option to indicate the path to the nuclei single image to run Stardist.",
201225
exists=True,
202226
file_okay=True,
203227
dir_okay=False,
@@ -207,7 +231,7 @@ def he_analysis(
207231
),
208232
mask_path: Path = typer.Option(
209233
None,
210-
help="The path to a binary mask to hide slide region during analysis.",
234+
help="The path to a binary mask to hide slide region during analysis. It needs to be of the same resolution as input image and only pixel marked as 1 will be analyzed.",
211235
exists=True,
212236
file_okay=True,
213237
dir_okay=False,
@@ -262,6 +286,16 @@ def he_analysis(
262286
export_stats: bool = typer.Option(
263287
True, help="Export per fiber and per nuclei stat table."
264288
),
289+
fluo_nuc: Path = typer.Option(
290+
None,
291+
help="The path to single channel fluo image for nuclei.",
292+
exists=True,
293+
file_okay=True,
294+
dir_okay=False,
295+
writable=False,
296+
readable=True,
297+
resolve_path=True,
298+
),
265299
):
266300
"""Run the HE analysis and quantification on the image."""
267301
from .common_func import (
@@ -309,14 +343,39 @@ def he_analysis(
309343
"No Stardist mask provided, will run Stardist during the analysis.",
310344
style="blue",
311345
)
312-
model_stardist = load_stardist()
346+
if fluo_nuc is None:
347+
model_stardist = load_stardist(fluo=False)
348+
else:
349+
model_stardist = load_stardist(fluo=True)
313350
console.print("Stardist Model loaded !", style="blue")
314351
else:
315352
console.print(f"Stardist mask used: {stardist_path}", style="blue")
316353

317354
console.print("Reading image...", style="blue")
318355

319356
image_ndarray = imread(image_path)
357+
if fluo_nuc is not None:
358+
fluo_nuc_ndarray = imread(fluo_nuc)
359+
360+
if mask_path is not None:
361+
console.print(f"Reading binary mask: {mask_path} and masking...", style="blue")
362+
mask_ndarray = imread(mask_path)
363+
if np.unique(mask_ndarray).shape[0] != 2:
364+
console.print(
365+
"The mask image should be a binary image with only 2 values (0 and 1).",
366+
style="red",
367+
)
368+
raise ValueError
369+
if len(image_ndarray.shape) > 2:
370+
mask_ndarray = np.repeat(
371+
mask_ndarray.reshape(mask_ndarray.shape[0], mask_ndarray.shape[1], 1),
372+
image_ndarray.shape[2],
373+
axis=2,
374+
)
375+
image_ndarray = image_ndarray * mask_ndarray
376+
if fluo_nuc is not None:
377+
fluo_nuc_ndarray = fluo_nuc_ndarray * mask_ndarray
378+
console.print(f"Masking done.", style="blue")
320379
console.print("Image loaded.", style="blue")
321380
console.print("Starting the Analysis. This may take a while...", style="blue")
322381
if cellpose_path is None:
@@ -334,9 +393,14 @@ def he_analysis(
334393

335394
if stardist_path is None:
336395
console.print("Running Stardist...", style="blue")
337-
mask_stardist = run_stardist(
338-
image_ndarray, model_stardist, nms_thresh, prob_thresh
339-
)
396+
if fluo_nuc is not None:
397+
mask_stardist = run_stardist(
398+
fluo_nuc_ndarray, model_stardist, nms_thresh, prob_thresh
399+
)
400+
else:
401+
mask_stardist = run_stardist(
402+
image_ndarray, model_stardist, nms_thresh, prob_thresh
403+
)
340404
mask_stardist = mask_stardist.astype(np.uint16)
341405
stardist_mask_filename = image_path.stem + "_stardist_mask.tiff"
342406
Image.fromarray(mask_stardist).save(output_path / stardist_mask_filename)
@@ -348,6 +412,12 @@ def he_analysis(
348412
mask_stardist = imread(stardist_path)
349413

350414
console.print("Calculating all nuclei eccentricity scores... !", style="blue")
415+
416+
if mask_path is not None:
417+
mask_ndarray = imread(mask_path)
418+
mask_stardist = mask_stardist * mask_ndarray
419+
mask_cellpose = mask_cellpose * mask_ndarray
420+
351421
result_df, full_label_map, df_nuc_analysis, all_nuc_df_stats = run_he_analysis(
352422
image_ndarray, mask_cellpose, mask_stardist, eccentricity_thresh
353423
)

myoquant/common_func.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,11 @@ def load_cellpose():
3232
return model_c
3333

3434

35-
def load_stardist():
36-
model_s = StarDist2D.from_pretrained("2D_versatile_he")
35+
def load_stardist(fluo=False):
36+
if fluo:
37+
model_s = StarDist2D.from_pretrained("2D_versatile_fluo")
38+
else:
39+
model_s = StarDist2D.from_pretrained("2D_versatile_he")
3740
return model_s
3841

3942

sample_img/binary_mask_sdh.tif

2.1 MB
Binary file not shown.

tests/test_cli.py

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,49 @@
77

88
def test_sdh_analysis():
99
result = runner.invoke(
10-
app, ["sdh-analysis", "sample_img/sample_sdh.jpg", "--cellpose-diameter", 80]
10+
app,
11+
[
12+
"sdh-analysis",
13+
"sample_img/sample_sdh.jpg",
14+
"--cellpose-diameter",
15+
80,
16+
"--mask-path",
17+
"sample_img/binary_mask_sdh.tif",
18+
],
1119
)
1220
assert result.exit_code == 0
1321
assert "Analysis completed !" in result.stdout
1422

1523

1624
def test_he_analysis():
17-
# Note that we need to confirm here, hence the extra input!
1825
result = runner.invoke(
19-
app, ["he-analysis", "sample_img/sample_he.jpg", "--cellpose-diameter", 80]
26+
app,
27+
[
28+
"he-analysis",
29+
"sample_img/sample_he.jpg",
30+
"--cellpose-diameter",
31+
80,
32+
"--mask-path",
33+
"sample_img/binary_mask.tif",
34+
],
35+
)
36+
assert result.exit_code == 0
37+
assert "Analysis completed !" in result.stdout
38+
39+
40+
def test_he_analysis_fluo():
41+
result = runner.invoke(
42+
app,
43+
[
44+
"he-analysis",
45+
"sample_img/cytoplasm.tif",
46+
"--cellpose-diameter",
47+
80,
48+
"--mask-path",
49+
"sample_img/binary_mask_fluo.tif",
50+
"--fluo-nuc",
51+
"sample_img/nuclei.tif",
52+
],
2053
)
2154
assert result.exit_code == 0
2255
assert "Analysis completed !" in result.stdout

0 commit comments

Comments
 (0)