Skip to content

Commit eef85cf

Browse files
committed
types
Signed-off-by: Lukas Heumos <lukas.heumos@posteo.net>
1 parent b717064 commit eef85cf

File tree

2 files changed

+74
-118
lines changed

2 files changed

+74
-118
lines changed

src/scportrait/tools/stitch/_stitch.py

Lines changed: 70 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,23 @@
22
stitch
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

89
import os
910
import shutil
1011
import sys
1112
from concurrent.futures import ThreadPoolExecutor
13+
from typing import TYPE_CHECKING
1214

1315
import numpy as np
1416
from 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
1922
from tqdm import tqdm
2023

2124
from 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

561515
class 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

Comments
 (0)