You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

117 lines
5.5 KiB

  1. # -*- coding: utf-8 -*-
  2. import array
  3. import numpy as np
  4. import struct
  5. import six
  6. from nd2reader.model.image import Image
  7. from nd2reader.common.v3 import read_chunk
  8. from nd2reader.exc import NoImageError
  9. class V3Driver(object):
  10. def __init__(self, metadata, label_map, file_handle):
  11. self._metadata = metadata
  12. self._label_map = label_map
  13. self._file_handle = file_handle
  14. def _calculate_field_of_view(self, frame_number):
  15. images_per_cycle = len(self._metadata.z_levels) * len(self._metadata.channels)
  16. return int((frame_number - (frame_number % images_per_cycle)) / images_per_cycle) % len(self._metadata.fields_of_view)
  17. def _calculate_channel(self, frame_number):
  18. return self._metadata.channels[frame_number % len(self._metadata.channels)]
  19. def _calculate_z_level(self, frame_number):
  20. return self._metadata.z_levels[int(((frame_number - (frame_number % len(self._metadata.channels))) / len(self._metadata.channels)) % len(self._metadata.z_levels))]
  21. def _calculate_image_group_number(self, time_index, fov, z_level):
  22. """
  23. Images are grouped together if they share the same time index, field of view, and z-level.
  24. :type time_index: int
  25. :type fov: int
  26. :type z_level: int
  27. :rtype: int
  28. """
  29. return time_index * len(self._metadata.fields_of_view) * len(self._metadata.z_levels) + (fov * len(self._metadata.z_levels) + z_level)
  30. def _calculate_frame_number(self, image_group_number, fov, z_level):
  31. return (image_group_number - (fov * len(self._metadata.z_levels) + z_level)) / (len(self._metadata.fields_of_view) * len(self._metadata.z_levels))
  32. def get_image(self, index):
  33. channel_offset = index % len(self._metadata.channels)
  34. fov = self._calculate_field_of_view(index)
  35. channel = self._calculate_channel(index)
  36. z_level = self._calculate_z_level(index)
  37. image_group_number = int(index / len(self._metadata.channels))
  38. frame_number = self._calculate_frame_number(image_group_number, fov, z_level)
  39. try:
  40. timestamp, image = self._get_raw_image_data(image_group_number, channel_offset, self._metadata.height, self._metadata.width)
  41. except NoImageError:
  42. return None
  43. else:
  44. image.add_params(timestamp, frame_number, fov, channel, z_level)
  45. return image
  46. @property
  47. def _channel_offset(self):
  48. """
  49. Image data is interleaved for each image set. That is, if there are four images in a set, the first image
  50. will consist of pixels 1, 5, 9, etc, the second will be pixels 2, 6, 10, and so forth.
  51. :rtype: dict
  52. """
  53. channel_offset = {}
  54. for n, channel in enumerate(self._metadata.channels):
  55. channel_offset[channel] = n
  56. return channel_offset
  57. def _get_raw_image_data(self, image_group_number, channel_offset, height, width):
  58. """
  59. Reads the raw bytes and the timestamp of an image.
  60. :param image_group_number: groups are made of images with the same time index, field of view and z-level.
  61. :type image_group_number: int
  62. :param channel_offset: the offset in the array where the bytes for this image are found.
  63. :type channel_offset: int
  64. :return: (int, array.array()) or None
  65. """
  66. chunk = self._label_map[six.b("ImageDataSeq|%d!" % image_group_number)]
  67. data = read_chunk(self._file_handle, chunk)
  68. # All images in the same image group share the same timestamp! So if you have complicated image data,
  69. # your timestamps may not be entirely accurate. Practically speaking though, they'll only be off by a few
  70. # seconds unless you're doing something super weird.
  71. timestamp = struct.unpack("d", data[:8])[0]
  72. image_group_data = array.array("H", data)
  73. image_data_start = 4 + channel_offset
  74. # The images for the various channels are interleaved within the same array. For example, the second image
  75. # of a four image group will be composed of bytes 2, 6, 10, etc. If you understand why someone would design
  76. # a data structure that way, please send the author of this library a message.
  77. image_data = np.reshape(image_group_data[image_data_start::len(self._metadata.channels)], (height, width))
  78. # Skip images that are all zeros! This is important, since NIS Elements creates blank "gap" images if you
  79. # don't have the same number of images each cycle. We discovered this because we only took GFP images every
  80. # other cycle to reduce phototoxicity, but NIS Elements still allocated memory as if we were going to take
  81. # them every cycle.
  82. if np.any(image_data):
  83. return timestamp, Image(image_data)
  84. raise NoImageError
  85. def get_image_by_attributes(self, frame_number, field_of_view, channel_name, z_level, height, width):
  86. image_group_number = self._calculate_image_group_number(frame_number, field_of_view, z_level)
  87. try:
  88. timestamp, raw_image_data = self._get_raw_image_data(image_group_number,
  89. self._channel_offset[channel_name],
  90. height,
  91. width)
  92. image = Image(raw_image_data)
  93. image.add_params(timestamp, frame_number, field_of_view, channel_name, z_level)
  94. except (TypeError, NoImageError):
  95. return None
  96. else:
  97. return image