imagesequence.py 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. """
  2. This module implements :class:`ImageSequence`, a 3D array.
  3. :class:`ImageSequence` inherits from :class:`basesignal.BaseSignal` which
  4. derives from :class:`BaseNeo`, and from :class:`quantites.Quantity`which
  5. in turn inherits from :class:`numpy.array`.
  6. Inheritance from :class:`numpy.array` is explained here:
  7. http://docs.scipy.org/doc/numpy/user/basics.subclassing.html
  8. In brief:
  9. * Initialization of a new object from constructor happens in :meth:`__new__`.
  10. This is where user-specified attributes are set.
  11. * :meth:`__array_finalize__` is called for all new objects, including those
  12. created by slicing. This is where attributes are copied over from
  13. the old object.
  14. """
  15. from neo.core.analogsignal import AnalogSignal, _get_sampling_rate
  16. import quantities as pq
  17. import numpy as np
  18. from neo.core.baseneo import BaseNeo
  19. from neo.core.basesignal import BaseSignal
  20. from neo.core.dataobject import DataObject
  21. class ImageSequence(BaseSignal):
  22. """
  23. Representation of a sequence of images, as an array of three dimensions
  24. organized as [frame][row][column].
  25. Inherits from :class:`quantities.Quantity`, which in turn inherits from
  26. :class:`numpy.ndarray`.
  27. *usage*::
  28. >>> from neo.core import ImageSequence
  29. >>> import quantities as pq
  30. >>>
  31. >>> img_sequence_array = [[[column for column in range(20)]for row in range(20)]
  32. ... for frame in range(10)]
  33. >>> image_sequence = ImageSequence(img_sequence_array, units='V',
  34. ... sampling_rate=1 * pq.Hz,
  35. ... spatial_scale=1 * pq.micrometer)
  36. >>> image_sequence
  37. ImageSequence 10 frames with width 20 px and height 20 px; units V; datatype int64
  38. sampling rate: 1.0
  39. spatial_scale: 1.0
  40. >>> image_sequence.spatial_scale
  41. array(1.) * um
  42. *Required attributes/properties*:
  43. :image_data: (3D NumPy array, or a list of 2D arrays)
  44. The data itself
  45. :units: (quantity units)
  46. :sampling_rate: *or* **frame_duration** (quantity scalar) Number of
  47. samples per unit time or
  48. duration of a single image frame.
  49. If both are specified, they are
  50. checked for consistency.
  51. :spatial_scale: (quantity scalar) size for a pixel.
  52. :t_start: (quantity scalar) Time when sequence begins. Default 0.
  53. *Recommended attributes/properties*:
  54. :name: (str) A label for the dataset.
  55. :description: (str) Text description.
  56. :file_origin: (str) Filesystem path or URL of the original data file.
  57. *Optional attributes/properties*:
  58. :dtype: (numpy dtype or str) Override the dtype of the signal array.
  59. :copy: (bool) True by default.
  60. Note: Any other additional arguments are assumed to be user-specific
  61. metadata and stored in :attr:`annotations`.
  62. *Properties available on this object*:
  63. :sampling_rate: (quantity scalar) Number of samples per unit time.
  64. (1/:attr:`frame_duration`)
  65. :frame_duration: (quantity scalar) Duration of each image frame.
  66. (1/:attr:`sampling_rate`)
  67. :spatial_scale: Size of a pixel
  68. :duration: (Quantity) Sequence duration, read-only.
  69. (size * :attr:`frame_duration`)
  70. :t_stop: (quantity scalar) Time when sequence ends, read-only.
  71. (:attr:`t_start` + :attr:`duration`)
  72. """
  73. _single_parent_objects = ("Segment",)
  74. _single_parent_attrs = ("segment",)
  75. _quantity_attr = "image_data"
  76. _necessary_attrs = (
  77. ("image_data", pq.Quantity, 3),
  78. ("sampling_rate", pq.Quantity, 0),
  79. ("spatial_scale", pq.Quantity, 0),
  80. ("t_start", pq.Quantity, 0),
  81. )
  82. _recommended_attrs = BaseNeo._recommended_attrs
  83. def __new__(cls, image_data, units=None, dtype=None, copy=True, t_start=0 * pq.s,
  84. spatial_scale=None, frame_duration=None,
  85. sampling_rate=None, name=None, description=None, file_origin=None,
  86. **annotations):
  87. """
  88. Constructs new :class:`ImageSequence` from data.
  89. This is called whenever a new class:`ImageSequence` is created from
  90. the constructor, but not when slicing.
  91. __array_finalize__ is called on the new object.
  92. """
  93. if spatial_scale is None:
  94. raise ValueError("spatial_scale is required")
  95. image_data = np.stack(image_data)
  96. if len(image_data.shape) != 3:
  97. raise ValueError("list doesn't have the correct number of dimensions")
  98. obj = pq.Quantity(image_data, units=units, dtype=dtype, copy=copy).view(cls)
  99. obj.segment = None
  100. # function from analogsignal.py in neo/core directory
  101. obj.sampling_rate = _get_sampling_rate(sampling_rate, frame_duration)
  102. obj.spatial_scale = spatial_scale
  103. if t_start is None:
  104. raise ValueError("t_start cannot be None")
  105. obj._t_start = t_start
  106. return obj
  107. def __init__(self, image_data, units=None, dtype=None, copy=True, t_start=0 * pq.s,
  108. spatial_scale=None, frame_duration=None,
  109. sampling_rate=None, name=None, description=None, file_origin=None,
  110. **annotations):
  111. """
  112. Initializes a newly constructed :class:`ImageSequence` instance.
  113. """
  114. DataObject.__init__(
  115. self, name=name, file_origin=file_origin, description=description, **annotations
  116. )
  117. def __array_finalize__spec(self, obj):
  118. self.sampling_rate = getattr(obj, "sampling_rate", None)
  119. self.spatial_scale = getattr(obj, "spatial_scale", None)
  120. self.units = getattr(obj, "units", None)
  121. self._t_start = getattr(obj, "_t_start", 0 * pq.s)
  122. return obj
  123. def signal_from_region(self, *region):
  124. """
  125. Method that takes 1 or multiple regionofinterest, uses the method of each region
  126. of interest to get the list of pixels to average.
  127. Return a list of :class:`AnalogSignal` for each regionofinterest
  128. """
  129. if len(region) == 0:
  130. raise ValueError("no regions of interest have been given")
  131. region_pixel = []
  132. for i, b in enumerate(region):
  133. r = region[i].pixels_in_region()
  134. if not r:
  135. raise ValueError("region " + str(i) + "is empty")
  136. else:
  137. region_pixel.append(r)
  138. analogsignal_list = []
  139. for i in region_pixel:
  140. data = []
  141. for frame in range(len(self)):
  142. picture_data = []
  143. for v in i:
  144. picture_data.append(self.view(pq.Quantity)[frame][v[0]][v[1]])
  145. average = picture_data[0]
  146. for b in range(1, len(picture_data)):
  147. average += picture_data[b]
  148. data.append((average * 1.0) / len(i))
  149. analogsignal_list.append(
  150. AnalogSignal(
  151. data, units=self.units, t_start=self.t_start, sampling_rate=self.sampling_rate
  152. )
  153. )
  154. return analogsignal_list
  155. def _repr_pretty_(self, pp, cycle):
  156. """
  157. Handle pretty-printing the :class:`ImageSequence`.
  158. """
  159. pp.text(
  160. "{cls} {nframe} frames with width {width} px and height {height} px; "
  161. "units {units}; datatype {dtype} ".format(
  162. cls=self.__class__.__name__,
  163. nframe=self.shape[0],
  164. height=self.shape[1],
  165. width=self.shape[2],
  166. units=self.units.dimensionality.string,
  167. dtype=self.dtype,
  168. )
  169. )
  170. def _pp(line):
  171. pp.breakable()
  172. with pp.group(indent=1):
  173. pp.text(line)
  174. for line in [
  175. "sampling rate: {!s}".format(self.sampling_rate),
  176. "spatial_scale: {!s}".format(self.spatial_scale),
  177. ]:
  178. _pp(line)
  179. def _check_consistency(self, other):
  180. """
  181. Check if the attributes of another :class:`ImageSequence`
  182. are compatible with this one.
  183. """
  184. if isinstance(other, ImageSequence):
  185. for attr in ("sampling_rate", "spatial_scale", "t_start"):
  186. if getattr(self, attr) != getattr(other, attr):
  187. raise ValueError("Inconsistent values of %s" % attr)
  188. # t_start attribute is handled as a property so type checking can be done
  189. @property
  190. def t_start(self):
  191. """
  192. Time when sequence begins.
  193. """
  194. return self._t_start
  195. @t_start.setter
  196. def t_start(self, start):
  197. """
  198. Setter for :attr:`t_start`
  199. """
  200. if start is None:
  201. raise ValueError("t_start cannot be None")
  202. self._t_start = start
  203. @property
  204. def duration(self):
  205. """
  206. Sequence duration
  207. (:attr:`size` * :attr:`frame_duration`)
  208. """
  209. return self.shape[0] / self.sampling_rate
  210. @property
  211. def t_stop(self):
  212. """
  213. Time when Sequence ends.
  214. (:attr:`t_start` + :attr:`duration`)
  215. """
  216. return self.t_start + self.duration
  217. @property
  218. def times(self):
  219. """
  220. The time points of each frame in the sequence
  221. (:attr:`t_start` + arange(:attr:`shape`)/:attr:`sampling_rate`)
  222. """
  223. return self.t_start + np.arange(self.shape[0]) / self.sampling_rate
  224. @property
  225. def frame_duration(self):
  226. """
  227. Duration of a single image frame in the sequence.
  228. (1/:attr:`sampling_rate`)
  229. """
  230. return 1.0 / self.sampling_rate
  231. @frame_duration.setter
  232. def frame_duration(self, duration):
  233. """
  234. Setter for :attr:`frame_duration`
  235. """
  236. if duration is None:
  237. raise ValueError("frame_duration cannot be None")
  238. elif not hasattr(duration, "units"):
  239. raise ValueError("frame_duration must have units")
  240. self.sampling_rate = 1.0 / duration