tiff_based.py 3.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
  1. from ..base_classes import BaseROIData
  2. from skimage.measure import find_contours
  3. import numpy as np
  4. import typing
  5. class SpatialFootprintROIData(BaseROIData):
  6. """
  7. Class for storing ROI data available as spatial footprints in TIFF files
  8. """
  9. def __init__(
  10. self, label, spatial_footprint: np.ndarray, thresh=75, basic_text_description="A spatial footprint ROI"):
  11. """
  12. Spatial footprints are normalized to sum up to 1. When creating a boolean mask for this ROI,
  13. all pixels with value higher than the <thresh>th percentile will be considered to belong to the ROI.
  14. :param label: str, a label for the ROI
  15. :param spatial_footprint: numpy.ndarray, of float values
  16. :param thresh: float, between 0 and 100.
  17. """
  18. super().__init__(label=label, basic_text_description=basic_text_description)
  19. self.thresh = thresh
  20. self.spatial_footprint_norm = spatial_footprint / np.nansum(spatial_footprint)
  21. self.value_at_thresh_percentile = np.nanpercentile(self.spatial_footprint_norm, self.thresh)
  22. def get_text_description(self, frame_size):
  23. return \
  24. f'File: {self.roi_file}; Pixels: {self.get_boolean_mask(frame_size).sum()}; ' \
  25. f'Label;{self.label};{self.basic_text_description}'
  26. def get_perimeter_mask(self, frame_size: typing.Iterable[int] = None) -> typing.Tuple[np.ndarray, np.ndarray]:
  27. if frame_size is not None:
  28. assert frame_size == self.spatial_footprint_norm.shape, \
  29. f"This SpatialFootprintROI has a shape of {self.spatial_footprint_norm.shape}, while the requested " \
  30. f"perimeter mask shape is {frame_size}"
  31. else:
  32. frame_size = self.spatial_footprint_norm.shape
  33. perimeter_mask = np.zeros(frame_size, dtype=bool)
  34. for contour_coords in find_contours(self.spatial_footprint_norm, self.value_at_thresh_percentile):
  35. contour_coords_int = contour_coords.astype(int)
  36. perimeter_mask[contour_coords_int[:, 0], contour_coords_int[:, 1]] = True
  37. return perimeter_mask, self.get_boolean_mask(frame_size)
  38. def get_weighted_mask(self, frame_size: typing.Iterable[int] = None) -> np.ndarray:
  39. if frame_size is not None:
  40. assert frame_size == self.spatial_footprint_norm.shape, \
  41. f"This SpatialFootprintROI has a shape of {self.spatial_footprint_norm.shape}, while the requested " \
  42. f"mask shape is {frame_size}"
  43. return self.spatial_footprint_norm
  44. def get_boolean_mask(self, frame_size: typing.Iterable[int] = None) -> np.ndarray:
  45. if frame_size is not None:
  46. assert frame_size == self.spatial_footprint_norm.shape, \
  47. f"This SpatialFootprintROI has a shape of {self.spatial_footprint_norm.shape}, while the requested " \
  48. f"mask shape is {frame_size}"
  49. return self.spatial_footprint_norm >= self.value_at_thresh_percentile
  50. def get_boolean_mask_without_perimeter(self, frame_size: typing.Iterable[int] = None) -> np.ndarray:
  51. if frame_size is None:
  52. return super().get_boolean_mask_without_perimeter(frame_size=self.spatial_footprint_norm.shape)
  53. else:
  54. return super().get_boolean_mask_without_perimeter(frame_size)