def __init__(self, fig, files, load_func=None, robust=False, sps=None, time_markers=None, **kwargs) : """ Create an animation object for viewing radar reflectivities. *fig* matplotlib Figure object *files* list of filenames containing the radar data *load_func* The function to use to load the data from a file. Must return a dictionary of 'vals' which contains the 3D numpy array (T by Y by X), 'lats' and 'lons'. It is also optional that the loading function also provides a 'scan_time', either as a :class:`datetime.datetime` object or as an integer or a float representing the number of seconds since UNIX Epoch. *robust* Boolean (default: False) indicating whether or not we can assume all the data will be for the same domain. If you can't assume a consistant domain, then set *robust* to True. This often happens for PAR data. Note that a robust rendering is slower. *sps* The rate of data time for each second displayed. Default: None (a data frame per display frame). *time_markers* A list of time offsets (in seconds) for each frame. If None, then autogenerate from the event_source and data (unless *sps* is None). All other kwargs for :class:`FuncAnimation` are also allowed. To use, specify the axes to display the image on using :meth:`add_axes`. If no axes are added by draw time, then this class will use the current axes by default with no extra keywords. """ #self._rd = files #self._loadfunc = load_func if load_func is not None else LoadRastRadar self._rd = RadarCache(files, cachewidth=3, load_func=load_func, cyclable=True) self.startTime = self.curr_time self.endTime = self.prev_time self._ims = [] self._im_kwargs = [] self._new_axes = [] #self._curr_time = None self._robust = robust frames = kwargs.pop('frames', None) #if len(files) < frames : # raise ValueError("Not enough data files for the number of frames") self.time_markers = None FuncAnimation.__init__(self, fig, self.nextframe, #frames=len(self._rd), init_func=self.firstframe, **kwargs) self._sps = sps if time_markers is None : # Convert milliseconds per frame to frames per second self._fps = 1000.0 / self.event_source.interval if self._sps is not None : #timelen = (self.endTime - self.startTime) timestep = timedelta(0, self._sps / self._fps) currTime = self.startTime self.time_markers = [currTime] while currTime < self.endTime : currTime += timestep self.time_markers.append(currTime) else : # Don't even bother trying to make playback uniform. self.time_markers = None else : self.time_markers = time_markers self._fps = ((len(time_markers) - 1) / ((self.time_markers[-1] - self.time_markers[0]).total_seconds() / self._sps)) self.event_source.interval = 1000.0 / self._fps self.save_count = (len(self.time_markers) if self.time_markers is not None else len(self._rd))
class RadarAnim(FuncAnimation) : def __init__(self, fig, files, load_func=None, robust=False, sps=None, time_markers=None, **kwargs) : """ Create an animation object for viewing radar reflectivities. *fig* matplotlib Figure object *files* list of filenames containing the radar data *load_func* The function to use to load the data from a file. Must return a dictionary of 'vals' which contains the 3D numpy array (T by Y by X), 'lats' and 'lons'. It is also optional that the loading function also provides a 'scan_time', either as a :class:`datetime.datetime` object or as an integer or a float representing the number of seconds since UNIX Epoch. *robust* Boolean (default: False) indicating whether or not we can assume all the data will be for the same domain. If you can't assume a consistant domain, then set *robust* to True. This often happens for PAR data. Note that a robust rendering is slower. *sps* The rate of data time for each second displayed. Default: None (a data frame per display frame). *time_markers* A list of time offsets (in seconds) for each frame. If None, then autogenerate from the event_source and data (unless *sps* is None). All other kwargs for :class:`FuncAnimation` are also allowed. To use, specify the axes to display the image on using :meth:`add_axes`. If no axes are added by draw time, then this class will use the current axes by default with no extra keywords. """ #self._rd = files #self._loadfunc = load_func if load_func is not None else LoadRastRadar self._rd = RadarCache(files, cachewidth=3, load_func=load_func, cyclable=True) self.startTime = self.curr_time self.endTime = self.prev_time self._ims = [] self._im_kwargs = [] self._new_axes = [] #self._curr_time = None self._robust = robust frames = kwargs.pop('frames', None) #if len(files) < frames : # raise ValueError("Not enough data files for the number of frames") self.time_markers = None FuncAnimation.__init__(self, fig, self.nextframe, #frames=len(self._rd), init_func=self.firstframe, **kwargs) self._sps = sps if time_markers is None : # Convert milliseconds per frame to frames per second self._fps = 1000.0 / self.event_source.interval if self._sps is not None : #timelen = (self.endTime - self.startTime) timestep = timedelta(0, self._sps / self._fps) currTime = self.startTime self.time_markers = [currTime] while currTime < self.endTime : currTime += timestep self.time_markers.append(currTime) else : # Don't even bother trying to make playback uniform. self.time_markers = None else : self.time_markers = time_markers self._fps = ((len(time_markers) - 1) / ((self.time_markers[-1] - self.time_markers[0]).total_seconds() / self._sps)) self.event_source.interval = 1000.0 / self._fps self.save_count = (len(self.time_markers) if self.time_markers is not None else len(self._rd)) @property def curr_time(self) : """ The :class:`datetime.datetime` object for the current frame's time. Could possibly be None. This is a read-only property. """ return self._get_time(self._rd.curr()) @property def prev_time(self) : """ Similar to :meth:`curr_time`. """ return self._get_time(self._rd.peek_prev()) @property def next_time(self) : """ Similar to :meth:`curr_time`. """ return self._get_time(self._rd.peek_next()) @staticmethod def _get_time(data) : currTime = data.get('scan_time', None) if (currTime is not None and not isinstance(currTime, datetime)) : currTime = datetime.utcfromtimestamp(currTime) return currTime def firstframe(self, *args) : #if len(self._ims) == 0 and len(self._new_axes) == 0 : # self.add_axes(plt.gca()) return self.nextframe(0) def _advance_anim(self) : data = next(self._rd) if not self._robust : for im in self._ims : im.set_array(data['vals'][0, :-1, :-1].flatten()) else : for im, kwargs in zip(self._ims, self._im_kwargs) : # Add this im object's axes object to _new_axes so # that a completely new rendering is made. self._new_axes.append((im.axes, kwargs)) # Remove the current rendering from the Axes im.remove() # Reset these arrays so that they can be refilled self._ims = [] self._im_kwargs = [] for ax, kwargs in self._new_axes : self._im_kwargs.append(kwargs) self._ims.append(MakeReflectPPI(data['vals'][0], data['lats'], data['lons'], meth='pcmesh', axis_labels=False, mask=False, ax=ax, **kwargs)) # Reset the "stack" self._new_axes = [] def add_axes(self, ax, **kwargs) : """ Display the animation on Axes *ax*. Can also specify what *kwargs* to pass to the call of :func:`MakeReflectPPI` except 'meth', 'axis_labels', 'ax', and 'mask'. Others such as 'zorder' and 'alpha' can be passed. """ self._new_axes.append((ax, kwargs)) def nextframe(self, frameindex, *args) : if self.time_markers is None : self._advance_anim() print("CurrTime:", str(self.curr_time)) return self._ims frametime = self.time_markers[frameindex % self.save_count] if frametime > self.endTime : # We have no additional data to display. # Just simply hold until frametime cycles return None if frameindex % self.save_count == 0 and frameindex > 0 : # Force a cycling of the data while self.startTime < self.curr_time <= self.endTime : self._rd.next() print("Skipping ahead:", str(self.curr_time)) if frametime >= self.curr_time : while self.next_time < frametime < self.endTime : # Dropping frames self._rd.next() print("Dropped:", str(self.curr_time)) print("CurrTime:", str(self.curr_time)) self._advance_anim() return self._ims else : return None def add_axes(self, ax, **kwargs) : """ Display the animation on Axes *ax*. Can also specify what *kwargs* to pass to the call of :func:`MakeReflectPPI` except 'meth', 'axis_labels', 'ax', and 'mask'. Others such as 'zorder' and 'alpha' can be passed. """ self._new_axes.append((ax, kwargs)) """