From 45f21ef063ef547f232cb8918dcd96314b616f5d Mon Sep 17 00:00:00 2001 From: Mate Lampert <52581826+thelampire@users.noreply.github.com> Date: Mon, 18 Nov 2019 19:27:21 -0500 Subject: [PATCH 01/27] Update plot.py Addition of the options['Plot units'] for the plot.py. It uses the unit_conversion from the updated tools. The following description is added to the options part of the plot.py: 'Plot units': The plotting units for each axis. It can be a dictionary or a list. Its use is different for the two cases as such: Dictionary input: keywords: axis name (e.g. 'Device R'), value: unit (e.g. 'mm') The number of keys is not limited. The ones from the axes will be used. e.g.: options['Plot units']={'Device R':'mm', 'Device z':'mm', 'Time':'ms'} List input: the number of values should correspond to the axes input as such: e.g.: axes=['Device R','Device z','Time'] --> options['Plot units']=['mm','mm','ms'] Further modifications: Cleaned the unnecessary old code fragments from the commented lines. --- flap/plot.py | 526 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 323 insertions(+), 203 deletions(-) diff --git a/flap/plot.py b/flap/plot.py index 95bafeb..8ad08f4 100644 --- a/flap/plot.py +++ b/flap/plot.py @@ -134,13 +134,14 @@ def axes_to_pdd_list(d,axes): class PlotAnimation: - def __init__(self, ax_list, axes, d, xdata, ydata, tdata, xdata_range, ydata_range, + def __init__(self, ax_list, axes, axes_unit_conversion, d, xdata, ydata, tdata, xdata_range, ydata_range, cmap_obj, contour_levels, coord_t, coord_x, coord_y, cmap, options, xrange, yrange, zrange, image_like, plot_options, language, plot_id, gs): self.ax_list = ax_list - self.axes = axes + self.axes = axes + self.axes_unit_conversion=axes_unit_conversion self.contour_levels = contour_levels self.cmap = cmap self.cmap_obj = cmap_obj @@ -176,26 +177,6 @@ def __init__(self, ax_list, axes, d, xdata, ydata, tdata, xdata_range, ydata_ran def animate(self): - #These lines do the coordinate unit conversion - self.axes_unit_conversion=np.zeros(len(self.axes)) - self.axes_unit_conversion[:]=1. - - if self.options['Plot units'] is not None: - unit_length=len(self.options['Plot units']) - if unit_length > 3: - raise ValueError('Only three units are allowed for the three coordinates.') - unit_conversion_coeff={} - for plot_unit_name in self.options['Plot units']: - for index_data_unit in range(len(self.d.coordinates)): - if (plot_unit_name == self.d.coordinates[index_data_unit].unit.name): - data_coordinate_unit=self.d.coordinates[index_data_unit].unit.unit - plot_coordinate_unit=self.options['Plot units'][plot_unit_name] - unit_conversion_coeff[plot_unit_name]=flap.tools.unit_conversion(original_unit=data_coordinate_unit, - new_unit=plot_coordinate_unit) - for index_axes in range(len(self.axes)): - if self.axes[index_axes] in self.options['Plot units']: - self.axes_unit_conversion[index_axes]=unit_conversion_coeff[self.axes[index_axes]] - pause_ax = plt.figure(self.plot_id.figure).add_axes((0.78, 0.94, 0.1, 0.04)) self.pause_button = Button(pause_ax, 'Pause', hovercolor='0.975') self.pause_button.on_clicked(self._pause_animation) @@ -241,8 +222,6 @@ def animate(self): self.d.coordinates[axes_coordinate_decrypt[j_check]].unit.unit): self.ax_act.axis('equal') - - time_index = [slice(0,dim) for dim in self.d.data.shape] time_index[self.coord_t.dimension_list[0]] = 0 time_index = tuple(time_index) @@ -255,13 +234,6 @@ def animate(self): np.nanmax(self.d.data)] self.vmin = self.zrange[0] self.vmax = self.zrange[1] - -# if (self.zrange is None): -# self.vmin = np.nanmin(self.d.data[time_index]) -# self.vmax = np.nanmax(self.d.data[time_index]) -# else: -# self.vmin = self.zrange[0] -# self.vmax = self.zrange[1] if (self.vmax <= self.vmin): @@ -281,8 +253,6 @@ def animate(self): if (self.image_like): try: - #There is a problem here, but I cant find it. Image is rotated with 90degree here, but not in anim-image. - #if (self.coord_x.dimension_list[0] == 0): if (self.coord_x.dimension_list[0] < self.coord_y.dimension_list[0]): im = np.clip(np.transpose(self.d.data[time_index]),self.vmin,self.vmax) else: @@ -294,9 +264,9 @@ def animate(self): except Exception as e: raise e else: - if (len(self.xdata.shape) == 3 and len(self.xdata.shape) == 3): - #xgrid, ygrid = flap.tools.grid_to_box(self.xdata[0,:,:],self.ydata[0,:,:]) #Same issue, time is not necessarily the first flap.coordinate. - xgrid, ygrid = flap.tools.grid_to_box(self.xdata[0,:,:]*self.axes_unit_conversion[0],self.ydata[0,:,:]*self.axes_unit_conversion[1]) #Same issue, time is not necessarily the first flap.coordinate. + if (len(self.xdata.shape) == 3 and len(self.ydata.shape) == 3): + xgrid, ygrid = flap.tools.grid_to_box(self.xdata[time_index]*self.axes_unit_conversion[0], + self.ydata[time_index]*self.axes_unit_conversion[1]) #Same issue, time is not necessarily the first flap.coordinate. else: xgrid, ygrid = flap.tools.grid_to_box(self.xdata*self.axes_unit_conversion[0], self.ydata*self.axes_unit_conversion[1]) @@ -348,67 +318,56 @@ def animate(self): if ((self.efit_options[setting.capitalize()+' X']) and (self.efit_options[setting.capitalize()+' Y'])): try: - R_object=flap.get_data_object(self.efit_options[setting.capitalize()+' X'],exp_id=self.d.exp_id) + R_object=flap.get_data_object_ref(self.efit_options[setting.capitalize()+' X'],exp_id=self.d.exp_id) except: raise ValueError("The objects "+self.efit_options[setting.capitalize()+' X']+" cannot be read.") try: - Z_object=flap.get_data_object(self.efit_options[setting.capitalize()+' Y'],exp_id=self.d.exp_id) + Z_object=flap.get_data_object_ref(self.efit_options[setting.capitalize()+' Y'],exp_id=self.d.exp_id) except: raise ValueError("The objects "+self.efit_options[setting.capitalize()+' Y']+" cannot be read.") if (len(R_object.data.shape) != 2 or len(Z_object.data.shape) != 2): raise ValueError("The "+setting.capitalize()+' Y'+" data is not 1D. Use 2D or modify data reading.") + self.efit_data[setting]['Data']=np.asarray([R_object.data,Z_object.data]) - self.efit_data[setting]['Time']=R_object.coordinate('Time')[0][:,0] #TIME IS NOT ALWAYS THE FIRST COORDINATE, ELLIPSIS CHANGE SHOULD BE IMPLEMENTED - elif (self.efit_options[setting.capitalize()+' XY']): + elif (self.efit_options[setting.capitalize()+' 2D']): try: - R_object=flap.get_data_object(self.efit_options[setting.capitalize()+' 2D'],exp_id=self.d.exp_id) + R_object=flap.get_data_object_ref(self.efit_options[setting.capitalize()+' 2D'],exp_id=self.d.exp_id) except: raise ValueError(setting.capitalize()+' 2D data is not available. FLAP data object needs to be read first.') if R_object.data.shape[2] == 2: self.efit_data[setting]['Data']=np.asarray([R_object.data[:,:,0],R_object.data[:,:,1]]) else: - raise ValueError(setting.capitalize()+' XY data needs to be in the format [n_time,n_points,2 (xy)') - self.efit_data[setting]['Time']=R_object.coordinate('Time')[0][:,0,0] #TIME IS NOT ALWAYS THE FIRST COORDINATE, ELLIPSIS CHANGE SHOULD BE IMPLEMENTED + raise ValueError(setting.capitalize()+' 2D data needs to be in the format [n_time,n_points,2 (xy)]') else: raise ValueError('Both '+setting.capitalize()+' X and'+ setting.capitalize()+' Y or '+ setting.capitalize()+' 2D need to be set.') + #Finding the time coordinate + for index_time in range(len(R_object.coordinates)): + if (R_object.coordinates[index_time].unit.name == 'Time'): + time_dimension_efit=R_object.coordinates[index_time].dimension_list[0] + time_index_efit=index_time + #Gathering the 1D time vector for the EFIT data + time_data=R_object.coordinate('Time')[0] + coordinate_index=[0]*len(time_data.shape) + coordinate_index[time_dimension_efit]=Ellipsis + self.efit_data[setting]['Time']=time_data[tuple(coordinate_index)] + #Gathering the spatial unit for translation for index_coordinate in range(len(self.d.coordinates)): if ((self.d.coordinates[index_coordinate].unit.name in self.axes) and (self.d.coordinates[index_coordinate].unit.name != 'Time')): - coordinate_index=index_coordinate - #Spatial unit translation (mm vs m usually) - if (R_object.data_unit.unit != self.d.coordinates[coordinate_index].unit.unit): - try: - coeff_efit_spatial=flap.tools.spatial_unit_translation(R_object.data_unit.unit) - except: - raise ValueError("Time unit translation cannot be made. Check the time unit of the object.") - try: - coeff_data_spatial=flap.tools.spatial_unit_translation(self.d.coordinates[coordinate_index].unit.unit) - except: - raise ValueError("Spatial unit translation cannot be made. Check the time unit of the object.") - #print('Spatial unit translation factor: '+str(coeff_efit_spatial/coeff_data_spatial)) - self.efit_data[setting]['Data'] *= coeff_efit_spatial/coeff_data_spatial + spatial_coordinate_index=index_coordinate + self.efit_data[setting]['Data'] *= flap.tools.unit_conversion(original_unit=R_object.data_unit.unit, + new_unit=self.d.coordinates[spatial_coordinate_index].unit.unit) #Assumes that R and z data is in the same units + #Time translation (usually ms vs s) for index_time in range(len(self.d.coordinates)): if (self.d.coordinates[index_time].unit.name == 'Time'): - time_index_data=index_time - - for index_time in range(len(R_object.coordinates)): - if (self.d.coordinates[index_time].unit.name == 'Time'): - time_index_efit=index_time + time_index_data=index_time - if (R_object.coordinates[time_index_efit].unit.unit != self.d.coordinates[time_index_data].unit.unit): - try: - coeff_efit_time=flap.tools.time_unit_translation(R_object.coordinates[time_index_efit].unit.unit) - except: - raise ValueError("Time unit translation cannot be made. Check the time unit of the object.") - try: - coeff_data_time=flap.tools.time_unit_translation(self.d.coordinates[time_index_data].unit.unit) - except: - raise ValueError("Time unit translation cannot be made. Check the time unit of the object.") - self.efit_data[setting]['Time'] *= coeff_efit_time/coeff_data_time + self.efit_data[setting]['Time'] *= flap.tools.unit_conversion(original_unit=R_object.coordinates[time_index_efit].unit.unit, + new_unit=self.d.coordinates[time_index_data].unit.unit) else: raise ValueError(setting.capitalize()+' keywords are not set for the data objects.') @@ -427,55 +386,49 @@ def animate(self): raise ValueError('Flux XY data is not available. FLAP data object needs to be read first.') if len(flux_object.data.shape) != 3: raise ValueError('Flux XY data needs to be a 3D matrix (r,z,t), not necessarily in this order.') - if (flux_object.coordinates[0].unit.name != 'Time'): - raise ValueError('Time should be the first coordinate in the flux data object.') self.efit_data['flux']['Data']=flux_object.data - self.efit_data['flux']['Time']=flux_object.coordinate('Time')[0][:,0,0] #TIME IS NOT ALWAYS THE FIRST COORDINATE, ELLIPSIS CHANGE SHOULD BE IMPLEMENTED - self.efit_data['flux']['X coord']=flux_object.coordinate(flux_object.coordinates[1].unit.name)[0] - self.efit_data['flux']['Y coord']=flux_object.coordinate(flux_object.coordinates[2].unit.name)[0] + + for index_time in range(len(flux_object.coordinates)): + if (flux_object.coordinates[index_time].unit.name == 'Time'): + time_dimension_efit=flux_object.coordinates[index_time].dimension_list[0] + time_index_efit=index_time + #Gathering the 1D time vector for the EFIT data + time_data=flux_object.coordinate('Time')[0] + coordinate_index=[0]*len(time_data.shape) + coordinate_index[time_dimension_efit]=Ellipsis + self.efit_data['flux']['Time']=time_data[tuple(coordinate_index)] + + try: + self.efit_data['flux']['X coord']=flux_object.coordinate(self.axes[0])[0] + self.efit_data['flux']['Y coord']=flux_object.coordinate(self.axes[1])[0] + except: + raise ValueError('The requested axes, '+self.axes[0]+' and '+self.axes[1]+', are not available in the given EFIT data.') + for index_coordinate in range(len(self.d.coordinates)): if ((self.d.coordinates[index_coordinate].unit.name in self.axes) and (self.d.coordinates[index_coordinate].unit.name != 'Time')): coordinate_index=index_coordinate - #Spatial unit translation (mm vs m usually) - if (flux_object.data_unit.unit != self.d.coordinates[coordinate_index].unit.unit): - try: - coeff_efit_spatial=flap.tools.spatial_unit_translation(flux_object.data_unit.unit) - except: - raise ValueError("Time unit translation cannot be made. Check the time unit of the object.") - try: - coeff_data_spatial=flap.tools.spatial_unit_translation(self.d.coordinates[coordinate_index].unit.unit) - except: - raise ValueError("Spatial unit translation cannot be made. Check the time unit of the object.") - #print('Spatial unit translation factor: '+str(coeff_efit_spatial/coeff_data_spatial)) - self.efit_data['flux']['X coord'] *= coeff_efit_spatial/coeff_data_spatial - self.efit_data['flux']['Y coord'] *= coeff_efit_spatial/coeff_data_spatial + + #Spatial unit translation (mm vs m usually) + spat_conversion_coeff=flap.tools.unit_conversion(original_unit=flux_object.data_unit.unit, + new_unit=self.d.coordinates[coordinate_index].unit.unit) + + self.efit_data['flux']['X coord'] *= spat_conversion_coeff + self.efit_data['flux']['Y coord'] *= spat_conversion_coeff #Time translation (usually ms vs s) for index_time in range(len(self.d.coordinates)): if (self.d.coordinates[index_time].unit.name == 'Time'): - time_index_data=index_time - - for index_time in range(len(flux_object.coordinates)): - if (flux_object.coordinates[index_time].unit.name == 'Time'): - time_index_efit=index_time + time_index_data=index_time - if (flux_object.coordinates[time_index_efit].unit.unit != self.d.coordinates[time_index_data].unit.unit): - try: - coeff_efit_time=flap.tools.time_unit_translation(flux_object.coordinates[time_index_efit].unit.unit) - except: - raise ValueError("Time unit translation cannot be made. Check the time unit of the object.") - try: - coeff_data_time=flap.tools.time_unit_translation(self.d.coordinates[time_index_data].unit.unit) - except: - raise ValueError("Time unit translation cannot be made. Check the time unit of the object.") - self.efit_data['flux']['Time'] *= coeff_efit_time/coeff_data_time + self.efit_data['flux']['Time'] *= flap.tools.unit_conversion(original_unit=flux_object.coordinates[time_index_efit].unit.unit, + new_unit=self.d.coordinates[time_index_data].unit.unit) #Interpolating EFIT data for the time vector of the data if ((self.efit_data['flux']['Data'] != []) and (self.efit_data['flux']['Time'] != [])): self.efit_data['flux']['Data resampled']=np.zeros([self.tdata.shape[0], - self.efit_data['flux']['Data'].shape[1], + self.efit_data['flux']['Data'].shape[1], #THESE SHAPES NEED TO BE REVISED self.efit_data['flux']['Data'].shape[2]]) self.efit_data['flux']['X coord resampled']=np.zeros([self.tdata.shape[0], self.efit_data['flux']['X coord'].shape[1], @@ -484,7 +437,7 @@ def animate(self): self.efit_data['flux']['Y coord'].shape[1], self.efit_data['flux']['Y coord'].shape[2]]) - for index_x in range(0,self.efit_data['flux']['Data'].shape[1]): + for index_x in range(0,self.efit_data['flux']['Data'].shape[1]): #THESE SHAPES NEED TO BE REVISED for index_y in range(0,self.efit_data['flux']['Data'].shape[2]): self.efit_data['flux']['Data resampled'][:,index_x,index_y]=np.interp(self.tdata,self.efit_data['flux']['Time'], self.efit_data['flux']['Data'][:,index_x,index_y]) @@ -495,20 +448,24 @@ def animate(self): if (self.xrange is not None): - plt.xlim(self.xrange[0]*self.axes_unit_conversion[0],self.xrange[1]*self.axes_unit_conversion[0]) + plt.xlim(self.xrange[0]*self.axes_unit_conversion[0], + self.xrange[1]*self.axes_unit_conversion[0]) if (self.yrange is not None): - plt.ylim(self.yrange[0]*self.axes_unit_conversion[1],self.yrange[1]*self.axes_unit_conversion[1]) + plt.ylim(self.yrange[0]*self.axes_unit_conversion[1], + self.yrange[1]*self.axes_unit_conversion[1]) if self.axes_unit_conversion[0] == 1.: plt.xlabel(self.ax_list[0].title(language=self.language)) else: - plt.xlabel(self.ax_list[0].title(language=self.language, new_unit=self.options['Plot units'][self.axes[0]])) + plt.xlabel(self.ax_list[0].title(language=self.language, + new_unit=self.options['Plot units'][self.axes[0]])) if self.axes_unit_conversion[1] == 1.: plt.ylabel(self.ax_list[1].title(language=self.language)) else: - plt.ylabel(self.ax_list[1].title(language=self.language, new_unit=self.options['Plot units'][self.axes[1]])) + plt.ylabel(self.ax_list[1].title(language=self.language, + new_unit=self.options['Plot units'][self.axes[1]])) if (self.options['Log x']): plt.xscale('log') @@ -552,7 +509,6 @@ def animate_plot(self, it): if (self.image_like): try: -# if (self.coord_x.dimension_list[0] == 0): if (self.coord_x.dimension_list[0] < self.coord_y.dimension_list[0]): im = np.clip(np.transpose(self.d.data[time_index]),self.vmin,self.vmax) else: @@ -567,9 +523,12 @@ def animate_plot(self, it): raise e else: if (len(self.xdata.shape) == 3 and len(self.ydata.shape) == 3): - xgrid, ygrid = flap.tools.grid_to_box(self.xdata[time_index,:,:]*self.axes_unit_conversion[0],self.ydata[time_index,:,:]*self.axes_unit_conversion[1]) #Same issue, time is not necessarily the first flap.coordinate. + xgrid, ygrid = flap.tools.grid_to_box(self.xdata[time_index]*self.axes_unit_conversion[0], + self.ydata[time_index]*self.axes_unit_conversion[1]) #Same issue, time is not necessarily the first flap.coordinate. + print('lofasz') else: - xgrid, ygrid = flap.tools.grid_to_box(self.xdata*self.axes_unit_conversion[0],self.ydata*self.axes_unit_conversion[1]) + xgrid, ygrid = flap.tools.grid_to_box(self.xdata*self.axes_unit_conversion[0], + self.ydata*self.axes_unit_conversion[1]) im = np.clip(np.transpose(self.d.data[time_index]),self.vmin,self.vmax) try: plt.pcolormesh(xgrid,ygrid,im,norm=self.norm,cmap=self.cmap,vmin=self.vmin, @@ -581,26 +540,23 @@ def animate_plot(self, it): for setting in ['limiter','separatrix']: if (self.efit_options['Plot '+setting]): self.ax_act.set_autoscale_on(False) - im = plt.plot(self.efit_data[setting]['Data resampled'][0,it,:], - self.efit_data[setting]['Data resampled'][1,it,:], + im = plt.plot(self.efit_data[setting]['Data resampled'][0,it,:]*self.axes_unit_conversion[0], + self.efit_data[setting]['Data resampled'][1,it,:]*self.axes_unit_conversion[1], color=self.efit_options[setting.capitalize()+' color']) if (self.efit_options['Plot flux']): self.ax_act.set_autoscale_on(False) - #im = plt.contour(self.efit_data['flux']['X coord'][it,:,:], - # self.efit_data['flux']['Y coord'][it,:,:], - # self.efit_data['flux']['Data resampled'][it,:,:], - # levels=self.efit_options['Flux nlevel']) - - im = plt.contour(self.efit_data['flux']['X coord resampled'][it,:,:].transpose(), - self.efit_data['flux']['Y coord resampled'][it,:,:].transpose(), + im = plt.contour(self.efit_data['flux']['X coord resampled'][it,:,:].transpose()*self.axes_unit_conversion[0], + self.efit_data['flux']['Y coord resampled'][it,:,:].transpose()*self.axes_unit_conversion[1], self.efit_data['flux']['Data resampled'][it,:,:], levels=self.efit_options['Flux nlevel']) if (self.xrange is not None): - self.ax_act.set_xlim(self.xrange[0],self.xrange[1]) + self.ax_act.set_xlim(self.xrange[0]*self.axes_unit_conversion[0], + self.xrange[1]*self.axes_unit_conversion[0]) if (self.yrange is not None): - self.ax_act.set_ylim(self.yrange[0],self.yrange[1]) + self.ax_act.set_ylim(self.yrange[0]*self.axes_unit_conversion[1], + self.yrange[1]*self.axes_unit_conversion[1]) if self.axes_unit_conversion[0] == 1.: plt.xlabel(self.ax_list[0].title(language=self.language)) @@ -616,6 +572,9 @@ def animate_plot(self, it): if self.axes[2] in self.options['Plot units']: time_unit=self.options['Plot units'][self.axes[2]] time_coeff=self.axes_unit_conversion[2] + else: + time_unit=self.coord_t.unit.unit + time_coeff=1. else: time_unit=self.coord_t.unit.unit time_coeff=1. @@ -629,7 +588,7 @@ def _reset_animation(self, event): self.anim.event_source.stop() self.speed = 40. self.anim = animation.FuncAnimation(plt.figure(self.plot_id.figure), self.animate_plot, - len(self.tdata),interval=self.speed,blit=False) + len(self.tdata),interval=self.speed,blit=False) self.anim.event_source.start() self.pause = False @@ -708,12 +667,6 @@ def clear(self): self.plot_data = [] self.plt_axis_list = None self.options = [] - - -# def __del__(self): -# if (self.plt_axis_list is not None): -# for ax in self.plt_axis_list: -# ax.remove() def check_axes(self,d, axes, clear=True, default_axes=None, force=False): """ Checks whether the required plot axes are correct, present and compatible with the self PlotID. @@ -779,9 +732,6 @@ def check_axes(self,d, axes, clear=True, default_axes=None, force=False): if (not clear and (self.axes is not None)): for ax_in,ax_plot in zip(ax_list, self.axes): # If none of the plot and the new axis has name and ot unit, the new axis will not have it either -# if ((ax_plot.name == '') or (ax_plot.unit == '') or -# (ax_in.name == '') or (ax_in.unit == '')): -# ax_out_list.append(Unit()) if ((ax_in.name != ax_plot.name) or (ax_in.unit != ax_plot.unit)): if (force): u = flap.coordinate.Unit() @@ -997,6 +947,13 @@ def _plot(data_object, 'nlevels': Number of contour lines for the flux surface plotting 'Prevent saturation': Prevents saturation of the video signal when it exceeds zrange[1] It uses data modulo zrange[1] to overcome the saturation. (works for animation) + 'Plot units': The plotting units for each axis. It can be a dictionary or a list. Its + use is different for the two cases as such: + Dictionary input: keywords: axis name (e.g. 'Device R'), value: unit (e.g. 'mm') + The number of keys is not limited. The ones from the axes will be used. + e.g.: options['Plot units']={'Device R':'mm', 'Device z':'mm', 'Time':'ms'} + List input: the number of values should correspond to the axes input as such: + e.g.: axes=['Device R','Device z','Time'] --> options['Plot units']=['mm','mm','ms'] """ @@ -1052,7 +1009,6 @@ def _plot(data_object, plt.cla() if (_options['Clear'] ): _plot_id = PlotID() -# _plot_id.clear() if (_plot_id.figure is not None): plt.figure(_plot_id.figure) else: @@ -1125,8 +1081,40 @@ def _plot(data_object, video_codec_code = 'XVID' else: raise ValueError("Cannot write video in format '"+_options['Video format']+"'.") - - + + #These lines do the coordinate unit conversion + axes_unit_conversion=np.zeros(len(axes)) + axes_unit_conversion[:]=1. + + if _options['Plot units'] is not None: + unit_length=len(_options['Plot units']) + if unit_length > 3: + raise ValueError('Only three units are allowed for the three coordinates.') + #For some reason the input list or dictionary can morph into a list or a dict class + possible_types=[list,dict,'',''] + if not (type(_options['Plot units']) in possible_types): + raise TypeError('The \'Plot units\' option needs to be either a dictionary or a list.') + #Converting the input list into dictionary: + if (type(_options['Plot units']) is list or + type(_options['Plot units']) == ''): + temp_units= _options['Plot units'][:] + _options['Plot units']={} + for index_axes in range(len(axes)): + _options['Plot units'][axes[index_axes]]=temp_units[index_axes] + #Finding the corresponding data coordinate to the input unit conversion and converting + unit_conversion_coeff={} + for plot_unit_name in _options['Plot units']: + for index_data_unit in range(len(d.coordinates)): + if (plot_unit_name == d.coordinates[index_data_unit].unit.name): + data_coordinate_unit=d.coordinates[index_data_unit].unit.unit + plot_coordinate_unit=_options['Plot units'][plot_unit_name] + unit_conversion_coeff[plot_unit_name]=flap.tools.unit_conversion(original_unit=data_coordinate_unit, + new_unit=plot_coordinate_unit) + #Saving the coefficients in the same order as the axes are + for index_axes in range(len(axes)): + if axes[index_axes] in _options['Plot units']: + axes_unit_conversion[index_axes]=unit_conversion_coeff[axes[index_axes]] + # X range and Z range is processed here, but Y range not as it might have multiple entries for some plots xrange = _options['X range'] if (xrange is not None): @@ -1144,6 +1132,10 @@ def _plot(data_object, contour_levels = _options['Levels'] # Here _plot_id is a valid (maybe empty) PlotID + """ --------------------------------------------- + | XY AND SCATTER PLOT DEFINITION | + ---------------------------------------------""" + if ((_plot_type == 'xy') or (_plot_type == 'scatter')): # 1D plots: xy, scatter and complex versions # Checking whether oveplotting into the same plot type @@ -1155,7 +1147,7 @@ def _plot(data_object, if ((_plot_id.plot_type is not None) and ((_plot_id.plot_type != 'xy') and (_plot_id.plot_type != 'scatter')) or (_plot_id.plot_subtype is not None) and (_plot_id.plot_subtype != subtype)): raise ValueError("Overplotting into different plot type. Use option={'Clear':True} to erase first.") - # Processing axes + #Processing axes default_axes = [d.coordinates[0].unit.name, '__Data__'] try: pdd_list, ax_list = _plot_id.check_axes(d, @@ -1252,29 +1244,55 @@ def _plot(data_object, _plot_opt = _plot_options[0] if (type(_plot_opt) is not dict): raise ValueError("Plot options should be a dictionary or list of dictionaries.") - + + if xerr is not None: + xerr_plot=xerr*axes_unit_conversion[0] + else: + xerr_plot=xerr + if (_plot_type == 'xy'): if (plot_error): - ax.errorbar(x,y,xerr=xerr,yerr=yerr,errorevery=errorevery,**_plot_opt) + ax.errorbar(x*axes_unit_conversion[0], + y, + xerr=xerr_plot, + yerr=yerr,errorevery=errorevery,**_plot_opt) else: - ax.plot(x,y,**_plot_opt) + ax.plot(x*axes_unit_conversion[0], + y,**_plot_opt) else: if (plot_error): - ax.errorbar(x,y,xerr=xerr,yerr=yerr,errorevery=errorevery,fmt='o',**_plot_opt) + ax.errorbar(x*axes_unit_conversion[0], + y, + xerr=xerr_plot, + yerr=yerr, + errorevery=errorevery,fmt='o',**_plot_opt) else: - ax.scatter(x,y,**_plot_opt) + ax.scatter(x*axes_unit_conversion[0], + y,**_plot_opt) - ax.set_xlabel(ax_list[0].title(language=language)) - ax.set_ylabel(ax_list[1].title(language=language)) if (_options['Log x']): ax.set_xscale('log') if (_options['Log y']): ax.set_yscale('log') + if (xrange is not None): - ax.set_xlim(xrange[0],xrange[1]) + ax.set_xlim(xrange[0]*axes_unit_conversion[0], + xrange[1]*axes_unit_conversion[0]) + if (yrange is not None): - ax.set_ylim(yrange[0],yrange[1]) + ax.set_ylim(yrange[0], + yrange[1]) + # Setting axis labels + if axes_unit_conversion[0] == 1.: + ax.set_xlabel(ax_list[0].title(language=language)) + else: + ax.set_xlabel(ax_list[0].title(language=language, + new_unit=_options['Plot units'][axes[0]])) + ax.set_ylabel(ax_list[1].title(language=language)) + + + title = ax.get_title() if (title is None): title = '' @@ -1395,30 +1413,55 @@ def _plot(data_object, _plot_opt[i_comp] if (type(_plot_opt) is not dict): raise ValueError("Plot options should be a dictionary or list of dictionaries.") - + + if xerr is not None: + xerr_plot=xerr*axes_unit_conversion[0] + else: + xerr_plot=xerr + if (_plot_type == 'xy'): if (plot_error): - ax.errorbar(x,y,xerr=xerr,yerr=yerr,errorevery=errorevery,**_plot_opt) + ax.errorbar(x*axes_unit_conversion[0], + y, + xerr=xerr_plot, + yerr=yerr, + errorevery=errorevery,**_plot_opt) else: - ax.plot(x,y,**_plot_opt) + ax.plot(x*axes_unit_conversion[0], + y,**_plot_opt) else: if (plot_error): - ax.errorbar(x,y,xerr=xerr,yerr=yerr,errorevery=errorevery,fmt='o',**_plot_opt) + ax.errorbar(x*axes_unit_conversion[0], + y, + xerr=xerr_plot, + yerr=yerr, + errorevery=errorevery,fmt='o',**_plot_opt) else: - ax.scatter(x,y,**_plot_opt) - - # Setting axis labels - ax.set_xlabel(ax_list[0].title(language=language)) - ax.set_ylabel(ax_list[1].title(language=language,complex_txt=[comptype,i_comp])) + ax.scatter(x*axes_unit_conversion[0], + y,**_plot_opt) if (_options['Log x']): ax.set_xscale('log') if (_options['Log y']): ax.set_yscale('log') + if (xrange is not None): - ax.set_xlim(xrange[0],xrange[1]) + ax.set_xlim(xrange[0]*axes_unit_conversion[0], + xrange[1]*axes_unit_conversion[0]) + if (yrange is not None): - ax.set_ylim(yrange[i_comp][0],yrange[i_comp][1]) + ax.set_ylim(yrange[i_comp][0],yrange[i_comp][1]) + # Setting axis labels + if axes_unit_conversion[0] == 1.: + ax.set_xlabel(ax_list[0].title(language=language)) + else: + ax.set_xlabel(ax_list[0].title(language=language, + new_unit=_options['Plot units'][axes[0]])) + + + ax.set_ylabel(ax_list[1].title(language=language), + complex_txt=[comptype,i_comp]) + title = ax.get_title() if (title is None): title = '' @@ -1440,6 +1483,10 @@ def _plot(data_object, _plot_id.plot_subtype = 1 # End of complex xy and scatter plot + + """ --------------------------------------------- + | MULTIXY PLOT DEFINITION | + ---------------------------------------------""" elif (_plot_type == 'multi xy'): if (len(d.shape) > 2): @@ -1607,22 +1654,42 @@ def _plot(data_object, errorevery = int(round(len(x)/errorbars)) if (errorevery < 1): errorevery = 1 + if xerr is not None: + xerr_plot=xerr*axes_unit_conversion[0] + else: + xerr_plot=xerr if (plot_error): - ax.errorbar(x,y,xerr=xerr,yerr=yerr,errorevery=errorevery,**_plot_opt) + ax.errorbar(x*axes_unit_conversion[0], + y, + xerr=xerr_plot, + yerr=yerr, + errorevery=errorevery,**_plot_opt) else: - ax.plot(x,y,**_plot_opt) - + ax.plot(x*axes_unit_conversion[0], + y, + **_plot_opt) + if (xrange is not None): - ax.set_xlim(xrange[0],xrange[1]) + ax.set_xlim(xrange[0]*axes_unit_conversion[0], + xrange[1]*axes_unit_conversion[0]) + if (yrange is not None): - ax.set_ylim(yrange[0],yrange[1]) - ax.set_xlabel(ax_list[0].title(language=language)) - ax.set_ylabel(ax_list[1].title(language=language)) + ax.set_ylim(yrange[0],yrange[1]) + + if axes_unit_conversion[0] == 1.: + ax.set_xlabel(ax_list[0].title(language=language)) + else: + ax.set_xlabel(ax_list[0].title(language=language, + new_unit=_options['Plot units'][axes[0]])) + + ax.set_ylabel(ax_list[1].title(language=language)) + if (_options['Log x']): ax.set_xscale('log') if (_options['Log y']): ax.set_yscale('log') title = ax.get_title() + if (title is None): title = '' if (title[-3:] != '...'): @@ -1642,6 +1709,10 @@ def _plot(data_object, ax.set_title(title) _plot_id.plot_subtype = 0 # real multi xy plot + """ --------------------------------------------- + | IMAGE AND CONTOUR PLOT DEFINITION | + ---------------------------------------------""" + elif ((_plot_type == 'image') or (_plot_type == 'contour')): if (d.data is None): raise ValueError("Cannot plot DataObject without data.") @@ -1688,6 +1759,7 @@ def _plot(data_object, coord_x = pdd_list[0].value coord_y = pdd_list[1].value + if (_plot_type == 'image'): if ((coord_x.mode.equidistant) and (len(coord_x.dimension_list) == 1) and (coord_y.mode.equidistant) and (len(coord_y.dimension_list) == 1)): @@ -1719,11 +1791,11 @@ def _plot(data_object, else: image_like = False if (image_like): - xdata_range = coord_x.data_range(data_shape=d.shape)[0] - ydata_range = coord_y.data_range(data_shape=d.shape)[0] + xdata_range = coord_x.data_range(data_shape=d.shape)[0] + ydata_range = coord_y.data_range(data_shape=d.shape)[0] else: - ydata,ydata_low,ydata_high = coord_y.data(data_shape=d.shape) xdata,xdata_low,xdata_high = coord_x.data(data_shape=d.shape) + ydata,ydata_low,ydata_high = coord_y.data(data_shape=d.shape) if (zrange is None): vmin = np.nanmin(d.data) @@ -1771,16 +1843,23 @@ def _plot(data_object, raise e else: if (_plot_type == 'image'): - xgrid, ygrid = flap.tools.grid_to_box(xdata,ydata) + xgrid, ygrid = flap.tools.grid_to_box(xdata*axes_unit_conversion[0], + ydata*axes_unit_conversion[1]) try: - img = ax.pcolormesh(xgrid,ygrid,np.clip(np.transpose(d.data),vmin,vmax),norm=norm,cmap=cmap,vmin=vmin, + img = ax.pcolormesh(xgrid,ygrid, + np.clip(np.transpose(d.data),vmin,vmax), + norm=norm,cmap=cmap,vmin=vmin, vmax=vmax,**_plot_opt) except Exception as e: raise e else: try: - img = ax.contourf(xdata,ydata,np.clip(d.data,vmin,vmax),contour_levels,norm=norm, - origin='lower',cmap=cmap,vmin=vmin,vmax=vmax,**_plot_opt) + img = ax.contourf(xdata*axes_unit_conversion[0], + ydata*axes_unit_conversion[1], + np.clip(d.data,vmin,vmax), + contour_levels,norm=norm, + origin='lower',cmap=cmap, + vmin=vmin,vmax=vmax,**_plot_opt) except Exception as e: raise e @@ -1793,15 +1872,30 @@ def _plot(data_object, cbar.set_label(d.data_unit.name+' '+unit_name) if (xrange is not None): - ax.set_xlim(xrange[0],xrange[1]) + ax.set_xlim(xrange[0]*axes_unit_conversion[0], + xrange[1]*axes_unit_conversion[0]) + if (yrange is not None): - ax.set_ylim(yrange[0],yrange[1]) - ax.set_xlabel(ax_list[0].title(language=language)) - ax.set_ylabel(ax_list[1].title(language=language)) + ax.set_ylim(yrange[0]*axes_unit_conversion[1], + yrange[1]*axes_unit_conversion[1]) + + if axes_unit_conversion[0] == 1.: + ax.set_xlabel(ax_list[0].title(language=language)) + else: + ax.set_xlabel(ax_list[0].title(language=language, + new_unit=_options['Plot units'][axes[0]])) + + if axes_unit_conversion[1] == 1.: + ax.set_ylabel(ax_list[1].title(language=language)) + else: + ax.set_ylabel(ax_list[1].title(language=language, + new_unit=_options['Plot units'][axes[1]])) + if (_options['Log x']): ax.set_xscale('log') if (_options['Log y']): - ax.set_yscale('log') + ax.set_yscale('log') + title = ax.get_title() if (title is None): title = '' @@ -1820,6 +1914,10 @@ def _plot(data_object, else: title += ',...' ax.set_title(title) + + """ ----------------------------------------------- + | ANIM-IMAGE AND ANIM-CONTOUR PLOT DEFINITION | + -----------------------------------------------""" elif ((_plot_type == 'anim-image') or (_plot_type == 'anim-contour')): if (d.data is None): @@ -1911,8 +2009,8 @@ def _plot(data_object, else: image_like = False if (image_like and (_plot_type == 'anim-image')): - xdata_range = coord_x.data_range(data_shape=d.shape)[0] - ydata_range = coord_y.data_range(data_shape=d.shape)[0] + xdata_range = coord_x.data_range(data_shape=d.shape)[0] + ydata_range = coord_y.data_range(data_shape=d.shape)[0] else: index = [...]*3 index[coord_t.dimension_list[0]] = 0 @@ -1927,13 +2025,9 @@ def _plot(data_object, raise ValueError("Invalid color map.") gs = gridspec.GridSpecFromSubplotSpec(1, 1, subplot_spec=_plot_id.base_subplot) -# ax=plt.plot() _plot_id.plt_axis_list = [] _plot_id.plt_axis_list.append(plt.subplot(gs[0,0])) -# plt.subplot(_plot_id.base_subplot) -# plt.plot() -# plt.cla() -# ax=plt.gca() + for it in range(len(tdata)): plt.subplot(_plot_id.base_subplot) ax_act = plt.subplot(gs[0,0]) @@ -1967,7 +2061,6 @@ def _plot(data_object, if (image_like and (_plot_type == 'anim-image')): try: - #if (coord_x.dimension_list[0] == 0): if (coord_x.dimension_list[0] < coord_y.dimension_list[0]): im = np.clip(np.transpose(d.data[time_index]),vmin,vmax) else: @@ -1983,7 +2076,8 @@ def _plot(data_object, raise e else: if (_plot_type == 'anim-image'): - xgrid, ygrid = flap.tools.grid_to_box(xdata,ydata) + xgrid, ygrid = flap.tools.grid_to_box(xdata*axes_unit_conversion[0], + ydata*axes_unit_conversion[1]) im = np.clip(np.transpose(d.data[time_index]),vmin,vmax) try: img = plt.pcolormesh(xgrid,ygrid,im,norm=norm,cmap=cmap,vmin=vmin, @@ -1994,27 +2088,57 @@ def _plot(data_object, else: try: im = np.clip(d.data[time_index],vmin,vmax) - img = plt.contourf(xdata,ydata,im,contour_levels,norm=norm, - origin='lower',cmap=cmap,vmin=vmin,vmax=vmax,**_plot_opt) + img = plt.contourf(xdata*axes_unit_conversion[0], + ydata*axes_unit_conversion[1], + im, + contour_levels,norm=norm,origin='lower', + cmap=cmap,vmin=vmin,vmax=vmax,**_plot_opt) del im except Exception as e: raise e - + if (_options['Colorbar']): cbar = plt.colorbar(img,ax=ax_act) cbar.set_label(d.data_unit.name) if (xrange is not None): - plt.xlim(xrange[0],xrange[1]) + plt.xlim(xrange[0]*axes_unit_conversion[0], + xrange[1]*axes_unit_conversion[0]) + if (yrange is not None): - plt.ylim(yrange[0],yrange[1]) - plt.xlabel(ax_list[0].title(language=language)) - plt.ylabel(ax_list[1].title(language=language)) + plt.ylim(yrange[0]*axes_unit_conversion[1], + yrange[1]*axes_unit_conversion[1]) + + if axes_unit_conversion[0] == 1.: + plt.xlabel(ax_list[0].title(language=language)) + else: + plt.xlabel(ax_list[0].title(language=language, + new_unit=_options['Plot units'][axes[0]])) + + if axes_unit_conversion[1] == 1.: + plt.ylabel(ax_list[1].title(language=language)) + else: + plt.ylabel(ax_list[1].title(language=language, + new_unit=_options['Plot units'][axes[1]])) + if (_options['Log x']): plt.xscale('log') if (_options['Log y']): plt.yscale('log') - title = str(d.exp_id)+' @ '+coord_t.unit.name+'='+"{:10.5f}".format(tdata[it])+' ['+coord_t.unit.unit+']' + + if _options['Plot units'] is not None: + if axes[2] in _options['Plot units']: + time_unit=_options['Plot units'][axes[2]] + time_coeff=axes_unit_conversion[2] + else: + time_unit=coord_t.unit.unit + time_coeff=1. + else: + time_unit=coord_t.unit.unit + time_coeff=1. + title = str(d.exp_id)+' @ '+coord_t.unit.name+'='+"{:10.5f}".format(tdata[it]*time_coeff)+\ + ' ['+time_unit+']' + plt.title(title) plt.show(block=False) time.sleep(_options['Waittime']) @@ -2042,6 +2166,10 @@ def _plot(data_object, cv2.destroyAllWindows() video.release() del video + + """ --------------------------------------------- + | ANIMATION PLOT DEFINITION | + ---------------------------------------------""" elif (_plot_type == 'animation'): if (d.data is None): @@ -2146,7 +2274,7 @@ def _plot(data_object, _plot_id.plt_axis_list = [] _plot_id.plt_axis_list.append(plt.subplot(gs[0,0])) - oargs=(ax_list, axes, d, xdata, ydata, tdata, xdata_range, ydata_range, + oargs=(ax_list, axes, axes_unit_conversion, d, xdata, ydata, tdata, xdata_range, ydata_range, cmap_obj, contour_levels, coord_t, coord_x, coord_y, cmap, _options, xrange, yrange, zrange, image_like, @@ -2155,15 +2283,7 @@ def _plot(data_object, anim = PlotAnimation(*oargs) anim.animate() - -# print("------ plot finished, show() ----") plt.show(block=False) - -# if (_options['Clear']): -# _plot_id.number_of_plots = 0 -# _plot_id.plot_data = [] -# _plot_id.plt_axis_list = None -# _plot_id.number_of_plots += 1 _plot_id.axes = ax_list _plot_id.plot_data.append(pdd_list) #Setting this one the default plot ID From c05bfb3b4a7c633575cd59ce8ff2b471f4e6a8b7 Mon Sep 17 00:00:00 2001 From: Mate Lampert Date: Mon, 18 Nov 2019 19:40:35 -0500 Subject: [PATCH 02/27] Update tools.py Removed spatial_unit_translation as it was only used by the plot.py where it was removed. --- flap/tools.py | 70 ++++++++++++++++----------------------------------- 1 file changed, 21 insertions(+), 49 deletions(-) diff --git a/flap/tools.py b/flap/tools.py index ede137a..87f6c70 100644 --- a/flap/tools.py +++ b/flap/tools.py @@ -183,6 +183,7 @@ def submatrix_index(mx_shape, index): # for i in range(len(mx_shape)): #THIS IS A SOLUTION FOR LARGE MATRICES, BUT NOT COMMITED # index_arrays.append(slice(min(index[i]),max(index[i])+1)) #DUE TO BEING UNTESTED. NEEDS TO BE UNCOMMENTED IF ONE WANTS TO USE IT + return tuple(index_arrays) @@ -430,6 +431,16 @@ def grid_to_box(xdata,ydata): return xbox,ybox def time_unit_translation(time_unit=None,max_value=None): + + """ + This tool returns the numerical pre-fix of the time_unit. It is almost + obsolate due to the existance of the methos named unit_conversion, but + this script can provide backwards conversion (used by flap_mdsplus.py) + time_unit: can be a string e.g. ms or a number e.g. 1e-3 + max_value: returns a suspected time unit based on this input + e.g.: max_value=1000. + """ + if (str(type(time_unit)) == 'str' or str(type(time_unit)) == ""): _time_unit=time_unit.lower() @@ -440,7 +451,7 @@ def time_unit_translation(time_unit=None,max_value=None): if VERBOSE: print('Time unit: \''+str(_time_unit)+'\'') print('Time unit translation based on values only works for shots under 1000s.') - value_translation=[[1,1e3,1e6,1e9,1e12], + value_translation=[[0.,1e3,1e6,1e9,1e12], ['s','ms','us','ns','ps']] for i in range(len(value_translation[0])-1): if (max_value > value_translation[0][i] and max_value < value_translation[0][i+1]): @@ -476,30 +487,6 @@ def time_unit_translation(time_unit=None,max_value=None): print(_time_unit+' was not found in the translation. Returning 1.') return 1 -def spatial_unit_translation(spatial_unit=None): - _spatial_unit=spatial_unit.lower() - translation={'meters':1, - 'meter':1, - 'm':1, - 'millimeters':1e-3, - 'millimeter':1e-3, - 'mm':1e-3, - 'micrometers':1e-6, - 'micrometer':1e-6, - 'um':1e-6, - 'nanometers':1e-9, - 'nanometer':1e-9, - 'nm':1e-9, - 'picometers':1e-12, - 'picometer':1e-12, - 'pm':1e-12, - } - if (_spatial_unit in translation.keys()): - return translation[_spatial_unit] - else: - print(_spatial_unit+' was not found in the translation. Returning 1.') - return 1 - def unit_conversion(original_unit=None, new_unit=None ): @@ -541,7 +528,7 @@ def unit_conversion(original_unit=None, new_unit_translation=known_conversions_full[keys_full] if original_unit_translation is None: - if len(original_unit) == 1 or len(original_unit) > 3 : # SI units are longer than 3 if using the full name + if ((len(original_unit) == 1) or (len(original_unit) > 3)): # SI units are longer than 3 if using the full name original_unit_translation=1. else: for keys_short in known_conversions_short: @@ -549,7 +536,7 @@ def unit_conversion(original_unit=None, original_unit_translation=known_conversions_short[keys_short] if new_unit_translation is None: - if len(new_unit) == 1 or len(new_unit) > 3: + if ((len(new_unit) == 1) or (len(new_unit) > 3)): new_unit_translation=1. else: for keys_short in known_conversions_short: @@ -557,44 +544,29 @@ def unit_conversion(original_unit=None, new_unit_translation=known_conversions_short[keys_short] if original_unit_translation is None: - print('Unit translation cannot be done for the original unit. Returning 1.') + print('Unit translation cannot be done for the original unit, '+original_unit+'. Returning 1.') if VERBOSE: if len(original_unit) > 3: print('Known conversion units are:') print(known_conversions_full) + print('\n') else: print('Known conversion units are:') print(known_conversions_short) + print('\n') original_unit_translation=1. if new_unit_translation is None: - print('Unit translation cannot be done for the new unit. Returning 1.') + print('Unit translation cannot be done for the new unit, '+new_unit+'. Returning 1.') if VERBOSE: if len(original_unit) > 3: print('Known conversion units are:') print(known_conversions_full) + print('\n') else: print('Known conversion units are:') print(known_conversions_short) + print('\n') new_unit_translation=1. - return original_unit_translation/new_unit_translation - - - - - - -#import matplotlib.pyplot as plt - -#plt.clf() -#ydata, xdata = np.meshgrid(np.arange(10),np.arange(20)) -#xdata = xdata.astype(float) -#ydata = ydata.astype(float) -#xdata += ydata*0.1 -#ydata += xdata*0.2 -#xbox, ybox = grid_to_box(xdata,ydata) -#data = (xdata + ydata) -#plt.pcolormesh(xbox,ybox,np.transpose(data),cmap='Greys') -#plt.scatter(xdata.flatten(), ydata.flatten()) - + return original_unit_translation/new_unit_translation \ No newline at end of file From fd6861e3d0813b603474d9d57fed30b6be0fbeb6 Mon Sep 17 00:00:00 2001 From: Mate Lampert <52581826+thelampire@users.noreply.github.com> Date: Tue, 19 Nov 2019 13:53:48 -0500 Subject: [PATCH 03/27] Update plot.py Please don't check what I modified. Left in a debugger print. --- flap/plot.py | 1 - 1 file changed, 1 deletion(-) diff --git a/flap/plot.py b/flap/plot.py index 8ad08f4..1897722 100644 --- a/flap/plot.py +++ b/flap/plot.py @@ -525,7 +525,6 @@ def animate_plot(self, it): if (len(self.xdata.shape) == 3 and len(self.ydata.shape) == 3): xgrid, ygrid = flap.tools.grid_to_box(self.xdata[time_index]*self.axes_unit_conversion[0], self.ydata[time_index]*self.axes_unit_conversion[1]) #Same issue, time is not necessarily the first flap.coordinate. - print('lofasz') else: xgrid, ygrid = flap.tools.grid_to_box(self.xdata*self.axes_unit_conversion[0], self.ydata*self.axes_unit_conversion[1]) From 9dd4c062c86e6ff3d976b2c45cf4e3a830760763 Mon Sep 17 00:00:00 2001 From: Mate Lampert Date: Tue, 19 Nov 2019 22:34:09 -0500 Subject: [PATCH 04/27] Path and contour overplotting This revision is a general overplotting option for the plot.py. It has replaced the 'EFIT options' as that has only been used by me. It can handle any number of contours and paths. It needs to be in the same time unit as the original data and in the format like the default_overplot_options variable. --- flap/plot.py | 365 ++++++++++++++++++++++++++------------------------- 1 file changed, 183 insertions(+), 182 deletions(-) diff --git a/flap/plot.py b/flap/plot.py index 8ad08f4..ccf8575 100644 --- a/flap/plot.py +++ b/flap/plot.py @@ -136,7 +136,7 @@ class PlotAnimation: def __init__(self, ax_list, axes, axes_unit_conversion, d, xdata, ydata, tdata, xdata_range, ydata_range, cmap_obj, contour_levels, coord_t, coord_x, - coord_y, cmap, options, xrange, yrange, + coord_y, cmap, options, overplot_options, xrange, yrange, zrange, image_like, plot_options, language, plot_id, gs): self.ax_list = ax_list @@ -155,6 +155,7 @@ def __init__(self, ax_list, axes, axes_unit_conversion, d, xdata, ydata, tdata, self.image_like = image_like self.language = language self.options = options + self.overplot_options = overplot_options self.pause = False self.plot_id = plot_id self.plot_options = plot_options @@ -266,7 +267,7 @@ def animate(self): else: if (len(self.xdata.shape) == 3 and len(self.ydata.shape) == 3): xgrid, ygrid = flap.tools.grid_to_box(self.xdata[time_index]*self.axes_unit_conversion[0], - self.ydata[time_index]*self.axes_unit_conversion[1]) #Same issue, time is not necessarily the first flap.coordinate. + self.ydata[time_index]*self.axes_unit_conversion[1]) else: xgrid, ygrid = flap.tools.grid_to_box(self.xdata*self.axes_unit_conversion[0], self.ydata*self.axes_unit_conversion[1]) @@ -287,166 +288,7 @@ def animate(self): cbar.set_label(self.d.data_unit.name) #EFIT overplot feature implementation: #It needs to be more generalied in the future as the coordinates are not necessarily in this order: [time_index,spat_index] - #This needs to be cross-checked with the time array's dimensions wherever there is a call for a certain index. - if ('EFIT options' in self.options and self.options['EFIT options'] is not None): - - default_efit_options={'Plot limiter': None, - 'Limiter X': None, - 'Limiter Y': None, - 'Limiter 2D': None, - 'Limiter color': 'white', - 'Plot separatrix': None, - 'Separatrix X': None, - 'Separatrix Y': None, - 'Separatrix 2D': None, - 'Separatrix color': 'red', - 'Plot flux': None, - 'Flux XY': None, - 'Flux nlevel': 51} - - self.efit_options=flap.config.merge_options(default_efit_options,self.options['EFIT options'],data_source=self.d.data_source) - self.efit_data={'limiter': {'Time':[],'Data':[]}, - 'separatrix':{'Time':[],'Data':[]}, - 'flux': {'Time':[],'Data':[]}} - - for setting in ['limiter','separatrix']: - if (self.efit_options['Plot '+setting]): - if (((self.efit_options[setting.capitalize()+' X']) and - (self.efit_options[setting.capitalize()+' Y' ])) or - (self.efit_options[setting.capitalize()+' 2D'])): - - if ((self.efit_options[setting.capitalize()+' X']) and - (self.efit_options[setting.capitalize()+' Y'])): - try: - R_object=flap.get_data_object_ref(self.efit_options[setting.capitalize()+' X'],exp_id=self.d.exp_id) - except: - raise ValueError("The objects "+self.efit_options[setting.capitalize()+' X']+" cannot be read.") - try: - Z_object=flap.get_data_object_ref(self.efit_options[setting.capitalize()+' Y'],exp_id=self.d.exp_id) - except: - raise ValueError("The objects "+self.efit_options[setting.capitalize()+' Y']+" cannot be read.") - if (len(R_object.data.shape) != 2 or len(Z_object.data.shape) != 2): - raise ValueError("The "+setting.capitalize()+' Y'+" data is not 1D. Use 2D or modify data reading.") - - self.efit_data[setting]['Data']=np.asarray([R_object.data,Z_object.data]) - elif (self.efit_options[setting.capitalize()+' 2D']): - try: - R_object=flap.get_data_object_ref(self.efit_options[setting.capitalize()+' 2D'],exp_id=self.d.exp_id) - except: - raise ValueError(setting.capitalize()+' 2D data is not available. FLAP data object needs to be read first.') - if R_object.data.shape[2] == 2: - self.efit_data[setting]['Data']=np.asarray([R_object.data[:,:,0],R_object.data[:,:,1]]) - else: - raise ValueError(setting.capitalize()+' 2D data needs to be in the format [n_time,n_points,2 (xy)]') - else: - raise ValueError('Both '+setting.capitalize()+' X and'+ - setting.capitalize()+' Y or '+ - setting.capitalize()+' 2D need to be set.') - #Finding the time coordinate - for index_time in range(len(R_object.coordinates)): - if (R_object.coordinates[index_time].unit.name == 'Time'): - time_dimension_efit=R_object.coordinates[index_time].dimension_list[0] - time_index_efit=index_time - #Gathering the 1D time vector for the EFIT data - time_data=R_object.coordinate('Time')[0] - coordinate_index=[0]*len(time_data.shape) - coordinate_index[time_dimension_efit]=Ellipsis - self.efit_data[setting]['Time']=time_data[tuple(coordinate_index)] - - #Gathering the spatial unit for translation - for index_coordinate in range(len(self.d.coordinates)): - if ((self.d.coordinates[index_coordinate].unit.name in self.axes) and - (self.d.coordinates[index_coordinate].unit.name != 'Time')): - spatial_coordinate_index=index_coordinate - self.efit_data[setting]['Data'] *= flap.tools.unit_conversion(original_unit=R_object.data_unit.unit, - new_unit=self.d.coordinates[spatial_coordinate_index].unit.unit) #Assumes that R and z data is in the same units - - #Time translation (usually ms vs s) - for index_time in range(len(self.d.coordinates)): - if (self.d.coordinates[index_time].unit.name == 'Time'): - time_index_data=index_time - - self.efit_data[setting]['Time'] *= flap.tools.unit_conversion(original_unit=R_object.coordinates[time_index_efit].unit.unit, - new_unit=self.d.coordinates[time_index_data].unit.unit) - else: - raise ValueError(setting.capitalize()+' keywords are not set for the data objects.') - - #Interpolating EFIT data for the time vector of the data - if ((self.efit_data[setting]['Data'] != []) and (self.efit_data[setting]['Time'] != [])): - self.efit_data[setting]['Data resampled']=np.zeros([2,self.tdata.shape[0],self.efit_data[setting]['Data'].shape[2]]) - for index_xy in range(0,2): - for index_coordinate in range(0,self.efit_data[setting]['Data'].shape[2]): - self.efit_data[setting]['Data resampled'][index_xy,:,index_coordinate]=np.interp(self.tdata,self.efit_data[setting]['Time'], - self.efit_data[setting]['Data'][index_xy,:,index_coordinate]) - - if (self.efit_options['Plot flux']): - try: - flux_object=flap.get_data_object(self.efit_options['Flux XY'],exp_id=self.d.exp_id) - except: - raise ValueError('Flux XY data is not available. FLAP data object needs to be read first.') - if len(flux_object.data.shape) != 3: - raise ValueError('Flux XY data needs to be a 3D matrix (r,z,t), not necessarily in this order.') - self.efit_data['flux']['Data']=flux_object.data - - for index_time in range(len(flux_object.coordinates)): - if (flux_object.coordinates[index_time].unit.name == 'Time'): - time_dimension_efit=flux_object.coordinates[index_time].dimension_list[0] - time_index_efit=index_time - - #Gathering the 1D time vector for the EFIT data - time_data=flux_object.coordinate('Time')[0] - coordinate_index=[0]*len(time_data.shape) - coordinate_index[time_dimension_efit]=Ellipsis - self.efit_data['flux']['Time']=time_data[tuple(coordinate_index)] - - try: - self.efit_data['flux']['X coord']=flux_object.coordinate(self.axes[0])[0] - self.efit_data['flux']['Y coord']=flux_object.coordinate(self.axes[1])[0] - except: - raise ValueError('The requested axes, '+self.axes[0]+' and '+self.axes[1]+', are not available in the given EFIT data.') - - for index_coordinate in range(len(self.d.coordinates)): - if ((self.d.coordinates[index_coordinate].unit.name in self.axes) and - (self.d.coordinates[index_coordinate].unit.name != 'Time')): - coordinate_index=index_coordinate - - #Spatial unit translation (mm vs m usually) - spat_conversion_coeff=flap.tools.unit_conversion(original_unit=flux_object.data_unit.unit, - new_unit=self.d.coordinates[coordinate_index].unit.unit) - - self.efit_data['flux']['X coord'] *= spat_conversion_coeff - self.efit_data['flux']['Y coord'] *= spat_conversion_coeff - - #Time translation (usually ms vs s) - for index_time in range(len(self.d.coordinates)): - if (self.d.coordinates[index_time].unit.name == 'Time'): - time_index_data=index_time - - self.efit_data['flux']['Time'] *= flap.tools.unit_conversion(original_unit=flux_object.coordinates[time_index_efit].unit.unit, - new_unit=self.d.coordinates[time_index_data].unit.unit) - - #Interpolating EFIT data for the time vector of the data - if ((self.efit_data['flux']['Data'] != []) and (self.efit_data['flux']['Time'] != [])): - self.efit_data['flux']['Data resampled']=np.zeros([self.tdata.shape[0], - self.efit_data['flux']['Data'].shape[1], #THESE SHAPES NEED TO BE REVISED - self.efit_data['flux']['Data'].shape[2]]) - self.efit_data['flux']['X coord resampled']=np.zeros([self.tdata.shape[0], - self.efit_data['flux']['X coord'].shape[1], - self.efit_data['flux']['X coord'].shape[2]]) - self.efit_data['flux']['Y coord resampled']=np.zeros([self.tdata.shape[0], - self.efit_data['flux']['Y coord'].shape[1], - self.efit_data['flux']['Y coord'].shape[2]]) - - for index_x in range(0,self.efit_data['flux']['Data'].shape[1]): #THESE SHAPES NEED TO BE REVISED - for index_y in range(0,self.efit_data['flux']['Data'].shape[2]): - self.efit_data['flux']['Data resampled'][:,index_x,index_y]=np.interp(self.tdata,self.efit_data['flux']['Time'], - self.efit_data['flux']['Data'][:,index_x,index_y]) - self.efit_data['flux']['X coord resampled'][:,index_x,index_y]=np.interp(self.tdata,self.efit_data['flux']['Time'], - self.efit_data['flux']['X coord'][:,index_x,index_y]) - self.efit_data['flux']['Y coord resampled'][:,index_x,index_y]=np.interp(self.tdata,self.efit_data['flux']['Time'], - self.efit_data['flux']['Y coord'][:,index_x,index_y]) - - + #This needs to be cross-checked with the time array's dimensions wherever there is a call for a certain index. if (self.xrange is not None): plt.xlim(self.xrange[0]*self.axes_unit_conversion[0], self.xrange[1]*self.axes_unit_conversion[0]) @@ -525,7 +367,6 @@ def animate_plot(self, it): if (len(self.xdata.shape) == 3 and len(self.ydata.shape) == 3): xgrid, ygrid = flap.tools.grid_to_box(self.xdata[time_index]*self.axes_unit_conversion[0], self.ydata[time_index]*self.axes_unit_conversion[1]) #Same issue, time is not necessarily the first flap.coordinate. - print('lofasz') else: xgrid, ygrid = flap.tools.grid_to_box(self.xdata*self.axes_unit_conversion[0], self.ydata*self.axes_unit_conversion[1]) @@ -536,20 +377,22 @@ def animate_plot(self, it): except Exception as e: raise e del im - if ('EFIT options' in self.options and self.options['EFIT options'] is not None): - for setting in ['limiter','separatrix']: - if (self.efit_options['Plot '+setting]): - self.ax_act.set_autoscale_on(False) - im = plt.plot(self.efit_data[setting]['Data resampled'][0,it,:]*self.axes_unit_conversion[0], - self.efit_data[setting]['Data resampled'][1,it,:]*self.axes_unit_conversion[1], - color=self.efit_options[setting.capitalize()+' color']) - - if (self.efit_options['Plot flux']): - self.ax_act.set_autoscale_on(False) - im = plt.contour(self.efit_data['flux']['X coord resampled'][it,:,:].transpose()*self.axes_unit_conversion[0], - self.efit_data['flux']['Y coord resampled'][it,:,:].transpose()*self.axes_unit_conversion[1], - self.efit_data['flux']['Data resampled'][it,:,:], - levels=self.efit_options['Flux nlevel']) + + if (self.overplot_options is not None): + self.ax_act.set_autoscale_on(False) + for path_obj_keys in self.overplot_options['path']: + if self.overplot_options['path'][path_obj_keys]['Plot']: + im = plt.plot(self.overplot_options['path'][path_obj_keys]['data']['Data resampled'][0,it,:]*self.axes_unit_conversion[0], + self.overplot_options['path'][path_obj_keys]['data']['Data resampled'][1,it,:]*self.axes_unit_conversion[1], + color=self.overplot_options['path'][path_obj_keys]['Color']) + + for contour_obj_keys in self.overplot_options['contour']: + if self.overplot_options['contour'][contour_obj_keys]['Plot']: + im = plt.contour(self.overplot_options['contour'][contour_obj_keys]['data']['X coord resampled'][it,:,:].transpose()*self.axes_unit_conversion[0], + self.overplot_options['contour'][contour_obj_keys]['data']['Y coord resampled'][it,:,:].transpose()*self.axes_unit_conversion[1], + self.overplot_options['contour'][contour_obj_keys]['data']['Data resampled'][it,:,:], + levels=self.overplot_options['contour'][contour_obj_keys]['nlevel'], + cmap=self.overplot_options['contour'][contour_obj_keys]['Colormap']) if (self.xrange is not None): self.ax_act.set_xlim(self.xrange[0]*self.axes_unit_conversion[0], @@ -931,7 +774,7 @@ def _plot(data_object, 'Nan color': The color to use in image data plotting for np.nan (not-a-number) values 'Interpolation': Interpolation method for image plot. 'Language': Language of certain standard elements in the plot. ('EN', 'HU') - 'EFIT options': Dictionary of EFIT plotting options: + 'Overplot options': Dictionary of overplotting options (EFIT for now): 'Plot separatrix': Set to plot the separatrix onto the video 'Separatrix X': Name of the flap.DataObject for the separatrix X data (usually R) 'Separatrix Y': Name of the flap.DataObject for the separatrix Y data (usually z) @@ -963,7 +806,7 @@ def _plot(data_object, 'Clear':False,'Force axes':False,'Language':'EN','Maxpoints': 4000, 'Levels': 10, 'Colormap':None, 'Waittime':1,'Colorbar':True,'Nan color':None, 'Interpolation':'bilinear','Video file':None, 'Video framerate': 20,'Video format':'avi', - 'EFIT options':None, 'Prevent saturation':False, 'Plot units':None, + 'Video saving only':False, 'Overplot options':None, 'Prevent saturation':False, 'Plot units':None, } _options = flap.config.merge_options(default_options, options, data_source=data_object.data_source, section='Plot') @@ -1114,7 +957,140 @@ def _plot(data_object, for index_axes in range(len(axes)): if axes[index_axes] in _options['Plot units']: axes_unit_conversion[index_axes]=unit_conversion_coeff[axes[index_axes]] - + + #overplot_available_plot_types=['image', 'anim-image','contour','anim-contour','animation'] + overplot_available_plot_types=['anim-image','anim-contour','animation'] + overplot_options=None + if _options['Overplot options'] is not None: + if not plot_type in overplot_available_plot_types: + raise ValueError('Overplotting is not available in '+plot_type+' plotting.') + + default_overplot_options={'contour':{'NAME':{'Data object':None, + 'Plot':False, + 'Colormap':None, + 'nlevel':51, + }}, + + 'path':{'NAME':{'Data object X':None, + 'Data object Y':None, + 'Plot':False, + 'Color':'black', + }} + } + + overplot_options=flap.config.merge_options(default_overplot_options,_options['Overplot options'],data_source=data_object.data_source) + + for index_time in range(len(d.coordinates)): + if (d.coordinates[index_time].unit.name == axes[2]): + if len(d.coordinates[index_time].dimension_list) != 1: + raise ValueError('The time coordinate is changing along multiple dimensions.') + time_dimension_data=d.coordinates[index_time].dimension_list[0] + data_time_index_coordinate=index_time + + time_index=[0]*len(d.coordinate(axes[2])[0].shape) + time_index[time_dimension_data]=Ellipsis + tdata=d.coordinate(axes[2])[0][tuple(time_index)] + + for path_obj_keys in overplot_options['path']: + if (overplot_options['path'][path_obj_keys]['Plot']): + try: + x_object_name=overplot_options['path'][path_obj_keys]['Data object X'] + x_object=flap.get_data_object_ref(x_object_name,exp_id=d.exp_id) + except: + raise ValueError("The object "+x_object_name+" cannot be read.") + try: + y_object_name=overplot_options['path'][path_obj_keys]['Data object Y'] + y_object=flap.get_data_object_ref(y_object_name,exp_id=d.exp_id) + except: + raise ValueError("The object "+y_object_name+" cannot be read.") + #Error handling + if (len(x_object.data.shape) != 2 or len(y_object.data.shape) != 2): + raise ValueError("The "+overplot_options['path'][path_obj_keys]['Data object X']+' or ' + "the "+overplot_options['path'][path_obj_keys]['Data object Y']+" data is not 1D.") + + for index_coordinate in range(len(x_object.coordinates)): + if (x_object.coordinates[index_coordinate].unit.name == axes[2]): + oplot_x_time_index_coordinate=index_coordinate + for index_coordinate in range(len(x_object.coordinates)): + if (x_object.coordinates[index_coordinate].unit.name == axes[2]): + oplot_y_time_index_coordinate=index_coordinate + + if (d.coordinates[data_time_index_coordinate].unit.unit != x_object.coordinates[oplot_x_time_index_coordinate].unit.unit or + d.coordinates[data_time_index_coordinate].unit.unit != x_object.coordinates[oplot_y_time_index_coordinate].unit.unit): + raise TypeError('The '+axes[2]+' unit of the overplotted contour object differs from the data\'s. Interpolation cannot be done.') + + #Interpolate the path data to the time vector of the original data + try: + x_object_interp=flap.slice_data(x_object_name, + slicing={axes[2]:tdata}, + options={'Interpolation':'Linear'}, + output_name='X OBJ INTERP') + except: + raise ValueError('Interpolation cannot be done for the \'Data object X\' along axis '+axes[2]+'.') + try: + y_object_interp=flap.slice_data(y_object_name, + slicing={axes[2]:tdata}, + options={'Interpolation':'Linear'}, + output_name='Y OBJ INTERP') + except: + raise ValueError('Interpolation cannot be done for the \'Data object Y\' along axis '+axes[2]+'.') + + #Finding the time coordinate + for index_time in range(len(x_object.coordinates)): + if (x_object.coordinates[index_time].unit.name == axes[2]): + time_dimension_oplot=x_object.coordinates[index_time].dimension_list[0] + + #Convert the units of the path if its coordinates are not the same as the data object's. + unit_conversion_coeff=[1]*2 + original_units=[x_object.data_unit.unit,y_object.data_unit.unit] + for index_coordinate in range(len(d.coordinates)): + for index_axes in range(2): + if (d.coordinates[index_coordinate].unit.name == axes[index_axes]): + unit_conversion_coeff[index_axes] = flap.tools.unit_conversion(original_unit=original_units[index_axes], + new_unit=d.coordinates[index_coordinate].unit.unit) #Assumes that R and z data is in the same units + overplot_options['path'][path_obj_keys]['data']={} + overplot_options['path'][path_obj_keys]['data']['Data resampled']=np.asarray([x_object_interp.data*unit_conversion_coeff[0], + y_object_interp.data*unit_conversion_coeff[1]]) + overplot_options['path'][path_obj_keys]['data']['Time dimension']=time_dimension_oplot + + for contour_obj_keys in overplot_options['contour']: + if (overplot_options['contour'][contour_obj_keys]['Plot']): + try: + xy_object_name=overplot_options['contour'][contour_obj_keys]['Data object'] + xy_object=flap.get_data_object(xy_object_name,exp_id=d.exp_id) + except: + raise ValueError(xy_object_name+'data is not available. The data object needs to be read first.') + if len(xy_object.data.shape) != 3: + raise ValueError('Contour object\'s, ('+xy_object_name+') data needs to be a 3D matrix (x,y,temporal), not necessarily in this order.') + + for index_coordinate in range(len(xy_object.coordinates)): + if (xy_object.coordinates[index_coordinate].unit.name == axes[2]): + time_dimension_index=xy_object.coordinates[index_coordinate].dimension_list[0] + oplot_time_index_coordinate=index_coordinate + + if d.coordinates[data_time_index_coordinate].unit.unit != xy_object.coordinates[oplot_time_index_coordinate].unit.unit: + raise TypeError('The '+axes[2]+' unit of the overplotted contour object differs from the data\'s. Interpolation cannot be done.') + + unit_conversion_coeff=[1.] * 3 + for index_data_coordinate in range(len(d.coordinates)): + for index_oplot_coordinate in range(len(xy_object.coordinates)): + for index_axes in range(3): + if ((d.coordinates[index_data_coordinate].unit.name == axes[index_axes]) and + (xy_object.coordinates[index_oplot_coordinate].unit.name) == axes[index_axes]): + unit_conversion_coeff[index_axes] = flap.tools.unit_conversion(original_unit=xy_object.coordinates[index_oplot_coordinate].unit.unit, + new_unit=d.coordinates[index_data_coordinate].unit.unit) #Assumes that R and z data is in the same units + + xy_object_interp=flap.slice_data(xy_object_name, + slicing={axes[2]:tdata}, + options={'Interpolation':'Linear'}, + output_name='XY OBJ INTERP') + overplot_options['contour'][contour_obj_keys]['data']={} + overplot_options['contour'][contour_obj_keys]['data']['Data resampled']=xy_object_interp.data + overplot_options['contour'][contour_obj_keys]['data']['X coord resampled']=xy_object_interp.coordinate(axes[0])[0]*unit_conversion_coeff[0] + overplot_options['contour'][contour_obj_keys]['data']['Y coord resampled']=xy_object_interp.coordinate(axes[1])[0]*unit_conversion_coeff[1] + overplot_options['contour'][contour_obj_keys]['data'][axes[2]]=tdata + overplot_options['contour'][contour_obj_keys]['data']['Time dimension']=time_dimension_index + # X range and Z range is processed here, but Y range not as it might have multiple entries for some plots xrange = _options['X range'] if (xrange is not None): @@ -2096,6 +2072,28 @@ def _plot(data_object, del im except Exception as e: raise e + + if (overplot_options is not None): + ax_act.set_autoscale_on(False) + for path_obj_keys in overplot_options['path']: + if overplot_options['path'][path_obj_keys]['Plot']: + time_index = [slice(0,dim) for dim in overplot_options['path'][path_obj_keys]['data']['Data resampled'][0].shape] + time_index[overplot_options['path'][path_obj_keys]['data']['Time dimension']] = it + time_index = tuple(time_index) + im = plt.plot(overplot_options['path'][path_obj_keys]['data']['Data resampled'][0][time_index]*axes_unit_conversion[0], + overplot_options['path'][path_obj_keys]['data']['Data resampled'][1][time_index]*axes_unit_conversion[1], + color=overplot_options['path'][path_obj_keys]['Color']) + + for contour_obj_keys in overplot_options['contour']: + if overplot_options['contour'][contour_obj_keys]['Plot']: + time_index = [slice(0,dim) for dim in overplot_options['contour'][contour_obj_keys]['data']['Data resampled'].shape] + time_index[overplot_options['contour'][contour_obj_keys]['data']['Time dimension']] = it + time_index = tuple(time_index) + im = plt.contour(overplot_options['contour'][contour_obj_keys]['data']['X coord resampled'][time_index].transpose()*axes_unit_conversion[0], + overplot_options['contour'][contour_obj_keys]['data']['Y coord resampled'][time_index].transpose()*axes_unit_conversion[1], + overplot_options['contour'][contour_obj_keys]['data']['Data resampled'][time_index], + levels=overplot_options['contour'][contour_obj_keys]['nlevel'], + cmap=overplot_options['contour'][contour_obj_keys]['Colormap']) if (_options['Colorbar']): cbar = plt.colorbar(img,ax=ax_act) @@ -2149,7 +2147,10 @@ def _plot(data_object, # Get the RGBA buffer from the figure w,h = fig.canvas.get_width_height() buf = np.frombuffer(fig.canvas.tostring_rgb(), dtype=np.uint8) - buf.shape = ( h, w, 3 ) + if buf.shape[0] == h*2 * w*2 * 3: + buf.shape = ( h*2, w*2, 3 ) #ON THE MAC'S INTERNAL SCREEN, THIS COULD HAPPEN NEEDS A MORE ELEGANT FIX + else: + buf.shape = ( h, w, 3 ) buf = cv2.cvtColor(buf, cv2.COLOR_RGBA2BGR) try: video @@ -2276,7 +2277,7 @@ def _plot(data_object, oargs=(ax_list, axes, axes_unit_conversion, d, xdata, ydata, tdata, xdata_range, ydata_range, cmap_obj, contour_levels, - coord_t, coord_x, coord_y, cmap, _options, + coord_t, coord_x, coord_y, cmap, _options, overplot_options, xrange, yrange, zrange, image_like, _plot_options, language, _plot_id, gs) From 5187a581bf147927e1b6eef684d502982391711c Mon Sep 17 00:00:00 2001 From: Mate Lampert Date: Wed, 20 Nov 2019 20:21:14 -0500 Subject: [PATCH 05/27] Overplotting, equal axes and axes visibility options Overplotting options extended, they are working now for all the 2D plot types. Also added the option of 'Equal axes' and 'Axes visibility'. The former maintains equal x and y axis if their coordinate units are the same. The latter removes the xtitle and the ytitle from the axes. It's useful when plotting multiple plots. --- flap/plot.py | 332 +++++++++++++++++++++++++++++++++----------------- flap/tools.py | 41 ++++--- 2 files changed, 239 insertions(+), 134 deletions(-) diff --git a/flap/plot.py b/flap/plot.py index ccf8575..735c31d 100644 --- a/flap/plot.py +++ b/flap/plot.py @@ -211,17 +211,17 @@ def animate(self): self.ax_act = plt.subplot(self.gs[0,0]) #The following lines set the axes to be equal if the units of the axes-to-be-plotted are the same - - axes_coordinate_decrypt=[0] * len(self.axes) - for i_axes in range(len(self.axes)): - for j_coordinate in range(len(self.d.coordinates)): - if (self.d.coordinates[j_coordinate].unit.name == self.axes[i_axes]): - axes_coordinate_decrypt[i_axes]=j_coordinate - for i_check in range(len(self.axes)) : - for j_check in range(i_check+1,len(self.axes)): - if (self.d.coordinates[axes_coordinate_decrypt[i_check]].unit.unit == - self.d.coordinates[axes_coordinate_decrypt[j_check]].unit.unit): - self.ax_act.axis('equal') + if self.options['Equal axes']: + axes_coordinate_decrypt=[0] * len(self.axes) + for i_axes in range(len(self.axes)): + for j_coordinate in range(len(self.d.coordinates)): + if (self.d.coordinates[j_coordinate].unit.name == self.axes[i_axes]): + axes_coordinate_decrypt[i_axes]=j_coordinate + for i_check in range(len(self.axes)) : + for j_check in range(i_check+1,len(self.axes)): + if (self.d.coordinates[axes_coordinate_decrypt[i_check]].unit.unit == + self.d.coordinates[axes_coordinate_decrypt[j_check]].unit.unit): + self.ax_act.axis('equal') time_index = [slice(0,dim) for dim in self.d.data.shape] time_index[self.coord_t.dimension_list[0]] = 0 @@ -806,7 +806,8 @@ def _plot(data_object, 'Clear':False,'Force axes':False,'Language':'EN','Maxpoints': 4000, 'Levels': 10, 'Colormap':None, 'Waittime':1,'Colorbar':True,'Nan color':None, 'Interpolation':'bilinear','Video file':None, 'Video framerate': 20,'Video format':'avi', - 'Video saving only':False, 'Overplot options':None, 'Prevent saturation':False, 'Plot units':None, + 'Overplot options':None, 'Prevent saturation':False, 'Plot units':None, + 'Equal axes':False, 'Axes visibility':[True,True], } _options = flap.config.merge_options(default_options, options, data_source=data_object.data_source, section='Plot') @@ -959,40 +960,37 @@ def _plot(data_object, axes_unit_conversion[index_axes]=unit_conversion_coeff[axes[index_axes]] #overplot_available_plot_types=['image', 'anim-image','contour','anim-contour','animation'] - overplot_available_plot_types=['anim-image','anim-contour','animation'] overplot_options=None if _options['Overplot options'] is not None: - if not plot_type in overplot_available_plot_types: - raise ValueError('Overplotting is not available in '+plot_type+' plotting.') default_overplot_options={'contour':{'NAME':{'Data object':None, 'Plot':False, 'Colormap':None, 'nlevel':51, + 'Slicing':None, + 'Summing':None, }}, 'path':{'NAME':{'Data object X':None, 'Data object Y':None, 'Plot':False, 'Color':'black', + 'Slicing':None, + 'Summing':None, + }}, + + 'line':{'NAME':{'Coord1':None, + 'Coord2':None, + 'Plot':False, + 'Color':None, }} } overplot_options=flap.config.merge_options(default_overplot_options,_options['Overplot options'],data_source=data_object.data_source) - - for index_time in range(len(d.coordinates)): - if (d.coordinates[index_time].unit.name == axes[2]): - if len(d.coordinates[index_time].dimension_list) != 1: - raise ValueError('The time coordinate is changing along multiple dimensions.') - time_dimension_data=d.coordinates[index_time].dimension_list[0] - data_time_index_coordinate=index_time - - time_index=[0]*len(d.coordinate(axes[2])[0].shape) - time_index[time_dimension_data]=Ellipsis - tdata=d.coordinate(axes[2])[0][tuple(time_index)] - - for path_obj_keys in overplot_options['path']: - if (overplot_options['path'][path_obj_keys]['Plot']): + + #TIME (AXES(2)) INDEPENDENT OVERPLOTTING OBJECT CREATION + if plot_type in ['image', 'contour']: + for path_obj_keys in overplot_options['path']: try: x_object_name=overplot_options['path'][path_obj_keys]['Data object X'] x_object=flap.get_data_object_ref(x_object_name,exp_id=d.exp_id) @@ -1004,92 +1002,170 @@ def _plot(data_object, except: raise ValueError("The object "+y_object_name+" cannot be read.") #Error handling - if (len(x_object.data.shape) != 2 or len(y_object.data.shape) != 2): - raise ValueError("The "+overplot_options['path'][path_obj_keys]['Data object X']+' or ' - "the "+overplot_options['path'][path_obj_keys]['Data object Y']+" data is not 1D.") - - for index_coordinate in range(len(x_object.coordinates)): - if (x_object.coordinates[index_coordinate].unit.name == axes[2]): - oplot_x_time_index_coordinate=index_coordinate - for index_coordinate in range(len(x_object.coordinates)): - if (x_object.coordinates[index_coordinate].unit.name == axes[2]): - oplot_y_time_index_coordinate=index_coordinate - - if (d.coordinates[data_time_index_coordinate].unit.unit != x_object.coordinates[oplot_x_time_index_coordinate].unit.unit or - d.coordinates[data_time_index_coordinate].unit.unit != x_object.coordinates[oplot_y_time_index_coordinate].unit.unit): - raise TypeError('The '+axes[2]+' unit of the overplotted contour object differs from the data\'s. Interpolation cannot be done.') - - #Interpolate the path data to the time vector of the original data - try: - x_object_interp=flap.slice_data(x_object_name, - slicing={axes[2]:tdata}, - options={'Interpolation':'Linear'}, - output_name='X OBJ INTERP') - except: - raise ValueError('Interpolation cannot be done for the \'Data object X\' along axis '+axes[2]+'.') - try: - y_object_interp=flap.slice_data(y_object_name, - slicing={axes[2]:tdata}, - options={'Interpolation':'Linear'}, - output_name='Y OBJ INTERP') - except: - raise ValueError('Interpolation cannot be done for the \'Data object Y\' along axis '+axes[2]+'.') - #Finding the time coordinate - for index_time in range(len(x_object.coordinates)): - if (x_object.coordinates[index_time].unit.name == axes[2]): - time_dimension_oplot=x_object.coordinates[index_time].dimension_list[0] + if 'Slicing' in overplot_options['path'][path_obj_keys]: + try: + x_object=x_object.slice_data(slicing=overplot_options['path'][path_obj_keys]['Slicing']) + y_object=y_object.slice_data(slicing=overplot_options['path'][path_obj_keys]['Slicing']) + except: + raise ValueError('Slicing did not succeed. Try it outside the plotting!') - #Convert the units of the path if its coordinates are not the same as the data object's. + if (len(x_object.data.shape) != 1 or len(y_object.data.shape) != 1): + raise ValueError("The "+overplot_options['path'][path_obj_keys]['Data object X']+' or ' + "the "+overplot_options['path'][path_obj_keys]['Data object Y']+" data is not 1D. Use slicing.") unit_conversion_coeff=[1]*2 original_units=[x_object.data_unit.unit,y_object.data_unit.unit] for index_coordinate in range(len(d.coordinates)): for index_axes in range(2): if (d.coordinates[index_coordinate].unit.name == axes[index_axes]): unit_conversion_coeff[index_axes] = flap.tools.unit_conversion(original_unit=original_units[index_axes], - new_unit=d.coordinates[index_coordinate].unit.unit) #Assumes that R and z data is in the same units + new_unit=d.coordinates[index_coordinate].unit.unit) + overplot_options['path'][path_obj_keys]['data']={} - overplot_options['path'][path_obj_keys]['data']['Data resampled']=np.asarray([x_object_interp.data*unit_conversion_coeff[0], - y_object_interp.data*unit_conversion_coeff[1]]) - overplot_options['path'][path_obj_keys]['data']['Time dimension']=time_dimension_oplot - - for contour_obj_keys in overplot_options['contour']: - if (overplot_options['contour'][contour_obj_keys]['Plot']): - try: - xy_object_name=overplot_options['contour'][contour_obj_keys]['Data object'] - xy_object=flap.get_data_object(xy_object_name,exp_id=d.exp_id) - except: - raise ValueError(xy_object_name+'data is not available. The data object needs to be read first.') - if len(xy_object.data.shape) != 3: - raise ValueError('Contour object\'s, ('+xy_object_name+') data needs to be a 3D matrix (x,y,temporal), not necessarily in this order.') + overplot_options['path'][path_obj_keys]['data']['Data']=np.asarray([x_object.data*unit_conversion_coeff[0], + y_object.data*unit_conversion_coeff[1]]) + + for contour_obj_keys in overplot_options['contour']: + if (overplot_options['contour'][contour_obj_keys]['Plot']): + try: + xy_object_name=overplot_options['contour'][contour_obj_keys]['Data object'] + xy_object=flap.get_data_object(xy_object_name,exp_id=d.exp_id) + except: + raise ValueError(xy_object_name+'data is not available. The data object needs to be read first.') + if 'Slicing' in overplot_options['path'][path_obj_keys]: + try: + xy_object.slice_data(slicing=overplot_options['path'][path_obj_keys]['Slicing']) + except: + raise ValueError('Slicing did not succeed. Try it outside the plotting!') + + if len(xy_object.data.shape) != 2: + raise ValueError('Contour object\'s, ('+xy_object_name+') data needs to be a 2D matrix.') + + unit_conversion_coeff=[1.] * 2 + for index_data_coordinate in range(len(d.coordinates)): + for index_oplot_coordinate in range(len(xy_object.coordinates)): + for index_axes in range(2): + if ((d.coordinates[index_data_coordinate].unit.name == axes[index_axes]) and + (xy_object.coordinates[index_oplot_coordinate].unit.name) == axes[index_axes]): + unit_conversion_coeff[index_axes] = flap.tools.unit_conversion(original_unit=xy_object.coordinates[index_oplot_coordinate].unit.unit, + new_unit=d.coordinates[index_data_coordinate].unit.unit) + + overplot_options['contour'][contour_obj_keys]['data']={} + overplot_options['contour'][contour_obj_keys]['data']['Data']=xy_object.data + overplot_options['contour'][contour_obj_keys]['data']['X coord']=xy_object.coordinate(axes[0])[0]*unit_conversion_coeff[0] + overplot_options['contour'][contour_obj_keys]['data']['Y coord']=xy_object.coordinate(axes[1])[0]*unit_conversion_coeff[1] + + if plot_type in ['anim-image','anim-contour','animation'] : + for index_time in range(len(d.coordinates)): + if (d.coordinates[index_time].unit.name == axes[2]): + if len(d.coordinates[index_time].dimension_list) != 1: + raise ValueError('The time coordinate is changing along multiple dimensions.') + time_dimension_data=d.coordinates[index_time].dimension_list[0] + data_time_index_coordinate=index_time - for index_coordinate in range(len(xy_object.coordinates)): - if (xy_object.coordinates[index_coordinate].unit.name == axes[2]): - time_dimension_index=xy_object.coordinates[index_coordinate].dimension_list[0] - oplot_time_index_coordinate=index_coordinate + time_index=[0]*len(d.coordinate(axes[2])[0].shape) + time_index[time_dimension_data]=Ellipsis + tdata=d.coordinate(axes[2])[0][tuple(time_index)] + + for path_obj_keys in overplot_options['path']: + if (overplot_options['path'][path_obj_keys]['Plot']): + try: + x_object_name=overplot_options['path'][path_obj_keys]['Data object X'] + x_object=flap.get_data_object_ref(x_object_name,exp_id=d.exp_id) + except: + raise ValueError("The object "+x_object_name+" cannot be read.") + try: + y_object_name=overplot_options['path'][path_obj_keys]['Data object Y'] + y_object=flap.get_data_object_ref(y_object_name,exp_id=d.exp_id) + except: + raise ValueError("The object "+y_object_name+" cannot be read.") + #Error handling + if (len(x_object.data.shape) != 2 or len(y_object.data.shape) != 2): + raise ValueError("The "+overplot_options['path'][path_obj_keys]['Data object X']+' or ' + "the "+overplot_options['path'][path_obj_keys]['Data object Y']+" data is not 1D.") - if d.coordinates[data_time_index_coordinate].unit.unit != xy_object.coordinates[oplot_time_index_coordinate].unit.unit: - raise TypeError('The '+axes[2]+' unit of the overplotted contour object differs from the data\'s. Interpolation cannot be done.') - - unit_conversion_coeff=[1.] * 3 - for index_data_coordinate in range(len(d.coordinates)): - for index_oplot_coordinate in range(len(xy_object.coordinates)): - for index_axes in range(3): - if ((d.coordinates[index_data_coordinate].unit.name == axes[index_axes]) and - (xy_object.coordinates[index_oplot_coordinate].unit.name) == axes[index_axes]): - unit_conversion_coeff[index_axes] = flap.tools.unit_conversion(original_unit=xy_object.coordinates[index_oplot_coordinate].unit.unit, - new_unit=d.coordinates[index_data_coordinate].unit.unit) #Assumes that R and z data is in the same units + for index_coordinate in range(len(x_object.coordinates)): + if (x_object.coordinates[index_coordinate].unit.name == axes[2]): + oplot_x_time_index_coordinate=index_coordinate + for index_coordinate in range(len(x_object.coordinates)): + if (x_object.coordinates[index_coordinate].unit.name == axes[2]): + oplot_y_time_index_coordinate=index_coordinate + + if (d.coordinates[data_time_index_coordinate].unit.unit != x_object.coordinates[oplot_x_time_index_coordinate].unit.unit or + d.coordinates[data_time_index_coordinate].unit.unit != x_object.coordinates[oplot_y_time_index_coordinate].unit.unit): + raise TypeError('The '+axes[2]+' unit of the overplotted contour object differs from the data\'s. Interpolation cannot be done.') + + #Interpolate the path data to the time vector of the original data + try: + x_object_interp=flap.slice_data(x_object_name, + slicing={axes[2]:tdata}, + options={'Interpolation':'Linear'}, + output_name='X OBJ INTERP') + except: + raise ValueError('Interpolation cannot be done for the \'Data object X\' along axis '+axes[2]+'.') + try: + y_object_interp=flap.slice_data(y_object_name, + slicing={axes[2]:tdata}, + options={'Interpolation':'Linear'}, + output_name='Y OBJ INTERP') + except: + raise ValueError('Interpolation cannot be done for the \'Data object Y\' along axis '+axes[2]+'.') + + #Finding the time coordinate + for index_time in range(len(x_object.coordinates)): + if (x_object.coordinates[index_time].unit.name == axes[2]): + time_dimension_oplot=x_object.coordinates[index_time].dimension_list[0] + + #Convert the units of the path if its coordinates are not the same as the data object's. + unit_conversion_coeff=[1]*2 + original_units=[x_object.data_unit.unit,y_object.data_unit.unit] + for index_coordinate in range(len(d.coordinates)): + for index_axes in range(2): + if (d.coordinates[index_coordinate].unit.name == axes[index_axes]): + unit_conversion_coeff[index_axes] = flap.tools.unit_conversion(original_unit=original_units[index_axes], + new_unit=d.coordinates[index_coordinate].unit.unit) + overplot_options['path'][path_obj_keys]['data']={} + overplot_options['path'][path_obj_keys]['data']['Data resampled']=np.asarray([x_object_interp.data*unit_conversion_coeff[0], + y_object_interp.data*unit_conversion_coeff[1]]) + overplot_options['path'][path_obj_keys]['data']['Time dimension']=time_dimension_oplot - xy_object_interp=flap.slice_data(xy_object_name, - slicing={axes[2]:tdata}, - options={'Interpolation':'Linear'}, - output_name='XY OBJ INTERP') - overplot_options['contour'][contour_obj_keys]['data']={} - overplot_options['contour'][contour_obj_keys]['data']['Data resampled']=xy_object_interp.data - overplot_options['contour'][contour_obj_keys]['data']['X coord resampled']=xy_object_interp.coordinate(axes[0])[0]*unit_conversion_coeff[0] - overplot_options['contour'][contour_obj_keys]['data']['Y coord resampled']=xy_object_interp.coordinate(axes[1])[0]*unit_conversion_coeff[1] - overplot_options['contour'][contour_obj_keys]['data'][axes[2]]=tdata - overplot_options['contour'][contour_obj_keys]['data']['Time dimension']=time_dimension_index + for contour_obj_keys in overplot_options['contour']: + if (overplot_options['contour'][contour_obj_keys]['Plot']): + try: + xy_object_name=overplot_options['contour'][contour_obj_keys]['Data object'] + xy_object=flap.get_data_object(xy_object_name,exp_id=d.exp_id) + except: + raise ValueError(xy_object_name+'data is not available. The data object needs to be read first.') + if len(xy_object.data.shape) != 3: + raise ValueError('Contour object\'s, ('+xy_object_name+') data needs to be a 3D matrix (x,y,temporal), not necessarily in this order.') + + for index_coordinate in range(len(xy_object.coordinates)): + if (xy_object.coordinates[index_coordinate].unit.name == axes[2]): + time_dimension_index=xy_object.coordinates[index_coordinate].dimension_list[0] + oplot_time_index_coordinate=index_coordinate + + if d.coordinates[data_time_index_coordinate].unit.unit != xy_object.coordinates[oplot_time_index_coordinate].unit.unit: + raise TypeError('The '+axes[2]+' unit of the overplotted contour object differs from the data\'s. Interpolation cannot be done.') + + unit_conversion_coeff=[1.] * 3 + for index_data_coordinate in range(len(d.coordinates)): + for index_oplot_coordinate in range(len(xy_object.coordinates)): + for index_axes in range(3): + if ((d.coordinates[index_data_coordinate].unit.name == axes[index_axes]) and + (xy_object.coordinates[index_oplot_coordinate].unit.name) == axes[index_axes]): + unit_conversion_coeff[index_axes] = flap.tools.unit_conversion(original_unit=xy_object.coordinates[index_oplot_coordinate].unit.unit, + new_unit=d.coordinates[index_data_coordinate].unit.unit) + + xy_object_interp=flap.slice_data(xy_object_name, + slicing={axes[2]:tdata}, + options={'Interpolation':'Linear'}, + output_name='XY OBJ INTERP') + overplot_options['contour'][contour_obj_keys]['data']={} + overplot_options['contour'][contour_obj_keys]['data']['Data resampled']=xy_object_interp.data + overplot_options['contour'][contour_obj_keys]['data']['X coord resampled']=xy_object_interp.coordinate(axes[0])[0]*unit_conversion_coeff[0] + overplot_options['contour'][contour_obj_keys]['data']['Y coord resampled']=xy_object_interp.coordinate(axes[1])[0]*unit_conversion_coeff[1] + overplot_options['contour'][contour_obj_keys]['data'][axes[2]]=tdata + overplot_options['contour'][contour_obj_keys]['data']['Time dimension']=time_dimension_index # X range and Z range is processed here, but Y range not as it might have multiple entries for some plots xrange = _options['X range'] @@ -1267,7 +1343,8 @@ def _plot(data_object, ax.set_ylabel(ax_list[1].title(language=language)) - + ax.get_xaxis().set_visible(_options['Axes visibility'][0]) + ax.get_yaxis().set_visible(_options['Axes visibility'][1]) title = ax.get_title() if (title is None): @@ -1437,7 +1514,9 @@ def _plot(data_object, ax.set_ylabel(ax_list[1].title(language=language), complex_txt=[comptype,i_comp]) - + ax.get_xaxis().set_visible(_options['Axes visibility'][0]) + ax.get_yaxis().set_visible(_options['Axes visibility'][1]) + title = ax.get_title() if (title is None): title = '' @@ -1721,13 +1800,11 @@ def _plot(data_object, # No overplotting is possible for this type of plot, erasing and restarting a Plot_ID if (not _options['Clear']): - plt.subplot(_plot_id.base_subplot) plt.cla() gs = gridspec.GridSpecFromSubplotSpec(1, 1, subplot_spec=_plot_id.base_subplot) _plot_id.plt_axis_list = [] _plot_id.plt_axis_list.append(plt.subplot(gs[0,0])) ax = _plot_id.plt_axis_list[0] - pdd_list[2].data_type = PddType.Data pdd_list[2].data_object = d if ((pdd_list[0].data_type != PddType.Coordinate) or (pdd_list[1].data_type != PddType.Coordinate)) : @@ -1735,7 +1812,11 @@ def _plot(data_object, coord_x = pdd_list[0].value coord_y = pdd_list[1].value - + if _options['Equal axes']: + if (coord_x.unit.unit == coord_y.unit.unit): + ax.axis('equal') + else: + print('Equal axis is not possible. The axes units are not equal.') if (_plot_type == 'image'): if ((coord_x.mode.equidistant) and (len(coord_x.dimension_list) == 1) and (coord_y.mode.equidistant) and (len(coord_y.dimension_list) == 1)): @@ -1837,8 +1918,24 @@ def _plot(data_object, origin='lower',cmap=cmap, vmin=vmin,vmax=vmax,**_plot_opt) except Exception as e: - raise e - + raise e + + if (overplot_options is not None): + ax.set_autoscale_on(False) + for path_obj_keys in overplot_options['path']: + if overplot_options['path'][path_obj_keys]['Plot']: + ax.plot(overplot_options['path'][path_obj_keys]['data']['Data'][0,:]*axes_unit_conversion[0], + overplot_options['path'][path_obj_keys]['data']['Data'][1,:]*axes_unit_conversion[1], + color=overplot_options['path'][path_obj_keys]['Color']) + + for contour_obj_keys in overplot_options['contour']: + if overplot_options['contour'][contour_obj_keys]['Plot']: + ax.contour(overplot_options['contour'][contour_obj_keys]['data']['X coord'].transpose()*axes_unit_conversion[0], + overplot_options['contour'][contour_obj_keys]['data']['Y coord'].transpose()*axes_unit_conversion[1], + overplot_options['contour'][contour_obj_keys]['data']['Data'], + levels=overplot_options['contour'][contour_obj_keys]['nlevel'], + cmap=overplot_options['contour'][contour_obj_keys]['Colormap']) + if (_options['Colorbar']): cbar = plt.colorbar(img,ax=ax) if (d.data_unit.unit is not None) and (d.data_unit.unit != ''): @@ -1854,7 +1951,6 @@ def _plot(data_object, if (yrange is not None): ax.set_ylim(yrange[0]*axes_unit_conversion[1], yrange[1]*axes_unit_conversion[1]) - if axes_unit_conversion[0] == 1.: ax.set_xlabel(ax_list[0].title(language=language)) else: @@ -1866,7 +1962,8 @@ def _plot(data_object, else: ax.set_ylabel(ax_list[1].title(language=language, new_unit=_options['Plot units'][axes[1]])) - + ax.get_xaxis().set_visible(_options['Axes visibility'][0]) + ax.get_yaxis().set_visible(_options['Axes visibility'][1]) if (_options['Log x']): ax.set_xscale('log') if (_options['Log y']): @@ -2007,6 +2104,11 @@ def _plot(data_object, for it in range(len(tdata)): plt.subplot(_plot_id.base_subplot) ax_act = plt.subplot(gs[0,0]) + if _options['Equal axes']: + if (coord_x.unit.unit == coord_y.unit.unit): + ax_act.axis('equal') + else: + print('Equal axis is not possible. The axes units are not equal.') time_index = [slice(0,dim) for dim in d.data.shape] time_index[coord_t.dimension_list[0]] = it time_index = tuple(time_index) @@ -2094,7 +2196,11 @@ def _plot(data_object, overplot_options['contour'][contour_obj_keys]['data']['Data resampled'][time_index], levels=overplot_options['contour'][contour_obj_keys]['nlevel'], cmap=overplot_options['contour'][contour_obj_keys]['Colormap']) + + if (coord_x.unit.unit == coord_y.unit.unit): + ax_act.axis('equal') + if (_options['Colorbar']): cbar = plt.colorbar(img,ax=ax_act) cbar.set_label(d.data_unit.name) @@ -2283,7 +2389,6 @@ def _plot(data_object, anim = PlotAnimation(*oargs) anim.animate() - plt.show(block=False) _plot_id.axes = ax_list _plot_id.plot_data.append(pdd_list) @@ -2291,5 +2396,4 @@ def _plot(data_object, _plot_id.options.append(_options) _plot_id.plot_type = _plot_type set_plot_id(_plot_id) - return _plot_id diff --git a/flap/tools.py b/flap/tools.py index 87f6c70..b26240a 100644 --- a/flap/tools.py +++ b/flap/tools.py @@ -163,26 +163,27 @@ def submatrix_index(mx_shape, index): """ index_arrays = [] - mx_dim = len(mx_shape) - for i in range(mx_dim): - # Creating a matrix with 1 element in each direction and the - # number of elements in index[i] in the i-th dimension - shape = [1] * mx_dim - shape[i] = index[i].size - # Creating this matrix - ind = np.zeros(tuple(shape),dtype=int) - # Creating a list of indices with 0 at all places except at i where '...' - ind_ind = [0] * mx_dim - ind_ind[i] = ... - ind[tuple(ind_ind)] = index[i] - # Expanding this matrix in all other dimensions - for j in range(mx_dim): - if (j != i): - ind = np.repeat(ind,index[j].size,j) - index_arrays.append(ind) - -# for i in range(len(mx_shape)): #THIS IS A SOLUTION FOR LARGE MATRICES, BUT NOT COMMITED -# index_arrays.append(slice(min(index[i]),max(index[i])+1)) #DUE TO BEING UNTESTED. NEEDS TO BE UNCOMMENTED IF ONE WANTS TO USE IT + +# mx_dim = len(mx_shape) +# for i in range(mx_dim): +# # Creating a matrix with 1 element in each direction and the +# # number of elements in index[i] in the i-th dimension +# shape = [1] * mx_dim +# shape[i] = index[i].size +# # Creating this matrix +# ind = np.zeros(tuple(shape),dtype=int) +# # Creating a list of indices with 0 at all places except at i where '...' +# ind_ind = [0] * mx_dim +# ind_ind[i] = ... +# ind[tuple(ind_ind)] = index[i] +# # Expanding this matrix in all other dimensions +# for j in range(mx_dim): +# if (j != i): +# ind = np.repeat(ind,index[j].size,j) +# index_arrays.append(ind) + + for i in range(len(mx_shape)): #THIS IS A SOLUTION FOR LARGE MATRICES, BUT NOT COMMITED + index_arrays.append(slice(min(index[i]),max(index[i])+1)) #DUE TO BEING UNTESTED. NEEDS TO BE UNCOMMENTED IF ONE WANTS TO USE IT return tuple(index_arrays) From 838042a8f60de5f7782ea6b306dcff008553235e Mon Sep 17 00:00:00 2001 From: Mate Lampert Date: Thu, 21 Nov 2019 16:14:17 -0500 Subject: [PATCH 06/27] Multiple new option implementation Option changes: 'Video format': can be multiple types, OS is checked whether it's available in the CV2 or not. 'Overplot options': line added as an overplottable object. Description added to the options. 'Prevent saturation': Now it works for all the plot-types and doesn't change the values of the original data object. 'Equal axes': description added 'Axes visibility': description added Misc. mods: Locator variable is not used --> commented it out plotdata_low, plotdata_high are unused --> commented them out, Fixed the issue with the incorrect value of the [0,0] coordinate. d[0,0] +=1 assigns the value to the data object, which is undesired. c,ydata_low,ydata_high were undefined at lines 1437, 1446. I defined them. data_object.py: added a whitespace, no functional change. --- flap/data_object.py | 2 +- flap/plot.py | 286 ++++++++++++++++++++++++++++++++++---------- 2 files changed, 223 insertions(+), 65 deletions(-) diff --git a/flap/data_object.py b/flap/data_object.py index e1e2f81..7e8cc53 100644 --- a/flap/data_object.py +++ b/flap/data_object.py @@ -677,7 +677,7 @@ def _plot_error_ranges(self, index=None): else: return self.error[_index].flatten() - def _plot_coord_ranges(self,coord, c_data, c_low, c_high): + def _plot_coord_ranges(self, coord, c_data, c_low, c_high): """ Helper function to return error low and high limits from coordiniate data in the format needed by matplotlib """ diff --git a/flap/plot.py b/flap/plot.py index 735c31d..07d3fd7 100644 --- a/flap/plot.py +++ b/flap/plot.py @@ -27,11 +27,12 @@ - Constants are considered as unit-less, therefore they don't need forcing. """ - +import os import matplotlib.pyplot as plt import matplotlib.gridspec as gridspec import matplotlib.colors as colors import matplotlib.animation as animation +import matplotlib.lines as mlines from matplotlib.widgets import Button, Slider from matplotlib import ticker @@ -244,10 +245,11 @@ def animate(self): if (self.vmin <= 0): raise ValueError("z range[0] cannot be negative or zero for logarithmic scale.") self.norm = colors.LogNorm(vmin=self.vmin, vmax=self.vmax) - self.locator = ticker.LogLocator(subs='all') + #self.locator = ticker.LogLocator(subs='all') + ticker.LogLocator(subs='all') else: self.norm = None - self.locator = None + #self.locator = None _plot_opt = self.plot_options[0] @@ -393,6 +395,22 @@ def animate_plot(self, it): self.overplot_options['contour'][contour_obj_keys]['data']['Data resampled'][it,:,:], levels=self.overplot_options['contour'][contour_obj_keys]['nlevel'], cmap=self.overplot_options['contour'][contour_obj_keys]['Colormap']) + for line_obj_keys in self.overplot_options['line']: + xmin, xmax = self.ax_act.get_xbound() + ymin, ymax = self.ax_act.get_ybound() + if self.overplot_options['line'][line_obj_keys]['Plot']: + if self.overplot_options['line'][line_obj_keys]['Horizontal'] is not None: + h_coords=self.overplot_options['line'][line_obj_keys]['Horizontal'] + for segments in h_coords: + if segments[0] > ymin and segments[0] < ymax: + l = mlines.Line2D([xmin,xmax], [segments[0],segments[0]], color=segments[1]) + self.ax_act.add_line(l) + if self.overplot_options['line'][line_obj_keys]['Vertical'] is not None: + v_coords=self.overplot_options['line'][line_obj_keys]['Vertical'] + for segments in v_coords: + if segments[0] > xmin and segments[0] < xmax: + l = mlines.Line2D([segments[0],segments[0]], [ymin,ymax], color=segments[1]) + self.ax_act.add_line(l) if (self.xrange is not None): self.ax_act.set_xlim(self.xrange[0]*self.axes_unit_conversion[0], @@ -635,12 +653,12 @@ def __get_gca_invalid(): This flag is set invalid when set_plot changes the default plot and this way the Matplotlib current axis (get_gca()) becomes invalid for this plot. """ + global gca_invalid try: gca_invalid except NameError: gca_invalid = False - return gca_invalid - + return gca_invalid def sample_for_plot(x,y,x_error,y_error,n_points): """ @@ -767,29 +785,61 @@ def _plot(data_object, 'Waittime' : Time to wait [Seconds] between two images in anim-... type plots 'Video file': Name of output video file for anim-... plots 'Video framerate': Frame rate for output video. - 'Video format': Format of the video. Valid: 'avi' + 'Video format': Format of the video. Valid: 'avi' (windows,macOS,linux), + 'mkv', 'mp4' (macOS,linux) + Note for macOS: mp4 is the only embeddable in Powerpoint. 'Clear': Boolean. If True don't use the existing plots, generate new. (No overplotting.) 'Force axes': Force overplotting even if axes are incpomatible 'Colorbar': Boolelan. Switch on/off colorbar 'Nan color': The color to use in image data plotting for np.nan (not-a-number) values 'Interpolation': Interpolation method for image plot. 'Language': Language of certain standard elements in the plot. ('EN', 'HU') - 'Overplot options': Dictionary of overplotting options (EFIT for now): - 'Plot separatrix': Set to plot the separatrix onto the video - 'Separatrix X': Name of the flap.DataObject for the separatrix X data (usually R) - 'Separatrix Y': Name of the flap.DataObject for the separatrix Y data (usually z) - 'Separatrix XY': Name of the 2D flap.DataObject for the separatrix XY data (usually Rz) - 'Separatrix color': Color of the separatrix for the plot - 'Plot limiter': Set to plot the limiter onto the video - 'Limiter X': Name of the flap.DataObject for the limiter X data (usually R) - 'Limiter Y': Name of the flap.DataObject for the limiter Y data (usually z) - 'Limiter XY': Name of the 2D flap.DataObject for the limiter XY data (usually Rz) - 'Limiter color': Color of the limiter for the plot - 'Plot flux surfaces': Name of the 2D flap.DataObject for the flux surfaces - (should have the same coordinate names as the plot) - 'nlevels': Number of contour lines for the flux surface plotting + 'Overplot options': Dictionary of overplotting options: + Path, contour or line overplotting for the different plot-types: + Dictionary with keys of 'contour', 'path' or 'line': + + Example options['Overplot options']['contour']: + + options['Overplot options']['contour']=\ + {'CNAME':{'Data object':None, #2D spatial FLAP object name + 'Plot':False, #Boolean for plotting + 'Colormap':None, #Colormap for the contour + 'nlevel':51, #Level for the contour + 'Slicing':None, #Slicing for the FLAP object + }} + Can contain multiple CNAME (contour name) keywords, each is + plotted. Works in 2D plots. Should be in the same time unit as + the data is in the animations. + + Example options['Overplot options']['path']: + + options['Overplot options']['path']=\ + {'PNAME':{'Data object X':None, #1D spatial FLAP object name + 'Data object Y':None, #1D spatial FLAP object name + 'Plot':False, #BOOlean for plotting + 'Color':'black', #Color of the path + 'Slicing':None, #Slicing for the FLAP object + }} + Can contain multiple PNAME (path name) keyword, each is + plotted. Works in 2D plots. Should be in the same time unit as + the data is in the animations. + + Example options['Overplot options']['line']: + + options['Overplot options']['path']=\ + {'LNAME':{'Vertical':[X_coord,'red'], #[X coordinate in the unit of the plot, plot color] + 'Horizontal':[Y_coord,'blue'], #[Y coordinate in the unit of the plot, plot color] + 'Plot':False, + }} + Can contain multiple LNAME (line name) keywords. If 'Vertical' + keyword is present, a line is vertical plotted at X_coord. + If 'Horizontal' keyword is present, a line is plottad at Y_coord. + Works in all plot types. + 'Prevent saturation': Prevents saturation of the video signal when it exceeds zrange[1] It uses data modulo zrange[1] to overcome the saturation. (works for animation) + (default: False) + Caveat: doubles the memory usage of the plotted dataset during the plotting time. 'Plot units': The plotting units for each axis. It can be a dictionary or a list. Its use is different for the two cases as such: Dictionary input: keywords: axis name (e.g. 'Device R'), value: unit (e.g. 'mm') @@ -797,6 +847,11 @@ def _plot(data_object, e.g.: options['Plot units']={'Device R':'mm', 'Device z':'mm', 'Time':'ms'} List input: the number of values should correspond to the axes input as such: e.g.: axes=['Device R','Device z','Time'] --> options['Plot units']=['mm','mm','ms'] + 'Equal axes': IF the units of the x and y axes coordinates match, it makes the plot's + axes to have equal spacing. Doesn't care about the plot units, just data units. + (default: False) + 'Axes visibility': Hides the title and labels of the axes if set to [False,False]. First + value is for the X axis and the second is for the Y axis. (default: [True,True]) """ @@ -809,6 +864,7 @@ def _plot(data_object, 'Overplot options':None, 'Prevent saturation':False, 'Plot units':None, 'Equal axes':False, 'Axes visibility':[True,True], } + _options = flap.config.merge_options(default_options, options, data_source=data_object.data_source, section='Plot') if (plot_options is None): @@ -823,11 +879,21 @@ def _plot(data_object, raise TypeError("Invalid type for option Clear. Should be boolean.") if (type(_options['Force axes']) is not bool): raise TypeError("Invalid type for option 'Force axes'. Should be boolean.") - - if ((slicing is not None) or (summing is not None)): - d = data_object.slice_data(slicing=slicing, summing=summing, options=slicing_options) + + if (_options['Z range'] is not None) and (_options['Prevent saturation']): + if (_options['Z range'] is not None): + if ((type(_options['Z range']) is not list) or (len(_options['Z range']) != 2)): + raise ValueError("Invalid Z range setting.") + if ((slicing is not None) or (summing is not None)): + d = copy.deepcopy(data_object.slice_data(slicing=slicing, summing=summing, options=slicing_options)) + else: + d = copy.deepcopy(data_object) + d.data=np.mod(d.data,_options['Z range'][1]) else: - d = data_object + if ((slicing is not None) or (summing is not None)): + d = data_object.slice_data(slicing=slicing, summing=summing, options=slicing_options) + else: + d = data_object # Determining a PlotID: # argument, actual or a new one @@ -921,10 +987,15 @@ def _plot(data_object, language = _options['Language'] if (_options['Video file'] is not None): - if (_options['Video format'] == 'avi'): - video_codec_code = 'XVID' - else: - raise ValueError("Cannot write video in format '"+_options['Video format']+"'.") + if ((os.sys.platform == 'darwin' or 'linux' in os.sys.platform) and + (options['Video format'] not in ['avi','mkv', 'mp4'])): + raise ValueError("The chosen video format is not cupported on macOS.") + if os.sys.platform == 'win32' and _options['Video format'] != 'avi': + raise ValueError("The chosen video format is not cupported on Windowd.") + video_codec_decrypt={'avi':'XVID', + 'mkv':'X264', + 'mp4':'mp4v'} + video_codec_code=video_codec_decrypt[_options['Video format']] #These lines do the coordinate unit conversion axes_unit_conversion=np.zeros(len(axes)) @@ -968,7 +1039,6 @@ def _plot(data_object, 'Colormap':None, 'nlevel':51, 'Slicing':None, - 'Summing':None, }}, 'path':{'NAME':{'Data object X':None, @@ -976,13 +1046,11 @@ def _plot(data_object, 'Plot':False, 'Color':'black', 'Slicing':None, - 'Summing':None, }}, - 'line':{'NAME':{'Coord1':None, - 'Coord2':None, + 'line':{'NAME':{'Vertical':[0,'red'], + 'Horizontal':[1,'blue'], 'Plot':False, - 'Color':None, }} } @@ -1054,7 +1122,7 @@ def _plot(data_object, overplot_options['contour'][contour_obj_keys]['data']['Data']=xy_object.data overplot_options['contour'][contour_obj_keys]['data']['X coord']=xy_object.coordinate(axes[0])[0]*unit_conversion_coeff[0] overplot_options['contour'][contour_obj_keys]['data']['Y coord']=xy_object.coordinate(axes[1])[0]*unit_conversion_coeff[1] - + #TIME (AXES(2)) DEPENDENT OVERPLOTTING OBJECT CREATION if plot_type in ['anim-image','anim-contour','animation'] : for index_time in range(len(d.coordinates)): if (d.coordinates[index_time].unit.name == axes[2]): @@ -1211,8 +1279,8 @@ def _plot(data_object, raise e # Preparing data plotdata = [0]*2 - plotdata_low = [0]*2 #UNUSED - plotdata_high = [0]*2 #UNUSED +# plotdata_low = [0]*2 #UNUSED +# plotdata_high = [0]*2 #UNUSED ploterror = [0]*2 for ax_ind in range(2): if (pdd_list[ax_ind].data_type == PddType.Data): @@ -1333,7 +1401,24 @@ def _plot(data_object, if (yrange is not None): ax.set_ylim(yrange[0], - yrange[1]) + yrange[1]) + if overplot_options['line'] is not None: + for line_obj_keys in overplot_options['line']: + xmin, xmax = ax.get_xbound() + ymin, ymax = ax.get_ybound() + if overplot_options['line'][line_obj_keys]['Plot']: + if overplot_options['line'][line_obj_keys]['Horizontal'] is not None: + h_coords=overplot_options['line'][line_obj_keys]['Horizontal'] + for segments in h_coords: + if segments[0] > ymin and segments[0] < ymax: + l = mlines.Line2D([xmin,xmax], [segments[0],segments[0]], color=segments[1]) + ax.add_line(l) + if overplot_options['line'][line_obj_keys]['Vertical'] is not None: + v_coords=overplot_options['line'][line_obj_keys]['Vertical'] + for segments in v_coords: + if segments[0] > xmin and segments[0] < xmax: + l = mlines.Line2D([segments[0],segments[0]], [ymin,ymax], color=segments[1]) + ax.add_line(l) # Setting axis labels if axes_unit_conversion[0] == 1.: ax.set_xlabel(ax_list[0].title(language=language)) @@ -1428,24 +1513,30 @@ def _plot(data_object, yerror = yerror_abs # Checking for constants, converting to numpy array + if (np.isscalar(xdata)): + if (np.isscalar(ydata)): + xdata = np.full(1, xdata) + else: + xdata = np.full(ydata.size, xdata) + if (plot_error): + c=pdd_list[0].value #THESE THREE VARIABLES WERE UNDIFINED + xdata_low=np.min(xdata) + xdata_high=np.max(xdata) + xerror = d._plot_coord_ranges(c, xdata, xdata_low, xdata_high) + else: + xerror = None if (np.isscalar(ydata)): if (np.isscalar(xdata)): ydata = np.full(1, ydata) else: ydata = np.full(xdata.size, ydata) if (plot_error): - yerror = d._plot_coord_ranges(c, ydata, ydata_low, ydata_high) #THESE ARE UNDEFINED + c=pdd_list[1].value #THESE THREE VARIABLES WERE UNDIFINED + ydata_low=np.min(ydata) + ydata_high=np.max(ydata) + yerror = d._plot_coord_ranges(c, ydata, ydata_low, ydata_high) else: yerror = None - if (np.isscalar(xdata)): - if (np.isscalar(ydata)): - xdata = np.full(1, xdata) - else: - xdata = np.full(ydata.size, xdata) - if (plot_error): - xerror = d._plot_coord_ranges(c, xdata, xdata_low, xdata_high) #THESE ARE UNDEFINED - else: - xerror = None if (all_points is True): x = xdata @@ -1504,6 +1595,25 @@ def _plot(data_object, if (yrange is not None): ax.set_ylim(yrange[i_comp][0],yrange[i_comp][1]) + + if overplot_options['line'] is not None: + for line_obj_keys in overplot_options['line']: + xmin, xmax = ax.get_xbound() + ymin, ymax = ax.get_ybound() + if overplot_options['line'][line_obj_keys]['Plot']: + if overplot_options['line'][line_obj_keys]['Horizontal'] is not None: + h_coords=overplot_options['line'][line_obj_keys]['Horizontal'] + for segments in h_coords: + if segments[0] > ymin and segments[0] < ymax: + l = mlines.Line2D([xmin,xmax], [segments[0],segments[0]], color=segments[1]) + ax.add_line(l) + if overplot_options['line'][line_obj_keys]['Vertical'] is not None: + v_coords=overplot_options['line'][line_obj_keys]['Vertical'] + for segments in v_coords: + if segments[0] > xmin and segments[0] < xmax: + l = mlines.Line2D([segments[0],segments[0]], [ymin,ymax], color=segments[1]) + ax.add_line(l) + # Setting axis labels if axes_unit_conversion[0] == 1.: ax.set_xlabel(ax_list[0].title(language=language)) @@ -1731,6 +1841,24 @@ def _plot(data_object, if (yrange is not None): ax.set_ylim(yrange[0],yrange[1]) + if overplot_options['line'] is not None: + for line_obj_keys in overplot_options['line']: + xmin, xmax = ax.get_xbound() + ymin, ymax = ax.get_ybound() + if overplot_options['line'][line_obj_keys]['Plot']: + if overplot_options['line'][line_obj_keys]['Horizontal'] is not None: + h_coords=overplot_options['line'][line_obj_keys]['Horizontal'] + for segments in h_coords: + if segments[0] > ymin and segments[0] < ymax: + l = mlines.Line2D([xmin,xmax], [segments[0],segments[0]], color=segments[1]) + ax.add_line(l) + if overplot_options['line'][line_obj_keys]['Vertical'] is not None: + v_coords=overplot_options['line'][line_obj_keys]['Vertical'] + for segments in v_coords: + if segments[0] > xmin and segments[0] < xmax: + l = mlines.Line2D([segments[0],segments[0]], [ymin,ymax], color=segments[1]) + ax.add_line(l) + if axes_unit_conversion[0] == 1.: ax.set_xlabel(ax_list[0].title(language=language)) else: @@ -1777,7 +1905,7 @@ def _plot(data_object, raise TypeError("Image/contour plot is applicable only to real data.") # Checking for numeric type try: - d.data[0,0] += 1 + d.data[0,0]+1 except TypeError: raise TypeError("Image plot is applicable only to numeric data.") @@ -1868,10 +1996,11 @@ def _plot(data_object, if (vmin <= 0): raise ValueError("z range[0] cannot be negative or zero for logarithmic scale.") norm = colors.LogNorm(vmin=vmin, vmax=vmax) - locator = ticker.LogLocator(subs='all') + ticker.LogLocator(subs='all') + #locator = ticker.LogLocator(subs='all') #VARIABLE WAS UNUSED --> UNASSIGNED NOW else: norm = None - locator = None + #locator = None if (contour_levels is None): contour_levels = 255 @@ -1934,7 +2063,23 @@ def _plot(data_object, overplot_options['contour'][contour_obj_keys]['data']['Y coord'].transpose()*axes_unit_conversion[1], overplot_options['contour'][contour_obj_keys]['data']['Data'], levels=overplot_options['contour'][contour_obj_keys]['nlevel'], - cmap=overplot_options['contour'][contour_obj_keys]['Colormap']) + cmap=overplot_options['contour'][contour_obj_keys]['Colormap']) + for line_obj_keys in overplot_options['line']: + xmin, xmax = ax.get_xbound() + ymin, ymax = ax.get_ybound() + if overplot_options['line'][line_obj_keys]['Plot']: + if overplot_options['line'][line_obj_keys]['Horizontal'] is not None: + h_coords=overplot_options['line'][line_obj_keys]['Horizontal'] + for segments in h_coords: + if segments[0] > ymin and segments[0] < ymax: + l = mlines.Line2D([xmin,xmax], [segments[0],segments[0]], color=segments[1]) + ax.add_line(l) + if overplot_options['line'][line_obj_keys]['Vertical'] is not None: + v_coords=overplot_options['line'][line_obj_keys]['Vertical'] + for segments in v_coords: + if segments[0] > xmin and segments[0] < xmax: + l = mlines.Line2D([segments[0],segments[0]], [ymin,ymax], color=segments[1]) + ax.add_line(l) if (_options['Colorbar']): cbar = plt.colorbar(img,ax=ax) @@ -1964,6 +2109,7 @@ def _plot(data_object, new_unit=_options['Plot units'][axes[1]])) ax.get_xaxis().set_visible(_options['Axes visibility'][0]) ax.get_yaxis().set_visible(_options['Axes visibility'][1]) + if (_options['Log x']): ax.set_xscale('log') if (_options['Log y']): @@ -2001,7 +2147,7 @@ def _plot(data_object, raise TypeError("Animated image plot is applicable only to real data.") # Checking for numeric type try: - d.data[0,0] += 1 + d.data[0,0]+1 #THERE WAS A BUG HERE +=1 assigns the value to the element and messes up the data. except TypeError: raise TypeError("Animated image plot is applicable only to numeric data.") @@ -2089,7 +2235,6 @@ def _plot(data_object, index[coord_t.dimension_list[0]] = 0 ydata = np.squeeze(coord_y.data(data_shape=d.shape,index=index)[0]) xdata = np.squeeze(coord_x.data(data_shape=d.shape,index=index)[0]) - try: cmap_obj = plt.cm.get_cmap(cmap) if (_options['Nan color'] is not None): @@ -2127,16 +2272,16 @@ def _plot(data_object, if (vmin <= 0): raise ValueError("z range[0] cannot be negative or zero for logarithmic scale.") norm = colors.LogNorm(vmin=vmin, vmax=vmax) - locator = ticker.LogLocator(subs='all') + #locator = ticker.LogLocator(subs='all') + ticker.LogLocator(subs='all') else: norm = None - locator = None #UNUSED + #locator = None if (contour_levels is None): contour_levels = 255 _plot_opt = _plot_options[0] - if (image_like and (_plot_type == 'anim-image')): try: if (coord_x.dimension_list[0] < coord_y.dimension_list[0]): @@ -2195,11 +2340,24 @@ def _plot(data_object, overplot_options['contour'][contour_obj_keys]['data']['Y coord resampled'][time_index].transpose()*axes_unit_conversion[1], overplot_options['contour'][contour_obj_keys]['data']['Data resampled'][time_index], levels=overplot_options['contour'][contour_obj_keys]['nlevel'], - cmap=overplot_options['contour'][contour_obj_keys]['Colormap']) + cmap=overplot_options['contour'][contour_obj_keys]['Colormap']) - - if (coord_x.unit.unit == coord_y.unit.unit): - ax_act.axis('equal') + for line_obj_keys in overplot_options['line']: + xmin, xmax = ax_act.get_xbound() + ymin, ymax = ax_act.get_ybound() + if overplot_options['line'][line_obj_keys]['Plot']: + if overplot_options['line'][line_obj_keys]['Horizontal'] is not None: + h_coords=overplot_options['line'][line_obj_keys]['Horizontal'] + for segments in h_coords: + if segments[0] > ymin and segments[0] < ymax: + l = mlines.Line2D([xmin,xmax], [segments[0],segments[0]], color=segments[1]) + ax_act.add_line(l) + if overplot_options['line'][line_obj_keys]['Vertical'] is not None: + v_coords=overplot_options['line'][line_obj_keys]['Vertical'] + for segments in v_coords: + if segments[0] > xmin and segments[0] < xmax: + l = mlines.Line2D([segments[0],segments[0]], [ymin,ymax], color=segments[1]) + ax_act.add_line(l) if (_options['Colorbar']): cbar = plt.colorbar(img,ax=ax_act) @@ -2287,7 +2445,7 @@ def _plot(data_object, raise TypeError("Animated image plot is applicable only to real data.") # Checking for numeric type try: - d.data[0,0] += 1 + d.data[0,0]+1 except TypeError: raise TypeError("Animated image plot is applicable only to numeric data.") @@ -2295,8 +2453,6 @@ def _plot(data_object, if (yrange is not None): if ((type(yrange) is not list) or (len(yrange) != 2)): raise ValueError("Invalid Y range setting.") - if (zrange is not None) and (_options['Prevent saturation']): - d.data=np.mod(d.data,zrange[1]) # Processing axes # Although the plot will be cleared the existing plot axes will be considered default_axes = [d.coordinates[0].unit.name, d.coordinates[1].unit.name,d.coordinates[2].unit.name,'__Data__'] @@ -2389,11 +2545,13 @@ def _plot(data_object, anim = PlotAnimation(*oargs) anim.animate() - plt.show(block=False) + plt.show(block=False) _plot_id.axes = ax_list _plot_id.plot_data.append(pdd_list) #Setting this one the default plot ID _plot_id.options.append(_options) _plot_id.plot_type = _plot_type set_plot_id(_plot_id) + if _options['Prevent saturation']: + del d return _plot_id From 239fe02fe51cfb2821b40d9127936bf412a9a0fa Mon Sep 17 00:00:00 2001 From: Mate Lampert Date: Fri, 22 Nov 2019 13:59:01 -0500 Subject: [PATCH 07/27] Image like plot unit bugfix Axes unit conversions were not working for image_like plotting so far. Now it shows the correct coordinate. --- flap/plot.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/flap/plot.py b/flap/plot.py index 07d3fd7..87b0db7 100644 --- a/flap/plot.py +++ b/flap/plot.py @@ -166,8 +166,10 @@ def __init__(self, ax_list, axes, axes_unit_conversion, d, xdata, ydata, tdata, self.xdata = xdata self.ydata = ydata - self.xdata_range = xdata_range - self.ydata_range = ydata_range + self.xdata_range = [self.axes_unit_conversion[0] * xdata_range[0], + self.axes_unit_conversion[0] * xdata_range[1]] + self.ydata_range = [self.axes_unit_conversion[1] * ydata_range[0], + self.axes_unit_conversion[1] * ydata_range[1]] self.xrange = xrange self.yrange = yrange @@ -347,10 +349,9 @@ def animate_plot(self, it): self.time_slider.eventson = True self.current_frame = it - + print(self.axes_unit_conversion) plot_opt = copy.deepcopy(self.plot_options[0]) self.ax_act.clear() - if (self.image_like): try: if (self.coord_x.dimension_list[0] < self.coord_y.dimension_list[0]): @@ -1978,6 +1979,10 @@ def _plot(data_object, if (image_like): xdata_range = coord_x.data_range(data_shape=d.shape)[0] ydata_range = coord_y.data_range(data_shape=d.shape)[0] + xdata_range = [axes_unit_conversion[0] * xdata_range[0], + axes_unit_conversion[0] * xdata_range[1]] + ydata_range = [axes_unit_conversion[1] * ydata_range[0], + axes_unit_conversion[1] * ydata_range[1]] else: xdata,xdata_low,xdata_high = coord_x.data(data_shape=d.shape) ydata,ydata_low,ydata_high = coord_y.data(data_shape=d.shape) @@ -2230,6 +2235,10 @@ def _plot(data_object, if (image_like and (_plot_type == 'anim-image')): xdata_range = coord_x.data_range(data_shape=d.shape)[0] ydata_range = coord_y.data_range(data_shape=d.shape)[0] + xdata_range = [axes_unit_conversion[0] * xdata_range[0], + axes_unit_conversion[0] * xdata_range[1]] + ydata_range = [axes_unit_conversion[1] * ydata_range[0], + axes_unit_conversion[1] * ydata_range[1]] else: index = [...]*3 index[coord_t.dimension_list[0]] = 0 From e445625896a5d4233e2d3cf564ea57a9b9c26f17 Mon Sep 17 00:00:00 2001 From: Mate Lampert Date: Fri, 22 Nov 2019 16:48:07 -0500 Subject: [PATCH 08/27] Hanning window is working spectral_analysis.py: Fixed the hanning window option. plot.py: Bugfix --- flap/plot.py | 17 +++++++++++------ flap/spectral_analysis.py | 10 ++++++---- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/flap/plot.py b/flap/plot.py index 87b0db7..26f4a43 100644 --- a/flap/plot.py +++ b/flap/plot.py @@ -165,11 +165,16 @@ def __init__(self, ax_list, axes, axes_unit_conversion, d, xdata, ydata, tdata, self.tdata = tdata self.xdata = xdata self.ydata = ydata - - self.xdata_range = [self.axes_unit_conversion[0] * xdata_range[0], - self.axes_unit_conversion[0] * xdata_range[1]] - self.ydata_range = [self.axes_unit_conversion[1] * ydata_range[0], - self.axes_unit_conversion[1] * ydata_range[1]] + if xdata_range is not None: + self.xdata_range = [self.axes_unit_conversion[0] * xdata_range[0], + self.axes_unit_conversion[0] * xdata_range[1]] + else: + self.xdata_range=None + if ydata_range is not None: + self.ydata_range = [self.axes_unit_conversion[1] * ydata_range[0], + self.axes_unit_conversion[1] * ydata_range[1]] + else: + self.ydata_range=None self.xrange = xrange self.yrange = yrange @@ -349,7 +354,7 @@ def animate_plot(self, it): self.time_slider.eventson = True self.current_frame = it - print(self.axes_unit_conversion) + plot_opt = copy.deepcopy(self.plot_options[0]) self.ax_act.clear() if (self.image_like): diff --git a/flap/spectral_analysis.py b/flap/spectral_analysis.py index b53d532..bc3d86f 100644 --- a/flap/spectral_analysis.py +++ b/flap/spectral_analysis.py @@ -972,14 +972,15 @@ def _cpsd(d, ref=None, coordinate=None, intervals=None, options=None): if (hanning): hanning_window = np.hanning(index_int_high[0] - index_int_low[0] + 1) hanning_window /= math.sqrt(3./8) + hanning_window_ref=hanning_window if (len(d.shape) > 1): han_sh = [1] * len(d.shape) han_sh[proc_dim] = hanning_window.size hanning_window = hanning_window.reshape(han_sh) if (len(_ref.shape) > 1): han_sh = [1] * len(_ref.shape) - han_sh[proc_dim_ref] = hanning_window.size - hanning_window_ref = hanning_window.reshape(han_sh) + han_sh[proc_dim_ref] = hanning_window_ref.size + hanning_window_ref = hanning_window_ref.reshape(han_sh) # We need to determine a shape to which the out_data_num array will be broadcasted to # allow dividing all spectra. bs is this shape @@ -1016,8 +1017,9 @@ def _cpsd(d, ref=None, coordinate=None, intervals=None, options=None): except Exception as e: raise e if (hanning): - data_proc *= hanning_window.astype(data_proc.dtype) - data_proc_ref *= hanning_window_ref.astype(data_proc_ref.dtype) + data_proc = data_proc * hanning_window.astype(data_proc.dtype) + data_proc_ref = data_proc_ref * hanning_window_ref.astype(data_proc_ref.dtype) + # Calculating FFT dfft = np.fft.fft(data_proc,axis=proc_dim) dfft_ref = np.fft.fft(data_proc_ref,axis=proc_dim_ref) From 82b5fc42e484b2a5200f37a7df6e3cb74726a38b Mon Sep 17 00:00:00 2001 From: Mate Lampert Date: Fri, 22 Nov 2019 17:46:12 -0500 Subject: [PATCH 09/27] Misc mods One bugfix in spectral analysis and a whitespace in plot.py --- flap/plot.py | 1 + flap/spectral_analysis.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/flap/plot.py b/flap/plot.py index 26f4a43..10bed24 100644 --- a/flap/plot.py +++ b/flap/plot.py @@ -2559,6 +2559,7 @@ def _plot(data_object, anim = PlotAnimation(*oargs) anim.animate() + plt.show(block=False) _plot_id.axes = ax_list _plot_id.plot_data.append(pdd_list) diff --git a/flap/spectral_analysis.py b/flap/spectral_analysis.py index bc3d86f..f8d24cb 100644 --- a/flap/spectral_analysis.py +++ b/flap/spectral_analysis.py @@ -462,7 +462,7 @@ def _apsd(d, coordinate=None, intervals=None, options=None): except Exception as e: raise e if (hanning): - data_proc *= hanning_window + data_proc = data_proc * hanning_window # Calculating APS on natural resolution, full frequency scale dfft = np.fft.fft(data_proc,axis=proc_dim) dps = (dfft.conjugate() * dfft).real From 89c91d3c5570bc6c1900221bd8906932f26c5f6b Mon Sep 17 00:00:00 2001 From: Mate Lampert Date: Fri, 22 Nov 2019 18:20:57 -0500 Subject: [PATCH 10/27] Bugfix Sandor's implementation is working, I shouldn't have fixed the Spyder warning. --- flap/plot copy.py | 2174 +++++++++++++++++++++++++++++++++++++++++++++ flap/plot.py | 5 +- 2 files changed, 2176 insertions(+), 3 deletions(-) create mode 100644 flap/plot copy.py diff --git a/flap/plot copy.py b/flap/plot copy.py new file mode 100644 index 0000000..95bafeb --- /dev/null +++ b/flap/plot copy.py @@ -0,0 +1,2174 @@ +# -*- coding: utf-8 -*- +""" +Created on Sat May 18 18:37:06 2019 + +@author: Zoletnik +@coauthor: Lampert +""" +""" Notes on how to use subplots. + plt.gca() returns the current subplot + to create sub-sub plots use + gs = gridspec.GridSpecFromSubplotSpec(2, 2, subplot_spec=gs0) + Then create the figures (axes): + ax1 = plt.subplot(gs[0, :]) + Use ax1.plot and other calls to plot onto the figure. + + Note on FLAP plotting mechanism. + - The axes paramater of plot() can be the following: + string: Name of a coordinate or __Data__. This latter means the data in the data object is on the axis. + float value: Constant + data object: Enables plotting one data object as a function of another + - When a plot is made parameters are recorded in PlotID. This later enables overplotting + using the same axes. When the axis are incompatble in overplotting they can be forced. As + a link to the data objects involved in the plot are also recorded it is also possible + to regenerate the plot when megnified and 'All points' is False. + - If axis are forced or no axis name and unit is avalilable (like in constant) overplotting is possible + without forcing. If forced the name and unit of the incompatible axes will be set to ''. + - Constants are considered as unit-less, therefore they don't need forcing. + +""" + +import matplotlib.pyplot as plt +import matplotlib.gridspec as gridspec +import matplotlib.colors as colors +import matplotlib.animation as animation +from matplotlib.widgets import Button, Slider +from matplotlib import ticker + +import numpy as np +import copy +from enum import Enum +import math +import time + +try: + import cv2 + cv2_presence = True +except: + print('OpenCV is not present on the computer. Video saving is not available.') + cv2_presence = False + +#from .coordinate import * +#from .tools import * +import flap.config +import flap.tools +import flap.coordinate + +global act_plot_id, gca_invalid + +class PddType(Enum): + Coordinate = 0 + Constant = 1 + Data = 2 + +class PlotDataDescription: + """ Plot axis description for use in PlotID() and plot(). + data_object: The data object from which the data for this coordinate originates. This may + be None if the data is constant. + data_type: PddType + PddType.Coordinate: A coordinate in data_object. + self.value is a flap.Coordinate object. + PddType.Constant: A float constant, stored in value. + PddType.Data: The data in self.data_object. + value: Value, see above + """ + def __init__(self, data_type=None, data_object=None, value=None): + self.data_type = data_type + self.data_object = data_object + self.value = value + +def axes_to_pdd_list(d,axes): + """ Convert a plot() axes parameter to a list of PlotAxisDescription and axes list for PlotID + d: data object + axes: axes parameter of plot() + + return pdd_list, ax_list + """ + if (axes is None): + return [],[] + if (type(axes) is not list): + _axes = [axes] + else: + _axes = axes + + pdd_list = [] + ax_list = [] + for ax in _axes: + if (type(ax) is str): + if (ax != '__Data__'): + try: + coord = d.get_coordinate_object(ax) + except ValueError as e: + raise e + pdd = PlotDataDescription(data_type=PddType.Coordinate, + data_object=d, + value=coord + ) + axx = coord.unit + else: + if (d.data is None): + raise ValueError("No data available for plotting.") + pdd = PlotDataDescription(data_type=PddType.Data, + data_object=d + ) + axx = d.data_unit + elif (type(ax) is type(d)): + if (ax.data is None): + raise ValueError("No data available for axis.") + pdd = PlotDataDescription(data_type=PddType.Data, + data_object=ax + ) + axx = ax.data_unit + else: + try: + val = float(ax) + except ValueError: + raise ValueError("Invalid axis description.") + pdd = PlotDataDescription(data_type=PddType.Constant, + value=val + ) + axx = flap.coordinate.Unit() + pdd_list.append(pdd) + ax_list.append(axx) + return pdd_list,ax_list + +class PlotAnimation: + + def __init__(self, ax_list, axes, d, xdata, ydata, tdata, xdata_range, ydata_range, + cmap_obj, contour_levels, coord_t, coord_x, + coord_y, cmap, options, xrange, yrange, + zrange, image_like, plot_options, language, plot_id, gs): + + self.ax_list = ax_list + self.axes = axes + self.contour_levels = contour_levels + self.cmap = cmap + self.cmap_obj = cmap_obj + self.coord_t = coord_t + self.coord_x = coord_x + self.coord_y = coord_y + self.current_frame = 0. + self.d = d + self.fig = plt.figure(plot_id.figure) + self.gs = gs + self.image_like = image_like + self.language = language + self.options = options + self.pause = False + self.plot_id = plot_id + self.plot_options = plot_options + self.speed = 40. + + self.tdata = tdata + self.xdata = xdata + self.ydata = ydata + + self.xdata_range = xdata_range + self.ydata_range = ydata_range + + self.xrange = xrange + self.yrange = yrange + self.zrange = zrange + + + if (self.contour_levels is None): + self.contour_levels = 255 + + def animate(self): + + #These lines do the coordinate unit conversion + self.axes_unit_conversion=np.zeros(len(self.axes)) + self.axes_unit_conversion[:]=1. + + if self.options['Plot units'] is not None: + unit_length=len(self.options['Plot units']) + if unit_length > 3: + raise ValueError('Only three units are allowed for the three coordinates.') + unit_conversion_coeff={} + for plot_unit_name in self.options['Plot units']: + for index_data_unit in range(len(self.d.coordinates)): + if (plot_unit_name == self.d.coordinates[index_data_unit].unit.name): + data_coordinate_unit=self.d.coordinates[index_data_unit].unit.unit + plot_coordinate_unit=self.options['Plot units'][plot_unit_name] + unit_conversion_coeff[plot_unit_name]=flap.tools.unit_conversion(original_unit=data_coordinate_unit, + new_unit=plot_coordinate_unit) + for index_axes in range(len(self.axes)): + if self.axes[index_axes] in self.options['Plot units']: + self.axes_unit_conversion[index_axes]=unit_conversion_coeff[self.axes[index_axes]] + + pause_ax = plt.figure(self.plot_id.figure).add_axes((0.78, 0.94, 0.1, 0.04)) + self.pause_button = Button(pause_ax, 'Pause', hovercolor='0.975') + self.pause_button.on_clicked(self._pause_animation) + pause_ax._button=self.pause_button + + reset_ax = plt.figure(self.plot_id.figure).add_axes((0.78, 0.89, 0.1, 0.04)) + reset_button = Button(reset_ax, 'Reset', hovercolor='0.975') + reset_button.on_clicked(self._reset_animation) + reset_ax._button=reset_button + + slow_ax = plt.figure(self.plot_id.figure).add_axes((0.88, 0.94, 0.1, 0.04)) + self.slow_button = Button(slow_ax, str(int(1000./(self.speed/0.8)))+'fps', hovercolor='0.975') + self.slow_button.on_clicked(self._slow_animation) + slow_ax._button=self.slow_button + + speed_ax = plt.figure(self.plot_id.figure).add_axes((0.88, 0.89, 0.1, 0.04)) + self.speed_button = Button(speed_ax, str(int(1000./(self.speed*0.8)))+'fps', hovercolor='0.975') + self.speed_button.on_clicked(self._speed_animation) + speed_ax._button=self.speed_button + + + slider_ax = plt.figure(self.plot_id.figure).add_axes((0.1, 0.94, 0.5, 0.04)) + self.time_slider = Slider(slider_ax, label=self.axes[2], + valmin=self.tdata[0]*self.axes_unit_conversion[2], + valmax=self.tdata[-1]*self.axes_unit_conversion[2], + valinit=self.tdata[0]*self.axes_unit_conversion[2]) + self.time_slider.on_changed(self._set_animation) + + plt.subplot(self.plot_id.base_subplot) + + self.ax_act = plt.subplot(self.gs[0,0]) + +#The following lines set the axes to be equal if the units of the axes-to-be-plotted are the same + + axes_coordinate_decrypt=[0] * len(self.axes) + for i_axes in range(len(self.axes)): + for j_coordinate in range(len(self.d.coordinates)): + if (self.d.coordinates[j_coordinate].unit.name == self.axes[i_axes]): + axes_coordinate_decrypt[i_axes]=j_coordinate + for i_check in range(len(self.axes)) : + for j_check in range(i_check+1,len(self.axes)): + if (self.d.coordinates[axes_coordinate_decrypt[i_check]].unit.unit == + self.d.coordinates[axes_coordinate_decrypt[j_check]].unit.unit): + self.ax_act.axis('equal') + + + + time_index = [slice(0,dim) for dim in self.d.data.shape] + time_index[self.coord_t.dimension_list[0]] = 0 + time_index = tuple(time_index) + + act_ax_pos=self.ax_act.get_position() + slider_ax.set_position([act_ax_pos.x0,0.94,0.5,0.04]) + + if (self.zrange is None): + self.zrange=[np.nanmin(self.d.data), + np.nanmax(self.d.data)] + self.vmin = self.zrange[0] + self.vmax = self.zrange[1] + +# if (self.zrange is None): +# self.vmin = np.nanmin(self.d.data[time_index]) +# self.vmax = np.nanmax(self.d.data[time_index]) +# else: +# self.vmin = self.zrange[0] +# self.vmax = self.zrange[1] + + + if (self.vmax <= self.vmin): + raise ValueError("Invalid z range.") + + if (self.options['Log z']): + if (self.vmin <= 0): + raise ValueError("z range[0] cannot be negative or zero for logarithmic scale.") + self.norm = colors.LogNorm(vmin=self.vmin, vmax=self.vmax) + self.locator = ticker.LogLocator(subs='all') + else: + self.norm = None + self.locator = None + + + _plot_opt = self.plot_options[0] + + if (self.image_like): + try: + #There is a problem here, but I cant find it. Image is rotated with 90degree here, but not in anim-image. + #if (self.coord_x.dimension_list[0] == 0): + if (self.coord_x.dimension_list[0] < self.coord_y.dimension_list[0]): + im = np.clip(np.transpose(self.d.data[time_index]),self.vmin,self.vmax) + else: + im = np.clip(self.d.data[time_index],self.vmin,self.vmax) + img = plt.imshow(im,extent=self.xdata_range + self.ydata_range,norm=self.norm, + cmap=self.cmap_obj,vmin=self.vmin,aspect=self.options['Aspect ratio'],interpolation=self.options['Interpolation'], + vmax=self.vmax,origin='lower',**_plot_opt) + del im + except Exception as e: + raise e + else: + if (len(self.xdata.shape) == 3 and len(self.xdata.shape) == 3): + #xgrid, ygrid = flap.tools.grid_to_box(self.xdata[0,:,:],self.ydata[0,:,:]) #Same issue, time is not necessarily the first flap.coordinate. + xgrid, ygrid = flap.tools.grid_to_box(self.xdata[0,:,:]*self.axes_unit_conversion[0],self.ydata[0,:,:]*self.axes_unit_conversion[1]) #Same issue, time is not necessarily the first flap.coordinate. + else: + xgrid, ygrid = flap.tools.grid_to_box(self.xdata*self.axes_unit_conversion[0], + self.ydata*self.axes_unit_conversion[1]) + + im = np.clip(np.transpose(self.d.data[time_index]),self.vmin,self.vmax) + try: + img = plt.pcolormesh(xgrid,ygrid,im,norm=self.norm,cmap=self.cmap,vmin=self.vmin, + vmax=self.vmax,**_plot_opt) + except Exception as e: + raise e + del im + + self.xdata_range=None + self.ydata_range=None + + if (self.options['Colorbar']): + cbar = plt.colorbar(img,ax=self.ax_act) + cbar.set_label(self.d.data_unit.name) +#EFIT overplot feature implementation: + #It needs to be more generalied in the future as the coordinates are not necessarily in this order: [time_index,spat_index] + #This needs to be cross-checked with the time array's dimensions wherever there is a call for a certain index. + if ('EFIT options' in self.options and self.options['EFIT options'] is not None): + + default_efit_options={'Plot limiter': None, + 'Limiter X': None, + 'Limiter Y': None, + 'Limiter 2D': None, + 'Limiter color': 'white', + 'Plot separatrix': None, + 'Separatrix X': None, + 'Separatrix Y': None, + 'Separatrix 2D': None, + 'Separatrix color': 'red', + 'Plot flux': None, + 'Flux XY': None, + 'Flux nlevel': 51} + + self.efit_options=flap.config.merge_options(default_efit_options,self.options['EFIT options'],data_source=self.d.data_source) + self.efit_data={'limiter': {'Time':[],'Data':[]}, + 'separatrix':{'Time':[],'Data':[]}, + 'flux': {'Time':[],'Data':[]}} + + for setting in ['limiter','separatrix']: + if (self.efit_options['Plot '+setting]): + if (((self.efit_options[setting.capitalize()+' X']) and + (self.efit_options[setting.capitalize()+' Y' ])) or + (self.efit_options[setting.capitalize()+' 2D'])): + + if ((self.efit_options[setting.capitalize()+' X']) and + (self.efit_options[setting.capitalize()+' Y'])): + try: + R_object=flap.get_data_object(self.efit_options[setting.capitalize()+' X'],exp_id=self.d.exp_id) + except: + raise ValueError("The objects "+self.efit_options[setting.capitalize()+' X']+" cannot be read.") + try: + Z_object=flap.get_data_object(self.efit_options[setting.capitalize()+' Y'],exp_id=self.d.exp_id) + except: + raise ValueError("The objects "+self.efit_options[setting.capitalize()+' Y']+" cannot be read.") + if (len(R_object.data.shape) != 2 or len(Z_object.data.shape) != 2): + raise ValueError("The "+setting.capitalize()+' Y'+" data is not 1D. Use 2D or modify data reading.") + self.efit_data[setting]['Data']=np.asarray([R_object.data,Z_object.data]) + self.efit_data[setting]['Time']=R_object.coordinate('Time')[0][:,0] #TIME IS NOT ALWAYS THE FIRST COORDINATE, ELLIPSIS CHANGE SHOULD BE IMPLEMENTED + elif (self.efit_options[setting.capitalize()+' XY']): + try: + R_object=flap.get_data_object(self.efit_options[setting.capitalize()+' 2D'],exp_id=self.d.exp_id) + except: + raise ValueError(setting.capitalize()+' 2D data is not available. FLAP data object needs to be read first.') + if R_object.data.shape[2] == 2: + self.efit_data[setting]['Data']=np.asarray([R_object.data[:,:,0],R_object.data[:,:,1]]) + else: + raise ValueError(setting.capitalize()+' XY data needs to be in the format [n_time,n_points,2 (xy)') + self.efit_data[setting]['Time']=R_object.coordinate('Time')[0][:,0,0] #TIME IS NOT ALWAYS THE FIRST COORDINATE, ELLIPSIS CHANGE SHOULD BE IMPLEMENTED + else: + raise ValueError('Both '+setting.capitalize()+' X and'+ + setting.capitalize()+' Y or '+ + setting.capitalize()+' 2D need to be set.') + + for index_coordinate in range(len(self.d.coordinates)): + if ((self.d.coordinates[index_coordinate].unit.name in self.axes) and + (self.d.coordinates[index_coordinate].unit.name != 'Time')): + coordinate_index=index_coordinate + #Spatial unit translation (mm vs m usually) + if (R_object.data_unit.unit != self.d.coordinates[coordinate_index].unit.unit): + try: + coeff_efit_spatial=flap.tools.spatial_unit_translation(R_object.data_unit.unit) + except: + raise ValueError("Time unit translation cannot be made. Check the time unit of the object.") + try: + coeff_data_spatial=flap.tools.spatial_unit_translation(self.d.coordinates[coordinate_index].unit.unit) + except: + raise ValueError("Spatial unit translation cannot be made. Check the time unit of the object.") + #print('Spatial unit translation factor: '+str(coeff_efit_spatial/coeff_data_spatial)) + self.efit_data[setting]['Data'] *= coeff_efit_spatial/coeff_data_spatial + #Time translation (usually ms vs s) + for index_time in range(len(self.d.coordinates)): + if (self.d.coordinates[index_time].unit.name == 'Time'): + time_index_data=index_time + + for index_time in range(len(R_object.coordinates)): + if (self.d.coordinates[index_time].unit.name == 'Time'): + time_index_efit=index_time + + if (R_object.coordinates[time_index_efit].unit.unit != self.d.coordinates[time_index_data].unit.unit): + try: + coeff_efit_time=flap.tools.time_unit_translation(R_object.coordinates[time_index_efit].unit.unit) + except: + raise ValueError("Time unit translation cannot be made. Check the time unit of the object.") + try: + coeff_data_time=flap.tools.time_unit_translation(self.d.coordinates[time_index_data].unit.unit) + except: + raise ValueError("Time unit translation cannot be made. Check the time unit of the object.") + self.efit_data[setting]['Time'] *= coeff_efit_time/coeff_data_time + else: + raise ValueError(setting.capitalize()+' keywords are not set for the data objects.') + + #Interpolating EFIT data for the time vector of the data + if ((self.efit_data[setting]['Data'] != []) and (self.efit_data[setting]['Time'] != [])): + self.efit_data[setting]['Data resampled']=np.zeros([2,self.tdata.shape[0],self.efit_data[setting]['Data'].shape[2]]) + for index_xy in range(0,2): + for index_coordinate in range(0,self.efit_data[setting]['Data'].shape[2]): + self.efit_data[setting]['Data resampled'][index_xy,:,index_coordinate]=np.interp(self.tdata,self.efit_data[setting]['Time'], + self.efit_data[setting]['Data'][index_xy,:,index_coordinate]) + + if (self.efit_options['Plot flux']): + try: + flux_object=flap.get_data_object(self.efit_options['Flux XY'],exp_id=self.d.exp_id) + except: + raise ValueError('Flux XY data is not available. FLAP data object needs to be read first.') + if len(flux_object.data.shape) != 3: + raise ValueError('Flux XY data needs to be a 3D matrix (r,z,t), not necessarily in this order.') + if (flux_object.coordinates[0].unit.name != 'Time'): + raise ValueError('Time should be the first coordinate in the flux data object.') + self.efit_data['flux']['Data']=flux_object.data + self.efit_data['flux']['Time']=flux_object.coordinate('Time')[0][:,0,0] #TIME IS NOT ALWAYS THE FIRST COORDINATE, ELLIPSIS CHANGE SHOULD BE IMPLEMENTED + self.efit_data['flux']['X coord']=flux_object.coordinate(flux_object.coordinates[1].unit.name)[0] + self.efit_data['flux']['Y coord']=flux_object.coordinate(flux_object.coordinates[2].unit.name)[0] + + for index_coordinate in range(len(self.d.coordinates)): + if ((self.d.coordinates[index_coordinate].unit.name in self.axes) and + (self.d.coordinates[index_coordinate].unit.name != 'Time')): + coordinate_index=index_coordinate + #Spatial unit translation (mm vs m usually) + if (flux_object.data_unit.unit != self.d.coordinates[coordinate_index].unit.unit): + try: + coeff_efit_spatial=flap.tools.spatial_unit_translation(flux_object.data_unit.unit) + except: + raise ValueError("Time unit translation cannot be made. Check the time unit of the object.") + try: + coeff_data_spatial=flap.tools.spatial_unit_translation(self.d.coordinates[coordinate_index].unit.unit) + except: + raise ValueError("Spatial unit translation cannot be made. Check the time unit of the object.") + #print('Spatial unit translation factor: '+str(coeff_efit_spatial/coeff_data_spatial)) + self.efit_data['flux']['X coord'] *= coeff_efit_spatial/coeff_data_spatial + self.efit_data['flux']['Y coord'] *= coeff_efit_spatial/coeff_data_spatial + + #Time translation (usually ms vs s) + for index_time in range(len(self.d.coordinates)): + if (self.d.coordinates[index_time].unit.name == 'Time'): + time_index_data=index_time + + for index_time in range(len(flux_object.coordinates)): + if (flux_object.coordinates[index_time].unit.name == 'Time'): + time_index_efit=index_time + + if (flux_object.coordinates[time_index_efit].unit.unit != self.d.coordinates[time_index_data].unit.unit): + try: + coeff_efit_time=flap.tools.time_unit_translation(flux_object.coordinates[time_index_efit].unit.unit) + except: + raise ValueError("Time unit translation cannot be made. Check the time unit of the object.") + try: + coeff_data_time=flap.tools.time_unit_translation(self.d.coordinates[time_index_data].unit.unit) + except: + raise ValueError("Time unit translation cannot be made. Check the time unit of the object.") + self.efit_data['flux']['Time'] *= coeff_efit_time/coeff_data_time + + #Interpolating EFIT data for the time vector of the data + if ((self.efit_data['flux']['Data'] != []) and (self.efit_data['flux']['Time'] != [])): + self.efit_data['flux']['Data resampled']=np.zeros([self.tdata.shape[0], + self.efit_data['flux']['Data'].shape[1], + self.efit_data['flux']['Data'].shape[2]]) + self.efit_data['flux']['X coord resampled']=np.zeros([self.tdata.shape[0], + self.efit_data['flux']['X coord'].shape[1], + self.efit_data['flux']['X coord'].shape[2]]) + self.efit_data['flux']['Y coord resampled']=np.zeros([self.tdata.shape[0], + self.efit_data['flux']['Y coord'].shape[1], + self.efit_data['flux']['Y coord'].shape[2]]) + + for index_x in range(0,self.efit_data['flux']['Data'].shape[1]): + for index_y in range(0,self.efit_data['flux']['Data'].shape[2]): + self.efit_data['flux']['Data resampled'][:,index_x,index_y]=np.interp(self.tdata,self.efit_data['flux']['Time'], + self.efit_data['flux']['Data'][:,index_x,index_y]) + self.efit_data['flux']['X coord resampled'][:,index_x,index_y]=np.interp(self.tdata,self.efit_data['flux']['Time'], + self.efit_data['flux']['X coord'][:,index_x,index_y]) + self.efit_data['flux']['Y coord resampled'][:,index_x,index_y]=np.interp(self.tdata,self.efit_data['flux']['Time'], + self.efit_data['flux']['Y coord'][:,index_x,index_y]) + + + if (self.xrange is not None): + plt.xlim(self.xrange[0]*self.axes_unit_conversion[0],self.xrange[1]*self.axes_unit_conversion[0]) + + if (self.yrange is not None): + plt.ylim(self.yrange[0]*self.axes_unit_conversion[1],self.yrange[1]*self.axes_unit_conversion[1]) + + if self.axes_unit_conversion[0] == 1.: + plt.xlabel(self.ax_list[0].title(language=self.language)) + else: + plt.xlabel(self.ax_list[0].title(language=self.language, new_unit=self.options['Plot units'][self.axes[0]])) + + if self.axes_unit_conversion[1] == 1.: + plt.ylabel(self.ax_list[1].title(language=self.language)) + else: + plt.ylabel(self.ax_list[1].title(language=self.language, new_unit=self.options['Plot units'][self.axes[1]])) + + if (self.options['Log x']): + plt.xscale('log') + if (self.options['Log y']): + plt.yscale('log') + + if self.options['Plot units'] is not None: + if self.axes[2] in self.options['Plot units']: + time_unit=self.options['Plot units'][self.axes[2]] + time_coeff=self.axes_unit_conversion[2] + else: + time_unit=self.coord_t.unit.unit + time_coeff=1. + else: + time_unit=self.coord_t.unit.unit + time_coeff=1. + title = str(self.d.exp_id)+' @ '+self.coord_t.unit.name+'='+"{:10.5f}".format(self.tdata[0]*time_coeff)+\ + ' ['+time_unit+']' + + plt.title(title) + + plt.show(block=False) + self.anim = animation.FuncAnimation(self.fig, self.animate_plot, + len(self.tdata), + interval=self.speed,blit=False) + + def animate_plot(self, it): + + time_index = [slice(0,dim) for dim in self.d.data.shape] + time_index[self.coord_t.dimension_list[0]] = it + time_index = tuple(time_index) + + self.time_slider.eventson = False + self.time_slider.set_val(self.tdata[it]*self.axes_unit_conversion[2]) + self.time_slider.eventson = True + + self.current_frame = it + + plot_opt = copy.deepcopy(self.plot_options[0]) + self.ax_act.clear() + + if (self.image_like): + try: +# if (self.coord_x.dimension_list[0] == 0): + if (self.coord_x.dimension_list[0] < self.coord_y.dimension_list[0]): + im = np.clip(np.transpose(self.d.data[time_index]),self.vmin,self.vmax) + else: + im = np.clip(self.d.data[time_index],self.vmin,self.vmax) + plt.imshow(im,extent=self.xdata_range + self.ydata_range,norm=self.norm, + cmap=self.cmap_obj,vmin=self.vmin, + aspect=self.options['Aspect ratio'], + interpolation=self.options['Interpolation'], + vmax=self.vmax,origin='lower',**plot_opt) + del im + except Exception as e: + raise e + else: + if (len(self.xdata.shape) == 3 and len(self.ydata.shape) == 3): + xgrid, ygrid = flap.tools.grid_to_box(self.xdata[time_index,:,:]*self.axes_unit_conversion[0],self.ydata[time_index,:,:]*self.axes_unit_conversion[1]) #Same issue, time is not necessarily the first flap.coordinate. + else: + xgrid, ygrid = flap.tools.grid_to_box(self.xdata*self.axes_unit_conversion[0],self.ydata*self.axes_unit_conversion[1]) + im = np.clip(np.transpose(self.d.data[time_index]),self.vmin,self.vmax) + try: + plt.pcolormesh(xgrid,ygrid,im,norm=self.norm,cmap=self.cmap,vmin=self.vmin, + vmax=self.vmax,**plot_opt) + except Exception as e: + raise e + del im + if ('EFIT options' in self.options and self.options['EFIT options'] is not None): + for setting in ['limiter','separatrix']: + if (self.efit_options['Plot '+setting]): + self.ax_act.set_autoscale_on(False) + im = plt.plot(self.efit_data[setting]['Data resampled'][0,it,:], + self.efit_data[setting]['Data resampled'][1,it,:], + color=self.efit_options[setting.capitalize()+' color']) + + if (self.efit_options['Plot flux']): + self.ax_act.set_autoscale_on(False) + #im = plt.contour(self.efit_data['flux']['X coord'][it,:,:], + # self.efit_data['flux']['Y coord'][it,:,:], + # self.efit_data['flux']['Data resampled'][it,:,:], + # levels=self.efit_options['Flux nlevel']) + + im = plt.contour(self.efit_data['flux']['X coord resampled'][it,:,:].transpose(), + self.efit_data['flux']['Y coord resampled'][it,:,:].transpose(), + self.efit_data['flux']['Data resampled'][it,:,:], + levels=self.efit_options['Flux nlevel']) + + if (self.xrange is not None): + self.ax_act.set_xlim(self.xrange[0],self.xrange[1]) + if (self.yrange is not None): + self.ax_act.set_ylim(self.yrange[0],self.yrange[1]) + + if self.axes_unit_conversion[0] == 1.: + plt.xlabel(self.ax_list[0].title(language=self.language)) + else: + plt.xlabel(self.ax_list[0].title(language=self.language, new_unit=self.options['Plot units'][self.axes[0]])) + + if self.axes_unit_conversion[1] == 1.: + plt.ylabel(self.ax_list[1].title(language=self.language)) + else: + plt.ylabel(self.ax_list[1].title(language=self.language, new_unit=self.options['Plot units'][self.axes[1]])) + + if self.options['Plot units'] is not None: + if self.axes[2] in self.options['Plot units']: + time_unit=self.options['Plot units'][self.axes[2]] + time_coeff=self.axes_unit_conversion[2] + else: + time_unit=self.coord_t.unit.unit + time_coeff=1. + + title = str(self.d.exp_id)+' @ '+self.coord_t.unit.name+'='+"{:10.5f}".format(self.tdata[it]*time_coeff)+\ + ' ['+time_unit+']' + + self.ax_act.set_title(title) + + def _reset_animation(self, event): + self.anim.event_source.stop() + self.speed = 40. + self.anim = animation.FuncAnimation(plt.figure(self.plot_id.figure), self.animate_plot, + len(self.tdata),interval=self.speed,blit=False) + self.anim.event_source.start() + self.pause = False + + def _pause_animation(self, event): + if self.pause: + self.anim.event_source.start() + self.pause = False + self.pause_button.label.set_text("Pause") + else: + self.anim.event_source.stop() + self.pause = True + self.pause_button.label.set_text("Start") + + def _set_animation(self, time): + self.anim.event_source.stop() + frame=(np.abs(self.tdata*self.axes_unit_conversion[2]-time)).argmin() + self.anim = animation.FuncAnimation(plt.figure(self.plot_id.figure), self.animate_plot, + frames=np.arange(frame,len(self.tdata)-1), + interval=self.speed,blit=False) + self.anim.event_source.start() + self.pause = False + + def _slow_animation(self, event): + self.anim.event_source.stop() + self.speed=self.speed/0.8 + self.anim = animation.FuncAnimation(plt.figure(self.plot_id.figure), self.animate_plot, + frames=np.arange(self.current_frame,len(self.tdata)-1), + interval=self.speed,blit=False) + self.speed_button.label.set_text(str(int(1000./(self.speed*0.8)))+'fps') + self.slow_button.label.set_text(str(int(1000./(self.speed/0.8)))+'fps') + self.anim.event_source.start() + self.pause = False + + def _speed_animation(self, event): + self.anim.event_source.stop() + self.speed=self.speed*0.8 + self.anim = animation.FuncAnimation(plt.figure(self.plot_id.figure), self.animate_plot, + frames=np.arange(self.current_frame,len(self.tdata)-1), + interval=self.speed,blit=False) + self.speed_button.label.set_text(str(int(1000./(self.speed*0.8)))+'fps') + self.slow_button.label.set_text(str(int(1000./(self.speed/0.8)))+'fps') + self.anim.event_source.start() + self.pause = False + +class PlotID: + def __init__(self): + # The figure number where the plto resides + self.figure = None + # The subplot containing the whole plot + self.base_subplot = None + # The plot type string + self.plot_type = None + # Subtype is dependent on the plot type. It marks various versions, e.g. real-complex + self.plot_subtype = None + # Number of plot calls which generated this + self.number_of_plots = 0 + # The axes list. Each element is a flap.Unit and describes one axis of the plot. + self.axes = None + # The description of the axis data. This is a list of self.number_of_plots lists. Each inner list + # is a list of PlotDataDescriptions. + self.plot_data = [] + # The list of Axes which can be used for plotting into the individual plots. If there is only a single + # plot self.base_subplot is the same as plt_axis_list[0] + self.plt_axis_list = None + # These are a list of the options to plot + self.options = [] + + def clear(self): + """ Clears the parameters of the plot, but does not clear the + base_subplot and figure + """ + self.plot_type = None + self.plot_subtype = None + self.number_of_plots = 0 + self.axes = None + self.plot_data = [] + self.plt_axis_list = None + self.options = [] + + +# def __del__(self): +# if (self.plt_axis_list is not None): +# for ax in self.plt_axis_list: +# ax.remove() + + def check_axes(self,d, axes, clear=True, default_axes=None, force=False): + """ Checks whether the required plot axes are correct, present and compatible with the self PlotID. + In case of problems raises ValueError. + INPUT: + d: data object + axes: List of the required axes or None as input to plot() + clear: (bool) If True the plot will be cleared, therefore the axes in the PlotID are irrelevant. + default_axes: The default axes desired for this type of plot. (strings) + force: (bool) Force accept incompatibe axes + Return value: + pdd_list, ax_list + pdd_list: list of PlotDataDescription objects which can be used to generate the plot. + ax_list: axis list which can be put into axes in self. + """ + if (axes is not None): + if (type(axes) is not list): + _axes = [axes] + else: + _axes = axes + else: + _axes = [] + + # Converting to PDD and axes list + try: + pdd_list,ax_list = axes_to_pdd_list(d,_axes) + except ValueError as e: + raise e + # Adding default axes if not all are specified + if (len(_axes) < len(default_axes)): + # Determining default axes for the ones which are not in _axes: + # either the ones in the plot or defaults + if (not clear and (self.axes is not None)): + # Using the axis in the plot + plot_axes = [''] * (len(default_axes) - len(_axes)) + for i,ax in enumerate(self.axes[len(_axes):len(default_axes)]): + # If axis in plot has no name then using default axis else the plot axis + if (ax.name == ''): + plot_axes[i] = default_axes[i] + else: + if (ax.name == d.data_unit.name): + plot_axes[i] = '__Data__' + else: + plot_axes[i] = ax.name + try: + pdd_def,plot_def_axes = axes_to_pdd_list(d,plot_axes) + except ValueError as e: + for axname in plot_axes: + if (axname in str(e)): + raise ValueError("Plot coordinate '"+axname+"' not found neither as coordinate nor data name. Must specifiy axes and use option={'Force axes':True} to overplot.") + else: + # If overplotting or no plot is available then using the default + try: + pdd_def, plot_def_axes = axes_to_pdd_list(d,default_axes[len(_axes):]) + except ValueError as e: + raise e + ax_list += plot_def_axes + pdd_list += pdd_def + + + ax_out_list = [] + # Checking whether the units of this plot are consistent with the present axes + if (not clear and (self.axes is not None)): + for ax_in,ax_plot in zip(ax_list, self.axes): + # If none of the plot and the new axis has name and ot unit, the new axis will not have it either +# if ((ax_plot.name == '') or (ax_plot.unit == '') or +# (ax_in.name == '') or (ax_in.unit == '')): +# ax_out_list.append(Unit()) + if ((ax_in.name != ax_plot.name) or (ax_in.unit != ax_plot.unit)): + if (force): + u = flap.coordinate.Unit() + if (ax_in.name == ax_plot.name): + u.name = ax_in.name + elif ((ax_in.name is None) or (ax_in.name == '')): + u.name = ax_plot.name + elif ((ax_plot.name is None) or (ax_plot.name == '')): + u.name = ax_in.name + if (ax_in.unit == ax_plot.unit): + u.unit = ax_in.unit + elif ((ax_in.unit is None) or (ax_in.unit == '')): + u.unit = ax_plot.unit + elif ((ax_plot.unit is None) or (ax_plot.unit == '')): + u.unit = ax_in.unit + ax_out_list.append(u) + continue + raise ValueError("Axis '"+ax_in.name+"' is incompatible with plot. Use option={'Force axes':True}") + else: + ax_out_list.append(ax_in) + else: + ax_out_list = ax_list + return pdd_list, ax_out_list + +def set_plot_id(plot_id): + """ + Set the current plot. + """ + global act_plot_id, gca_invalid + + if ((plot_id is not None) and (type(plot_id) is not PlotID)): + raise ValueError("flap.set_plot_id should receive a valid flap PlotID or None") + act_plot_id = plot_id + if (act_plot_id is None): + gca_invalid = False + else: + if (plot_id.plt_axis_list is None): + gca_invalid = False + else: + if (plt.gca() != plot_id.plt_axis_list[-1]): + gca_invalid = True + else: + gca_invalid = False + +def get_plot_id(): + """ + Return the current PlotID or None if no act plot. + """ + global act_plot_id, gca_invalid + try: + return act_plot_id + except NameError: + gca_invalid = False + return None + +def __get_gca_invalid(): + """ This returns the global gca_invalid flag. + This flag is set invalid when set_plot changes the default plot and this way + the Matplotlib current axis (get_gca()) becomes invalid for this plot. + """ + try: + gca_invalid + except NameError: + gca_invalid = False + return gca_invalid + + +def sample_for_plot(x,y,x_error,y_error,n_points): + """ + Resamples the y(x) function to np points for plotting. + This is useful for plotting large arrays in a way that short outlier pulses + are still indicated. + The original function is divided into np equal size blocks in x and in each block + the minimum and maximum is determined. The output will contain 2*np number + or points. Each consecutive point pair contains the minimum and maximum in + a block, the time is the centre time of the block. + + x: Input x array. + y: input y array. + np: Desired number of blocks. This would be a number larger than the number + of pixels in the plot in the horizontal direction. + + Return values: + x_out, y_out + If the box length is less than 5 the original data will be returned. + """ + + binlen = int(len(x) / n_points) + if (binlen < 5): + return x,y,x_error,y_error + nx = int(len(x) / binlen) + _y = y[0:nx * binlen] + _y = np.reshape(_y, (nx,binlen)) + y_bin = np.empty(nx * 4, dtype=type(y)) + ind = np.arange(nx) * 4 + y_bin[ind + 1] = np.amin(_y, 1) + y_bin[ind + 2] = np.amax(_y, 1) + y_bin[ind] = np.mean(_y, 1) + y_bin[ind + 3] = y_bin[ind] + x_bin = np.empty(nx * 4, dtype=type(x)) + ind1 = np.arange(nx) * binlen + int(binlen / 2) + x_bin[ind] = x[ind1] + x_bin[ind + 1] = x[ind1] + x_bin[ind + 2] = x[ind1] + x_bin[ind + 3] = x[ind1] + + if ((x_error is not None) or (y_error is not None)): + ind = np.arange(nx,dtype=np.int32) * binlen + binlen // 2 + if (x_error is not None): + if (x_error.ndim == 1): + x_error_bin = np.empty(x_bin.size,dtype=x_error.dtype) + for i in range(4): + x_error_bin[slice(i, nx * 4 + i, 4)] = x_error[ind] + else: + x_error_bin = np.empty((2,x_bin.size),dtype=x_error.dtype) + for i in range(4): + x_error_bin[0,slice(i, nx * 4 + i, 4)] = x_error[0,ind] + x_error_bin[1,slice(i, nx * 4 + i, 4)] = x_error[1,ind] + else: + x_error_bin = None + + if (y_error is not None): + if (y_error.ndim == 1): + y_error_bin = np.empty(y_bin.size,dtype=y_error.dtype) + for i in range(4): + y_error_bin[slice(i, nx * 4 + i, 4)] = y_error[ind] + else: + y_error_bin = np.empty((2,x_bin.size),dtype=y_error.dtype) + for i in range(4): + y_error_bin[0,slice(i, nx * 4 + i, 4)] = y_error[0,ind] + y_error_bin[1,slice(i, nx * 4 + i, 4)] = y_error[1,ind] + else: + y_error_bin = None + + return x_bin, y_bin, x_error_bin, y_error_bin + +def _plot(data_object, + axes=None, + slicing=None, + summing=None, + slicing_options=None, + options=None, + plot_type=None, + plot_options={}, + plot_id=None): + """ + Plot a data object. + axes: A list of coordinate names (strings). They should be one of the coordinate + names in the data object or 'Data' + They describe the axes of the plot. + If the number of axes is less than the number required for the plot, '__Data__' will be added. + If no axes are given default will be used depending on the plot type. E.g. for + x-y plot the default first axis is the firs coordinate, the second axis is '__Data__' + slicing, summing: arguments for slice_data. Slicing will be applied before plotting. + slicing_options: options for slicing. See slice_data() + plot_type: The plot type (string). Can be abbreviated. + 'xy': Simple 1D plot. Default axes are: first coordinate, Data. For complex signals this + produces two plots, see option "Complex mode'. + 'multi xy': In case of 2D data plots 1D curves with vertical shift + Default x axis is first coordinate, y axis is Data + The signals are named in the label with the 'Signal name' coordinate or the one + named in options['Signal name'] + 'image': Plots a 2D data matrix as an image. Options: Colormap, Data range, ... + 'contour': Contour plot + plot_options: Dictionary or list of dictionaries. Will be passed over to the plot call. For plots with multiple subplots this can be + a list of dictionaries, each for one subplot. + plot_id: A PlotID object is the plot should go into an existing plot. + options: + 'Error' True: Plot all error bars (default: True) + False: Do not plot errors + number > 0: Plot this many error bars in plot + 'Y separation' Vertical separation of curves in multi xy plot. For linear scale this will + be added to consecutive cureves. For Log scale consecutive curves will be + multiplied by this. + 'Log x' : Logscale X axis + 'Log y' : Logscale Y axis + 'All points' True or False + Default is False. If True will plot all points otherwise will plot only a reduced + number of points (see maxpoints). Each plotted point will be the mean of data + in a box and a vertical bar will indicate the value range in each box. + 'Maxpoints': The maximum number of data points plotted. Above this only this many points will + be plotted if "All points" is False. + 'Complex mode': 'Amp-phase': Plot amplitude and phase + 'Real-imag': Plot real and imaginary part + 'X range','Y range': Axes ranges. (List of two numbers.) + 'Z range': Range of the vertical axis. (List of two numbers.) + 'Colormap': Cmap name for image and contour plots. + 'Levels': Number of contour levels or array of levels. + 'Aspect ratio': 'equal', 'auto' or float. (See imshow) + 'Waittime' : Time to wait [Seconds] between two images in anim-... type plots + 'Video file': Name of output video file for anim-... plots + 'Video framerate': Frame rate for output video. + 'Video format': Format of the video. Valid: 'avi' + 'Clear': Boolean. If True don't use the existing plots, generate new. (No overplotting.) + 'Force axes': Force overplotting even if axes are incpomatible + 'Colorbar': Boolelan. Switch on/off colorbar + 'Nan color': The color to use in image data plotting for np.nan (not-a-number) values + 'Interpolation': Interpolation method for image plot. + 'Language': Language of certain standard elements in the plot. ('EN', 'HU') + 'EFIT options': Dictionary of EFIT plotting options: + 'Plot separatrix': Set to plot the separatrix onto the video + 'Separatrix X': Name of the flap.DataObject for the separatrix X data (usually R) + 'Separatrix Y': Name of the flap.DataObject for the separatrix Y data (usually z) + 'Separatrix XY': Name of the 2D flap.DataObject for the separatrix XY data (usually Rz) + 'Separatrix color': Color of the separatrix for the plot + 'Plot limiter': Set to plot the limiter onto the video + 'Limiter X': Name of the flap.DataObject for the limiter X data (usually R) + 'Limiter Y': Name of the flap.DataObject for the limiter Y data (usually z) + 'Limiter XY': Name of the 2D flap.DataObject for the limiter XY data (usually Rz) + 'Limiter color': Color of the limiter for the plot + 'Plot flux surfaces': Name of the 2D flap.DataObject for the flux surfaces + (should have the same coordinate names as the plot) + 'nlevels': Number of contour lines for the flux surface plotting + 'Prevent saturation': Prevents saturation of the video signal when it exceeds zrange[1] + It uses data modulo zrange[1] to overcome the saturation. (works for animation) + + """ + + default_options = {'All points': False, 'Error':True, 'Y separation': None, + 'Log x': False, 'Log y': False, 'Log z': False, 'maxpoints':4000, 'Complex mode':'Amp-phase', + 'X range':None, 'Y range': None, 'Z range': None,'Aspect ratio':'auto', + 'Clear':False,'Force axes':False,'Language':'EN','Maxpoints': 4000, + 'Levels': 10, 'Colormap':None, 'Waittime':1,'Colorbar':True,'Nan color':None, + 'Interpolation':'bilinear','Video file':None, 'Video framerate': 20,'Video format':'avi', + 'EFIT options':None, 'Prevent saturation':False, 'Plot units':None, + } + _options = flap.config.merge_options(default_options, options, data_source=data_object.data_source, section='Plot') + + if (plot_options is None): + _plot_options = {} + else: + _plot_options = plot_options + if (type(_plot_options) is not list): + _plot_options = [_plot_options] + + + if (type(_options['Clear']) is not bool): + raise TypeError("Invalid type for option Clear. Should be boolean.") + if (type(_options['Force axes']) is not bool): + raise TypeError("Invalid type for option 'Force axes'. Should be boolean.") + + if ((slicing is not None) or (summing is not None)): + d = data_object.slice_data(slicing=slicing, summing=summing, options=slicing_options) + else: + d = data_object + + # Determining a PlotID: + # argument, actual or a new one + if (type(plot_id) is PlotID): + _plot_id = plot_id + else: + _plot_id = get_plot_id() + if (_plot_id is None): + # If there is no actual plot we create a new one + _plot_id = PlotID() + _plot_id.figure = plt.gcf().number + _plot_id.base_subplot = plt.gca() + if (_plot_id.plt_axis_list is not None): + if ((_plot_id.plt_axis_list[-1] != plt.gca()) or (_plot_id.figure != plt.gcf().number)): + # If the actual subplot is not the one in the plot ID then either the subplot was + # changed to a new one or the plot_ID changed with set_plot. + if (not __get_gca_invalid()): + # This means the plot ID was not changed, the actual plot or axis was changed. + # Therefore we need to use the actual values + _plot_id = PlotID() + _plot_id.figure = plt.gcf().number + _plot_id.base_subplot = plt.gca() + plt.cla() + if (_options['Clear'] ): + _plot_id = PlotID() +# _plot_id.clear() + if (_plot_id.figure is not None): + plt.figure(_plot_id.figure) + else: + _plot_id.figure = plt.gcf().number + if (_plot_id.base_subplot is not None): + plt.subplot(_plot_id.base_subplot) + else: + _plot_id.base_subplot = plt.gca() + plt.cla() + + # Setting plot type + known_plot_types = ['xy','scatter','multi xy', 'image', 'anim-image','contour','anim-contour','animation'] + if (plot_type is None): + if (len(d.shape) == 1): + _plot_type = 'xy' + elif (len(d.shape) == 2): + _plot_type = 'multi xy' + elif (len(d.shape) == 3): + _plot_type = 'anim-image' + else: + raise ValueError("No default plot type for this kind of data, set plot_type.") + else: + try: + _plot_type = flap.tools.find_str_match(plot_type,known_plot_types) + except TypeError: + raise TypeError("Invalid type for plot_type. String is expected.") + except ValueError: + raise ValueError("Unknown plot type or too short abbreviation") + + # Processing some options + if ((_plot_type == 'xy') or (_plot_type == 'multi xy')): + all_points = _options['All points'] + if (type(all_points) is not bool): + raise TypeError("Option 'All points' should be boolean.") + else: + all_points = True + plot_error = _options['Error'] + if (type(plot_error) is not bool): + try: + if (int(plot_error) <= 0): + raise ValueError("Invalid number of error bars in plot (option: Error).") + errorbars = int(plot_error) + plot_error = True + except: + raise ValueError("Invalid 'Error' option.") + else: + errorbars = -1 + # The maximum number of points expected in the horizontal dimension of a plot + try: + maxpoints = int(_options['Maxpoints']) + except ValueError: + raise ValueError("Invalid maxpoints setting.") + + try: + compt = flap.tools.find_str_match(_options['Complex mode'], ['Amp-phase','Real-imag']) + except: + raise ValueError("Invalid 'Complex mode' option:" +_options['Complex mode']) + if (compt == 'Amp-phase'): + comptype = 0 + elif (compt == 'Real-imag'): + comptype = 1 + if (_plot_id.number_of_plots > 0): + if (_plot_id.options[-1]['Complex mode'] != _options['Complex mode']): + raise ValueError("Different complex plot mode in overplot.") + + language = _options['Language'] + + if (_options['Video file'] is not None): + if (_options['Video format'] == 'avi'): + video_codec_code = 'XVID' + else: + raise ValueError("Cannot write video in format '"+_options['Video format']+"'.") + + + # X range and Z range is processed here, but Y range not as it might have multiple entries for some plots + xrange = _options['X range'] + if (xrange is not None): + if ((type(xrange) is not list) or (len(xrange) != 2)): + raise ValueError("Invalid X range setting.") + zrange = _options['Z range'] + if (zrange is not None): + if ((type(zrange) is not list) or (len(zrange) != 2)): + raise ValueError("Invalid Z range setting.") + + cmap = _options['Colormap'] + if ((cmap is not None) and (type(cmap) is not str)): + raise ValueError("Colormap should be a string.") + + contour_levels = _options['Levels'] + # Here _plot_id is a valid (maybe empty) PlotID + + if ((_plot_type == 'xy') or (_plot_type == 'scatter')): + # 1D plots: xy, scatter and complex versions + # Checking whether oveplotting into the same plot type + if ((d.data is not None) and (d.data.dtype.kind == 'c')): + subtype = 1 + else: + subtype = 0 + if (not _options['Clear']): + if ((_plot_id.plot_type is not None) and ((_plot_id.plot_type != 'xy') and (_plot_id.plot_type != 'scatter')) + or (_plot_id.plot_subtype is not None) and (_plot_id.plot_subtype != subtype)): + raise ValueError("Overplotting into different plot type. Use option={'Clear':True} to erase first.") + # Processing axes + default_axes = [d.coordinates[0].unit.name, '__Data__'] + try: + pdd_list, ax_list = _plot_id.check_axes(d, + axes, + clear=_options['Clear'], + default_axes=default_axes, + force=_options['Force axes']) + except ValueError as e: + raise e + # Preparing data + plotdata = [0]*2 + plotdata_low = [0]*2 #UNUSED + plotdata_high = [0]*2 #UNUSED + ploterror = [0]*2 + for ax_ind in range(2): + if (pdd_list[ax_ind].data_type == PddType.Data): + if (len(pdd_list[ax_ind].data_object.shape) > 1): + raise ValueError("xy plot is applicable only to 1D data. Use slicing.") + plotdata[ax_ind] = pdd_list[ax_ind].data_object.data.flatten() + if (plot_error): + ploterror[ax_ind] = pdd_list[ax_ind].data_object._plot_error_ranges() + else: + ploterror[ax_ind] = None + elif (pdd_list[ax_ind].data_type == PddType.Coordinate): + if (not pdd_list[ax_ind].value.isnumeric()): + raise ValueError("Coordinate is not of numeric type, cannot plot.") + pdata, pdata_low, pdata_high = \ + pdd_list[ax_ind].value.data(data_shape=pdd_list[ax_ind].data_object.shape) + plotdata[ax_ind] = pdata.flatten() + if (pdata_low is not None): + pdata_low = pdata_low.flatten() + if (pdata_high is not None): + pdata_high = pdata_high.flatten() + if (plot_error): + ploterror[ax_ind] = pdd_list[ax_ind].data_object._plot_coord_ranges(pdd_list[ax_ind].value, + pdata, + pdata_low, + pdata_high + ) + else: + ploterror[ax_ind] = None + elif (pdd_list[ax_ind].data_type == PddType.Constant): + plotdata[ax_ind] = pdd_list[ax_ind].value + ploterror[ax_ind] = None + else: + raise RuntimeError("Internal error, invalid PlotDataDescription.") + if (_plot_id.number_of_plots != 0): + _options['Log x'] = _plot_id.options[-1]['Log x'] + _options['Log y'] = _plot_id.options[-1]['Log y'] + + + xdata = plotdata[0] + xerror = ploterror[0] + + if (subtype == 0): + yrange = _options['Y range'] + if (yrange is not None): + if ((type(yrange) is not list) or (len(yrange) != 2)): + raise ValueError("Invalid Y range setting.") + + ax = _plot_id.base_subplot + _plot_id.plt_axis_list = [ax] + + ydata = plotdata[1] + yerror = ploterror[1] + # Checking for constants, converting to numpy array + if (np.isscalar(ydata)): + if (np.isscalar(xdata)): + ydata = np.full(1, ydata) + else: + ydata = np.full(xdata.size, ydata) + yerror = None + if (np.isscalar(xdata)): + if (np.isscalar(ydata)): + xdata = np.full(1, xdata) + else: + xdata = np.full(ydata.size, xdata) + xerror = None + + if (all_points is True): + x = xdata + y = ydata + xerr = xerror + yerr = yerror + else: + x,y,xerr,yerr = sample_for_plot(xdata,ydata,xerror,yerror,maxpoints) + if (errorbars < 0): + errorevery = 1 + else: + errorevery = int(round(len(x)/errorbars)) + if (errorevery == 0): + errorevery = 1 + + _plot_opt = _plot_options[0] + if (type(_plot_opt) is not dict): + raise ValueError("Plot options should be a dictionary or list of dictionaries.") + + if (_plot_type == 'xy'): + if (plot_error): + ax.errorbar(x,y,xerr=xerr,yerr=yerr,errorevery=errorevery,**_plot_opt) + else: + ax.plot(x,y,**_plot_opt) + else: + if (plot_error): + ax.errorbar(x,y,xerr=xerr,yerr=yerr,errorevery=errorevery,fmt='o',**_plot_opt) + else: + ax.scatter(x,y,**_plot_opt) + + ax.set_xlabel(ax_list[0].title(language=language)) + ax.set_ylabel(ax_list[1].title(language=language)) + if (_options['Log x']): + ax.set_xscale('log') + if (_options['Log y']): + ax.set_yscale('log') + if (xrange is not None): + ax.set_xlim(xrange[0],xrange[1]) + if (yrange is not None): + ax.set_ylim(yrange[0],yrange[1]) + + title = ax.get_title() + if (title is None): + title = '' + if (title[-3:] != '...'): + newtitle = '' + if (d.exp_id is not None): + newtitle += str(d.exp_id) + ' ' + if (d.data_title is not None): + newtitle += d.data_title + if (len(newtitle) != 0): + if (len(title + newtitle) < 40): + if (title != ''): + title += ','+newtitle + else: + title = newtitle + else: + title += ',...' + ax.set_title(title) + + _plot_id.plot_subtype = 0 # real xy plot + # End of real xy and scatter plot + else: + # Complex xy plot + yrange = _options['Y range'] + if (yrange is not None): + if ((type(yrange) is not list) or (len(yrange) != 2)): + raise ValueError("Invalid Y range setting.") + if ((type(yrange[0]) is list) and (type(yrange[1]) is list) and + ((len(yrange[0]) == 2)) and (len(yrange[1]) == 2)): + pass + else: + try: + yrange = [float(yrange[0]), float(yrange[1])] + except ValueError: + raise ValueError("Invalid Y range setting. For complex xy plot either a list or list of two lists can be used.") + yrange = [yrange,yrange] + + # Complex xy and scatter plot + # Creating two sublots if this is a new plot + if (_plot_id.number_of_plots == 0): + gs = gridspec.GridSpecFromSubplotSpec(2, 1, subplot_spec=_plot_id.base_subplot) + _plot_id.plt_axis_list = [] + _plot_id.plt_axis_list.append(plt.subplot(gs[0,0])) + _plot_id.plt_axis_list.append(plt.subplot(gs[1,0],sharex=_plot_id.plt_axis_list[0])) + # Taking the mean of the complex errors + if (ploterror[1] is not None): + if (type(ploterror[1]) is list): + yerror_abs = (np.abs(ploterror[1][0]) + np.abs(ploterror[1][1])) / 2 + else: + yerror_abs = np.abs(ploterror[1]) + else: + yerror_abs = None + for i_comp in range(2): + ax = _plot_id.plt_axis_list[i_comp] + if (comptype == 0): + # amp-phase + if (i_comp == 0): + ydata = np.abs(plotdata[1]) + yerror = yerror_abs + else: + ydata_abs = np.abs(plotdata[1]) + ydata = np.angle(plotdata[1]) + if (yerror_abs is not None): + yerror = np.empty(ydata.shape,dtype=float) + ind = np.nonzero(ydata_abs <= yerror_abs)[0] + if (ind.size > 0): + yerror[ind] = math.pi + ind = np.nonzero(ydata_abs > yerror_abs)[0] + if (ind.size > 0): + yerror[ind] = np.arctan2(yerror_abs[ind],ydata_abs[ind]) + else: + yerror = None + else: + # real-imag + if (i_comp == 0): + ydata = np.real(plotdata[1]) + yerror = yerror_abs + else: + ydata = np.imag(plotdata[1]) + yerror = yerror_abs + + # Checking for constants, converting to numpy array + if (np.isscalar(ydata)): + if (np.isscalar(xdata)): + ydata = np.full(1, ydata) + else: + ydata = np.full(xdata.size, ydata) + if (plot_error): + yerror = d._plot_coord_ranges(c, ydata, ydata_low, ydata_high) #THESE ARE UNDEFINED + else: + yerror = None + if (np.isscalar(xdata)): + if (np.isscalar(ydata)): + xdata = np.full(1, xdata) + else: + xdata = np.full(ydata.size, xdata) + if (plot_error): + xerror = d._plot_coord_ranges(c, xdata, xdata_low, xdata_high) #THESE ARE UNDEFINED + else: + xerror = None + + if (all_points is True): + x = xdata + y = ydata + xerr = xerror + yerr = yerror + else: + x,y,xerr,yerr = sample_for_plot(xdata,ydata,xerror,yerror,maxpoints) + if (errorbars < 0): + errorevery = 1 + else: + errorevery = int(round(len(x)/errorbars)) + if (errorevery == 0): + errorevery = 1 + + _plot_opt = _plot_options[0] + if (type(_plot_opt) is list): + _plot_opt[i_comp] + if (type(_plot_opt) is not dict): + raise ValueError("Plot options should be a dictionary or list of dictionaries.") + + if (_plot_type == 'xy'): + if (plot_error): + ax.errorbar(x,y,xerr=xerr,yerr=yerr,errorevery=errorevery,**_plot_opt) + else: + ax.plot(x,y,**_plot_opt) + else: + if (plot_error): + ax.errorbar(x,y,xerr=xerr,yerr=yerr,errorevery=errorevery,fmt='o',**_plot_opt) + else: + ax.scatter(x,y,**_plot_opt) + + # Setting axis labels + ax.set_xlabel(ax_list[0].title(language=language)) + ax.set_ylabel(ax_list[1].title(language=language,complex_txt=[comptype,i_comp])) + + if (_options['Log x']): + ax.set_xscale('log') + if (_options['Log y']): + ax.set_yscale('log') + if (xrange is not None): + ax.set_xlim(xrange[0],xrange[1]) + if (yrange is not None): + ax.set_ylim(yrange[i_comp][0],yrange[i_comp][1]) + title = ax.get_title() + if (title is None): + title = '' + if (title[-3:] != '...'): + newtitle = '' + if (d.exp_id is not None): + newtitle += str(d.exp_id) + ' ' + if (d.data_title is not None): + newtitle += d.data_title + if (len(newtitle) != 0): + if (len(title + newtitle) < 40): + if (title != ''): + title += ','+newtitle + else: + title = newtitle + else: + title += ',...' + ax.set_title(title) + + _plot_id.plot_subtype = 1 + # End of complex xy and scatter plot + + elif (_plot_type == 'multi xy'): + if (len(d.shape) > 2): + raise TypeError("multi x-y plot is applicable to 2D data only. Use slicing.") + if (len(d.shape) != 2): + raise TypeError("multi x-y plot is applicable to 2D data only.") + if (d.data.dtype.kind == 'c'): + raise TypeError("multi x-y plot is applicable only to real data.") + if ((axes is not None) and (type(axes) is list) and (len(axes) > 1)): + raise ValueError("For multi xy plot only one axis can be given.") + + yrange = _options['Y range'] + if (yrange is not None): + if ((type(yrange) is not list) or (len(yrange) != 2)): + raise ValueError("Invalid Y range setting.") + + # Processing axes + default_axes = [d.coordinates[0].unit.name, '__Data__'] + try: + pdd_list, ax_list = _plot_id.check_axes(d, + axes, + clear=_options['Clear'], + default_axes=default_axes, + force=_options['Force axes']) + except ValueError as e: + raise e + + if (pdd_list[0].data_type == PddType.Coordinate): + coord = pdd_list[0].value + if (len(coord.dimension_list) != 1): + raise ValueError("Plotting coordinate for multi xy plot should change only along 1 dimension.") + axis_index = coord.dimension_list[0] + if (not ((pdd_list[1].data_type == PddType.Data) and (pdd_list[1].data_object == d))): + raise ValueError("For multi xy plot only data can be plotted on the y axis.") + + # Trying to get signal names + try: + signals_coord = d.get_coordinate_object('Signal name') + except ValueError: + signals_coord = None + if((signals_coord is not None) and (len(signals_coord.dimension_list) == 1)\ + and (signals_coord.dimension_list[0] != axis_index)): + if (axis_index == 0): + index = [0,...] + else: + index = [...,0] + signal_names, l, h = signals_coord.data(data_shape=d.shape,index=index) + signal_names = signal_names.flatten() + else: + signal_names = None + + # Getting x data + if (pdd_list[0].data_type == PddType.Data): + xdata = pdd_list[0].data_object.data.flatten() + if (plot_error): + xerror = pdd_list[0].data_object._plot_error_ranges() + else: + xerror = None + elif (pdd_list[0].data_type == PddType.Coordinate): + if (not pdd_list[0].value.isnumeric()): + raise ValueError("Coordinate is not of numeric type, cannot plot.") + index = [0]*2 + index[axis_index] = ... + xdata, xdata_low, xdata_high = \ + pdd_list[0].value.data(data_shape=pdd_list[0].data_object.shape,index=index) + xdata = xdata.flatten() + if (xdata_low is not None): + xdata_low = xdata_low.flatten() + if (xdata_high is not None): + xdata_high = xdata_high.flatten() + if (plot_error): + xerror = pdd_list[0].data_object._plot_coord_ranges(pdd_list[0].value, + xdata, + xdata_low, + xdata_high + ) + else: + xerror = None + elif (pdd_list[0].data_type == PddType.Constant): + xdata = pdd_list[ax_ind].value + xerror = None + else: + raise RuntimeError("Internal error, invalid PlotDataDescription.") + + if (_plot_id.number_of_plots != 0): + _options['Log x'] = _plot_id.options[-1]['Log x'] + _options['Log y'] = _plot_id.options[-1]['Log y'] + + ax = _plot_id.base_subplot + _plot_id.plt_axis_list = [ax] + + ysep = _options['Y separation'] + if (ysep is None): + if (_plot_id.number_of_plots != 0): + ysep = _plot_id.options[-1]['Y separation'] + else: + if (_options['Log y']): + maxval = np.nanmax(d.data) + minval = np.nanmin(d.data) + if ((minval <= 0) or (minval == maxval)): + ysep = 1 + else: + ysep = math.sqrt(maxval/minval) + else: + ysep = float(np.nanmax(d.data)) + else: + try: + ysep = float(ysep) + except ValueError: + raise ValueError("Invalid Y separation option.") + _options['Y separation'] = ysep + + # Determining Y range + if (axis_index == 0): + if (_options['Log y']): + ax_min = np.nanmin(d.data[:,0]) + ax_max = np.nanmax(d.data[:,-1]) * ysep ** (d.data.shape[1]-1) + else: + ax_min = np.nanmin(d.data[:,0]) + ax_max = np.nanmax(d.data[:,-1]) + ysep * (d.data.shape[1]-1) + signal_index = 1 + else: + if (_options['Log y']): + ax_min = np.nanmin(d.data[0,:]) + ax_max = np.nanmax(d.data[-1,:]) * ysep ** (d.data.shape[0]-1) + else: + ax_min = np.nanmin(d.data[0,:]) + ax_max = np.nanmax(d.data[-1,:]) + ysep * (d.data.shape[0]-1) + signal_index = 0 + # If overplot then taking min and max of this and previous plots + if (_plot_id.number_of_plots != 0): + ax_min = min(ax.get_ylim()[0], ax_min) + ax_max = max(ax.get_ylim()[1], ax_max) + if (ax_max <= ax_min): + ax_max += 1 + ax.set_ylim(ax_min, ax_max) + + legend = [] + if (signal_names is not None): + for i in range(d.shape[signal_index]): + legend.append(signal_names[i]) + ax.legend(legend) + + _plot_opt = _plot_options[0] + for i in range(d.shape[signal_index]): + index = [...] * 2 + index[signal_index] = i + yerror = d._plot_error_ranges(index=tuple(index)) + if (_options['Log y']): + ydata = d.data[tuple(index)].flatten() * (ysep ** i) + if (yerror is not None): + yerror *= (ysep ** i) + else: + ydata = d.data[tuple(index)].flatten() + ysep * i + if (all_points is True): + x = xdata + y = ydata + xerr = xerror + yerr = yerror + else: + x,y,xerr,yerr = sample_for_plot(xdata,ydata,xerror,yerror,maxpoints) + if (errorbars < 0): + errorevery = 1 + else: + errorevery = int(round(len(x)/errorbars)) + if (errorevery < 1): + errorevery = 1 + if (plot_error): + ax.errorbar(x,y,xerr=xerr,yerr=yerr,errorevery=errorevery,**_plot_opt) + else: + ax.plot(x,y,**_plot_opt) + + if (xrange is not None): + ax.set_xlim(xrange[0],xrange[1]) + if (yrange is not None): + ax.set_ylim(yrange[0],yrange[1]) + ax.set_xlabel(ax_list[0].title(language=language)) + ax.set_ylabel(ax_list[1].title(language=language)) + if (_options['Log x']): + ax.set_xscale('log') + if (_options['Log y']): + ax.set_yscale('log') + title = ax.get_title() + if (title is None): + title = '' + if (title[-3:] != '...'): + newtitle = '' + if (d.exp_id is not None): + newtitle += str(d.exp_id) + ' ' + if (d.data_title is not None): + newtitle += d.data_title + if (len(newtitle) != 0): + if (len(title + newtitle) < 40): + if (title != ''): + title += ','+newtitle + else: + title = newtitle + else: + title += ',...' + ax.set_title(title) + _plot_id.plot_subtype = 0 # real multi xy plot + + elif ((_plot_type == 'image') or (_plot_type == 'contour')): + if (d.data is None): + raise ValueError("Cannot plot DataObject without data.") + if (len(d.shape) != 2): + raise TypeError("Image/contour plot is applicable to 2D data only. Use slicing.") + if (d.data.dtype.kind == 'c'): + raise TypeError("Image/contour plot is applicable only to real data.") + # Checking for numeric type + try: + d.data[0,0] += 1 + except TypeError: + raise TypeError("Image plot is applicable only to numeric data.") + + yrange = _options['Y range'] + if (yrange is not None): + if ((type(yrange) is not list) or (len(yrange) != 2)): + raise ValueError("Invalid Y range setting.") + + # Processing axes + # Although the plot will be cleared the existing plot axes will be considered + default_axes = [d.coordinates[0].unit.name, d.coordinates[1].unit.name, '__Data__'] + try: + pdd_list, ax_list = _plot_id.check_axes(d, + axes, + clear=_options['Clear'], + default_axes=default_axes, + force=_options['Force axes']) + except ValueError as e: + raise e + + # No overplotting is possible for this type of plot, erasing and restarting a Plot_ID + if (not _options['Clear']): + plt.subplot(_plot_id.base_subplot) + plt.cla() + gs = gridspec.GridSpecFromSubplotSpec(1, 1, subplot_spec=_plot_id.base_subplot) + _plot_id.plt_axis_list = [] + _plot_id.plt_axis_list.append(plt.subplot(gs[0,0])) + ax = _plot_id.plt_axis_list[0] + + pdd_list[2].data_type = PddType.Data + pdd_list[2].data_object = d + if ((pdd_list[0].data_type != PddType.Coordinate) or (pdd_list[1].data_type != PddType.Coordinate)) : + raise ValueError("X and y coordinates of image plot type should be coordinates.") + + coord_x = pdd_list[0].value + coord_y = pdd_list[1].value + if (_plot_type == 'image'): + if ((coord_x.mode.equidistant) and (len(coord_x.dimension_list) == 1) and + (coord_y.mode.equidistant) and (len(coord_y.dimension_list) == 1)): + # This data is image-like with data points on a rectangular array + image_like = True + elif ((len(coord_x.dimension_list) == 1) and (len(coord_y.dimension_list) == 1)): + if (not coord_x.isnumeric()): + raise ValueError('Coordinate '+coord_x.unit.name+' is not numeric.') + if (not coord_y.isnumeric()): + raise ValueError('Coordinate '+coord_y.unit.name+' is not numeric.') + index = [0] * len(d.shape) + index[coord_x.dimension_list[0]] = ... + xdata,xdata_low,xdata_high = coord_x.data(data_shape=d.shape,index=index) + xdata = xdata.flatten() + dx = xdata[1:] - xdata[:-1] + index = [0] * len(d.shape) + index[coord_y.dimension_list[0]] = ... + ydata,ydata_low,ydata_high = coord_y.data(data_shape=d.shape,index=index) + ydata = ydata.flatten() + dy = ydata[1:] - ydata[:-1] + if ((np.nonzero(np.abs(dx - dx[0]) / math.fabs(dx[0]) > 0.01)[0].size == 0) and + (np.nonzero(np.abs(dy - dy[0]) / math.fabs(dy[0]) > 0.01)[0].size == 0)): + # Actually the non-equidistant coordinates are equidistant + image_like = True + else: + image_like = False + else: + image_like = False + else: + image_like = False + if (image_like): + xdata_range = coord_x.data_range(data_shape=d.shape)[0] + ydata_range = coord_y.data_range(data_shape=d.shape)[0] + else: + ydata,ydata_low,ydata_high = coord_y.data(data_shape=d.shape) + xdata,xdata_low,xdata_high = coord_x.data(data_shape=d.shape) + + if (zrange is None): + vmin = np.nanmin(d.data) + vmax = np.nanmax(d.data) + else: + vmin = zrange[0] + vmax = zrange[1] + + if (vmax <= vmin): + raise ValueError("Invalid z range.") + + if (_options['Log z']): + if (vmin <= 0): + raise ValueError("z range[0] cannot be negative or zero for logarithmic scale.") + norm = colors.LogNorm(vmin=vmin, vmax=vmax) + locator = ticker.LogLocator(subs='all') + else: + norm = None + locator = None + + if (contour_levels is None): + contour_levels = 255 + + _plot_opt = _plot_options[0] + + try: + cmap_obj = plt.cm.get_cmap(cmap) + if (_options['Nan color'] is not None): + cmap_obj.set_bad(_options['Nan color']) + except ValueError: + raise ValueError("Invalid color map.") + + if (image_like): + try: +# if (coord_x.dimension_list[0] == 0): + if (coord_x.dimension_list[0] < coord_y.dimension_list[0]): + im=np.clip(np.transpose(d.data),vmin,vmax) + else: + im=np.clip(d.data,vmin,vmax) + img = ax.imshow(im,extent=xdata_range + ydata_range,norm=norm, + cmap=cmap_obj,vmin=vmin,aspect=_options['Aspect ratio'],interpolation=_options['Interpolation'], + vmax=vmax,origin='lower',**_plot_opt) + del im + except Exception as e: + raise e + else: + if (_plot_type == 'image'): + xgrid, ygrid = flap.tools.grid_to_box(xdata,ydata) + try: + img = ax.pcolormesh(xgrid,ygrid,np.clip(np.transpose(d.data),vmin,vmax),norm=norm,cmap=cmap,vmin=vmin, + vmax=vmax,**_plot_opt) + except Exception as e: + raise e + else: + try: + img = ax.contourf(xdata,ydata,np.clip(d.data,vmin,vmax),contour_levels,norm=norm, + origin='lower',cmap=cmap,vmin=vmin,vmax=vmax,**_plot_opt) + except Exception as e: + raise e + + if (_options['Colorbar']): + cbar = plt.colorbar(img,ax=ax) + if (d.data_unit.unit is not None) and (d.data_unit.unit != ''): + unit_name = '['+d.data_unit.unit+']' + else: + unit_name = '' + cbar.set_label(d.data_unit.name+' '+unit_name) + + if (xrange is not None): + ax.set_xlim(xrange[0],xrange[1]) + if (yrange is not None): + ax.set_ylim(yrange[0],yrange[1]) + ax.set_xlabel(ax_list[0].title(language=language)) + ax.set_ylabel(ax_list[1].title(language=language)) + if (_options['Log x']): + ax.set_xscale('log') + if (_options['Log y']): + ax.set_yscale('log') + title = ax.get_title() + if (title is None): + title = '' + if (title[-3:] != '...'): + newtitle = '' + if (d.exp_id is not None): + newtitle += str(d.exp_id) + if (d.data_title is not None): + newtitle += ' ' + d.data_title + if (len(newtitle) != 0): + if (len(title + newtitle) < 40): + if (title != ''): + title += ','+newtitle + else: + title = newtitle + else: + title += ',...' + ax.set_title(title) + + elif ((_plot_type == 'anim-image') or (_plot_type == 'anim-contour')): + if (d.data is None): + raise ValueError("Cannot plot DataObject without data.") + if (len(d.shape) != 3): + raise TypeError("Animated image plot is applicable to 3D data only. Use slicing.") + if (d.data.dtype.kind == 'c'): + raise TypeError("Animated image plot is applicable only to real data.") + # Checking for numeric type + try: + d.data[0,0] += 1 + except TypeError: + raise TypeError("Animated image plot is applicable only to numeric data.") + + yrange = _options['Y range'] + if (yrange is not None): + if ((type(yrange) is not list) or (len(yrange) != 2)): + raise ValueError("Invalid Y range setting.") + + # Processing axes + # Although the plot will be cleared the existing plot axes will be considered + default_axes = [d.coordinates[0].unit.name, d.coordinates[1].unit.name,d.coordinates[2].unit.name,'__Data__'] + try: + pdd_list, ax_list = _plot_id.check_axes(d, + axes, + clear=_options['Clear'], + default_axes=default_axes, + force=_options['Force axes']) + except ValueError as e: + raise e + + if (not ((pdd_list[3].data_type == PddType.Data) and (pdd_list[3].data_object == d))): + raise ValueError("For anim-image/anim-contour plot only data can be plotted on the z axis.") + if ((pdd_list[0].data_type != PddType.Coordinate) or (pdd_list[1].data_type != PddType.Coordinate)) : + raise ValueError("X and y coordinates of anim-image/anim-contour plot type should be coordinates.") + if (pdd_list[2].data_type != PddType.Coordinate) : + raise ValueError("Time coordinate of anim-image/anim-contour plot should be flap.coordinate.") + + coord_x = pdd_list[0].value + coord_y = pdd_list[1].value + coord_t = pdd_list[2].value + + if (len(coord_t.dimension_list) != 1): + raise ValueError("Time coordinate for anim-image/anim-contour plot should be changing only along one dimension.") + try: + coord_x.dimension_list.index(coord_t.dimension_list[0]) + badx = True + except: + badx = False + try: + coord_y.dimension_list.index(coord_t.dimension_list[0]) + bady = True + except: + bady = False + if (badx or bady): + raise ValueError("X and y coordinate for anim-image plot should not change in time dimension.") + + index = [0] * 3 + index[coord_t.dimension_list[0]] = ... + tdata = coord_t.data(data_shape=d.shape,index=index)[0].flatten() + if (not coord_y.isnumeric()): + raise ValueError('Coordinate '+coord_y.unit.name+' is not numeric.') + + if ((coord_x.mode.equidistant) and (len(coord_x.dimension_list) == 1) and + (coord_y.mode.equidistant) and (len(coord_y.dimension_list) == 1)): + # This data is image-like with data points on a rectangular array + image_like = True + elif ((len(coord_x.dimension_list) == 1) and (len(coord_y.dimension_list) == 1)): + if (not coord_x.isnumeric()): + raise ValueError('Coordinate '+coord_x.unit.name+' is not numeric.') + if (not coord_y.isnumeric()): + raise ValueError('Coordinate '+coord_y.unit.name+' is not numeric.') + index = [0] * len(d.shape) + index[coord_x.dimension_list[0]] = ... + xdata,xdata_low,xdata_high = coord_x.data(data_shape=d.shape,index=index) + xdata = xdata.flatten() + dx = xdata[1:] - xdata[:-1] + index = [0] * len(d.shape) + index[coord_y.dimension_list[0]] = ... + ydata,ydata_low,ydata_high = coord_y.data(data_shape=d.shape,index=index) + ydata = ydata.flatten() + dy = ydata[1:] - ydata[:-1] + if ((np.nonzero(np.abs(dx - dx[0]) / math.fabs(dx[0]) > 0.01)[0].size == 0) and + (np.nonzero(np.abs(dy - dy[0]) / math.fabs(dy[0]) > 0.01)[0].size == 0)): + # Actually the non-equidistant coordinates are equidistant + image_like = True + else: + image_like = False + else: + image_like = False + if (image_like and (_plot_type == 'anim-image')): + xdata_range = coord_x.data_range(data_shape=d.shape)[0] + ydata_range = coord_y.data_range(data_shape=d.shape)[0] + else: + index = [...]*3 + index[coord_t.dimension_list[0]] = 0 + ydata = np.squeeze(coord_y.data(data_shape=d.shape,index=index)[0]) + xdata = np.squeeze(coord_x.data(data_shape=d.shape,index=index)[0]) + + try: + cmap_obj = plt.cm.get_cmap(cmap) + if (_options['Nan color'] is not None): + cmap_obj.set_bad(_options['Nan color']) + except ValueError: + raise ValueError("Invalid color map.") + + gs = gridspec.GridSpecFromSubplotSpec(1, 1, subplot_spec=_plot_id.base_subplot) +# ax=plt.plot() + _plot_id.plt_axis_list = [] + _plot_id.plt_axis_list.append(plt.subplot(gs[0,0])) +# plt.subplot(_plot_id.base_subplot) +# plt.plot() +# plt.cla() +# ax=plt.gca() + for it in range(len(tdata)): + plt.subplot(_plot_id.base_subplot) + ax_act = plt.subplot(gs[0,0]) + time_index = [slice(0,dim) for dim in d.data.shape] + time_index[coord_t.dimension_list[0]] = it + time_index = tuple(time_index) + + if (zrange is None): + vmin = np.nanmin(d.data[time_index]) + vmax = np.nanmax(d.data[time_index]) + else: + vmin = zrange[0] + vmax = zrange[1] + + if (vmax <= vmin): + raise ValueError("Invalid z range.") + + if (_options['Log z']): + if (vmin <= 0): + raise ValueError("z range[0] cannot be negative or zero for logarithmic scale.") + norm = colors.LogNorm(vmin=vmin, vmax=vmax) + locator = ticker.LogLocator(subs='all') + else: + norm = None + locator = None #UNUSED + + if (contour_levels is None): + contour_levels = 255 + + _plot_opt = _plot_options[0] + + if (image_like and (_plot_type == 'anim-image')): + try: + #if (coord_x.dimension_list[0] == 0): + if (coord_x.dimension_list[0] < coord_y.dimension_list[0]): + im = np.clip(np.transpose(d.data[time_index]),vmin,vmax) + else: + im = np.clip(d.data[time_index],vmin,vmax) + + img = plt.imshow(im,extent=xdata_range + ydata_range,norm=norm, + cmap=cmap_obj,vmin=vmin,vmax=vmax, + aspect=_options['Aspect ratio'], + interpolation=_options['Interpolation'], + origin='lower',**_plot_opt) + del im + except Exception as e: + raise e + else: + if (_plot_type == 'anim-image'): + xgrid, ygrid = flap.tools.grid_to_box(xdata,ydata) + im = np.clip(np.transpose(d.data[time_index]),vmin,vmax) + try: + img = plt.pcolormesh(xgrid,ygrid,im,norm=norm,cmap=cmap,vmin=vmin, + vmax=vmax,**_plot_opt) + except Exception as e: + raise e + del im + else: + try: + im = np.clip(d.data[time_index],vmin,vmax) + img = plt.contourf(xdata,ydata,im,contour_levels,norm=norm, + origin='lower',cmap=cmap,vmin=vmin,vmax=vmax,**_plot_opt) + del im + except Exception as e: + raise e + + if (_options['Colorbar']): + cbar = plt.colorbar(img,ax=ax_act) + cbar.set_label(d.data_unit.name) + + if (xrange is not None): + plt.xlim(xrange[0],xrange[1]) + if (yrange is not None): + plt.ylim(yrange[0],yrange[1]) + plt.xlabel(ax_list[0].title(language=language)) + plt.ylabel(ax_list[1].title(language=language)) + if (_options['Log x']): + plt.xscale('log') + if (_options['Log y']): + plt.yscale('log') + title = str(d.exp_id)+' @ '+coord_t.unit.name+'='+"{:10.5f}".format(tdata[it])+' ['+coord_t.unit.unit+']' + plt.title(title) + plt.show(block=False) + time.sleep(_options['Waittime']) + plt.pause(0.001) + if ((_options['Video file'] is not None) and (cv2_presence is not False)): + fig = plt.gcf() + fig.canvas.draw() + # Get the RGBA buffer from the figure + w,h = fig.canvas.get_width_height() + buf = np.frombuffer(fig.canvas.tostring_rgb(), dtype=np.uint8) + buf.shape = ( h, w, 3 ) + buf = cv2.cvtColor(buf, cv2.COLOR_RGBA2BGR) + try: + video + except NameError: + height = buf.shape[0] + width = buf.shape[1] + video = cv2.VideoWriter(_options['Video file'], + cv2.VideoWriter_fourcc(*video_codec_code), + float(_options['Video framerate']), + (width,height), + isColor=True) + video.write(buf) + if ((_options['Video file'] is not None) and (cv2_presence is not False)): + cv2.destroyAllWindows() + video.release() + del video + + elif (_plot_type == 'animation'): + if (d.data is None): + raise ValueError("Cannot plot DataObject without data.") + if (len(d.shape) != 3): + raise TypeError("Animated image plot is applicable to 3D data only. Use slicing.") + if (d.data.dtype.kind == 'c'): + raise TypeError("Animated image plot is applicable only to real data.") + # Checking for numeric type + try: + d.data[0,0] += 1 + except TypeError: + raise TypeError("Animated image plot is applicable only to numeric data.") + + yrange = _options['Y range'] + if (yrange is not None): + if ((type(yrange) is not list) or (len(yrange) != 2)): + raise ValueError("Invalid Y range setting.") + if (zrange is not None) and (_options['Prevent saturation']): + d.data=np.mod(d.data,zrange[1]) + # Processing axes + # Although the plot will be cleared the existing plot axes will be considered + default_axes = [d.coordinates[0].unit.name, d.coordinates[1].unit.name,d.coordinates[2].unit.name,'__Data__'] + try: + pdd_list, ax_list = _plot_id.check_axes(d, + axes, + clear=_options['Clear'], + default_axes=default_axes, + force=_options['Force axes']) + except ValueError as e: + raise e + + if (not ((pdd_list[3].data_type == PddType.Data) and (pdd_list[3].data_object == d))): + raise ValueError("For the animation plot only data can be plotted on the z axis.") + if ((pdd_list[0].data_type != PddType.Coordinate) or (pdd_list[1].data_type != PddType.Coordinate)) : + raise ValueError("X and y coordinates of the animation plot type should be coordinates.") + if (pdd_list[2].data_type != PddType.Coordinate) : + raise ValueError("Time coordinate of the animation plot should be flap.coordinate.") + + coord_x = pdd_list[0].value + coord_y = pdd_list[1].value + coord_t = pdd_list[2].value + + if (len(coord_t.dimension_list) != 1): + raise ValueError("Time coordinate for anim-image/anim-contour plot should be changing only along one dimension.") + + index = [0] * 3 + index[coord_t.dimension_list[0]] = ... + tdata = coord_t.data(data_shape=d.shape,index=index)[0].flatten() + if (not coord_y.isnumeric()): + raise ValueError('Coordinate '+coord_y.unit.name+' is not numeric.') + + if ((coord_x.mode.equidistant) and (len(coord_x.dimension_list) == 1) and + (coord_y.mode.equidistant) and (len(coord_y.dimension_list) == 1)): + # This data is image-like with data points on a rectangular array + image_like = True + elif ((len(coord_x.dimension_list) == 1) and (len(coord_y.dimension_list) == 1)): + if (not coord_x.isnumeric()): + raise ValueError('Coordinate '+coord_x.unit.name+' is not numeric.') + if (not coord_y.isnumeric()): + raise ValueError('Coordinate '+coord_y.unit.name+' is not numeric.') + index = [0] * len(d.shape) + index[coord_x.dimension_list[0]] = ... + xdata,xdata_low,xdata_high = coord_x.data(data_shape=d.shape,index=index) + xdata = xdata.flatten() + dx = xdata[1:] - xdata[:-1] + index = [0] * len(d.shape) + index[coord_y.dimension_list[0]] = ... + ydata,ydata_low,ydata_high = coord_y.data(data_shape=d.shape,index=index) + ydata = ydata.flatten() + dy = ydata[1:] - ydata[:-1] + if ((np.nonzero(np.abs(dx - dx[0]) / math.fabs(dx[0]) > 0.01)[0].size == 0) and + (np.nonzero(np.abs(dy - dy[0]) / math.fabs(dy[0]) > 0.01)[0].size == 0)): + # Actually the non-equidistant coordinates are equidistant + image_like = True + else: + image_like = False + else: + image_like = False + if (image_like): + xdata_range = coord_x.data_range(data_shape=d.shape)[0] + ydata_range = coord_y.data_range(data_shape=d.shape)[0] + ydata = np.squeeze(coord_y.data(data_shape=d.shape,index=index)[0]) + xdata = np.squeeze(coord_x.data(data_shape=d.shape,index=index)[0]) + else: + index = [...]*3 + index[coord_t.dimension_list[0]] = 0 + xdata_range = None + ydata_range = None + ydata = np.squeeze(coord_y.data(data_shape=d.shape,index=index)[0]) + xdata = np.squeeze(coord_x.data(data_shape=d.shape,index=index)[0]) + + try: + cmap_obj = plt.cm.get_cmap(cmap) + if (_options['Nan color'] is not None): + cmap_obj.set_bad(_options['Nan color']) + except ValueError: + raise ValueError("Invalid color map.") + + gs = gridspec.GridSpecFromSubplotSpec(1, 1, subplot_spec=_plot_id.base_subplot) + + _plot_id.plt_axis_list = [] + _plot_id.plt_axis_list.append(plt.subplot(gs[0,0])) + + oargs=(ax_list, axes, d, xdata, ydata, tdata, xdata_range, ydata_range, + cmap_obj, contour_levels, + coord_t, coord_x, coord_y, cmap, _options, + xrange, yrange, zrange, image_like, + _plot_options, language, _plot_id, gs) + + anim = PlotAnimation(*oargs) + anim.animate() + + +# print("------ plot finished, show() ----") + plt.show(block=False) + +# if (_options['Clear']): +# _plot_id.number_of_plots = 0 +# _plot_id.plot_data = [] +# _plot_id.plt_axis_list = None +# _plot_id.number_of_plots += 1 + _plot_id.axes = ax_list + _plot_id.plot_data.append(pdd_list) + #Setting this one the default plot ID + _plot_id.options.append(_options) + _plot_id.plot_type = _plot_type + set_plot_id(_plot_id) + + return _plot_id diff --git a/flap/plot.py b/flap/plot.py index 10bed24..f6acfc7 100644 --- a/flap/plot.py +++ b/flap/plot.py @@ -252,11 +252,11 @@ def animate(self): if (self.vmin <= 0): raise ValueError("z range[0] cannot be negative or zero for logarithmic scale.") self.norm = colors.LogNorm(vmin=self.vmin, vmax=self.vmax) - #self.locator = ticker.LogLocator(subs='all') + self.locator = ticker.LogLocator(subs='all') ticker.LogLocator(subs='all') else: self.norm = None - #self.locator = None + self.locator = None _plot_opt = self.plot_options[0] @@ -659,7 +659,6 @@ def __get_gca_invalid(): This flag is set invalid when set_plot changes the default plot and this way the Matplotlib current axis (get_gca()) becomes invalid for this plot. """ - global gca_invalid try: gca_invalid except NameError: From d5ab0f7150aaecedc0a37a33bd47782cbfc43982 Mon Sep 17 00:00:00 2001 From: Mate Lampert Date: Mon, 25 Nov 2019 10:33:29 -0500 Subject: [PATCH 11/27] Bugfixes axes_unit conversion was only working if the axes was given from the option's. Now it works if it is not an input. Another bugfix with the overplot options. --- flap/plot.py | 42 +++++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/flap/plot.py b/flap/plot.py index f6acfc7..77e553e 100644 --- a/flap/plot.py +++ b/flap/plot.py @@ -1003,8 +1003,11 @@ def _plot(data_object, video_codec_code=video_codec_decrypt[_options['Video format']] #These lines do the coordinate unit conversion - axes_unit_conversion=np.zeros(len(axes)) - axes_unit_conversion[:]=1. + if axes is not None: + axes_unit_conversion=np.zeros(len(axes)) + axes_unit_conversion[:]=1. + else: + axes_unit_conversion=[1.,1.,1.] if _options['Plot units'] is not None: unit_length=len(_options['Plot units']) @@ -1407,23 +1410,24 @@ def _plot(data_object, if (yrange is not None): ax.set_ylim(yrange[0], yrange[1]) - if overplot_options['line'] is not None: - for line_obj_keys in overplot_options['line']: - xmin, xmax = ax.get_xbound() - ymin, ymax = ax.get_ybound() - if overplot_options['line'][line_obj_keys]['Plot']: - if overplot_options['line'][line_obj_keys]['Horizontal'] is not None: - h_coords=overplot_options['line'][line_obj_keys]['Horizontal'] - for segments in h_coords: - if segments[0] > ymin and segments[0] < ymax: - l = mlines.Line2D([xmin,xmax], [segments[0],segments[0]], color=segments[1]) - ax.add_line(l) - if overplot_options['line'][line_obj_keys]['Vertical'] is not None: - v_coords=overplot_options['line'][line_obj_keys]['Vertical'] - for segments in v_coords: - if segments[0] > xmin and segments[0] < xmax: - l = mlines.Line2D([segments[0],segments[0]], [ymin,ymax], color=segments[1]) - ax.add_line(l) + if (overplot_options is not None): + if overplot_options['line'] is not None: + for line_obj_keys in overplot_options['line']: + xmin, xmax = ax.get_xbound() + ymin, ymax = ax.get_ybound() + if overplot_options['line'][line_obj_keys]['Plot']: + if overplot_options['line'][line_obj_keys]['Horizontal'] is not None: + h_coords=overplot_options['line'][line_obj_keys]['Horizontal'] + for segments in h_coords: + if segments[0] > ymin and segments[0] < ymax: + l = mlines.Line2D([xmin,xmax], [segments[0],segments[0]], color=segments[1]) + ax.add_line(l) + if overplot_options['line'][line_obj_keys]['Vertical'] is not None: + v_coords=overplot_options['line'][line_obj_keys]['Vertical'] + for segments in v_coords: + if segments[0] > xmin and segments[0] < xmax: + l = mlines.Line2D([segments[0],segments[0]], [ymin,ymax], color=segments[1]) + ax.add_line(l) # Setting axis labels if axes_unit_conversion[0] == 1.: ax.set_xlabel(ax_list[0].title(language=language)) From 0052b6ebdd0cdc6bafdf3f1a44c5f1e7e97582ab Mon Sep 17 00:00:00 2001 From: Mate Lampert Date: Tue, 26 Nov 2019 21:16:05 -0500 Subject: [PATCH 12/27] Bugfixes Plotting against coordinates which have temporal changes is now allowed. This version is only a backup. It needs to be fixed for stationary coordinates, because this uses way too much memory. --- flap/plot.py | 82 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 51 insertions(+), 31 deletions(-) diff --git a/flap/plot.py b/flap/plot.py index 77e553e..0bc21c3 100644 --- a/flap/plot.py +++ b/flap/plot.py @@ -301,11 +301,16 @@ def animate(self): if (self.xrange is not None): plt.xlim(self.xrange[0]*self.axes_unit_conversion[0], self.xrange[1]*self.axes_unit_conversion[0]) - + else: + plt.xlim(np.min(self.xdata)*self.axes_unit_conversion[0], + np.max(self.xdata)*self.axes_unit_conversion[0]) if (self.yrange is not None): plt.ylim(self.yrange[0]*self.axes_unit_conversion[1], - self.yrange[1]*self.axes_unit_conversion[1]) - + self.yrange[1]*self.axes_unit_conversion[1]) + else: + plt.ylim(np.min(self.ydata)*self.axes_unit_conversion[0], + np.max(self.ydata)*self.axes_unit_conversion[0]) + if self.axes_unit_conversion[0] == 1.: plt.xlabel(self.ax_list[0].title(language=self.language)) else: @@ -2194,18 +2199,18 @@ def _plot(data_object, if (len(coord_t.dimension_list) != 1): raise ValueError("Time coordinate for anim-image/anim-contour plot should be changing only along one dimension.") - try: - coord_x.dimension_list.index(coord_t.dimension_list[0]) - badx = True - except: - badx = False - try: - coord_y.dimension_list.index(coord_t.dimension_list[0]) - bady = True - except: - bady = False - if (badx or bady): - raise ValueError("X and y coordinate for anim-image plot should not change in time dimension.") +# try: +# coord_x.dimension_list.index(coord_t.dimension_list[0]) +# badx = True +# except: +# badx = False +# try: +# coord_y.dimension_list.index(coord_t.dimension_list[0]) +# bady = True +# except: +# bady = False +# if (badx or bady): +# raise ValueError("X and y coordinate for anim-image plot should not change in time dimension.") index = [0] * 3 index[coord_t.dimension_list[0]] = ... @@ -2249,7 +2254,7 @@ def _plot(data_object, axes_unit_conversion[1] * ydata_range[1]] else: index = [...]*3 - index[coord_t.dimension_list[0]] = 0 + #index[coord_t.dimension_list[0]] = 0 ydata = np.squeeze(coord_y.data(data_shape=d.shape,index=index)[0]) xdata = np.squeeze(coord_x.data(data_shape=d.shape,index=index)[0]) try: @@ -2262,22 +2267,20 @@ def _plot(data_object, gs = gridspec.GridSpecFromSubplotSpec(1, 1, subplot_spec=_plot_id.base_subplot) _plot_id.plt_axis_list = [] _plot_id.plt_axis_list.append(plt.subplot(gs[0,0])) - + if _options['Equal axes'] and not coord_x.unit.unit == coord_y.unit.unit: + print('Equal axis is not possible. The axes units are not equal.') for it in range(len(tdata)): plt.subplot(_plot_id.base_subplot) ax_act = plt.subplot(gs[0,0]) - if _options['Equal axes']: - if (coord_x.unit.unit == coord_y.unit.unit): - ax_act.axis('equal') - else: - print('Equal axis is not possible. The axes units are not equal.') + if _options['Equal axes'] and coord_x.unit.unit == coord_y.unit.unit: + ax_act.axis('equal') time_index = [slice(0,dim) for dim in d.data.shape] time_index[coord_t.dimension_list[0]] = it time_index = tuple(time_index) if (zrange is None): - vmin = np.nanmin(d.data[time_index]) - vmax = np.nanmax(d.data[time_index]) + vmin = np.nanmin(d.data) + vmax = np.nanmax(d.data) else: vmin = zrange[0] vmax = zrange[1] @@ -2316,8 +2319,14 @@ def _plot(data_object, raise e else: if (_plot_type == 'anim-image'): - xgrid, ygrid = flap.tools.grid_to_box(xdata*axes_unit_conversion[0], - ydata*axes_unit_conversion[1]) + #xgrid, ygrid = flap.tools.grid_to_box(xdata*axes_unit_conversion[0], + # ydata*axes_unit_conversion[1]) + if (len(xdata.shape) == 3 and len(ydata.shape) == 3): + xgrid, ygrid = flap.tools.grid_to_box(xdata[time_index]*axes_unit_conversion[0], + ydata[time_index]*axes_unit_conversion[1]) + else: + xgrid, ygrid = flap.tools.grid_to_box(xdata*axes_unit_conversion[0], + ydata*axes_unit_conversion[1]) im = np.clip(np.transpose(d.data[time_index]),vmin,vmax) try: img = plt.pcolormesh(xgrid,ygrid,im,norm=norm,cmap=cmap,vmin=vmin, @@ -2327,9 +2336,15 @@ def _plot(data_object, del im else: try: + if (len(xdata.shape) == 3 and len(ydata.shape) == 3): + xgrid=xdata[time_index]*axes_unit_conversion[1] + ygrid=ydata[time_index]*axes_unit_conversion[1] + else: + xgrid=xdata*axes_unit_conversion[0] + ygrid=ydata*axes_unit_conversion[1] im = np.clip(d.data[time_index],vmin,vmax) - img = plt.contourf(xdata*axes_unit_conversion[0], - ydata*axes_unit_conversion[1], + img = plt.contourf(xgrid, + ygrid, im, contour_levels,norm=norm,origin='lower', cmap=cmap,vmin=vmin,vmax=vmax,**_plot_opt) @@ -2383,10 +2398,16 @@ def _plot(data_object, if (xrange is not None): plt.xlim(xrange[0]*axes_unit_conversion[0], xrange[1]*axes_unit_conversion[0]) + else: + plt.xlim(np.min(xdata)*axes_unit_conversion[0], + np.max(xdata)*axes_unit_conversion[0]) if (yrange is not None): plt.ylim(yrange[0]*axes_unit_conversion[1], - yrange[1]*axes_unit_conversion[1]) + yrange[1]*axes_unit_conversion[1]) + else: + plt.ylim(np.min(ydata)*axes_unit_conversion[0], + np.max(ydata)*axes_unit_conversion[0]) if axes_unit_conversion[0] == 1.: plt.xlabel(ax_list[0].title(language=language)) @@ -2536,12 +2557,11 @@ def _plot(data_object, xdata = np.squeeze(coord_x.data(data_shape=d.shape,index=index)[0]) else: index = [...]*3 - index[coord_t.dimension_list[0]] = 0 + #index[coord_t.dimension_list[0]] = 0 #Why is this even needed? It prevents temporal change in coordinates. xdata_range = None ydata_range = None ydata = np.squeeze(coord_y.data(data_shape=d.shape,index=index)[0]) xdata = np.squeeze(coord_x.data(data_shape=d.shape,index=index)[0]) - try: cmap_obj = plt.cm.get_cmap(cmap) if (_options['Nan color'] is not None): From 9edac82b54229665b0f959b95c1f79e45d829b34 Mon Sep 17 00:00:00 2001 From: Mate Lampert Date: Wed, 27 Nov 2019 19:03:17 -0500 Subject: [PATCH 13/27] Bugfixes Fixed the sliding axis in the animations and the memory issue in the previous release. There were also some bugs related to line plotting when either horizontal or vertical plotting was not present. --- flap/plot.py | 131 +++++++++++++++++++++++++-------------------------- 1 file changed, 65 insertions(+), 66 deletions(-) diff --git a/flap/plot.py b/flap/plot.py index 0bc21c3..125e0f3 100644 --- a/flap/plot.py +++ b/flap/plot.py @@ -217,7 +217,11 @@ def animate(self): plt.subplot(self.plot_id.base_subplot) self.ax_act = plt.subplot(self.gs[0,0]) - + if (len(self.coord_x.dimension_list) == 3 or + len(self.coord_y.dimension_list) == 3): + self.ax_act.set_autoscale_on(False) + + #The following lines set the axes to be equal if the units of the axes-to-be-plotted are the same if self.options['Equal axes']: axes_coordinate_decrypt=[0] * len(self.axes) @@ -298,19 +302,16 @@ def animate(self): #EFIT overplot feature implementation: #It needs to be more generalied in the future as the coordinates are not necessarily in this order: [time_index,spat_index] #This needs to be cross-checked with the time array's dimensions wherever there is a call for a certain index. - if (self.xrange is not None): - plt.xlim(self.xrange[0]*self.axes_unit_conversion[0], - self.xrange[1]*self.axes_unit_conversion[0]) - else: - plt.xlim(np.min(self.xdata)*self.axes_unit_conversion[0], - np.max(self.xdata)*self.axes_unit_conversion[0]) - if (self.yrange is not None): - plt.ylim(self.yrange[0]*self.axes_unit_conversion[1], - self.yrange[1]*self.axes_unit_conversion[1]) - else: - plt.ylim(np.min(self.ydata)*self.axes_unit_conversion[0], - np.max(self.ydata)*self.axes_unit_conversion[0]) - + if (self.xrange is None): + self.xrange=[np.min(self.xdata),np.max(self.xdata)] + plt.xlim(self.xrange[0]*self.axes_unit_conversion[0], + self.xrange[1]*self.axes_unit_conversion[0]) + + if (self.yrange is None): + self.yrange=[np.min(self.ydata),np.max(self.ydata)] + plt.ylim(self.yrange[0]*self.axes_unit_conversion[1], + self.yrange[1]*self.axes_unit_conversion[1]) + if self.axes_unit_conversion[0] == 1.: plt.xlabel(self.ax_list[0].title(language=self.language)) else: @@ -362,6 +363,13 @@ def animate_plot(self, it): plot_opt = copy.deepcopy(self.plot_options[0]) self.ax_act.clear() + self.ax_act.set_autoscale_on(False) + + self.ax_act.set_xlim(self.xrange[0]*self.axes_unit_conversion[0], + self.xrange[1]*self.axes_unit_conversion[0]) + self.ax_act.set_ylim(self.yrange[0]*self.axes_unit_conversion[1], + self.yrange[1]*self.axes_unit_conversion[1]) + if (self.image_like): try: if (self.coord_x.dimension_list[0] < self.coord_y.dimension_list[0]): @@ -390,9 +398,8 @@ def animate_plot(self, it): except Exception as e: raise e del im - + if (self.overplot_options is not None): - self.ax_act.set_autoscale_on(False) for path_obj_keys in self.overplot_options['path']: if self.overplot_options['path'][path_obj_keys]['Plot']: im = plt.plot(self.overplot_options['path'][path_obj_keys]['data']['Data resampled'][0,it,:]*self.axes_unit_conversion[0], @@ -410,26 +417,21 @@ def animate_plot(self, it): xmin, xmax = self.ax_act.get_xbound() ymin, ymax = self.ax_act.get_ybound() if self.overplot_options['line'][line_obj_keys]['Plot']: - if self.overplot_options['line'][line_obj_keys]['Horizontal'] is not None: + + if 'Horizontal' in self.overplot_options['line'][line_obj_keys]: h_coords=self.overplot_options['line'][line_obj_keys]['Horizontal'] for segments in h_coords: if segments[0] > ymin and segments[0] < ymax: l = mlines.Line2D([xmin,xmax], [segments[0],segments[0]], color=segments[1]) - self.ax_act.add_line(l) - if self.overplot_options['line'][line_obj_keys]['Vertical'] is not None: + self.ax_act.add_line(l) + + if 'Vertical' in self.overplot_options['line'][line_obj_keys]: v_coords=self.overplot_options['line'][line_obj_keys]['Vertical'] for segments in v_coords: if segments[0] > xmin and segments[0] < xmax: l = mlines.Line2D([segments[0],segments[0]], [ymin,ymax], color=segments[1]) self.ax_act.add_line(l) - - if (self.xrange is not None): - self.ax_act.set_xlim(self.xrange[0]*self.axes_unit_conversion[0], - self.xrange[1]*self.axes_unit_conversion[0]) - if (self.yrange is not None): - self.ax_act.set_ylim(self.yrange[0]*self.axes_unit_conversion[1], - self.yrange[1]*self.axes_unit_conversion[1]) - + if self.axes_unit_conversion[0] == 1.: plt.xlabel(self.ax_list[0].title(language=self.language)) else: @@ -1415,19 +1417,20 @@ def _plot(data_object, if (yrange is not None): ax.set_ylim(yrange[0], yrange[1]) + if (overplot_options is not None): if overplot_options['line'] is not None: for line_obj_keys in overplot_options['line']: xmin, xmax = ax.get_xbound() ymin, ymax = ax.get_ybound() if overplot_options['line'][line_obj_keys]['Plot']: - if overplot_options['line'][line_obj_keys]['Horizontal'] is not None: + if 'Horizontal' in overplot_options['line'][line_obj_keys]: h_coords=overplot_options['line'][line_obj_keys]['Horizontal'] for segments in h_coords: if segments[0] > ymin and segments[0] < ymax: l = mlines.Line2D([xmin,xmax], [segments[0],segments[0]], color=segments[1]) ax.add_line(l) - if overplot_options['line'][line_obj_keys]['Vertical'] is not None: + if 'Vertical' in overplot_options['line'][line_obj_keys]: v_coords=overplot_options['line'][line_obj_keys]['Vertical'] for segments in v_coords: if segments[0] > xmin and segments[0] < xmax: @@ -1614,14 +1617,16 @@ def _plot(data_object, for line_obj_keys in overplot_options['line']: xmin, xmax = ax.get_xbound() ymin, ymax = ax.get_ybound() + if overplot_options['line'][line_obj_keys]['Plot']: - if overplot_options['line'][line_obj_keys]['Horizontal'] is not None: + if 'Horizontal' in overplot_options['line'][line_obj_keys]: h_coords=overplot_options['line'][line_obj_keys]['Horizontal'] for segments in h_coords: if segments[0] > ymin and segments[0] < ymax: l = mlines.Line2D([xmin,xmax], [segments[0],segments[0]], color=segments[1]) ax.add_line(l) - if overplot_options['line'][line_obj_keys]['Vertical'] is not None: + + if 'Vertical' in overplot_options['line'][line_obj_keys]: v_coords=overplot_options['line'][line_obj_keys]['Vertical'] for segments in v_coords: if segments[0] > xmin and segments[0] < xmax: @@ -1860,13 +1865,13 @@ def _plot(data_object, xmin, xmax = ax.get_xbound() ymin, ymax = ax.get_ybound() if overplot_options['line'][line_obj_keys]['Plot']: - if overplot_options['line'][line_obj_keys]['Horizontal'] is not None: + if 'Horizontal' in overplot_options['line'][line_obj_keys]: h_coords=overplot_options['line'][line_obj_keys]['Horizontal'] for segments in h_coords: if segments[0] > ymin and segments[0] < ymax: l = mlines.Line2D([xmin,xmax], [segments[0],segments[0]], color=segments[1]) ax.add_line(l) - if overplot_options['line'][line_obj_keys]['Vertical'] is not None: + if 'Vertical' in overplot_options['line'][line_obj_keys]: v_coords=overplot_options['line'][line_obj_keys]['Vertical'] for segments in v_coords: if segments[0] > xmin and segments[0] < xmax: @@ -2086,13 +2091,13 @@ def _plot(data_object, xmin, xmax = ax.get_xbound() ymin, ymax = ax.get_ybound() if overplot_options['line'][line_obj_keys]['Plot']: - if overplot_options['line'][line_obj_keys]['Horizontal'] is not None: + if 'Horizontal' in overplot_options['line'][line_obj_keys]: h_coords=overplot_options['line'][line_obj_keys]['Horizontal'] for segments in h_coords: if segments[0] > ymin and segments[0] < ymax: l = mlines.Line2D([xmin,xmax], [segments[0],segments[0]], color=segments[1]) ax.add_line(l) - if overplot_options['line'][line_obj_keys]['Vertical'] is not None: + if 'Vertical' in overplot_options['line'][line_obj_keys]: v_coords=overplot_options['line'][line_obj_keys]['Vertical'] for segments in v_coords: if segments[0] > xmin and segments[0] < xmax: @@ -2113,7 +2118,8 @@ def _plot(data_object, if (yrange is not None): ax.set_ylim(yrange[0]*axes_unit_conversion[1], - yrange[1]*axes_unit_conversion[1]) + yrange[1]*axes_unit_conversion[1]) + if axes_unit_conversion[0] == 1.: ax.set_xlabel(ax_list[0].title(language=language)) else: @@ -2199,18 +2205,6 @@ def _plot(data_object, if (len(coord_t.dimension_list) != 1): raise ValueError("Time coordinate for anim-image/anim-contour plot should be changing only along one dimension.") -# try: -# coord_x.dimension_list.index(coord_t.dimension_list[0]) -# badx = True -# except: -# badx = False -# try: -# coord_y.dimension_list.index(coord_t.dimension_list[0]) -# bady = True -# except: -# bady = False -# if (badx or bady): -# raise ValueError("X and y coordinate for anim-image plot should not change in time dimension.") index = [0] * 3 index[coord_t.dimension_list[0]] = ... @@ -2254,7 +2248,9 @@ def _plot(data_object, axes_unit_conversion[1] * ydata_range[1]] else: index = [...]*3 - #index[coord_t.dimension_list[0]] = 0 + if (len(coord_x.dimension_list) < 3 and + len(coord_y.dimension_list) < 3): + index[coord_t.dimension_list[0]] = 0 ydata = np.squeeze(coord_y.data(data_shape=d.shape,index=index)[0]) xdata = np.squeeze(coord_x.data(data_shape=d.shape,index=index)[0]) try: @@ -2300,6 +2296,21 @@ def _plot(data_object, if (contour_levels is None): contour_levels = 255 + ax_act.clear() + ax_act.set_autoscale_on(False) + if (xrange is not None): + plt.xlim(xrange[0]*axes_unit_conversion[0], + xrange[1]*axes_unit_conversion[0]) + else: + plt.xlim(np.min(xdata)*axes_unit_conversion[0], + np.max(xdata)*axes_unit_conversion[0]) + + if (yrange is not None): + plt.ylim(yrange[0]*axes_unit_conversion[1], + yrange[1]*axes_unit_conversion[1]) + else: + plt.ylim(np.min(ydata)*axes_unit_conversion[0], + np.max(ydata)*axes_unit_conversion[0]) _plot_opt = _plot_options[0] if (image_like and (_plot_type == 'anim-image')): @@ -2378,13 +2389,13 @@ def _plot(data_object, xmin, xmax = ax_act.get_xbound() ymin, ymax = ax_act.get_ybound() if overplot_options['line'][line_obj_keys]['Plot']: - if overplot_options['line'][line_obj_keys]['Horizontal'] is not None: + if 'Horizontal' in overplot_options['line'][line_obj_keys]: h_coords=overplot_options['line'][line_obj_keys]['Horizontal'] for segments in h_coords: if segments[0] > ymin and segments[0] < ymax: l = mlines.Line2D([xmin,xmax], [segments[0],segments[0]], color=segments[1]) ax_act.add_line(l) - if overplot_options['line'][line_obj_keys]['Vertical'] is not None: + if 'Vertical' in overplot_options['line'][line_obj_keys]: v_coords=overplot_options['line'][line_obj_keys]['Vertical'] for segments in v_coords: if segments[0] > xmin and segments[0] < xmax: @@ -2394,20 +2405,6 @@ def _plot(data_object, if (_options['Colorbar']): cbar = plt.colorbar(img,ax=ax_act) cbar.set_label(d.data_unit.name) - - if (xrange is not None): - plt.xlim(xrange[0]*axes_unit_conversion[0], - xrange[1]*axes_unit_conversion[0]) - else: - plt.xlim(np.min(xdata)*axes_unit_conversion[0], - np.max(xdata)*axes_unit_conversion[0]) - - if (yrange is not None): - plt.ylim(yrange[0]*axes_unit_conversion[1], - yrange[1]*axes_unit_conversion[1]) - else: - plt.ylim(np.min(ydata)*axes_unit_conversion[0], - np.max(ydata)*axes_unit_conversion[0]) if axes_unit_conversion[0] == 1.: plt.xlabel(ax_list[0].title(language=language)) @@ -2557,7 +2554,9 @@ def _plot(data_object, xdata = np.squeeze(coord_x.data(data_shape=d.shape,index=index)[0]) else: index = [...]*3 - #index[coord_t.dimension_list[0]] = 0 #Why is this even needed? It prevents temporal change in coordinates. + if (len(coord_x.dimension_list) < 3 and + len(coord_y.dimension_list) < 3): + index[coord_t.dimension_list[0]] = 0 xdata_range = None ydata_range = None ydata = np.squeeze(coord_y.data(data_shape=d.shape,index=index)[0]) From 6b48804b8b344fd0cb1e2efc4bfe92f222c37235 Mon Sep 17 00:00:00 2001 From: Mate Lampert Date: Mon, 2 Dec 2019 14:34:19 -0500 Subject: [PATCH 14/27] Bugfix Bugfixes for image_like plotting. It got messed up after the set_autoscale_on setting and some modifications had to be issued to get the former functionality back. --- flap/plot.py | 61 ++++++++++++++++++++++++++-------------------------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/flap/plot.py b/flap/plot.py index 125e0f3..0da1800 100644 --- a/flap/plot.py +++ b/flap/plot.py @@ -295,6 +295,17 @@ def animate(self): self.xdata_range=None self.ydata_range=None + if (self.xrange is None): + self.xrange=[np.min(self.xdata),np.max(self.xdata)] + + plt.xlim(self.xrange[0]*self.axes_unit_conversion[0], + self.xrange[1]*self.axes_unit_conversion[0]) + + if (self.yrange is None): + self.yrange=[np.min(self.ydata),np.max(self.ydata)] + + plt.ylim(self.yrange[0]*self.axes_unit_conversion[1], + self.yrange[1]*self.axes_unit_conversion[1]) if (self.options['Colorbar']): cbar = plt.colorbar(img,ax=self.ax_act) @@ -302,15 +313,7 @@ def animate(self): #EFIT overplot feature implementation: #It needs to be more generalied in the future as the coordinates are not necessarily in this order: [time_index,spat_index] #This needs to be cross-checked with the time array's dimensions wherever there is a call for a certain index. - if (self.xrange is None): - self.xrange=[np.min(self.xdata),np.max(self.xdata)] - plt.xlim(self.xrange[0]*self.axes_unit_conversion[0], - self.xrange[1]*self.axes_unit_conversion[0]) - - if (self.yrange is None): - self.yrange=[np.min(self.ydata),np.max(self.ydata)] - plt.ylim(self.yrange[0]*self.axes_unit_conversion[1], - self.yrange[1]*self.axes_unit_conversion[1]) + if self.axes_unit_conversion[0] == 1.: plt.xlabel(self.ax_list[0].title(language=self.language)) @@ -363,12 +366,6 @@ def animate_plot(self, it): plot_opt = copy.deepcopy(self.plot_options[0]) self.ax_act.clear() - self.ax_act.set_autoscale_on(False) - - self.ax_act.set_xlim(self.xrange[0]*self.axes_unit_conversion[0], - self.xrange[1]*self.axes_unit_conversion[0]) - self.ax_act.set_ylim(self.yrange[0]*self.axes_unit_conversion[1], - self.yrange[1]*self.axes_unit_conversion[1]) if (self.image_like): try: @@ -380,11 +377,16 @@ def animate_plot(self, it): cmap=self.cmap_obj,vmin=self.vmin, aspect=self.options['Aspect ratio'], interpolation=self.options['Interpolation'], - vmax=self.vmax,origin='lower',**plot_opt) + vmax=self.vmax,origin='lower',**plot_opt) del im except Exception as e: raise e else: + self.ax_act.set_autoscale_on(False) + self.ax_act.set_xlim(self.xrange[0]*self.axes_unit_conversion[0], + self.xrange[1]*self.axes_unit_conversion[0]) + self.ax_act.set_ylim(self.yrange[0]*self.axes_unit_conversion[1], + self.yrange[1]*self.axes_unit_conversion[1]) if (len(self.xdata.shape) == 3 and len(self.ydata.shape) == 3): xgrid, ygrid = flap.tools.grid_to_box(self.xdata[time_index]*self.axes_unit_conversion[0], self.ydata[time_index]*self.axes_unit_conversion[1]) #Same issue, time is not necessarily the first flap.coordinate. @@ -2263,8 +2265,15 @@ def _plot(data_object, gs = gridspec.GridSpecFromSubplotSpec(1, 1, subplot_spec=_plot_id.base_subplot) _plot_id.plt_axis_list = [] _plot_id.plt_axis_list.append(plt.subplot(gs[0,0])) + if _options['Equal axes'] and not coord_x.unit.unit == coord_y.unit.unit: print('Equal axis is not possible. The axes units are not equal.') + + if (xrange is None): + xrange=[np.min(xdata),np.max(xdata)] + if (yrange is None): + yrange=[np.min(ydata),np.max(ydata)] + for it in range(len(tdata)): plt.subplot(_plot_id.base_subplot) ax_act = plt.subplot(gs[0,0]) @@ -2297,20 +2306,10 @@ def _plot(data_object, if (contour_levels is None): contour_levels = 255 ax_act.clear() - ax_act.set_autoscale_on(False) - if (xrange is not None): - plt.xlim(xrange[0]*axes_unit_conversion[0], - xrange[1]*axes_unit_conversion[0]) - else: - plt.xlim(np.min(xdata)*axes_unit_conversion[0], - np.max(xdata)*axes_unit_conversion[0]) - - if (yrange is not None): - plt.ylim(yrange[0]*axes_unit_conversion[1], - yrange[1]*axes_unit_conversion[1]) - else: - plt.ylim(np.min(ydata)*axes_unit_conversion[0], - np.max(ydata)*axes_unit_conversion[0]) + plt.xlim(xrange[0]*axes_unit_conversion[0], + xrange[1]*axes_unit_conversion[0]) + plt.ylim(yrange[0]*axes_unit_conversion[1], + yrange[1]*axes_unit_conversion[1]) _plot_opt = _plot_options[0] if (image_like and (_plot_type == 'anim-image')): @@ -2329,6 +2328,7 @@ def _plot(data_object, except Exception as e: raise e else: + ax_act.set_autoscale_on(False) if (_plot_type == 'anim-image'): #xgrid, ygrid = flap.tools.grid_to_box(xdata*axes_unit_conversion[0], # ydata*axes_unit_conversion[1]) @@ -2567,7 +2567,6 @@ def _plot(data_object, cmap_obj.set_bad(_options['Nan color']) except ValueError: raise ValueError("Invalid color map.") - gs = gridspec.GridSpecFromSubplotSpec(1, 1, subplot_spec=_plot_id.base_subplot) _plot_id.plt_axis_list = [] From ca6a96158f7ca5704da0ece8b1c515b997bd7bd1 Mon Sep 17 00:00:00 2001 From: Mate Lampert Date: Mon, 2 Dec 2019 14:55:27 -0500 Subject: [PATCH 15/27] Bugfix for hanning window The hanning window was first type casted to the data type and then multipied with the data. That made the whole result to flicker. In this new code the typecast is done on the result after the multiplication. --- flap/spectral_analysis.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/flap/spectral_analysis.py b/flap/spectral_analysis.py index f8d24cb..7177473 100644 --- a/flap/spectral_analysis.py +++ b/flap/spectral_analysis.py @@ -462,7 +462,7 @@ def _apsd(d, coordinate=None, intervals=None, options=None): except Exception as e: raise e if (hanning): - data_proc = data_proc * hanning_window + data_proc = data_proc * hanning_window # Calculating APS on natural resolution, full frequency scale dfft = np.fft.fft(data_proc,axis=proc_dim) dps = (dfft.conjugate() * dfft).real @@ -1017,9 +1017,8 @@ def _cpsd(d, ref=None, coordinate=None, intervals=None, options=None): except Exception as e: raise e if (hanning): - data_proc = data_proc * hanning_window.astype(data_proc.dtype) - data_proc_ref = data_proc_ref * hanning_window_ref.astype(data_proc_ref.dtype) - + data_proc = (data_proc * hanning_window).astype(data_proc.dtype) + data_proc_ref = (data_proc_ref * hanning_window_ref).astype(data_proc_ref.dtype) # Calculating FFT dfft = np.fft.fft(data_proc,axis=proc_dim) dfft_ref = np.fft.fft(data_proc_ref,axis=proc_dim_ref) From 28b8386c84a17feec0381c22731ac5b73bbfe799 Mon Sep 17 00:00:00 2001 From: Mate Lampert Date: Wed, 18 Dec 2019 15:33:50 -0500 Subject: [PATCH 16/27] Typo fixes Typo fixes and a whole lot of issues remain unresolved. See the issues for reference --- flap/data_object.py | 6 +++--- flap/spectral_analysis.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/flap/data_object.py b/flap/data_object.py index 7e8cc53..4c5dc10 100644 --- a/flap/data_object.py +++ b/flap/data_object.py @@ -1401,7 +1401,7 @@ def slice_data(self,slicing=None,summing=None,options=None): Dictionary with keys referring to coordinates and values as processing strings. If the processed coordinate changes along multiple dimensions those dimensions will be flattened. - For mean and avereage data errors are calculated as error of independent variables, that is taking the square + For mean and average data errors are calculated as error of independent variables, that is taking the square root of the squared sum of errors. For coordinates the mean of the ranges is taken. Processing strings are the following: @@ -1513,8 +1513,8 @@ def simple_slice_index(slicing, slicing_coord, data_shape, _options): coord_obj.step[0]) if ((type(slicing) is flap.coordinate.Intervals) # Regular slice is possible only with a single interval - and ((slicing.step is None) and (len(slicing.start) == 1) or - (slicing.step is not None) and (slicing.number == 1))): + and ((slicing.step is None) and (len(slicing.start) == 1) or + (slicing.step is not None) and (slicing.number == 1))): if (slicing_coord.step[0] > 0): regular_slice = slice(slicing.start[0], slicing.stop[0], slicing_coord.step[0]) else: diff --git a/flap/spectral_analysis.py b/flap/spectral_analysis.py index 7177473..6a34dfc 100644 --- a/flap/spectral_analysis.py +++ b/flap/spectral_analysis.py @@ -173,7 +173,7 @@ def _spectral_calc_interval_selection(d, ref, coordinate,intervals,interval_n): return flap.coordinate.Intervals(proc_interval_start, proc_interval_end), \ flap.coordinate.Intervals(proc_interval_index_start, proc_interval_index_end), -def trend_removal_func(d,ax, trend, x=None, return_trend=False, return_poly=False): +def trend_removal_func(d, ax, trend, x=None, return_trend=False, return_poly=False): """ This function makes the _trend_removal internal function public """ return _trend_removal(d, ax, trend, x=x, return_trend=return_trend, return_poly=return_poly) @@ -1252,7 +1252,7 @@ def _ccf(d, ref=None, coordinate=None, intervals=None, options=None): ['Poly', n]: Fit an n order polynomial to the data and subtract. Trend removal will be applied to each interval separately. At present trend removal can be applied to 1D CCF only. - 'Normalize': Normalize with autocorrelations, that is calculate correlation instead of + 'Normalize': Normalize with autocorrelations, that is to calculate correlation instead of covariance. 'Verbose': Print progress messages """ @@ -1640,7 +1640,7 @@ def _ccf(d, ref=None, coordinate=None, intervals=None, options=None): = copy.deepcopy(ind_autocorr[0:len(autocorr_index_shape)]) autocorr_mx = corr_binned[tuple(ind_autocorr)] extend_shape = [1] * (len(out_corr.shape) - len(autocorr_mx.shape)) - corr_binned /= np.sqrt(np.reshape(autocorr_mx,tuple(list(autocorr_mx.shape) + extend_shape))) + corr_binned /= np.sqrt(np.reshape(autocorr_mx, tuple(list(autocorr_mx.shape) + extend_shape))) corr_binned /= np.sqrt(np.reshape(autocorr_mx, tuple(extend_shape + list(autocorr_mx.shape)))) else: # We do not have the autocorrelations, calculating From b9cb1b5e02bdd7a50a56f0fffaeeac46499c5162 Mon Sep 17 00:00:00 2001 From: Mate Lampert Date: Wed, 22 Jan 2020 15:29:36 -0500 Subject: [PATCH 17/27] Misc fixes Nothing substantion was modified. --- flap/data_object.py | 2 +- flap/plot copy.py | 2174 ------------------------------------- flap/plot.py | 4 +- flap/spectral_analysis.py | 2 +- flap/tools.py | 1 + 5 files changed, 5 insertions(+), 2178 deletions(-) delete mode 100644 flap/plot copy.py diff --git a/flap/data_object.py b/flap/data_object.py index 4c5dc10..5ecf819 100644 --- a/flap/data_object.py +++ b/flap/data_object.py @@ -3516,7 +3516,7 @@ def get_data_object(self,name,exp_id='*'): nlist.append(n) if len(nlist) == 0: raise KeyError("Data object " + name - + "(exp_id:" + str(exp_id) + ") does not exists.") + + "(exp_id:" + str(exp_id) + ") does not exist.") if (len(nlist) > 1): raise KeyError("Multiple data objects found for name " + name + "(exp_id:" + str(exp_id) + ").") diff --git a/flap/plot copy.py b/flap/plot copy.py deleted file mode 100644 index 95bafeb..0000000 --- a/flap/plot copy.py +++ /dev/null @@ -1,2174 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Sat May 18 18:37:06 2019 - -@author: Zoletnik -@coauthor: Lampert -""" -""" Notes on how to use subplots. - plt.gca() returns the current subplot - to create sub-sub plots use - gs = gridspec.GridSpecFromSubplotSpec(2, 2, subplot_spec=gs0) - Then create the figures (axes): - ax1 = plt.subplot(gs[0, :]) - Use ax1.plot and other calls to plot onto the figure. - - Note on FLAP plotting mechanism. - - The axes paramater of plot() can be the following: - string: Name of a coordinate or __Data__. This latter means the data in the data object is on the axis. - float value: Constant - data object: Enables plotting one data object as a function of another - - When a plot is made parameters are recorded in PlotID. This later enables overplotting - using the same axes. When the axis are incompatble in overplotting they can be forced. As - a link to the data objects involved in the plot are also recorded it is also possible - to regenerate the plot when megnified and 'All points' is False. - - If axis are forced or no axis name and unit is avalilable (like in constant) overplotting is possible - without forcing. If forced the name and unit of the incompatible axes will be set to ''. - - Constants are considered as unit-less, therefore they don't need forcing. - -""" - -import matplotlib.pyplot as plt -import matplotlib.gridspec as gridspec -import matplotlib.colors as colors -import matplotlib.animation as animation -from matplotlib.widgets import Button, Slider -from matplotlib import ticker - -import numpy as np -import copy -from enum import Enum -import math -import time - -try: - import cv2 - cv2_presence = True -except: - print('OpenCV is not present on the computer. Video saving is not available.') - cv2_presence = False - -#from .coordinate import * -#from .tools import * -import flap.config -import flap.tools -import flap.coordinate - -global act_plot_id, gca_invalid - -class PddType(Enum): - Coordinate = 0 - Constant = 1 - Data = 2 - -class PlotDataDescription: - """ Plot axis description for use in PlotID() and plot(). - data_object: The data object from which the data for this coordinate originates. This may - be None if the data is constant. - data_type: PddType - PddType.Coordinate: A coordinate in data_object. - self.value is a flap.Coordinate object. - PddType.Constant: A float constant, stored in value. - PddType.Data: The data in self.data_object. - value: Value, see above - """ - def __init__(self, data_type=None, data_object=None, value=None): - self.data_type = data_type - self.data_object = data_object - self.value = value - -def axes_to_pdd_list(d,axes): - """ Convert a plot() axes parameter to a list of PlotAxisDescription and axes list for PlotID - d: data object - axes: axes parameter of plot() - - return pdd_list, ax_list - """ - if (axes is None): - return [],[] - if (type(axes) is not list): - _axes = [axes] - else: - _axes = axes - - pdd_list = [] - ax_list = [] - for ax in _axes: - if (type(ax) is str): - if (ax != '__Data__'): - try: - coord = d.get_coordinate_object(ax) - except ValueError as e: - raise e - pdd = PlotDataDescription(data_type=PddType.Coordinate, - data_object=d, - value=coord - ) - axx = coord.unit - else: - if (d.data is None): - raise ValueError("No data available for plotting.") - pdd = PlotDataDescription(data_type=PddType.Data, - data_object=d - ) - axx = d.data_unit - elif (type(ax) is type(d)): - if (ax.data is None): - raise ValueError("No data available for axis.") - pdd = PlotDataDescription(data_type=PddType.Data, - data_object=ax - ) - axx = ax.data_unit - else: - try: - val = float(ax) - except ValueError: - raise ValueError("Invalid axis description.") - pdd = PlotDataDescription(data_type=PddType.Constant, - value=val - ) - axx = flap.coordinate.Unit() - pdd_list.append(pdd) - ax_list.append(axx) - return pdd_list,ax_list - -class PlotAnimation: - - def __init__(self, ax_list, axes, d, xdata, ydata, tdata, xdata_range, ydata_range, - cmap_obj, contour_levels, coord_t, coord_x, - coord_y, cmap, options, xrange, yrange, - zrange, image_like, plot_options, language, plot_id, gs): - - self.ax_list = ax_list - self.axes = axes - self.contour_levels = contour_levels - self.cmap = cmap - self.cmap_obj = cmap_obj - self.coord_t = coord_t - self.coord_x = coord_x - self.coord_y = coord_y - self.current_frame = 0. - self.d = d - self.fig = plt.figure(plot_id.figure) - self.gs = gs - self.image_like = image_like - self.language = language - self.options = options - self.pause = False - self.plot_id = plot_id - self.plot_options = plot_options - self.speed = 40. - - self.tdata = tdata - self.xdata = xdata - self.ydata = ydata - - self.xdata_range = xdata_range - self.ydata_range = ydata_range - - self.xrange = xrange - self.yrange = yrange - self.zrange = zrange - - - if (self.contour_levels is None): - self.contour_levels = 255 - - def animate(self): - - #These lines do the coordinate unit conversion - self.axes_unit_conversion=np.zeros(len(self.axes)) - self.axes_unit_conversion[:]=1. - - if self.options['Plot units'] is not None: - unit_length=len(self.options['Plot units']) - if unit_length > 3: - raise ValueError('Only three units are allowed for the three coordinates.') - unit_conversion_coeff={} - for plot_unit_name in self.options['Plot units']: - for index_data_unit in range(len(self.d.coordinates)): - if (plot_unit_name == self.d.coordinates[index_data_unit].unit.name): - data_coordinate_unit=self.d.coordinates[index_data_unit].unit.unit - plot_coordinate_unit=self.options['Plot units'][plot_unit_name] - unit_conversion_coeff[plot_unit_name]=flap.tools.unit_conversion(original_unit=data_coordinate_unit, - new_unit=plot_coordinate_unit) - for index_axes in range(len(self.axes)): - if self.axes[index_axes] in self.options['Plot units']: - self.axes_unit_conversion[index_axes]=unit_conversion_coeff[self.axes[index_axes]] - - pause_ax = plt.figure(self.plot_id.figure).add_axes((0.78, 0.94, 0.1, 0.04)) - self.pause_button = Button(pause_ax, 'Pause', hovercolor='0.975') - self.pause_button.on_clicked(self._pause_animation) - pause_ax._button=self.pause_button - - reset_ax = plt.figure(self.plot_id.figure).add_axes((0.78, 0.89, 0.1, 0.04)) - reset_button = Button(reset_ax, 'Reset', hovercolor='0.975') - reset_button.on_clicked(self._reset_animation) - reset_ax._button=reset_button - - slow_ax = plt.figure(self.plot_id.figure).add_axes((0.88, 0.94, 0.1, 0.04)) - self.slow_button = Button(slow_ax, str(int(1000./(self.speed/0.8)))+'fps', hovercolor='0.975') - self.slow_button.on_clicked(self._slow_animation) - slow_ax._button=self.slow_button - - speed_ax = plt.figure(self.plot_id.figure).add_axes((0.88, 0.89, 0.1, 0.04)) - self.speed_button = Button(speed_ax, str(int(1000./(self.speed*0.8)))+'fps', hovercolor='0.975') - self.speed_button.on_clicked(self._speed_animation) - speed_ax._button=self.speed_button - - - slider_ax = plt.figure(self.plot_id.figure).add_axes((0.1, 0.94, 0.5, 0.04)) - self.time_slider = Slider(slider_ax, label=self.axes[2], - valmin=self.tdata[0]*self.axes_unit_conversion[2], - valmax=self.tdata[-1]*self.axes_unit_conversion[2], - valinit=self.tdata[0]*self.axes_unit_conversion[2]) - self.time_slider.on_changed(self._set_animation) - - plt.subplot(self.plot_id.base_subplot) - - self.ax_act = plt.subplot(self.gs[0,0]) - -#The following lines set the axes to be equal if the units of the axes-to-be-plotted are the same - - axes_coordinate_decrypt=[0] * len(self.axes) - for i_axes in range(len(self.axes)): - for j_coordinate in range(len(self.d.coordinates)): - if (self.d.coordinates[j_coordinate].unit.name == self.axes[i_axes]): - axes_coordinate_decrypt[i_axes]=j_coordinate - for i_check in range(len(self.axes)) : - for j_check in range(i_check+1,len(self.axes)): - if (self.d.coordinates[axes_coordinate_decrypt[i_check]].unit.unit == - self.d.coordinates[axes_coordinate_decrypt[j_check]].unit.unit): - self.ax_act.axis('equal') - - - - time_index = [slice(0,dim) for dim in self.d.data.shape] - time_index[self.coord_t.dimension_list[0]] = 0 - time_index = tuple(time_index) - - act_ax_pos=self.ax_act.get_position() - slider_ax.set_position([act_ax_pos.x0,0.94,0.5,0.04]) - - if (self.zrange is None): - self.zrange=[np.nanmin(self.d.data), - np.nanmax(self.d.data)] - self.vmin = self.zrange[0] - self.vmax = self.zrange[1] - -# if (self.zrange is None): -# self.vmin = np.nanmin(self.d.data[time_index]) -# self.vmax = np.nanmax(self.d.data[time_index]) -# else: -# self.vmin = self.zrange[0] -# self.vmax = self.zrange[1] - - - if (self.vmax <= self.vmin): - raise ValueError("Invalid z range.") - - if (self.options['Log z']): - if (self.vmin <= 0): - raise ValueError("z range[0] cannot be negative or zero for logarithmic scale.") - self.norm = colors.LogNorm(vmin=self.vmin, vmax=self.vmax) - self.locator = ticker.LogLocator(subs='all') - else: - self.norm = None - self.locator = None - - - _plot_opt = self.plot_options[0] - - if (self.image_like): - try: - #There is a problem here, but I cant find it. Image is rotated with 90degree here, but not in anim-image. - #if (self.coord_x.dimension_list[0] == 0): - if (self.coord_x.dimension_list[0] < self.coord_y.dimension_list[0]): - im = np.clip(np.transpose(self.d.data[time_index]),self.vmin,self.vmax) - else: - im = np.clip(self.d.data[time_index],self.vmin,self.vmax) - img = plt.imshow(im,extent=self.xdata_range + self.ydata_range,norm=self.norm, - cmap=self.cmap_obj,vmin=self.vmin,aspect=self.options['Aspect ratio'],interpolation=self.options['Interpolation'], - vmax=self.vmax,origin='lower',**_plot_opt) - del im - except Exception as e: - raise e - else: - if (len(self.xdata.shape) == 3 and len(self.xdata.shape) == 3): - #xgrid, ygrid = flap.tools.grid_to_box(self.xdata[0,:,:],self.ydata[0,:,:]) #Same issue, time is not necessarily the first flap.coordinate. - xgrid, ygrid = flap.tools.grid_to_box(self.xdata[0,:,:]*self.axes_unit_conversion[0],self.ydata[0,:,:]*self.axes_unit_conversion[1]) #Same issue, time is not necessarily the first flap.coordinate. - else: - xgrid, ygrid = flap.tools.grid_to_box(self.xdata*self.axes_unit_conversion[0], - self.ydata*self.axes_unit_conversion[1]) - - im = np.clip(np.transpose(self.d.data[time_index]),self.vmin,self.vmax) - try: - img = plt.pcolormesh(xgrid,ygrid,im,norm=self.norm,cmap=self.cmap,vmin=self.vmin, - vmax=self.vmax,**_plot_opt) - except Exception as e: - raise e - del im - - self.xdata_range=None - self.ydata_range=None - - if (self.options['Colorbar']): - cbar = plt.colorbar(img,ax=self.ax_act) - cbar.set_label(self.d.data_unit.name) -#EFIT overplot feature implementation: - #It needs to be more generalied in the future as the coordinates are not necessarily in this order: [time_index,spat_index] - #This needs to be cross-checked with the time array's dimensions wherever there is a call for a certain index. - if ('EFIT options' in self.options and self.options['EFIT options'] is not None): - - default_efit_options={'Plot limiter': None, - 'Limiter X': None, - 'Limiter Y': None, - 'Limiter 2D': None, - 'Limiter color': 'white', - 'Plot separatrix': None, - 'Separatrix X': None, - 'Separatrix Y': None, - 'Separatrix 2D': None, - 'Separatrix color': 'red', - 'Plot flux': None, - 'Flux XY': None, - 'Flux nlevel': 51} - - self.efit_options=flap.config.merge_options(default_efit_options,self.options['EFIT options'],data_source=self.d.data_source) - self.efit_data={'limiter': {'Time':[],'Data':[]}, - 'separatrix':{'Time':[],'Data':[]}, - 'flux': {'Time':[],'Data':[]}} - - for setting in ['limiter','separatrix']: - if (self.efit_options['Plot '+setting]): - if (((self.efit_options[setting.capitalize()+' X']) and - (self.efit_options[setting.capitalize()+' Y' ])) or - (self.efit_options[setting.capitalize()+' 2D'])): - - if ((self.efit_options[setting.capitalize()+' X']) and - (self.efit_options[setting.capitalize()+' Y'])): - try: - R_object=flap.get_data_object(self.efit_options[setting.capitalize()+' X'],exp_id=self.d.exp_id) - except: - raise ValueError("The objects "+self.efit_options[setting.capitalize()+' X']+" cannot be read.") - try: - Z_object=flap.get_data_object(self.efit_options[setting.capitalize()+' Y'],exp_id=self.d.exp_id) - except: - raise ValueError("The objects "+self.efit_options[setting.capitalize()+' Y']+" cannot be read.") - if (len(R_object.data.shape) != 2 or len(Z_object.data.shape) != 2): - raise ValueError("The "+setting.capitalize()+' Y'+" data is not 1D. Use 2D or modify data reading.") - self.efit_data[setting]['Data']=np.asarray([R_object.data,Z_object.data]) - self.efit_data[setting]['Time']=R_object.coordinate('Time')[0][:,0] #TIME IS NOT ALWAYS THE FIRST COORDINATE, ELLIPSIS CHANGE SHOULD BE IMPLEMENTED - elif (self.efit_options[setting.capitalize()+' XY']): - try: - R_object=flap.get_data_object(self.efit_options[setting.capitalize()+' 2D'],exp_id=self.d.exp_id) - except: - raise ValueError(setting.capitalize()+' 2D data is not available. FLAP data object needs to be read first.') - if R_object.data.shape[2] == 2: - self.efit_data[setting]['Data']=np.asarray([R_object.data[:,:,0],R_object.data[:,:,1]]) - else: - raise ValueError(setting.capitalize()+' XY data needs to be in the format [n_time,n_points,2 (xy)') - self.efit_data[setting]['Time']=R_object.coordinate('Time')[0][:,0,0] #TIME IS NOT ALWAYS THE FIRST COORDINATE, ELLIPSIS CHANGE SHOULD BE IMPLEMENTED - else: - raise ValueError('Both '+setting.capitalize()+' X and'+ - setting.capitalize()+' Y or '+ - setting.capitalize()+' 2D need to be set.') - - for index_coordinate in range(len(self.d.coordinates)): - if ((self.d.coordinates[index_coordinate].unit.name in self.axes) and - (self.d.coordinates[index_coordinate].unit.name != 'Time')): - coordinate_index=index_coordinate - #Spatial unit translation (mm vs m usually) - if (R_object.data_unit.unit != self.d.coordinates[coordinate_index].unit.unit): - try: - coeff_efit_spatial=flap.tools.spatial_unit_translation(R_object.data_unit.unit) - except: - raise ValueError("Time unit translation cannot be made. Check the time unit of the object.") - try: - coeff_data_spatial=flap.tools.spatial_unit_translation(self.d.coordinates[coordinate_index].unit.unit) - except: - raise ValueError("Spatial unit translation cannot be made. Check the time unit of the object.") - #print('Spatial unit translation factor: '+str(coeff_efit_spatial/coeff_data_spatial)) - self.efit_data[setting]['Data'] *= coeff_efit_spatial/coeff_data_spatial - #Time translation (usually ms vs s) - for index_time in range(len(self.d.coordinates)): - if (self.d.coordinates[index_time].unit.name == 'Time'): - time_index_data=index_time - - for index_time in range(len(R_object.coordinates)): - if (self.d.coordinates[index_time].unit.name == 'Time'): - time_index_efit=index_time - - if (R_object.coordinates[time_index_efit].unit.unit != self.d.coordinates[time_index_data].unit.unit): - try: - coeff_efit_time=flap.tools.time_unit_translation(R_object.coordinates[time_index_efit].unit.unit) - except: - raise ValueError("Time unit translation cannot be made. Check the time unit of the object.") - try: - coeff_data_time=flap.tools.time_unit_translation(self.d.coordinates[time_index_data].unit.unit) - except: - raise ValueError("Time unit translation cannot be made. Check the time unit of the object.") - self.efit_data[setting]['Time'] *= coeff_efit_time/coeff_data_time - else: - raise ValueError(setting.capitalize()+' keywords are not set for the data objects.') - - #Interpolating EFIT data for the time vector of the data - if ((self.efit_data[setting]['Data'] != []) and (self.efit_data[setting]['Time'] != [])): - self.efit_data[setting]['Data resampled']=np.zeros([2,self.tdata.shape[0],self.efit_data[setting]['Data'].shape[2]]) - for index_xy in range(0,2): - for index_coordinate in range(0,self.efit_data[setting]['Data'].shape[2]): - self.efit_data[setting]['Data resampled'][index_xy,:,index_coordinate]=np.interp(self.tdata,self.efit_data[setting]['Time'], - self.efit_data[setting]['Data'][index_xy,:,index_coordinate]) - - if (self.efit_options['Plot flux']): - try: - flux_object=flap.get_data_object(self.efit_options['Flux XY'],exp_id=self.d.exp_id) - except: - raise ValueError('Flux XY data is not available. FLAP data object needs to be read first.') - if len(flux_object.data.shape) != 3: - raise ValueError('Flux XY data needs to be a 3D matrix (r,z,t), not necessarily in this order.') - if (flux_object.coordinates[0].unit.name != 'Time'): - raise ValueError('Time should be the first coordinate in the flux data object.') - self.efit_data['flux']['Data']=flux_object.data - self.efit_data['flux']['Time']=flux_object.coordinate('Time')[0][:,0,0] #TIME IS NOT ALWAYS THE FIRST COORDINATE, ELLIPSIS CHANGE SHOULD BE IMPLEMENTED - self.efit_data['flux']['X coord']=flux_object.coordinate(flux_object.coordinates[1].unit.name)[0] - self.efit_data['flux']['Y coord']=flux_object.coordinate(flux_object.coordinates[2].unit.name)[0] - - for index_coordinate in range(len(self.d.coordinates)): - if ((self.d.coordinates[index_coordinate].unit.name in self.axes) and - (self.d.coordinates[index_coordinate].unit.name != 'Time')): - coordinate_index=index_coordinate - #Spatial unit translation (mm vs m usually) - if (flux_object.data_unit.unit != self.d.coordinates[coordinate_index].unit.unit): - try: - coeff_efit_spatial=flap.tools.spatial_unit_translation(flux_object.data_unit.unit) - except: - raise ValueError("Time unit translation cannot be made. Check the time unit of the object.") - try: - coeff_data_spatial=flap.tools.spatial_unit_translation(self.d.coordinates[coordinate_index].unit.unit) - except: - raise ValueError("Spatial unit translation cannot be made. Check the time unit of the object.") - #print('Spatial unit translation factor: '+str(coeff_efit_spatial/coeff_data_spatial)) - self.efit_data['flux']['X coord'] *= coeff_efit_spatial/coeff_data_spatial - self.efit_data['flux']['Y coord'] *= coeff_efit_spatial/coeff_data_spatial - - #Time translation (usually ms vs s) - for index_time in range(len(self.d.coordinates)): - if (self.d.coordinates[index_time].unit.name == 'Time'): - time_index_data=index_time - - for index_time in range(len(flux_object.coordinates)): - if (flux_object.coordinates[index_time].unit.name == 'Time'): - time_index_efit=index_time - - if (flux_object.coordinates[time_index_efit].unit.unit != self.d.coordinates[time_index_data].unit.unit): - try: - coeff_efit_time=flap.tools.time_unit_translation(flux_object.coordinates[time_index_efit].unit.unit) - except: - raise ValueError("Time unit translation cannot be made. Check the time unit of the object.") - try: - coeff_data_time=flap.tools.time_unit_translation(self.d.coordinates[time_index_data].unit.unit) - except: - raise ValueError("Time unit translation cannot be made. Check the time unit of the object.") - self.efit_data['flux']['Time'] *= coeff_efit_time/coeff_data_time - - #Interpolating EFIT data for the time vector of the data - if ((self.efit_data['flux']['Data'] != []) and (self.efit_data['flux']['Time'] != [])): - self.efit_data['flux']['Data resampled']=np.zeros([self.tdata.shape[0], - self.efit_data['flux']['Data'].shape[1], - self.efit_data['flux']['Data'].shape[2]]) - self.efit_data['flux']['X coord resampled']=np.zeros([self.tdata.shape[0], - self.efit_data['flux']['X coord'].shape[1], - self.efit_data['flux']['X coord'].shape[2]]) - self.efit_data['flux']['Y coord resampled']=np.zeros([self.tdata.shape[0], - self.efit_data['flux']['Y coord'].shape[1], - self.efit_data['flux']['Y coord'].shape[2]]) - - for index_x in range(0,self.efit_data['flux']['Data'].shape[1]): - for index_y in range(0,self.efit_data['flux']['Data'].shape[2]): - self.efit_data['flux']['Data resampled'][:,index_x,index_y]=np.interp(self.tdata,self.efit_data['flux']['Time'], - self.efit_data['flux']['Data'][:,index_x,index_y]) - self.efit_data['flux']['X coord resampled'][:,index_x,index_y]=np.interp(self.tdata,self.efit_data['flux']['Time'], - self.efit_data['flux']['X coord'][:,index_x,index_y]) - self.efit_data['flux']['Y coord resampled'][:,index_x,index_y]=np.interp(self.tdata,self.efit_data['flux']['Time'], - self.efit_data['flux']['Y coord'][:,index_x,index_y]) - - - if (self.xrange is not None): - plt.xlim(self.xrange[0]*self.axes_unit_conversion[0],self.xrange[1]*self.axes_unit_conversion[0]) - - if (self.yrange is not None): - plt.ylim(self.yrange[0]*self.axes_unit_conversion[1],self.yrange[1]*self.axes_unit_conversion[1]) - - if self.axes_unit_conversion[0] == 1.: - plt.xlabel(self.ax_list[0].title(language=self.language)) - else: - plt.xlabel(self.ax_list[0].title(language=self.language, new_unit=self.options['Plot units'][self.axes[0]])) - - if self.axes_unit_conversion[1] == 1.: - plt.ylabel(self.ax_list[1].title(language=self.language)) - else: - plt.ylabel(self.ax_list[1].title(language=self.language, new_unit=self.options['Plot units'][self.axes[1]])) - - if (self.options['Log x']): - plt.xscale('log') - if (self.options['Log y']): - plt.yscale('log') - - if self.options['Plot units'] is not None: - if self.axes[2] in self.options['Plot units']: - time_unit=self.options['Plot units'][self.axes[2]] - time_coeff=self.axes_unit_conversion[2] - else: - time_unit=self.coord_t.unit.unit - time_coeff=1. - else: - time_unit=self.coord_t.unit.unit - time_coeff=1. - title = str(self.d.exp_id)+' @ '+self.coord_t.unit.name+'='+"{:10.5f}".format(self.tdata[0]*time_coeff)+\ - ' ['+time_unit+']' - - plt.title(title) - - plt.show(block=False) - self.anim = animation.FuncAnimation(self.fig, self.animate_plot, - len(self.tdata), - interval=self.speed,blit=False) - - def animate_plot(self, it): - - time_index = [slice(0,dim) for dim in self.d.data.shape] - time_index[self.coord_t.dimension_list[0]] = it - time_index = tuple(time_index) - - self.time_slider.eventson = False - self.time_slider.set_val(self.tdata[it]*self.axes_unit_conversion[2]) - self.time_slider.eventson = True - - self.current_frame = it - - plot_opt = copy.deepcopy(self.plot_options[0]) - self.ax_act.clear() - - if (self.image_like): - try: -# if (self.coord_x.dimension_list[0] == 0): - if (self.coord_x.dimension_list[0] < self.coord_y.dimension_list[0]): - im = np.clip(np.transpose(self.d.data[time_index]),self.vmin,self.vmax) - else: - im = np.clip(self.d.data[time_index],self.vmin,self.vmax) - plt.imshow(im,extent=self.xdata_range + self.ydata_range,norm=self.norm, - cmap=self.cmap_obj,vmin=self.vmin, - aspect=self.options['Aspect ratio'], - interpolation=self.options['Interpolation'], - vmax=self.vmax,origin='lower',**plot_opt) - del im - except Exception as e: - raise e - else: - if (len(self.xdata.shape) == 3 and len(self.ydata.shape) == 3): - xgrid, ygrid = flap.tools.grid_to_box(self.xdata[time_index,:,:]*self.axes_unit_conversion[0],self.ydata[time_index,:,:]*self.axes_unit_conversion[1]) #Same issue, time is not necessarily the first flap.coordinate. - else: - xgrid, ygrid = flap.tools.grid_to_box(self.xdata*self.axes_unit_conversion[0],self.ydata*self.axes_unit_conversion[1]) - im = np.clip(np.transpose(self.d.data[time_index]),self.vmin,self.vmax) - try: - plt.pcolormesh(xgrid,ygrid,im,norm=self.norm,cmap=self.cmap,vmin=self.vmin, - vmax=self.vmax,**plot_opt) - except Exception as e: - raise e - del im - if ('EFIT options' in self.options and self.options['EFIT options'] is not None): - for setting in ['limiter','separatrix']: - if (self.efit_options['Plot '+setting]): - self.ax_act.set_autoscale_on(False) - im = plt.plot(self.efit_data[setting]['Data resampled'][0,it,:], - self.efit_data[setting]['Data resampled'][1,it,:], - color=self.efit_options[setting.capitalize()+' color']) - - if (self.efit_options['Plot flux']): - self.ax_act.set_autoscale_on(False) - #im = plt.contour(self.efit_data['flux']['X coord'][it,:,:], - # self.efit_data['flux']['Y coord'][it,:,:], - # self.efit_data['flux']['Data resampled'][it,:,:], - # levels=self.efit_options['Flux nlevel']) - - im = plt.contour(self.efit_data['flux']['X coord resampled'][it,:,:].transpose(), - self.efit_data['flux']['Y coord resampled'][it,:,:].transpose(), - self.efit_data['flux']['Data resampled'][it,:,:], - levels=self.efit_options['Flux nlevel']) - - if (self.xrange is not None): - self.ax_act.set_xlim(self.xrange[0],self.xrange[1]) - if (self.yrange is not None): - self.ax_act.set_ylim(self.yrange[0],self.yrange[1]) - - if self.axes_unit_conversion[0] == 1.: - plt.xlabel(self.ax_list[0].title(language=self.language)) - else: - plt.xlabel(self.ax_list[0].title(language=self.language, new_unit=self.options['Plot units'][self.axes[0]])) - - if self.axes_unit_conversion[1] == 1.: - plt.ylabel(self.ax_list[1].title(language=self.language)) - else: - plt.ylabel(self.ax_list[1].title(language=self.language, new_unit=self.options['Plot units'][self.axes[1]])) - - if self.options['Plot units'] is not None: - if self.axes[2] in self.options['Plot units']: - time_unit=self.options['Plot units'][self.axes[2]] - time_coeff=self.axes_unit_conversion[2] - else: - time_unit=self.coord_t.unit.unit - time_coeff=1. - - title = str(self.d.exp_id)+' @ '+self.coord_t.unit.name+'='+"{:10.5f}".format(self.tdata[it]*time_coeff)+\ - ' ['+time_unit+']' - - self.ax_act.set_title(title) - - def _reset_animation(self, event): - self.anim.event_source.stop() - self.speed = 40. - self.anim = animation.FuncAnimation(plt.figure(self.plot_id.figure), self.animate_plot, - len(self.tdata),interval=self.speed,blit=False) - self.anim.event_source.start() - self.pause = False - - def _pause_animation(self, event): - if self.pause: - self.anim.event_source.start() - self.pause = False - self.pause_button.label.set_text("Pause") - else: - self.anim.event_source.stop() - self.pause = True - self.pause_button.label.set_text("Start") - - def _set_animation(self, time): - self.anim.event_source.stop() - frame=(np.abs(self.tdata*self.axes_unit_conversion[2]-time)).argmin() - self.anim = animation.FuncAnimation(plt.figure(self.plot_id.figure), self.animate_plot, - frames=np.arange(frame,len(self.tdata)-1), - interval=self.speed,blit=False) - self.anim.event_source.start() - self.pause = False - - def _slow_animation(self, event): - self.anim.event_source.stop() - self.speed=self.speed/0.8 - self.anim = animation.FuncAnimation(plt.figure(self.plot_id.figure), self.animate_plot, - frames=np.arange(self.current_frame,len(self.tdata)-1), - interval=self.speed,blit=False) - self.speed_button.label.set_text(str(int(1000./(self.speed*0.8)))+'fps') - self.slow_button.label.set_text(str(int(1000./(self.speed/0.8)))+'fps') - self.anim.event_source.start() - self.pause = False - - def _speed_animation(self, event): - self.anim.event_source.stop() - self.speed=self.speed*0.8 - self.anim = animation.FuncAnimation(plt.figure(self.plot_id.figure), self.animate_plot, - frames=np.arange(self.current_frame,len(self.tdata)-1), - interval=self.speed,blit=False) - self.speed_button.label.set_text(str(int(1000./(self.speed*0.8)))+'fps') - self.slow_button.label.set_text(str(int(1000./(self.speed/0.8)))+'fps') - self.anim.event_source.start() - self.pause = False - -class PlotID: - def __init__(self): - # The figure number where the plto resides - self.figure = None - # The subplot containing the whole plot - self.base_subplot = None - # The plot type string - self.plot_type = None - # Subtype is dependent on the plot type. It marks various versions, e.g. real-complex - self.plot_subtype = None - # Number of plot calls which generated this - self.number_of_plots = 0 - # The axes list. Each element is a flap.Unit and describes one axis of the plot. - self.axes = None - # The description of the axis data. This is a list of self.number_of_plots lists. Each inner list - # is a list of PlotDataDescriptions. - self.plot_data = [] - # The list of Axes which can be used for plotting into the individual plots. If there is only a single - # plot self.base_subplot is the same as plt_axis_list[0] - self.plt_axis_list = None - # These are a list of the options to plot - self.options = [] - - def clear(self): - """ Clears the parameters of the plot, but does not clear the - base_subplot and figure - """ - self.plot_type = None - self.plot_subtype = None - self.number_of_plots = 0 - self.axes = None - self.plot_data = [] - self.plt_axis_list = None - self.options = [] - - -# def __del__(self): -# if (self.plt_axis_list is not None): -# for ax in self.plt_axis_list: -# ax.remove() - - def check_axes(self,d, axes, clear=True, default_axes=None, force=False): - """ Checks whether the required plot axes are correct, present and compatible with the self PlotID. - In case of problems raises ValueError. - INPUT: - d: data object - axes: List of the required axes or None as input to plot() - clear: (bool) If True the plot will be cleared, therefore the axes in the PlotID are irrelevant. - default_axes: The default axes desired for this type of plot. (strings) - force: (bool) Force accept incompatibe axes - Return value: - pdd_list, ax_list - pdd_list: list of PlotDataDescription objects which can be used to generate the plot. - ax_list: axis list which can be put into axes in self. - """ - if (axes is not None): - if (type(axes) is not list): - _axes = [axes] - else: - _axes = axes - else: - _axes = [] - - # Converting to PDD and axes list - try: - pdd_list,ax_list = axes_to_pdd_list(d,_axes) - except ValueError as e: - raise e - # Adding default axes if not all are specified - if (len(_axes) < len(default_axes)): - # Determining default axes for the ones which are not in _axes: - # either the ones in the plot or defaults - if (not clear and (self.axes is not None)): - # Using the axis in the plot - plot_axes = [''] * (len(default_axes) - len(_axes)) - for i,ax in enumerate(self.axes[len(_axes):len(default_axes)]): - # If axis in plot has no name then using default axis else the plot axis - if (ax.name == ''): - plot_axes[i] = default_axes[i] - else: - if (ax.name == d.data_unit.name): - plot_axes[i] = '__Data__' - else: - plot_axes[i] = ax.name - try: - pdd_def,plot_def_axes = axes_to_pdd_list(d,plot_axes) - except ValueError as e: - for axname in plot_axes: - if (axname in str(e)): - raise ValueError("Plot coordinate '"+axname+"' not found neither as coordinate nor data name. Must specifiy axes and use option={'Force axes':True} to overplot.") - else: - # If overplotting or no plot is available then using the default - try: - pdd_def, plot_def_axes = axes_to_pdd_list(d,default_axes[len(_axes):]) - except ValueError as e: - raise e - ax_list += plot_def_axes - pdd_list += pdd_def - - - ax_out_list = [] - # Checking whether the units of this plot are consistent with the present axes - if (not clear and (self.axes is not None)): - for ax_in,ax_plot in zip(ax_list, self.axes): - # If none of the plot and the new axis has name and ot unit, the new axis will not have it either -# if ((ax_plot.name == '') or (ax_plot.unit == '') or -# (ax_in.name == '') or (ax_in.unit == '')): -# ax_out_list.append(Unit()) - if ((ax_in.name != ax_plot.name) or (ax_in.unit != ax_plot.unit)): - if (force): - u = flap.coordinate.Unit() - if (ax_in.name == ax_plot.name): - u.name = ax_in.name - elif ((ax_in.name is None) or (ax_in.name == '')): - u.name = ax_plot.name - elif ((ax_plot.name is None) or (ax_plot.name == '')): - u.name = ax_in.name - if (ax_in.unit == ax_plot.unit): - u.unit = ax_in.unit - elif ((ax_in.unit is None) or (ax_in.unit == '')): - u.unit = ax_plot.unit - elif ((ax_plot.unit is None) or (ax_plot.unit == '')): - u.unit = ax_in.unit - ax_out_list.append(u) - continue - raise ValueError("Axis '"+ax_in.name+"' is incompatible with plot. Use option={'Force axes':True}") - else: - ax_out_list.append(ax_in) - else: - ax_out_list = ax_list - return pdd_list, ax_out_list - -def set_plot_id(plot_id): - """ - Set the current plot. - """ - global act_plot_id, gca_invalid - - if ((plot_id is not None) and (type(plot_id) is not PlotID)): - raise ValueError("flap.set_plot_id should receive a valid flap PlotID or None") - act_plot_id = plot_id - if (act_plot_id is None): - gca_invalid = False - else: - if (plot_id.plt_axis_list is None): - gca_invalid = False - else: - if (plt.gca() != plot_id.plt_axis_list[-1]): - gca_invalid = True - else: - gca_invalid = False - -def get_plot_id(): - """ - Return the current PlotID or None if no act plot. - """ - global act_plot_id, gca_invalid - try: - return act_plot_id - except NameError: - gca_invalid = False - return None - -def __get_gca_invalid(): - """ This returns the global gca_invalid flag. - This flag is set invalid when set_plot changes the default plot and this way - the Matplotlib current axis (get_gca()) becomes invalid for this plot. - """ - try: - gca_invalid - except NameError: - gca_invalid = False - return gca_invalid - - -def sample_for_plot(x,y,x_error,y_error,n_points): - """ - Resamples the y(x) function to np points for plotting. - This is useful for plotting large arrays in a way that short outlier pulses - are still indicated. - The original function is divided into np equal size blocks in x and in each block - the minimum and maximum is determined. The output will contain 2*np number - or points. Each consecutive point pair contains the minimum and maximum in - a block, the time is the centre time of the block. - - x: Input x array. - y: input y array. - np: Desired number of blocks. This would be a number larger than the number - of pixels in the plot in the horizontal direction. - - Return values: - x_out, y_out - If the box length is less than 5 the original data will be returned. - """ - - binlen = int(len(x) / n_points) - if (binlen < 5): - return x,y,x_error,y_error - nx = int(len(x) / binlen) - _y = y[0:nx * binlen] - _y = np.reshape(_y, (nx,binlen)) - y_bin = np.empty(nx * 4, dtype=type(y)) - ind = np.arange(nx) * 4 - y_bin[ind + 1] = np.amin(_y, 1) - y_bin[ind + 2] = np.amax(_y, 1) - y_bin[ind] = np.mean(_y, 1) - y_bin[ind + 3] = y_bin[ind] - x_bin = np.empty(nx * 4, dtype=type(x)) - ind1 = np.arange(nx) * binlen + int(binlen / 2) - x_bin[ind] = x[ind1] - x_bin[ind + 1] = x[ind1] - x_bin[ind + 2] = x[ind1] - x_bin[ind + 3] = x[ind1] - - if ((x_error is not None) or (y_error is not None)): - ind = np.arange(nx,dtype=np.int32) * binlen + binlen // 2 - if (x_error is not None): - if (x_error.ndim == 1): - x_error_bin = np.empty(x_bin.size,dtype=x_error.dtype) - for i in range(4): - x_error_bin[slice(i, nx * 4 + i, 4)] = x_error[ind] - else: - x_error_bin = np.empty((2,x_bin.size),dtype=x_error.dtype) - for i in range(4): - x_error_bin[0,slice(i, nx * 4 + i, 4)] = x_error[0,ind] - x_error_bin[1,slice(i, nx * 4 + i, 4)] = x_error[1,ind] - else: - x_error_bin = None - - if (y_error is not None): - if (y_error.ndim == 1): - y_error_bin = np.empty(y_bin.size,dtype=y_error.dtype) - for i in range(4): - y_error_bin[slice(i, nx * 4 + i, 4)] = y_error[ind] - else: - y_error_bin = np.empty((2,x_bin.size),dtype=y_error.dtype) - for i in range(4): - y_error_bin[0,slice(i, nx * 4 + i, 4)] = y_error[0,ind] - y_error_bin[1,slice(i, nx * 4 + i, 4)] = y_error[1,ind] - else: - y_error_bin = None - - return x_bin, y_bin, x_error_bin, y_error_bin - -def _plot(data_object, - axes=None, - slicing=None, - summing=None, - slicing_options=None, - options=None, - plot_type=None, - plot_options={}, - plot_id=None): - """ - Plot a data object. - axes: A list of coordinate names (strings). They should be one of the coordinate - names in the data object or 'Data' - They describe the axes of the plot. - If the number of axes is less than the number required for the plot, '__Data__' will be added. - If no axes are given default will be used depending on the plot type. E.g. for - x-y plot the default first axis is the firs coordinate, the second axis is '__Data__' - slicing, summing: arguments for slice_data. Slicing will be applied before plotting. - slicing_options: options for slicing. See slice_data() - plot_type: The plot type (string). Can be abbreviated. - 'xy': Simple 1D plot. Default axes are: first coordinate, Data. For complex signals this - produces two plots, see option "Complex mode'. - 'multi xy': In case of 2D data plots 1D curves with vertical shift - Default x axis is first coordinate, y axis is Data - The signals are named in the label with the 'Signal name' coordinate or the one - named in options['Signal name'] - 'image': Plots a 2D data matrix as an image. Options: Colormap, Data range, ... - 'contour': Contour plot - plot_options: Dictionary or list of dictionaries. Will be passed over to the plot call. For plots with multiple subplots this can be - a list of dictionaries, each for one subplot. - plot_id: A PlotID object is the plot should go into an existing plot. - options: - 'Error' True: Plot all error bars (default: True) - False: Do not plot errors - number > 0: Plot this many error bars in plot - 'Y separation' Vertical separation of curves in multi xy plot. For linear scale this will - be added to consecutive cureves. For Log scale consecutive curves will be - multiplied by this. - 'Log x' : Logscale X axis - 'Log y' : Logscale Y axis - 'All points' True or False - Default is False. If True will plot all points otherwise will plot only a reduced - number of points (see maxpoints). Each plotted point will be the mean of data - in a box and a vertical bar will indicate the value range in each box. - 'Maxpoints': The maximum number of data points plotted. Above this only this many points will - be plotted if "All points" is False. - 'Complex mode': 'Amp-phase': Plot amplitude and phase - 'Real-imag': Plot real and imaginary part - 'X range','Y range': Axes ranges. (List of two numbers.) - 'Z range': Range of the vertical axis. (List of two numbers.) - 'Colormap': Cmap name for image and contour plots. - 'Levels': Number of contour levels or array of levels. - 'Aspect ratio': 'equal', 'auto' or float. (See imshow) - 'Waittime' : Time to wait [Seconds] between two images in anim-... type plots - 'Video file': Name of output video file for anim-... plots - 'Video framerate': Frame rate for output video. - 'Video format': Format of the video. Valid: 'avi' - 'Clear': Boolean. If True don't use the existing plots, generate new. (No overplotting.) - 'Force axes': Force overplotting even if axes are incpomatible - 'Colorbar': Boolelan. Switch on/off colorbar - 'Nan color': The color to use in image data plotting for np.nan (not-a-number) values - 'Interpolation': Interpolation method for image plot. - 'Language': Language of certain standard elements in the plot. ('EN', 'HU') - 'EFIT options': Dictionary of EFIT plotting options: - 'Plot separatrix': Set to plot the separatrix onto the video - 'Separatrix X': Name of the flap.DataObject for the separatrix X data (usually R) - 'Separatrix Y': Name of the flap.DataObject for the separatrix Y data (usually z) - 'Separatrix XY': Name of the 2D flap.DataObject for the separatrix XY data (usually Rz) - 'Separatrix color': Color of the separatrix for the plot - 'Plot limiter': Set to plot the limiter onto the video - 'Limiter X': Name of the flap.DataObject for the limiter X data (usually R) - 'Limiter Y': Name of the flap.DataObject for the limiter Y data (usually z) - 'Limiter XY': Name of the 2D flap.DataObject for the limiter XY data (usually Rz) - 'Limiter color': Color of the limiter for the plot - 'Plot flux surfaces': Name of the 2D flap.DataObject for the flux surfaces - (should have the same coordinate names as the plot) - 'nlevels': Number of contour lines for the flux surface plotting - 'Prevent saturation': Prevents saturation of the video signal when it exceeds zrange[1] - It uses data modulo zrange[1] to overcome the saturation. (works for animation) - - """ - - default_options = {'All points': False, 'Error':True, 'Y separation': None, - 'Log x': False, 'Log y': False, 'Log z': False, 'maxpoints':4000, 'Complex mode':'Amp-phase', - 'X range':None, 'Y range': None, 'Z range': None,'Aspect ratio':'auto', - 'Clear':False,'Force axes':False,'Language':'EN','Maxpoints': 4000, - 'Levels': 10, 'Colormap':None, 'Waittime':1,'Colorbar':True,'Nan color':None, - 'Interpolation':'bilinear','Video file':None, 'Video framerate': 20,'Video format':'avi', - 'EFIT options':None, 'Prevent saturation':False, 'Plot units':None, - } - _options = flap.config.merge_options(default_options, options, data_source=data_object.data_source, section='Plot') - - if (plot_options is None): - _plot_options = {} - else: - _plot_options = plot_options - if (type(_plot_options) is not list): - _plot_options = [_plot_options] - - - if (type(_options['Clear']) is not bool): - raise TypeError("Invalid type for option Clear. Should be boolean.") - if (type(_options['Force axes']) is not bool): - raise TypeError("Invalid type for option 'Force axes'. Should be boolean.") - - if ((slicing is not None) or (summing is not None)): - d = data_object.slice_data(slicing=slicing, summing=summing, options=slicing_options) - else: - d = data_object - - # Determining a PlotID: - # argument, actual or a new one - if (type(plot_id) is PlotID): - _plot_id = plot_id - else: - _plot_id = get_plot_id() - if (_plot_id is None): - # If there is no actual plot we create a new one - _plot_id = PlotID() - _plot_id.figure = plt.gcf().number - _plot_id.base_subplot = plt.gca() - if (_plot_id.plt_axis_list is not None): - if ((_plot_id.plt_axis_list[-1] != plt.gca()) or (_plot_id.figure != plt.gcf().number)): - # If the actual subplot is not the one in the plot ID then either the subplot was - # changed to a new one or the plot_ID changed with set_plot. - if (not __get_gca_invalid()): - # This means the plot ID was not changed, the actual plot or axis was changed. - # Therefore we need to use the actual values - _plot_id = PlotID() - _plot_id.figure = plt.gcf().number - _plot_id.base_subplot = plt.gca() - plt.cla() - if (_options['Clear'] ): - _plot_id = PlotID() -# _plot_id.clear() - if (_plot_id.figure is not None): - plt.figure(_plot_id.figure) - else: - _plot_id.figure = plt.gcf().number - if (_plot_id.base_subplot is not None): - plt.subplot(_plot_id.base_subplot) - else: - _plot_id.base_subplot = plt.gca() - plt.cla() - - # Setting plot type - known_plot_types = ['xy','scatter','multi xy', 'image', 'anim-image','contour','anim-contour','animation'] - if (plot_type is None): - if (len(d.shape) == 1): - _plot_type = 'xy' - elif (len(d.shape) == 2): - _plot_type = 'multi xy' - elif (len(d.shape) == 3): - _plot_type = 'anim-image' - else: - raise ValueError("No default plot type for this kind of data, set plot_type.") - else: - try: - _plot_type = flap.tools.find_str_match(plot_type,known_plot_types) - except TypeError: - raise TypeError("Invalid type for plot_type. String is expected.") - except ValueError: - raise ValueError("Unknown plot type or too short abbreviation") - - # Processing some options - if ((_plot_type == 'xy') or (_plot_type == 'multi xy')): - all_points = _options['All points'] - if (type(all_points) is not bool): - raise TypeError("Option 'All points' should be boolean.") - else: - all_points = True - plot_error = _options['Error'] - if (type(plot_error) is not bool): - try: - if (int(plot_error) <= 0): - raise ValueError("Invalid number of error bars in plot (option: Error).") - errorbars = int(plot_error) - plot_error = True - except: - raise ValueError("Invalid 'Error' option.") - else: - errorbars = -1 - # The maximum number of points expected in the horizontal dimension of a plot - try: - maxpoints = int(_options['Maxpoints']) - except ValueError: - raise ValueError("Invalid maxpoints setting.") - - try: - compt = flap.tools.find_str_match(_options['Complex mode'], ['Amp-phase','Real-imag']) - except: - raise ValueError("Invalid 'Complex mode' option:" +_options['Complex mode']) - if (compt == 'Amp-phase'): - comptype = 0 - elif (compt == 'Real-imag'): - comptype = 1 - if (_plot_id.number_of_plots > 0): - if (_plot_id.options[-1]['Complex mode'] != _options['Complex mode']): - raise ValueError("Different complex plot mode in overplot.") - - language = _options['Language'] - - if (_options['Video file'] is not None): - if (_options['Video format'] == 'avi'): - video_codec_code = 'XVID' - else: - raise ValueError("Cannot write video in format '"+_options['Video format']+"'.") - - - # X range and Z range is processed here, but Y range not as it might have multiple entries for some plots - xrange = _options['X range'] - if (xrange is not None): - if ((type(xrange) is not list) or (len(xrange) != 2)): - raise ValueError("Invalid X range setting.") - zrange = _options['Z range'] - if (zrange is not None): - if ((type(zrange) is not list) or (len(zrange) != 2)): - raise ValueError("Invalid Z range setting.") - - cmap = _options['Colormap'] - if ((cmap is not None) and (type(cmap) is not str)): - raise ValueError("Colormap should be a string.") - - contour_levels = _options['Levels'] - # Here _plot_id is a valid (maybe empty) PlotID - - if ((_plot_type == 'xy') or (_plot_type == 'scatter')): - # 1D plots: xy, scatter and complex versions - # Checking whether oveplotting into the same plot type - if ((d.data is not None) and (d.data.dtype.kind == 'c')): - subtype = 1 - else: - subtype = 0 - if (not _options['Clear']): - if ((_plot_id.plot_type is not None) and ((_plot_id.plot_type != 'xy') and (_plot_id.plot_type != 'scatter')) - or (_plot_id.plot_subtype is not None) and (_plot_id.plot_subtype != subtype)): - raise ValueError("Overplotting into different plot type. Use option={'Clear':True} to erase first.") - # Processing axes - default_axes = [d.coordinates[0].unit.name, '__Data__'] - try: - pdd_list, ax_list = _plot_id.check_axes(d, - axes, - clear=_options['Clear'], - default_axes=default_axes, - force=_options['Force axes']) - except ValueError as e: - raise e - # Preparing data - plotdata = [0]*2 - plotdata_low = [0]*2 #UNUSED - plotdata_high = [0]*2 #UNUSED - ploterror = [0]*2 - for ax_ind in range(2): - if (pdd_list[ax_ind].data_type == PddType.Data): - if (len(pdd_list[ax_ind].data_object.shape) > 1): - raise ValueError("xy plot is applicable only to 1D data. Use slicing.") - plotdata[ax_ind] = pdd_list[ax_ind].data_object.data.flatten() - if (plot_error): - ploterror[ax_ind] = pdd_list[ax_ind].data_object._plot_error_ranges() - else: - ploterror[ax_ind] = None - elif (pdd_list[ax_ind].data_type == PddType.Coordinate): - if (not pdd_list[ax_ind].value.isnumeric()): - raise ValueError("Coordinate is not of numeric type, cannot plot.") - pdata, pdata_low, pdata_high = \ - pdd_list[ax_ind].value.data(data_shape=pdd_list[ax_ind].data_object.shape) - plotdata[ax_ind] = pdata.flatten() - if (pdata_low is not None): - pdata_low = pdata_low.flatten() - if (pdata_high is not None): - pdata_high = pdata_high.flatten() - if (plot_error): - ploterror[ax_ind] = pdd_list[ax_ind].data_object._plot_coord_ranges(pdd_list[ax_ind].value, - pdata, - pdata_low, - pdata_high - ) - else: - ploterror[ax_ind] = None - elif (pdd_list[ax_ind].data_type == PddType.Constant): - plotdata[ax_ind] = pdd_list[ax_ind].value - ploterror[ax_ind] = None - else: - raise RuntimeError("Internal error, invalid PlotDataDescription.") - if (_plot_id.number_of_plots != 0): - _options['Log x'] = _plot_id.options[-1]['Log x'] - _options['Log y'] = _plot_id.options[-1]['Log y'] - - - xdata = plotdata[0] - xerror = ploterror[0] - - if (subtype == 0): - yrange = _options['Y range'] - if (yrange is not None): - if ((type(yrange) is not list) or (len(yrange) != 2)): - raise ValueError("Invalid Y range setting.") - - ax = _plot_id.base_subplot - _plot_id.plt_axis_list = [ax] - - ydata = plotdata[1] - yerror = ploterror[1] - # Checking for constants, converting to numpy array - if (np.isscalar(ydata)): - if (np.isscalar(xdata)): - ydata = np.full(1, ydata) - else: - ydata = np.full(xdata.size, ydata) - yerror = None - if (np.isscalar(xdata)): - if (np.isscalar(ydata)): - xdata = np.full(1, xdata) - else: - xdata = np.full(ydata.size, xdata) - xerror = None - - if (all_points is True): - x = xdata - y = ydata - xerr = xerror - yerr = yerror - else: - x,y,xerr,yerr = sample_for_plot(xdata,ydata,xerror,yerror,maxpoints) - if (errorbars < 0): - errorevery = 1 - else: - errorevery = int(round(len(x)/errorbars)) - if (errorevery == 0): - errorevery = 1 - - _plot_opt = _plot_options[0] - if (type(_plot_opt) is not dict): - raise ValueError("Plot options should be a dictionary or list of dictionaries.") - - if (_plot_type == 'xy'): - if (plot_error): - ax.errorbar(x,y,xerr=xerr,yerr=yerr,errorevery=errorevery,**_plot_opt) - else: - ax.plot(x,y,**_plot_opt) - else: - if (plot_error): - ax.errorbar(x,y,xerr=xerr,yerr=yerr,errorevery=errorevery,fmt='o',**_plot_opt) - else: - ax.scatter(x,y,**_plot_opt) - - ax.set_xlabel(ax_list[0].title(language=language)) - ax.set_ylabel(ax_list[1].title(language=language)) - if (_options['Log x']): - ax.set_xscale('log') - if (_options['Log y']): - ax.set_yscale('log') - if (xrange is not None): - ax.set_xlim(xrange[0],xrange[1]) - if (yrange is not None): - ax.set_ylim(yrange[0],yrange[1]) - - title = ax.get_title() - if (title is None): - title = '' - if (title[-3:] != '...'): - newtitle = '' - if (d.exp_id is not None): - newtitle += str(d.exp_id) + ' ' - if (d.data_title is not None): - newtitle += d.data_title - if (len(newtitle) != 0): - if (len(title + newtitle) < 40): - if (title != ''): - title += ','+newtitle - else: - title = newtitle - else: - title += ',...' - ax.set_title(title) - - _plot_id.plot_subtype = 0 # real xy plot - # End of real xy and scatter plot - else: - # Complex xy plot - yrange = _options['Y range'] - if (yrange is not None): - if ((type(yrange) is not list) or (len(yrange) != 2)): - raise ValueError("Invalid Y range setting.") - if ((type(yrange[0]) is list) and (type(yrange[1]) is list) and - ((len(yrange[0]) == 2)) and (len(yrange[1]) == 2)): - pass - else: - try: - yrange = [float(yrange[0]), float(yrange[1])] - except ValueError: - raise ValueError("Invalid Y range setting. For complex xy plot either a list or list of two lists can be used.") - yrange = [yrange,yrange] - - # Complex xy and scatter plot - # Creating two sublots if this is a new plot - if (_plot_id.number_of_plots == 0): - gs = gridspec.GridSpecFromSubplotSpec(2, 1, subplot_spec=_plot_id.base_subplot) - _plot_id.plt_axis_list = [] - _plot_id.plt_axis_list.append(plt.subplot(gs[0,0])) - _plot_id.plt_axis_list.append(plt.subplot(gs[1,0],sharex=_plot_id.plt_axis_list[0])) - # Taking the mean of the complex errors - if (ploterror[1] is not None): - if (type(ploterror[1]) is list): - yerror_abs = (np.abs(ploterror[1][0]) + np.abs(ploterror[1][1])) / 2 - else: - yerror_abs = np.abs(ploterror[1]) - else: - yerror_abs = None - for i_comp in range(2): - ax = _plot_id.plt_axis_list[i_comp] - if (comptype == 0): - # amp-phase - if (i_comp == 0): - ydata = np.abs(plotdata[1]) - yerror = yerror_abs - else: - ydata_abs = np.abs(plotdata[1]) - ydata = np.angle(plotdata[1]) - if (yerror_abs is not None): - yerror = np.empty(ydata.shape,dtype=float) - ind = np.nonzero(ydata_abs <= yerror_abs)[0] - if (ind.size > 0): - yerror[ind] = math.pi - ind = np.nonzero(ydata_abs > yerror_abs)[0] - if (ind.size > 0): - yerror[ind] = np.arctan2(yerror_abs[ind],ydata_abs[ind]) - else: - yerror = None - else: - # real-imag - if (i_comp == 0): - ydata = np.real(plotdata[1]) - yerror = yerror_abs - else: - ydata = np.imag(plotdata[1]) - yerror = yerror_abs - - # Checking for constants, converting to numpy array - if (np.isscalar(ydata)): - if (np.isscalar(xdata)): - ydata = np.full(1, ydata) - else: - ydata = np.full(xdata.size, ydata) - if (plot_error): - yerror = d._plot_coord_ranges(c, ydata, ydata_low, ydata_high) #THESE ARE UNDEFINED - else: - yerror = None - if (np.isscalar(xdata)): - if (np.isscalar(ydata)): - xdata = np.full(1, xdata) - else: - xdata = np.full(ydata.size, xdata) - if (plot_error): - xerror = d._plot_coord_ranges(c, xdata, xdata_low, xdata_high) #THESE ARE UNDEFINED - else: - xerror = None - - if (all_points is True): - x = xdata - y = ydata - xerr = xerror - yerr = yerror - else: - x,y,xerr,yerr = sample_for_plot(xdata,ydata,xerror,yerror,maxpoints) - if (errorbars < 0): - errorevery = 1 - else: - errorevery = int(round(len(x)/errorbars)) - if (errorevery == 0): - errorevery = 1 - - _plot_opt = _plot_options[0] - if (type(_plot_opt) is list): - _plot_opt[i_comp] - if (type(_plot_opt) is not dict): - raise ValueError("Plot options should be a dictionary or list of dictionaries.") - - if (_plot_type == 'xy'): - if (plot_error): - ax.errorbar(x,y,xerr=xerr,yerr=yerr,errorevery=errorevery,**_plot_opt) - else: - ax.plot(x,y,**_plot_opt) - else: - if (plot_error): - ax.errorbar(x,y,xerr=xerr,yerr=yerr,errorevery=errorevery,fmt='o',**_plot_opt) - else: - ax.scatter(x,y,**_plot_opt) - - # Setting axis labels - ax.set_xlabel(ax_list[0].title(language=language)) - ax.set_ylabel(ax_list[1].title(language=language,complex_txt=[comptype,i_comp])) - - if (_options['Log x']): - ax.set_xscale('log') - if (_options['Log y']): - ax.set_yscale('log') - if (xrange is not None): - ax.set_xlim(xrange[0],xrange[1]) - if (yrange is not None): - ax.set_ylim(yrange[i_comp][0],yrange[i_comp][1]) - title = ax.get_title() - if (title is None): - title = '' - if (title[-3:] != '...'): - newtitle = '' - if (d.exp_id is not None): - newtitle += str(d.exp_id) + ' ' - if (d.data_title is not None): - newtitle += d.data_title - if (len(newtitle) != 0): - if (len(title + newtitle) < 40): - if (title != ''): - title += ','+newtitle - else: - title = newtitle - else: - title += ',...' - ax.set_title(title) - - _plot_id.plot_subtype = 1 - # End of complex xy and scatter plot - - elif (_plot_type == 'multi xy'): - if (len(d.shape) > 2): - raise TypeError("multi x-y plot is applicable to 2D data only. Use slicing.") - if (len(d.shape) != 2): - raise TypeError("multi x-y plot is applicable to 2D data only.") - if (d.data.dtype.kind == 'c'): - raise TypeError("multi x-y plot is applicable only to real data.") - if ((axes is not None) and (type(axes) is list) and (len(axes) > 1)): - raise ValueError("For multi xy plot only one axis can be given.") - - yrange = _options['Y range'] - if (yrange is not None): - if ((type(yrange) is not list) or (len(yrange) != 2)): - raise ValueError("Invalid Y range setting.") - - # Processing axes - default_axes = [d.coordinates[0].unit.name, '__Data__'] - try: - pdd_list, ax_list = _plot_id.check_axes(d, - axes, - clear=_options['Clear'], - default_axes=default_axes, - force=_options['Force axes']) - except ValueError as e: - raise e - - if (pdd_list[0].data_type == PddType.Coordinate): - coord = pdd_list[0].value - if (len(coord.dimension_list) != 1): - raise ValueError("Plotting coordinate for multi xy plot should change only along 1 dimension.") - axis_index = coord.dimension_list[0] - if (not ((pdd_list[1].data_type == PddType.Data) and (pdd_list[1].data_object == d))): - raise ValueError("For multi xy plot only data can be plotted on the y axis.") - - # Trying to get signal names - try: - signals_coord = d.get_coordinate_object('Signal name') - except ValueError: - signals_coord = None - if((signals_coord is not None) and (len(signals_coord.dimension_list) == 1)\ - and (signals_coord.dimension_list[0] != axis_index)): - if (axis_index == 0): - index = [0,...] - else: - index = [...,0] - signal_names, l, h = signals_coord.data(data_shape=d.shape,index=index) - signal_names = signal_names.flatten() - else: - signal_names = None - - # Getting x data - if (pdd_list[0].data_type == PddType.Data): - xdata = pdd_list[0].data_object.data.flatten() - if (plot_error): - xerror = pdd_list[0].data_object._plot_error_ranges() - else: - xerror = None - elif (pdd_list[0].data_type == PddType.Coordinate): - if (not pdd_list[0].value.isnumeric()): - raise ValueError("Coordinate is not of numeric type, cannot plot.") - index = [0]*2 - index[axis_index] = ... - xdata, xdata_low, xdata_high = \ - pdd_list[0].value.data(data_shape=pdd_list[0].data_object.shape,index=index) - xdata = xdata.flatten() - if (xdata_low is not None): - xdata_low = xdata_low.flatten() - if (xdata_high is not None): - xdata_high = xdata_high.flatten() - if (plot_error): - xerror = pdd_list[0].data_object._plot_coord_ranges(pdd_list[0].value, - xdata, - xdata_low, - xdata_high - ) - else: - xerror = None - elif (pdd_list[0].data_type == PddType.Constant): - xdata = pdd_list[ax_ind].value - xerror = None - else: - raise RuntimeError("Internal error, invalid PlotDataDescription.") - - if (_plot_id.number_of_plots != 0): - _options['Log x'] = _plot_id.options[-1]['Log x'] - _options['Log y'] = _plot_id.options[-1]['Log y'] - - ax = _plot_id.base_subplot - _plot_id.plt_axis_list = [ax] - - ysep = _options['Y separation'] - if (ysep is None): - if (_plot_id.number_of_plots != 0): - ysep = _plot_id.options[-1]['Y separation'] - else: - if (_options['Log y']): - maxval = np.nanmax(d.data) - minval = np.nanmin(d.data) - if ((minval <= 0) or (minval == maxval)): - ysep = 1 - else: - ysep = math.sqrt(maxval/minval) - else: - ysep = float(np.nanmax(d.data)) - else: - try: - ysep = float(ysep) - except ValueError: - raise ValueError("Invalid Y separation option.") - _options['Y separation'] = ysep - - # Determining Y range - if (axis_index == 0): - if (_options['Log y']): - ax_min = np.nanmin(d.data[:,0]) - ax_max = np.nanmax(d.data[:,-1]) * ysep ** (d.data.shape[1]-1) - else: - ax_min = np.nanmin(d.data[:,0]) - ax_max = np.nanmax(d.data[:,-1]) + ysep * (d.data.shape[1]-1) - signal_index = 1 - else: - if (_options['Log y']): - ax_min = np.nanmin(d.data[0,:]) - ax_max = np.nanmax(d.data[-1,:]) * ysep ** (d.data.shape[0]-1) - else: - ax_min = np.nanmin(d.data[0,:]) - ax_max = np.nanmax(d.data[-1,:]) + ysep * (d.data.shape[0]-1) - signal_index = 0 - # If overplot then taking min and max of this and previous plots - if (_plot_id.number_of_plots != 0): - ax_min = min(ax.get_ylim()[0], ax_min) - ax_max = max(ax.get_ylim()[1], ax_max) - if (ax_max <= ax_min): - ax_max += 1 - ax.set_ylim(ax_min, ax_max) - - legend = [] - if (signal_names is not None): - for i in range(d.shape[signal_index]): - legend.append(signal_names[i]) - ax.legend(legend) - - _plot_opt = _plot_options[0] - for i in range(d.shape[signal_index]): - index = [...] * 2 - index[signal_index] = i - yerror = d._plot_error_ranges(index=tuple(index)) - if (_options['Log y']): - ydata = d.data[tuple(index)].flatten() * (ysep ** i) - if (yerror is not None): - yerror *= (ysep ** i) - else: - ydata = d.data[tuple(index)].flatten() + ysep * i - if (all_points is True): - x = xdata - y = ydata - xerr = xerror - yerr = yerror - else: - x,y,xerr,yerr = sample_for_plot(xdata,ydata,xerror,yerror,maxpoints) - if (errorbars < 0): - errorevery = 1 - else: - errorevery = int(round(len(x)/errorbars)) - if (errorevery < 1): - errorevery = 1 - if (plot_error): - ax.errorbar(x,y,xerr=xerr,yerr=yerr,errorevery=errorevery,**_plot_opt) - else: - ax.plot(x,y,**_plot_opt) - - if (xrange is not None): - ax.set_xlim(xrange[0],xrange[1]) - if (yrange is not None): - ax.set_ylim(yrange[0],yrange[1]) - ax.set_xlabel(ax_list[0].title(language=language)) - ax.set_ylabel(ax_list[1].title(language=language)) - if (_options['Log x']): - ax.set_xscale('log') - if (_options['Log y']): - ax.set_yscale('log') - title = ax.get_title() - if (title is None): - title = '' - if (title[-3:] != '...'): - newtitle = '' - if (d.exp_id is not None): - newtitle += str(d.exp_id) + ' ' - if (d.data_title is not None): - newtitle += d.data_title - if (len(newtitle) != 0): - if (len(title + newtitle) < 40): - if (title != ''): - title += ','+newtitle - else: - title = newtitle - else: - title += ',...' - ax.set_title(title) - _plot_id.plot_subtype = 0 # real multi xy plot - - elif ((_plot_type == 'image') or (_plot_type == 'contour')): - if (d.data is None): - raise ValueError("Cannot plot DataObject without data.") - if (len(d.shape) != 2): - raise TypeError("Image/contour plot is applicable to 2D data only. Use slicing.") - if (d.data.dtype.kind == 'c'): - raise TypeError("Image/contour plot is applicable only to real data.") - # Checking for numeric type - try: - d.data[0,0] += 1 - except TypeError: - raise TypeError("Image plot is applicable only to numeric data.") - - yrange = _options['Y range'] - if (yrange is not None): - if ((type(yrange) is not list) or (len(yrange) != 2)): - raise ValueError("Invalid Y range setting.") - - # Processing axes - # Although the plot will be cleared the existing plot axes will be considered - default_axes = [d.coordinates[0].unit.name, d.coordinates[1].unit.name, '__Data__'] - try: - pdd_list, ax_list = _plot_id.check_axes(d, - axes, - clear=_options['Clear'], - default_axes=default_axes, - force=_options['Force axes']) - except ValueError as e: - raise e - - # No overplotting is possible for this type of plot, erasing and restarting a Plot_ID - if (not _options['Clear']): - plt.subplot(_plot_id.base_subplot) - plt.cla() - gs = gridspec.GridSpecFromSubplotSpec(1, 1, subplot_spec=_plot_id.base_subplot) - _plot_id.plt_axis_list = [] - _plot_id.plt_axis_list.append(plt.subplot(gs[0,0])) - ax = _plot_id.plt_axis_list[0] - - pdd_list[2].data_type = PddType.Data - pdd_list[2].data_object = d - if ((pdd_list[0].data_type != PddType.Coordinate) or (pdd_list[1].data_type != PddType.Coordinate)) : - raise ValueError("X and y coordinates of image plot type should be coordinates.") - - coord_x = pdd_list[0].value - coord_y = pdd_list[1].value - if (_plot_type == 'image'): - if ((coord_x.mode.equidistant) and (len(coord_x.dimension_list) == 1) and - (coord_y.mode.equidistant) and (len(coord_y.dimension_list) == 1)): - # This data is image-like with data points on a rectangular array - image_like = True - elif ((len(coord_x.dimension_list) == 1) and (len(coord_y.dimension_list) == 1)): - if (not coord_x.isnumeric()): - raise ValueError('Coordinate '+coord_x.unit.name+' is not numeric.') - if (not coord_y.isnumeric()): - raise ValueError('Coordinate '+coord_y.unit.name+' is not numeric.') - index = [0] * len(d.shape) - index[coord_x.dimension_list[0]] = ... - xdata,xdata_low,xdata_high = coord_x.data(data_shape=d.shape,index=index) - xdata = xdata.flatten() - dx = xdata[1:] - xdata[:-1] - index = [0] * len(d.shape) - index[coord_y.dimension_list[0]] = ... - ydata,ydata_low,ydata_high = coord_y.data(data_shape=d.shape,index=index) - ydata = ydata.flatten() - dy = ydata[1:] - ydata[:-1] - if ((np.nonzero(np.abs(dx - dx[0]) / math.fabs(dx[0]) > 0.01)[0].size == 0) and - (np.nonzero(np.abs(dy - dy[0]) / math.fabs(dy[0]) > 0.01)[0].size == 0)): - # Actually the non-equidistant coordinates are equidistant - image_like = True - else: - image_like = False - else: - image_like = False - else: - image_like = False - if (image_like): - xdata_range = coord_x.data_range(data_shape=d.shape)[0] - ydata_range = coord_y.data_range(data_shape=d.shape)[0] - else: - ydata,ydata_low,ydata_high = coord_y.data(data_shape=d.shape) - xdata,xdata_low,xdata_high = coord_x.data(data_shape=d.shape) - - if (zrange is None): - vmin = np.nanmin(d.data) - vmax = np.nanmax(d.data) - else: - vmin = zrange[0] - vmax = zrange[1] - - if (vmax <= vmin): - raise ValueError("Invalid z range.") - - if (_options['Log z']): - if (vmin <= 0): - raise ValueError("z range[0] cannot be negative or zero for logarithmic scale.") - norm = colors.LogNorm(vmin=vmin, vmax=vmax) - locator = ticker.LogLocator(subs='all') - else: - norm = None - locator = None - - if (contour_levels is None): - contour_levels = 255 - - _plot_opt = _plot_options[0] - - try: - cmap_obj = plt.cm.get_cmap(cmap) - if (_options['Nan color'] is not None): - cmap_obj.set_bad(_options['Nan color']) - except ValueError: - raise ValueError("Invalid color map.") - - if (image_like): - try: -# if (coord_x.dimension_list[0] == 0): - if (coord_x.dimension_list[0] < coord_y.dimension_list[0]): - im=np.clip(np.transpose(d.data),vmin,vmax) - else: - im=np.clip(d.data,vmin,vmax) - img = ax.imshow(im,extent=xdata_range + ydata_range,norm=norm, - cmap=cmap_obj,vmin=vmin,aspect=_options['Aspect ratio'],interpolation=_options['Interpolation'], - vmax=vmax,origin='lower',**_plot_opt) - del im - except Exception as e: - raise e - else: - if (_plot_type == 'image'): - xgrid, ygrid = flap.tools.grid_to_box(xdata,ydata) - try: - img = ax.pcolormesh(xgrid,ygrid,np.clip(np.transpose(d.data),vmin,vmax),norm=norm,cmap=cmap,vmin=vmin, - vmax=vmax,**_plot_opt) - except Exception as e: - raise e - else: - try: - img = ax.contourf(xdata,ydata,np.clip(d.data,vmin,vmax),contour_levels,norm=norm, - origin='lower',cmap=cmap,vmin=vmin,vmax=vmax,**_plot_opt) - except Exception as e: - raise e - - if (_options['Colorbar']): - cbar = plt.colorbar(img,ax=ax) - if (d.data_unit.unit is not None) and (d.data_unit.unit != ''): - unit_name = '['+d.data_unit.unit+']' - else: - unit_name = '' - cbar.set_label(d.data_unit.name+' '+unit_name) - - if (xrange is not None): - ax.set_xlim(xrange[0],xrange[1]) - if (yrange is not None): - ax.set_ylim(yrange[0],yrange[1]) - ax.set_xlabel(ax_list[0].title(language=language)) - ax.set_ylabel(ax_list[1].title(language=language)) - if (_options['Log x']): - ax.set_xscale('log') - if (_options['Log y']): - ax.set_yscale('log') - title = ax.get_title() - if (title is None): - title = '' - if (title[-3:] != '...'): - newtitle = '' - if (d.exp_id is not None): - newtitle += str(d.exp_id) - if (d.data_title is not None): - newtitle += ' ' + d.data_title - if (len(newtitle) != 0): - if (len(title + newtitle) < 40): - if (title != ''): - title += ','+newtitle - else: - title = newtitle - else: - title += ',...' - ax.set_title(title) - - elif ((_plot_type == 'anim-image') or (_plot_type == 'anim-contour')): - if (d.data is None): - raise ValueError("Cannot plot DataObject without data.") - if (len(d.shape) != 3): - raise TypeError("Animated image plot is applicable to 3D data only. Use slicing.") - if (d.data.dtype.kind == 'c'): - raise TypeError("Animated image plot is applicable only to real data.") - # Checking for numeric type - try: - d.data[0,0] += 1 - except TypeError: - raise TypeError("Animated image plot is applicable only to numeric data.") - - yrange = _options['Y range'] - if (yrange is not None): - if ((type(yrange) is not list) or (len(yrange) != 2)): - raise ValueError("Invalid Y range setting.") - - # Processing axes - # Although the plot will be cleared the existing plot axes will be considered - default_axes = [d.coordinates[0].unit.name, d.coordinates[1].unit.name,d.coordinates[2].unit.name,'__Data__'] - try: - pdd_list, ax_list = _plot_id.check_axes(d, - axes, - clear=_options['Clear'], - default_axes=default_axes, - force=_options['Force axes']) - except ValueError as e: - raise e - - if (not ((pdd_list[3].data_type == PddType.Data) and (pdd_list[3].data_object == d))): - raise ValueError("For anim-image/anim-contour plot only data can be plotted on the z axis.") - if ((pdd_list[0].data_type != PddType.Coordinate) or (pdd_list[1].data_type != PddType.Coordinate)) : - raise ValueError("X and y coordinates of anim-image/anim-contour plot type should be coordinates.") - if (pdd_list[2].data_type != PddType.Coordinate) : - raise ValueError("Time coordinate of anim-image/anim-contour plot should be flap.coordinate.") - - coord_x = pdd_list[0].value - coord_y = pdd_list[1].value - coord_t = pdd_list[2].value - - if (len(coord_t.dimension_list) != 1): - raise ValueError("Time coordinate for anim-image/anim-contour plot should be changing only along one dimension.") - try: - coord_x.dimension_list.index(coord_t.dimension_list[0]) - badx = True - except: - badx = False - try: - coord_y.dimension_list.index(coord_t.dimension_list[0]) - bady = True - except: - bady = False - if (badx or bady): - raise ValueError("X and y coordinate for anim-image plot should not change in time dimension.") - - index = [0] * 3 - index[coord_t.dimension_list[0]] = ... - tdata = coord_t.data(data_shape=d.shape,index=index)[0].flatten() - if (not coord_y.isnumeric()): - raise ValueError('Coordinate '+coord_y.unit.name+' is not numeric.') - - if ((coord_x.mode.equidistant) and (len(coord_x.dimension_list) == 1) and - (coord_y.mode.equidistant) and (len(coord_y.dimension_list) == 1)): - # This data is image-like with data points on a rectangular array - image_like = True - elif ((len(coord_x.dimension_list) == 1) and (len(coord_y.dimension_list) == 1)): - if (not coord_x.isnumeric()): - raise ValueError('Coordinate '+coord_x.unit.name+' is not numeric.') - if (not coord_y.isnumeric()): - raise ValueError('Coordinate '+coord_y.unit.name+' is not numeric.') - index = [0] * len(d.shape) - index[coord_x.dimension_list[0]] = ... - xdata,xdata_low,xdata_high = coord_x.data(data_shape=d.shape,index=index) - xdata = xdata.flatten() - dx = xdata[1:] - xdata[:-1] - index = [0] * len(d.shape) - index[coord_y.dimension_list[0]] = ... - ydata,ydata_low,ydata_high = coord_y.data(data_shape=d.shape,index=index) - ydata = ydata.flatten() - dy = ydata[1:] - ydata[:-1] - if ((np.nonzero(np.abs(dx - dx[0]) / math.fabs(dx[0]) > 0.01)[0].size == 0) and - (np.nonzero(np.abs(dy - dy[0]) / math.fabs(dy[0]) > 0.01)[0].size == 0)): - # Actually the non-equidistant coordinates are equidistant - image_like = True - else: - image_like = False - else: - image_like = False - if (image_like and (_plot_type == 'anim-image')): - xdata_range = coord_x.data_range(data_shape=d.shape)[0] - ydata_range = coord_y.data_range(data_shape=d.shape)[0] - else: - index = [...]*3 - index[coord_t.dimension_list[0]] = 0 - ydata = np.squeeze(coord_y.data(data_shape=d.shape,index=index)[0]) - xdata = np.squeeze(coord_x.data(data_shape=d.shape,index=index)[0]) - - try: - cmap_obj = plt.cm.get_cmap(cmap) - if (_options['Nan color'] is not None): - cmap_obj.set_bad(_options['Nan color']) - except ValueError: - raise ValueError("Invalid color map.") - - gs = gridspec.GridSpecFromSubplotSpec(1, 1, subplot_spec=_plot_id.base_subplot) -# ax=plt.plot() - _plot_id.plt_axis_list = [] - _plot_id.plt_axis_list.append(plt.subplot(gs[0,0])) -# plt.subplot(_plot_id.base_subplot) -# plt.plot() -# plt.cla() -# ax=plt.gca() - for it in range(len(tdata)): - plt.subplot(_plot_id.base_subplot) - ax_act = plt.subplot(gs[0,0]) - time_index = [slice(0,dim) for dim in d.data.shape] - time_index[coord_t.dimension_list[0]] = it - time_index = tuple(time_index) - - if (zrange is None): - vmin = np.nanmin(d.data[time_index]) - vmax = np.nanmax(d.data[time_index]) - else: - vmin = zrange[0] - vmax = zrange[1] - - if (vmax <= vmin): - raise ValueError("Invalid z range.") - - if (_options['Log z']): - if (vmin <= 0): - raise ValueError("z range[0] cannot be negative or zero for logarithmic scale.") - norm = colors.LogNorm(vmin=vmin, vmax=vmax) - locator = ticker.LogLocator(subs='all') - else: - norm = None - locator = None #UNUSED - - if (contour_levels is None): - contour_levels = 255 - - _plot_opt = _plot_options[0] - - if (image_like and (_plot_type == 'anim-image')): - try: - #if (coord_x.dimension_list[0] == 0): - if (coord_x.dimension_list[0] < coord_y.dimension_list[0]): - im = np.clip(np.transpose(d.data[time_index]),vmin,vmax) - else: - im = np.clip(d.data[time_index],vmin,vmax) - - img = plt.imshow(im,extent=xdata_range + ydata_range,norm=norm, - cmap=cmap_obj,vmin=vmin,vmax=vmax, - aspect=_options['Aspect ratio'], - interpolation=_options['Interpolation'], - origin='lower',**_plot_opt) - del im - except Exception as e: - raise e - else: - if (_plot_type == 'anim-image'): - xgrid, ygrid = flap.tools.grid_to_box(xdata,ydata) - im = np.clip(np.transpose(d.data[time_index]),vmin,vmax) - try: - img = plt.pcolormesh(xgrid,ygrid,im,norm=norm,cmap=cmap,vmin=vmin, - vmax=vmax,**_plot_opt) - except Exception as e: - raise e - del im - else: - try: - im = np.clip(d.data[time_index],vmin,vmax) - img = plt.contourf(xdata,ydata,im,contour_levels,norm=norm, - origin='lower',cmap=cmap,vmin=vmin,vmax=vmax,**_plot_opt) - del im - except Exception as e: - raise e - - if (_options['Colorbar']): - cbar = plt.colorbar(img,ax=ax_act) - cbar.set_label(d.data_unit.name) - - if (xrange is not None): - plt.xlim(xrange[0],xrange[1]) - if (yrange is not None): - plt.ylim(yrange[0],yrange[1]) - plt.xlabel(ax_list[0].title(language=language)) - plt.ylabel(ax_list[1].title(language=language)) - if (_options['Log x']): - plt.xscale('log') - if (_options['Log y']): - plt.yscale('log') - title = str(d.exp_id)+' @ '+coord_t.unit.name+'='+"{:10.5f}".format(tdata[it])+' ['+coord_t.unit.unit+']' - plt.title(title) - plt.show(block=False) - time.sleep(_options['Waittime']) - plt.pause(0.001) - if ((_options['Video file'] is not None) and (cv2_presence is not False)): - fig = plt.gcf() - fig.canvas.draw() - # Get the RGBA buffer from the figure - w,h = fig.canvas.get_width_height() - buf = np.frombuffer(fig.canvas.tostring_rgb(), dtype=np.uint8) - buf.shape = ( h, w, 3 ) - buf = cv2.cvtColor(buf, cv2.COLOR_RGBA2BGR) - try: - video - except NameError: - height = buf.shape[0] - width = buf.shape[1] - video = cv2.VideoWriter(_options['Video file'], - cv2.VideoWriter_fourcc(*video_codec_code), - float(_options['Video framerate']), - (width,height), - isColor=True) - video.write(buf) - if ((_options['Video file'] is not None) and (cv2_presence is not False)): - cv2.destroyAllWindows() - video.release() - del video - - elif (_plot_type == 'animation'): - if (d.data is None): - raise ValueError("Cannot plot DataObject without data.") - if (len(d.shape) != 3): - raise TypeError("Animated image plot is applicable to 3D data only. Use slicing.") - if (d.data.dtype.kind == 'c'): - raise TypeError("Animated image plot is applicable only to real data.") - # Checking for numeric type - try: - d.data[0,0] += 1 - except TypeError: - raise TypeError("Animated image plot is applicable only to numeric data.") - - yrange = _options['Y range'] - if (yrange is not None): - if ((type(yrange) is not list) or (len(yrange) != 2)): - raise ValueError("Invalid Y range setting.") - if (zrange is not None) and (_options['Prevent saturation']): - d.data=np.mod(d.data,zrange[1]) - # Processing axes - # Although the plot will be cleared the existing plot axes will be considered - default_axes = [d.coordinates[0].unit.name, d.coordinates[1].unit.name,d.coordinates[2].unit.name,'__Data__'] - try: - pdd_list, ax_list = _plot_id.check_axes(d, - axes, - clear=_options['Clear'], - default_axes=default_axes, - force=_options['Force axes']) - except ValueError as e: - raise e - - if (not ((pdd_list[3].data_type == PddType.Data) and (pdd_list[3].data_object == d))): - raise ValueError("For the animation plot only data can be plotted on the z axis.") - if ((pdd_list[0].data_type != PddType.Coordinate) or (pdd_list[1].data_type != PddType.Coordinate)) : - raise ValueError("X and y coordinates of the animation plot type should be coordinates.") - if (pdd_list[2].data_type != PddType.Coordinate) : - raise ValueError("Time coordinate of the animation plot should be flap.coordinate.") - - coord_x = pdd_list[0].value - coord_y = pdd_list[1].value - coord_t = pdd_list[2].value - - if (len(coord_t.dimension_list) != 1): - raise ValueError("Time coordinate for anim-image/anim-contour plot should be changing only along one dimension.") - - index = [0] * 3 - index[coord_t.dimension_list[0]] = ... - tdata = coord_t.data(data_shape=d.shape,index=index)[0].flatten() - if (not coord_y.isnumeric()): - raise ValueError('Coordinate '+coord_y.unit.name+' is not numeric.') - - if ((coord_x.mode.equidistant) and (len(coord_x.dimension_list) == 1) and - (coord_y.mode.equidistant) and (len(coord_y.dimension_list) == 1)): - # This data is image-like with data points on a rectangular array - image_like = True - elif ((len(coord_x.dimension_list) == 1) and (len(coord_y.dimension_list) == 1)): - if (not coord_x.isnumeric()): - raise ValueError('Coordinate '+coord_x.unit.name+' is not numeric.') - if (not coord_y.isnumeric()): - raise ValueError('Coordinate '+coord_y.unit.name+' is not numeric.') - index = [0] * len(d.shape) - index[coord_x.dimension_list[0]] = ... - xdata,xdata_low,xdata_high = coord_x.data(data_shape=d.shape,index=index) - xdata = xdata.flatten() - dx = xdata[1:] - xdata[:-1] - index = [0] * len(d.shape) - index[coord_y.dimension_list[0]] = ... - ydata,ydata_low,ydata_high = coord_y.data(data_shape=d.shape,index=index) - ydata = ydata.flatten() - dy = ydata[1:] - ydata[:-1] - if ((np.nonzero(np.abs(dx - dx[0]) / math.fabs(dx[0]) > 0.01)[0].size == 0) and - (np.nonzero(np.abs(dy - dy[0]) / math.fabs(dy[0]) > 0.01)[0].size == 0)): - # Actually the non-equidistant coordinates are equidistant - image_like = True - else: - image_like = False - else: - image_like = False - if (image_like): - xdata_range = coord_x.data_range(data_shape=d.shape)[0] - ydata_range = coord_y.data_range(data_shape=d.shape)[0] - ydata = np.squeeze(coord_y.data(data_shape=d.shape,index=index)[0]) - xdata = np.squeeze(coord_x.data(data_shape=d.shape,index=index)[0]) - else: - index = [...]*3 - index[coord_t.dimension_list[0]] = 0 - xdata_range = None - ydata_range = None - ydata = np.squeeze(coord_y.data(data_shape=d.shape,index=index)[0]) - xdata = np.squeeze(coord_x.data(data_shape=d.shape,index=index)[0]) - - try: - cmap_obj = plt.cm.get_cmap(cmap) - if (_options['Nan color'] is not None): - cmap_obj.set_bad(_options['Nan color']) - except ValueError: - raise ValueError("Invalid color map.") - - gs = gridspec.GridSpecFromSubplotSpec(1, 1, subplot_spec=_plot_id.base_subplot) - - _plot_id.plt_axis_list = [] - _plot_id.plt_axis_list.append(plt.subplot(gs[0,0])) - - oargs=(ax_list, axes, d, xdata, ydata, tdata, xdata_range, ydata_range, - cmap_obj, contour_levels, - coord_t, coord_x, coord_y, cmap, _options, - xrange, yrange, zrange, image_like, - _plot_options, language, _plot_id, gs) - - anim = PlotAnimation(*oargs) - anim.animate() - - -# print("------ plot finished, show() ----") - plt.show(block=False) - -# if (_options['Clear']): -# _plot_id.number_of_plots = 0 -# _plot_id.plot_data = [] -# _plot_id.plt_axis_list = None -# _plot_id.number_of_plots += 1 - _plot_id.axes = ax_list - _plot_id.plot_data.append(pdd_list) - #Setting this one the default plot ID - _plot_id.options.append(_options) - _plot_id.plot_type = _plot_type - set_plot_id(_plot_id) - - return _plot_id diff --git a/flap/plot.py b/flap/plot.py index 0da1800..8d0f530 100644 --- a/flap/plot.py +++ b/flap/plot.py @@ -342,7 +342,7 @@ def animate(self): else: time_unit=self.coord_t.unit.unit time_coeff=1. - title = str(self.d.exp_id)+' @ '+self.coord_t.unit.name+'='+"{:10.5f}".format(self.tdata[0]*time_coeff)+\ + title = str(self.d.exp_id)+' @ '+self.coord_t.unit.name+'='+"{:10.7f}".format(self.tdata[0]*time_coeff)+\ ' ['+time_unit+']' plt.title(title) @@ -455,7 +455,7 @@ def animate_plot(self, it): time_unit=self.coord_t.unit.unit time_coeff=1. - title = str(self.d.exp_id)+' @ '+self.coord_t.unit.name+'='+"{:10.5f}".format(self.tdata[it]*time_coeff)+\ + title = str(self.d.exp_id)+' @ '+self.coord_t.unit.name+'='+"{:10.7f}".format(self.tdata[it]*time_coeff)+\ ' ['+time_unit+']' self.ax_act.set_title(title) diff --git a/flap/spectral_analysis.py b/flap/spectral_analysis.py index 6a34dfc..656418f 100644 --- a/flap/spectral_analysis.py +++ b/flap/spectral_analysis.py @@ -1519,7 +1519,7 @@ def _ccf(d, ref=None, coordinate=None, intervals=None, options=None): # zero_ind is the index where the 0 time lag will be after rearranging the CCF to final form zero_ind = [0] * len(correlation_dimensions) # A slicing list which will cut out the needed part of the CCF - # The CCF will have first the remaining dimensions of d, then the correlation dimensions, + # The CCF will have the remaining dimensions of d first, then the correlation dimensions, # then the remaining dimensions of ref ind_slice = [slice(0,d) for d in out_shape] ind_bin = copy.deepcopy(ind_slice) diff --git a/flap/tools.py b/flap/tools.py index b26240a..e65f228 100644 --- a/flap/tools.py +++ b/flap/tools.py @@ -3,6 +3,7 @@ Created on Wed Jan 23 13:18:25 2019 @author: Zoletnik +@coauthor: Lampert Tools for the FLAP module From 2a790416d1b3cf73fc43aab5891ba27fe3b9c123 Mon Sep 17 00:00:00 2001 From: Mate Lampert Date: Mon, 27 Jan 2020 14:04:51 -0500 Subject: [PATCH 18/27] Spectral analysis 2D temporary solution and plot bugfix Spectral analysis couldn't do anything about a 2D CCF function, it was failing all the time. A temporary solution has been implemented, which only work with 2D data, but not with 2+1D. A solution needs to be found, but it serves the purpuse. It is also a bit slow compared to the scipy CCF calculator. --- flap/plot.py | 6 +--- flap/spectral_analysis.py | 65 ++++++++++++++++++++++++++++++--------- 2 files changed, 52 insertions(+), 19 deletions(-) diff --git a/flap/plot.py b/flap/plot.py index 8d0f530..8774230 100644 --- a/flap/plot.py +++ b/flap/plot.py @@ -1012,11 +1012,7 @@ def _plot(data_object, video_codec_code=video_codec_decrypt[_options['Video format']] #These lines do the coordinate unit conversion - if axes is not None: - axes_unit_conversion=np.zeros(len(axes)) - axes_unit_conversion[:]=1. - else: - axes_unit_conversion=[1.,1.,1.] + axes_unit_conversion=[1.,1.,1.] if _options['Plot units'] is not None: unit_length=len(_options['Plot units']) diff --git a/flap/spectral_analysis.py b/flap/spectral_analysis.py index 656418f..df98fba 100644 --- a/flap/spectral_analysis.py +++ b/flap/spectral_analysis.py @@ -170,6 +170,7 @@ def _spectral_calc_interval_selection(d, ref, coordinate,intervals,interval_n): proc_interval_index_len = int(round(proc_interval_len / step)) - 2 proc_interval_index_start = np.round((proc_interval_end - coord.start) / coord.step[0]).astype(np.int32) + 1 proc_interval_index_end = proc_interval_index_start + proc_interval_index_len + return flap.coordinate.Intervals(proc_interval_start, proc_interval_end), \ flap.coordinate.Intervals(proc_interval_index_start, proc_interval_index_end), @@ -1414,7 +1415,7 @@ def _ccf(d, ref=None, coordinate=None, intervals=None, options=None): # Sanity check for lag range for i,coord in enumerate(coord_obj): dr, dr1 = coord.data_range(data_shape=d.shape) - if ((abs(corr_range[i][0]) > (int_high[0] - int_low[0])) or (abs(corr_range[i][1]) > (int_high[0] - int_low[0]))): + if ((abs(corr_range[i][0]) > (dr[1] - dr[0])) or (abs(corr_range[i][1]) > (dr[1] - dr[0]))): raise ValueError("Correlation lag calculation range is too large for coordinate '{:s}'.".format(coord.unit.name)) if ((corr_range[i][1] - corr_range[i][0]) < coord.step[0]*3): raise ValueError("Correlation lag calculation range is too small for coordinate '{:s}'.".format(coord.unit.name)) @@ -1608,11 +1609,22 @@ def _ccf(d, ref=None, coordinate=None, intervals=None, options=None): if (out_dtype is float): res = np.real(res) corr = np.empty(res.shape,dtype=res.dtype) - corr[tuple(ind_out1)] = res[tuple(ind_in1)] - corr[tuple(ind_out2)] = res[tuple(ind_in2)] + + if len(_coordinate) == 2: + corr[tuple([ind_out1[0],ind_out1[1]])] = res[tuple([ind_in1[0],ind_in1[1]])] + corr[tuple([ind_out2[0],ind_out1[1]])] = res[tuple([ind_in2[0],ind_in1[1]])] + corr[tuple([ind_out2[0],ind_out2[1]])] = res[tuple([ind_in2[0],ind_in2[1]])] + corr[tuple([ind_out1[0],ind_out2[1]])] = res[tuple([ind_in1[0],ind_in2[1]])] + else: + corr[tuple(ind_out1)] = res[tuple(ind_in1)] + corr[tuple(ind_out2)] = res[tuple(ind_in2)] + corr_sliced = corr[tuple(ind_slice)] corr_binned = np.zeros(tuple(out_shape),dtype=res.dtype) - np.add.at(corr_binned,tuple(ind_bin),corr_sliced) + if corr_binned.shape != corr_binned.shape: + np.add.at(corr_binned,tuple(ind_bin),corr_sliced) + else: + corr_binned=corr_sliced if (norm): zero_ind_out = [0] * len(correlation_dimensions) for i in range(len(correlation_dimensions)): @@ -1650,11 +1662,22 @@ def _ccf(d, ref=None, coordinate=None, intervals=None, options=None): # we need to move the correlation axes to the end to be consistent with the CCF res_acf, ax_map = flap.tools.move_axes_to_end(res_acf,correlation_dimensions) corr_acf = np.empty(res_acf.shape,dtype=res.dtype) - corr_acf[tuple(ind_out1_acf)] = res_acf[tuple(ind_in1_acf)] - corr_acf[tuple(ind_out2_acf)] = res_acf[tuple(ind_in2_acf)] + + if len(_coordinate) == 2: + corr_acf[tuple([ind_out1_acf[0],ind_out1_acf[1]])] = res_acf[tuple([ind_in1_acf[0],ind_in1_acf[1]])] + corr_acf[tuple([ind_out2_acf[0],ind_out1_acf[1]])] = res_acf[tuple([ind_in2_acf[0],ind_in1_acf[1]])] + corr_acf[tuple([ind_out2_acf[0],ind_out2_acf[1]])] = res_acf[tuple([ind_in2_acf[0],ind_in2_acf[1]])] + corr_acf[tuple([ind_out1_acf[0],ind_out2_acf[1]])] = res_acf[tuple([ind_in1_acf[0],ind_in2_acf[1]])] + else: + corr_acf[tuple(ind_out1_acf)] = res_acf[tuple(ind_in1_acf)] + corr_acf[tuple(ind_out2_acf)] = res_acf[tuple(ind_in2_acf)] + corr_sliced_acf = corr_acf[tuple(ind_slice_acf)] corr_binned_acf = np.zeros(tuple(out_shape_acf),dtype=res_acf.dtype) - np.add.at(corr_binned_acf,tuple(ind_bin_acf),corr_sliced_acf) + if not corr_binned_acf.shape == corr_sliced_acf.shape: + np.add.at(corr_binned_acf,tuple(ind_bin_acf),corr_sliced_acf) + else: + corr_binned_acf=corr_sliced_acf # We need to take the zero lag elements from each correlation dimension corr_dimension_start = len(out_shape_acf)-len(correlation_dimensions) for i in range(len(correlation_dimensions)): @@ -1662,16 +1685,31 @@ def _ccf(d, ref=None, coordinate=None, intervals=None, options=None): corr_binned_acf = np.take(corr_binned_acf,zero_ind_out[i],axis=corr_dimension_start) if (ref is not None): res_acf_ref = np.fft.ifftn(fft_ref * np.conj(fft_ref),axes=correlation_dimensions_ref) + if (out_dtype is float): res_acf_ref = np.real(res_acf_ref) # we need to move the correlation axes to the start to be consistent with the CCF - res_acf_ref,ax_map_ref = flap.tools.move_axes_to_start(res_acf_ref,correlation_dimensions_ref) + if len(_coordinate) != 2: + res_acf_ref,ax_map_ref = flap.tools.move_axes_to_start(res_acf_ref,correlation_dimensions_ref) + corr_acf_ref = np.empty(res_acf_ref.shape,dtype=res_acf.dtype) - corr_acf_ref[tuple(ind_out1_acf_ref)] = res_acf_ref[tuple(ind_in1_acf_ref)] - corr_acf_ref[tuple(ind_out2_acf_ref)] = res_acf_ref[tuple(ind_in2_acf_ref)] + if len(_coordinate) == 2: + corr_acf_ref[tuple([ind_out1_acf_ref[0],ind_out1_acf_ref[1]])] = res_acf_ref[tuple([ind_in1_acf_ref[0],ind_in1_acf_ref[1]])] + corr_acf_ref[tuple([ind_out2_acf_ref[0],ind_out1_acf_ref[1]])] = res_acf_ref[tuple([ind_in2_acf_ref[0],ind_in1_acf_ref[1]])] + corr_acf_ref[tuple([ind_out2_acf_ref[0],ind_out2_acf_ref[1]])] = res_acf_ref[tuple([ind_in2_acf_ref[0],ind_in2_acf_ref[1]])] + corr_acf_ref[tuple([ind_out1_acf_ref[0],ind_out2_acf_ref[1]])] = res_acf_ref[tuple([ind_in1_acf_ref[0],ind_in2_acf_ref[1]])] + else: + corr_acf_ref[tuple(ind_out1_acf_ref)] = res_acf_ref[tuple(ind_in1_acf_ref)] + corr_acf_ref[tuple(ind_out2_acf_ref)] = res_acf_ref[tuple(ind_in2_acf_ref)] + corr_sliced_acf_ref = corr_acf_ref[tuple(ind_slice_acf_ref)] corr_binned_acf_ref = np.zeros(tuple(out_shape_acf_ref),dtype=res_acf_ref.dtype) - np.add.at(corr_binned_acf_ref,tuple(ind_bin_acf_ref),corr_sliced_acf_ref) + + if not corr_binned_acf_ref.shape == corr_sliced_acf_ref.shape: + np.add.at(corr_binned_acf_ref,tuple(ind_bin_acf_ref),corr_sliced_acf_ref) + else: + corr_binned_acf_ref=corr_sliced_acf_ref + # We need to take the zero lag elements from each correlation dimension for i in range(len(correlation_dimensions)): # Always the cor_dimension_start axis is taken as it is removed by take @@ -1738,8 +1776,7 @@ def _ccf(d, ref=None, coordinate=None, intervals=None, options=None): start = range_sampout[i][0] * corr_res_sample[i] * c.step[0], step = [corr_res_sample[i] * c.step[0]], dimension_list=[len(d.data.shape) - len(correlation_dimensions) + i]) - coord_list.append(c_new) - + coord_list.append(c_new) if (norm): unit_name = 'Correlation' else: @@ -1758,4 +1795,4 @@ def _ccf(d, ref=None, coordinate=None, intervals=None, options=None): data_unit = flap.coordinate.Unit(unit_name) ) - return d_out + return d_out \ No newline at end of file From 8a38c527732349e4bce29e8c0fa46e7dd0e15a95 Mon Sep 17 00:00:00 2001 From: Mate Lampert Date: Mon, 27 Jan 2020 16:20:45 -0500 Subject: [PATCH 19/27] Slicing bugfix The end of the slicing range was not set to the proper one. --- flap/data_object.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flap/data_object.py b/flap/data_object.py index 5ecf819..9367140 100644 --- a/flap/data_object.py +++ b/flap/data_object.py @@ -1595,7 +1595,7 @@ def simple_slice_index(slicing, slicing_coord, data_shape, _options): start_index = int(round((regular_slice.start - range_coord[0]) / abs(slicing_coord.step[0]))) step_index = int(round(regular_slice.step / abs(slicing_coord.step[0]))) stop_index = int((regular_slice.stop - range_coord[0]) / abs(slicing_coord.step[0])) - return slice(start_index, stop_index, step_index) + return slice(start_index, stop_index+1, step_index) else: # slice object could not be created for data array # Creating flattened coordinate data array From b73c4867949f54fe27046159894e9e3ce5e77830 Mon Sep 17 00:00:00 2001 From: Mate Lampert Date: Wed, 26 May 2021 22:04:13 -0400 Subject: [PATCH 20/27] Substantial modification plot.py: minor modifications, two lines removed for macos compatibility. They need to be checked for windows. tools.py: Added the 2D polynomial fitting and a helper function for spectral_analysis spectral_analysis.py: Fixed the 2D correlation calculation, now a time series of 2D images can be correlated with another 2D image. Added the option of "Correct ACF peak" which currects the peak of the cross-correlation function by fitting a parabola on its peak. --- flap/data_object.py | 2 +- flap/plot.py | 158 +++++++++++++++++----------- flap/spectral_analysis.py | 210 +++++++++++++++++++++++++++++++++----- flap/tools.py | 113 +++++++++++++++++++- 4 files changed, 398 insertions(+), 85 deletions(-) diff --git a/flap/data_object.py b/flap/data_object.py index 9367140..5c9d1bb 100644 --- a/flap/data_object.py +++ b/flap/data_object.py @@ -3535,7 +3535,7 @@ def get_data_object_ref(self,name,exp_id='*'): nlist.append(n) if len(nlist) == 0: raise KeyError("Data object " + name - + "(exp_id:" + str(exp_id) + ") does not exists.") + + "(exp_id:" + str(exp_id) + ") does not exist.") if (len(nlist) > 1): raise KeyError("Multiple data objects found for name " + name + "(exp_id:" + str(exp_id) + ").") diff --git a/flap/plot.py b/flap/plot.py index 8774230..c6fd7e2 100644 --- a/flap/plot.py +++ b/flap/plot.py @@ -28,6 +28,11 @@ """ import os +import copy +from enum import Enum +import math +import time + import matplotlib.pyplot as plt import matplotlib.gridspec as gridspec import matplotlib.colors as colors @@ -36,11 +41,37 @@ from matplotlib.widgets import Button, Slider from matplotlib import ticker +#styled=True +#if styled: +# plt.rc('font', family='serif', serif='Helvetica') +# labelsize=9 +# linewidth=1 +# major_ticksize=2 +# plt.rc('text', usetex=False) +# plt.rcParams['pdf.fonttype'] = 42 +# plt.rcParams['ps.fonttype'] = 42 +# plt.rcParams['lines.linewidth'] = linewidth +# plt.rcParams['axes.linewidth'] = linewidth +# plt.rcParams['axes.labelsize'] = labelsize +# plt.rcParams['axes.titlesize'] = labelsize +# +# plt.rcParams['xtick.labelsize'] = labelsize +# plt.rcParams['xtick.major.size'] = major_ticksize +# plt.rcParams['xtick.major.width'] = linewidth +# plt.rcParams['xtick.minor.width'] = linewidth/2 +# plt.rcParams['xtick.minor.size'] = major_ticksize/2 +# +# plt.rcParams['ytick.labelsize'] = labelsize +# plt.rcParams['ytick.major.width'] = linewidth +# plt.rcParams['ytick.major.size'] = major_ticksize +# plt.rcParams['ytick.minor.width'] = linewidth/2 +# plt.rcParams['ytick.minor.size'] = major_ticksize/2 +# plt.rcParams['legend.fontsize'] = labelsize +#else: +# import matplotlib.style as pltstyle +# pltstyle.use('default') + import numpy as np -import copy -from enum import Enum -import math -import time try: import cv2 @@ -213,8 +244,9 @@ def animate(self): valmax=self.tdata[-1]*self.axes_unit_conversion[2], valinit=self.tdata[0]*self.axes_unit_conversion[2]) self.time_slider.on_changed(self._set_animation) - - plt.subplot(self.plot_id.base_subplot) + + #The following line needed to be removed for matplotlib 3.4.1 + #plt.subplot(self.plot_id.base_subplot) self.ax_act = plt.subplot(self.gs[0,0]) if (len(self.coord_x.dimension_list) == 3 or @@ -233,7 +265,7 @@ def animate(self): for j_check in range(i_check+1,len(self.axes)): if (self.d.coordinates[axes_coordinate_decrypt[i_check]].unit.unit == self.d.coordinates[axes_coordinate_decrypt[j_check]].unit.unit): - self.ax_act.axis('equal') + self.ax_act.set_aspect(1.0) time_index = [slice(0,dim) for dim in self.d.data.shape] time_index[self.coord_t.dimension_list[0]] = 0 @@ -1009,7 +1041,9 @@ def _plot(data_object, video_codec_decrypt={'avi':'XVID', 'mkv':'X264', 'mp4':'mp4v'} - video_codec_code=video_codec_decrypt[_options['Video format']] + video_codec_code=video_codec_decrypt[_options['Video format']] + print('Forcing waittime to be 0s for video saving.') + _options['Waittime']=0. #These lines do the coordinate unit conversion axes_unit_conversion=[1.,1.,1.] @@ -1179,16 +1213,18 @@ def _plot(data_object, #Interpolate the path data to the time vector of the original data try: x_object_interp=flap.slice_data(x_object_name, - slicing={axes[2]:tdata}, - options={'Interpolation':'Linear'}, - output_name='X OBJ INTERP') + exp_id=d.exp_id, + slicing={axes[2]:tdata}, + options={'Interpolation':'Linear'}, + output_name='X OBJ INTERP') except: raise ValueError('Interpolation cannot be done for the \'Data object X\' along axis '+axes[2]+'.') try: y_object_interp=flap.slice_data(y_object_name, - slicing={axes[2]:tdata}, - options={'Interpolation':'Linear'}, - output_name='Y OBJ INTERP') + exp_id=d.exp_id, + slicing={axes[2]:tdata}, + options={'Interpolation':'Linear'}, + output_name='Y OBJ INTERP') except: raise ValueError('Interpolation cannot be done for the \'Data object Y\' along axis '+axes[2]+'.') @@ -1238,9 +1274,10 @@ def _plot(data_object, new_unit=d.coordinates[index_data_coordinate].unit.unit) xy_object_interp=flap.slice_data(xy_object_name, - slicing={axes[2]:tdata}, - options={'Interpolation':'Linear'}, - output_name='XY OBJ INTERP') + exp_id=d.exp_id, + slicing={axes[2]:tdata}, + options={'Interpolation':'Linear'}, + output_name='XY OBJ INTERP') overplot_options['contour'][contour_obj_keys]['data']={} overplot_options['contour'][contour_obj_keys]['data']['Data resampled']=xy_object_interp.data overplot_options['contour'][contour_obj_keys]['data']['X coord resampled']=xy_object_interp.coordinate(axes[0])[0]*unit_conversion_coeff[0] @@ -1610,26 +1647,26 @@ def _plot(data_object, if (yrange is not None): ax.set_ylim(yrange[i_comp][0],yrange[i_comp][1]) - - if overplot_options['line'] is not None: - for line_obj_keys in overplot_options['line']: - xmin, xmax = ax.get_xbound() - ymin, ymax = ax.get_ybound() - - if overplot_options['line'][line_obj_keys]['Plot']: - if 'Horizontal' in overplot_options['line'][line_obj_keys]: - h_coords=overplot_options['line'][line_obj_keys]['Horizontal'] - for segments in h_coords: - if segments[0] > ymin and segments[0] < ymax: - l = mlines.Line2D([xmin,xmax], [segments[0],segments[0]], color=segments[1]) - ax.add_line(l) - - if 'Vertical' in overplot_options['line'][line_obj_keys]: - v_coords=overplot_options['line'][line_obj_keys]['Vertical'] - for segments in v_coords: - if segments[0] > xmin and segments[0] < xmax: - l = mlines.Line2D([segments[0],segments[0]], [ymin,ymax], color=segments[1]) - ax.add_line(l) + if (overplot_options is not None): + if overplot_options['line'] is not None: + for line_obj_keys in overplot_options['line']: + xmin, xmax = ax.get_xbound() + ymin, ymax = ax.get_ybound() + + if overplot_options['line'][line_obj_keys]['Plot']: + if 'Horizontal' in overplot_options['line'][line_obj_keys]: + h_coords=overplot_options['line'][line_obj_keys]['Horizontal'] + for segments in h_coords: + if segments[0] > ymin and segments[0] < ymax: + l = mlines.Line2D([xmin,xmax], [segments[0],segments[0]], color=segments[1]) + ax.add_line(l) + + if 'Vertical' in overplot_options['line'][line_obj_keys]: + v_coords=overplot_options['line'][line_obj_keys]['Vertical'] + for segments in v_coords: + if segments[0] > xmin and segments[0] < xmax: + l = mlines.Line2D([segments[0],segments[0]], [ymin,ymax], color=segments[1]) + ax.add_line(l) # Setting axis labels if axes_unit_conversion[0] == 1.: @@ -1857,24 +1894,24 @@ def _plot(data_object, if (yrange is not None): ax.set_ylim(yrange[0],yrange[1]) - - if overplot_options['line'] is not None: - for line_obj_keys in overplot_options['line']: - xmin, xmax = ax.get_xbound() - ymin, ymax = ax.get_ybound() - if overplot_options['line'][line_obj_keys]['Plot']: - if 'Horizontal' in overplot_options['line'][line_obj_keys]: - h_coords=overplot_options['line'][line_obj_keys]['Horizontal'] - for segments in h_coords: - if segments[0] > ymin and segments[0] < ymax: - l = mlines.Line2D([xmin,xmax], [segments[0],segments[0]], color=segments[1]) - ax.add_line(l) - if 'Vertical' in overplot_options['line'][line_obj_keys]: - v_coords=overplot_options['line'][line_obj_keys]['Vertical'] - for segments in v_coords: - if segments[0] > xmin and segments[0] < xmax: - l = mlines.Line2D([segments[0],segments[0]], [ymin,ymax], color=segments[1]) - ax.add_line(l) + if (overplot_options is not None): + if overplot_options['line'] is not None: + for line_obj_keys in overplot_options['line']: + xmin, xmax = ax.get_xbound() + ymin, ymax = ax.get_ybound() + if overplot_options['line'][line_obj_keys]['Plot']: + if 'Horizontal' in overplot_options['line'][line_obj_keys]: + h_coords=overplot_options['line'][line_obj_keys]['Horizontal'] + for segments in h_coords: + if segments[0] > ymin and segments[0] < ymax: + l = mlines.Line2D([xmin,xmax], [segments[0],segments[0]], color=segments[1]) + ax.add_line(l) + if 'Vertical' in overplot_options['line'][line_obj_keys]: + v_coords=overplot_options['line'][line_obj_keys]['Vertical'] + for segments in v_coords: + if segments[0] > xmin and segments[0] < xmax: + l = mlines.Line2D([segments[0],segments[0]], [ymin,ymax], color=segments[1]) + ax.add_line(l) if axes_unit_conversion[0] == 1.: ax.set_xlabel(ax_list[0].title(language=language)) @@ -1959,7 +1996,8 @@ def _plot(data_object, coord_y = pdd_list[1].value if _options['Equal axes']: if (coord_x.unit.unit == coord_y.unit.unit): - ax.axis('equal') + #ax.axis('equal') + ax.set_aspect(1.0) else: print('Equal axis is not possible. The axes units are not equal.') if (_plot_type == 'image'): @@ -2214,6 +2252,8 @@ def _plot(data_object, (coord_y.mode.equidistant) and (len(coord_y.dimension_list) == 1)): # This data is image-like with data points on a rectangular array image_like = True + xdata=coord_x.data(data_shape=d.data.shape)[0] + ydata=coord_y.data(data_shape=d.data.shape)[0] elif ((len(coord_x.dimension_list) == 1) and (len(coord_y.dimension_list) == 1)): if (not coord_x.isnumeric()): raise ValueError('Coordinate '+coord_x.unit.name+' is not numeric.') @@ -2271,10 +2311,12 @@ def _plot(data_object, yrange=[np.min(ydata),np.max(ydata)] for it in range(len(tdata)): - plt.subplot(_plot_id.base_subplot) + #The following line was removed for matplotlib 3.4.1 + #plt.subplot(_plot_id.base_subplot) ax_act = plt.subplot(gs[0,0]) if _options['Equal axes'] and coord_x.unit.unit == coord_y.unit.unit: - ax_act.axis('equal') + #ax_act.axis('equal') + ax_act.set_aspect(1.0) time_index = [slice(0,dim) for dim in d.data.shape] time_index[coord_t.dimension_list[0]] = it time_index = tuple(time_index) diff --git a/flap/spectral_analysis.py b/flap/spectral_analysis.py index df98fba..46a8377 100644 --- a/flap/spectral_analysis.py +++ b/flap/spectral_analysis.py @@ -1266,6 +1266,7 @@ def _ccf(d, ref=None, coordinate=None, intervals=None, options=None): 'Trend removal': ['Poly', 2], 'Error calculation': True, 'Normalize': False, + 'Correct ACF peak': False, 'Verbose':True } _options = flap.config.merge_options(default_options, options, data_source=d.data_source, section='Correlation') @@ -1285,8 +1286,13 @@ def _ccf(d, ref=None, coordinate=None, intervals=None, options=None): trend = _options['Trend removal'] if (len(_coordinate) != 1 and (trend is not None)): raise NotImplementedError("Trend removal for multi-dimensional correlation is not implemented.") + interval_n = _options['Interval_n'] + norm = _options['Normalize'] + + correct_acf_peak=_options['Correct ACF peak'] + error_calc = _options['Error calculation'] if (len(_coordinate) != 1 and (intervals is not None)): raise NotImplementedError("Interval selection for multi-dimensional correlation is not implemented.") @@ -1465,6 +1471,7 @@ def _ccf(d, ref=None, coordinate=None, intervals=None, options=None): correlation_dimensions_out = [] out_shape_acf = [] out_shape_acf_ref = [] + for i in range(len(d.data.shape)): try: correlation_dimensions.index(i) @@ -1534,11 +1541,14 @@ def _ccf(d, ref=None, coordinate=None, intervals=None, options=None): nfft = corr_point_n_nat[i] + pad_length[i] ind_in1[cd] = slice(0,int(nfft / 2)) ind_out1[cd] = slice(nfft-int(nfft / 2), nfft) + zero_ind[i] = nfft - int(nfft / 2) ind_in2[cd] = slice(int(nfft / 2),nfft) ind_out2[cd] = slice(0, nfft - int(nfft / 2)) + ind_slice[cd] = slice(shift_range[i][0] + zero_ind[i], shift_range[i][1] + zero_ind[i]+1) ind_bin[cd] = np.arange(ind_slice[cd].stop - ind_slice[cd].start,dtype=np.int32) // corr_res_sample[i] + if (calc_acf): ind_in1_acf[cd] = ind_in1[cd] ind_out1_acf[cd] = ind_out1[cd] @@ -1608,14 +1618,21 @@ def _ccf(d, ref=None, coordinate=None, intervals=None, options=None): res = np.fft.ifftn(res,axes=cps_corr_dims) if (out_dtype is float): res = np.real(res) - corr = np.empty(res.shape,dtype=res.dtype) - if len(_coordinate) == 2: - corr[tuple([ind_out1[0],ind_out1[1]])] = res[tuple([ind_in1[0],ind_in1[1]])] - corr[tuple([ind_out2[0],ind_out1[1]])] = res[tuple([ind_in2[0],ind_in1[1]])] - corr[tuple([ind_out2[0],ind_out2[1]])] = res[tuple([ind_in2[0],ind_in2[1]])] - corr[tuple([ind_out1[0],ind_out2[1]])] = res[tuple([ind_in1[0],ind_in2[1]])] + if len(correlation_dimensions) == 2: + # This part of the code rearranges the corners of the 2D CCF function + # so the maximum is going to be in the middle. It does this in a generalized way + # where the non-correlating dimensions are left alone. + + corr=flap.tools.reorder_2d_ccf_indices(res, + cd, + ind_in1, + ind_in2, + ind_out1, + ind_out2) + else: + corr = np.empty(res.shape,dtype=res.dtype) corr[tuple(ind_out1)] = res[tuple(ind_in1)] corr[tuple(ind_out2)] = res[tuple(ind_in2)] @@ -1657,21 +1674,28 @@ def _ccf(d, ref=None, coordinate=None, intervals=None, options=None): else: # We do not have the autocorrelations, calculating res_acf = np.fft.ifftn(fft * np.conj(fft),axes=correlation_dimensions) + if (out_dtype is float): res_acf = np.real(res_acf) # we need to move the correlation axes to the end to be consistent with the CCF res_acf, ax_map = flap.tools.move_axes_to_end(res_acf,correlation_dimensions) - corr_acf = np.empty(res_acf.shape,dtype=res.dtype) - if len(_coordinate) == 2: - corr_acf[tuple([ind_out1_acf[0],ind_out1_acf[1]])] = res_acf[tuple([ind_in1_acf[0],ind_in1_acf[1]])] - corr_acf[tuple([ind_out2_acf[0],ind_out1_acf[1]])] = res_acf[tuple([ind_in2_acf[0],ind_in1_acf[1]])] - corr_acf[tuple([ind_out2_acf[0],ind_out2_acf[1]])] = res_acf[tuple([ind_in2_acf[0],ind_in2_acf[1]])] - corr_acf[tuple([ind_out1_acf[0],ind_out2_acf[1]])] = res_acf[tuple([ind_in1_acf[0],ind_in2_acf[1]])] + if len(correlation_dimensions) == 2: + # This part of the code rearranges the corners of the 2D CCF function + # so the maximum is going to be in the middle. It does this in a generalized way + # where the non-correlating dimensions are left alone. + + corr_acf=flap.tools.reorder_2d_ccf_indices(res_acf, + cd, + ind_in1_acf, + ind_in2_acf, + ind_out1_acf, + ind_out2_acf) else: + corr_acf = np.empty(res_acf.shape,dtype=res.dtype) corr_acf[tuple(ind_out1_acf)] = res_acf[tuple(ind_in1_acf)] corr_acf[tuple(ind_out2_acf)] = res_acf[tuple(ind_in2_acf)] - + corr_sliced_acf = corr_acf[tuple(ind_slice_acf)] corr_binned_acf = np.zeros(tuple(out_shape_acf),dtype=res_acf.dtype) if not corr_binned_acf.shape == corr_sliced_acf.shape: @@ -1680,25 +1704,127 @@ def _ccf(d, ref=None, coordinate=None, intervals=None, options=None): corr_binned_acf=corr_sliced_acf # We need to take the zero lag elements from each correlation dimension corr_dimension_start = len(out_shape_acf)-len(correlation_dimensions) + if correct_acf_peak: + #if False: + + if corr_dimension_start == 0: #Every dimension is correlation with a lag dimension + array_2b_corrected=copy.deepcopy(corr_binned_acf) + peak_value=0. + for i in range(len(correlation_dimensions)): + if zero_ind_out[i]-2 < 0: + raise ValueError("Not enough datapoints for ACF correction.") + ind_correction = np.arange(zero_ind_out[i]-2,zero_ind_out[i]+3) + array_2b_corrected = np.take(array_2b_corrected,ind_correction,axis=corr_dimension_start+i) + + for i in range(len(correlation_dimensions)): + coeff = np.polyfit([-2,-1,1,2], + np.take(array_2b_corrected, 2, axis=corr_dimension_start+i)[np.asarray([0,1,3,4])], + 2) + + peak_value += coeff[2]-coeff[1]**2/(4*coeff[0]) + peak_value /= (i+1) + corr_binned_acf[zero_ind_out]=peak_value + + elif corr_dimension_start == 1: #The first dimension is not correlation, but the rest is. + for i_non_corr in range(corr_binned_acf.shape[0]): + array_2b_corrected=np.take(corr_binned_acf,i_non_corr,axis=0) + peak_value=0. + for i in range(len(correlation_dimensions)): + if zero_ind_out[i]-2 < 0: + raise ValueError("Not enough datapoints for ACF correction.") + ind_correction = np.arange(zero_ind_out[i]-2,zero_ind_out[i]+3) + array_2b_corrected = np.take(array_2b_corrected,ind_correction,axis=i) + if len(correlation_dimensions) == 1: + for i in range(len(correlation_dimensions)): + coeff = np.polyfit([-2,-1,1,2], + array_2b_corrected[np.asarray([0,1,3,4])], + 2) + + peak_value += coeff[2]-coeff[1]**2/(4*coeff[0]) + peak_value /= (i+1) + corr_binned_acf[i_non_corr,zero_ind_out]=peak_value + elif len(correlation_dimensions) == 2: + for i in range(len(correlation_dimensions)): + coeff = np.polyfit([-2,-1,1,2], + np.take(array_2b_corrected, 2, axis=i)[np.asarray([0,1,3,4])], + 2) + + peak_value += coeff[2]-coeff[1]**2/(4*coeff[0]) + peak_value /= (i+1) + corr_binned_acf[i_non_corr,zero_ind_out]=peak_value + elif len(correlation_dimensions) == 3: + + raise NotImplementedError('3D correlation dimensions has not been implemented yet.') + + elif corr_dimension_start == 2: + for i_non_corr_1 in range(corr_binned_acf.shape[0]): + for i_non_corr_2 in range(corr_binned_acf.shape[1]): + array_2b_corrected=np.take(corr_binned_acf,i_non_corr_1,axis=0) + array_2b_corrected=np.take(array_2b_corrected,i_non_corr_2,axis=0) + peak_value=0. + for i in range(len(correlation_dimensions)): + if zero_ind_out[i]-2 < 0: + raise ValueError("Not enough datapoints for ACF correction.") + ind_correction = np.arange(zero_ind_out[i]-2,zero_ind_out[i]+3) + array_2b_corrected = np.take(array_2b_corrected,ind_correction,axis=i) + + for i in range(len(correlation_dimensions)): + coeff = np.polyfit([-2,-1,1,2], + np.take(array_2b_corrected, 2, axis=i)[np.asarray([0,1,3,4])], + 2) + + peak_value += coeff[2]-coeff[1]**2/(4*coeff[0]) + peak_value /= (i+1) + corr_binned_acf[i_non_corr_1,i_non_corr_2,zero_ind_out]=peak_value + + elif corr_dimension_start == 3: + for i_non_corr_1 in range(corr_binned_acf.shape[0]): + for i_non_corr_2 in range(corr_binned_acf.shape[1]): + for i_non_corr_3 in range(corr_binned_acf.shape[2]): + + array_2b_corrected=np.take(corr_binned_acf,i_non_corr_1,axis=0) + array_2b_corrected=np.take(array_2b_corrected,i_non_corr_2,axis=0) + array_2b_corrected=np.take(array_2b_corrected,i_non_corr_3,axis=0) + peak_value=0. + for i in range(len(correlation_dimensions)): + if zero_ind_out[i]-2 < 0: + raise ValueError("Not enough datapoints for ACF correction.") + ind_correction = np.arange(zero_ind_out[i]-2,zero_ind_out[i]+3) + array_2b_corrected = np.take(array_2b_corrected,ind_correction,axis=i) + + for i in range(len(correlation_dimensions)): + coeff = np.polyfit([-2,-1,1,2], + np.take(array_2b_corrected, 2, axis=i)[np.asarray([0,1,3,4])], + 2) + + peak_value += coeff[2]-coeff[1]**2/(4*coeff[0]) + peak_value /= (i+1) + corr_binned_acf[i_non_corr_1,i_non_corr_2,i_non_corr_3,zero_ind_out]=peak_value + + + # We need to take the zero lag elements from each correlation dimension for i in range(len(correlation_dimensions)): - # Always the cor_dimension_start axis is taken as it is removed by take + # Always the cor_dimension_start axis is taken as it is removed by take corr_binned_acf = np.take(corr_binned_acf,zero_ind_out[i],axis=corr_dimension_start) + if (ref is not None): res_acf_ref = np.fft.ifftn(fft_ref * np.conj(fft_ref),axes=correlation_dimensions_ref) - if (out_dtype is float): res_acf_ref = np.real(res_acf_ref) # we need to move the correlation axes to the start to be consistent with the CCF if len(_coordinate) != 2: - res_acf_ref,ax_map_ref = flap.tools.move_axes_to_start(res_acf_ref,correlation_dimensions_ref) + res_acf_ref,ax_map_ref = flap.tools.move_axes_to_end(res_acf_ref,correlation_dimensions_ref) - corr_acf_ref = np.empty(res_acf_ref.shape,dtype=res_acf.dtype) - if len(_coordinate) == 2: - corr_acf_ref[tuple([ind_out1_acf_ref[0],ind_out1_acf_ref[1]])] = res_acf_ref[tuple([ind_in1_acf_ref[0],ind_in1_acf_ref[1]])] - corr_acf_ref[tuple([ind_out2_acf_ref[0],ind_out1_acf_ref[1]])] = res_acf_ref[tuple([ind_in2_acf_ref[0],ind_in1_acf_ref[1]])] - corr_acf_ref[tuple([ind_out2_acf_ref[0],ind_out2_acf_ref[1]])] = res_acf_ref[tuple([ind_in2_acf_ref[0],ind_in2_acf_ref[1]])] - corr_acf_ref[tuple([ind_out1_acf_ref[0],ind_out2_acf_ref[1]])] = res_acf_ref[tuple([ind_in1_acf_ref[0],ind_in2_acf_ref[1]])] + + if len(correlation_dimensions) == 2: + corr_acf_ref=flap.tools.reorder_2d_ccf_indices(res_acf_ref, + cd, + ind_in1_acf_ref, + ind_in2_acf_ref, + ind_out1_acf_ref, + ind_out2_acf_ref) else: + corr_acf_ref = np.empty(res_acf_ref.shape,dtype=res_acf.dtype) corr_acf_ref[tuple(ind_out1_acf_ref)] = res_acf_ref[tuple(ind_in1_acf_ref)] corr_acf_ref[tuple(ind_out2_acf_ref)] = res_acf_ref[tuple(ind_in2_acf_ref)] @@ -1709,13 +1835,49 @@ def _ccf(d, ref=None, coordinate=None, intervals=None, options=None): np.add.at(corr_binned_acf_ref,tuple(ind_bin_acf_ref),corr_sliced_acf_ref) else: corr_binned_acf_ref=corr_sliced_acf_ref - + + if correct_acf_peak: + + array_2b_corrected=copy.deepcopy(corr_binned_acf_ref) + + peak_value=0. + for i in range(len(correlation_dimensions_ref)): + if zero_ind_out[i]-2 < 0: + raise ValueError("Not enough datapoints for ACF correction.") + ind_correction = np.arange(zero_ind_out[i]-2,zero_ind_out[i]+3) + array_2b_corrected = np.take(array_2b_corrected,ind_correction,axis=i) + if len(correlation_dimensions) == 1: + for i in range(len(correlation_dimensions)): + coeff = np.polyfit([-2,-1,1,2], + array_2b_corrected[np.asarray([0,1,3,4])], + 2) + + peak_value += coeff[2]-coeff[1]**2/(4*coeff[0]) + peak_value /= (i+1) + corr_binned_acf_ref[zero_ind_out]=peak_value + elif len(correlation_dimensions) == 2: + for i in range(len(correlation_dimensions)): + coeff = np.polyfit([-2,-1,1,2], + np.take(array_2b_corrected, 2, axis=i)[np.asarray([0,1,3,4])], + 2) + + peak_value += coeff[2]-coeff[1]**2/(4*coeff[0]) + peak_value /= (i+1) + corr_binned_acf_ref[zero_ind_out]=peak_value + elif len(correlation_dimensions) == 3: + + raise NotImplementedError('3D correlation dimensions has not been implemented yet.') + + # Always the cor_dimension_start axis is taken as it is removed by take # We need to take the zero lag elements from each correlation dimension for i in range(len(correlation_dimensions)): - # Always the cor_dimension_start axis is taken as it is removed by take corr_binned_acf_ref = np.take(corr_binned_acf_ref,zero_ind_out[i],axis=0) + + # for i in range(len(correlation_dimensions)): + # corr_binned_acf_ref = np.take(corr_binned_acf_ref,zero_ind_out[i],axis=0) else: corr_binned_acf_ref = corr_binned_acf + extend_shape = [1] * (len(out_corr.shape) - len(corr_binned_acf.shape)) corr_binned /= np.sqrt(np.reshape(corr_binned_acf,tuple(list(corr_binned_acf.shape) + extend_shape))) extend_shape = [1] * (len(out_corr.shape) - len(corr_binned_acf_ref.shape)) diff --git a/flap/tools.py b/flap/tools.py index e65f228..fdfe299 100644 --- a/flap/tools.py +++ b/flap/tools.py @@ -184,7 +184,7 @@ def submatrix_index(mx_shape, index): # index_arrays.append(ind) for i in range(len(mx_shape)): #THIS IS A SOLUTION FOR LARGE MATRICES, BUT NOT COMMITED - index_arrays.append(slice(min(index[i]),max(index[i])+1)) #DUE TO BEING UNTESTED. NEEDS TO BE UNCOMMENTED IF ONE WANTS TO USE IT + index_arrays.append(slice(min(index[i]),max(index[i])+1)) return tuple(index_arrays) @@ -571,4 +571,113 @@ def unit_conversion(original_unit=None, print('\n') new_unit_translation=1. - return original_unit_translation/new_unit_translation \ No newline at end of file + return original_unit_translation/new_unit_translation + +def polyfit_2D(x=None, + y=None, + values=None, + sigma=None, + order=None, + irregular=False, + return_covariance=False, + return_fit=False): + + if sigma is None: + sigma=np.zeros(values.shape) + sigma[:]=1. + else: + if sigma.shape != sigma.shape: + raise ValueError('The shape of the errors do not match the shape of the values!') + + if not irregular: + if len(values.shape) != 2: + raise ValueError('Values are not 2D') + if x is not None and y is not None: + if x.shape != values.shape or y.shape != values.shape: + raise ValueError('There should be as many points as values and their shape should match.') + if order is None: + raise ValueError('The order is not set.') + if (x is None and y is not None) or (x is not None and y is None): + raise ValueError('Either both or neither x and y need to be set.') + if x is None and y is None: + polynom=np.asarray([[i**k * j**l / sigma[i,j] for k in range(order+1) for l in range(order-k+1)] for i in range(values.shape[0]) for j in range(values.shape[1])]) #The actual polynomial calculation + else: + polynom=np.asarray([[x[i,j]**k * y[i,j]**l / sigma[i,j] for k in range(order+1) for l in range(order-k+1)] for i in range(values.shape[0]) for j in range(values.shape[1])]) #The actual polynomial calculation + + original_shape=values.shape + values_reshape=np.reshape(values/sigma, values.shape[0]*values.shape[1]) + + covariance_matrix=np.linalg.inv(np.dot(polynom.T,polynom)) + + coefficients=np.dot(np.dot(covariance_matrix,polynom.T),values_reshape) #This performs the linear regression + + if not return_fit: + if return_covariance: + return (coefficients, covariance_matrix) + else: + return coefficients + else: + return np.reshape(np.dot(polynom,coefficients),original_shape) + else: + if x.shape != y.shape or x.shape != values.shape: + raise ValueError('The points should be an [n,2] vector.') + if len(x.shape) != 1 or len(y.shape) != 1 or len(values_reshape.shape) != 1: + raise ValueError('x,y,values should be a 1D vector when irregular is set.') + if order is None: + raise ValueError('The order is not set.') + polynom=np.asarray([[x[i]**k * y[i]**l for k in range(order+1) for l in range(order-k+1)] for i in range(values.shape[0])]) #The actual polynomial calculation + if not return_fit: + return np.dot(np.dot(np.linalg.inv(np.dot(polynom.T,polynom)),polynom.T),values) #This performs the linear regression + else: + return np.dot(polynom,np.dot(np.dot(np.linalg.inv(np.dot(polynom.T,polynom)),polynom.T),values)) + +def reorder_2d_ccf_indices(res, #Original result of the ccf calculation from the fft + cd, #Correlation dimensions + ind_in1, #Input indices 1 from 1D + ind_in2, #Input indices 2 from 1D + ind_out1, #Output indices 1 from 1D + ind_out2): #Output indices 2 from 1D + + """ + This helper function reorganizes the 2D cross-correlation or auto-correlation + functions in order to have the maximum in the middle of the matrix and + not somewhere around the corner. Used in _ccf in spectral_analysis.py + """ + + + corr = np.empty(res.shape,dtype=res.dtype) + + ind_out_1=ind_out1[:] + ind_out_2=ind_out1[:] + ind_out_3=ind_out1[:] + ind_out_4=ind_out1[:] + + ind_out_1[cd[0]]=ind_out1[cd[0]] + ind_out_1[cd[1]]=ind_out1[cd[1]] + ind_out_2[cd[0]]=ind_out2[cd[0]] + ind_out_2[cd[1]]=ind_out1[cd[1]] + ind_out_3[cd[0]]=ind_out2[cd[0]] + ind_out_3[cd[1]]=ind_out2[cd[1]] + ind_out_4[cd[0]]=ind_out1[cd[0]] + ind_out_4[cd[1]]=ind_out2[cd[1]] + + ind_in_1=ind_in1[:] + ind_in_2=ind_in1[:] + ind_in_3=ind_in1[:] + ind_in_4=ind_in1[:] + + ind_in_1[cd[0]]=ind_in1[cd[0]] + ind_in_1[cd[1]]=ind_in1[cd[1]] + ind_in_2[cd[0]]=ind_in2[cd[0]] + ind_in_2[cd[1]]=ind_in1[cd[1]] + ind_in_3[cd[0]]=ind_in2[cd[0]] + ind_in_3[cd[1]]=ind_in2[cd[1]] + ind_in_4[cd[0]]=ind_in1[cd[0]] + ind_in_4[cd[1]]=ind_in2[cd[1]] + + corr[tuple(ind_out_1)] = res[tuple(ind_in_1)] + corr[tuple(ind_out_2)] = res[tuple(ind_in_2)] + corr[tuple(ind_out_3)] = res[tuple(ind_in_3)] + corr[tuple(ind_out_4)] = res[tuple(ind_in_4)] + + return corr \ No newline at end of file From a911d8db289bfa0289d4ab77e0fa0dc72f6625fa Mon Sep 17 00:00:00 2001 From: Mate Lampert Date: Wed, 26 May 2021 22:52:17 -0400 Subject: [PATCH 21/27] Deleting commented parts --- flap/plot.py | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/flap/plot.py b/flap/plot.py index c6fd7e2..2401554 100644 --- a/flap/plot.py +++ b/flap/plot.py @@ -41,36 +41,6 @@ from matplotlib.widgets import Button, Slider from matplotlib import ticker -#styled=True -#if styled: -# plt.rc('font', family='serif', serif='Helvetica') -# labelsize=9 -# linewidth=1 -# major_ticksize=2 -# plt.rc('text', usetex=False) -# plt.rcParams['pdf.fonttype'] = 42 -# plt.rcParams['ps.fonttype'] = 42 -# plt.rcParams['lines.linewidth'] = linewidth -# plt.rcParams['axes.linewidth'] = linewidth -# plt.rcParams['axes.labelsize'] = labelsize -# plt.rcParams['axes.titlesize'] = labelsize -# -# plt.rcParams['xtick.labelsize'] = labelsize -# plt.rcParams['xtick.major.size'] = major_ticksize -# plt.rcParams['xtick.major.width'] = linewidth -# plt.rcParams['xtick.minor.width'] = linewidth/2 -# plt.rcParams['xtick.minor.size'] = major_ticksize/2 -# -# plt.rcParams['ytick.labelsize'] = labelsize -# plt.rcParams['ytick.major.width'] = linewidth -# plt.rcParams['ytick.major.size'] = major_ticksize -# plt.rcParams['ytick.minor.width'] = linewidth/2 -# plt.rcParams['ytick.minor.size'] = major_ticksize/2 -# plt.rcParams['legend.fontsize'] = labelsize -#else: -# import matplotlib.style as pltstyle -# pltstyle.use('default') - import numpy as np try: From 385978a2b49e2f3bb9112b6fc75a00854427a77e Mon Sep 17 00:00:00 2001 From: Mate Lampert Date: Thu, 27 May 2021 14:27:02 -0400 Subject: [PATCH 22/27] Bugfix cd was an index variable, should have been correlation_dimensions --- flap/spectral_analysis.py | 6 +++--- flap/tools.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/flap/spectral_analysis.py b/flap/spectral_analysis.py index 46a8377..c5c6401 100644 --- a/flap/spectral_analysis.py +++ b/flap/spectral_analysis.py @@ -1625,7 +1625,7 @@ def _ccf(d, ref=None, coordinate=None, intervals=None, options=None): # where the non-correlating dimensions are left alone. corr=flap.tools.reorder_2d_ccf_indices(res, - cd, + correlation_dimensions, ind_in1, ind_in2, ind_out1, @@ -1686,7 +1686,7 @@ def _ccf(d, ref=None, coordinate=None, intervals=None, options=None): # where the non-correlating dimensions are left alone. corr_acf=flap.tools.reorder_2d_ccf_indices(res_acf, - cd, + correlation_dimensions, ind_in1_acf, ind_in2_acf, ind_out1_acf, @@ -1818,7 +1818,7 @@ def _ccf(d, ref=None, coordinate=None, intervals=None, options=None): if len(correlation_dimensions) == 2: corr_acf_ref=flap.tools.reorder_2d_ccf_indices(res_acf_ref, - cd, + correlation_dimensions, ind_in1_acf_ref, ind_in2_acf_ref, ind_out1_acf_ref, diff --git a/flap/tools.py b/flap/tools.py index fdfe299..c470f0d 100644 --- a/flap/tools.py +++ b/flap/tools.py @@ -651,7 +651,7 @@ def reorder_2d_ccf_indices(res, #Original result of the ccf calculat ind_out_2=ind_out1[:] ind_out_3=ind_out1[:] ind_out_4=ind_out1[:] - + ind_out_1[cd[0]]=ind_out1[cd[0]] ind_out_1[cd[1]]=ind_out1[cd[1]] ind_out_2[cd[0]]=ind_out2[cd[0]] From d4f1aeae8a9a623682e2b679cf627563a5ceea26 Mon Sep 17 00:00:00 2001 From: Mate Lampert Date: Thu, 27 May 2021 17:18:43 -0400 Subject: [PATCH 23/27] Added adjustable fitting range for the photon peak subtraction The option of 'Correct ACF range' was added with a default of [-2,-1,1,2]. --- flap/spectral_analysis.py | 74 +++++++++++++++++++++++++++------------ 1 file changed, 52 insertions(+), 22 deletions(-) diff --git a/flap/spectral_analysis.py b/flap/spectral_analysis.py index c5c6401..909c7eb 100644 --- a/flap/spectral_analysis.py +++ b/flap/spectral_analysis.py @@ -1267,6 +1267,7 @@ def _ccf(d, ref=None, coordinate=None, intervals=None, options=None): 'Error calculation': True, 'Normalize': False, 'Correct ACF peak': False, + 'Correct ACF range':[-2,-1,1,2], 'Verbose':True } _options = flap.config.merge_options(default_options, options, data_source=d.data_source, section='Correlation') @@ -1292,7 +1293,10 @@ def _ccf(d, ref=None, coordinate=None, intervals=None, options=None): norm = _options['Normalize'] correct_acf_peak=_options['Correct ACF peak'] - + correct_acf_range=np.asarray(_options['Correct ACF range']) + if correct_acf_range.shape[0] < 4: + raise ValueError("The range of the correlation peak subtraction should have at least 4 elements, e.g., [-2,-1,1,2]") + error_calc = _options['Error calculation'] if (len(_coordinate) != 1 and (intervals is not None)): raise NotImplementedError("Interval selection for multi-dimensional correlation is not implemented.") @@ -1713,13 +1717,17 @@ def _ccf(d, ref=None, coordinate=None, intervals=None, options=None): for i in range(len(correlation_dimensions)): if zero_ind_out[i]-2 < 0: raise ValueError("Not enough datapoints for ACF correction.") - ind_correction = np.arange(zero_ind_out[i]-2,zero_ind_out[i]+3) + # ind_correction = np.arange(zero_ind_out[i]-2,zero_ind_out[i]+3) + ind_correction=correct_acf_range+zero_ind_out[i] array_2b_corrected = np.take(array_2b_corrected,ind_correction,axis=corr_dimension_start+i) for i in range(len(correlation_dimensions)): - coeff = np.polyfit([-2,-1,1,2], - np.take(array_2b_corrected, 2, axis=corr_dimension_start+i)[np.asarray([0,1,3,4])], - 2) + # coeff = np.polyfit([-2,-1,1,2], + # np.take(array_2b_corrected, 2, axis=corr_dimension_start+i)[np.asarray([0,1,3,4])], + # 2) + coeff = np.polyfit(correct_acf_range, + np.take(array_2b_corrected, 2, axis=corr_dimension_start+i), + 2) peak_value += coeff[2]-coeff[1]**2/(4*coeff[0]) peak_value /= (i+1) @@ -1732,21 +1740,28 @@ def _ccf(d, ref=None, coordinate=None, intervals=None, options=None): for i in range(len(correlation_dimensions)): if zero_ind_out[i]-2 < 0: raise ValueError("Not enough datapoints for ACF correction.") - ind_correction = np.arange(zero_ind_out[i]-2,zero_ind_out[i]+3) + # ind_correction = np.arange(zero_ind_out[i]-2,zero_ind_out[i]+3) + ind_correction=correct_acf_range+zero_ind_out[i] array_2b_corrected = np.take(array_2b_corrected,ind_correction,axis=i) if len(correlation_dimensions) == 1: for i in range(len(correlation_dimensions)): - coeff = np.polyfit([-2,-1,1,2], - array_2b_corrected[np.asarray([0,1,3,4])], - 2) + # coeff = np.polyfit([-2,-1,1,2], + # array_2b_corrected[np.asarray([0,1,3,4])], + # 2) + coeff = np.polyfit(correct_acf_range, + array_2b_corrected, + 2) peak_value += coeff[2]-coeff[1]**2/(4*coeff[0]) peak_value /= (i+1) corr_binned_acf[i_non_corr,zero_ind_out]=peak_value elif len(correlation_dimensions) == 2: for i in range(len(correlation_dimensions)): - coeff = np.polyfit([-2,-1,1,2], - np.take(array_2b_corrected, 2, axis=i)[np.asarray([0,1,3,4])], + # coeff = np.polyfit([-2,-1,1,2], + # np.take(array_2b_corrected, 2, axis=i)[np.asarray([0,1,3,4])], + # 2) + coeff = np.polyfit(correct_acf_range, + np.take(array_2b_corrected, 2, axis=i), 2) peak_value += coeff[2]-coeff[1]**2/(4*coeff[0]) @@ -1765,12 +1780,16 @@ def _ccf(d, ref=None, coordinate=None, intervals=None, options=None): for i in range(len(correlation_dimensions)): if zero_ind_out[i]-2 < 0: raise ValueError("Not enough datapoints for ACF correction.") - ind_correction = np.arange(zero_ind_out[i]-2,zero_ind_out[i]+3) + #ind_correction = np.arange(zero_ind_out[i]-2,zero_ind_out[i]+3) + ind_correction=correct_acf_range+zero_ind_out[i] array_2b_corrected = np.take(array_2b_corrected,ind_correction,axis=i) for i in range(len(correlation_dimensions)): - coeff = np.polyfit([-2,-1,1,2], - np.take(array_2b_corrected, 2, axis=i)[np.asarray([0,1,3,4])], + # coeff = np.polyfit([-2,-1,1,2], + # np.take(array_2b_corrected, 2, axis=i)[np.asarray([0,1,3,4])], + # 2) + coeff = np.polyfit(correct_acf_range, + np.take(array_2b_corrected, 2, axis=i), 2) peak_value += coeff[2]-coeff[1]**2/(4*coeff[0]) @@ -1789,12 +1808,16 @@ def _ccf(d, ref=None, coordinate=None, intervals=None, options=None): for i in range(len(correlation_dimensions)): if zero_ind_out[i]-2 < 0: raise ValueError("Not enough datapoints for ACF correction.") - ind_correction = np.arange(zero_ind_out[i]-2,zero_ind_out[i]+3) + #ind_correction = np.arange(zero_ind_out[i]-2,zero_ind_out[i]+3) + ind_correction=correct_acf_range+zero_ind_out[i] array_2b_corrected = np.take(array_2b_corrected,ind_correction,axis=i) for i in range(len(correlation_dimensions)): - coeff = np.polyfit([-2,-1,1,2], - np.take(array_2b_corrected, 2, axis=i)[np.asarray([0,1,3,4])], + # coeff = np.polyfit([-2,-1,1,2], + # np.take(array_2b_corrected, 2, axis=i)[np.asarray([0,1,3,4])], + # 2) + coeff = np.polyfit(correct_acf_range, + np.take(array_2b_corrected, 2, axis=i), 2) peak_value += coeff[2]-coeff[1]**2/(4*coeff[0]) @@ -1844,12 +1867,16 @@ def _ccf(d, ref=None, coordinate=None, intervals=None, options=None): for i in range(len(correlation_dimensions_ref)): if zero_ind_out[i]-2 < 0: raise ValueError("Not enough datapoints for ACF correction.") - ind_correction = np.arange(zero_ind_out[i]-2,zero_ind_out[i]+3) + #ind_correction = np.arange(zero_ind_out[i]-2,zero_ind_out[i]+3) + ind_correction=correct_acf_range+zero_ind_out[i] array_2b_corrected = np.take(array_2b_corrected,ind_correction,axis=i) if len(correlation_dimensions) == 1: for i in range(len(correlation_dimensions)): - coeff = np.polyfit([-2,-1,1,2], - array_2b_corrected[np.asarray([0,1,3,4])], + # coeff = np.polyfit([-2,-1,1,2], + # array_2b_corrected[np.asarray([0,1,3,4])], + # 2) + coeff = np.polyfit(correct_acf_range, + array_2b_corrected, 2) peak_value += coeff[2]-coeff[1]**2/(4*coeff[0]) @@ -1857,8 +1884,11 @@ def _ccf(d, ref=None, coordinate=None, intervals=None, options=None): corr_binned_acf_ref[zero_ind_out]=peak_value elif len(correlation_dimensions) == 2: for i in range(len(correlation_dimensions)): - coeff = np.polyfit([-2,-1,1,2], - np.take(array_2b_corrected, 2, axis=i)[np.asarray([0,1,3,4])], + # coeff = np.polyfit([-2,-1,1,2], + # np.take(array_2b_corrected, 2, axis=i)[np.asarray([0,1,3,4])], + # 2) + coeff = np.polyfit(correct_acf_range, + np.take(array_2b_corrected, 2, axis=i), 2) peak_value += coeff[2]-coeff[1]**2/(4*coeff[0]) From 4cd77c4e9c55607090e71980e9e3dd078255f530 Mon Sep 17 00:00:00 2001 From: Mate Lampert Date: Thu, 27 May 2021 17:48:35 -0400 Subject: [PATCH 24/27] Added documentation for the ACF peak fitting. --- flap/spectral_analysis.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/flap/spectral_analysis.py b/flap/spectral_analysis.py index 909c7eb..ba518ab 100644 --- a/flap/spectral_analysis.py +++ b/flap/spectral_analysis.py @@ -1255,6 +1255,10 @@ def _ccf(d, ref=None, coordinate=None, intervals=None, options=None): At present trend removal can be applied to 1D CCF only. 'Normalize': Normalize with autocorrelations, that is to calculate correlation instead of covariance. + 'Correct ACF peak': Switch to correct the photon peak of the ACF during normalization. + Default: False + 'Correct ACF range': Indices for the ACF peak correction around the peak. + Default: [-2,-1,1,2] 'Verbose': Print progress messages """ if (d.data is None): From caa5b7b9c79cb3225dabf638d189e2932c3cfcc9 Mon Sep 17 00:00:00 2001 From: Mate Lampert Date: Thu, 3 Jun 2021 18:35:25 -0400 Subject: [PATCH 25/27] Added arrow plotting to plot.py data_object: Added a clarification string into the Error raising. plot: Added the option of plotting a grid of arrows onto the contour, anim-contour or animation. Useful for depiction of velocimetry. A good example is shown in flap_nstx in analyze_hesel_data.py. --- flap/data_object.py | 2 +- flap/plot.py | 220 ++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 215 insertions(+), 7 deletions(-) diff --git a/flap/data_object.py b/flap/data_object.py index 253907e..5aaa15b 100644 --- a/flap/data_object.py +++ b/flap/data_object.py @@ -1478,7 +1478,7 @@ def check_multi_slice(slicing, coord_dim): return False else: return True - raise TypeError("Invalid slicing description.") + raise TypeError("Invalid slicing description, its type is: "+str(type(slicing))) def simple_slice_index(slicing, slicing_coord, data_shape, _options): """ Returns index array which can be used for indexing the selected elements in the diff --git a/flap/plot.py b/flap/plot.py index a805275..999bd7d 100644 --- a/flap/plot.py +++ b/flap/plot.py @@ -417,6 +417,29 @@ def animate_plot(self, it): self.overplot_options['contour'][contour_obj_keys]['data']['Data resampled'][it,:,:], levels=self.overplot_options['contour'][contour_obj_keys]['nlevel'], cmap=self.overplot_options['contour'][contour_obj_keys]['Colormap']) + + for contour_obj_keys in self.overplot_options['arrow']: + if self.overplot_options['arrow'][contour_obj_keys]['Plot']: + + time_index = [slice(0,dim) for dim in self.overplot_options['arrow'][contour_obj_keys]['data']['Data X'].shape] + time_index[self.overplot_options['arrow'][contour_obj_keys]['data']['Time dimension']] = it + time_index = tuple(time_index) + + x_coords=self.overplot_options['arrow'][contour_obj_keys]['data']['X coord'][time_index].flatten()*self.axes_unit_conversion[0] + y_coords=self.overplot_options['arrow'][contour_obj_keys]['data']['Y coord'][time_index].flatten()*self.axes_unit_conversion[1] + data_x=self.overplot_options['arrow'][contour_obj_keys]['data']['Data X'][time_index].flatten()*self.axes_unit_conversion[0] + data_y=self.overplot_options['arrow'][contour_obj_keys]['data']['Data Y'][time_index].flatten()*self.axes_unit_conversion[1] + + for i_coord in range(len(x_coords)): + im = plt.arrow(x_coords[i_coord], + y_coords[i_coord], + data_x[i_coord], + data_y[i_coord], + width=self.overplot_options['arrow'][contour_obj_keys]['width'], + color=self.overplot_options['arrow'][contour_obj_keys]['color'], + length_includes_head=True, + ) + for line_obj_keys in self.overplot_options['line']: xmin, xmax = self.ax_act.get_xbound() ymin, ymax = self.ax_act.get_ybound() @@ -811,7 +834,7 @@ def _plot(data_object, 'Interpolation': Interpolation method for image plot. 'Language': Language of certain standard elements in the plot. ('EN', 'HU') 'Overplot options': Dictionary of overplotting options: - Path, contour or line overplotting for the different plot-types: + Path, contour, arrow or line overplotting for the different plot-types: Dictionary with keys of 'contour', 'path' or 'line': Example options['Overplot options']['contour']: @@ -852,6 +875,33 @@ def _plot(data_object, If 'Horizontal' keyword is present, a line is plottad at Y_coord. Works in all plot types. + Example options['Overplot options']['arrow']: + + options['Overplot options']['arrow']=\ + {'ANAME':{'Data object X':'X_OBJ', + 'Data object Y':'Y_OBJ', + 'Plot':True, + 'Slicing':{'Time':flap.Intervals(t1,t2)} + 'width':0.0001, + 'color':'red', + }} + Can contain multiple ANAME keywords. X_OBJ and Y_OBJ should contain + the same coordinates for the start of the arrow. The length of the + arrow is determined by the data in the two objects and the unit + conversion factors. The head of the arrow is 3 times the width. + Generally this feature is useful for velocimetry plotting. + + + options['Overplot options']['path']=\ + {'ANAME':{'Vertical':[X_coord,'red'], #[X coordinate in the unit of the plot, plot color] + 'Horizontal':[Y_coord,'blue'], #[Y coordinate in the unit of the plot, plot color] + 'Plot':False, + }} + Can contain multiple LNAME (line name) keywords. If 'Vertical' + keyword is present, a line is vertical plotted at X_coord. + If 'Horizontal' keyword is present, a line is plottad at Y_coord. + Works in all plot types. + 'Prevent saturation': Prevents saturation of the video signal when it exceeds zrange[1] It uses data modulo zrange[1] to overcome the saturation. (works for animation) (default: False) @@ -1007,7 +1057,7 @@ def _plot(data_object, (options['Video format'] not in ['avi','mkv', 'mp4'])): raise ValueError("The chosen video format is not cupported on macOS.") if os.sys.platform == 'win32' and _options['Video format'] != 'avi': - raise ValueError("The chosen video format is not cupported on Windowd.") + raise ValueError("The chosen video format is not cupported on Windows.") video_codec_decrypt={'avi':'XVID', 'mkv':'X264', 'mp4':'mp4v'} @@ -1056,6 +1106,7 @@ def _plot(data_object, 'Colormap':None, 'nlevel':51, 'Slicing':None, + 'Arrow':False, }}, 'path':{'NAME':{'Data object X':None, @@ -1068,13 +1119,22 @@ def _plot(data_object, 'line':{'NAME':{'Vertical':[0,'red'], 'Horizontal':[1,'blue'], 'Plot':False, - }} + }}, + + 'arrow':{'NAME':{'Data object X':None, + 'Data object Y':None, + 'Plot':False, + 'Slicing':None, + 'width':0.5, + 'color':'red', + }}, } overplot_options=flap.config.merge_options(default_overplot_options,_options['Overplot options'],data_source=data_object.data_source) #TIME (AXES(2)) INDEPENDENT OVERPLOTTING OBJECT CREATION if plot_type in ['image', 'contour']: + for path_obj_keys in overplot_options['path']: try: x_object_name=overplot_options['path'][path_obj_keys]['Data object X'] @@ -1098,6 +1158,7 @@ def _plot(data_object, if (len(x_object.data.shape) != 1 or len(y_object.data.shape) != 1): raise ValueError("The "+overplot_options['path'][path_obj_keys]['Data object X']+' or ' "the "+overplot_options['path'][path_obj_keys]['Data object Y']+" data is not 1D. Use slicing.") + unit_conversion_coeff=[1]*2 original_units=[x_object.data_unit.unit,y_object.data_unit.unit] for index_coordinate in range(len(d.coordinates)): @@ -1117,9 +1178,9 @@ def _plot(data_object, xy_object=flap.get_data_object(xy_object_name,exp_id=d.exp_id) except: raise ValueError(xy_object_name+'data is not available. The data object needs to be read first.') - if 'Slicing' in overplot_options['path'][path_obj_keys]: + if 'Slicing' in overplot_options['contour'][path_obj_keys]: try: - xy_object.slice_data(slicing=overplot_options['path'][path_obj_keys]['Slicing']) + xy_object.slice_data(slicing=overplot_options['contour'][path_obj_keys]['Slicing']) except: raise ValueError('Slicing did not succeed. Try it outside the plotting!') @@ -1139,6 +1200,46 @@ def _plot(data_object, overplot_options['contour'][contour_obj_keys]['data']['Data']=xy_object.data overplot_options['contour'][contour_obj_keys]['data']['X coord']=xy_object.coordinate(axes[0])[0]*unit_conversion_coeff[0] overplot_options['contour'][contour_obj_keys]['data']['Y coord']=xy_object.coordinate(axes[1])[0]*unit_conversion_coeff[1] + + for contour_obj_keys in overplot_options['arrow']: + if (overplot_options['arrow'][contour_obj_keys]['Plot']): + try: + x_object_name=overplot_options['arrow'][contour_obj_keys]['Data object X'] + x_object=flap.get_data_object(x_object_name,exp_id=d.exp_id) + + y_object_name=overplot_options['arrow'][contour_obj_keys]['Data object Y'] + y_object=flap.get_data_object(y_object_name,exp_id=d.exp_id) + except: + raise ValueError(x_object_name+' or '+y_object_name+' data is not available. The data object needs to be read first.') + + if 'Slicing' in overplot_options['arrow'][path_obj_keys]: + try: + x_object.slice_data(slicing=overplot_options['arrow'][path_obj_keys]['Slicing']) + y_object.slice_data(slicing=overplot_options['arrow'][path_obj_keys]['Slicing']) + except: + raise ValueError('Slicing did not succeed. Try it outside the plotting!') + + if len(x_object.data.shape) != 2 or len(y_object.data.shape): + raise ValueError('Arrow object\'s, ('+x_object_name+','+y_object_name+') data need to be a 2D matrix.') + if (x_object.coordinate(axes[0])[0] != y_object.coordinate(axes[0])[0] or + x_object.coordinate(axes[1])[0] != y_object.coordinate(axes[1])[0]): + raise ValueError('The x or y coordinates of the x and y arrow objects do not match.') + + unit_conversion_coeff=[1.] * 2 + for index_data_coordinate in range(len(d.coordinates)): + for index_oplot_coordinate in range(len(x_object.coordinates)): + for index_axes in range(2): + if ((d.coordinates[index_data_coordinate].unit.name == axes[index_axes]) and + (x_object.coordinates[index_oplot_coordinate].unit.name) == axes[index_axes]): + unit_conversion_coeff[index_axes] = flap.tools.unit_conversion(original_unit=x_object.coordinates[index_oplot_coordinate].unit.unit, + new_unit=d.coordinates[index_data_coordinate].unit.unit) + + overplot_options['arrow'][contour_obj_keys]['data']={} + overplot_options['arrow'][contour_obj_keys]['data']['Data X']=x_object.data*unit_conversion_coeff[0] + overplot_options['arrow'][contour_obj_keys]['data']['Data Y']=y_object.data*unit_conversion_coeff[1] + overplot_options['arrow'][contour_obj_keys]['data']['X coord']=x_object.coordinate(axes[0])[0]*unit_conversion_coeff[0] + overplot_options['arrow'][contour_obj_keys]['data']['Y coord']=y_object.coordinate(axes[1])[0]*unit_conversion_coeff[1] + #TIME (AXES(2)) DEPENDENT OVERPLOTTING OBJECT CREATION if plot_type in ['anim-image','anim-contour','animation'] : for index_time in range(len(d.coordinates)): @@ -1224,7 +1325,7 @@ def _plot(data_object, except: raise ValueError(xy_object_name+'data is not available. The data object needs to be read first.') if len(xy_object.data.shape) != 3: - raise ValueError('Contour object\'s, ('+xy_object_name+') data needs to be a 3D matrix (x,y,temporal), not necessarily in this order.') + raise ValueError('Contour object\'s, ('+xy_object_name+') data needs to be a 3D matrix (x,y,t), not necessarily in this order.') for index_coordinate in range(len(xy_object.coordinates)): if (xy_object.coordinates[index_coordinate].unit.name == axes[2]): @@ -1248,18 +1349,73 @@ def _plot(data_object, slicing={axes[2]:tdata}, options={'Interpolation':'Linear'}, output_name='XY OBJ INTERP') + overplot_options['contour'][contour_obj_keys]['data']={} overplot_options['contour'][contour_obj_keys]['data']['Data resampled']=xy_object_interp.data overplot_options['contour'][contour_obj_keys]['data']['X coord resampled']=xy_object_interp.coordinate(axes[0])[0]*unit_conversion_coeff[0] overplot_options['contour'][contour_obj_keys]['data']['Y coord resampled']=xy_object_interp.coordinate(axes[1])[0]*unit_conversion_coeff[1] overplot_options['contour'][contour_obj_keys]['data'][axes[2]]=tdata overplot_options['contour'][contour_obj_keys]['data']['Time dimension']=time_dimension_index + + for contour_obj_keys in overplot_options['arrow']: + if (overplot_options['arrow'][contour_obj_keys]['Plot']): + #try: + if True: + x_object_name=overplot_options['arrow'][contour_obj_keys]['Data object X'] + x_object=flap.get_data_object(x_object_name,exp_id=d.exp_id) + y_object_name=overplot_options['arrow'][contour_obj_keys]['Data object Y'] + y_object=flap.get_data_object(y_object_name,exp_id=d.exp_id) + #except: + # raise ValueError(x_object_name+' or '+y_object_name+' data is not available. The data object needs to be read first.') + if len(x_object.data.shape) != 3 or len(y_object.data.shape) != 3: + raise ValueError('Arrow object\'s, ('+x_object_name+','+y_object_name+') data needs to be a 3D matrix (x,y,t), not necessarily in this order.') + + for index_coordinate in range(len(x_object.coordinates)): + if (x_object.coordinates[index_coordinate].unit.name == axes[2]): + time_dimension_index=x_object.coordinates[index_coordinate].dimension_list[0] + oplot_time_index_coordinate=index_coordinate + + if (d.coordinates[data_time_index_coordinate].unit.unit != x_object.coordinates[oplot_time_index_coordinate].unit.unit or + d.coordinates[data_time_index_coordinate].unit.unit != y_object.coordinates[oplot_time_index_coordinate].unit.unit): + raise TypeError('The '+axes[2]+' unit of the overplotted contour object differs from the data\'s. Interpolation cannot be done.') + + if (np.sum(x_object.coordinate(axes[0])[0] - y_object.coordinate(axes[0])[0]) != 0 or + np.sum(x_object.coordinate(axes[1])[0] - y_object.coordinate(axes[1])[0]) != 0): + raise ValueError('The x or y coordinates of the x and y arrow objects do not match.') + + unit_conversion_coeff=[1.] * 3 + for index_data_coordinate in range(len(d.coordinates)): + for index_oplot_coordinate in range(len(x_object.coordinates)): + for index_axes in range(3): + if ((d.coordinates[index_data_coordinate].unit.name == axes[index_axes]) and + (x_object.coordinates[index_oplot_coordinate].unit.name) == axes[index_axes]): + unit_conversion_coeff[index_axes] = flap.tools.unit_conversion(original_unit=x_object.coordinates[index_oplot_coordinate].unit.unit, + new_unit=d.coordinates[index_data_coordinate].unit.unit) + x_object_interp=flap.slice_data(x_object_name, + exp_id=d.exp_id, + slicing={axes[2]:tdata}, + options={'Interpolation':'Linear'}) + y_object_interp=flap.slice_data(y_object_name, + exp_id=d.exp_id, + slicing={axes[2]:tdata}, + options={'Interpolation':'Linear'}) + + overplot_options['arrow'][contour_obj_keys]['data']={} + overplot_options['arrow'][contour_obj_keys]['data']['Data X']=x_object_interp.data*unit_conversion_coeff[0] + overplot_options['arrow'][contour_obj_keys]['data']['Data Y']=y_object_interp.data*unit_conversion_coeff[1] + + overplot_options['arrow'][contour_obj_keys]['data']['X coord']=x_object_interp.coordinate(axes[0])[0]*unit_conversion_coeff[0] + overplot_options['arrow'][contour_obj_keys]['data']['Y coord']=y_object_interp.coordinate(axes[1])[0]*unit_conversion_coeff[1] + + overplot_options['arrow'][contour_obj_keys]['data'][axes[2]]=tdata + overplot_options['arrow'][contour_obj_keys]['data']['Time dimension']=time_dimension_index # X range and Z range is processed here, but Y range not as it might have multiple entries for some plots xrange = _options['X range'] if (xrange is not None): if ((type(xrange) is not list) or (len(xrange) != 2)): raise ValueError("Invalid X range setting.") + zrange = _options['Z range'] if (zrange is not None): if ((type(zrange) is not list) or (len(zrange) != 2)): @@ -2093,6 +2249,35 @@ def _plot(data_object, overplot_options['contour'][contour_obj_keys]['data']['Data'], levels=overplot_options['contour'][contour_obj_keys]['nlevel'], cmap=overplot_options['contour'][contour_obj_keys]['Colormap']) + + for contour_obj_keys in overplot_options['arrow']: + if overplot_options['arrow'][contour_obj_keys]['Plot']: + + x_coords=overplot_options['arrow'][contour_obj_keys]['data']['X coord'].flatten() + y_coords=overplot_options['arrow'][contour_obj_keys]['data']['Y coord'].flatten() + data_x=overplot_options['arrow'][contour_obj_keys]['data']['Data X'].flatten() + data_y=overplot_options['arrow'][contour_obj_keys]['data']['Data Y'].flatten() + if 'width' in overplot_options['arrow'][contour_obj_keys].keys(): + arrow_width=overplot_options['arrow'][contour_obj_keys]['width'] + else: + arrow_width=0.001 + if 'color' in overplot_options['arrow'][contour_obj_keys].keys(): + arrow_color=overplot_options['arrow'][contour_obj_keys]['color'] + else: + arrow_color='black' + + for i_coord in range(len(x_coords)): + ax.arrow(x_coords[i_coord], + y_coords[i_coord], + data_x[i_coord], + data_y[i_coord], + width=arrow_width, + color=arrow_color, + length_includes_head=True, + head_width=arrow_width*3, + head_length=arrow_width*3, + ) + for line_obj_keys in overplot_options['line']: xmin, xmax = ax.get_xbound() ymin, ymax = ax.get_ybound() @@ -2401,6 +2586,29 @@ def _plot(data_object, overplot_options['contour'][contour_obj_keys]['data']['Data resampled'][time_index], levels=overplot_options['contour'][contour_obj_keys]['nlevel'], cmap=overplot_options['contour'][contour_obj_keys]['Colormap']) + + + for contour_obj_keys in overplot_options['arrow']: + if overplot_options['arrow'][contour_obj_keys]['Plot']: + + time_index = [slice(0,dim) for dim in overplot_options['arrow'][contour_obj_keys]['data']['Data X'].shape] + time_index[overplot_options['arrow'][contour_obj_keys]['data']['Time dimension']] = it + time_index = tuple(time_index) + + x_coords=overplot_options['arrow'][contour_obj_keys]['data']['X coord'][time_index].flatten()*axes_unit_conversion[0] + y_coords=overplot_options['arrow'][contour_obj_keys]['data']['Y coord'][time_index].flatten()*axes_unit_conversion[1] + data_x=overplot_options['arrow'][contour_obj_keys]['data']['Data X'][time_index].flatten()*axes_unit_conversion[0] + data_y=overplot_options['arrow'][contour_obj_keys]['data']['Data Y'][time_index].flatten()*axes_unit_conversion[1] + + for i_coord in range(len(x_coords)): + plt.arrow(x_coords[i_coord], + y_coords[i_coord], + data_x[i_coord], + data_y[i_coord], + width=overplot_options['arrow'][contour_obj_keys]['width'], + color=overplot_options['arrow'][contour_obj_keys]['color'], + length_includes_head=True, + ) for line_obj_keys in overplot_options['line']: xmin, xmax = ax_act.get_xbound() From 42c6d4a1aaff4fc9dbca4610bb0cf3420a78e916 Mon Sep 17 00:00:00 2001 From: Mate Lampert Date: Thu, 2 Jun 2022 16:55:32 +0200 Subject: [PATCH 26/27] Update __init__.py --- flap/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/flap/__init__.py b/flap/__init__.py index b9e9128..4546166 100644 --- a/flap/__init__.py +++ b/flap/__init__.py @@ -1,5 +1,7 @@ global VERBOSE VERBOSE = True +if (VERBOSE): + print("Importing flap") from .tools import * from .coordinate import * from .data_object import * From 84ad5d8b4135e248e4b9e7a8ec000dc709f1d97f Mon Sep 17 00:00:00 2001 From: Mate Lampert Date: Fri, 19 Aug 2022 01:34:50 -0400 Subject: [PATCH 27/27] Update .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index f2e4e1b..3661626 100644 --- a/.gitignore +++ b/.gitignore @@ -107,3 +107,5 @@ venv.bak/ # VSCOde .vs* +flap_mdsplus/ +flap_nstx/