Skip to content

Commit 0c628da

Browse files
Implement mobie export and combined segmentation workflow
1 parent 0c08cbf commit 0c628da

File tree

5 files changed

+242
-5
lines changed

5 files changed

+242
-5
lines changed

flamingo_tools/data_conversion.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,10 +134,16 @@ def flamingo_filename_parser(file_path, name_mapping):
134134
illumination_mapping = name_mapping.get("illumination", {})
135135
attributes["illumination"] = {"id": illumination, "name": illumination_mapping.get(illumination, str(illumination))}
136136

137+
# Extract D. TODO what is this?
138+
match = re.search(r'_D(\d+)_', filename)
139+
D = int(match.group(1)) if match else 0
140+
D_mapping = name_mapping.get("D", {})
141+
attributes["D"] = {"id": D, "name": D_mapping.get(D, str(D))}
142+
137143
# BDV also supports an angle attribute, but it does not seem to be stored in the filename
138144
# "angle": {"id": 0, "name": "0"}
139145

140-
attribute_id = f"c{channel}-t{tile}-i{illumination}"
146+
attribute_id = f"c{channel}-t{tile}-i{illumination}-d{D}"
141147
return timepoint, attributes, attribute_id
142148

143149

@@ -242,13 +248,13 @@ def convert_lightsheet_to_bdv(
242248
else: # We have metadata and read it.
243249
resolution, unit, tile_transformation = read_metadata_flamingo(metadata_file, offset)
244250

251+
print(f"Converting tp={timepoint}, channel={attributes['channel']}, tile={attributes['tile']}")
245252
try:
246253
data = tifffile.memmap(file_path, mode="r")
247254
except ValueError:
248255
print(f"Could not memmap the data from {file_path}. Fall back to load it into memory.")
249256
data = tifffile.imread(file_path)
250257

251-
print(f"Converting tp={timepoint}, channel={attributes['channel']}, tile={attributes['tile']}")
252258
if scale_factors is None:
253259
scale_factors = derive_scale_factors(data.shape)
254260

flamingo_tools/mobie.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import os
2+
import tempfile
3+
from typing import Tuple
4+
5+
from mobie import add_bdv_image, add_segmentation
6+
from mobie.metadata.dataset_metadata import read_dataset_metadata
7+
8+
9+
# TODO refactor to mobie utils
10+
def _source_exists(mobie_project, mobie_dataset, source_name):
11+
dataset_folder = os.path.join(mobie_project, mobie_dataset)
12+
metadata = read_dataset_metadata(dataset_folder)
13+
sources = metadata.get("sources", {})
14+
return source_name in sources
15+
16+
17+
def add_raw_to_mobie(
18+
mobie_project: str,
19+
mobie_dataset: str,
20+
source_name: str,
21+
xml_path: str,
22+
skip_existing: bool = True,
23+
setup_id: int = 0,
24+
):
25+
"""
26+
"""
27+
# Check if we have converted this data already.
28+
have_source = _source_exists(mobie_project, mobie_dataset, source_name)
29+
if have_source and skip_existing:
30+
print(f"Source {source_name} already exists in {mobie_project}:{mobie_dataset}.")
31+
print("Conversion to mobie will be skipped.")
32+
return
33+
elif have_source:
34+
raise NotImplementedError
35+
36+
with tempfile.TemporaryDirectory() as tmpdir:
37+
add_bdv_image(
38+
xml_path=xml_path,
39+
root=mobie_project,
40+
dataset_name=mobie_dataset,
41+
image_name=source_name,
42+
tmp_folder=tmpdir,
43+
file_format="bdv.n5",
44+
setup_ids=[setup_id],
45+
)
46+
47+
48+
def add_segmentation_to_mobie(
49+
mobie_project: str,
50+
mobie_dataset: str,
51+
source_name: str,
52+
segmentation_path: str,
53+
segmentation_key: str,
54+
resolution: Tuple[int, int, int],
55+
unit: str,
56+
scale_factors: Tuple[Tuple[int, int, int]],
57+
chunks: Tuple[int, int, int],
58+
skip_existing: bool = True,
59+
):
60+
# Check if we have converted this data already.
61+
have_source = _source_exists(mobie_project, mobie_dataset, source_name)
62+
if have_source and skip_existing:
63+
print(f"Source {source_name} already exists in {mobie_project}:{mobie_dataset}.")
64+
print("Conversion to mobie will be skipped.")
65+
return
66+
elif have_source:
67+
raise NotImplementedError
68+
69+
with tempfile.TemporaryDirectory() as tmpdir:
70+
add_segmentation(
71+
input_path=segmentation_path, input_key=segmentation_key,
72+
root=mobie_project, dataset_name=mobie_dataset,
73+
segmentation_name=source_name,
74+
resolution=resolution, scale_factors=scale_factors,
75+
chunks=chunks, file_format="bdv.n5",
76+
tmp_folder=tmpdir
77+
)

scripts/data_transfer/README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,7 @@ $ smbclient \\\\wfs-medizin.top.gwdg.de\\ukon-all\$\\ukon100 -U GWDG\\pape41"
1818
- mget *
1919
- Copy this to HLRN by logging into it and running
2020
```
21-
$ rsync -e "ssh -i ~/.ssh/id_rsa_hlrn" -avz [email protected]:/scratch1/projects/cca/data/mose
22-
r/<NAME> /mnt/lustre-emmy-hdd/projects/nim00007/data/moser/lightsheet/volumes/<NAME>
21+
$ rsync -e "ssh -i ~/.ssh/id_rsa_hlrn" -avz [email protected]:/scratch1/projects/cca/data/moser/<NAME> /mnt/lustre-emmy-hdd/projects/nim00007/data/moser/lightsheet/volumes/<NAME>
2322
```
2423
- Remove on SCC
2524

@@ -28,9 +27,11 @@ r/<NAME> /mnt/lustre-emmy-hdd/projects/nim00007/data/moser/lightsheet/volumes/<N
2827
- UKON100\archiv\imaging\Lightsheet\Huiskengroup_CTLSM\2024\M171_2R_converted_n5
2928
- unclear what the converted data is
3029
- UKON100\archiv\imaging\Lightsheet\Huiskengroup_CTLSM\2024\155_1L_converted_n5\BDVexport.n5
31-
- Copied to SCC, need to rsync.
30+
- Copied to SCC, rsync in progress.
3231
- UKON100\archiv\imaging\Lightsheet\Huiskengroup_CTLSM\2024\MLR151_2R_converted_n5
32+
- unclear what the converted data is
3333
- UKON100\archiv\imaging\Lightsheet\Huiskengroup_CTLSM\2024\G11_1L_converted_n5
34+
- unclear what the converted data is
3435

3536
## Improvements
3637

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
import argparse
2+
import os
3+
from shutil import rmtree
4+
5+
import pybdv.metadata as bdv_metadata
6+
import torch
7+
import z5py
8+
9+
from flamingo_tools.segmentation import run_unet_prediction, filter_isolated_objects
10+
from flamingo_tools.mobie import add_raw_to_mobie, add_segmentation_to_mobie
11+
12+
MOBIE_ROOT = "/mnt/lustre-emmy-hdd/projects/nim00007/data/moser/lightsheet/mobie"
13+
14+
15+
def postprocess_seg(output_folder):
16+
print("Run segmentation postprocessing ...")
17+
seg_path = os.path.join(output_folder, "segmentation.zarr")
18+
seg_key = "segmentation"
19+
20+
with z5py.File(seg_path, "r") as f:
21+
segmentation = f[seg_key][:]
22+
23+
seg_filtered, n_pre, n_post = filter_isolated_objects(segmentation)
24+
25+
with z5py.File(seg_path, "a") as f:
26+
chunks = f[seg_key].chunks
27+
f.create_dataset(
28+
"segmentation_postprocessed", data=seg_filtered, compression="gzip",
29+
chunks=chunks, dtype=seg_filtered.dtype
30+
)
31+
32+
33+
def export_to_mobie(xml_path, segmentation_folder, scale, mobie_dataset, chunks):
34+
# Add to mobie:
35+
36+
# - raw data (if not yet present)
37+
add_raw_to_mobie(
38+
mobie_project=MOBIE_ROOT,
39+
mobie_dataset=mobie_dataset,
40+
source_name="pv-channel",
41+
xml_path=xml_path,
42+
setup_id=0,
43+
)
44+
45+
# TODO enable passing extra channel names
46+
# - additional channels
47+
setup_ids = bdv_metadata.get_setup_ids(xml_path)
48+
if len(setup_ids) > 1:
49+
extra_channel_names = ["gfp_channel", "myo_channel"]
50+
for i, setup_id in enumerate(setup_ids[1:]):
51+
add_raw_to_mobie(
52+
mobie_project=MOBIE_ROOT,
53+
mobie_dataset=mobie_dataset,
54+
source_name=extra_channel_names[i],
55+
xml_path=xml_path,
56+
setup_id=setup_id
57+
)
58+
59+
# - segmentation and post-processed segmentation
60+
seg_path = os.path.join(segmentation_folder, "segmentation.zarr")
61+
seg_resolution = bdv_metadata.get_resolution(xml_path, setup_id=0)
62+
if scale == 1:
63+
seg_resolution = [2 * res for res in seg_resolution]
64+
unit = bdv_metadata.get_unit(xml_path, setup_id=0)
65+
66+
seg_key = "segmentation"
67+
seg_name = "nuclei_fullscale" if scale == 0 else "nuclei_downscaled"
68+
add_segmentation_to_mobie(
69+
mobie_project=MOBIE_ROOT,
70+
mobie_dataset=mobie_dataset,
71+
source_name=seg_name,
72+
segmentation_path=seg_path,
73+
segmentation_key=seg_key,
74+
resolution=seg_resolution,
75+
unit=unit,
76+
scale_factors=4*[[2, 2, 2]],
77+
chunks=chunks,
78+
)
79+
80+
seg_key = "segmentation_postprocessed"
81+
seg_name += "_postprocessed"
82+
add_segmentation_to_mobie(
83+
mobie_project=MOBIE_ROOT,
84+
mobie_dataset=mobie_dataset,
85+
source_name=seg_name,
86+
segmentation_path=seg_path,
87+
segmentation_key=seg_key,
88+
resolution=seg_resolution,
89+
unit=unit,
90+
scale_factors=4*[[2, 2, 2]],
91+
chunks=chunks,
92+
)
93+
94+
95+
def main():
96+
parser = argparse.ArgumentParser()
97+
parser.add_argument("-i", "--input", required=True)
98+
parser.add_argument("-o", "--output_folder", required=True)
99+
parser.add_argument("-s", "--scale", required=True, type=int)
100+
parser.add_argument("-m", "--mobie_dataset", required=True)
101+
parser.add_argument("--model")
102+
103+
args = parser.parse_args()
104+
105+
scale = args.scale
106+
if scale == 0:
107+
min_size = 1000
108+
elif scale == 1:
109+
min_size = 250
110+
else:
111+
raise ValueError
112+
113+
xml_path = args.input
114+
assert os.path.splitext(xml_path)[1] == ".xml"
115+
input_path = bdv_metadata.get_data_path(xml_path, return_absolute_path=True)
116+
117+
# TODO need to make sure that PV is always setup 0
118+
input_key = f"setup0/timepoint0/s{scale}"
119+
120+
have_cuda = torch.cuda.is_available()
121+
chunks = z5py.File(input_path, "r")[input_key].chunks
122+
block_shape = tuple([2 * ch for ch in chunks]) if have_cuda else tuple(chunks)
123+
halo = (16, 64, 64) if have_cuda else (8, 32, 32)
124+
125+
if args.model is not None:
126+
model = args.model
127+
else:
128+
if scale == 0:
129+
model = "../training/checkpoints/cochlea_distance_unet"
130+
else:
131+
model = "../training/checkpoints/cochlea_distance_unet-train-downsampled"
132+
133+
run_unet_prediction(
134+
input_path, input_key, args.output_folder, model,
135+
scale=None, min_size=min_size,
136+
block_shape=block_shape, halo=halo,
137+
)
138+
139+
postprocess_seg(args.output_folder)
140+
141+
export_to_mobie(xml_path, args.output_folder, scale, args.mobie_dataset, chunks)
142+
143+
# clean up: remove segmentation folders
144+
print("Cleaning up intermediate segmentation results")
145+
print("This may take a while, but everything else is done.")
146+
print("You can check the results in the MoBIE project already at:")
147+
print(f"{MOBIE_ROOT}:{args.mobie_dataset}")
148+
rmtree(args.output_folder)
149+
150+
151+
if __name__ == "__main__":
152+
main()

scripts/prediction/upload_to_s3.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# TODO find suitable bucket

0 commit comments

Comments
 (0)