| |
| """ |
| Developed by Nikhil Nageshwar Inturi |
| |
| This module provides MaskStitcher for stitching tiled .npy masks |
| back into full-size masks, one per original image stem. |
| """ |
|
|
| import re |
| from pathlib import Path |
| import numpy as np |
| import logging |
|
|
| class NPYMaskStitcher: |
| """ |
| Scans an input directory for files matching |
| <stem>_<row>_<col>.npy, groups them by stem, and |
| stitches each group into a single full-size mask. |
| """ |
|
|
| TILE_PATTERN = re.compile(r'^(?P<stem>.+)_(?P<row>\d+)_(?P<col>\d+)\.npy$') |
|
|
| def __init__(self, input_dir: Path, output_dir: Path) -> None: |
| self.input_dir = Path(input_dir) |
| self.output_dir = Path(output_dir) |
| self.logger = logging.getLogger(self.__class__.__name__) |
| self._setup_output_directory() |
|
|
| def _setup_output_directory(self) -> None: |
| try: |
| self.output_dir.mkdir(parents=True, exist_ok=True) |
| self.logger.debug(f"Output directory ready: {self.output_dir}") |
| except Exception as e: |
| self.logger.error(f"Could not create output directory {self.output_dir}: {e}") |
| raise |
|
|
| def stitch_all(self) -> None: |
| """ |
| Find all .npy tiles, group by stem, and stitch each group. |
| """ |
| all_files = list(self.input_dir.glob("*.npy")) |
| if not all_files: |
| self.logger.warning(f"No .npy files found in {self.input_dir}") |
| return |
|
|
| |
| stems = {} |
| for p in all_files: |
| m = self.TILE_PATTERN.match(p.name) |
| if not m: |
| self.logger.warning(f"Skipping unrecognized file name: {p.name}") |
| continue |
| stem = m.group("stem") |
| stems.setdefault(stem, []).append(p) |
|
|
| for stem, paths in stems.items(): |
| try: |
| self._stitch_stem(stem, paths) |
| self.logger.info(f"Stitched mask for '{stem}' → {stem}.npy") |
| except Exception: |
| self.logger.exception(f"Failed to stitch tiles for '{stem}'") |
|
|
| def _stitch_stem(self, stem: str, paths: list[Path]) -> None: |
| """ |
| Given all tile paths for a single stem, reconstruct the full mask. |
| """ |
| |
| mask_map = {} |
| rows = set() |
| cols = set() |
|
|
| for p in paths: |
| m = self.TILE_PATTERN.match(p.name) |
| row, col = int(m.group("row")), int(m.group("col")) |
| tile = np.load(p) |
| mask_map[(row, col)] = tile |
| rows.add(row) |
| cols.add(col) |
|
|
| all_rows = sorted(rows) |
| all_cols = sorted(cols) |
|
|
| |
| row_heights = {r: max(mask_map[(r, c)].shape[0] |
| for c in all_cols if (r, c) in mask_map) |
| for r in all_rows} |
| col_widths = {c: max(mask_map[(r, c)].shape[1] |
| for r in all_rows if (r, c) in mask_map) |
| for c in all_cols} |
|
|
| |
| row_offsets = {r: sum(row_heights[rr] for rr in all_rows if rr < r) |
| for r in all_rows} |
| col_offsets = {c: sum(col_widths[cc] for cc in all_cols if cc < c) |
| for c in all_cols} |
|
|
| |
| total_h = sum(row_heights.values()) |
| total_w = sum(col_widths.values()) |
|
|
| |
| full_mask = np.zeros((total_h, total_w), dtype=np.uint16) |
|
|
| |
| for (r, c), tile in mask_map.items(): |
| y0, x0 = row_offsets[r], col_offsets[c] |
| h, w = tile.shape |
| full_mask[y0:y0+h, x0:x0+w] = tile |
|
|
| |
| out_path = self.output_dir / f"{stem}.npy" |
| np.save(out_path, full_mask) |
|
|
|
|
|
|
|
|
| |
| |
| |
|
|
| |
| |
|
|
| |
| |
| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| |
| |
| |
|
|
| |
| |
| |
|
|
| |
| |
| |
| |
| |
| |
|
|
| |
| |
| |
|
|
| |
| |
| |
|
|
| |
| |
|
|
| |
| |
| |
| |
| |
| |
|
|
| |
| |