11import os
2- from functools import partial
2+ import warnings
33from glob import glob
44from tqdm import tqdm
55from pathlib import Path
6+ from functools import partial
67from typing import Dict , List , Optional , Union , Tuple
78
89import numpy as np
@@ -95,7 +96,7 @@ def automatic_tracking(
9596 segmenter: The automatic instance segmentation class.
9697 input_path: input_path: The input image file(s). Can either be a single image file (e.g. tif or png),
9798 or a container file (e.g. hdf5 or zarr).
98- output_path: The output path where the instance segmentations will be saved.
99+ output_path: The folder where the tracking outputs will be saved in CTC format .
99100 embedding_path: The path where the embeddings are cached already / will be saved.
100101 key: The key to the input file. This is needed for container files (eg. hdf5 or zarr)
101102 or to load several images as 3d volume. Provide a glob patterm, eg. "*.tif", for this case.
@@ -111,11 +112,9 @@ def automatic_tracking(
111112 generate_kwargs: optional keyword arguments for the generate function of the AMG or AIS class.
112113
113114 Returns:
115+ The tracking result as a timeseries, where each object is labeled by its track id.
116+ The lineages representing cell divisions, stored as a dictionary.
114117 """
115- if output_path is not None :
116- # TODO implement saving tracking results in CTC format and use it to save the result here.
117- raise NotImplementedError ("Saving the tracking result to file is currently not supported." )
118-
119118 # Load the input image file.
120119 if isinstance (input_path , np .ndarray ):
121120 image_data = input_path
@@ -142,7 +141,8 @@ def automatic_tracking(
142141 halo = halo ,
143142 verbose = verbose ,
144143 batch_size = batch_size ,
145- return_image_embeddings = True ,
144+ return_embeddings = True ,
145+ output_folder = output_path ,
146146 ** generate_kwargs ,
147147 )
148148
@@ -335,12 +335,6 @@ def _get_inputs_from_paths(paths, pattern):
335335 return fpaths
336336
337337
338- def _has_extension (fpath : Union [os .PathLike , str ]) -> bool :
339- "Returns whether the provided path has an extension or not."
340- breakpoint ()
341- return bool (os .path .splitext (fpath )[1 ])
342-
343-
344338def main ():
345339 """@private"""
346340 import argparse
@@ -349,23 +343,34 @@ def main():
349343 available_models = ", " .join (available_models )
350344
351345 parser = argparse .ArgumentParser (
352- description = "Run automatic segmentation for an image using either automatic instance segmentation (AIS) \n "
353- "or automatic mask generation (AMG). In addition to the arguments explained below,\n "
346+ description = "Run automatic segmentation or tracking for 2d, 3d or timeseries data.\n "
347+ "Either a single input file or multiple input files are supported. You can specify multiple files "
348+ "by either providing multiple filepaths to the '--i/--input_paths' argument, or by providing an argument "
349+ "to '--pattern' to use a wildcard pattern ('*') for selecting multiple files.\n "
350+ "NOTE: for automatic 3d segmentation or tracking the data has to be stored as volume / timeseries, "
351+ "stacking individual tif images is not supported.\n "
352+ "Segmentation is performed using one of the two modes supported by micro_sam: \n "
353+ "automatic instance segmentation (AIS) or automatic mask generation (AMG).\n "
354+ "In addition to the options listed below, "
354355 "you can also passed additional arguments for these two segmentation modes:\n "
355356 "For AIS: '--center_distance_threshold', '--boundary_distance_threshold' and other arguments of `InstanceSegmentationWithDecoder.generate`." # noqa
356357 "For AMG: '--pred_iou_thresh', '--stability_score_thresh' and other arguments of `AutomaticMaskGenerator.generate`." # noqa
357358 )
358359 parser .add_argument (
359360 "-i" , "--input_path" , required = True , type = str , nargs = "+" ,
360- help = "The filepath to the image data. Supports all data types that can be read by imageio (e.g. tif, png, ...) "
361+ help = "The filepath(s) to the image data. Supports all data types that can be read by imageio (e.g. tif, png, ...) " # noqa
361362 "or elf.io.open_file (e.g. hdf5, zarr, mrc). For the latter you also need to pass the 'key' parameter."
362363 )
363364 parser .add_argument (
364365 "-o" , "--output_path" , required = True , type = str ,
365- help = "The filepath to store the instance segmentation. The current support stores segmentation in a 'tif' file."
366+ help = "The filepath to store the results. If multiple inputs are provied, "
367+ "this should be a folder. For a single image, you should provide the path to a tif file for the output segmentation." # noqa
368+ "NOTE: Segmentation results are stored as tif files, tracking results in the CTC fil format ."
366369 )
367370 parser .add_argument (
368- "-e" , "--embedding_path" , default = None , type = str , help = "The path where the embeddings will be saved."
371+ "-e" , "--embedding_path" , default = None , type = str ,
372+ help = "An optional path where the embeddings will be saved. If multiple inputs are provided, "
373+ "this should be a folder. Otherwise you can store embeddings in single zarr file."
369374 )
370375 parser .add_argument (
371376 "--pattern" , type = str , help = "Pattern / wildcard for selecting files in a folder. To select all files use '*'."
@@ -411,8 +416,8 @@ def main():
411416 "By default, computes the image embeddings for one tile / z-plane at a time."
412417 )
413418 parser .add_argument (
414- "--tracking" , action = "store_true" , help = "Run tracking instead of instance segmentation. "
415- "Only supported for timeseries inputs. ."
419+ "--tracking" , action = "store_true" , help = "Run automatic tracking instead of instance segmentation. "
420+ "NOTE: It is only supported for timeseries inputs."
416421 )
417422 parser .add_argument (
418423 "-v" , "--verbose" , action = "store_true" , help = "Whether to allow verbosity of outputs."
@@ -473,34 +478,51 @@ def _convert_argval(value):
473478 )
474479
475480 # Run automatic segmentation per image.
476- for path in tqdm (input_paths , desc = "Run automatic segmentation" ):
477- if has_one_input : # if we have one image only.
478- _output_fpath = str (Path (output_path ).with_suffix (".tif" ))
479- _embedding_fpath = embedding_path
480-
481- else : # if we have multiple image, we need to make the other target filepaths compatible.
482- # Let's check for 'embedding_path'.
483- _embedding_fpath = embedding_path
484- if embedding_path :
485- if _has_extension (embedding_path ): # in this case, use filename as addl. suffix to provided path.
486- _embedding_fpath = str (Path (embedding_path ).with_suffix (".zarr" ))
487- _embedding_fpath = _embedding_fpath .replace (".zarr" , f"_{ Path (path ).stem } .zarr" )
488- else : # otherwise, for directory, use image filename for multiple images.
489- os .makedirs (embedding_path , exist_ok = True )
490- _embedding_fpath = os .path .join (embedding_path , Path (os .path .basename (path )).with_suffix (".zarr" ))
491-
492- # Next, let's check for output file to store segmentation.
493- if _has_extension (output_path ): # in this case, use filename as addl. suffix to provided path.
494- _output_fpath = str (Path (output_path ).with_suffix (".tif" ))
495- _output_fpath = _output_fpath .replace (".tif" , f"_{ Path (path ).stem } .tif" )
496- else : # otherwise, for directory, use image filename for multiple images.
497- os .makedirs (output_path , exist_ok = True )
498- _output_fpath = os .path .join (output_path , Path (os .path .basename (path )).with_suffix (".tif" ))
481+ for input_path in tqdm (input_paths , desc = "Run automatic " + ("tracking" if args .tracking else "segmentation" )):
482+ if has_one_input : # When we have only one image / volume.
483+ _embedding_fpath = embedding_path # Either folder or zarr file, would work for both.
484+
485+ output_fdir = os .path .splitext (output_path )[0 ]
486+ os .makedirs (output_fdir , exist_ok = True )
487+
488+ # For tracking, we ensure that the output path is a folder,
489+ # i.e. does not have an extension. We throw a warning if the user provided an extension.
490+ if args .tracking :
491+ if os .path .splitext (output_path )[- 1 ]:
492+ warnings .warn (
493+ f"The output folder has an extension '{ os .path .splitext (output_path )[- 1 ]} '. "
494+ "We remove it and treat it as a folder to store tracking outputs in CTC format."
495+ )
496+ _output_fpath = output_fdir
497+ else : # Otherwise, we can store outputs for user directly in the provided filepath, ensuring extension .tif
498+ _output_fpath = f"{ output_fdir } .tif"
499+
500+ else : # When we have multiple images.
501+ # Get the input filename, without the extension.
502+ input_name = str (Path (input_path ).stem )
503+
504+ # Let's check the 'embedding_path'.
505+ if embedding_path is None : # For computing embeddings on-the-fly, we don't care about the path logic.
506+ _embedding_fpath = embedding_path
507+ else : # Otherwise, store each embeddings inside a folder.
508+ embedding_folder = os .path .splitext (embedding_path )[0 ] # Treat the provided embedding path as folder.
509+ os .makedirs (embedding_folder , exist_ok = True )
510+ _embedding_fpath = os .path .join (embedding_folder , f"{ input_name } .zarr" ) # Create each embedding file.
511+
512+ # Get the output folder name.
513+ output_folder = os .path .splitext (output_path )[0 ]
514+ os .makedirs (output_folder , exist_ok = True )
515+
516+ # Next, let's check for output file to store segmentation (or tracks).
517+ if args .tracking : # For tracking, we store CTC outputs in subfolders, with input_name as folder.
518+ _output_fpath = os .path .join (output_folder , input_name )
519+ else : # Otherwise, store each result inside a folder.
520+ _output_fpath = os .path .join (output_folder , f"{ input_name } .tif" )
499521
500522 instance_seg_function (
501523 predictor = predictor ,
502524 segmenter = segmenter ,
503- input_path = path ,
525+ input_path = input_path ,
504526 output_path = _output_fpath ,
505527 embedding_path = _embedding_fpath ,
506528 key = args .key ,
0 commit comments