roi_io.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  1. import logging
  2. from abc import ABC, abstractmethod
  3. from view.python_core.areas import AreaMaskIO
  4. from view.python_core.flags import FlagsManager
  5. from view.python_core.io import read_tif_2Dor3D
  6. from view.python_core.paths import check_get_file_existence_in_folder
  7. from view.python_core.rois.idl_rois import SquareIDLROIData, TIFFIDLROIData
  8. from view.python_core.rois.iltis_rois import CircleILTISROIData, PolygonILTISROIData, SpatialFootprintROIData
  9. import typing
  10. from view.python_core.rois.non_file_based_rois import UniformROIData
  11. class BaseROIIO(ABC):
  12. def __init__(self):
  13. super().__init__()
  14. @classmethod
  15. @abstractmethod
  16. def read(cls, flags: FlagsManager, measurement_label: str = "") -> typing.Tuple[dict, str]:
  17. return # to be implemented in subclass
  18. class ROIFileIO(BaseROIIO, ABC):
  19. def __init__(self):
  20. super().__init__()
  21. @classmethod
  22. def read(cls, flags: FlagsManager, measurement_label: str = "") -> typing.Tuple[dict, str]:
  23. """
  24. Read ROI data from a ROI file indicated by <flags> and optionally <measurement_label>
  25. :param flags: view flags object
  26. :param measurement_label: str, optional
  27. :return: roi_data_dict, roi_file
  28. roi_data_dict: dict with roi labels as keys and ROI Data objects as values
  29. roi_file: str, name of the file from which ROI data was read
  30. """
  31. parent_folders = cls.get_parent_dirs(flags)
  32. for parent_folder in parent_folders:
  33. roi_file = check_get_file_existence_in_folder(
  34. folder=parent_folder, possible_extensions=[cls.get_extension()],
  35. stems=flags.get_file_stem_hierarchy(measurement_label)
  36. )
  37. if roi_file is not None:
  38. break
  39. else:
  40. raise FileNotFoundError(
  41. f"Could not find a ROI file\nin any of {parent_folders}\nfor animal={flags['STG_ReportTag']}"
  42. f"\nand measurement_label={measurement_label}\nwith extension={cls.get_extension()}"
  43. )
  44. logging.getLogger("VIEW").info(f"Loading ROI data from {roi_file}")
  45. roi_data_list = cls.read_roi_file(roi_file, flags)
  46. # look for duplicate labels and issue warning
  47. roi_data_dict = {}
  48. for roi_data in roi_data_list:
  49. if roi_data.label in roi_data_dict:
  50. logging.getLogger("VIEW").warning(
  51. f"Multiple glomeruli found with label {roi_data.label} in {roi_file}. Ignoring the "
  52. f"second one")
  53. roi_data_dict[roi_data.label] = roi_data
  54. return roi_data_dict, roi_file
  55. @classmethod
  56. @abstractmethod
  57. def get_extension(cls):
  58. raise NotImplementedError
  59. @classmethod
  60. @abstractmethod
  61. def read_roi_file(cls, roi_file, flags):
  62. raise NotImplementedError
  63. @classmethod
  64. @abstractmethod
  65. def write(cls, filename, roi_datas):
  66. raise NotImplementedError
  67. @classmethod
  68. def get_parent_dirs(cls, flags):
  69. return [flags.get_coor_dir_str()]
  70. class IDLCoorFileIO(ROIFileIO):
  71. def __init__(self):
  72. super().__init__()
  73. @classmethod
  74. def get_extension(cls):
  75. return ".coor"
  76. @classmethod
  77. def read_roi_file(cls, roi_file, flags):
  78. """
  79. Read coor file and return a list of objects containing information of square ROIs in it.
  80. :param roi_file: str, path to a coor file on the file system
  81. :param flags: view flags object
  82. :return: list of SquareIDLROIData objects
  83. """
  84. with open(roi_file) as fh:
  85. text_lines = fh.readlines()
  86. n_rois = int(text_lines[0])
  87. assert len(text_lines) - 1 >= n_rois, f"The number of ROIs indicated in the first line exceeds the number" \
  88. f"of ROI lines that follow in {roi_file}"
  89. roi_data = []
  90. for roi_ind in range(n_rois):
  91. roi = SquareIDLROIData.read_from_text_line(text_lines[1 + roi_ind])
  92. roi.half_width = flags["RM_Radius"]
  93. roi.roi_file = roi_file
  94. roi_data.append(roi)
  95. return roi_data
  96. @classmethod
  97. def write(cls, filename, roi_datas):
  98. raise NotImplementedError
  99. class IDLAREAFileIO(ROIFileIO):
  100. def __init__(self):
  101. super().__init__()
  102. @classmethod
  103. def get_extension(cls):
  104. return ".Area"
  105. @classmethod
  106. def read_roi_file(cls, roi_file, flags):
  107. """
  108. Read IDL AREA file and return a list with one SpatialFootprintROIData object
  109. :param roi_file: str, path to a AREA file on the file system
  110. :param flags: view flags object
  111. :return: list of one SpatialFootprintROIData object
  112. """
  113. idl_tiff_frame = AreaMaskIO().read_footprint(roi_file)
  114. roi_data = TIFFIDLROIData(idl_tiff_frame=idl_tiff_frame, label="Area0")
  115. return [roi_data]
  116. @classmethod
  117. def write(cls, filename, roi_datas):
  118. raise NotImplementedError
  119. @classmethod
  120. def get_parent_dirs(cls, flags):
  121. return [flags.get_area_dir_str(), flags.get_coor_dir_str()]
  122. class ILTISTextROIFileIO(ROIFileIO):
  123. def __init__(self):
  124. super().__init__()
  125. @classmethod
  126. def get_extension(cls):
  127. return ".roi"
  128. @classmethod
  129. def read_roi_file(cls, roi_file, flags):
  130. """
  131. Read .roi file and return information as list of roi objects
  132. :param roi_file: str, path to a AREA file on the file system
  133. :param flags: view flags object
  134. :return: list of roi objects, belonging to one of the classes: PolygonILTISROIData, CircleILTISROIData
  135. """
  136. with open(roi_file) as fh:
  137. text_lines = fh.readlines()
  138. roi_data = []
  139. for text_line in text_lines:
  140. if text_line.startswith("circle"):
  141. class_to_use = CircleILTISROIData
  142. elif text_line.startswith("polygon"):
  143. class_to_use = PolygonILTISROIData
  144. else:
  145. raise NotImplementedError
  146. roi_data.append(class_to_use.read_from_text_line(text_line))
  147. return roi_data
  148. @classmethod
  149. def write(cls, filename: str, roi_datas: list):
  150. """
  151. Write information in ROIs <roi_datas> into the file <filename> as text.
  152. :param filename: str, path of a file on filesystem
  153. :param roi_datas: list of objects, belonging to one of the classes: PolygonILTISROIData, CircleILTISROIData
  154. :return: None
  155. """
  156. lines_to_write = [roi_data.write_to_text_line() for roi_data in roi_datas]
  157. with open(filename, "w") as fh:
  158. fh.writelines(lines_to_write)
  159. class ILTISTiffROIFileIO(ROIFileIO):
  160. def __init__(self):
  161. super().__init__()
  162. @classmethod
  163. def read(cls, flags: FlagsManager, measurement_label: str = "", labels=()) -> typing.Tuple[dict, str]:
  164. """
  165. Read ROI data from a ROI file indicated by <flags> and optionally <measurement_label>
  166. :param flags: view flags object
  167. :param measurement_label: str, optional
  168. :param labels: iterable, of str. Must have same number of elements as the number of pages in <file>
  169. :return: roi_data_dict, roi_file
  170. roi_data_dict: dict with roi labels as keys and ROI Data objects as values
  171. roi_file: str, name of the file from which ROI data was read
  172. """
  173. roi_data_dict_temp, roi_file = super().read(flags=flags, measurement_label=measurement_label)
  174. roi_data_dict = {}
  175. if len(labels) == 0:
  176. roi_data_dict = roi_data_dict_temp
  177. elif len(labels) == len(roi_data_dict_temp):
  178. for label, (roi_label, roi_data) in zip(labels, roi_data_dict_temp.items()):
  179. roi_data.label = label
  180. roi_data_dict[label] = roi_data
  181. else:
  182. logging.getLogger("VIEW").warning(
  183. f"The specified tiff file, {roi_file}, has {len(roi_data_dict_temp)}, "
  184. f"while {len(labels)} were specified. Ignoring the labels specified")
  185. roi_data_dict = roi_data_dict_temp
  186. return roi_data_dict, roi_file
  187. @classmethod
  188. def get_extension(cls):
  189. return ".roi.tif"
  190. @classmethod
  191. def read_roi_file(cls, roi_file, flags):
  192. """
  193. Read spatial footprints of ROIs in the tiff file <file>. Spatial footprints are individually normalized by
  194. dividing by their maximum pixel values and all pixels with value lower than <thresh> will be considered to
  195. belong to the ROI.
  196. :param roi_file: str, path to a TIFF file on file system
  197. :param flags, view flags object
  198. :returns: list, of SpatialFootprintROIData
  199. """
  200. roi_footprints, labels = read_tif_2Dor3D(roi_file, return_3D=True)
  201. if labels is None:
  202. labels = [str(x) for x in range(roi_footprints.shape[2])]
  203. roi_data = []
  204. for roi_ind, label in enumerate(labels[:roi_footprints.shape[2]]):
  205. roi = SpatialFootprintROIData(
  206. spatial_footprint=roi_footprints[:, :, roi_ind],
  207. thresh=flags["RM_ROIThreshold"], label=label)
  208. roi_data.append(roi)
  209. return roi_data
  210. @classmethod
  211. def write(cls, filename, roi_datas):
  212. raise NotImplementedError
  213. class ILTISAreaROIFileIO(ILTISTiffROIFileIO):
  214. def __init__(self):
  215. super().__init__()
  216. @classmethod
  217. def get_extension(cls):
  218. return ".area.tif"
  219. @classmethod
  220. def write(cls, filename, roi_datas):
  221. raise NotImplementedError
  222. @classmethod
  223. def get_parent_dirs(cls, flags):
  224. return [flags.get_area_dir_str(), flags.get_coor_dir_str()]
  225. class NonFileUniformROIIO(BaseROIIO):
  226. def __init__(self):
  227. super().__init__()
  228. @classmethod
  229. def read(cls, flags: FlagsManager, measurement_label: str = ""):
  230. """
  231. returns a one member dict with a fake label as key and a fake ROI data object representing uniform ROI covering
  232. the entire frame
  233. :param flags: view flags object
  234. :param measurement_label: str, optional
  235. :return: roi_data_dict, roi_file
  236. roi_data_dict: dict with roi labels as keys and ROI Data objects as values
  237. roi_file: str, name of the file from which ROI data was read
  238. """
  239. logging.getLogger("VIEW").info(f"Create fictive uniform ROI data")
  240. roi_data = UniformROIData()
  241. roi_data_dict = {roi_data.label: roi_data}
  242. return roi_data_dict, None
  243. def get_roi_io_class(RM_ROITrace):
  244. if RM_ROITrace == 0:
  245. io_class = IDLCoorFileIO
  246. elif RM_ROITrace == 2:
  247. io_class = IDLAREAFileIO
  248. elif RM_ROITrace == 3:
  249. io_class = ILTISTextROIFileIO
  250. elif RM_ROITrace == 4:
  251. io_class = ILTISTiffROIFileIO
  252. elif RM_ROITrace == 5:
  253. io_class = ILTISAreaROIFileIO
  254. elif RM_ROITrace == 6:
  255. io_class = NonFileUniformROIIO
  256. else:
  257. raise NotImplementedError(f"RM_ROITrace={RM_ROITrace}")
  258. return io_class