From fe2e99140e2b744a804c97eea435bdd30915fc6b Mon Sep 17 00:00:00 2001 From: Rutger Kok Date: Fri, 22 Sep 2023 11:18:49 +0200 Subject: [PATCH] Better support for 2D images - Z resolution is no longer required - Fixed loading 2D images from LIF files --- organoid_tracker/core/images.py | 11 +++++++++-- organoid_tracker/core/resolution.py | 7 +++++-- .../image_loading/imsfile_image_loader.py | 14 +++++++------- .../image_loading/liffile_image_loader.py | 2 ++ organoid_tracker/imaging/list_io.py | 10 ++++++++++ 5 files changed, 33 insertions(+), 11 deletions(-) diff --git a/organoid_tracker/core/images.py b/organoid_tracker/core/images.py index 313c0625..f45fde72 100644 --- a/organoid_tracker/core/images.py +++ b/organoid_tracker/core/images.py @@ -285,7 +285,12 @@ def resolution(self, allow_incomplete: bool = False) -> ImageResolution: the data has multiple time points, but no time resolution has been set. """ require_time_resolution = self.first_time_point_number() != self.last_time_point_number() - if not allow_incomplete and self._resolution.is_incomplete(require_time_resolution=require_time_resolution): + require_z = False + image_size = self._image_loader.get_image_size_zyx() + if image_size is not None and image_size[0] > 1: + require_z = True # We have 3D images, require a Z resolution + if not allow_incomplete and self._resolution.is_incomplete(require_time_resolution=require_time_resolution, + require_z=require_z): raise UserError("No image resolution set", "No image resolution was set. Please set a resolution first." " This can be done in the Edit menu of the program.") return self._resolution @@ -328,7 +333,9 @@ def set_timings(self, timings: Optional[ImageTimings]): self._timings = timings # Also keep time resolution in sync - self._resolution.time_point_interval_m = timings.get_time_m_since_previous(TimePoint(1)) + self._resolution = ImageResolution(self._resolution.pixel_size_x_um, self._resolution.pixel_size_y_um, + self._resolution.pixel_size_z_um, + timings.get_time_m_since_previous(TimePoint(1))) def is_inside_image(self, position: Position, *, margin_xy: int = 0, margin_z: int = 0) -> Optional[bool]: """Checks if the given position is inside the images. If there are no images loaded, this returns None. Any diff --git a/organoid_tracker/core/resolution.py b/organoid_tracker/core/resolution.py index 48afa5ff..7ef2ae35 100644 --- a/organoid_tracker/core/resolution.py +++ b/organoid_tracker/core/resolution.py @@ -44,12 +44,15 @@ def __repr__(self) -> str: return f"ImageResolution({self.pixel_size_x_um}, {self.pixel_size_y_um}, {self.pixel_size_z_um}," \ f" {self.time_point_interval_m})" - def is_incomplete(self, *, require_time_resolution: bool = True) -> bool: + def is_incomplete(self, *, require_time_resolution: bool = True, require_z: bool = True) -> bool: """Returns True if the x, y, z and/or t resolution is zero. Otherwise it returns False.""" if require_time_resolution: if self.time_point_interval_m == 0 or self.time_point_interval_m == float("inf"): return True - return 0 in self.pixel_size_zyx_um + if require_z: + if self.pixel_size_z_um == 0: + return True + return self.pixel_size_x_um == 0 or self.pixel_size_y_um == 0 # See typehint at beginning of ImageResolution class diff --git a/organoid_tracker/image_loading/imsfile_image_loader.py b/organoid_tracker/image_loading/imsfile_image_loader.py index 201d94ee..74a6ec56 100644 --- a/organoid_tracker/image_loading/imsfile_image_loader.py +++ b/organoid_tracker/image_loading/imsfile_image_loader.py @@ -732,7 +732,7 @@ class _ImsImageLoader(ImageLoader): def __init__(self, file_name: str, min_time_point: Optional[int], max_time_point: Optional[int]): self._reader = _ImsReader(file_name) self._min_time_point = 0 if min_time_point is None else min_time_point - self._max_time_point = self._reader.TimePoints if max_time_point is None else max_time_point + self._max_time_point = self._reader.TimePoints - 1 if max_time_point is None else max_time_point def get_3d_image_array(self, time_point: TimePoint, image_channel: ImageChannel) -> Optional[ndarray]: time_point_number = time_point.time_point_number() @@ -740,7 +740,7 @@ def get_3d_image_array(self, time_point: TimePoint, image_channel: ImageChannel) return None if image_channel.index_zero < 0 or image_channel.index_zero >= self._reader.Channels: return None - array = self._reader[time_point_number - 1, image_channel.index_zero] + array = self._reader[time_point_number, image_channel.index_zero] if not numpy.any(array): return None # Got an all-zero array, ignore return array @@ -753,7 +753,7 @@ def get_2d_image_array(self, time_point: TimePoint, image_channel: ImageChannel, return None if image_z < 0 or image_z >= self._reader.shape[2]: return None - array = self._reader[time_point_number - 1, image_channel.index_zero, image_z] + array = self._reader[time_point_number, image_channel.index_zero, image_z] if not numpy.any(array): return None # Got an all-zero array, ignore return array @@ -762,10 +762,10 @@ def get_image_size_zyx(self) -> Optional[Tuple[int, int, int]]: return self._reader.shape[-3], self._reader.shape[-2], self._reader.shape[-1] def first_time_point_number(self) -> int: - return max(self._min_time_point, 1) + return max(self._min_time_point, 0) def last_time_point_number(self) -> int: - return min(self._max_time_point, self._reader.TimePoints) + return min(self._max_time_point, self._reader.TimePoints - 1) def get_channel_count(self) -> int: return self._reader.Channels @@ -793,7 +793,7 @@ def get_time_resolution_m(self) -> float: def get_timings(self) -> Optional[ImageTimings]: if "DataSetTimes" in self._reader.hf: timespans_s = [float(moment[2] / 1_000_000_000) for moment in self._reader.hf["DataSetTimes"]["Time"]] - timespans_s = [0.0] + timespans_s timespans_s = numpy.array(timespans_s, dtype=numpy.float64) - return ImageTimings(1, timespans_s / 60) + timespans_s -= timespans_s[0] + return ImageTimings(0, timespans_s / 60) return None diff --git a/organoid_tracker/image_loading/liffile_image_loader.py b/organoid_tracker/image_loading/liffile_image_loader.py index 143bc70e..84b2231c 100644 --- a/organoid_tracker/image_loading/liffile_image_loader.py +++ b/organoid_tracker/image_loading/liffile_image_loader.py @@ -139,6 +139,8 @@ def get_3d_image_array(self, time_point: TimePoint, image_channel: ImageChannel) return None array = self._serie.getFrame(channel=image_channel.index_zero, T=time_point.time_point_number()) + if len(array.shape) == 2: # Convert to 3D + array = array[numpy.newaxis, :, :] if array.dtype != numpy.uint8: # Saves memory array = bits.image_to_8bit(array) if self._inverted_z: diff --git a/organoid_tracker/imaging/list_io.py b/organoid_tracker/imaging/list_io.py index e5b2b3b4..f8fd9d26 100644 --- a/organoid_tracker/imaging/list_io.py +++ b/organoid_tracker/imaging/list_io.py @@ -67,3 +67,13 @@ def _contains_images(experiment_json: Dict[str, Any]) -> bool: if key.startswith("images_"): return True return False + + +def count_experiments_in_list_file(open_files_list_file: str) -> int: + """Counts the number of experiments in the given file. Used as a quick way to get that number, instead of loading + everything.""" + with open(open_files_list_file, "r", encoding="utf-8") as handle: + experiments_json = json.load(handle) + if not isinstance(experiments_json, list): + raise TypeError("Expected a list in " + open_files_list_file + ", got " + repr(experiments_json)) + return len(experiments_json) \ No newline at end of file