def show_shpere(self, x, radius=70.0, x_range=70.0): """ """ # Show P(r) y_true = numpy.zeros(len(x)) sum_true = 0.0 for i in range(len(x)): y_true[i] = self.pr_theory(x[i], radius) sum_true += y_true[i] y_true = y_true / sum_true * x_range / len(x) # Show the theory P(r) new_plot = Data1D(x, y_true) new_plot.symbol = GUIFRAME_ID.CURVE_SYMBOL_NUM new_plot.name = "P_{true}(r)" new_plot.xaxis("\\rm{r}", 'A') new_plot.yaxis("\\rm{P(r)} ", "cm^{-3}") new_plot.id = "P_{true}(r)" new_plot.group_id = "P_{true}(r)" self.parent.update_theory(data_id=self.data_id, theory=new_plot) #Put this call in plottables/guitools wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title="Sphere P(r)"))
def plot_theory(self, data=None, name=None): """ Receive a data set and post a NewPlotEvent to parent. :param data: extrapolated data to be plotted :param name: Data's name to use for the legend """ # import copy if data is None: id = str(self.__data.id) + name group_id = self.__data.group_id wx.PostEvent(self.parent, NewPlotEvent(id=id, group_id=group_id, action='Remove')) return new_plot = Data1D(x=[], y=[], dy=None) new_plot.symbol = GUIFRAME_ID.CURVE_SYMBOL_NUM scale = self.invariant_panel.get_scale() background = self.invariant_panel.get_background() if scale != 0: # Put back the sacle and bkg for plotting data.y = (data.y + background) / scale new_plot = Data1D(x=data.x, y=data.y, dy=None) new_plot.symbol = GUIFRAME_ID.CURVE_SYMBOL_NUM else: msg = "Scale can not be zero." raise ValueError, msg if len(new_plot.x) == 0: return new_plot.name = name new_plot.xaxis(self.__data._xaxis, self.__data._xunit) new_plot.yaxis(self.__data._yaxis, self.__data._yunit) new_plot.group_id = self.__data.group_id new_plot.id = str(self.__data.id) + name new_plot.title = self.__data.title # Save theory_data in a state if data != None: name_head = name.split('-') if name_head[0] == 'Low': self.invariant_panel.state.theory_lowQ = copy.deepcopy(new_plot) elif name_head[0] == 'High': self.invariant_panel.state.theory_highQ = copy.deepcopy(new_plot) self.parent.update_theory(data_id=self.__data.id, theory=new_plot) wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title=self.__data.title))
def _post_data(self, nbins=None): """ compute sector averaging of data2D into data1D :param nbins: the number of point to plot for the average 1D data """ ## get the data2D to average data = self.base.data2D # If we have no data, just return if data == None: return ## Averaging from sas.dataloader.manipulations import SectorQ radius = self.qmax phimin = -self.left_line.phi + self.main_line.theta phimax = self.left_line.phi + self.main_line.theta if nbins == None: nbins = 20 sect = SectorQ(r_min=0.0, r_max=radius, phi_min=phimin + math.pi, phi_max=phimax + math.pi, nbins=nbins) sector = sect(self.base.data2D) ##Create 1D data resulting from average if hasattr(sector, "dxl"): dxl = sector.dxl else: dxl = None if hasattr(sector, "dxw"): dxw = sector.dxw else: dxw = None new_plot = Data1D(x=sector.x, y=sector.y, dy=sector.dy, dx=sector.dx) new_plot.dxl = dxl new_plot.dxw = dxw new_plot.name = "SectorQ" + "(" + self.base.data2D.name + ")" new_plot.source = self.base.data2D.source #new_plot.info=self.base.data2D.info new_plot.interactive = True new_plot.detector = self.base.data2D.detector ## If the data file does not tell us what the axes are, just assume... new_plot.xaxis("\\rm{Q}", "A^{-1}") new_plot.yaxis("\\rm{Intensity}", "cm^{-1}") if hasattr(data, "scale") and data.scale == 'linear' and \ self.base.data2D.name.count("Residuals") > 0: new_plot.ytransform = 'y' new_plot.yaxis("\\rm{Residuals} ", "/") new_plot.group_id = "2daverage" + self.base.data2D.name new_plot.id = "SectorQ" + self.base.data2D.name new_plot.is_data = True self.base.parent.update_theory(data_id=data.id, theory=new_plot) wx.PostEvent( self.base.parent, NewPlotEvent(plot=new_plot, title="SectorQ" + self.base.data2D.name))
def set_state(self, state=None, datainfo=None): """ Call-back method for the inversion state reader. This method is called when a .prv file is loaded. :param state: InversionState object :param datainfo: Data1D object [optional] """ try: if datainfo.__class__.__name__ == 'list': if len(datainfo) >= 1: data = datainfo[0] else: data = None else: data = datainfo if data is None: msg = "Pr.set_state: datainfo parameter cannot " msg += "be None in standalone mode" raise RuntimeError, msg # Ensuring that plots are coordinated correctly t = time.localtime(data.meta_data['prstate'].timestamp) time_str = time.strftime("%b %d %H:%M", t) # Check that no time stamp is already appended max_char = data.meta_data['prstate'].file.find("[") if max_char < 0: max_char = len(data.meta_data['prstate'].file) datainfo.meta_data['prstate'].file = \ data.meta_data['prstate'].file[0:max_char]\ + ' [' + time_str + ']' data.filename = data.meta_data['prstate'].file # TODO: #remove this call when state save all information about the gui data # such as ID , Group_ID, etc... #make self.current_plottable = datainfo directly self.current_plottable = self.parent.create_gui_data(data, None) self.current_plottable.group_id = data.meta_data['prstate'].file # Make sure the user sees the P(r) panel after loading #self.parent.set_perspective(self.perspective) self.on_perspective(event=None) # Load the P(r) results #state = self.state_reader.get_state() data_dict = {self.current_plottable.id: self.current_plottable} self.parent.add_data(data_list=data_dict) wx.PostEvent( self.parent, NewPlotEvent(plot=self.current_plottable, title=self.current_plottable.title)) self.control_panel.set_state(state) except: logging.error("prview.set_state: %s" % sys.exc_value)
def post_data(self, new_sector): """ post data averaging in Q""" if self.inner_circle.get_radius() < self.outer_circle.get_radius(): rmin = self.inner_circle.get_radius() rmax = self.outer_circle.get_radius() else: rmin = self.outer_circle.get_radius() rmax = self.inner_circle.get_radius() if self.right_edge.get_angle() < self.left_edge.get_angle(): phimin = self.right_edge.get_angle() phimax = self.left_edge.get_angle() else: phimin = self.left_edge.get_angle() phimax = self.right_edge.get_angle() # print "phimin, phimax, rmin ,rmax",math.degrees(phimin), # math.degrees(phimax), rmin ,rmax # from sas.dataloader.manipulations import SectorQ sect = new_sector(r_min=rmin, r_max=rmax, phi_min=phimin, phi_max=phimax) sector = sect(self.base.data2D) from sas.guiframe.dataFitting import Data1D if hasattr(sector, "dxl"): dxl = sector.dxl else: dxl = None if hasattr(sector, "dxw"): dxw = sector.dxw else: dxw = None new_plot = Data1D(x=sector.x, y=sector.y, dy=sector.dy, dxl=dxl, dxw=dxw) new_plot.name = str(new_sector.__name__) + \ "(" + self.base.data2D.name + ")" new_plot.source = self.base.data2D.source new_plot.interactive = True # print "loader output.detector",output.source new_plot.detector = self.base.data2D.detector # If the data file does not tell us what the axes are, just assume... new_plot.xaxis("\\rm{Q}", 'rad') new_plot.yaxis("\\rm{Intensity} ", "cm^{-1}") new_plot.group_id = str(new_sector.__name__) + self.base.data2D.name self.base.parent.update_theory(data_id=self.base.data2D.id, \ theory=new_plot) wx.PostEvent( self.base.parent, NewPlotEvent(plot=new_plot, title=str(new_sector.__name__)))
def show_pr(self, out, pr, cov=None): """ """ # Show P(r) x = pylab.arange(0.0, pr.d_max, pr.d_max / self._pr_npts) y = numpy.zeros(len(x)) dy = numpy.zeros(len(x)) y_true = numpy.zeros(len(x)) total = 0.0 pmax = 0.0 cov2 = numpy.ascontiguousarray(cov) for i in range(len(x)): if cov2 == None: value = pr.pr(out, x[i]) else: (value, dy[i]) = pr.pr_err(out, cov2, x[i]) total += value * pr.d_max / len(x) # keep track of the maximum P(r) value if value > pmax: pmax = value y[i] = value if self._normalize_output == True: y = y / total dy = dy / total elif self._scale_output_unity == True: y = y / pmax dy = dy / pmax if cov2 == None: new_plot = Data1D(x, y) new_plot.symbol = GUIFRAME_ID.CURVE_SYMBOL_NUM else: new_plot = Data1D(x, y, dy=dy) new_plot.name = PR_FIT_LABEL new_plot.xaxis("\\rm{r}", 'A') new_plot.yaxis("\\rm{P(r)} ", "cm^{-3}") new_plot.title = "P(r) fit" new_plot.id = PR_FIT_LABEL # Make sure that the plot is linear new_plot.xtransform = "x" new_plot.ytransform = "y" new_plot.group_id = GROUP_ID_PR_FIT self.parent.update_theory(data_id=self.data_id, theory=new_plot) wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title="P(r) fit")) return x, pr.d_max
def _fit_pr(self, evt): """ """ # Generate P(r) for sphere radius = 60.0 d_max = 2 * radius r = pylab.arange(0.01, d_max, d_max / 51.0) M = len(r) y = numpy.zeros(M) pr_err = numpy.zeros(M) total = 0.0 for j in range(M): value = self.pr_theory(r[j], radius) total += value y[j] = value pr_err[j] = math.sqrt(y[j]) y = y / total * d_max / len(r) # Perform fit pr = Invertor() pr.d_max = d_max pr.alpha = 0 pr.x = r pr.y = y pr.err = pr_err out, cov = pr.pr_fit() for i in range(len(out)): print "%g +- %g" % (out[i], math.sqrt(cov[i][i])) # Show input P(r) title = "Pr" new_plot = Data1D(pr.x, pr.y, dy=pr.err) new_plot.name = "P_{obs}(r)" new_plot.xaxis("\\rm{r}", 'A') new_plot.yaxis("\\rm{P(r)} ", "cm^{-3}") new_plot.group_id = "P_{obs}(r)" new_plot.id = "P_{obs}(r)" new_plot.title = title self.parent.update_theory(data_id=self.data_id, theory=new_plot) wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title=title)) # Show P(r) fit self.show_pr(out, pr) # Show I(q) fit q = pylab.arange(0.001, 0.1, 0.01 / 51.0) self.show_iq(out, pr, q)
def plot_data(self, scale, background): """ replot the current data if the user enters a new scale or background """ new_plot = scale * self.__data - background new_plot.name = self.__data.name new_plot.group_id = self.__data.group_id new_plot.id = self.__data.id new_plot.title = self.__data.title # Save data in a state: but seems to never happen if new_plot != None: self.invariant_panel.state.data = copy.deepcopy(new_plot) wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title=new_plot.title))
def set_state(self, state=None, datainfo=None): """ Call-back method for the state reader. This method is called when a .inv/.svs file is loaded. :param state: State object """ self.temp_state = None try: if datainfo.__class__.__name__ == 'list': data = datainfo[0] else: data = datainfo if data is None: msg = "invariant.set_state: datainfo parameter cannot" msg += " be None in standalone mode" raise RuntimeError, msg # Make sure the user sees the invariant panel after loading # self.parent.set_perspective(self.perspective) self.on_perspective(event=None) name = data.meta_data['invstate'].file data.meta_data['invstate'].file = name data.name = name data.filename = name data = self.parent.create_gui_data(data, None) self.__data = data wx.PostEvent( self.parent, NewPlotEvent(plot=self.__data, reset=True, title=self.__data.title)) data_dict = {self.__data.id: self.__data} self.parent.add_data(data_list=data_dict) # set state self.invariant_panel.is_state_data = True # Load the invariant states self.temp_state = state # Requires to have self.__data and self.temp_state first. self.on_set_state_helper(None) except: logging.error("invariant.set_state: %s" % sys.exc_value)
def show_data(self, path=None, data=None, reset=False): """ Show data read from a file :param path: file path :param reset: if True all other plottables will be cleared """ #if path is not None: if data is not None: try: pr = self._create_file_pr(data) except: status = "Problem reading data: %s" % sys.exc_value wx.PostEvent(self.parent, StatusEvent(status=status)) raise RuntimeError, status # If the file contains nothing, just return if pr is None: raise RuntimeError, "Loaded data is invalid" self.pr = pr # Make a plot of I(q) data if self.pr.err == None: new_plot = Data1D(self.pr.x, self.pr.y) new_plot.symbol = GUIFRAME_ID.CURVE_SYMBOL_NUM else: new_plot = Data1D(self.pr.x, self.pr.y, dy=self.pr.err) new_plot.name = IQ_DATA_LABEL new_plot.xaxis("\\rm{Q}", 'A^{-1}') new_plot.yaxis("\\rm{Intensity} ", "cm^{-1}") new_plot.interactive = True new_plot.group_id = GROUP_ID_IQ_DATA new_plot.id = self.data_id new_plot.title = "I(q)" wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title="I(q)", reset=reset)) self.current_plottable = new_plot # Get Q range self.control_panel.q_min = min(self.pr.x) self.control_panel.q_max = max(self.pr.x)
def _on_disable_scaling(self, evt): """ Disable P(r) scaling :param evt: Menu event """ self._normalize_output = False self._scale_output_unity = False self.show_pr(self._last_out, self._last_pr, self._last_cov) # Now replot the original added data for plot in self._added_plots: self._added_plots[plot].y = numpy.copy(self._default_Iq[plot]) wx.PostEvent( self.parent, NewPlotEvent(plot=self._added_plots[plot], title=self._added_plots[plot].name, update=True))
def set_data(self, data_list=None): """ receive a list of data and compute invariant """ msg = "" data = None if data_list is None: data_list = [] if len(data_list) >= 1: if len(data_list) == 1: data = data_list[0] else: data_1d_list = [] data_2d_list = [] error_msg = "" # separate data into data1d and data2d list for data in data_list: if data is not None: if issubclass(data.__class__, Data1D): data_1d_list.append(data) else: error_msg += " %s type %s \n" % (str(data.name), str(data.__class__.__name__)) data_2d_list.append(data) if len(data_2d_list) > 0: msg = "Invariant does not support the following data types:\n" msg += error_msg if len(data_1d_list) == 0: wx.PostEvent(self.parent, StatusEvent(status=msg, info='error')) return msg += "Invariant panel does not allow multiple data!\n" msg += "Please select one.\n" if len(data_list) > 1: from invariant_widgets import DataDialog dlg = DataDialog(data_list=data_1d_list, text=msg) if dlg.ShowModal() == wx.ID_OK: data = dlg.get_data() else: data = None dlg.Destroy() if data is None: msg += "invariant receives no data. \n" wx.PostEvent(self.parent, StatusEvent(status=msg, info='error')) return if not issubclass(data.__class__, Data1D): msg += "invariant cannot be computed for data of " msg += "type %s\n" % (data.__class__.__name__) wx.PostEvent(self.parent, StatusEvent(status=msg, info='error')) return else: wx.PostEvent(self.parent, NewPlotEvent(plot=data, title=data.title)) try: self.compute_helper(data) except: msg = "Invariant Set_data: " + str(sys.exc_value) wx.PostEvent(self.parent, StatusEvent(status=msg, info="error")) else: msg = "invariant cannot be computed for data of " msg += "type %s" % (data.__class__.__name__) wx.PostEvent(self.parent, StatusEvent(status=msg, info='error'))
def show_iq(self, out, pr, q=None): """ Display computed I(q) """ qtemp = pr.x if not q == None: qtemp = q # Make a plot maxq = -1 for q_i in qtemp: if q_i > maxq: maxq = q_i minq = 0.001 # Check for user min/max if not pr.q_min == None: minq = pr.q_min if not pr.q_max == None: maxq = pr.q_max x = pylab.arange(minq, maxq, maxq / 301.0) y = numpy.zeros(len(x)) err = numpy.zeros(len(x)) for i in range(len(x)): value = pr.iq(out, x[i]) y[i] = value try: err[i] = math.sqrt(math.fabs(value)) except: err[i] = 1.0 print "Error getting error", value, x[i] new_plot = Data1D(x, y) new_plot.symbol = GUIFRAME_ID.CURVE_SYMBOL_NUM new_plot.name = IQ_FIT_LABEL new_plot.xaxis("\\rm{Q}", 'A^{-1}') new_plot.yaxis("\\rm{Intensity} ", "cm^{-1}") title = "I(q)" new_plot.title = title # If we have a group ID, use it if pr.info.has_key("plot_group_id"): new_plot.group_id = pr.info["plot_group_id"] new_plot.id = IQ_FIT_LABEL self.parent.update_theory(data_id=self.data_id, theory=new_plot) wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title=title)) # If we have used slit smearing, plot the smeared I(q) too if pr.slit_width > 0 or pr.slit_height > 0: x = pylab.arange(minq, maxq, maxq / 301.0) y = numpy.zeros(len(x)) err = numpy.zeros(len(x)) for i in range(len(x)): value = pr.iq_smeared(out, x[i]) y[i] = value try: err[i] = math.sqrt(math.fabs(value)) except: err[i] = 1.0 print "Error getting error", value, x[i] new_plot = Data1D(x, y) new_plot.symbol = GUIFRAME_ID.CURVE_SYMBOL_NUM new_plot.name = IQ_SMEARED_LABEL new_plot.xaxis("\\rm{Q}", 'A^{-1}') new_plot.yaxis("\\rm{Intensity} ", "cm^{-1}") # If we have a group ID, use it if pr.info.has_key("plot_group_id"): new_plot.group_id = pr.info["plot_group_id"] new_plot.id = IQ_SMEARED_LABEL new_plot.title = title self.parent.update_theory(data_id=self.data_id, theory=new_plot) wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title=title))
def set_data(self, data_list=None): """ receive a list of data to compute pr """ if data_list is None: data_list = [] if len(data_list) >= 1: if len(data_list) == 1: data = data_list[0] else: data_1d_list = [] data_2d_list = [] error_msg = "" # separate data into data1d and data2d list for data in data_list: if data is not None: if issubclass(data.__class__, Data1D): data_1d_list.append(data) else: error_msg += " %s type %s \n" % (str( data.name), str(data.__class__.__name__)) data_2d_list.append(data) if len(data_2d_list) > 0: msg = "PrView does not support the following data types:\n" msg += error_msg if len(data_1d_list) == 0: wx.PostEvent(self.parent, StatusEvent(status=msg, info='error')) return msg += "Prview does not allow multiple data!\n" msg += "Please select one.\n" if len(data_list) > 1: from pr_widgets import DataDialog dlg = DataDialog(data_list=data_1d_list, text=msg) if dlg.ShowModal() == wx.ID_OK: data = dlg.get_data() else: data = None dlg.Destroy() if data is None: msg += "PrView receives no data. \n" wx.PostEvent(self.parent, StatusEvent(status=msg, info='error')) return if issubclass(data.__class__, Data1D): try: wx.PostEvent( self.parent, NewPlotEvent(action='remove', group_id=GROUP_ID_IQ_DATA, id=self.data_id)) self.data_id = data.id self.control_panel._change_file(evt=None, data=data) except: msg = "Prview Set_data: " + str(sys.exc_value) wx.PostEvent(self.parent, StatusEvent(status=msg, info="error")) else: msg = "Pr cannot be computed for data of " msg += "type %s" % (data_list[0].__class__.__name__) wx.PostEvent(self.parent, StatusEvent(status=msg, info='error')) else: msg = "Pr contain no data" wx.PostEvent(self.parent, StatusEvent(status=msg, info='warning'))
def post_data(self, new_slab=None, nbins=None, direction=None): """ post data averaging in Qx or Qy given new_slab type :param new_slab: slicer that determine with direction to average :param nbins: the number of points plotted when averaging :param direction: the direction of averaging """ if self.direction == None: self.direction = direction x_min = -1 * math.fabs(self.vertical_lines.x) x_max = math.fabs(self.vertical_lines.x) y_min = -1 * math.fabs(self.horizontal_lines.y) y_max = math.fabs(self.horizontal_lines.y) if nbins != None: self.nbins = nbins if self.averager == None: if new_slab == None: msg = "post data:cannot average , averager is empty" raise ValueError, msg self.averager = new_slab if self.direction == "X": if self.fold: x_low = 0 else: x_low = math.fabs(x_min) bin_width = (x_max + x_low) / self.nbins elif self.direction == "Y": if self.fold: y_low = 0 else: y_low = math.fabs(y_min) bin_width = (y_max + y_low) / self.nbins else: msg = "post data:no Box Average direction was supplied" raise ValueError, msg # # Average data2D given Qx or Qy box = self.averager(x_min=x_min, x_max=x_max, y_min=y_min, y_max=y_max, bin_width=bin_width) box.fold = self.fold boxavg = box(self.base.data2D) # 3 Create Data1D to plot if hasattr(boxavg, "dxl"): dxl = boxavg.dxl else: dxl = None if hasattr(boxavg, "dxw"): dxw = boxavg.dxw else: dxw = None new_plot = Data1D(x=boxavg.x, y=boxavg.y, dy=boxavg.dy) new_plot.dxl = dxl new_plot.dxw = dxw new_plot.name = str(self.averager.__name__) + \ "(" + self.base.data2D.name + ")" new_plot.source = self.base.data2D.source new_plot.interactive = True new_plot.detector = self.base.data2D.detector # # If the data file does not tell us what the axes are, just assume... new_plot.xaxis("\\rm{Q}", "A^{-1}") new_plot.yaxis("\\rm{Intensity} ", "cm^{-1}") data = self.base.data2D if hasattr(data, "scale") and data.scale == 'linear' and \ self.base.data2D.name.count("Residuals") > 0: new_plot.ytransform = 'y' new_plot.yaxis("\\rm{Residuals} ", "/") new_plot.group_id = "2daverage" + self.base.data2D.name new_plot.id = (self.averager.__name__) + self.base.data2D.name new_plot.is_data = True self.base.parent.update_theory(data_id=self.base.data2D.id, \ theory=new_plot) wx.PostEvent( self.base.parent, NewPlotEvent(plot=new_plot, title=str(self.averager.__name__)))
def onCircular(self, event, ismask=False): """ perform circular averaging on Data2D :param event: wx.menu event """ # Find the best number of bins npt = math.sqrt(len(self.data2D.data[numpy.isfinite( self.data2D.data)])) npt = math.floor(npt) from sas.dataloader.manipulations import CircularAverage ## compute the maximum radius of data2D self.qmax = max(math.fabs(self.data2D.xmax), math.fabs(self.data2D.xmin)) self.ymax = max(math.fabs(self.data2D.ymax), math.fabs(self.data2D.ymin)) self.radius = math.sqrt( math.pow(self.qmax, 2) + math.pow(self.ymax, 2)) ##Compute beam width bin_width = (self.qmax + self.qmax) / npt ## Create data1D circular average of data2D Circle = CircularAverage(r_min=0, r_max=self.radius, bin_width=bin_width) circ = Circle(self.data2D, ismask=ismask) from sas.guiframe.dataFitting import Data1D if hasattr(circ, "dxl"): dxl = circ.dxl else: dxl = None if hasattr(circ, "dxw"): dxw = circ.dxw else: dxw = None new_plot = Data1D(x=circ.x, y=circ.y, dy=circ.dy, dx=circ.dx) new_plot.dxl = dxl new_plot.dxw = dxw new_plot.name = "Circ avg " + self.data2D.name new_plot.source = self.data2D.source #new_plot.info = self.data2D.info new_plot.interactive = True new_plot.detector = self.data2D.detector ## If the data file does not tell us what the axes are, just assume... new_plot.xaxis("\\rm{Q}", "A^{-1}") if hasattr(self.data2D, "scale") and \ self.data2D.scale == 'linear': new_plot.ytransform = 'y' new_plot.yaxis("\\rm{Residuals} ", "normalized") else: new_plot.yaxis("\\rm{Intensity} ", "cm^{-1}") new_plot.group_id = "2daverage" + self.data2D.name new_plot.id = "Circ avg " + self.data2D.name new_plot.is_data = True self.parent.update_theory(data_id=self.data2D.id, \ theory=new_plot) wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title=new_plot.name))