22stitch
33=======
44
5- Contains functions to assemble tiled images into fullscale mosaics. Uses out-of-memory computation for the assembly of larger than memory image mosaics.
5+ Functions to assemble tiled images into fullscale mosaics.
6+ Uses out-of-memory computation for the assembly of larger than memory image mosaics.
67"""
78
89import os
910import shutil
1011import sys
1112from concurrent .futures import ThreadPoolExecutor
13+ from typing import TYPE_CHECKING
1214
1315import numpy as np
1416from alphabase .io .tempmmap import (
1517 create_empty_mmap ,
1618 mmap_array_from_path ,
1719 redefine_temp_location ,
1820)
21+ from ashlar .reg import EdgeAligner , Mosaic
1922from tqdm import tqdm
2023
2124from scportrait .io .daskmmap import dask_array_from_path
@@ -66,44 +69,25 @@ def __init__(
6669 overwrite : bool = False ,
6770 cache : str = None ,
6871 ) -> None :
69- """
70- Initialize the Stitcher object.
71-
72- Parameters:
73- -----------
74- input_dir : str
75- Directory containing the input image tiles.
76- slidename : str
77- Name of the slide.
78- outdir : str
79- Output directory to save the stitched mosaic.
80- stitching_channel : str
81- Name of the channel to be used for stitching.
82- pattern : str
83- File pattern to match the image tiles.
84- overlap : float, optional
85- Overlap between adjacent image tiles (default is 0.1).
86- max_shift : float, optional
87- Maximum allowed shift during alignment (default is 30).
88- filter_sigma : int, optional
89- Sigma value for Gaussian filter applied during alignment (default is 0).
90- do_intensity_rescale : bool or "full_image", optional
91- Flag to indicate whether to rescale image intensities (default is True). Alternatively, set to "full_image" to rescale the entire image.
92- rescale_range : tuple or dictionary, optional
93- If all channels should be rescaled to the same range pass a tuple with the percentiles for rescaleing (default is (1, 99)). Alternatively
94- a dictionary can be passed with the channel names as keys and the percentiles as values if each channel should be rescaled to a different range.
95- channel_order : list, optional
96- Order of channels in the generated output mosaic. If none (default value) the order of the channels is left unchanged.
97- reader_type : class, optional
98- Type of reader to use for reading image tiles (default is FilePatternReaderRescale).
99- orientation : dict, optional
100- Dictionary specifiying which dimensions of the slide to flip (default is {'flip_x': False, 'flip_y': True}).
101- plot_QC : bool, optional
102- Flag to indicate whether to plot quality control (QC) figures (default is True).
103- overwrite : bool, optional
104- Flag to indicate whether to overwrite the output directory if it already exists (default is False).
105- cache : str, optional
106- Directory to store temporary files during stitching (default is None). If set to none this directory will be created in the outdir.
72+ """Initialize the Stitcher object.
73+
74+ Args:
75+ input_dir: Directory containing the input image tiles
76+ slidename: Name of the slide
77+ outdir: Output directory to save the stitched mosaic
78+ stitching_channel: Name of the channel to be used for stitching
79+ pattern: File pattern to match the image tiles
80+ overlap: Overlap between adjacent image tiles
81+ max_shift: Maximum allowed shift during alignment
82+ filter_sigma: Sigma value for Gaussian filter applied during alignment
83+ do_intensity_rescale: Flag to rescale image intensities or "full_image" to rescale entire image
84+ rescale_range: Percentiles for intensity rescaling as tuple or dict with channel names as keys
85+ channel_order: Order of channels in output mosaic
86+ reader_type: Type of reader for image tiles
87+ orientation: Dict specifying dimensions to flip {'flip_x', 'flip_y'}
88+ plot_QC: Generate quality control figures
89+ overwrite: Overwrite existing output directory
90+ cache: Directory for temporary files
10791 """
10892 self ._lazy_imports ()
10993
@@ -151,9 +135,7 @@ def __init__(
151135 self .reader = None
152136
153137 def _lazy_imports (self ):
154- """
155- Import necessary packages for stitching.
156- """
138+ """Import necessary packages for stitching."""
157139 from ashlar import thumbnail
158140 from ashlar .reg import EdgeAligner , Mosaic
159141 from ashlar .scripts .ashlar import process_axis_flip
@@ -180,9 +162,7 @@ def __del__(self):
180162 self ._clear_cache ()
181163
182164 def _create_cache (self ):
183- """
184- Create a temporary cache directory for storing intermediate files during stitching.
185- """
165+ """Create a temporary cache directory for storing intermediate files during stitching."""
186166 if self .cache is None :
187167 TEMP_DIR_NAME = redefine_temp_location (self .outdir )
188168 else :
@@ -191,17 +171,13 @@ def _create_cache(self):
191171 self .TEMP_DIR_NAME = TEMP_DIR_NAME
192172
193173 def _clear_cache (self ):
194- """
195- Clear the temporary cache directory.
196- """
174+ """Clear the temporary cache directory."""
197175 if "TEMP_DIR_NAME" in self .__dict__ :
198176 if os .path .exists (self .TEMP_DIR_NAME ):
199177 shutil .rmtree (self .TEMP_DIR_NAME )
200178
201179 def _initialize_outdir (self ):
202- """
203- Initialize the output directory for saving the stitched mosaic.
204- """
180+ """Initialize the output directory for saving the stitched mosaic."""
205181 if not os .path .exists (self .outdir ):
206182 os .makedirs (self .outdir )
207183 print ("Output directory created at: " , self .outdir )
@@ -216,9 +192,7 @@ def _initialize_outdir(self):
216192 )
217193
218194 def _get_channel_info (self ):
219- """
220- Get information about the channels in the image tiles.
221- """
195+ """Get information about the channels in the image tiles."""
222196 # get channel names
223197 self .channel_lookup = self .reader .metadata .channel_map
224198 self .channel_names = list (self .reader .metadata .channel_map .values ())
@@ -240,9 +214,7 @@ def get_stitching_information(self):
240214 print ("Output will be written to:" , self .outdir )
241215
242216 def _setup_rescaling (self ):
243- """
244- Setup image rescaling based on the specified rescale_range.
245- """
217+ """Setup image rescaling based on the specified rescale_range."""
246218 # set up rescaling
247219 if self .do_intensity_rescale :
248220 self .reader .no_rescale_channel = []
@@ -263,7 +235,8 @@ def _setup_rescaling(self):
263235
264236 if len (missing_channels ) > 0 :
265237 Warning (
266- "The rescale_range dictionary does not contain all channels in the experiment. This may lead to unexpected results. For the missing channels rescaling will be turned off."
238+ "The rescale_range dictionary does not contain all channels in the experiment."
239+ "This may lead to unexpected results. For the missing channels rescaling will be turned off."
267240 )
268241
269242 missing_channels = set .difference (self .channel_names , rescale_channels )
@@ -285,9 +258,7 @@ def _setup_rescaling(self):
285258 self .reader .rescale_range = None
286259
287260 def _reorder_channels (self ):
288- """
289- Reorder the channels in the mosaic based on the specified channel_order.
290- """
261+ """Reorder the channels in the mosaic based on the specified channel_order."""
291262 if self .channel_order is None :
292263 self .channels = self .channels
293264 else :
@@ -301,9 +272,7 @@ def _reorder_channels(self):
301272 self .channels = channels
302273
303274 def _initialize_reader (self ):
304- """
305- Initialize the reader for reading image tiles.
306- """
275+ """Initialize the reader for reading image tiles."""
307276 if self .reader_type == self .FilePatternReaderRescale :
308277 self .reader = self .reader_type (
309278 self .input_dir ,
@@ -326,23 +295,19 @@ def _initialize_reader(self):
326295 self ._setup_rescaling ()
327296
328297 def save_positions (self ):
329- """
330- Save the positions of the aligned image tiles.
331- """
298+ """Save the positions of the aligned image tiles."""
332299 positions = self .aligner .positions
333300 np .savetxt (
334301 os .path .join (self .outdir , self .slidename + "_tile_positions.tsv" ),
335302 positions ,
336303 delimiter = "\t " ,
337304 )
338305
339- def generate_thumbnail (self , scale = 0.05 ):
340- """
341- Generate a thumbnail of the stitched mosaic.
306+ def generate_thumbnail (self , scale : float | None = 0.05 ) -> None :
307+ """Generate a thumbnail of the stitched mosaic.
342308
343309 Args:
344- scale (float, optional): Scale factor for the thumbnail. Defaults to 0.05.
345-
310+ scale: Scale factor for the thumbnail.
346311 """
347312 self ._initialize_reader ()
348313 self .thumbnail = self .ashlar_thumbnail .make_thumbnail (
@@ -355,7 +320,7 @@ def generate_thumbnail(self, scale=0.05):
355320 rescale_range = {k : self .rescale_range for k in self .channel_names }
356321 rescale = True
357322 elif type (self .rescale_range ) is dict :
358- rescale_range = self .rescale_range [self .stitching_channel ]
323+ rescale_range = self .rescale_range [self .stitching_channel ] # type: ignore
359324 rescale = True
360325 else :
361326 if not self .do_intensity_rescale :
@@ -365,12 +330,11 @@ def generate_thumbnail(self, scale=0.05):
365330 if rescale :
366331 self .thumbnail = rescale_image (self .thumbnail , rescale_range )
367332
368- def _initialize_aligner (self ):
369- """
370- Initialize the aligner for aligning the image tiles.
333+ def _initialize_aligner (self ) -> EdgeAligner :
334+ """Initialize the aligner for aligning the image tiles.
371335
372336 Returns:
373- aligner (EdgeAligner): Initialized EdgeAligner object.
337+ Initialized EdgeAligner object.
374338 """
375339 aligner = self .ashlar_EdgeAligner (
376340 self .reader ,
@@ -383,16 +347,12 @@ def _initialize_aligner(self):
383347 return aligner
384348
385349 def plot_qc (self ):
386- """
387- Plot quality control (QC) figures for the alignment.
388- """
350+ """Plot quality control (QC) figures for the alignment."""
389351 plot_edge_scatter (self .aligner , self .outdir )
390352 plot_edge_quality (self .aligner , self .outdir )
391353
392354 def _perform_alignment (self ):
393- """
394- Perform alignment of the image tiles.
395- """
355+ """Perform alignment of the image tiles."""
396356 # intitialize reader for getting individual image tiles
397357 self ._initialize_reader ()
398358
@@ -410,12 +370,11 @@ def _perform_alignment(self):
410370
411371 print ("Alignment complete." )
412372
413- def _initialize_mosaic (self ):
414- """
415- Initialize the mosaic object for assembling the image tiles.
373+ def _initialize_mosaic (self ) -> Mosaic :
374+ """Initialize the mosaic object for assembling the image tiles.
416375
417376 Returns:
418- mosaic (Mosaic): Initialized Mosaic object.
377+ Initialized Mosaic object.
419378 """
420379 mosaic = self .ashlar_Mosaic (
421380 self .aligner ,
@@ -426,9 +385,7 @@ def _initialize_mosaic(self):
426385 return mosaic
427386
428387 def _assemble_mosaic (self ):
429- """
430- Assemble the image tiles into a mosaic.
431- """
388+ """Assemble the image tiles into a mosaic."""
432389 # get dimensions of assembled final mosaic
433390 x , y = self .mosaic .shape
434391 shape = (self .n_channels , x , y )
@@ -472,18 +429,16 @@ def _generate_mosaic(self):
472429 self ._assemble_mosaic ()
473430
474431 def stitch (self ):
475- """
476- Generate the stitched mosaic.
477- """
432+ """Generate the stitched mosaic."""
478433 self ._perform_alignment ()
479434 self ._generate_mosaic ()
480435
481436 def write_tif (self , export_xml : bool = True ) -> None :
482- """
483- Write the assembled mosaic as TIFF files.
437+ """Write the assembled mosaic as TIFF files.
484438
485439 Args:
486- export_xml (bool, optional): Flag to indicate whether to export an XML file for the TIFF files (default is True). This XML file is compatible with loading the generated TIFF files into BIAS.
440+ export_xml: Whether to export an XML file for the TIFF files.
441+ This XML file is compatible with loading the generated TIFF files into BIAS.
487442
488443 Returns:
489444 The assembled mosaic are written to file as TIFF files in the specified output directory.
@@ -497,16 +452,18 @@ def write_tif(self, export_xml: bool = True) -> None:
497452 if export_xml :
498453 write_xml (filenames , self .channel_names , self .slidename )
499454
500- def write_ome_zarr (self , downscaling_size = 4 , n_downscaling_layers = 4 , chunk_size = (1 , 1024 , 1024 )):
455+ def write_ome_zarr (
456+ self ,
457+ downscaling_size : int = 4 ,
458+ n_downscaling_layers : int = 4 ,
459+ chunk_size : tuple [int , int , int ] = (1 , 1024 , 1024 ),
460+ ) -> None :
501461 """Write the assembled mosaic as an OME-Zarr file.
502462
503463 Args:
504- downscaling_size (int, optional): Downscaling factor for generating lower resolution layers (default is 4).
505- n_downscaling_layers (int, optional): Number of downscaling layers to generate (default is 4).
506- chunk_size (tuple, optional): Chunk size for the generated OME-Zarr file (default is (1, 1024, 1024)).
507-
508- Returns:
509- None
464+ downscaling_size: Downscaling factor for generating lower resolution layers (default is 4).
465+ n_downscaling_layers: Number of downscaling layers to generate (default is 4).
466+ chunk_size: Chunk size for the generated OME-Zarr file (default is (1, 1024, 1024)).
510467 """
511468 filepath = os .path .join (self .outdir , f"{ self .slidename } .ome.zarr" )
512469
@@ -522,9 +479,7 @@ def write_ome_zarr(self, downscaling_size=4, n_downscaling_layers=4, chunk_size=
522479 )
523480
524481 def write_thumbnail (self ):
525- """
526- Write the generated thumbnail as a TIFF file.
527- """
482+ """Write the generated thumbnail as a TIFF file."""
528483 # calculate thumbnail if this has not already been done
529484 if "thumbnail" not in self .__dict__ :
530485 self .generate_thumbnail ()
@@ -535,13 +490,12 @@ def write_thumbnail(self):
535490 )
536491 write_tif (filename , self .thumbnail )
537492
538- def write_spatialdata (self , scale_factors = None ):
539- """
540- Write the assembled mosaic as a SpatialData object.
493+ def write_spatialdata (self , scale_factors : list [int ] | None = None ) -> None :
494+ """Write the assembled mosaic as a SpatialData object.
541495
542496 Args:
543- scale_factors (list, optional) : List of scale factors for the generated SpatialData object.
544- Default is [2, 4, 8]. The scale factors are used to generate downsampled versions of the
497+ scale_factors: List of scale factors for the generated SpatialData object.
498+ Defaults to [2, 4, 8]. The scale factors are used to generate downsampled versions of the
545499 image for faster visualization at lower resolutions.
546500 """
547501 if scale_factors is None :
@@ -559,8 +513,7 @@ def write_spatialdata(self, scale_factors=None):
559513
560514
561515class ParallelStitcher (Stitcher ):
562- """
563- Class for parallel stitching of image tiles and generating a mosaic. For applicable steps multi-threading is used for faster processing.
516+ """Class for parallel stitching of image tiles and generating a mosaic. For applicable steps multi-threading is used for faster processing.
564517
565518 Args:
566519 input_dir (str): Directory containing the input image tiles.
@@ -637,8 +590,7 @@ def __init__(
637590 self .threads = threads
638591
639592 def _initialize_aligner (self ):
640- """
641- Initialize the aligner for aligning the image tiles.
593+ """Initialize the aligner for aligning the image tiles.
642594
643595 Returns:
644596 aligner (ParallelEdgeAligner): Initialized ParallelEdgeAligner object.
@@ -707,11 +659,12 @@ def _assemble_mosaic(self):
707659 # conver to dask array
708660 self .assembled_mosaic = dask_array_from_path (hdf5_path )
709661
710- def write_tif_parallel (self , export_xml = True ):
662+ def write_tif_parallel (self , export_xml : bool = True ):
711663 """Parallelized version of the write_tif method to write the assembled mosaic as TIFF files.
712664
713665 Args:
714- export_xml (bool, optional): Flag to indicate whether to export an XML file for the TIFF files (default is True). This XML file is compatible with loading the generarted TIFF files into BIAS.
666+ export_xml: Whether to export an XML file for the TIFF files.
667+ This XML file is compatible with loading the generarted TIFF files into BIAS.
715668
716669 """
717670
0 commit comments