@@ -41,9 +41,10 @@ class ImageSequence(BaseSignal):
4141 >>> img_sequence_array = [[[column for column in range(20)]for row in range(20)]
4242 ... for frame in range(10)]
4343 >>> image_sequence = ImageSequence(img_sequence_array, units='V',
44- ... sampling_rate=1*pq.Hz, spatial_scale=1*pq.micrometer)
44+ ... sampling_rate=1 * pq.Hz,
45+ ... spatial_scale=1 * pq.micrometer)
4546 >>> image_sequence
46- ImageSequence 10 frame with 20 px of height and 20 px of width ; units V; datatype int64
47+ ImageSequence 10 frames with width 20 px and height 20 px ; units V; datatype int64
4748 sampling rate: 1.0
4849 spatial_scale: 1.0
4950 >>> image_sequence.spatial_scale
@@ -53,12 +54,13 @@ class ImageSequence(BaseSignal):
5354 :image_data: (3D NumPy array, or a list of 2D arrays)
5455 The data itself
5556 :units: (quantity units)
56- :sampling_rate: *or* **sampling_period ** (quantity scalar) Number of
57+ :sampling_rate: *or* **frame_duration ** (quantity scalar) Number of
5758 samples per unit time or
58- interval beween to samples .
59+ duration of a single image frame .
5960 If both are specified, they are
6061 checked for consistency.
6162 :spatial_scale: (quantity scalar) size for a pixel.
63+ :t_start: (quantity scalar) Time when sequence begins. Default 0.
6264
6365 *Recommended attributes/properties*:
6466 :name: (str) A label for the dataset.
@@ -74,20 +76,29 @@ class ImageSequence(BaseSignal):
7476
7577 *Properties available on this object*:
7678 :sampling_rate: (quantity scalar) Number of samples per unit time.
77- (1/:attr:`sampling_period`)
78- :sampling_period: (quantity scalar) Interval between two samples.
79- (1/:attr:`quantity scalar`)
80- :spatial_scale: size of a pixel
79+ (1/:attr:`frame_duration`)
80+ :frame_duration: (quantity scalar) Duration of each image frame.
81+ (1/:attr:`sampling_rate`)
82+ :spatial_scale: Size of a pixel
83+ :duration: (Quantity) Sequence duration, read-only.
84+ (size * :attr:`frame_duration`)
85+ :t_stop: (quantity scalar) Time when sequence ends, read-only.
86+ (:attr:`t_start` + :attr:`duration`)
8187 """
82- _single_parent_objects = ('Segment' ,)
83- _single_parent_attrs = ('segment' ,)
84- _quantity_attr = 'image_data'
85- _necessary_attrs = (('image_data' , pq .Quantity , 3 ),
86- ('sampling_rate' , pq .Quantity , 0 ),
87- ('spatial_scale' , pq .Quantity , 0 ))
88+
89+ _single_parent_objects = ("Segment" ,)
90+ _single_parent_attrs = ("segment" ,)
91+ _quantity_attr = "image_data"
92+ _necessary_attrs = (
93+ ("image_data" , pq .Quantity , 3 ),
94+ ("sampling_rate" , pq .Quantity , 0 ),
95+ ("spatial_scale" , pq .Quantity , 0 ),
96+ ("t_start" , pq .Quantity , 0 ),
97+ )
8898 _recommended_attrs = BaseNeo ._recommended_attrs
8999
90- def __new__ (cls , image_data , units = None , dtype = None , copy = True , spatial_scale = None , sampling_period = None ,
100+ def __new__ (cls , image_data , units = None , dtype = None , copy = True , t_start = 0 * pq .s ,
101+ spatial_scale = None , frame_duration = None ,
91102 sampling_rate = None , name = None , description = None , file_origin = None ,
92103 ** annotations ):
93104 """
@@ -99,52 +110,58 @@ def __new__(cls, image_data, units=None, dtype=None, copy=True, spatial_scale=No
99110 __array_finalize__ is called on the new object.
100111 """
101112 if spatial_scale is None :
102- raise ValueError (' spatial_scale is required' )
113+ raise ValueError (" spatial_scale is required" )
103114
104115 image_data = np .stack (image_data )
105116 if len (image_data .shape ) != 3 :
106- raise ValueError (' list doesn\ ' t have the good number of dimension' )
117+ raise ValueError (" list doesn't have the correct number of dimensions" )
107118
108119 obj = pq .Quantity (image_data , units = units , dtype = dtype , copy = copy ).view (cls )
109120 obj .segment = None
110121 # function from analogsignal.py in neo/core directory
111- obj .sampling_rate = _get_sampling_rate (sampling_rate , sampling_period )
122+ obj .sampling_rate = _get_sampling_rate (sampling_rate , frame_duration )
112123 obj .spatial_scale = spatial_scale
124+ if t_start is None :
125+ raise ValueError ("t_start cannot be None" )
126+ obj ._t_start = t_start
113127
114128 return obj
115129
116- def __init__ (self , image_data , units = None , dtype = None , copy = True , spatial_scale = None , sampling_period = None ,
130+ def __init__ (self , image_data , units = None , dtype = None , copy = True , t_start = 0 * pq .s ,
131+ spatial_scale = None , frame_duration = None ,
117132 sampling_rate = None , name = None , description = None , file_origin = None ,
118133 ** annotations ):
119- '''
134+ """
120135 Initializes a newly constructed :class:`ImageSequence` instance.
121- '''
122- DataObject .__init__ (self , name = name , file_origin = file_origin , description = description ,
123- ** annotations )
136+ """
137+ DataObject .__init__ (
138+ self , name = name , file_origin = file_origin , description = description , ** annotations
139+ )
124140
125141 def __array_finalize__spec (self , obj ):
126142
127- self .sampling_rate = getattr (obj , 'sampling_rate' , None )
128- self .spatial_scale = getattr (obj , 'spatial_scale' , None )
129- self .units = getattr (obj , 'units' , None )
143+ self .sampling_rate = getattr (obj , "sampling_rate" , None )
144+ self .spatial_scale = getattr (obj , "spatial_scale" , None )
145+ self .units = getattr (obj , "units" , None )
146+ self ._t_start = getattr (obj , "_t_start" , 0 * pq .s )
130147
131148 return obj
132149
133150 def signal_from_region (self , * region ):
134151 """
135- Method that takes 1 or multiple regionofinterest, use the method of each region
136- of interest to get the list of pixel to average.
137- return a list of :class:`AnalogSignal` for each regionofinterest
152+ Method that takes 1 or multiple regionofinterest, uses the method of each region
153+ of interest to get the list of pixels to average.
154+ Return a list of :class:`AnalogSignal` for each regionofinterest
138155 """
139156
140157 if len (region ) == 0 :
141- raise ValueError (' no region of interest have been given' )
158+ raise ValueError (" no regions of interest have been given" )
142159
143160 region_pixel = []
144161 for i , b in enumerate (region ):
145162 r = region [i ].pixels_in_region ()
146163 if not r :
147- raise ValueError (' region ' + str (i )+ ' is empty' )
164+ raise ValueError (" region " + str (i ) + " is empty" )
148165 else :
149166 region_pixel .append (r )
150167 analogsignal_list = []
@@ -158,23 +175,29 @@ def signal_from_region(self, *region):
158175 for b in range (1 , len (picture_data )):
159176 average += picture_data [b ]
160177 data .append ((average * 1.0 ) / len (i ))
161- analogsignal_list .append (AnalogSignal (data , units = self .units ,
162- sampling_rate = self .sampling_rate ))
178+ analogsignal_list .append (
179+ AnalogSignal (
180+ data , units = self .units , t_start = self .t_start , sampling_rate = self .sampling_rate
181+ )
182+ )
163183
164184 return analogsignal_list
165185
166186 def _repr_pretty_ (self , pp , cycle ):
167- '''
187+ """
168188 Handle pretty-printing the :class:`ImageSequence`.
169- '''
170- pp .text ("{cls} {frame} frame with {width} px of width and {height} px of height; "
171- "units {units}; datatype {dtype} " .format (
172- cls = self .__class__ .__name__ ,
173- frame = self .shape [0 ],
174- height = self .shape [1 ],
175- width = self .shape [2 ],
176- units = self .units .dimensionality .string ,
177- dtype = self .dtype ))
189+ """
190+ pp .text (
191+ "{cls} {nframe} frames with width {width} px and height {height} px; "
192+ "units {units}; datatype {dtype} " .format (
193+ cls = self .__class__ .__name__ ,
194+ nframe = self .shape [0 ],
195+ height = self .shape [1 ],
196+ width = self .shape [2 ],
197+ units = self .units .dimensionality .string ,
198+ dtype = self .dtype ,
199+ )
200+ )
178201
179202 def _pp (line ):
180203 pp .breakable ()
@@ -188,11 +211,75 @@ def _pp(line):
188211 _pp (line )
189212
190213 def _check_consistency (self , other ):
191- '''
214+ """
192215 Check if the attributes of another :class:`ImageSequence`
193216 are compatible with this one.
194- '''
217+ """
195218 if isinstance (other , ImageSequence ):
196- for attr in ("sampling_rate" , "spatial_scale" ):
219+ for attr in ("sampling_rate" , "spatial_scale" , "t_start" ):
197220 if getattr (self , attr ) != getattr (other , attr ):
198221 raise ValueError ("Inconsistent values of %s" % attr )
222+
223+ # t_start attribute is handled as a property so type checking can be done
224+ @property
225+ def t_start (self ):
226+ """
227+ Time when sequence begins.
228+ """
229+ return self ._t_start
230+
231+ @t_start .setter
232+ def t_start (self , start ):
233+ """
234+ Setter for :attr:`t_start`
235+ """
236+ if start is None :
237+ raise ValueError ("t_start cannot be None" )
238+ self ._t_start = start
239+
240+ @property
241+ def duration (self ):
242+ """
243+ Sequence duration
244+
245+ (:attr:`size` * :attr:`frame_duration`)
246+ """
247+ return self .shape [0 ] / self .sampling_rate
248+
249+ @property
250+ def t_stop (self ):
251+ """
252+ Time when Sequence ends.
253+
254+ (:attr:`t_start` + :attr:`duration`)
255+ """
256+ return self .t_start + self .duration
257+
258+ @property
259+ def times (self ):
260+ """
261+ The time points of each frame in the sequence
262+
263+ (:attr:`t_start` + arange(:attr:`shape`)/:attr:`sampling_rate`)
264+ """
265+ return self .t_start + np .arange (self .shape [0 ]) / self .sampling_rate
266+
267+ @property
268+ def frame_duration (self ):
269+ """
270+ Duration of a single image frame in the sequence.
271+
272+ (1/:attr:`sampling_rate`)
273+ """
274+ return 1.0 / self .sampling_rate
275+
276+ @frame_duration .setter
277+ def frame_duration (self , duration ):
278+ """
279+ Setter for :attr:`frame_duration`
280+ """
281+ if duration is None :
282+ raise ValueError ("frame_duration cannot be None" )
283+ elif not hasattr (duration , "units" ):
284+ raise ValueError ("frame_duration must have units" )
285+ self .sampling_rate = 1.0 / duration
0 commit comments