Source code for sigima.proc.image.preprocessing

# -*- coding: utf-8 -*-
"""
Image preprocessing functions.

This module consolidates preprocessing functions that were previously scattered
across different modules (exposure, geometry, fourier). All functions in this
module operate on high-level ImageObj objects and use parameter classes from
the sigima.proc framework.
"""

from __future__ import annotations

import guidata.dataset as gds
import numpy as np

import sigima.enums
import sigima.tools.image
from sigima.config import _
from sigima.enums import PadLocation2D
from sigima.objects.image import ImageObj
from sigima.proc.decorator import computation_function
from sigima.proc.image.base import dst_1_to_1

__all__ = [
    "BinningParam",
    "ZeroPadding2DParam",
    "binning",
    "zero_padding",
]


[docs] class BinningParam(gds.DataSet): """Binning parameters.""" sx = gds.IntItem( _("Cluster size (X)"), default=2, min=2, help=_("Number of adjacent pixels to be combined together along X-axis."), ) sy = gds.IntItem( _("Cluster size (Y)"), default=2, min=2, help=_("Number of adjacent pixels to be combined together along Y-axis."), ) operation = gds.ChoiceItem( _("Operation"), sigima.enums.BinningOperation, default=sigima.enums.BinningOperation.SUM, ) dtypes = ["dtype"] + ImageObj.get_valid_dtypenames() dtype_str = gds.ChoiceItem( _("Data type"), list(zip(dtypes, dtypes)), help=_("Output image data type."), ) change_pixel_size = gds.BoolItem( _("Change pixel size"), default=True, help=_( "If checked, pixel size is updated according to binning factors. " "Users who prefer to work with pixel coordinates may want to uncheck this." ), )
[docs] @computation_function() def binning(src: ImageObj, p: BinningParam) -> ImageObj: """Binning: image pixel binning (or aggregation). Depending on the algorithm, the input image may be cropped to fit an integer number of blocks. Args: src: source image p: parameters Returns: Output image Raises: ValueError: if source image has non-uniform coordinates """ if not src.is_uniform_coords: raise ValueError("Binning only works with images having uniform coordinates") # Create destination image dst = dst_1_to_1( src, "binning", f"{p.sx}x{p.sy},{p.operation},change_pixel_size={p.change_pixel_size}", ) dst.data = sigima.tools.image.binning( src.data, sx=p.sx, sy=p.sy, operation=p.operation, dtype=None if p.dtype_str == "dtype" else p.dtype_str, ) if p.change_pixel_size: if not np.isnan(src.dx) and not np.isnan(src.dy): # Update coordinates with new pixel spacing new_dx = src.dx * p.sx new_dy = src.dy * p.sy dst.set_uniform_coords(new_dx, new_dy, src.x0, src.y0) return dst
[docs] class ZeroPadding2DParam(gds.DataSet): """Zero padding parameters for 2D images""" def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) self.__obj: ImageObj | None = None
[docs] def update_from_obj(self, obj: ImageObj) -> None: """Update parameters from image""" self.__obj = obj self.choice_callback(None, self.strategy)
[docs] def choice_callback(self, item, value): # pylint: disable=unused-argument """Callback to update padding values""" if self.__obj is None: return rows, cols = self.__obj.data.shape if value == "next_pow2": self.rows = 2 ** int(np.ceil(np.log2(rows))) - rows self.cols = 2 ** int(np.ceil(np.log2(cols))) - cols elif value == "multiple_of_64": self.rows = (64 - rows % 64) if rows % 64 != 0 else 0 self.cols = (64 - cols % 64) if cols % 64 != 0 else 0
strategies = ("next_pow2", "multiple_of_64", "custom") _prop = gds.GetAttrProp("strategy") strategy = gds.ChoiceItem( _("Padding strategy"), zip(strategies, strategies), default=strategies[-1] ).set_prop("display", store=_prop, callback=choice_callback) _func_prop = gds.FuncProp(_prop, lambda x: x == "custom") rows = gds.IntItem(_("Rows to add"), min=0, default=0).set_prop( "display", active=_func_prop ) cols = gds.IntItem(_("Columns to add"), min=0, default=0).set_prop( "display", active=_func_prop ) position = gds.ChoiceItem( _("Padding position"), PadLocation2D, default=PadLocation2D.BOTTOM_RIGHT )
[docs] @computation_function() def zero_padding( src: ImageObj, p: ZeroPadding2DParam | None = None, ) -> ImageObj: """Zero-padding: add zeros to image borders. Args: src: input image object p: parameters Returns: Output image object """ if p is None: p = ZeroPadding2DParam.create() if p.strategy == "custom": suffix = f"rows={p.rows}, cols={p.cols}" else: suffix = f"strategy={p.strategy}" suffix += f", position={p.position}" dst = dst_1_to_1(src, "zero_padding", suffix) result = sigima.tools.image.zero_padding( src.data, rows=p.rows, cols=p.cols, position=p.position, ) dst.data = result return dst