class SmoothingLowess(Smoothing): smoothing_parameter = t.Range(low=0., high=1., value=0.5, ) number_of_iterations = t.Range(low=1, value=1) def __init__(self, *args, **kwargs): super(SmoothingLowess, self).__init__(*args, **kwargs) def _smoothing_parameter_changed(self, old, new): if new == 0: self.smoothing_parameter = old else: self.update_lines() def _number_of_iterations_changed(self, old, new): self.update_lines() def model2plot(self, axes_manager=None): self.single_spectrum.data = self.signal().copy() self.single_spectrum.smooth_lowess( smoothing_parameter=self.smoothing_parameter, number_of_iterations=self.number_of_iterations, show_progressbar=False) return self.single_spectrum.data def apply(self): self.signal.smooth_lowess( smoothing_parameter=self.smoothing_parameter, number_of_iterations=self.number_of_iterations) self.signal._replot()
class PlotSettings(tr.HasStrictTraits): num_of_first_rows_to_take = tr.Range(low=0, high=10**9, value=6000, mode='spinner') num_of_rows_to_skip_after_each_section = tr.Range(low=0, high=10**9, value=20000, mode='spinner') num_of_rows_in_each_section = tr.Range(low=0, high=10**9, value=200, mode='spinner')
class cash_flow_series(trapi.HasTraits): name = trapi.Str short_rate = trapi.Range(0.0, 0.5, 0.05) time_list = trapi.Array(dtype=np.float, shape=(1, 6)) cash_flows = trapi.Array(dtype=np.float, shape=(1, 6)) disc_values = trapi.Array(dtype=np.float, shape=(1, 6)) present_values = trapi.Array(dtype=np.float, shape=(1, 6)) net_present_value = trapi.Float update = trapi.Button def _update_fired(self): self.disc_values = np.exp(-self.short_rate * self.time_list) self.present_values = self.disc_values * self.cash_flows self.net_present_value = np.sum(self.present_values) v = trui.View(trui.Group(trui.Item(name='name'), trui.Item(name='short_rate'), trui.Item(name='time_list', label='Time List'), trui.Item(name='cash_flows', label='Cash Flows'), trui.Item('update', show_label=False), trui.Item(name='disc_values', label='Discount Factors'), trui.Item(name='present_values', label='Present Values'), trui.Item(name='net_present_value', label='Net Present Value'), show_border=True, label='Calculate Present Values'), buttons=[trui.OKButton, trui.CancelButton], resizable=True)
class EELSConfig(t.HasTraits): eels_gos_files_path = t.Directory(guess_gos_path(), label = 'GOS directory', desc = 'The GOS files are required to create the EELS edge components') fine_structure_width = t.CFloat(30, label = 'Fine structure lenght', desc = 'The default lenght of the fine structure from the edge onset') fine_structure_active = t.CBool(False, label = 'Enable fine structure', desc = "If enabled, the regions of the EELS spectrum defined as fine " "structure will be fitted with a spline. Please note that it " "enabling this feature only makes sense when the model is " "convolved to account for multiple scattering") fine_structure_smoothing = t.Range(0., 1., value = 0.3, label = 'Fine structure smoothing factor', desc = 'The lower the value the smoother the fine structure spline fit') synchronize_cl_with_ll = t.CBool(False) preedge_safe_window_width = t.CFloat(2, label = 'Pre-onset region (in eV)', desc = 'Some functions needs to define the regions between two ' 'ionisation edges. Due to limited energy resolution or chemical ' 'shift, the region is limited on its higher energy side by ' 'the next ionisation edge onset minus an offset defined by this ' 'parameters') min_distance_between_edges_for_fine_structure = t.CFloat(0, label = 'Minimum distance between edges', desc = 'When automatically setting the fine structure energy regions, ' 'the fine structure of an EELS edge component is automatically ' 'disable if the next ionisation edge onset distance to the ' 'higher energy side of the fine structure region is lower that ' 'the value of this parameter')
class ButterworthFilter(Smoothing): cutoff_frequency_ratio = t.Range(0., 1., 0.05) type = t.Enum('low', 'high') order = t.Int(2) view = tu.View( tu.Group('cutoff_frequency_ratio', 'order', 'type'), kind='live', handler=SmoothingHandler, buttons=OKCancelButtons, title='Butterworth filter', ) def _cutoff_frequency_ratio_changed(self, old, new): self.update_lines() def _type_changed(self, old, new): self.update_lines() def _order_changed(self, old, new): self.update_lines() def model2plot(self, axes_manager=None): b, a = sp.signal.butter(self.order, self.cutoff_frequency_ratio, self.type) smoothed = sp.signal.filtfilt(b, a, self.signal()) return smoothed
class AutoRefreshDialog(traits.HasTraits): minutes = traits.Float(1.0) autoRefreshBool = traits.Bool() emailAlertBool = traits.Bool(False) soundAlertBool = traits.Bool(False) linesOfDataFrame = traits.Range(1, 10) alertCode = traits.Code( DEFAULT_ALERT_CODE, desc="python code for finding alert worthy elements") basicGroup = traitsui.Group("minutes", "autoRefreshBool") alertGroup = traitsui.VGroup( traitsui.HGroup(traitsui.Item("emailAlertBool"), traitsui.Item("soundAlertBool")), traitsui.Item("linesOfDataFrame", visible_when="emailAlertBool or soundAlertBool"), traitsui.Item("alertCode", visible_when="emailAlertBool or soundAlertBool")) traits_view = traitsui.View(traitsui.VGroup(basicGroup, alertGroup), title="auto refresh", buttons=[OKButton], kind='livemodal', resizable=True)
class ButterworthFilter(Smoothing): cutoff_frequency_ratio = t.Range(0.01, 1., 0.01) type = t.Enum('low', 'high') order = t.Int(2) def _cutoff_frequency_ratio_changed(self, old, new): self.update_lines() def _type_changed(self, old, new): self.update_lines() def _order_changed(self, old, new): self.update_lines() def model2plot(self, axes_manager=None): b, a = sp.signal.butter(self.order, self.cutoff_frequency_ratio, self.type) smoothed = sp.signal.filtfilt(b, a, self.signal()) return smoothed def apply(self): b, a = sp.signal.butter(self.order, self.cutoff_frequency_ratio, self.type) f = functools.partial(sp.signal.filtfilt, b, a) self.signal.map(f)
class RGBA(traits.HasTraits): # r,g,b,a in the range 0-1 with default color 0,0,0,1 (black) r = traits.Range(0., 1., 0.) g = traits.Range(0., 1., 0.) b = traits.Range(0., 1., 0.) a = traits.Range(0., 1., 1.) def __init__(self, r=0., g=0., b=0., a=1.): self.r = r self.g = g self.b = b self.a = a def __repr__(self): return 'r,g,b,a = (%1.2f, %1.2f, %1.2f, %1.2f)'%\ (self.r, self.g, self.b, self.a)
class SmoothingLowess(Smoothing): smoothing_parameter = t.Range(low=0., high=1., value=0.5, ) number_of_iterations = t.Range(low=1, value=1) view = tu.View( tu.Group( 'smoothing_parameter', 'number_of_iterations', 'line_color'), kind='live', handler=SmoothingHandler, buttons=OKCancelButtons, title='Lowess Smoothing',) def __init__(self, *args, **kwargs): super(SmoothingLowess, self).__init__(*args, **kwargs) def _smoothing_parameter_changed(self, old, new): if new == 0: self.smoothing_parameter = old else: self.update_lines() def _number_of_iterations_changed(self, old, new): self.update_lines() def model2plot(self, axes_manager=None): self.single_spectrum.data = self.signal().copy() self.single_spectrum.smooth_lowess( smoothing_parameter=self.smoothing_parameter, number_of_iterations=self.number_of_iterations, show_progressbar=False) return self.single_spectrum.data def apply(self): self.signal.smooth_lowess( smoothing_parameter=self.smoothing_parameter, number_of_iterations=self.number_of_iterations) self.signal._replot()
class MultiMeshMorpher(ta.HasTraits): visible = ta.Enum(values='_names') morph_target = ta.Enum(values='_names') morph_alpha = ta.Range(0.0, 1.0, 0.0) show_edges = ta.Bool(False) _names = ta.List() def __init__(self, list_verts, tris, names=None, fig=None, **kw): super(MultiMeshMorpher, self).__init__(**kw) self._list_verts = list_verts self._tris = tris if fig is None: self._fig = mlab.figure(bgcolor=(1, 1, 1)) else: self._fig = fig if names is None: names = map(str, range(len(list_verts))) self._names = list(names) self._verts_by_name = dict(zip(self._names, list_verts)) self._actor, self._pd = mesh_as_vtk_actor(list_verts[0], tris, return_polydata=True) self._actor.property.set( ambient=0.0, specular=0.15, specular_power=128., diffuse=0.8, ) self._fig.scene.add_actor(self._actor) self.visible = self._names[0] if len(self._names) > 1: self.morph_target = self._names[1] @ta.on_trait_change('visible, show_edges, morph_target, morph_alpha') def _update(self): self._actor.property.edge_visibility = self.show_edges v1 = self._verts_by_name[self.visible] if self.morph_alpha > 0: v2 = self._verts_by_name[self.morph_target] self._pd.points = v1 * (1 - self.morph_alpha) + v2 * self.morph_alpha else: self._pd.points = v1 self._fig.scene.render() view = tu.View( tu.Group( tu.Item('visible'), tu.Item('morph_target'), tu.Item('morph_alpha'), tu.Item('show_edges', name='Wireframe'), label="Viewer"), title="MultiMeshMorpher" )
class GaussianFilter(Filter): width = tr.Range(1, 10) def _chan_changed(self): self.update() @tr.on_trait_change('width') def update(self): self.yf = nd.gaussian_filter1d(self.d[:, self.chan], self.width, mode='nearest') self.replot = True traits_view = ui.View(['width'])
class SavGolFilter(Filter): order = tr.Range(1, 10) window_size = tr.Int(5) @tr.on_trait_change('order, window_size') def update(self): ws = (self.window_size / 2) * 2 + 1 y = self.d[:, self.chan] self.yf = dv.savitzky_golay(y, ws, self.order) self.replot = True range_edit = ui.RangeEditor(low_name='order', high=51) view_window_size = ui.Item('window_size', editor=range_edit) traits_view = ui.View(['order', view_window_size])
class Prop(tvtk_base.TVTKBase): def __init__(self, obj=None, update=1, **traits): tvtk_base.TVTKBase.__init__( self, vtk.vtkProperty, obj, update, **traits ) edge_visibility = tvtk_base.false_bool_trait def _edge_visibility_changed(self, old_val, new_val): self._do_change(self._vtk_obj.SetEdgeVisibility, self.edge_visibility_) representation = tvtk_base.RevPrefixMap( {'points': 0, 'wireframe': 1, 'surface': 2}, 4, 5, default_value='surface') def _representation_changed(self, old_val, new_val): self._do_change(self._vtk_obj.SetRepresentation, self.representation_) opacity = traits.Trait(1.0, traits.Range(0.0, 1.0)) def _opacity_changed(self, old_val, new_val): self._do_change(self._vtk_obj.SetOpacity, self.opacity) specular_color = tvtk_base.vtk_color_trait((1.0, 1.0, 1.0)) def _specular_color_changed(self, old_val, new_val): self._do_change(self._vtk_obj.SetSpecularColor, self.specular_color, 1) diffuse_color = tvtk_base.vtk_color_trait((1.0, 1.0, 1.0)) def _diffuse_color_changed(self, old_val, new_val): self._do_change(self._vtk_obj.SetDiffuseColor, self.diffuse_color, 1) color = tvtk_base.vtk_color_trait((1.0, 1.0, 1.0)) def _color_changed(self, old_val, new_val): self._do_change(self._vtk_obj.SetColor, self.color) _updateable_traits_ = (('edge_visibility', 'GetEdgeVisibility'), ('opacity', 'GetOpacity'), ('specular_color', 'GetSpecularColor'), ('color', 'GetColor'), ('diffuse_color', 'GetDiffuseColor'), ('representation', 'GetRepresentation'))
class Controller(traits.HasTraits): min_slab = traits.Int(0) max_slab = traits.Int(1E9) slab = traits.Range(low='min_slab', high='max_slab', value=0, mode='spinner', exclude_high=True) apply_registration = traits.Bool() pause = traits.Bool() view = View( Group(Item(name='slab'), Item(name='apply_registration'), Item(name='pause'))) def __init__(self, view, job, *args, **kwargs): HasTraits.__init__(self, *args, **kwargs) self.view = view self.job = job self.max_slab = 0 @traits.on_trait_change("pause") def job_pause(self, value): self.job.pause = value @traits.on_trait_change("slab,apply_registration") def update_slab(self): ## switch between the register of the previous and the current slab if self.apply_registration or self.slab > 0 and len( self.job.registered_slabs) > 0: sl = self.job.registered_slabs[self.slab] sl_reg = self.job.registered_slabs[self.slab + int(self.apply_registration) - 1] self.view.update_slab(sl[0], sl[1], sl_reg[2], sl[3]) def notify_changes(self): self.max_slab = len(self.job.registered_slabs) self.view.update_motion(np.c_[self.job.algo.filtered_state_means])
def __init__(self, robot): self.robot = robot for joint in robot.GetJoints(): lower, upper = joint.GetLimits() lower = np.clip(lower, -np.pi, np.inf) upper = np.clip(upper, -np.inf, np.pi) self.add_trait(joint.GetName(), tr.Range(lower[0].item(), upper[0].item(), joint.GetValues()[0])) T = robot.GetTransform() rx,ry,rz = rave.axisAngleFromRotationMatrix(T[:3,:3]) tx,ty,tz = T[:3,3] self.add_trait("tftx", tr.Range(-5.,5,tx.item())) self.add_trait("tfty", tr.Range(-5.,5,ty.item())) self.add_trait("tftz", tr.Range(-5.,5,tz.item())) self.add_trait("tfrx", tr.Range(-5.,5,rx.item())) self.add_trait("tfry", tr.Range(-5.,5,ry.item())) self.add_trait("tfrz", tr.Range(-5.,5,rz.item())) self.on_trait_change(self.update_robot, 'anytrait') tr.HasTraits.__init__(self)
class SvdFilter(Filter): number_of_components = tr.Range(1, 20, 5) def _df_default(self): self.apply_filter() return self.df @tr.on_trait_change('number_of_components') def apply_filter(self): u, s, v = np.linalg.svd(self.d, full_matrices=False) s[self.number_of_components:] = 0 s = np.diag(s) self.df = u.dot(np.dot(s, v)) self.yf = self.df[:, self.chan] self.replot = True return self.df def _chan_changed(self, n): self.yf = self.df[:, n] self.replot = True traits_view = ui.View(['number_of_components'])
class Morpher(ta.HasTraits): alpha = ta.Range(0.0, 1.0) def __init__( self, verts1, verts2, tris=None, lines=None, as_points=False, scalars=None, scalars2=None, vmin=None, vmax=None, actor_property=dict(specular=0.1, specular_power=128., diffuse=0.5), ): if tris is None: if lines is None: rep = 'points' else: rep = 'wireframe' else: rep = 'surface' super(Morpher, self).__init__() self._verts1, self._verts2 = verts1, verts2 self._polydata = tvtk.PolyData(points=verts1) if rep == 'points': self._polydata.verts = np.r_[:len(verts1)].reshape(-1, 1) if tris is not None: self._polydata.polys = tris if lines is not None: self._polydata.lines = lines n = tvtk.PolyDataNormals(splitting=False) configure_input_data(n, self._polydata) self._actor = tvtk.Actor(mapper=tvtk.PolyDataMapper()) configure_input(self._actor.mapper, n) self._actor.property.representation = rep if rep == 'points': self._actor.property.point_size = 5 if as_points and scalars is None: self._polydata.point_data.scalars = \ np.random.uniform(0, 255, (len(verts1), 3)).astype(np.uint8) self._scalars12 = None if scalars is not None: self._polydata.point_data.scalars = scalars # automatically determine minimum/maximum from scalars if not given by user if vmin is None: vmin = scalars.min() if scalars2 is not None: vmin = min(vmin, scalars2.min()) if vmax is None: vmax = scalars.max() if scalars2 is not None: vmax = max(vmax, scalars2.max()) if scalars.ndim == 1: self._actor.mapper.use_lookup_table_scalar_range = False self._actor.mapper.scalar_range = (vmin, vmax) self._actor.mapper.lookup_table.hue_range = (0.33, 0) # when scalars of second mesh given we need to store both scalars in order # to interpolate between them during rendering if scalars2 is not None: self._scalars12 = (scalars, scalars2) else: self._actor.property.set(**actor_property) mlab.gcf().scene.add_actor(self._actor) def _alpha_changed(self): self._polydata.points = self._verts1 * (1 - self.alpha) \ + self._verts2 * self.alpha if self._scalars12 is not None: blended = self._scalars12[0] * (1 - self.alpha) \ + self._scalars12[1] * self.alpha # when scalars is a (n_verts, 3) color array (type uint8) # then above blending will cast to float, undo this here: if self._scalars12[0].dtype == np.uint8: blended = blended.astype(np.uint8) self._polydata.point_data.scalars = blended mlab.gcf().scene.render() traits_view = tu.View(tu.Item('alpha', show_label=False), title='cgtools Morpher')
class Ctrl(ta.HasTraits): alpha = ta.Range(0., 1.) def _alpha_changed(self): pd.points = p + self.alpha * (p2 - p) mlab.gcf().scene.render()
class BackgroundRemoval(SpanSelectorInSpectrum): background_type = t.Enum( 'Power Law', 'Gaussian', 'Offset', 'Polynomial', default='Power Law') polynomial_order = t.Range(1, 10) estimate_background = t.Enum( 'Estimate', 'Full fit', default='Estimate') background_estimator = t.Instance(Component) bg_line_range = t.Enum('from_left_range', 'full', 'ss_range', default='full') hi = t.Int(0) view = tu.View( tu.Group( 'background_type', tu.Group( 'polynomial_order', visible_when='background_type == \'Polynomial\''),), 'estimate_background', buttons=[OKButton, CancelButton], handler=SpanSelectorInSpectrumHandler, title='Background removal tool') def __init__(self, signal): super(BackgroundRemoval, self).__init__(signal) self.set_background_estimator() self.estimate_fit = True self.bg_line = None def on_disabling_span_selector(self): if self.bg_line is not None: self.bg_line.close() self.bg_line = None def set_background_estimator(self): if self.background_type == 'Power Law': self.background_estimator = components.PowerLaw() self.bg_line_range = 'from_left_range' elif self.background_type == 'Gaussian': self.background_estimator = components.Gaussian() self.bg_line_range = 'full' elif self.background_type == 'Offset': self.background_estimator = components.Offset() self.bg_line_range = 'full' elif self.background_type == 'Polynomial': self.background_estimator = \ components.Polynomial(self.polynomial_order) self.bg_line_range = 'full' def _polynomial_order_changed(self, old, new): self.background_estimator = components.Polynomial(new) self.span_selector_changed() def _background_type_changed(self, old, new): self.set_background_estimator() self.span_selector_changed() def _estimate_background_changed(self, old, new): if self.estimate_background == 'Full fit': self.estimate_fit = False if self.estimate_background == 'Estimate': self.estimate_fit = True def _ss_left_value_changed(self, old, new): self.span_selector_changed() def _ss_right_value_changed(self, old, new): self.span_selector_changed() def create_background_line(self): self.bg_line = drawing.spectrum.SpectrumLine() self.bg_line.data_function = self.bg_to_plot self.bg_line.set_line_properties( color='blue', type='line', scaley=False) self.signal._plot.signal_plot.add_line(self.bg_line) self.bg_line.autoscale = False self.bg_line.plot() def bg_to_plot(self, axes_manager=None, fill_with=np.nan): # First try to update the estimation self.background_estimator.estimate_parameters( self.signal, self.ss_left_value, self.ss_right_value, only_current=True) if self.bg_line_range == 'from_left_range': bg_array = np.zeros(self.axis.axis.shape) bg_array[:] = fill_with from_index = self.axis.value2index(self.ss_left_value) bg_array[from_index:] = self.background_estimator.function( self.axis.axis[from_index:]) to_return = bg_array elif self.bg_line_range == 'full': to_return = self.background_estimator.function(self.axis.axis) elif self.bg_line_range == 'ss_range': bg_array = np.zeros(self.axis.axis.shape) bg_array[:] = fill_with from_index = self.axis.value2index(self.ss_left_value) to_index = self.axis.value2index(self.ss_right_value) bg_array[from_index:] = self.background_estimator.function( self.axis.axis[from_index:to_index]) to_return = bg_array if self.signal.metadata.Signal.binned is True: to_return *= self.axis.scale return to_return def span_selector_changed(self): if (self.ss_left_value is np.nan) or (self.ss_right_value is np.nan): return if self.background_estimator is None: print("No bg estimator") return if self.bg_line is None and \ self.background_estimator.estimate_parameters( self.signal, self.ss_left_value, self.ss_right_value, only_current=True) is True: self.create_background_line() else: self.bg_line.update() def apply(self): self.signal._plot.auto_update_plot = False new_spectra = self.signal._remove_background_cli( (self.ss_left_value, self.ss_right_value), self.background_estimator, estimate_background=self.estimate_fit) self.signal.data = new_spectra.data self.signal._replot() self.signal._plot.auto_update_plot = True
class SpikesRemoval(SpanSelectorInSpectrum): interpolator_kind = t.Enum( 'Linear', 'Spline', default='Linear') threshold = t.Float() show_derivative_histogram = t.Button() spline_order = t.Range(1, 10, 3) interpolator = None default_spike_width = t.Int(5) index = t.Int(0) add_noise = t.Bool(True, desc="Add noise to the healed portion of the " "spectrum. Use the noise properties " "defined in metadata if present, otherwise " "it defaults to shot noise.") view = tu.View(tu.Group( tu.Group( tu.Item('show_derivative_histogram', show_label=False), 'threshold', show_border=True,), tu.Group( 'add_noise', 'interpolator_kind', 'default_spike_width', tu.Group( 'spline_order', visible_when='interpolator_kind == \'Spline\''), show_border=True, label='Advanced settings'), ), buttons=[OKButton, OurPreviousButton, OurFindButton, OurApplyButton, ], handler=SpikesRemovalHandler, title='Spikes removal tool') def __init__(self, signal, navigation_mask=None, signal_mask=None): super(SpikesRemoval, self).__init__(signal) self.interpolated_line = None self.coordinates = [coordinate for coordinate in signal.axes_manager._am_indices_generator() if (navigation_mask is None or not navigation_mask[coordinate[::-1]])] self.signal = signal self.line = signal._plot.signal_plot.ax_lines[0] self.ax = signal._plot.signal_plot.ax signal._plot.auto_update_plot = False if len(self.coordinates) > 1: signal.axes_manager.indices = self.coordinates[0] self.threshold = 400 self.index = 0 self.argmax = None self.derivmax = None self.kind = "linear" self._temp_mask = np.zeros(self.signal().shape, dtype='bool') self.signal_mask = signal_mask self.navigation_mask = navigation_mask md = self.signal.metadata from hyperspy.signal import Signal if "Signal.Noise_properties" in md: if "Signal.Noise_properties.variance" in md: self.noise_variance = md.Signal.Noise_properties.variance if isinstance(md.Signal.Noise_properties.variance, Signal): self.noise_type = "heteroscedastic" else: self.noise_type = "white" else: self.noise_type = "shot noise" def _threshold_changed(self, old, new): self.index = 0 self.update_plot() def _show_derivative_histogram_fired(self): self.signal._spikes_diagnosis(signal_mask=self.signal_mask, navigation_mask=self.navigation_mask) def detect_spike(self): derivative = np.diff(self.signal()) if self.signal_mask is not None: derivative[self.signal_mask[:-1]] = 0 if self.argmax is not None: left, right = self.get_interpolation_range() self._temp_mask[left:right] = True derivative[self._temp_mask[:-1]] = 0 if abs(derivative.max()) >= self.threshold: self.argmax = derivative.argmax() self.derivmax = abs(derivative.max()) return True else: return False def _reset_line(self): if self.interpolated_line is not None: self.interpolated_line.close() self.interpolated_line = None self.reset_span_selector() def find(self, back=False): self._reset_line() ncoordinates = len(self.coordinates) spike = self.detect_spike() while not spike and ( (self.index < ncoordinates - 1 and back is False) or (self.index > 0 and back is True)): if back is False: self.index += 1 else: self.index -= 1 spike = self.detect_spike() if spike is False: messages.information('End of dataset reached') self.index = 0 self._reset_line() return else: minimum = max(0, self.argmax - 50) maximum = min(len(self.signal()) - 1, self.argmax + 50) thresh_label = DerivativeTextParameters( text="$\mathsf{\delta}_\mathsf{max}=$", color="black") self.ax.legend([thresh_label], [repr(int(self.derivmax))], handler_map={ DerivativeTextParameters: DerivativeTextHandler()}, loc='best') self.ax.set_xlim( self.signal.axes_manager.signal_axes[0].index2value( minimum), self.signal.axes_manager.signal_axes[0].index2value( maximum)) self.update_plot() self.create_interpolation_line() def update_plot(self): if self.interpolated_line is not None: self.interpolated_line.close() self.interpolated_line = None self.reset_span_selector() self.update_spectrum_line() if len(self.coordinates) > 1: self.signal._plot.pointer._update_patch_position() def update_spectrum_line(self): self.line.auto_update = True self.line.update() self.line.auto_update = False def _index_changed(self, old, new): self.signal.axes_manager.indices = self.coordinates[new] self.argmax = None self._temp_mask[:] = False def on_disabling_span_selector(self): if self.interpolated_line is not None: self.interpolated_line.close() self.interpolated_line = None def _spline_order_changed(self, old, new): self.kind = self.spline_order self.span_selector_changed() def _add_noise_changed(self, old, new): self.span_selector_changed() def _interpolator_kind_changed(self, old, new): if new == 'linear': self.kind = new else: self.kind = self.spline_order self.span_selector_changed() def _ss_left_value_changed(self, old, new): self.span_selector_changed() def _ss_right_value_changed(self, old, new): self.span_selector_changed() def create_interpolation_line(self): self.interpolated_line = drawing.spectrum.SpectrumLine() self.interpolated_line.data_function = \ self.get_interpolated_spectrum self.interpolated_line.set_line_properties( color='blue', type='line') self.signal._plot.signal_plot.add_line(self.interpolated_line) self.interpolated_line.autoscale = False self.interpolated_line.plot() def get_interpolation_range(self): axis = self.signal.axes_manager.signal_axes[0] if self.ss_left_value == self.ss_right_value: left = self.argmax - self.default_spike_width right = self.argmax + self.default_spike_width else: left = axis.value2index(self.ss_left_value) right = axis.value2index(self.ss_right_value) # Clip to the axis dimensions nchannels = self.signal.axes_manager.signal_shape[0] left = left if left >= 0 else 0 right = right if right < nchannels else nchannels - 1 return left, right def get_interpolated_spectrum(self, axes_manager=None): data = self.signal().copy() axis = self.signal.axes_manager.signal_axes[0] left, right = self.get_interpolation_range() if self.kind == 'linear': pad = 1 else: pad = 10 ileft = left - pad iright = right + pad ileft = np.clip(ileft, 0, len(data)) iright = np.clip(iright, 0, len(data)) left = int(np.clip(left, 0, len(data))) right = int(np.clip(right, 0, len(data))) x = np.hstack((axis.axis[ileft:left], axis.axis[right:iright])) y = np.hstack((data[ileft:left], data[right:iright])) if ileft == 0: # Extrapolate to the left data[left:right] = data[right + 1] elif iright == (len(data) - 1): # Extrapolate to the right data[left:right] = data[left - 1] else: # Interpolate intp = sp.interpolate.interp1d(x, y, kind=self.kind) data[left:right] = intp(axis.axis[left:right]) # Add noise if self.add_noise is True: if self.noise_type == "white": data[left:right] += np.random.normal( scale=np.sqrt(self.noise_variance), size=right - left) elif self.noise_type == "heteroscedastic": noise_variance = self.noise_variance( axes_manager=self.signal.axes_manager)[left:right] noise = [np.random.normal(scale=np.sqrt(item)) for item in noise_variance] data[left:right] += noise else: data[left:right] = np.random.poisson( np.clip(data[left:right], 0, np.inf)) return data def span_selector_changed(self): if self.interpolated_line is None: return else: self.interpolated_line.update() def apply(self): self.signal()[:] = self.get_interpolated_spectrum() self.update_spectrum_line() self.interpolated_line.close() self.interpolated_line = None self.reset_span_selector() self.find()
class CSVFile(tr.HasStrictTraits): path = tr.Str count_lines = tr.Button _lines_number = tr.Int # _ is the private field convention show_lines_number = tr.Bool num_of_first_lines_to_show = tr.Int(10) num_of_last_lines_to_show = tr.Int(10) first_lines = tr.Property( depends_on='path, num_of_first_lines_to_show, first_lines_to_skip') last_lines = tr.Property( depends_on='path, num_of_last_lines_to_show, last_lines_to_skip') first_lines_to_skip = tr.Range(low=0, high=10**9, mode='spinner') last_lines_to_skip = tr.Range(low=0, high=10**9, mode='spinner') def _count_lines_fired(self): # return sum(1 for line in open(self.path)) self.show_lines_number = True self._lines_number = self._count_lines_in_file(self.path) def get_lines_number(self): # If it was not yet calculated, calc it first if self._lines_number == 0: self._lines_number = self._count_lines_in_file(self.path) return self._lines_number def _count_lines_in_file(self, file_name): ''' This method will count the number of lines in a huge file pretty quickly using custom buffering''' f = open(file_name, 'rb') bufgen = takewhile(lambda x: x, (f.raw.read(1024 * 1024) for i in repeat(None))) return sum(buf.count(b'\n') for buf in bufgen) + 1 @tr.cached_property def _get_first_lines(self): first_lines_list = [] with open(self.path) as myfile: for i in range(self.num_of_first_lines_to_show): try: # Get next line if it exists! line = next(myfile) # The following will not executed if an exception was thrown first_lines_list.append(line) except StopIteration: # If last line ends with \n then a new empty line is # actually there if len(first_lines_list) != 0: if first_lines_list[-1].endswith('\n'): first_lines_list.append('') break first_lines_list = self.add_line_numbers(first_lines_list) first_lines_list = first_lines_list[self.first_lines_to_skip:] first_lines_str = ''.join(first_lines_list) return first_lines_str def add_line_numbers(self, lines_list): new_list = [] for line_num, line in zip(range(1, len(lines_list) + 1), lines_list): new_list.append('(' + str(line_num) + ')--> ' + str(line)) return new_list def add_reverse_line_numbers(self, lines_list): new_list = [] for line_num, line in zip(range(len(lines_list), 0, -1), lines_list): new_list.append('(' + str(line_num) + ')--> ' + str(line)) return new_list @tr.cached_property def _get_last_lines(self): last_lines_list = self.get_last_n_lines(self.path, self.num_of_last_lines_to_show, False) last_lines_list = self.add_reverse_line_numbers(last_lines_list) last_lines_list = last_lines_list[0:len(last_lines_list) - self.last_lines_to_skip] last_lines_str = ''.join(last_lines_list) return last_lines_str def get_last_n_lines(self, file_name, N, skip_empty_lines=False): # Create an empty list to keep the track of last N lines list_of_lines = [] # Open file for reading in binary mode with open(file_name, 'rb') as read_obj: # Move the cursor to the end of the file read_obj.seek(0, os.SEEK_END) # Create a buffer to keep the last read line buffer = bytearray() # Get the current position of pointer i.e eof pointer_location = read_obj.tell() # Loop till pointer reaches the top of the file while pointer_location >= 0: # Move the file pointer to the location pointed by pointer_location read_obj.seek(pointer_location) # Shift pointer location by -1 pointer_location = pointer_location - 1 # read that byte / character new_byte = read_obj.read(1) # If the read byte is new line character then it means one line is read if new_byte == b'\n': # Save the line in list of lines line = buffer.decode()[::-1] if (skip_empty_lines): line_is_empty = line.isspace() if (line_is_empty == False): list_of_lines.append(line) else: list_of_lines.append(line) # If the size of list reaches N, then return the reversed list if len(list_of_lines) == N: return list(reversed(list_of_lines)) # Reinitialize the byte array to save next line buffer = bytearray() else: # If last read character is not eol then add it in buffer buffer.extend(new_byte) # As file is read completely, if there is still data in buffer, then its first line. if len(buffer) > 0: list_of_lines.append(buffer.decode()[::-1]) # return the reversed list return list(reversed(list_of_lines)) traits_view = ui.View( ui.Item('path', style='readonly', label='File'), ui.HGroup( ui.UItem('count_lines'), ui.Item('_lines_number', style='readonly', visible_when='show_lines_number == True')), ui.VSplit( ui.HGroup(ui.Item('first_lines', style='custom'), 'first_lines_to_skip', label='First lines in the file'), ui.HGroup(ui.Item('last_lines', style='custom'), 'last_lines_to_skip', label='Last lines in the file')))
class HCFT(tr.HasStrictTraits): """High-Cycle Fatigue Tool""" # ========================================================================= # Traits definitions # ========================================================================= # Assigning the view traits_view = hcft_window # CSV import decimal = tr.Enum(',', '.') delimiter = tr.Str(';') file_path = tr.File open_file_button = tr.Button('Open file') columns_headers = tr.List npy_folder_path = tr.Str file_name = tr.Str # CSV processing take_time_from_time_column = tr.Bool(True) records_per_second = tr.Float(100) time_column = tr.Enum(values='columns_headers') skip_first_rows = tr.Range(low=1, high=10**9, value=3, mode='spinner') add_columns_average = tr.Button columns_to_be_averaged = tr.List parse_csv_to_npy = tr.Button # Plotting x_axis = tr.Enum(values='columns_headers') y_axis = tr.Enum(values='columns_headers') x_axis_multiplier = tr.Enum(1, -1) y_axis_multiplier = tr.Enum(-1, 1) add_plot = tr.Button apply_filters = tr.Bool plot_settings_btn = tr.Button plot_settings = PlotSettings() plot_settings_active = tr.Bool normalize_cycles = tr.Bool smooth = tr.Bool plot_every_nth_point = tr.Range(low=1, high=1000000, mode='spinner') old_peak_force_before_cycles = tr.Float peak_force_before_cycles = tr.Float add_creep_plot = tr.Button(desc='Creep plot of X axis array') clear_plot = tr.Button force_column = tr.Enum(values='columns_headers') window_length = tr.Range(low=1, high=10**9 - 1, value=31, mode='spinner') polynomial_order = tr.Range(low=1, high=10**9, value=2, mode='spinner') activate_ascending_branch_smoothing = tr.Bool(False, label='Activate') generate_filtered_and_creep_npy = tr.Button force_max = tr.Float(100) force_min = tr.Float(40) min_cycle_force_range = tr.Float(50) cutting_method = tr.Enum('Define min cycle range(force difference)', 'Define Max, Min') log = tr.Str('') clear_log = tr.Button # ========================================================================= # Assigning default values # ========================================================================= ax = tr.Any figure = tr.Instance(mpl.figure.Figure) def _figure_default(self): figure = mpl.figure.Figure(facecolor='white') figure.set_tight_layout(True) self.create_axes(figure) return figure def create_axes(self, figure): self.ax = figure.add_subplot(1, 1, 1) # ========================================================================= # File management # ========================================================================= def _open_file_button_fired(self): try: self.reset() dialog = FileDialog(title='Select text file', action='open', default_path=self.file_path) result = dialog.open() # Test if the user opened a file to avoid throwing an exception if he doesn't if result == OK: self.file_path = dialog.path else: return # Populate headers list which fills the x-axis and y-axis with values automatically self.columns_headers = get_headers(self.file_path, decimal=self.decimal, delimiter=self.delimiter) # Saving file name and path and creating NPY folder dir_path = os.path.dirname(self.file_path) self.npy_folder_path = os.path.join(dir_path, 'NPY') if not os.path.exists(self.npy_folder_path): os.makedirs(self.npy_folder_path) self.file_name = os.path.splitext(os.path.basename( self.file_path))[0] self.import_data_json() except: self.log_exception() def _add_columns_average_fired(self): try: columns_average = ColumnsAverage() for name in self.columns_headers: columns_average.columns.append(Column(column_name=name)) # kind='modal' pauses the implementation until the window is closed columns_average.configure_traits(kind='modal') columns_to_be_averaged_temp = [] for i in columns_average.columns: if i.selected: columns_to_be_averaged_temp.append(i.column_name) if columns_to_be_averaged_temp: # If it's not empty self.columns_to_be_averaged.append(columns_to_be_averaged_temp) avg_file_suffix = self.get_suffix_for_columns_to_be_averaged( columns_to_be_averaged_temp) self.columns_headers.append(avg_file_suffix) except: self.log_exception() def _parse_csv_to_npy_fired(self): # Run method on different thread so GUI doesn't freeze # thread = Thread(target = threaded_function, function_args = (10,)) thread = Thread(target=self.parse_csv_to_npy_fired) thread.start() def parse_csv_to_npy_fired(self): try: self.print_custom('Parsing csv into npy files...') self.export_data_json() """ Exporting npy arrays of original columns """ for i in range( len(self.columns_headers) - len(self.columns_to_be_averaged)): column_name = self.columns_headers[i] # One could provide the path directly to pd.read_csv but in this way we insure that this works also if the # path to the file include chars like ü,ä # (with) makes sure the file stream is closed after using it with open(self.file_path, encoding='utf-8') as file_stream: column_array = np.array( pd.read_csv(file_stream, delimiter=self.delimiter, decimal=self.decimal, skiprows=self.skip_first_rows, usecols=[i])) # TODO detect column name before loading completely to skip loading if the following condition applies if column_name == self.time_column and self.take_time_from_time_column is False: column_array = np.arange( start=0.0, stop=len(column_array) / self.records_per_second, step=1.0 / self.records_per_second) np.save(self.get_npy_file_path(column_name), column_array) """ Exporting npy arrays of averaged columns """ for columns_names in self.columns_to_be_averaged: temp_array = np.zeros((1)) for column_name in columns_names: temp_array = temp_array + np.load( self.get_npy_file_path(column_name)).flatten() avg = temp_array / len(columns_names) np.save(self.get_average_npy_file_path(columns_names), avg) self.print_custom('Finished parsing csv into npy files.') except: self.log_exception() def get_npy_file_path(self, column_name): return os.path.join(self.npy_folder_path, self.file_name + '_' + column_name + '.npy') def get_filtered_npy_file_path(self, column_name): return os.path.join( self.npy_folder_path, self.file_name + '_' + column_name + '_filtered.npy') def get_max_npy_file_path(self, column_name): return os.path.join(self.npy_folder_path, self.file_name + '_' + column_name + '_max.npy') def get_min_npy_file_path(self, column_name): return os.path.join(self.npy_folder_path, self.file_name + '_' + column_name + '_min.npy') def get_average_npy_file_path(self, columns_names): avg_file_suffix = self.get_suffix_for_columns_to_be_averaged( columns_names) return os.path.join(self.npy_folder_path, self.file_name + '_' + avg_file_suffix + '.npy') def get_suffix_for_columns_to_be_averaged(self, columns_names): suffix_for_saved_file_name = 'avg_' + '_'.join(columns_names) return suffix_for_saved_file_name def export_data_json(self): # Output data MUST have exactly similar keys and variable names output_data = { 'take_time_from_time_column': self.take_time_from_time_column, 'time_column': self.time_column, 'records_per_second': self.records_per_second, 'skip_first_rows': self.skip_first_rows, 'columns_headers': self.columns_headers, 'columns_to_be_averaged': self.columns_to_be_averaged, 'x_axis': self.x_axis, 'y_axis': self.y_axis, 'x_axis_multiplier': self.x_axis_multiplier, 'y_axis_multiplier': self.y_axis_multiplier, 'force_column': self.force_column, 'window_length': self.window_length, 'polynomial_order': self.polynomial_order, 'peak_force_before_cycles': self.peak_force_before_cycles, 'cutting_method': self.cutting_method, 'force_max': self.force_max, 'force_min': self.force_min, 'min_cycle_force_range': self.min_cycle_force_range } with open(self.get_json_file_path(), 'w') as outfile: json.dump(output_data, outfile, sort_keys=True, indent=4) self.print_custom('.json data file exported successfully.') def import_data_json(self): json_path = self.get_json_file_path() if not os.path.isfile(json_path): return # class_vars is a list with class variables names # vars(self) & self.__dict__.items() didn't include some Trait variables like force_column = tr.Enum(values=.. class_vars = [ attr for attr in dir(self) if not attr.startswith("_") and not attr.startswith("__") ] with open(json_path) as infile: data_in = json.load(infile) for key_data, value_data in data_in.items(): for key_class in class_vars: if key_data == key_class: # Equivalent to: self.key_class = value_data setattr(self, key_class, value_data) break self.print_custom('.json data file imported successfully.') def get_json_file_path(self): return os.path.join(self.npy_folder_path, self.file_name + '.json') def _generate_filtered_and_creep_npy_fired(self): # Run method on different thread so GUI doesn't freeze # thread = Thread(target = threaded_function, function_args = (10,)) thread = Thread(target=self.generate_filtered_and_creep_npy_fired) thread.start() def generate_filtered_and_creep_npy_fired(self): try: self.export_data_json() if not self.npy_files_exist( self.get_npy_file_path(self.force_column)): return self.print_custom('Generating filtered and creep files...') # 1- Export filtered force force = np.load(self.get_npy_file_path( self.force_column)).flatten() peak_force_before_cycles_index = np.where( abs((force)) > abs(self.peak_force_before_cycles))[0][0] force_ascending = force[0:peak_force_before_cycles_index] force_rest = force[peak_force_before_cycles_index:] force_max_indices, force_min_indices = self.get_array_max_and_min_indices( force_rest) force_max_min_indices = np.concatenate( (force_min_indices, force_max_indices)) force_max_min_indices.sort() force_rest_filtered = force_rest[force_max_min_indices] force_filtered = np.concatenate( (force_ascending, force_rest_filtered)) np.save(self.get_filtered_npy_file_path(self.force_column), force_filtered) # 2- Export filtered displacements # Export displacements combining processed ascending branch and unprocessed min/max values self.export_filtered_displacements(force_max_min_indices, peak_force_before_cycles_index) # 3- Export creep for displacements # Cut unwanted max min values to get correct full cycles and remove false min/max values caused by noise self.export_displacements_creep(force_rest, force_max_indices, force_min_indices, peak_force_before_cycles_index) self.print_custom('Filtered and creep npy files are generated.') except: self.log_exception() def export_filtered_displacements(self, force_max_min_indices, peak_force_before_cycles_index): for i in range(len(self.columns_headers)): if self.columns_headers[ i] != self.force_column and self.columns_headers[ i] != self.time_column: disp = np.load(self.get_npy_file_path( self.columns_headers[i])).flatten() disp_ascending = disp[0:peak_force_before_cycles_index] disp_rest = disp[peak_force_before_cycles_index:] if self.activate_ascending_branch_smoothing: disp_ascending = savgol_filter( disp_ascending, window_length=self.window_length, polyorder=self.polynomial_order) disp_rest_filtered = disp_rest[force_max_min_indices] filtered_disp = np.concatenate( (disp_ascending, disp_rest_filtered)) np.save( self.get_filtered_npy_file_path(self.columns_headers[i]), filtered_disp) def export_displacements_creep(self, force_rest, force_max_indices, force_min_indices, peak_force_before_cycles_index): if self.cutting_method == "Define Max, Min": force_max_indices_cut, force_min_indices_cut = self.cut_indices_of_min_max_range( force_rest, force_max_indices, force_min_indices, self.force_max, self.force_min) elif self.cutting_method == "Define min cycle range(force difference)": force_max_indices_cut, force_min_indices_cut = self.cut_indices_of_defined_range( force_rest, force_max_indices, force_min_indices, self.min_cycle_force_range) self.print_custom("Cycles number= ", len(force_min_indices)) self.print_custom("Cycles number after cutting fake cycles = ", len(force_min_indices_cut)) for i in range(len(self.columns_headers)): if self.columns_headers[i] != self.time_column: array = np.load(self.get_npy_file_path( self.columns_headers[i])).flatten() array_rest = array[peak_force_before_cycles_index:] array_rest_maxima = array_rest[force_max_indices_cut] array_rest_minima = array_rest[force_min_indices_cut] np.save(self.get_max_npy_file_path(self.columns_headers[i]), array_rest_maxima) np.save(self.get_min_npy_file_path(self.columns_headers[i]), array_rest_minima) def get_array_max_and_min_indices(self, input_array): # Checking dominant sign positive_values_count = np.sum(np.array(input_array) >= 0) negative_values_count = input_array.size - positive_values_count # Getting max and min indices if positive_values_count > negative_values_count: force_max_indices = self.get_max_indices(input_array) force_min_indices = self.get_min_indices(input_array) else: force_max_indices = self.get_min_indices(input_array) force_min_indices = self.get_max_indices(input_array) return force_max_indices, force_min_indices def get_max_indices(self, a): # TODO try to vectorize this # This method doesn't qualify first and last elements as max max_indices = [] i = 1 while i < a.size - 1: previous_element = a[i - 1] # Skip repeated elements and record previous element value first_repeated_element = True while a[i] == a[i + 1] and i < a.size - 1: if first_repeated_element: previous_element = a[i - 1] first_repeated_element = False if i < a.size - 2: i += 1 else: break # Append value if it's a local max if a[i] > a[i + 1] and a[i] > previous_element: max_indices.append(i) i += 1 return np.array(max_indices) def get_min_indices(self, a): # TODO try to vectorize this # This method doesn't qualify first and last elements as min min_indices = [] i = 1 while i < a.size - 1: previous_element = a[i - 1] # Skip repeated elements and record previous element value first_repeated_element = True while a[i] == a[i + 1]: if first_repeated_element: previous_element = a[i - 1] first_repeated_element = False if i < a.size - 2: i += 1 else: break # Append value if it's a local min if a[i] < a[i + 1] and a[i] < previous_element: min_indices.append(i) i += 1 return np.array(min_indices) def cut_indices_of_min_max_range(self, array, max_indices, min_indices, range_upper_value, range_lower_value): # TODO try to vectorize this cut_max_indices = [] cut_min_indices = [] for max_index in max_indices: if abs(array[max_index]) > abs(range_upper_value): cut_max_indices.append(max_index) for min_index in min_indices: if abs(array[min_index]) < abs(range_lower_value): cut_min_indices.append(min_index) return cut_max_indices, cut_min_indices def cut_indices_of_defined_range(self, array, max_indices, min_indices, range_): # TODO try to vectorize this cut_max_indices = [] cut_min_indices = [] for max_index, min_index in zip(max_indices, min_indices): if abs(array[max_index] - array[min_index]) > range_: cut_max_indices.append(max_index) cut_min_indices.append(min_index) if max_indices.size > min_indices.size: cut_max_indices.append(max_indices[-1]) elif min_indices.size > max_indices.size: cut_min_indices.append(min_indices[-1]) return cut_max_indices, cut_min_indices def _activate_changed(self): if not self.activate_ascending_branch_smoothing: self.old_peak_force_before_cycles = self.peak_force_before_cycles self.peak_force_before_cycles = 0 else: self.peak_force_before_cycles = self.old_peak_force_before_cycles def _window_length_changed(self, new): if new <= self.polynomial_order: dialog = MessageDialog( title='Attention!', message='Window length must be bigger than polynomial order.') dialog.open() if new % 2 == 0 or new <= 0: dialog = MessageDialog( title='Attention!', message='Window length must be odd positive integer.') dialog.open() def _polynomial_order_changed(self, new): if new >= self.window_length: dialog = MessageDialog( title='Attention!', message='Polynomial order must be smaller than window length.') dialog.open() # ========================================================================= # Plotting # ========================================================================= data_changed = tr.Event def _plot_settings_btn_fired(self): try: self.plot_settings.configure_traits(kind='modal') except: self.log_exception() def npy_files_exist(self, path): if os.path.exists(path): return True else: self.print_custom( 'Please parse csv file to generate npy files first!') return False def filtered_and_creep_npy_files_exist(self, path): if os.path.exists(path): return True else: self.print_custom( 'Please generate filtered and creep npy files first!') return False def _add_plot_fired(self): # Run method on different thread so GUI doesn't freeze # thread = Thread(target = threaded_function, function_args = (10,)) thread = Thread(target=self.add_plot_fired) thread.start() def add_plot_fired(self): try: if self.apply_filters: if not self.filtered_and_creep_npy_files_exist( self.get_filtered_npy_file_path(self.x_axis)): return # TODO link this _filtered to the path creation function x_axis_name = self.x_axis + '_filtered' y_axis_name = self.y_axis + '_filtered' self.print_custom('Loading npy files...') # when mmap_mode!=None, the array will be loaded as 'numpy.memmap' # object which doesn't load the array to memory until it's # indexed x_axis_array = np.load(self.get_filtered_npy_file_path( self.x_axis), mmap_mode='r') y_axis_array = np.load(self.get_filtered_npy_file_path( self.y_axis), mmap_mode='r') else: if not self.npy_files_exist(self.get_npy_file_path( self.x_axis)): return x_axis_name = self.x_axis y_axis_name = self.y_axis self.print_custom('Loading npy files...') # when mmap_mode!=None, the array will be loaded as 'numpy.memmap' # object which doesn't load the array to memory until it's # indexed x_axis_array = np.load(self.get_npy_file_path(self.x_axis), mmap_mode='r') y_axis_array = np.load(self.get_npy_file_path(self.y_axis), mmap_mode='r') if self.plot_settings_active: print(self.plot_settings.num_of_first_rows_to_take) print( self.plot_settings.num_of_rows_to_skip_after_each_section) print(self.plot_settings.num_of_rows_in_each_section) print(np.size(x_axis_array)) indices = self.get_indices_array( np.size(x_axis_array), self.plot_settings.num_of_first_rows_to_take, self.plot_settings.num_of_rows_to_skip_after_each_section, self.plot_settings.num_of_rows_in_each_section) x_axis_array = self.x_axis_multiplier * x_axis_array[indices] y_axis_array = self.y_axis_multiplier * y_axis_array[indices] else: x_axis_array = self.x_axis_multiplier * x_axis_array y_axis_array = self.y_axis_multiplier * y_axis_array self.print_custom('Adding Plot...') mpl.rcParams['agg.path.chunksize'] = 10000 ax = self.ax ax.set_xlabel(x_axis_name) ax.set_ylabel(y_axis_name) ax.plot(x_axis_array, y_axis_array, linewidth=1.2, color=np.random.rand(3), label=self.file_name + ', ' + x_axis_name) ax.legend() self.data_changed = True self.print_custom('Finished adding plot.') except: self.log_exception() def _add_creep_plot_fired(self): # Run method on different thread so GUI doesn't freeze # thread = Thread(target = threaded_function, function_args = (10,)) thread = Thread(target=self.add_creep_plot_fired) thread.start() def add_creep_plot_fired(self): try: if not self.filtered_and_creep_npy_files_exist( self.get_max_npy_file_path(self.x_axis)): return self.print_custom('Loading npy files...') disp_max = self.x_axis_multiplier * np.load( self.get_max_npy_file_path(self.x_axis)) disp_min = self.x_axis_multiplier * np.load( self.get_min_npy_file_path(self.x_axis)) complete_cycles_number = disp_max.size self.print_custom('Adding creep-fatigue plot...') mpl.rcParams['agg.path.chunksize'] = 10000 ax = self.ax ax.set_xlabel('Cycles number') ax.set_ylabel(self.x_axis) if self.plot_every_nth_point > 1: disp_max = disp_max[0::self.plot_every_nth_point] disp_min = disp_min[0::self.plot_every_nth_point] if self.smooth: # Keeping the first item of the array and filtering the rest disp_max = np.concatenate( (np.array([disp_max[0]]), savgol_filter(disp_max[1:], window_length=self.window_length, polyorder=self.polynomial_order))) disp_min = np.concatenate( (np.array([disp_min[0]]), savgol_filter(disp_min[1:], window_length=self.window_length, polyorder=self.polynomial_order))) if self.normalize_cycles: ax.plot(np.linspace(0, 1., disp_max.size), disp_max, 'k', linewidth=1.2, color=np.random.rand(3), label='Max' + ', ' + self.file_name + ', ' + self.x_axis) ax.plot(np.linspace(0, 1., disp_min.size), disp_min, 'k', linewidth=1.2, color=np.random.rand(3), label='Min' + ', ' + self.file_name + ', ' + self.x_axis) else: ax.plot(np.linspace(0, complete_cycles_number, disp_max.size), disp_max, 'k', linewidth=1.2, color=np.random.rand(3), label='Max' + ', ' + self.file_name + ', ' + self.x_axis) ax.plot(np.linspace(0, complete_cycles_number, disp_min.size), disp_min, 'k', linewidth=1.2, color=np.random.rand(3), label='Min' + ', ' + self.file_name + ', ' + self.x_axis) ax.legend() self.data_changed = True self.print_custom('Finished adding creep-fatigue plot.') except: self.log_exception() def get_indices_array(self, array_size, first_rows, distance, num_of_rows_after_each_distance): result_1 = np.arange(first_rows) result_2 = np.arange(start=first_rows, stop=array_size, step=distance + num_of_rows_after_each_distance) result_2_updated = np.array([], dtype=np.int_) for result_2_value in result_2: data_slice = np.arange( result_2_value, result_2_value + num_of_rows_after_each_distance) result_2_updated = np.concatenate((result_2_updated, data_slice)) result = np.concatenate((result_1, result_2_updated)) return result def _clear_plot_fired(self): self.figure.clear() self.create_axes(self.figure) self.data_changed = True # ========================================================================= # Logging # ========================================================================= def print_custom(self, *input_args): print(*input_args) if self.log == '': self.log = ''.join(str(e) for e in list(input_args)) else: self.log = self.log + '\n' + \ ''.join(str(e) for e in list(input_args)) def log_exception(self): self.print_custom('SOMETHING WENT WRONG!') self.print_custom('--------- Error message: ---------') self.print_custom(traceback.format_exc()) self.print_custom('----------------------------------') def _clear_log_fired(self): self.log = '' # ========================================================================= # Other functions # ========================================================================= def reset(self): self.columns_to_be_averaged = [] self.log = ''
class CSVJoiner(tr.HasStrictTraits): open_csv_files = tr.Button csv_files = tr.List(CSVFile) num_of_first_lines_to_show = tr.Range(low=0, high=10**9, value=10, mode='spinner') num_of_last_lines_to_show = tr.Range(low=0, high=10**9, value=10, mode='spinner') selected = tr.Instance(CSVFile) join_csv_files = tr.Button accumulate_time = tr.Bool files_end_with_empty_line = tr.Bool(True) columns_headers = tr.List time_column = tr.Enum(values='columns_headers') progress = tr.Int def _join_csv_files_fired(self): output_file_path = self.get_output_file_path() with open(output_file_path, 'w') as outfile: for csv_file, i in zip(self.csv_files, range(len(self.csv_files))): current_line = 1 num_of_first_lines_to_skip = csv_file.first_lines_to_skip num_of_last_lines_to_skip = csv_file.last_lines_to_skip last_line_to_write = csv_file.get_lines_number( ) - num_of_last_lines_to_skip progress_of_a_file = 1.0 / len(self.csv_files) initial_progress = i / len(self.csv_files) with open(csv_file.path) as opened_csv_file: for line in opened_csv_file: if current_line > num_of_first_lines_to_skip and current_line <= last_line_to_write: outfile.write(line) self.progress = int( (initial_progress + progress_of_a_file * (current_line / last_line_to_write)) * 100) current_line += 1 if not self.files_end_with_empty_line: outfile.write('\n') self.progress = 100 dialog = MessageDialog(title='Finished!', message='Files joined successfully, see "' + output_file_path + '"') dialog.open() def get_output_file_path(self): file_path = self.csv_files[0].path file_path_without_ext = os.path.splitext(file_path)[0] file_ext = os.path.splitext(file_path)[1] return file_path_without_ext + '_joined' + file_ext def _accumulate_time_changed(self): pass # if self.csv_files == []: # return # np.array(pd.read_csv( # self.file_csv, delimiter=self.delimiter, decimal=self.decimal, # nrows=1, header=None # ) # )[0] # if self.accumulate_time: # class TimeColumnChooser(tr.HasTraits): # time_column = tr.Enum(values = 'columns_headers') # chooser = TimeColumnChooser() # chooser.configure_traits(kind='modal') def _num_of_first_lines_to_show_changed(self): for file in self.csv_files: file.num_of_first_lines_to_show = self.num_of_first_lines_to_show def _num_of_last_lines_to_show_changed(self): for file in self.csv_files: file.num_of_last_lines_to_show = self.num_of_last_lines_to_show def _open_csv_files_fired(self): extensions = ['*.csv', '*.txt'] # handle only one extension... wildcard = ';'.join(extensions) dialog = pf.FileDialog(title='Select csv files', action='open files', wildcard=wildcard, default_path=os.path.expanduser("~")) result = dialog.open() csv_files_paths = [] # Test if the user opened a file to avoid throwing an exception # if he doesn't if result == pf.OK: csv_files_paths = dialog.paths else: return self.csv_files = [] for file_path in csv_files_paths: csv_file = CSVFile( path=file_path, num_of_first_lines_to_show=self.num_of_first_lines_to_show, num_of_last_lines_to_show=self.num_of_last_lines_to_show, ) self.csv_files.append(csv_file) # ========================================================================= # Configuration of the view # ========================================================================= traits_view = ui.View( ui.VGroup( ui.UItem('open_csv_files', width=150), ui.HGroup(ui.Item('num_of_first_lines_to_show'), ui.spring), ui.HGroup(ui.Item('num_of_last_lines_to_show'), ui.spring), ui.HGroup( ui.Item('files_end_with_empty_line'), # ui.Item('accumulate_time', enabled_when='False'), ui.spring), ui.VGroup( ui.Item('csv_files', show_label=False, style='custom', editor=ui.ListEditor(use_notebook=True, deletable=False, selected='selected', export='DockWindowShell', page_name='.name'))), ui.HGroup( ui.UItem('join_csv_files', width=150), ui.UItem('progress', editor=ProgressEditor(min=0, max=100))), show_border=True), title='CSV files joiner', resizable=True, width=0.6, height=0.7)
class SpikesRemoval(SpanSelectorInSpectrum): interpolator_kind = t.Enum( 'Linear', 'Spline', default = 'Linear') threshold = t.Float() show_derivative_histogram = t.Button() spline_order = t.Range(1,10, 3) interpolator = None default_spike_width = t.Int(5) index = t.Int(0) view = tu.View(tu.Group( tu.Group( tu.Item('show_derivative_histogram', show_label=False), 'threshold', show_border=True,), tu.Group( 'interpolator_kind', 'default_spike_width', tu.Group( 'spline_order', visible_when = 'interpolator_kind == \'Spline\''), show_border=True, label='Advanced settings'), ), buttons= [OKButton, OurPreviousButton, OurFindButton, OurApplyButton,], handler = SpikesRemovalHandler, title = 'Spikes removal tool') def __init__(self, signal,navigation_mask=None, signal_mask=None): super(SpikesRemoval, self).__init__(signal) self.interpolated_line = None self.coordinates = [coordinate for coordinate in signal.axes_manager._am_indices_generator() if (navigation_mask is None or not navigation_mask[coordinate[::-1]])] self.signal = signal sys.setrecursionlimit(np.cumprod(self.signal.data.shape)[-1]) self.line = signal._plot.signal_plot.ax_lines[0] self.ax = signal._plot.signal_plot.ax signal._plot.auto_update_plot = False signal.axes_manager.indices = self.coordinates[0] self.threshold = 400 self.index = 0 self.argmax = None self.kind = "linear" self._temp_mask = np.zeros(self.signal().shape, dtype='bool') self.signal_mask = signal_mask self.navigation_mask = navigation_mask def _threshold_changed(self, old, new): self.index = 0 self.update_plot() def _show_derivative_histogram_fired(self): self.signal._spikes_diagnosis(signal_mask=self.signal_mask, navigation_mask=self.navigation_mask) def detect_spike(self): derivative = np.diff(self.signal()) if self.signal_mask is not None: derivative[self.signal_mask[:-1]] = 0 if self.argmax is not None: left, right = self.get_interpolation_range() self._temp_mask[left:right] = True derivative[self._temp_mask[:-1]] = 0 if abs(derivative.max()) >= self.threshold: self.argmax = derivative.argmax() return True else: return False def find(self, back=False): if ((self.index == len(self.coordinates) - 1 and back is False) or (back is True and self.index == 0)): messages.information('End of dataset reached') return if self.interpolated_line is not None: self.interpolated_line.close() self.interpolated_line = None self.reset_span_selector() if self.detect_spike() is False: if back is False: self.index += 1 else: self.index -= 1 self.find(back=back) else: minimum = max(0,self.argmax - 50) maximum = min(len(self.signal()) - 1, self.argmax + 50) self.ax.set_xlim( self.signal.axes_manager.signal_axes[0].index2value( minimum), self.signal.axes_manager.signal_axes[0].index2value( maximum)) self.update_plot() self.create_interpolation_line() def update_plot(self): if self.interpolated_line is not None: self.interpolated_line.close() self.interpolated_line = None self.reset_span_selector() self.update_spectrum_line() self.signal._plot.pointer.update_patch_position() def update_spectrum_line(self): self.line.auto_update = True self.line.update() self.line.auto_update = False def _index_changed(self, old, new): self.signal.axes_manager.indices = self.coordinates[new] self.argmax = None self._temp_mask[:] = False def on_disabling_span_selector(self): if self.interpolated_line is not None: self.interpolated_line.close() self.interpolated_line = None def _spline_order_changed(self, old, new): self.kind = self.spline_order self.span_selector_changed() def _interpolator_kind_changed(self, old, new): if new == 'linear': self.kind = new else: self.kind = self.spline_order self.span_selector_changed() def _ss_left_value_changed(self, old, new): self.span_selector_changed() def _ss_right_value_changed(self, old, new): self.span_selector_changed() def create_interpolation_line(self): self.interpolated_line = drawing.spectrum.SpectrumLine() self.interpolated_line.data_function = \ self.get_interpolated_spectrum self.interpolated_line.set_line_properties( color='blue', type='line') self.signal._plot.signal_plot.add_line(self.interpolated_line) self.interpolated_line.autoscale = False self.interpolated_line.plot() def get_interpolation_range(self): axis = self.signal.axes_manager.signal_axes[0] if self.ss_left_value == self.ss_right_value: left = self.argmax - self.default_spike_width right = self.argmax + self.default_spike_width else: left = axis.value2index(self.ss_left_value) right = axis.value2index(self.ss_right_value) # Clip to the axis dimensions nchannels = self.signal.axes_manager.signal_shape[0] left = left if left >= 0 else 0 right = right if right < nchannels else nchannels - 1 return left,right def get_interpolated_spectrum(self, axes_manager=None): data = self.signal().copy() axis = self.signal.axes_manager.signal_axes[0] left, right = self.get_interpolation_range() if self.kind == 'linear': pad = 1 else: pad = 10 ileft = left - pad iright = right + pad ileft = np.clip(ileft, 0, len(data)) iright = np.clip(iright, 0, len(data)) left = np.clip(left, 0, len(data)) right = np.clip(right, 0, len(data)) x = np.hstack((axis.axis[ileft:left], axis.axis[right:iright])) y = np.hstack((data[ileft:left], data[right:iright])) if ileft == 0: # Extrapolate to the left data[left:right] = data[right + 1] elif iright == (len(data) - 1): # Extrapolate to the right data[left:right] = data[left - 1] else: # Interpolate intp = sp.interpolate.interp1d(x, y, kind=self.kind) data[left:right] = intp(axis.axis[left:right]) # Add noise data = np.random.poisson(np.clip(data, 0, np.inf)) return data def span_selector_changed(self): if self.interpolated_line is None: return else: self.interpolated_line.update() def apply(self): self.signal()[:] = self.get_interpolated_spectrum() self.update_spectrum_line() self.interpolated_line.close() self.interpolated_line = None self.reset_span_selector() self.find()
class DataAxis(t.HasTraits): name = t.Str() units = t.Str() scale = t.Float() offset = t.Float() size = t.CInt() low_value = t.Float() high_value = t.Float() value = t.Range('low_value', 'high_value') low_index = t.Int(0) high_index = t.Int() slice = t.Instance(slice) navigate = t.Bool(t.Undefined) index = t.Range('low_index', 'high_index') axis = t.Array() continuous_value = t.Bool(False) def __init__(self, size, index_in_array=None, name=t.Undefined, scale=1., offset=0., units=t.Undefined, navigate=t.Undefined): super(DataAxis, self).__init__() self.name = name self.units = units self.scale = scale self.offset = offset self.size = size self.high_index = self.size - 1 self.low_index = 0 self.index = 0 self.update_axis() self.navigate = navigate self.axes_manager = None self.on_trait_change(self.update_axis, ['scale', 'offset', 'size']) self.on_trait_change(self.update_value, 'index') self.on_trait_change(self.set_index_from_value, 'value') self.on_trait_change(self._update_slice, 'navigate') self.on_trait_change(self.update_index_bounds, 'size') # The slice must be updated even if the default value did not # change to correctly set its value. self._update_slice(self.navigate) @property def index_in_array(self): if self.axes_manager is not None: return self.axes_manager._axes.index(self) else: raise AttributeError( "This DataAxis does not belong to an AxesManager" " and therefore its index_in_array attribute " " is not defined") @property def index_in_axes_manager(self): if self.axes_manager is not None: return self.axes_manager._get_axes_in_natural_order().\ index(self) else: raise AttributeError( "This DataAxis does not belong to an AxesManager" " and therefore its index_in_array attribute " " is not defined") def _get_positive_index(self, index): if index < 0: index = self.size + index if index < 0: raise IndexError("index out of bounds") return index def _get_index(self, value): if isfloat(value): return self.value2index(value) else: return value def _get_array_slices(self, slice_): """Returns a slice to slice the corresponding data axis without changing the offset and scale of the DataAxis. Parameters ---------- slice_ : {float, int, slice} Returns ------- my_slice : slice """ v2i = self.value2index if isinstance(slice_, slice): start = slice_.start stop = slice_.stop step = slice_.step else: if isfloat(slice_): start = v2i(slice_) else: start = self._get_positive_index(slice_) stop = start + 1 step = None if isfloat(step): step = int(round(step / self.scale)) if isfloat(start): try: start = v2i(start) except ValueError: # The value is below the axis limits # we slice from the start. start = None if isfloat(stop): try: stop = v2i(stop) except ValueError: # The value is above the axes limits # we slice up to the end. stop = None if step == 0: raise ValueError("slice step cannot be zero") return slice(start, stop, step) def _slice_me(self, slice_): """Returns a slice to slice the corresponding data axis and change the offset and scale of the DataAxis acordingly. Parameters ---------- slice_ : {float, int, slice} Returns ------- my_slice : slice """ i2v = self.index2value my_slice = self._get_array_slices(slice_) start, stop, step = my_slice.start, my_slice.stop, my_slice.step if start is None: if step > 0 or step is None: start = 0 else: start = self.size - 1 self.offset = i2v(start) if step is not None: self.scale *= step return my_slice def _get_name(self): if self.name is t.Undefined: if self.axes_manager is None: name = "Unnamed" else: name = "Unnamed " + ordinal(self.index_in_axes_manager) else: name = self.name return name def __repr__(self): text = '<%s axis, size: %i' % (self._get_name(), self.size,) if self.navigate is True: text += ", index: %i" % self.index text += ">" return text.encode('utf8') def __str__(self): return self._get_name() + " axis" def connect(self, f, trait='value'): self.on_trait_change(f, trait) def disconnect(self, f, trait='value'): self.on_trait_change(f, trait, remove=True) def update_index_bounds(self): self.high_index = self.size - 1 def update_axis(self): self.axis = generate_axis(self.offset, self.scale, self.size) if len(self.axis) != 0: self.low_value, self.high_value = ( self.axis.min(), self.axis.max()) def _update_slice(self, value): if value is False: self.slice = slice(None) else: self.slice = None def get_axis_dictionary(self): adict = { 'name': self.name, 'scale': self.scale, 'offset': self.offset, 'size': self.size, 'units': self.units, 'navigate': self.navigate } return adict def copy(self): return DataAxis(**self.get_axis_dictionary()) def __copy__(self): return self.copy() def __deepcopy__(self, memo): cp = self.copy() return cp def update_value(self): self.value = self.axis[self.index] def value2index(self, value, rounding=round): """Return the closest index to the given value if between the limit. Parameters ---------- value : number or numpy array Returns ------- index : integer or numpy array Raises ------ ValueError if any value is out of the axis limits. """ if value is None: return None if isinstance(value, np.ndarray): if rounding is round: rounding = np.round elif rounding is math.ceil: rounding = np.ceil elif rounding is math.floor: rounding = np.floor index = rounding((value - self.offset) / self.scale) if isinstance(value, np.ndarray): index = index.astype(int) if np.all(self.size > index) and np.all(index >= 0): return index else: raise ValueError("A value is out of the axis limits") else: index = int(index) if self.size > index >= 0: return index else: raise ValueError("The value is out of the axis limits") def index2value(self, index): if isinstance(index, np.ndarray): return self.axis[index.ravel()].reshape(index.shape) else: return self.axis[index] def set_index_from_value(self, value): self.index = self.value2index(value) # If the value is above the limits we must correct the value if self.continuous_value is False: self.value = self.index2value(self.index) def calibrate(self, value_tuple, index_tuple, modify_calibration=True): scale = (value_tuple[1] - value_tuple[0]) /\ (index_tuple[1] - index_tuple[0]) offset = value_tuple[0] - scale * index_tuple[0] if modify_calibration is True: self.offset = offset self.scale = scale else: return offset, scale def value_range_to_indices(self, v1, v2): """Convert the given range to index range. When an out of the axis limits, the endpoint is used instead. Parameters ---------- v1, v2 : float The end points of the interval in the axis units. v2 must be greater than v1. """ if v1 > v2: raise ValueError("v2 must be greater than v1.") if v1 is not None and v1 > self.low_value and v1 <= self.high_value: i1 = self.value2index(v1) else: i1 = 0 if v2 is not None and v2 < self.high_value and v2 >= self.low_value: i2 = self.value2index(v2) else: i2 = self.size - 1 return i1, i2
class BackgroundRemoval(SpanSelectorInSpectrum): background_type = t.Enum('Power Law', 'Gaussian', 'Offset', 'Polynomial', default='Power Law') polynomial_order = t.Range(1, 10) background_estimator = t.Instance(Component) bg_line_range = t.Enum('from_left_range', 'full', 'ss_range', default='full') hi = t.Int(0) view = tu.View(tu.Group( 'background_type', tu.Group('polynomial_order', visible_when='background_type == \'Polynomial\''), ), buttons=[OKButton, CancelButton], handler=SpanSelectorInSpectrumHandler, title='Background removal tool') def __init__(self, signal): super(BackgroundRemoval, self).__init__(signal) self.set_background_estimator() self.bg_line = None def on_disabling_span_selector(self): if self.bg_line is not None: self.bg_line.close() self.bg_line = None def set_background_estimator(self): if self.background_type == 'Power Law': self.background_estimator = components.PowerLaw() self.bg_line_range = 'from_left_range' elif self.background_type == 'Gaussian': self.background_estimator = components.Gaussian() self.bg_line_range = 'full' elif self.background_type == 'Offset': self.background_estimator = components.Offset() self.bg_line_range = 'full' elif self.background_type == 'Polynomial': self.background_estimator = \ components.Polynomial(self.polynomial_order) self.bg_line_range = 'full' def _polynomial_order_changed(self, old, new): self.background_estimator = components.Polynomial(new) self.span_selector_changed() def _background_type_changed(self, old, new): self.set_background_estimator() self.span_selector_changed() def _ss_left_value_changed(self, old, new): self.span_selector_changed() def _ss_right_value_changed(self, old, new): self.span_selector_changed() def create_background_line(self): self.bg_line = drawing.spectrum.SpectrumLine() self.bg_line.data_function = self.bg_to_plot self.bg_line.set_line_properties(color='blue', type='line') self.signal._plot.signal_plot.add_line(self.bg_line) self.bg_line.autoscale = False self.bg_line.plot() def bg_to_plot(self, axes_manager=None, fill_with=np.nan): # First try to update the estimation self.background_estimator.estimate_parameters(self.signal, self.ss_left_value, self.ss_right_value, only_current=True) if self.bg_line_range == 'from_left_range': bg_array = np.zeros(self.axis.axis.shape) bg_array[:] = fill_with from_index = self.axis.value2index(self.ss_left_value) bg_array[from_index:] = self.background_estimator.function( self.axis.axis[from_index:]) return bg_array elif self.bg_line_range == 'full': return self.background_estimator.function(self.axis.axis) elif self.bg_line_range == 'ss_range': bg_array = np.zeros(self.axis.axis.shape) bg_array[:] = fill_with from_index = self.axis.value2index(self.ss_left_value) to_index = self.axis.value2index(self.ss_right_value) bg_array[from_index:] = self.background_estimator.function( self.axis.axis[from_index:to_index]) def span_selector_changed(self): if self.background_estimator is None: print("No bg estimator") return if self.bg_line is None and \ self.background_estimator.estimate_parameters( self.signal, self.ss_left_value, self.ss_right_value, only_current = True) is True: self.create_background_line() else: self.bg_line.update() def apply(self): self.signal._plot.auto_update_plot = False maxval = self.signal.axes_manager.navigation_size if maxval > 0: pbar = progressbar(maxval=maxval) i = 0 self.bg_line_range = 'full' for s in self.signal: s.data[:] -= \ np.nan_to_num(self.bg_to_plot(self.signal.axes_manager, 0)) if self.background_type == 'Power Law': s.data[:self.axis.value2index(self.ss_right_value)] = 0 i += 1 if maxval > 0: pbar.update(i) if maxval > 0: pbar.finish() self.signal._replot() self.signal._plot.auto_update_plot = True
class SpikesRemoval(SpanSelectorInSignal1D): interpolator_kind = t.Enum( 'Linear', 'Spline', default='Linear', desc="the type of interpolation to use when\n" "replacing the signal where a spike has been replaced") threshold = t.Float(desc="the derivative magnitude threshold above\n" "which to find spikes") click_to_show_instructions = t.Button() show_derivative_histogram = t.Button() spline_order = t.Range(1, 10, 3, desc="the order of the spline used to\n" "connect the reconstructed data") interpolator = None default_spike_width = t.Int( 5, desc="the width over which to do the interpolation\n" "when removing a spike (this can be " "adjusted for each\nspike by clicking " "and dragging on the display during\n" "spike replacement)") index = t.Int(0) add_noise = t.Bool(True, desc="whether to add noise to the interpolated\nportion" "of the spectrum. The noise properties defined\n" "in the Signal metadata are used if present," "otherwise\nshot noise is used as a default") thisOKButton = tu.Action(name="OK", action="OK", tooltip="Close the spikes removal tool") thisApplyButton = tu.Action(name="Remove spike", action="apply", tooltip="Remove the current spike by " "interpolating\n" "with the specified settings (and find\n" "the next spike automatically)") thisFindButton = tu.Action( name="Find next", action="find", tooltip="Find the next (in terms of navigation\n" "dimensions) spike in the data.") thisPreviousButton = tu.Action(name="Find previous", action="back", tooltip="Find the previous (in terms of " "navigation\n" "dimensions) spike in the data.") view = tu.View( tu.Group( tu.Group( tu.Item( 'click_to_show_instructions', show_label=False, ), tu.Item('show_derivative_histogram', show_label=False, tooltip="To determine the appropriate threshold,\n" "plot the derivative magnitude histogram, \n" "and look for outliers at high magnitudes \n" "(which represent sudden spikes in the data)"), 'threshold', show_border=True, ), tu.Group('add_noise', 'interpolator_kind', 'default_spike_width', tu.Group('spline_order', enabled_when='interpolator_kind == \'Spline\''), show_border=True, label='Advanced settings'), ), buttons=[ thisOKButton, thisPreviousButton, thisFindButton, thisApplyButton, ], handler=SpikesRemovalHandler, title='Spikes removal tool', resizable=False, ) def __init__(self, signal, navigation_mask=None, signal_mask=None): super(SpikesRemoval, self).__init__(signal) self.interpolated_line = None self.coordinates = [ coordinate for coordinate in signal.axes_manager._am_indices_generator() if (navigation_mask is None or not navigation_mask[coordinate[::-1]]) ] self.signal = signal self.line = signal._plot.signal_plot.ax_lines[0] self.ax = signal._plot.signal_plot.ax signal._plot.auto_update_plot = False if len(self.coordinates) > 1: signal.axes_manager.indices = self.coordinates[0] self.threshold = 400 self.index = 0 self.argmax = None self.derivmax = None self.kind = "linear" self._temp_mask = np.zeros(self.signal().shape, dtype='bool') self.signal_mask = signal_mask self.navigation_mask = navigation_mask md = self.signal.metadata from hyperspy.signal import BaseSignal if "Signal.Noise_properties" in md: if "Signal.Noise_properties.variance" in md: self.noise_variance = md.Signal.Noise_properties.variance if isinstance(md.Signal.Noise_properties.variance, BaseSignal): self.noise_type = "heteroscedastic" else: self.noise_type = "white" else: self.noise_type = "shot noise" else: self.noise_type = "shot noise" def _threshold_changed(self, old, new): self.index = 0 self.update_plot() def _click_to_show_instructions_fired(self): m = information(None, "\nTo remove spikes from the data:\n\n" " 1. Click \"Show derivative histogram\" to " "determine at what magnitude the spikes are present.\n" " 2. Enter a suitable threshold (lower than the " "lowest magnitude outlier in the histogram) in the " "\"Threshold\" box, which will be the magnitude " "from which to search. \n" " 3. Click \"Find next\" to find the first spike.\n" " 4. If desired, the width and position of the " "boundaries used to replace the spike can be " "adjusted by clicking and dragging on the displayed " "plot.\n " " 5. View the spike (and the replacement data that " "will be added) and click \"Remove spike\" in order " "to alter the data as shown. The tool will " "automatically find the next spike to replace.\n" " 6. Repeat this process for each spike throughout " "the dataset, until the end of the dataset is " "reached.\n" " 7. Click \"OK\" when finished to close the spikes " "removal tool.\n\n" "Note: Various settings can be configured in " "the \"Advanced settings\" section. Hover the " "mouse over each parameter for a description of what " "it does." "\n", title="Instructions"), def _show_derivative_histogram_fired(self): self.signal._spikes_diagnosis(signal_mask=self.signal_mask, navigation_mask=self.navigation_mask) def detect_spike(self): derivative = np.diff(self.signal()) if self.signal_mask is not None: derivative[self.signal_mask[:-1]] = 0 if self.argmax is not None: left, right = self.get_interpolation_range() self._temp_mask[left:right] = True derivative[self._temp_mask[:-1]] = 0 if abs(derivative.max()) >= self.threshold: self.argmax = derivative.argmax() self.derivmax = abs(derivative.max()) return True else: return False def _reset_line(self): if self.interpolated_line is not None: self.interpolated_line.close() self.interpolated_line = None self.reset_span_selector() def find(self, back=False): self._reset_line() ncoordinates = len(self.coordinates) spike = self.detect_spike() while not spike and ((self.index < ncoordinates - 1 and back is False) or (self.index > 0 and back is True)): if back is False: self.index += 1 else: self.index -= 1 spike = self.detect_spike() if spike is False: messages.information('End of dataset reached') self.index = 0 self._reset_line() return else: minimum = max(0, self.argmax - 50) maximum = min(len(self.signal()) - 1, self.argmax + 50) thresh_label = DerivativeTextParameters( text="$\mathsf{\delta}_\mathsf{max}=$", color="black") self.ax.legend([thresh_label], [repr(int(self.derivmax))], handler_map={ DerivativeTextParameters: DerivativeTextHandler() }, loc='best') self.ax.set_xlim( self.signal.axes_manager.signal_axes[0].index2value(minimum), self.signal.axes_manager.signal_axes[0].index2value(maximum)) self.update_plot() self.create_interpolation_line() def update_plot(self): if self.interpolated_line is not None: self.interpolated_line.close() self.interpolated_line = None self.reset_span_selector() self.update_spectrum_line() if len(self.coordinates) > 1: self.signal._plot.pointer._update_patch_position() def update_spectrum_line(self): self.line.auto_update = True self.line.update() self.line.auto_update = False def _index_changed(self, old, new): self.signal.axes_manager.indices = self.coordinates[new] self.argmax = None self._temp_mask[:] = False def on_disabling_span_selector(self): if self.interpolated_line is not None: self.interpolated_line.close() self.interpolated_line = None def _spline_order_changed(self, old, new): self.kind = self.spline_order self.span_selector_changed() def _add_noise_changed(self, old, new): self.span_selector_changed() def _interpolator_kind_changed(self, old, new): if new == 'linear': self.kind = new else: self.kind = self.spline_order self.span_selector_changed() def _ss_left_value_changed(self, old, new): if not (np.isnan(self.ss_right_value) or np.isnan(self.ss_left_value)): self.span_selector_changed() def _ss_right_value_changed(self, old, new): if not (np.isnan(self.ss_right_value) or np.isnan(self.ss_left_value)): self.span_selector_changed() def create_interpolation_line(self): self.interpolated_line = drawing.signal1d.Signal1DLine() self.interpolated_line.data_function = self.get_interpolated_spectrum self.interpolated_line.set_line_properties(color='blue', type='line') self.signal._plot.signal_plot.add_line(self.interpolated_line) self.interpolated_line.autoscale = False self.interpolated_line.plot() def get_interpolation_range(self): axis = self.signal.axes_manager.signal_axes[0] if np.isnan(self.ss_left_value) or np.isnan(self.ss_right_value): left = self.argmax - self.default_spike_width right = self.argmax + self.default_spike_width else: left = axis.value2index(self.ss_left_value) right = axis.value2index(self.ss_right_value) # Clip to the axis dimensions nchannels = self.signal.axes_manager.signal_shape[0] left = left if left >= 0 else 0 right = right if right < nchannels else nchannels - 1 return left, right def get_interpolated_spectrum(self, axes_manager=None): data = self.signal().copy() axis = self.signal.axes_manager.signal_axes[0] left, right = self.get_interpolation_range() if self.kind == 'linear': pad = 1 else: pad = 10 ileft = left - pad iright = right + pad ileft = np.clip(ileft, 0, len(data)) iright = np.clip(iright, 0, len(data)) left = int(np.clip(left, 0, len(data))) right = int(np.clip(right, 0, len(data))) x = np.hstack((axis.axis[ileft:left], axis.axis[right:iright])) y = np.hstack((data[ileft:left], data[right:iright])) if ileft == 0: # Extrapolate to the left data[left:right] = data[right + 1] elif iright == (len(data) - 1): # Extrapolate to the right data[left:right] = data[left - 1] else: # Interpolate intp = sp.interpolate.interp1d(x, y, kind=self.kind) data[left:right] = intp(axis.axis[left:right]) # Add noise if self.add_noise is True: if self.noise_type == "white": data[left:right] += np.random.normal(scale=np.sqrt( self.noise_variance), size=right - left) elif self.noise_type == "heteroscedastic": noise_variance = self.noise_variance( axes_manager=self.signal.axes_manager)[left:right] noise = [ np.random.normal(scale=np.sqrt(item)) for item in noise_variance ] data[left:right] += noise else: data[left:right] = np.random.poisson( np.clip(data[left:right], 0, np.inf)) return data def span_selector_changed(self): if self.interpolated_line is None: return else: self.interpolated_line.update() def apply(self): self.signal()[:] = self.get_interpolated_spectrum() self.signal.events.data_changed.trigger(obj=self.signal) self.update_spectrum_line() self.interpolated_line.close() self.interpolated_line = None self.reset_span_selector() self.find()
class Oscilloscope(traits.HasTraits): """Oscilloscope style plot of the selected channels. Rolling mode/ auto trigger """ masterContainer = traits.Instance(chaco.Plot) arrayPlotData = traits.Instance(chaco.ArrayPlotData) visibleChannels = traits.List([0]) connection = None numberOfPoints = traits.Int( 10000, desc= "number of points displayed on plot. number of Points*resolution = timespan of plot" ) verticalLimit = traits.Range(low=0.0, high=5.0, value=3.3) resolution = traits.Float( 0.1, desc= "number of points displayed on plot. number of Points*resolution = timespan of plot" ) settingsGroup = traitsui.Group( traitsui.Item("numberOfPoints"), traitsui.Item("verticalLimit", label="Vertical Scale")) traits_view = traitsui.View( traitsui.VGroup( settingsGroup, traitsui.Group( traitsui.Item('masterContainer', editor=ComponentEditor(), show_label=False)))) def __init__(self, **traitsDict): """initialise the plot""" super(Oscilloscope, self).__init__(**traitsDict) self.colors = ["black", "red", "blue"] self.initialiseData() self.initialisePlots() self._visibleChannels_changed() def _visibleChannels_changed(self): """make channels visible or not depending on list """ for i in range(0, 8): if i in self.visibleChannels: self.masterContainer.plots["channel" + str(i)][0].visible = True else: print i self.masterContainer.plots["channel" + str(i)][0].visible = False def _numberOfPoints_changed(self): """when number of points changes re-initialise the data """ self.reinitialiseData() def _resolution_changed(self): """when resolution of points changes re-initialise the data """ self.reinitialiseData() def _verticalLimit_changed(self): """"changes y scale of vertical axis """ self.masterContainer.range2d.y_range.high = self.verticalLimit def initialisePlots(self): """draw plots """ self.masterContainer = chaco.Plot(self.arrayPlotData, padding=100, bgcolor="white", use_backbuffer=True, border_visible=False, fill_padding=True) self.masterContainer.plot(("xs", "channel0"), type="line", name="channel0", color=self.colors[0 % len(self.colors)]) self.masterContainer.plot(("xs", "channel1"), type="line", name="channel1", color=self.colors[1 % len(self.colors)]) self.masterContainer.plot(("xs", "channel2"), type="line", name="channel2", color=self.colors[2 % len(self.colors)]) self.masterContainer.plot(("xs", "channel3"), type="line", name="channel3", color=self.colors[3 % len(self.colors)]) self.masterContainer.plot(("xs", "channel4"), type="line", name="channel4", color=self.colors[4 % len(self.colors)]) self.masterContainer.plot(("xs", "channel5"), type="line", name="channel5", color=self.colors[5 % len(self.colors)]) self.masterContainer.plot(("xs", "channel6"), type="line", name="channel6", color=self.colors[6 % len(self.colors)]) self.masterContainer.plot(("xs", "channel7"), type="line", name="channel7", color=self.colors[7 % len(self.colors)]) self.masterContainer.plot(("cursorXS", "cursorVertical"), type="line", line_style="dash", name="cursor", color="green") def getCurrentPositionAsFloat(self): return self.currentPosition * self.resolution def getCurrentPositionArray(self): return scipy.array([self.currentPosition * self.resolution] * 2) def initialiseData(self): """sets up data arrays and fills arrayPlotData """ self.currentPosition = 0 self.xs = scipy.linspace(0.0, self.numberOfPoints * self.resolution, self.numberOfPoints) self.cursorXS = self.getCurrentPositionArray() self.cursorVertical = scipy.array([self.verticalLimit, 0.0]) self.array0 = scipy.zeros(self.numberOfPoints) self.array1 = scipy.zeros(self.numberOfPoints) self.array2 = scipy.zeros(self.numberOfPoints) self.array3 = scipy.zeros(self.numberOfPoints) self.array4 = scipy.zeros(self.numberOfPoints) self.array5 = scipy.zeros(self.numberOfPoints) self.array6 = scipy.zeros(self.numberOfPoints) self.array7 = scipy.zeros(self.numberOfPoints) self.channels = [ self.array0, self.array1, self.array2, self.array3, self.array4, self.array5, self.array6, self.array7 ] self.arrayPlotData = chaco.ArrayPlotData( xs=self.xs, channel0=self.array0, channel1=self.array1, channel2=self.array2, channel3=self.array3, channel4=self.array4, channel5=self.array5, channel6=self.array6, channel7=self.array7, cursorXS=self.cursorXS, cursorVertical=self.cursorVertical ) #will be the ArrayPlotData We need def reinitialiseData(self): """sets up data arrays and fills arrayPlotData """ if self.arrayPlotData is not None: self.currentPosition = 0 self.xs = scipy.linspace(0.0, self.numberOfPoints * self.resolution, self.numberOfPoints) self.cursorXS = self.getCurrentPositionArray() self.cursorVertical = scipy.array([self.verticalLimit, 0.0]) self.arrayPlotData.set_data("xs", self.xs) self.array0 = scipy.zeros(self.numberOfPoints) self.array1 = scipy.zeros(self.numberOfPoints) self.array2 = scipy.zeros(self.numberOfPoints) self.array3 = scipy.zeros(self.numberOfPoints) self.array4 = scipy.zeros(self.numberOfPoints) self.array5 = scipy.zeros(self.numberOfPoints) self.array6 = scipy.zeros(self.numberOfPoints) self.array7 = scipy.zeros(self.numberOfPoints) self.channels = [ self.array0, self.array1, self.array2, self.array3, self.array4, self.array5, self.array6, self.array7 ] self.updateArrayPlotData() def _voltage_get(self, channelNumber): """Uses PyHWI connection to ADC server to return voltage """ #print "latest=%s" % self.connection.latestResults if channelNumber in self.connection.latestResults: return self.connection.latestResults[channelNumber] else: return scipy.NaN def updateArrays(self): """update all arrays with the latest value """ for channelNumber in range(0, 8): self.channels[channelNumber][ self.currentPosition] = self._voltage_get( channelNumber) #update next element in each array self.currentPosition += 1 if self.currentPosition >= self.numberOfPoints: #reset position to beginning when we hit max number of points (like rolling oscilloscope) self.currentPosition = 0 self.cursorXS = self.getCurrentPositionArray() #could also set the next points to NaN's to make a gap! def updateArrayPlotData(self): """push changes of arrays to the plot """ self.arrayPlotData.set_data("channel0", self.array0) self.arrayPlotData.set_data("channel1", self.array1) self.arrayPlotData.set_data("channel2", self.array2) self.arrayPlotData.set_data("channel3", self.array3) self.arrayPlotData.set_data("channel4", self.array4) self.arrayPlotData.set_data("channel5", self.array5) self.arrayPlotData.set_data("channel6", self.array6) self.arrayPlotData.set_data("channel7", self.array7) self.arrayPlotData.set_data("cursorXS", self.cursorXS)
class BackgroundRemoval(SpanSelectorInSignal1D): background_type = t.Enum('Power Law', 'Gaussian', 'Offset', 'Polynomial', default='Power Law') polynomial_order = t.Range(1, 10) fast = t.Bool(True, desc=("Perform a fast (analytic, but possibly less accurate)" " estimation of the background. Otherwise use " "non-linear least squares.")) background_estimator = t.Instance(Component) bg_line_range = t.Enum('from_left_range', 'full', 'ss_range', default='full') hi = t.Int(0) view = tu.View( tu.Group( 'background_type', 'fast', tu.Group('polynomial_order', visible_when='background_type == \'Polynomial\''), ), buttons=[OKButton, CancelButton], handler=SpanSelectorInSignal1DHandler, title='Background removal tool', resizable=True, width=300, ) def __init__(self, signal): super(BackgroundRemoval, self).__init__(signal) self.set_background_estimator() self.bg_line = None def on_disabling_span_selector(self): if self.bg_line is not None: self.bg_line.close() self.bg_line = None def set_background_estimator(self): if self.background_type == 'Power Law': self.background_estimator = components1d.PowerLaw() self.bg_line_range = 'from_left_range' elif self.background_type == 'Gaussian': self.background_estimator = components1d.Gaussian() self.bg_line_range = 'full' elif self.background_type == 'Offset': self.background_estimator = components1d.Offset() self.bg_line_range = 'full' elif self.background_type == 'Polynomial': self.background_estimator = components1d.Polynomial( self.polynomial_order) self.bg_line_range = 'full' def _polynomial_order_changed(self, old, new): self.background_estimator = components1d.Polynomial(new) self.span_selector_changed() def _background_type_changed(self, old, new): self.set_background_estimator() self.span_selector_changed() def _ss_left_value_changed(self, old, new): if not (np.isnan(self.ss_right_value) or np.isnan(self.ss_left_value)): self.span_selector_changed() def _ss_right_value_changed(self, old, new): if not (np.isnan(self.ss_right_value) or np.isnan(self.ss_left_value)): self.span_selector_changed() def create_background_line(self): self.bg_line = drawing.signal1d.Signal1DLine() self.bg_line.data_function = self.bg_to_plot self.bg_line.set_line_properties(color='blue', type='line', scaley=False) self.signal._plot.signal_plot.add_line(self.bg_line) self.bg_line.autoscale = False self.bg_line.plot() def bg_to_plot(self, axes_manager=None, fill_with=np.nan): # First try to update the estimation self.background_estimator.estimate_parameters(self.signal, self.ss_left_value, self.ss_right_value, only_current=True) if self.bg_line_range == 'from_left_range': bg_array = np.zeros(self.axis.axis.shape) bg_array[:] = fill_with from_index = self.axis.value2index(self.ss_left_value) bg_array[from_index:] = self.background_estimator.function( self.axis.axis[from_index:]) to_return = bg_array elif self.bg_line_range == 'full': to_return = self.background_estimator.function(self.axis.axis) elif self.bg_line_range == 'ss_range': bg_array = np.zeros(self.axis.axis.shape) bg_array[:] = fill_with from_index = self.axis.value2index(self.ss_left_value) to_index = self.axis.value2index(self.ss_right_value) bg_array[from_index:] = self.background_estimator.function( self.axis.axis[from_index:to_index]) to_return = bg_array if self.signal.metadata.Signal.binned is True: to_return *= self.axis.scale return to_return def span_selector_changed(self): if self.ss_left_value is np.nan or self.ss_right_value is np.nan or\ self.ss_right_value <= self.ss_left_value: return if self.background_estimator is None: return if self.bg_line is None and \ self.background_estimator.estimate_parameters( self.signal, self.ss_left_value, self.ss_right_value, only_current=True) is True: self.create_background_line() else: self.bg_line.update() def apply(self): self.signal._plot.auto_update_plot = False new_spectra = self.signal._remove_background_cli( (self.ss_left_value, self.ss_right_value), self.background_estimator, fast=self.fast) self.signal.data = new_spectra.data self.signal._replot() self.signal._plot.auto_update_plot = True
class DataAxis(t.HasTraits): name = t.Str() units = t.Str() scale = t.Float() offset = t.Float() size = t.CInt() low_value = t.Float() high_value = t.Float() value = t.Range('low_value', 'high_value') low_index = t.Int(0) high_index = t.Int() slice = t.Instance(slice) navigate = t.Bool(t.Undefined) index = t.Range('low_index', 'high_index') axis = t.Array() continuous_value = t.Bool(False) def __init__(self, size, index_in_array=None, name=t.Undefined, scale=1., offset=0., units=t.Undefined, navigate=t.Undefined): super(DataAxis, self).__init__() self.events = Events() self.events.index_changed = Event(""" Event that triggers when the index of the `DataAxis` changes Triggers after the internal state of the `DataAxis` has been updated. Arguments: --------- obj : The DataAxis that the event belongs to. index : The new index """, arguments=["obj", 'index']) self.events.value_changed = Event(""" Event that triggers when the value of the `DataAxis` changes Triggers after the internal state of the `DataAxis` has been updated. Arguments: --------- obj : The DataAxis that the event belongs to. value : The new value """, arguments=["obj", 'value']) self._suppress_value_changed_trigger = False self._suppress_update_value = False self.name = name self.units = units self.scale = scale self.offset = offset self.size = size self.high_index = self.size - 1 self.low_index = 0 self.index = 0 self.update_axis() self.navigate = navigate self.axes_manager = None self.on_trait_change(self.update_axis, ['scale', 'offset', 'size']) self.on_trait_change(self._update_slice, 'navigate') self.on_trait_change(self.update_index_bounds, 'size') # The slice must be updated even if the default value did not # change to correctly set its value. self._update_slice(self.navigate) def _index_changed(self, name, old, new): self.events.index_changed.trigger(obj=self, index=self.index) if not self._suppress_update_value: new_value = self.axis[self.index] if new_value != self.value: self.value = new_value def _value_changed(self, name, old, new): old_index = self.index new_index = self.value2index(new) if self.continuous_value is False: # Only values in the grid alowed if old_index != new_index: self.index = new_index if new == self.axis[self.index]: self.events.value_changed.trigger(obj=self, value=new) elif old_index == new_index: new_value = self.index2value(new_index) if new_value == old: self._suppress_value_changed_trigger = True try: self.value = new_value finally: self._suppress_value_changed_trigger = False elif new_value == new and not\ self._suppress_value_changed_trigger: self.events.value_changed.trigger(obj=self, value=new) else: # Intergrid values are alowed. This feature is deprecated self.events.value_changed.trigger(obj=self, value=new) if old_index != new_index: self._suppress_update_value = True self.index = new_index self._suppress_update_value = False @property def index_in_array(self): if self.axes_manager is not None: return self.axes_manager._axes.index(self) else: raise AttributeError( "This DataAxis does not belong to an AxesManager" " and therefore its index_in_array attribute " " is not defined") @property def index_in_axes_manager(self): if self.axes_manager is not None: return self.axes_manager._get_axes_in_natural_order().\ index(self) else: raise AttributeError( "This DataAxis does not belong to an AxesManager" " and therefore its index_in_array attribute " " is not defined") def _get_positive_index(self, index): if index < 0: index = self.size + index if index < 0: raise IndexError("index out of bounds") return index def _get_index(self, value): if isfloat(value): return self.value2index(value) else: return value def _get_array_slices(self, slice_): """Returns a slice to slice the corresponding data axis without changing the offset and scale of the DataAxis. Parameters ---------- slice_ : {float, int, slice} Returns ------- my_slice : slice """ v2i = self.value2index if isinstance(slice_, slice): start = slice_.start stop = slice_.stop step = slice_.step else: if isfloat(slice_): start = v2i(slice_) else: start = self._get_positive_index(slice_) stop = start + 1 step = None if isfloat(step): step = int(round(step / self.scale)) if isfloat(start): try: start = v2i(start) except ValueError: if start > self.high_value: # The start value is above the axis limit raise IndexError( "Start value above axis high bound for axis %s." "value: %f high_bound: %f" % (repr(self), start, self.high_value)) else: # The start value is below the axis limit, # we slice from the start. start = None if isfloat(stop): try: stop = v2i(stop) except ValueError: if stop < self.low_value: # The stop value is below the axis limits raise IndexError( "Stop value below axis low bound for axis %s." "value: %f low_bound: %f" % (repr(self), stop, self.low_value)) else: # The stop value is below the axis limit, # we slice until the end. stop = None if step == 0: raise ValueError("slice step cannot be zero") return slice(start, stop, step) def _slice_me(self, slice_): """Returns a slice to slice the corresponding data axis and change the offset and scale of the DataAxis acordingly. Parameters ---------- slice_ : {float, int, slice} Returns ------- my_slice : slice """ i2v = self.index2value my_slice = self._get_array_slices(slice_) start, stop, step = my_slice.start, my_slice.stop, my_slice.step if start is None: if step is None or step > 0: start = 0 else: start = self.size - 1 self.offset = i2v(start) if step is not None: self.scale *= step return my_slice def _get_name(self): if self.name is t.Undefined: if self.axes_manager is None: name = "Unnamed" else: name = "Unnamed " + ordinal(self.index_in_axes_manager) else: name = self.name return name def __repr__(self): text = '<%s axis, size: %i' % ( self._get_name(), self.size, ) if self.navigate is True: text += ", index: %i" % self.index text += ">" return text def __str__(self): return self._get_name() + " axis" def update_index_bounds(self): self.high_index = self.size - 1 def update_axis(self): self.axis = generate_axis(self.offset, self.scale, self.size) if len(self.axis) != 0: self.low_value, self.high_value = (self.axis.min(), self.axis.max()) def _update_slice(self, value): if value is False: self.slice = slice(None) else: self.slice = None def get_axis_dictionary(self): adict = { 'name': self.name, 'scale': self.scale, 'offset': self.offset, 'size': self.size, 'units': self.units, 'navigate': self.navigate } return adict def copy(self): return DataAxis(**self.get_axis_dictionary()) def __copy__(self): return self.copy() def __deepcopy__(self, memo): cp = self.copy() return cp def value2index(self, value, rounding=round): """Return the closest index to the given value if between the limit. Parameters ---------- value : number or numpy array Returns ------- index : integer or numpy array Raises ------ ValueError if any value is out of the axis limits. """ if value is None: return None if isinstance(value, np.ndarray): if rounding is round: rounding = np.round elif rounding is math.ceil: rounding = np.ceil elif rounding is math.floor: rounding = np.floor index = rounding((value - self.offset) / self.scale) if isinstance(value, np.ndarray): index = index.astype(int) if np.all(self.size > index) and np.all(index >= 0): return index else: raise ValueError("A value is out of the axis limits") else: index = int(index) if self.size > index >= 0: return index else: raise ValueError("The value is out of the axis limits") def index2value(self, index): if isinstance(index, np.ndarray): return self.axis[index.ravel()].reshape(index.shape) else: return self.axis[index] def calibrate(self, value_tuple, index_tuple, modify_calibration=True): scale = (value_tuple[1] - value_tuple[0]) /\ (index_tuple[1] - index_tuple[0]) offset = value_tuple[0] - scale * index_tuple[0] if modify_calibration is True: self.offset = offset self.scale = scale else: return offset, scale def value_range_to_indices(self, v1, v2): """Convert the given range to index range. When an out of the axis limits, the endpoint is used instead. Parameters ---------- v1, v2 : float The end points of the interval in the axis units. v2 must be greater than v1. """ if v1 is not None and v2 is not None and v1 > v2: raise ValueError("v2 must be greater than v1.") if v1 is not None and self.low_value < v1 <= self.high_value: i1 = self.value2index(v1) else: i1 = 0 if v2 is not None and self.high_value > v2 >= self.low_value: i2 = self.value2index(v2) else: i2 = self.size - 1 return i1, i2 def update_from(self, axis, attributes=["scale", "offset", "units"]): """Copy values of specified axes fields from the passed AxesManager. Parameters ---------- axis : DataAxis The DataAxis instance to use as a source for values. attributes : iterable container of strings. The name of the attribute to update. If the attribute does not exist in either of the AxesManagers, an AttributeError will be raised. Returns ------- A boolean indicating whether any changes were made. """ any_changes = False changed = {} for f in attributes: if getattr(self, f) != getattr(axis, f): changed[f] = getattr(axis, f) if len(changed) > 0: self.trait_set(**changed) any_changes = True return any_changes