Source code for neuralib.atlas.ccf.matrix

from __future__ import annotations

from pathlib import Path
from typing import Any, NamedTuple

import attrs
import cv2
import imageio.v3 as iio
import numpy as np
import polars as pl
from neuralib.atlas.typing import PLANE_TYPE
from neuralib.atlas.view import SlicePlane, get_slice_view
from neuralib.imglib.transform import apply_transformation
from neuralib.typing import PathLike, PathLikeType
from scipy.io import loadmat
from scipy.io.matlab import MatlabOpaque

__all__ = [
    'SLICE_DIMENSION_10um',
    'CCFTransMatrix',
    'load_transform_matrix',
    'slice_transform_helper'
]

SLICE_DIMENSION_10um: dict[PLANE_TYPE, tuple[int, int]] = {
    'coronal': (1140, 800),
    'sagittal': (1320, 800)
}


[docs] def load_transform_matrix(filepath: PathLike, plane_type: PLANE_TYPE, resolution: int = 10, default_name: str = 'test') -> CCFTransMatrix: """ matrix for image transformation :param filepath: transformation matrix .mat file :param plane_type: ``PLANE_TYPE`` {'coronal', 'sagittal', 'transverse'} :param resolution: atlas resolution :param default_name: name of the ``CCFTransMatrix`` :return: ``CCFTransMatrix`` """ mat = loadmat(filepath, squeeze_me=True, struct_as_record=False)['save_transform'] f = Path(filepath).name try: name = f[:f.index('_resize')] except ValueError: name = default_name return CCFTransMatrix( slice_id=name, matrix=MatMatrix(mat.allen_location, mat.transform, mat.transform_points), plane_type=plane_type, resolution=resolution, )
class MatMatrix(NamedTuple): allen_location: np.ndarray """abnormal parsing in loadmat. [996 array([ 0, -12], dtype=int16)]""" transform: MatlabOpaque """mat meta info. currently not used""" transform_points: np.ndarray """[(P, 2), (P, 2)] transformation point for 2dccf GUI site view""" @property def slice_index(self) -> int: return int(self.allen_location[0]) # noinspection PyTypeChecker @property def delta_values(self) -> tuple[int, int]: """shifting values for yx axis (height, width) for a specific plane type. Coronal slice: [0]:DV, [1]:ML Sagittal Slice: [0]:DV, [1]:AP """ delta = self.allen_location[1] return delta[0], delta[1]
[docs] @attrs.frozen class CCFTransMatrix: """matrix for image transformation""" slice_id: str """slice name id""" matrix: MatMatrix """MatMatrix""" plane_type: PLANE_TYPE """PLANE_TYPE""" resolution: int = attrs.field(kw_only=True, default=10) """resolution in um""" @property def slice_index(self) -> int: """slice index""" return self.matrix.slice_index @property def delta_xy(self) -> tuple[int, int]: """map delta value to xy slice view""" return int(self.matrix.delta_values[1]), int(self.matrix.delta_values[0])
[docs] def get_slice_plane(self) -> SlicePlane: """get slice plane""" dw = self.delta_xy[0] dh = self.delta_xy[1] ret = ( get_slice_view('reference', self.plane_type, resolution=self.resolution) .plane_at(self.slice_index) .with_offset( dw=dw + 1 if dw != 0 else 0, # avoid index err dh=dh + 1 if dh != 0 else 0, # avoid index err ) ) return ret
[docs] def matrix_info(self, to_polars: bool = True) -> dict[str, Any] | pl.DataFrame: """ transform matrix information dict :param to_polars: to polars dataframe :return: """ ret = { 'slice_id': self.slice_id, 'plane_type': self.plane_type, 'atlas_resolution': self.resolution, 'slice_index': self.slice_index, 'reference_value': self.get_slice_plane().reference_value, 'dw': self.delta_xy[0], 'dh': self.delta_xy[1] } if to_polars: ret = pl.DataFrame(ret) return ret
[docs] def slice_transform_helper(raw_image: PathLike | np.ndarray, trans_matrix: np.ndarray | PathLike, *, plane_type: PLANE_TYPE = 'coronal', flip_lr: bool = False, flip_ud: bool = False) -> tuple[np.ndarray, np.ndarray]: """ Transforms an input image according to the specified transformation matrix, plane orientation, and optional flipping parameters. This function reads a raw image, optionally flips it horizontally or vertically, applies a transformation matrix, and resizes it to a defined dimension based on the plane type. The result is a tuple containing the raw image and the transformed image. :param raw_image: Path to the raw input image or image array. :param trans_matrix: Transformation matrix to apply to the image. It can be a numpy array or the path to a valid file containing the matrix. Supported files are `.mat` for MATLAB files and `.npy` for NumPy files. :param plane_type: Defines the anatomical plane for transformation. Defaults to 'coronal'. :param flip_lr: Boolean flag to determine whether to flip the image horizontally (left to right). :param flip_ud: Boolean flag to determine whether to flip the image vertically (up to down). :return: A tuple containing two numpy arrays: the raw image as processed, and the transformed image. """ if isinstance(raw_image, np.ndarray): pass elif isinstance(raw_image, PathLikeType): raw_image = iio.imread(Path(raw_image)) else: raise TypeError(f"Unsupported type {type(raw_image)}") # if flip_ud: raw_image = np.flipud(raw_image) if flip_lr: raw_image = np.fliplr(raw_image) # if isinstance(trans_matrix, PathLikeType): s = Path(trans_matrix).suffix if s == '.mat': mtx = loadmat(trans_matrix)['t'].T elif s == '.npy': mtx = np.load(trans_matrix) else: raise ValueError(f'invalid file type for the transformation matrix: {s}') else: mtx = trans_matrix resize = cv2.resize(raw_image, SLICE_DIMENSION_10um[plane_type]) transform_image = apply_transformation(resize, mtx) return raw_image, transform_image