Source code for linumpy.mosaic.discovery

"""Discover OCT tile files in a directory and extract mosaic metadata."""

import re
from pathlib import Path

import numpy as np
from skimage.measure import label
from tqdm.auto import tqdm

from linumpy.microscope.oct import OCT


[docs] def get_largest_cc(segmentation: np.ndarray) -> np.ndarray: """Get the largest connected component in a binary image. Parameters ---------- segmentation : np.ndarray The binary image to process. Returns ------- np.ndarray The largest connected component. """ labels = label(segmentation) assert labels.max() != 0 # assume at least 1 CC largest_cc = labels == np.argmax(np.bincount(labels.flat)[1:]) + 1 return largest_cc
[docs] DEFAULT_TILE_FILE_PATTERN = r"tile_x(?P<x>\d+)_y(?P<y>\d+)_z(?P<z>\d+)"
[docs] def get_tiles_ids(directory: Path, z: int | None = None) -> tuple[list, list]: """Analyzes a directory and detects all the tiles in contains.""" input_directory = Path(directory) # Get a list of the input tiles tiles_to_process = f"*z{z:02d}" if z is not None else "tile_*" tiles = list(input_directory.rglob(tiles_to_process)) tiles = [t for t in tiles if t.name.startswith("tile_") and not t.is_file()] tile_ids = get_tiles_ids_from_list(tiles) return tiles, tile_ids
[docs] def get_tiles_ids_from_list(tiles_list: list, file_pattern: str = DEFAULT_TILE_FILE_PATTERN) -> list: """Extract tile (x, y, z) grid positions from a sorted list of tile paths.""" tiles_list.sort() # Get the tile positions tile_ids = [] n_tiles = len(tiles_list) for t in tqdm(tiles_list, desc="Extracting tile ids", total=n_tiles): # Extract the tile's mosaic position. match = re.match(file_pattern, t.name) assert match is not None mx = int(match.group("x")) my = int(match.group("y")) mz = int(match.group("z")) tile_ids.append((mx, my, mz)) return tile_ids
[docs] def get_mosaic_info(directory: Path, z: int, overlap_fraction: float = 0.2, use_stage_positions: bool = False) -> dict: """Return mosaic metadata for all tiles at a given z slice.""" # Get a list of the input tiles tiles, _tile_ids = get_tiles_ids(directory, z) # Get the tile positions (in pixel and mm) file_pattern = r"tile_x(?P<x>\d+)_y(?P<y>\d+)_z(?P<z>\d+)" tiles_positions_px = [] tiles_positions_mm = [] mosaic_tile_pos = [] # Progress bars overlap as the position is the same in all threads. Position is 1 to avoid overlap with outer loop. # No better solution has been found. oct_tile: OCT | None = None for t in tqdm(tiles, desc="Reading mosaic info", leave=False, position=1): oct_tile = OCT(t) # Extract the tile's mosaic position. match = re.match(file_pattern, t.name) assert match is not None mx = int(match.group("x")) my = int(match.group("y")) if oct_tile.position_available and use_stage_positions: x_mm, y_mm, _ = oct_tile.position else: # Compute the tile position in mm x_mm = oct_tile.dimension[0] * (1 - overlap_fraction) * mx y_mm = oct_tile.dimension[1] * (1 - overlap_fraction) * my x_px = int(np.floor(x_mm / oct_tile.resolution[0])) y_px = int(np.floor(y_mm / oct_tile.resolution[1])) mosaic_tile_pos.append((mx, my)) tiles_positions_mm.append((x_mm, y_mm)) tiles_positions_px.append((x_px, y_px)) assert oct_tile is not None # Compute the mosaic shape x_min = min([x for x, _ in tiles_positions_px]) y_min = min([y for _, y in tiles_positions_px]) x_max = max([x for x, _ in tiles_positions_px]) + oct_tile.shape[0] y_max = max([y for _, y in tiles_positions_px]) + oct_tile.shape[1] mosaic_nrows = x_max - x_min mosaic_ncols = y_max - y_min # Get the mosaic grid shape n_mx = len(np.unique([x[0] for x in mosaic_tile_pos])) n_my = len(np.unique([x[1] for x in mosaic_tile_pos])) # Get the mosaic limits in mm xmin_mm = np.min([p[0] for p in tiles_positions_mm]) - oct_tile.dimension[0] / 2 ymin_mm = np.min([p[1] for p in tiles_positions_mm]) - oct_tile.dimension[1] / 2 xmax_mm = np.max([p[0] for p in tiles_positions_mm]) + oct_tile.dimension[0] / 2 ymax_mm = np.max([p[1] for p in tiles_positions_mm]) + oct_tile.dimension[1] / 2 mosaic_center_mm = ((xmin_mm + xmax_mm) / 2, (ymin_mm + ymax_mm) / 2) mosaic_width_mm = xmax_mm - xmin_mm mosaic_height_mm = ymax_mm - ymin_mm info = { "tiles": tiles, "tiles_pos_px": tiles_positions_px, "tiles_pos_mm": tiles_positions_mm, "mosaic_tile_pos": mosaic_tile_pos, "mosaic_nrows": mosaic_nrows, "mosaic_ncols": mosaic_ncols, "mosaic_xmin_px": x_min, "mosaic_ymin_px": y_min, "mosaic_xmax_px": x_max, "mosaic_ymax_px": y_max, "mosaic_xmin_mm": xmin_mm, "mosaic_ymin_mm": ymin_mm, "mosaic_xmax_mm": xmax_mm, "mosaic_ymax_mm": ymax_mm, "mosaic_center_mm": mosaic_center_mm, "mosaic_width_mm": mosaic_width_mm, "mosaic_height_mm": mosaic_height_mm, "mosaic_grid_shape": (n_mx, n_my), "tile_shape_px": oct_tile.shape, "tile_shape_mm": oct_tile.dimension, "tile_resolution": oct_tile.resolution, } return info