class FiltWNoiseGenerator(WNoiseGenerator): """ Filtered white noise signal following an autoregressive (AR), moving-average (MA) or autoregressive moving-average (ARMA) process. The desired frequency response of the filter can be defined by specifying the filter coefficients :attr:`ar` and :attr:`ma`. The RMS value specified via the :attr:`rms` attribute belongs to the white noise signal and differs from the RMS value of the filtered signal. For numerical stability at high orders, the filter is a combination of second order sections (sos). """ ar = CArray( value=array([]), dtype=float, desc="autoregressive coefficients (coefficients of the denominator)") ma = CArray( value=array([]), dtype=float, desc="moving-average coefficients (coefficients of the numerator)") # internal identifier digest = Property( depends_on = [ 'ar', 'ma', 'rms', 'numsamples', \ 'sample_freq', 'seed', '__class__' ], ) @cached_property def _get_digest(self): return digest(self) def handle_empty_coefficients(self, coefficients): if coefficients.size == 0: return array([1.0]) else: return coefficients def signal(self): """ Deliver the signal. Returns ------- Array of floats The resulting signal as an array of length :attr:`~SignalGenerator.numsamples`. """ rnd_gen = RandomState(self.seed) ma = self.handle_empty_coefficients(self.ma) ar = self.handle_empty_coefficients(self.ar) sos = tf2sos(ma, ar) ntaps = ma.shape[0] sdelay = round(0.5 * (ntaps - 1)) wnoise = self.rms * rnd_gen.standard_normal( self.numsamples + sdelay) # create longer signal to compensate delay return sosfilt(sos, x=wnoise)[sdelay:]
class ElevationFilterFactory(PipeFactory): """Applies the Elevation Filter mayavi filter to the given VTK object.""" high_point = CArray(default=[0, 0, 1], shape=(3,), adapts="filter.high_point", help="The end point of the projection line") low_point = CArray(default=[0, 0, 0], shape=(3,), adapts="filter.low_point", help="The start point of the projection line") _target = Instance(filters.ElevationFilter, ())
class UniformFlowEnvironment(Environment): """ An acoustic environment with uniform flow. This class provides the facilities to calculate the travel time (distances) between grid point locations and microphone locations in a uniform flow field. """ #: The Mach number, defaults to 0. ma = Float(0.0, desc="flow mach number") #: The unit vector that gives the direction of the flow, defaults to #: flow in x-direction. fdv = CArray(dtype=float64, shape=(3, ), value=array((1.0, 0, 0)), desc="flow direction") # internal identifier digest = Property(depends_on=['c', 'ma', 'fdv'], ) @cached_property def _get_digest(self): return digest(self) def _r(self, gpos, mpos=0.0): """ Calculates the virtual distances between grid point locations and microphone locations or the origin. These virtual distances correspond to travel times of the sound. Functionality may change in the future. Parameters ---------- gpos : array of floats of shape (3, N) The locations of points in the beamforming map grid in 3D cartesian co-ordinates. mpos : array of floats of shape (3, M), optional The locations of microphones in 3D cartesian co-ordinates. If not given, then only one microphone at the origin (0, 0, 0) is considered. Returns ------- array of floats The distances in a twodimensional (N, M) array of floats. If M==1, then only a one-dimensional array is returned. """ if isscalar(mpos): mpos = array((0, 0, 0), dtype=float32)[:, newaxis] fdv = self.fdv / sqrt((self.fdv * self.fdv).sum()) mpos = mpos[:, newaxis, :] rmv = gpos[:, :, newaxis] - mpos rm = sqrt(sum(rmv * rmv, 0)) macostheta = (self.ma*sum(rmv.reshape((3, -1))*fdv[:, newaxis], 0)\ /rm.reshape(-1)).reshape(rm.shape) rm *= 1 / (-macostheta + sqrt(macostheta * macostheta - self.ma * self.ma + 1)) if rm.shape[1] == 1: rm = rm[:, 0] return rm
class Axes(AxesLikeModuleFactory): """ Creates axes for the current (or given) object.""" xlabel = String(None, adapts='axes.x_label', help='the label of the x axis') ylabel = String(None, adapts='axes.y_label', help='the label of the y axis') zlabel = String(None, adapts='axes.z_label', help='the label of the z axis') nb_labels = Range(0, 50, 2, adapts='axes.number_of_labels', desc='The number of labels along each direction') ranges = Trait( None, None, CArray(shape=(6, )), help="""[xmin, xmax, ymin, ymax, zmin, zmax] Ranges of the labels displayed on the axes. Default is the object's extents.""", ) x_axis_visibility = true( adapts='axes.x_axis_visibility', help="Whether or not the x axis is visible (boolean)") y_axis_visibility = true( adapts='axes.y_axis_visibility', help="Whether or not the y axis is visible (boolean)") z_axis_visibility = true( adapts='axes.z_axis_visibility', help="Whether or not the z axis is visible (boolean)") _target = Instance(modules.Axes, ()) def _extent_changed(self): """ Code to modify the extents for """ axes = self._target axes.axes.use_data_bounds = False axes.axes.bounds = self.extent if self.ranges is None: axes.axes.ranges = \ axes.module_manager.source.outputs[0].bounds def _ranges_changed(self): if self.ranges is not None: self._target.axes.ranges = self.ranges self._target.axes.use_ranges = True
class Text3D(ModuleFactory): """ Positions text at a 3D location in the scene. **Function signature**:: text3d(x, y, z, text, ...) x, y, and z are the position of the origin of the text. The text is positioned in 3D, in figure coordinates. """ _target = Instance(modules.Text3D, ()) scale = Either(CFloat(1), CArray(shape=(3, )), help="""The scale of the text, in figure units. Either a float, or 3-tuple of floats.""") orientation = CArray(shape=(3, ), adapts='orientation', desc="""the angles giving the orientation of the text. If the text is oriented to the camera, these angles are referenced to the axis of the camera. If not, these angles are referenced to the z axis.""") orient_to_camera = Bool(True, adapts='orient_to_camera', desc="""if the text is kept oriented to the camera, or is pointing in a specific direction, regardless of the camera position.""") def __init__(self, x, y, z, text, **kwargs): """ Override init as for different positional arguments.""" if not 'scale' in kwargs: kwargs['scale'] = 1 super(Text3D, self).__init__(None, **kwargs) self._target.text = text self._target.position = (x, y, z) def _scale_changed(self): scale = self.scale if isinstance(scale, numbers.Number): scale = scale * np.ones((3, )) self._target.scale = scale
class MapViewer(CharViewer): name = "map" pretty_name = "Map" valid_mouse_modes = [ m.RectangularSelectMode, m.EyedropperMode, m.DrawMode, m.LineMode, m.SquareMode, m.FilledSquareMode ] default_mouse_mode_cls = m.RectangularSelectMode draw_pattern = CArray(dtype=np.uint8, value=(0, )) @property def window_title(self): return "Map: " + self.machine.font_renderer.name + ", " + self.machine.font_mapping.name + ", " + self.machine.color_standard_name ##### Selections def highlight_selected_ranges_in_segment(self, selected_ranges, segment): # This is default implementation which simply highlights everything # between the start/end values of each range. Other selection types # (rectangular selection) will need to be defined in the subclass segment.set_style_ranges_rect(selected_ranges, self.control.items_per_row, selected=True) ##### Clipboard & Copy/Paste @property def clipboard_data_format(self): return "numpy,columns" def get_paste_command(self, serialized_data, *args, **kwargs): print(serialized_data) print((serialized_data.source_data_format_name)) if serialized_data.source_data_format_name == "numpy,columns": cmd_cls = PasteRectCommand cmd_cls = PasteCommand return cmd_cls(self.segment, serialized_data, *args, **kwargs) ##### Drawing pattern def set_draw_pattern(self, value): log.debug("new draw pattern: %s" % str(value)) self.draw_pattern = np.asarray([value], dtype=np.uint8) #### popup menus def common_popup_actions(self): return [ fa.CutAction, fa.CopyAction, fa.PasteAction, None, fa.SelectAllAction, fa.SelectNoneAction, fa.SelectInvertAction ]
class datx_d_file(HasPrivateTraits): """ Helper class for import of `*.datx` data, represents datx data file. """ # File name name = File(filter = ['*.datx'], desc = "name of datx data file") # File object f = Any() # Properties data_offset = Int() channel_count = Int() num_samples_per_block = Int() bytes_per_sample = Int() block_size = Property() # Number of blocks to read in one pull blocks = Int() # The actual block data data = CArray() def _get_block_size( self ): return self.channel_count*self.num_samples_per_block*\ self.bytes_per_sample def get_next_blocks( self ): """ pulls next blocks """ s = self.f.read(self.blocks*self.block_size) ls = len(s) if ls == 0: return -1 bl_no = ls/self.block_size self.data = fromstring(s, dtype = 'Int16').reshape((bl_no, \ self.channel_count, self.num_samples_per_block)).swapaxes(0, \ 1).reshape((self.channel_count, bl_no*self.num_samples_per_block)) def __init__(self, name, blocks = 128): self.name = name self.f = open(self.name, 'rb') s = self.f.read(32) # header s0 = struct.unpack('IIIIIIHHf', s) # Getting information about Properties of data-file # 3 = Offset to data 4 = channel count # 5 = number of samples per block 6 = bytes per sample self.data_offset = s0[3] self.channel_count = s0[4] self.num_samples_per_block = s0[5] self.bytes_per_sample = s0[6] self.blocks = blocks self.f.seek(self.data_offset)
class stick(HasTraits): r_bot = CArray(value=[0, 0, 0], dtype=float) r_top = CArray(value=[0, 0, 0], dtype=float) def __init__(self): self.cylinder = cylinder(radius=0.1) self.sphere = sphere(radius=0.5) self.sphere.color = color.red #@on_trait_change('pos_top,pos_bot') def _r_bot_changed(self, old, new): #print(' r_bot updated') self.cylinder.pos = self.r_bot self.cylinder.axis = self.r_top - self.r_bot #self.pos_axis def _r_top_changed(self, old, new): #print('r_top updated') #print(self.pos_bot + self.pos_axis) self.sphere.pos = self.r_top #self.pos_bot + self.pos_axis # Decorator didn't worked self.cylinder.axis = self.r_top - self.r_bot #self.pos_axis
class AxesLikeModuleFactory(SingletonModuleFactory): """ Base class for axes and outline""" extent = CArray( shape=(6, ), help="""[xmin, xmax, ymin, ymax, zmin, zmax] Default is the object's extents.""", ) def _extent_changed(self): """ There is no universal way of setting extents for decoration objects. This should be implemented in subclasses """ pass # Override the color and opacity handlers: axes and outlines do not # behave like other modules def _color_changed(self): if self.color: try: self._target.property.color = self.color except AttributeError: try: self._target.actor.property.color = self.color except AttributeError: pass def _opacity_changed(self): try: self._target.property.opacity = self.opacity except AttributeError: try: self._target.actor.property.opacity = self.opacity except AttributeError: pass def __init__(self, *args, **kwargs): """ Overide the call method to be able to catch the extents of the object, if any. """ SingletonModuleFactory.__init__(self, *args, **kwargs) if not 'extent' in kwargs: try: # XXX: Do not use tools.set_extent, as it does not work # on axes. self.extent = self._parent.actor.actor.bounds except AttributeError: """ Either this is not a module, or it has no actors"""
class Text3D(Module): # The version of this class. Used for persistence. __version__ = 0 # The Mayavi Actor. actor = Instance(Actor, allow_none=False, record=True) # And the text source vector_text = Instance(tvtk.VectorText, allow_none=False, record=True) # The text to be displayed. text = Str('Text', desc='the text to be displayed', enter_set=True, auto_set=False) # The position of the actor position = CArray(value=(0., 0., 0.), cols=3, desc='the world coordinates of the text', enter_set=True, auto_set=False) # The scale of the actor scale = CArray(value=(1., 1., 1.), cols=3, desc='the scale of the text', enter_set=True, auto_set=False) # The orientation of the actor orientation = CArray(value=(0., 0., 0.), cols=3, desc='the orientation angles of the text', enter_set=True, auto_set=False) # Orient actor to camera orient_to_camera = Bool(True, desc='if the text is kept facing the camera') input_info = PipelineInfo(datasets=['any'], attribute_types=['any'], attributes=['any']) ######################################## # The view of this object. view = View( Group( Item(name='text'), Group(Item(name='position'), show_labels=False, show_border=True, label='Position'), Group(Item(name='scale'), show_labels=False, show_border=True, label='Scale'), Group(Item(name='orient_to_camera'), Item(name='orientation', label='Angles'), show_border=True, label='Orientation'), label='Text', ), Group(Item(name='actor', style='custom', show_label=False), label='Actor'), ) ###################################################################### # `Module` interface ###################################################################### def setup_pipeline(self): """Override this method so that it *creates* the tvtk pipeline. This method is invoked when the object is initialized via `__init__`. Note that at the time this method is called, the tvtk data pipeline will *not* yet be setup. So upstream data will not be available. The idea is that you simply create the basic objects and setup those parts of the pipeline not dependent on upstream sources and filters. You should also set the `actors` attribute up at this point. """ self.vector_text = tvtk.VectorText(text=self.text) self.outputs = [self.vector_text] self.actor = Actor() self._text_changed(self.text) def update_pipeline(self): """Override this method so that it *updates* the tvtk pipeline when data upstream is known to have changed. This method is invoked (automatically) when any of the inputs sends a `pipeline_changed` event. """ self.pipeline_changed = True def has_output_port(self): """ Return True as the text3d has output port. """ return True def get_output_object(self): return self.vector_text.output_port ###################################################################### # Non-public interface ###################################################################### def _text_changed(self, value): vector_text = self.vector_text if vector_text is None: return vector_text.text = str(value) self.render() def _actor_changed(self, old, new): new.scene = self.scene new.inputs = [self] self._change_components(old, new) old_actor = None if old is not None: old_actor = old.actor new.actor = self._get_actor_or_follower(old=old_actor) self.actors = new.actors self.render() def _orient_to_camera_changed(self): self.actor.actor = \ self._get_actor_or_follower(old=self.actor.actor) def _get_actor_or_follower(self, old=None): """ Get a tvtk.Actor or a tvtk.Follower for the actor of the object and wire the callbacks to it. If old is given, it is the old actor, to remove its callbacks. """ if self.orient_to_camera: new = tvtk.Follower() if self.scene is not None: new.camera = self.scene.camera else: new = tvtk.Actor() if old is not None: self.sync_trait('position', old, 'position', remove=True) self.sync_trait('scale', old, 'scale', remove=True) self.sync_trait('orientation', old, 'orientation', remove=True) self.sync_trait('position', new, 'position') self.sync_trait('scale', new, 'scale') self.sync_trait('orientation', new, 'orientation') return new def _scene_changed(self, old, new): super(Text3D, self)._scene_changed(old, new) if new is not None and self.orient_to_camera: self.actor.actor.camera = new.camera
class SlotJet(FlowField): """ Provides an analytical approximation of the flow field of a slot jet, see :ref:`Albertson et al., 1950<Albertson1950>`. """ #: Exit velocity at jet origin, i.e. the nozzle. Defaults to 0. v0 = Float(0.0, desc="exit velocity") #: Location of a point at the slot center line, #: defaults to the co-ordinate origin. origin = CArray(dtype=float64, shape=(3, ), value=array((0., 0., 0.)), desc="center of nozzle") #: Unit flow direction of the slot jet, defaults to (1,0,0). flow = CArray(dtype=float64, shape=(3, ), value=array((1., 0., 0.)), desc="flow direction") #: Unit vector parallel to slot center plane, defaults to (0,1,0) plane = CArray(dtype=float64, shape=(3, ), value=array((0., 1., 0.)), desc="slot center line direction") #: Width of the slot, defaults to 0.2 . B = Float(0.2, desc="nozzle diameter") # internal identifier digest = Property(depends_on=['v0', 'origin', 'flow', 'plane', 'B'], ) traits_view = View([[ 'v0{Exit velocity}', 'origin{Jet origin}', 'flow', 'plane', 'B{Slot width}' ], '|[Slot jet]']) @cached_property def _get_digest(self): return digest(self) def v(self, xx): """ Provides the flow field as a function of the location. This is implemented here only for the component in the direction of :attr:`flow`; entrainment components are set to zero. Parameters ---------- xx : array of floats of shape (3, ) Location in the fluid for which to provide the data. Returns ------- tuple with two elements The first element in the tuple is the velocity vector and the second is the Jacobian of the velocity vector field, both at the given location. """ # TODO: better to make sure that self.flow and self.plane are indeed unit vectors before # normalize flow = self.flow / norm(self.flow) plane = self.plane / norm(self.plane) # additional axes of global co-ordinate system yy = -cross(flow, plane) zz = cross(flow, yy) # distance from slot exit plane xx1 = xx - self.origin # local co-ordinate system x = dot(flow, xx1) y = dot(yy, xx1) x1 = 0.109 * x h1 = abs(y) + sqrt(pi) * 0.5 * x1 - 0.5 * self.B if h1 < 0.0: # core jet Ux = self.v0 Udx = 0 Udy = 0 else: # shear layer Ux = self.v0 * exp(-h1 * h1 / (2 * x1 * x1)) Udx = (h1 * h1 / (x * x1 * x1) - sqrt(pi) * 0.5 * h1 / (x * x1)) * Ux Udy = -sign(y) * h1 * Ux / (x1 * x1) # Jacobi matrix dU = array(((Udx, 0, 0), (Udy, 0, 0), (0, 0, 0))).T # rotation matrix R = array((flow, yy, zz)).T return dot(R, array((Ux, 0, 0))), dot(dot(R, dU), R.T)
# Standard library imports from math import radians, sqrt # Major library imports from numpy import (array, argsort, concatenate, cos, diff, dot, empty, isfinite, nonzero, pi, searchsorted, seterr, sin, int8) # Enthought library imports from traits.api import CArray, Enum, Trait delta = {'ascending': 1, 'descending': -1, 'flat': 0} # Dimensions # A single array of numbers. NumericalSequenceTrait = Trait(None, None, CArray(value=empty(0))) # A sequence of pairs of numbers, i.e., an Nx2 array. PointTrait = Trait(None, None, CArray(value=empty(0))) # An NxM array of numbers. ImageTrait = Trait(None, None, CArray(value=empty(0))) # An 3D array of numbers of shape (Nx, Ny, Nz) CubeTrait = Trait(None, None, CArray(value=empty(0))) # This enumeration lists the fundamental mathematical coordinate types that # Chaco supports. DimensionTrait = Enum("scalar", "point", "image", "cube") # Linear sort order.
class BeamformerTimeSqTraj( BeamformerTimeSq ): """ Provides a time domain beamformer with time-dependent power signal output and possible autopower removal for a grid moving along a trajectory. """ #: :class:`~acoular.trajectory.Trajectory` or derived object. #: Start time is assumed to be the same as for the samples. trajectory = Trait(Trajectory, desc="trajectory of the grid center") #: Reference vector, perpendicular to the y-axis of moving grid. rvec = CArray( dtype=float, shape=(3, ), value=array((0, 0, 0)), desc="reference vector") # internal identifier digest = Property( depends_on = ['_steer_obj.digest', 'source.digest', 'r_diag', 'weights', \ 'rvec', 'trajectory.digest', '__class__'], ) traits_view = View( [ [Item('steer{}', style='custom')], [Item('source{}', style='custom'), '-<>'], [Item('trajectory{}', style='custom')], [Item('r_diag', label='diagonal removed')], [Item('weights{}', style='simple')], '|' ], title='Beamformer options', buttons = OKCancelButtons ) @cached_property def _get_digest( self ): return digest(self) def result( self, num=2048 ): """ Python generator that yields the *squared* beamformer output block-wise. Optional removal of autocorrelation. The "moving" grid can be translated and optionally rotated. Parameters ---------- num : integer, defaults to 2048 This parameter defines the size of the blocks to be yielded (i.e. the number of samples per block). Returns ------- Samples in blocks of shape \ (num, :attr:`~BeamformerTime.numchannels`). :attr:`~BeamformerTime.numchannels` is usually very \ large (number of grid points). The last block may be shorter than num. \ The output starts for signals that were emitted from the grid at `t=0`. """ if self.weights_: w = self.weights_(self)[newaxis] else: w = 1.0 c = self.env.c/self.source.sample_freq # temp array for the grid co-ordinates gpos = self.grid.pos() # max delay span = sum of # max diagonal lengths of circumscribing cuboids for grid and micarray dmax = sqrt(((gpos.max(1)-gpos.min(1))**2).sum()) dmax += sqrt(((self.steer.mics.mpos.max(1)-self.steer.mics.mpos.min(1))**2).sum()) dmax = int(dmax/c)+1 # max index span zi = empty((dmax+num, self.source.numchannels), \ dtype=float) #working copy of data o = empty((num, self.grid.size), dtype=float) # output array temp = empty((self.grid.size, self.source.numchannels), dtype=float) d_index2 = arange(self.steer.mics.num_mics, dtype=int) # second index (static) offset = dmax+num # start offset for working array ooffset = 0 # offset for output array # generators for trajectory, starting at time zero start_t = 0.0 g = self.trajectory.traj( start_t, delta_t=1/self.source.sample_freq) g1 = self.trajectory.traj( start_t, delta_t=1/self.source.sample_freq, der=1) rflag = (self.rvec == 0).all() #flag translation vs. rotation data = self.source.result(num) flag = True while flag: # yield output array if full if ooffset == num: yield o ooffset = 0 if rflag: # grid is only translated, not rotated tpos = gpos + array(next(g))[:, newaxis] else: # grid is both translated and rotated loc = array(next(g)) #translation dx = array(next(g1)) #direction vector (new x-axis) dy = cross(self.rvec, dx) # new y-axis dz = cross(dx, dy) # new z-axis RM = array((dx, dy, dz)).T # rotation matrix RM /= sqrt((RM*RM).sum(0)) # column normalized tpos = dot(RM, gpos)+loc[:, newaxis] # rotation+translation rm = self.steer.env._r( tpos, self.steer.mics.mpos) r0 = self.steer.env._r( tpos) delays = rm/c d_index = array(delays, dtype=int) # integer index d_interp1 = delays % 1 # 1st coeff for lin interpolation d_interp2 = 1-d_interp1 # 2nd coeff for lin interpolation amp = (w/(rm*rm)).sum(1) * r0 amp = 1.0/(amp[:, newaxis]*rm) # multiplication factor # now, we have to make sure that the needed data is available while offset+d_index.max()+2>dmax+num: # copy remaining samples in front of next block zi[0:dmax] = zi[-dmax:] # the offset is adjusted by one block length offset -= num # test if data generator is exhausted try: # get next data block = next(data) except StopIteration: flag = False break # samples in the block, equals to num except for the last block ns = block.shape[0] zi[dmax:dmax+ns] = block * w# copy data to working array else: # the next line needs to be implemented faster # it eats half of the time temp[:, :] = (zi[offset+d_index, d_index2]*d_interp1 \ + zi[offset+d_index+1, d_index2]*d_interp2)*amp if self.r_diag: # simple sum and remove autopower o[ooffset] = clip(temp.sum(-1)**2 - \ (temp**2).sum(-1), 1e-100, 1e+100) else: # simple sum o[ooffset] = temp.sum(-1)**2 offset += 1 ooffset += 1 # remaining data chunk yield o[:ooffset]
class MicGeom(HasPrivateTraits): """ Provides the geometric arrangement of microphones in the array. The geometric arrangement of microphones is read in from an xml-source with element tag names `pos` and attributes Name, `x`, `y` and `z`. Can also be used with programmatically generated arrangements. """ #: Name of the .xml-file from wich to read the data. from_file = File(filter=['*.xml'], desc="name of the xml file to import") #: Basename of the .xml-file, without the extension; is set automatically / readonly. basename = Property(depends_on='from_file', desc="basename of xml file") #: List that gives the indices of channels that should not be considered. #: Defaults to a blank list. invalid_channels = ListInt(desc="list of invalid channels") #: Number of microphones in the array; readonly. num_mics = Property(depends_on=[ 'mpos', ], desc="number of microphones in the geometry") #: Center of the array (arithmetic mean of all used array positions); readonly. center = Property(depends_on=[ 'mpos', ], desc="array center") #: Positions as (3, :attr:`num_mics`) array of floats, may include also invalid #: microphones (if any). Set either automatically on change of the #: :attr:`from_file` argument or explicitely by assigning an array of floats. mpos_tot = CArray(dtype=float, desc="x, y, z position of all microphones") #: Positions as (3, :attr:`num_mics`) array of floats, without invalid #: microphones; readonly. mpos = Property(depends_on=['mpos_tot', 'invalid_channels'], desc="x, y, z position of microphones") # internal identifier digest = Property(depends_on=[ 'mpos', ]) @cached_property def _get_digest(self): return digest(self) @cached_property def _get_basename(self): return path.splitext(path.basename(self.from_file))[0] @cached_property def _get_mpos(self): if len(self.invalid_channels) == 0: return self.mpos_tot allr = [ i for i in range(self.mpos_tot.shape[-1]) if i not in self.invalid_channels ] return self.mpos_tot[:, array(allr)] @cached_property def _get_num_mics(self): return self.mpos.shape[-1] @cached_property def _get_center(self): if self.mpos.any(): center = average(self.mpos, axis=1) # set very small values to zero center[abs(center) < 1e-16] = 0. return center @on_trait_change('basename') def import_mpos(self): """ Import the microphone positions from .xml file. Called when :attr:`basename` changes. """ if not path.isfile(self.from_file): # no file there self.mpos_tot = array([], 'd') self.num_mics = 0 return import xml.dom.minidom doc = xml.dom.minidom.parse(self.from_file) names = [] xyz = [] for el in doc.getElementsByTagName('pos'): names.append(el.getAttribute('Name')) xyz.append(list(map(lambda a: float(el.getAttribute(a)), 'xyz'))) self.mpos_tot = array(xyz, 'd').swapaxes(0, 1)
class LineSource(PointSource): """ Class to define a fixed Line source with an arbitrary signal. This can be used in simulations. The output is being generated via the :meth:`result` generator. """ #: Vector to define the orientation of the line source direction = Tuple((0.0, 0.0, 1.0), desc="Line orientation ") #: Vector to define the length of the line source in m length = Float(1, desc="length of the line source") #: number of monopol sources in the line source num_sources = Int(1) #: source strength for every monopole source_strength = CArray(desc="coefficients of the source strength") #:coherence coherence = Trait('coherent', 'incoherent', desc="coherence mode") # internal identifier digest = Property( depends_on = ['mics.digest', 'signal.digest', 'loc', \ 'env.digest', 'start_t', 'start', 'up', 'direction',\ 'source_strength','coherence','__class__'], ) @cached_property def _get_digest(self): return digest(self) def result(self, num=128): """ Python generator that yields the output at microphones block-wise. Parameters ---------- num : integer, defaults to 128 This parameter defines the size of the blocks to be yielded (i.e. the number of samples per block) . Returns ------- Samples in blocks of shape (num, numchannels). The last block may be shorter than num. """ #If signal samples are needed for te < t_start, then samples are taken #from the end of the calculated signal. mpos = self.mics.mpos # direction vector from tuple direc = array(self.direction, dtype=float) # normed direction vector direc_n = direc / norm(direc) c = self.env.c # distance between monopoles in the line dist = self.length / self.num_sources #blocwise output out = zeros((num, self.numchannels)) # distance from line start position to microphones loc = array(self.loc, dtype=float).reshape((3, 1)) # distances from monopoles in the line to microphones rms = empty((self.numchannels, self.num_sources)) inds = empty((self.numchannels, self.num_sources)) signals = empty((self.num_sources, len(self.signal.usignal(self.up)))) #for every source - distances for s in range(self.num_sources): rms[:, s] = self.env._r((loc.T + direc_n * dist * s).T, mpos) inds[:, s] = (-rms[:, s] / c - self.start_t + self.start) * self.sample_freq #new seed for every source if self.coherence == 'incoherent': self.signal.seed = s + abs(int(hash(self.digest) // 10e12)) self.signal.rms = self.signal.rms * self.source_strength[s] signals[s] = self.signal.usignal(self.up) i = 0 n = self.numsamples while n: n -= 1 try: for s in range(self.num_sources): # sum sources out[i] += (signals[ s, array(0.5 + inds[:, s].T * self.up, dtype=int64)] / rms[:, s]) inds += 1. i += 1 if i == num: yield out out = zeros((num, self.numchannels)) i = 0 except IndexError: break yield out[:i]
class BeamformerTimeTraj(BeamformerTime): """ Provides a basic time domain beamformer with time signal output for a grid moving along a trajectory. """ #: :class:`~acoular.trajectory.Trajectory` or derived object. #: Start time is assumed to be the same as for the samples. trajectory = Trait(Trajectory, desc="trajectory of the grid center") #: Reference vector, perpendicular to the y-axis of moving grid. rvec = CArray(dtype=float, shape=(3, ), value=array((0, 0, 0)), desc="reference vector") #: Considering of convective amplification in beamforming formula. conv_amp = Bool( False, desc="determines if convective amplification of source is considered") # internal identifier digest = Property( depends_on = ['_steer_obj.digest', 'source.digest', 'weights', \ 'rvec','conv_amp','trajectory.digest', '__class__'], ) @cached_property def _get_digest(self): return digest(self) def get_moving_gpos(self): """ Python generator that yields the moving grid coordinates samplewise """ start_t = 0.0 gpos = self.grid.pos() trajg = self.trajectory.traj(start_t, delta_t=1 / self.source.sample_freq) trajg1 = self.trajectory.traj(start_t, delta_t=1 / self.source.sample_freq, der=1) rflag = (self.rvec == 0).all() #flag translation vs. rotation if rflag: for g in trajg: # grid is only translated, not rotated tpos = gpos + array(g)[:, newaxis] yield tpos else: for (g, g1) in zip(trajg, trajg1): # grid is both translated and rotated loc = array(g) #translation array([0., 0.4, 1.]) dx = array(g1) #direction vector (new x-axis) dy = cross(self.rvec, dx) # new y-axis dz = cross(dx, dy) # new z-axis RM = array((dx, dy, dz)).T # rotation matrix RM /= sqrt((RM * RM).sum(0)) # column normalized tpos = dot(RM, gpos) + loc[:, newaxis] # rotation+translation # print(loc[:]) yield tpos def get_macostheta(self, g1, tpos, rm): vvec = array(g1) # velocity vector ma = norm(vvec) / self.steer.env.c # machnumber fdv = (vvec / sqrt( (vvec * vvec).sum()))[:, newaxis] # unit vecor velocity mpos = self.steer.mics.mpos[:, newaxis, :] rmv = tpos[:, :, newaxis] - mpos return (ma*sum(rmv.reshape((3, -1))*fdv, 0)\ /rm.reshape(-1)).reshape(rm.shape) def result(self, num=2048): """ Python generator that yields the beamformer output block-wise. Optional removal of autocorrelation. The "moving" grid can be translated and optionally rotated. Parameters ---------- num : integer, defaults to 2048 This parameter defines the size of the blocks to be yielded (i.e. the number of samples per block). Returns ------- Samples in blocks of shape \ (num, :attr:`~BeamformerTime.numchannels`). :attr:`~BeamformerTime.numchannels` is usually very \ large (number of grid points). The last block may be shorter than num. \ The output starts for signals that were emitted from the grid at `t=0`. """ if self.weights_: w = self.weights_(self)[newaxis] else: w = 1.0 c = self.steer.env.c / self.source.sample_freq # temp array for the grid co-ordinates gpos = self.grid.pos() # max delay span = sum of # max diagonal lengths of circumscribing cuboids for grid and micarray dmax = sqrt(((gpos.max(1) - gpos.min(1))**2).sum()) dmax += sqrt(((self.steer.mics.mpos.max(1) - self.steer.mics.mpos.min(1))**2).sum()) dmax = int(dmax / c) + 1 # max index span zi = empty((dmax+num, self.source.numchannels), \ dtype=float) #working copy of data o = empty((num, self.grid.size), dtype=float) # output array temp = empty((self.grid.size, self.source.numchannels), dtype=float) d_index2 = arange(self.steer.mics.num_mics, dtype=int) # second index (static) offset = dmax + num # start offset for working array ooffset = 0 # offset for output array movgpos = self.get_moving_gpos() # create moving grid pos generator movgspeed = self.trajectory.traj(0.0, delta_t=1 / self.source.sample_freq, der=1) data = self.source.result(num) flag = True while flag: # yield output array if full if ooffset == num: yield o ooffset = 0 tpos = next(movgpos) rm = self.steer.env._r(tpos, self.steer.mics.mpos) if isscalar(self.steer.ref) and self.steer.ref > 0: r0 = full((self.steer.grid.size, ), self.steer.ref) else: r0 = self.env._r(tpos) delays = rm / c d_index = array(delays, dtype=int) # integer index d_interp2 = delays % 1 # 2nd coeff for lin interpolation between samples d_interp1 = 1 - d_interp2 # 1st coeff for lin interpolation # now, we have to make sure that the needed data is available while offset + d_index.max() + 2 > dmax + num: # copy remaining samples in front of next block zi[0:dmax] = zi[-dmax:] # the offset is adjusted by one block length offset -= num # test if data generator is exhausted try: block = next(data) # get next data except StopIteration: flag = False break # samples in the block, equals to num except for the last block ns = block.shape[0] zi[dmax:dmax + ns] = block * w # copy data to working array else: if self.conv_amp: macostheta = self.get_macostheta(next(movgspeed), tpos, rm) conv_amp = (1 - macostheta)**2 amp = (w / (rm * conv_amp)**2).sum(1) * r0 amp = 1.0 / (amp[:, newaxis] * rm * conv_amp ) # multiplication factor else: amp = (w / (rm * rm)).sum(1) * r0 amp = 1.0 / (amp[:, newaxis] * rm) # multiplication factor # the next line needs to be implemented faster # it eats half of the time temp[:, :] = (zi[offset+d_index, d_index2]*d_interp1 \ + zi[offset+d_index+1, d_index2]*d_interp2)*amp o[ooffset] = temp.sum(-1) offset += 1 ooffset += 1 # remaining data chunk yield o[:ooffset]
class GaussianBeam(HasTraits): ''' This is a class to represent a Gaussian beam. A GaussianBeam object has its origin (pos) and a propagation direction (dirVect or dirAngle). A GaussianBeam is characterized by q-parameter(s) at its origin. The beam can be either circular or elliptic. In order to deal with elliptic beams, some parameters are stored in pairs like (q0x, q0y). x and y denote the axes of the cross section of the beam. x-axis is parallel to the paper and the y-axis is perpendicular to the paper. A beam object can be propagated through a free space or made to interact with an optics. As a beam propagate through optical system, optical distance and Gouy phase are accumerated. Attributes ---------- q : complex q-parameter of the beam. If the beam is eliptic, q is the q-parameter of the best matching circular mode. qx : complex q-parameter of the beam in the x-direction. qy : complex q-parameter of the beam in the y-direction. pos : array Position of the beam origin (x, y). dirVect : array Propagation direction vector. dirAngle : float Propagation direction angle measured from the positive x-axis. length : float Length of the beam (used for DXF export) layer : str Layer name of the beam when exported to a DXF file. name : str Name of the beam wl : float Wavelength in vacuum. Not the wavelength in the medium. n : float Index of refraction of the medium the beam is passing through. P : float Power. wx : float Beamwidth in x-direction. wy : float Beamwidth in y-direction. optDist : float Accumulated optical distance. Gouyx : float Accumulated Gouy phase in x-direction. Gouyy : float Accumulated Gouy phase in y-direction. Mx : array ABCD matrix in x-direction. This is a 2x2 matrix representing the product of ABCD transformations applied to this beam. It defaults to an identity matrix. Whenever a beam experience an ABCD matrix transformation, such as propagation in the space or reflection by a curved mirror, the applied ABCD matrix is multiplied to this matrix, so that we can keep track of what kind of transformations were made during beam propagation. My : array ABCD matrix in y-direction. The meaning is the same as Mx. departSurfAngle : None The angle formed by x-axis and the normal vector of the surface from which the beam is departing. Default is None. Used by the drawing routine. departSurfInvROC : None Inverse of the ROC of the surface from which the beam is departing. The ROC is positive for a concave surface seen from the beam side. Default is None. Used by the drawing routine. incSurfAngle : None The angle formed by the x-arm and the normal vector of the surface to which the beam is incident. Default is None. Used by the drawing routine. incSurfInvROC : None Inverse of the ROC of the surface to which the beam is incident. The ROC is positive for a concave surface seen from the beam side. Default is None. Used by the drawing routine. stray_order : int An integer indicating if this beam is a stray light or not. The default value is 0. Every time a beam is reflected by an AR surface or transmits an HR surface, this couter is increased by 1. ''' #{{{ Traits Definitions name = Str() wl = CFloat(1064.0 * nm) #Wavelength P = CFloat(1 * W) #Power q = CComplex() #q-parameter at the origin (best matching circular mode) qx = CComplex(1j) #q-parameter at the origin (x-direction) qy = CComplex(1j) #q-parameter at the origin (y-direction) qrx = CComplex() #Reduced q-parameter at the origin (x-direction) # qrx = qx/n qry = CComplex() #Reduced q-parameter at the origin (x-direction) # qry = qy/n Gouyx = CFloat(0.0) #Accumurated Gouy phase Gouyy = CFloat(0.0) #Accumurated Gouy phase wx = CFloat() wy = CFloat() n = CFloat(1.0) pos = CArray(dtype=np.float64, shape=(2, )) length = CFloat(1.0) layer = Str() dirVect = CArray(dtype=np.float64, shape=(2, )) dirAngle = CFloat() optDist = CFloat(0.0) Mx = CArray(value=[[1, 0], [0, 1]], dtype=np.float64, shape=(2, 2)) My = CArray(value=[[1, 0], [0, 1]], dtype=np.float64, shape=(2, 2)) #}}} #{{{ __init__ def __init__(self, q0=1j * 2 * pi / (1064 * nm) * 1e-6 / 2, q0x=False, q0y=False, pos=[0.0, 0.0], length=1.0, dirAngle=0.0, dirVect=None, wl=1064 * nm, P=1 * W, n=1.0, name="Beam", layer='main_beam'): '''Constructor Parameters ---------- q0 : complex, optional q-parameter. If q0x or q0y is not given, this parameter is used as both qx and qy. Defaults 1j*2*pi/(1064*nm)*1e-6/2. q0x : complex, optional q-parameter in x-direction. Defaults False. If False, set to q0. q0y : complex, optional q-parameter in y-direction. Defaults False. If False, set to q0. pos : array, optional Position of the origin of the beam (x, y). Defaults [0.0, 0.0]. length : float, optional Length of the beam (used for DXF export). Defaults 1.0. dirAngle : float, optional Propagation direction angle measured from the positive x-axis. Defaults 0.0. dirVect : array, optional Propagation direction vector. Defaults None. wl : float, optional. Wavelength. Defaults 1064nm. P : float, optional. Power. Defaults 1W. n : float, optional Index of refraction of the medium the beam is passing through. Defaults 1.0. name : str, optional Name of the beam. Defaults "Beam". layer : str, optional Layer name of the beam when exported to a DXF file. Defaults "main_beam". ''' self.wl = wl self.P = P self.pos = pos self.length = length self.name = name self.layer = layer self.n = n if q0x: ## Possible bug. If q0x is True then self.qx becomes True. self.qx = q0x else: self.qx = q0 if q0y: self.qy = q0y else: self.qy = q0 if dirVect != None: self.dirVect = dirVect else: self.dirAngle = dirAngle self._dirAngle_changed(0, 0) self.optDist = 0.0 self.departSurfAngle = None self.departSurfInvROC = None self.incSurfAngle = None self.incSurfInvROC = None self.stray_order = 0 #}}} #{{{ copy def copy(self): ''' Make a deep copy. ''' b = copy.deepcopy(self) b.qrx = self.qrx b.qry = self.qry return b #}}} #{{{ propagate def propagate(self, d): ''' Propagate the beam by a distance d from the current position. self.n is used as the index of refraction. During this process, the optical distance traveled is added to self.optDist. self.Goux and self.Gouyy are also updated to record the Gouy phase change. Parameters ---------- d: float Distance. ''' qx0 = self.qx qy0 = self.qy ABCD = np.array([[1.0, d / self.n], [0.0, 1.0]]) #ABCD = np.array([[1.0, d/self.n**2],[0.0, 1.0]]) self.ABCDTrans(ABCD) self.pos = self.pos + self.dirVect * d #Increase the optical distance self.optDist = self.optDist + self.n * d #Increase the Gouy phase self.Gouyx = self.Gouyx + np.arctan(np.real(self.qx)/np.imag(self.qx))\ - np.arctan(np.real(qx0)/np.imag(qx0)) self.Gouyy = self.Gouyy + np.arctan(np.real(self.qy)/np.imag(self.qy))\ - np.arctan(np.real(qy0)/np.imag(qy0)) #}}} #{{{ ABCD Trans def ABCDTrans(self, ABCDx, ABCDy=None): ''' Apply ABCD transformation to the beam. Parameters ---------- ABCDx : array ABCD matrix for x-direction. ABCDy : array or None, optional. ABCD matrix for y-direction. Defaults None. If None, set to ABCDx. ''' if ABCDy is None: ABCDy = ABCDx #Update q-parameters self.qrx = (ABCDx[0, 0] * self.qrx + ABCDx[0, 1]) / (ABCDx[1, 0] * self.qrx + ABCDx[1, 1]) self.qry = (ABCDy[0, 0] * self.qry + ABCDy[0, 1]) / (ABCDy[1, 0] * self.qry + ABCDy[1, 1]) #Update Mx and My self.Mx = np.dot(ABCDx, self.Mx) self.My = np.dot(ABCDy, self.My) #}}} #{{{ rotate def rotate(self, angle, center=False): ''' Rotate the beam around 'center'. If center is not given, the beam is rotated around self.pos. Parameters ---------- angle : float Rotation angle in radians. center : array or boolean. Center of rotation. Should be an array of shape(2,). Defaults False. ''' if center: center = np.array(center) pointer = self.pos - center pointer = optics.geometric.vector_rotation_2D(pointer, angle) self.pos = center + pointer self.dirAngle = self.dirAngle + angle #}}} #{{{ Translate def translate(self, trVect): ''' Translate the beam by the direction and the distance specified by a vector. Parameters ---------- trVect : array A vector to specify the translation direction and distance. Should be an array of shape(2,) ''' trVect = np.array(trVect) self.pos = self.pos + trVect #}}} #{{{ Flip def flip(self, flipDirVect=True): ''' Change the propagation direction of the beam by 180 degrees. This is equivalent to the reflection of the beam by a spherical mirror with the same ROC as the beam. If optional argument flipDirVect is set to False, the propagation direction of the beam is not changed. Parameters ---------- flipDirVect : boolean, optional Flip propagation direction. Defaults True. ''' self.qx = -np.real(self.qx) + 1j * np.imag(self.qx) self.qy = -np.real(self.qy) + 1j * np.imag(self.qy) if flipDirVect: self.dirVect = -self.dirVect #}}} #{{{ width def width(self, dist): ''' Returns the beam width at a distance dist from the origin of the beam. The width is the radius where the light power becomes 1/e^2. Parameters ---------- dist : float Distance. Returns ------- (float, float) The width of the beam in x and y direction. ''' dist = np.array(dist) k = 2 * pi / (self.wl / self.n) qx = self.qx + dist qy = self.qy + dist return (np.sqrt(-2.0 / (k * np.imag(1.0 / qx))), np.sqrt(-2.0 / (k * np.imag(1.0 / qy)))) #}}} #{{{ R def R(self, dist=0.0): ''' Returns the beam ROC at a distance dist from the origin of the beam. Parameters ---------- dist : float, optional Distance. Returns ------- (float, float) Beam ROC. ''' dist = np.array(dist) k = 2 * pi / self.wl qx = self.qx + dist / self.n qy = self.qy + dist / self.n return (q2R(qx), q2R(qy)) #}}} #{{{ Waist def waist(self): ''' Return the tuples of waist size and distance Returns ------- dict {"Waist Size": (float, float), "Waist Position": (float, float)} ''' #Waist positions dx = -np.real(self.qx) dy = -np.real(self.qy) #Waist sizes wx = self.width(dx)[0] wy = self.width(dy)[1] return {"Waist Size": (wx, wy), "Waist Position": (dx, dy)} #}}} #{{{ draw #{{{ Main Function def draw(self, cv, sigma=3., mode='x', drawWidth=True, fontSize=False, drawPower=False, drawROC=False, drawGouy=False, drawOptDist=False, drawName=False, debug=False): ''' Draw the beam into a DXF object. Parameters ---------- cv : gtrace.draw.draw.Canvas gtrace canvas. sigma : float, optional The width of the beam drawn is sigma * (1/e^2 radius of the beam). The default is sigma = 3. sigma = 2.7 gives 1ppm diffraction loss. Defaults 3. mode : str, optional 'avg', 'x', or 'y'. A beam can have different widths for x- and y- directions. If 'avg' is specified, the average of them are drawn. 'x' and 'y' specifies to show the width of the respective directions. Defaults 'x'. fontSize : float, optional Size of the font used to show supplemental informations. Defaults False. drawWidth : boolean, optional Whether to draw width or not. Defaults True. drawPower : boolean, optional Whether to show the beam power. Defaults False. drawROC : boolean, optional Whether to show the ROC or not. Defaults False. drawGouy : boolean, optional Whether to show the Gouy phase or not. Defaults False. drawOptDist : boolean, optional Whether to show the accumulated optical distance or not. Defaults False. drawName : boolean, optional Whether draw the name of the beam or not. Defaults False. debug : boolean, optional Debug. ''' if not fontSize: fontSize = self.width(self.length / 2)[0] * sigma / 5 start = tuple(self.pos) stop = tuple(self.pos + self.dirVect * self.length) #Location to put texts #mid = self.pos + self.dirVect * self.length/2 #side = mid+fontSize*1.2*k k = np.array((-self.dirVect[1], self.dirVect[0])) text_location = tuple(self.pos + (self.length - 10 * fontSize) * self.dirVect + k * fontSize * 1) #Draw the center line cv.add_shape(draw.Line(start, stop), self.layer) if drawWidth: self.drawWidth(cv, sigma, mode) annotation = '' if drawName: annotation = annotation + '%s ' % self.name if drawPower: annotation = annotation + 'P=%.2E ' % self.P if drawROC: annotation = annotation + 'ROCx=%.2E ' % q2R(self.qx) annotation = annotation + 'ROCy=%.2E ' % q2R(self.qy) if drawGouy: annotation = annotation + 'Gouyx=%.2E ' % self.Gouyx annotation = annotation + 'Gouyy=%.2E ' % self.Gouyy if drawOptDist: annotation = annotation + 'Optical distance=%.2E ' % self.optDist cv.add_shape(draw.Text(text=annotation, point=text_location, height=fontSize), layername='text') #Indicate the beam direction # text_location = tuple(self.pos + 10*fontSize*self.dirVect +k*fontSize*1) # dxf.append(sdxf.Text(text=self.name+':origin P=%.2E'%self.P, point=text_location, # height=fontSize, layer='text')) # text_location = tuple(self.pos + (self.length - 10*fontSize)*self.dirVect + k*fontSize*1) # dxf.append(sdxf.Text(text=self.name+':end P=%.2E'%self.P, point=text_location, # height=fontSize, layer='text')) #}}} #{{{ drawWidth def drawWidth(self, cv, sigma, mode): """Draw width on canvas. Parameters ---------- cv : gtrace.draw.draw.Canvas The canvas. sigma : float The width of the beam drawn is sigma * (1/e^2 radius of the beam). The default is sigma = 3. sigma = 2.7 gives 1ppm diffraction loss. mode : str 'avg', 'x', or 'y'. A beam can have different widths for x- and y- directions. If 'avg' is specified, the average of them are drawn. 'x' and 'y' specifies to show the width of the respective directions. """ #Draw the width #Determine the number of line segments to draw zr = q2zr(self.qx) resolution = zr / 10.0 if resolution > self.length / 10.0: resolution = self.length / 10.0 numSegments = int(self.length / resolution) if numSegments > 100: numSegments = 100 #q0 to be used if mode == 'x': q0 = self.qx elif mode == 'y': q0 = self.qy else: q0 = (self.qx + self.qy) / 2.0 #Determine the start z coordinates if self.departSurfAngle is not None: theta = self.departSurfAngle - self.dirAngle k = 2 * pi / (self.wl / self.n) if np.abs(self.departSurfInvROC) > 1.0 / 100: R = 1.0 / self.departSurfInvROC (z1u, z1d) = optimStartPointR(theta, R, q0, k, sigma) else: (z1u, z1d) = optimCrossPointFlat(theta, q0, k, sigma) else: z1u = 0.0 z1d = 0.0 #Determine the end z coordinates if self.incSurfAngle is not None: theta = np.mod(self.incSurfAngle + pi, 2 * pi) - self.dirAngle k = 2 * pi / (self.wl / self.n) if np.abs(self.incSurfInvROC) > 1.0 / 100: R = 1.0 / self.incSurfInvROC (z2u, z2d) = optimEndPointR(theta, R, q0 + self.length, k, sigma) else: (z2u, z2d) = optimCrossPointFlat(theta, q0 + self.length, k, sigma) else: z2u = 0.0 z2d = 0.0 du = np.linspace(z1u, self.length + z2u, numSegments) dd = np.linspace(z1d, self.length + z2d, numSegments) au = self.width(du) ad = self.width(dd) if mode == 'x': wu = au[0] * sigma wd = ad[0] * sigma elif mode == 'y': wu = au[1] * sigma wd = ad[1] * sigma else: wu = sigma * (au[0] + au[1]) / 2 wd = sigma * (ad[0] + ad[1]) / 2 v = np.vstack((du, wu)) v = optics.geometric.vector_rotation_2D(v, self.dirAngle) v = v + np.array([self.pos]).T cv.add_shape(draw.PolyLine(x=v[0, :], y=v[1, :]), layername=self.layer + "_width") v = np.vstack((dd, -wd)) v = optics.geometric.vector_rotation_2D(v, self.dirAngle) v = v + np.array([self.pos]).T cv.add_shape(draw.PolyLine(x=v[0, :], y=v[1, :]), layername=self.layer + "_width") #}}} #{{{ drawWidthOld def drawWidthOld(self, dxf, sigma, mode): '''Deprecated? ''' #Draw the width #Determine the number of line segments to draw zr = q2zr(self.qx) resolution = zr / 10.0 if resolution > self.length / 10.0: resolution = self.length / 10.0 numSegments = int(self.length / resolution) if numSegments > 100: numSegments = 100 d = np.linspace(0, self.length, numSegments) a = self.width(d) if mode == 'x': w = a[0] * sigma elif mode == 'y': w = a[1] * sigma else: w = sigma * (a[0] + a[1]) / 2 v = np.vstack((d, w)) v = optics.geometric.vector_rotation_2D(v, self.dirAngle) v = v + np.array([self.pos]).T dxf.append( sdxf.LwPolyLine(points=list(v.T), layer=self.layer + "_width")) v = np.vstack((d, -w)) v = optics.geometric.vector_rotation_2D(v, self.dirAngle) v = v + np.array([self.pos]).T dxf.append( sdxf.LwPolyLine(points=list(v.T), layer=self.layer + "_width")) #}}} #}}} #{{{ Notification Handlers def _dirAngle_changed(self, old, new): self.set(trait_change_notify=False, dirVect=array([np.cos(self.dirAngle), np.sin(self.dirAngle)])) self.set(trait_change_notify=False, dirAngle=np.mod(self.dirAngle, 2 * pi)) # self.dirVect = array([np.cos(self.dirAngle), np.sin(self.dirAngle)]) # self.dirAngle = np.mod(self.dirAngle, 2*pi) def _dirVect_changed(self, old, new): #Normalize self.set(trait_change_notify=False, dirVect=self.dirVect / np.linalg.norm(array(self.dirVect))) #Update dirAngle accordingly self.set(trait_change_notify=False, dirAngle=np.mod(np.arctan2(self.dirVect[1], self.dirVect[0]), 2 * pi)) def _qx_changed(self, old, new): self.wx = q2w(self.qx, wl=self.wl / self.n) self.set(trait_change_notify=False, qrx=self.qx / self.n) self.q = optimalMatching(self.qx, self.qy)[0] def _qy_changed(self, old, new): self.wy = q2w(self.qy, wl=self.wl / self.n) self.set(trait_change_notify=False, qry=self.qy / self.n) self.q = optimalMatching(self.qx, self.qy)[0] def _qrx_changed(self, old, new): self.qx = self.qrx * self.n def _qry_changed(self, old, new): self.qy = self.qry * self.n def _n_changed(self, old, new): self.set(trait_change_notify=False, qx=self.qrx * self.n) self.set(trait_change_notify=False, qy=self.qry * self.n)
def validate(self, object, name, value): if operator.isNumberType(value): value = np.atleast_1d(value) return CArray.validate(self, object, name, value)
class PhysioData(HasTraits): """ Contains the parameters needed to run a MEAP session """ available_widgets = Instance(list) def _available_widgets_default(self): available_panels = ["Annotation"] if "dzdt" in self.contents and "z0" in self.contents: available_panels.append("ICG B Point") if "doppler" in self.contents: available_panels.append("Doppler") if self.dzdt_warping_functions.size > 0: available_panels.append("Registration") return available_panels contents = Property(Set) def _get_contents(self): """ Assuming this object is already initialized, this trait will check for which data are available. For each signal type if the raw timeseries is available, """ contents = set() for signal in ENSEMBLE_SIGNALS | set(('respiration',)): attr = signal+"_data" if not hasattr(self, attr): continue if getattr(self,attr).size > 0: contents.update((signal,)) # Check for respiration-corrected versions of z0 and dzdt for signal in ["resp_corrected_z0", "resp_corrected_dzdt"]: if not hasattr(self, signal): continue if getattr(self,signal).size > 0: contents.update((signal,)) return contents calculable_indexes = Property(Set) @cached_property def _get_calculable_indexes(self): """ Determines, based on content, which indexes are possible to calculate. """ # Signals has_ecg = "ecg" in self.contents has_z0 = "z0" in self.contents has_dzdt = "dzdt" in self.contents has_resp = "respiration" in self.contents has_systolic = "systolic" in self.contents has_diastolic = "diastolic" in self.contents has_bp = "bp" in self.contents has_resp_corrected_z0 = self.resp_corrected_z0.size > 0 has_l = self.subject_l > 1 # Indexes has_hr = False has_lvet = False has_sv = False has_map = False has_co = False ix = set() if has_ecg: has_hr = True ix.update(("hr","hrv")) if has_ecg and has_dzdt: has_lvet = True ix.update(("pep", "lvet", "eef")) if has_lvet and has_l and has_z0: has_sv = True ix.update(("sv",)) if has_resp_corrected_z0: ix.update(("resp_corrected_sv",)) if has_bp or has_systolic and has_diastolic: has_map = True ix.update(("map",)) if has_hr and has_sv: has_co = True ix.update(("co",)) if has_resp_corrected_z0: ix.update(("resp_corrected_co",)) if has_co and has_map: ix.update(("tpr",)) if has_resp_corrected_z0: ix.update(("resp_corrected_tpr",)) if has_resp: ix.update(("nbreaths")) return ix meap_version = CStr(__version__) original_file = File file_location = File # -- Censored Epochs -- censored_intervals = Array censoring_sources = List @cached_property def _get_censored_regions(self): censor_regions = [] for signal in self.contents: censor_regions += getattr(self, signal+"_ts").censored_regions # MEA Weighting function mea_window_type = PrototypedFrom("config") mea_n_neighbors = PrototypedFrom("config") mea_window_secs = PrototypedFrom("config") mea_exp_power = PrototypedFrom("config") mea_func_name = PrototypedFrom("config") mea_weight_direction = PrototypedFrom("config") use_trimmed_co = PrototypedFrom("config") mea_smooth_hr = PrototypedFrom("config") mea_weights = Array use_secondary_heartbeat = PrototypedFrom("config") secondary_heartbeat = PrototypedFrom("config") secondary_heartbeat_pre_msec = PrototypedFrom("config") secondary_heartbeat_abs = PrototypedFrom("config") secondary_heartbeat_window = PrototypedFrom("config") secondary_heartbeat_window_len = PrototypedFrom("config") secondary_heartbeat_n_likelihood_bins = PrototypedFrom("config") use_ECG2 = PrototypedFrom("config") ecg2_weight = PrototypedFrom("config") qrs_signal_source = PrototypedFrom("config") # Bpoint classifier options bpoint_classifier_pre_point_msec = PrototypedFrom("config") bpoint_classifier_post_point_msec = PrototypedFrom("config") bpoint_classifier_sample_every_n_msec =PrototypedFrom("config") bpoint_classifier_false_distance_min =PrototypedFrom("config") bpoint_classifier_use_bpoint_prior =PrototypedFrom("config") bpoint_classifier_include_derivative =PrototypedFrom("config") # Contains errors in msec from bpoint cross validation bpoint_classifier_cv_error = Array # Points on doppler signal dx_point_type = PrototypedFrom("config") dx_point_window_len = PrototypedFrom("config") db_point_type = PrototypedFrom("config") db_point_window_len = PrototypedFrom("config") # Impedance Data z0_winsor_min = CFloat(0.005) z0_winsor_max = CFloat(0.005) z0_winsorize = CBool(False) z0_included = CBool(False) z0_decimated = CBool(False) z0_channel_name = CStr("") z0_sampling_rate = CFloat(1000) z0_sampling_rate_unit = CStr("Hz") z0_unit = CStr("Ohms") z0_start_time = CFloat(0.) z0_data = Array mea_z0_matrix = Array z0_matrix = Property(Array,depends_on="peak_indices") def _get_z0_matrix(self): if self.peak_indices.size == 0: return np.array([]) return peak_stack(self.peak_indices,self.z0_data, pre_msec=self.dzdt_pre_peak,post_msec=self.dzdt_post_peak, sampling_rate=self.z0_sampling_rate) mea_resp_corrected_z0_matrix = Array resp_corrected_z0_matrix = Property(Array,depends_on="peak_indices") def _get_resp_corrected_z0_matrix(self): if self.peak_indices.size == 0 or self.resp_corrected_z0.size == 0: return np.array([]) return peak_stack(self.peak_indices,self.resp_corrected_z0, pre_msec=self.dzdt_pre_peak,post_msec=self.dzdt_post_peak, sampling_rate=self.z0_sampling_rate) dzdt_winsor_min = CFloat(0.005) dzdt_winsor_max = CFloat(0.005) dzdt_winsorize = CBool(False) dzdt_included = CBool(False) dzdt_decimated = CBool(False) dzdt_channel_name = CStr("") dzdt_sampling_rate = CFloat(1000) dzdt_sampling_rate_unit = CStr("Hz") dzdt_unit = CStr("Ohms/Sec") dzdt_start_time = CFloat(0.) dzdt_data = Array dzdt_matrix = Property(Array,depends_on="peak_indices") mea_dzdt_matrix = Array @cached_property def _get_dzdt_matrix(self): logger.info("constructing dZ/dt matrix") if self.peak_indices.size == 0: return np.array([]) return peak_stack(self.peak_indices,self.dzdt_data, pre_msec=self.dzdt_pre_peak,post_msec=self.dzdt_post_peak, sampling_rate=self.dzdt_sampling_rate) # Doppler radar doppler_winsor_min = CFloat(0.005) doppler_winsor_max = CFloat(0.005) doppler_winsorize = CBool(False) doppler_included = CBool(False) doppler_decimated = CBool(False) doppler_channel_name = CStr("") doppler_sampling_rate = CFloat(1000) doppler_sampling_rate_unit = CStr("Hz") doppler_unit = CStr("Ohms/Sec") doppler_start_time = CFloat(0.) doppler_data = Array doppler_matrix = Property(Array,depends_on="peak_indices") mea_doppler_matrix = Array @cached_property def _get_doppler_matrix(self): if self.peak_indices.size == 0: return np.array([]) return peak_stack(self.peak_indices,self.doppler_data, pre_msec=self.doppler_pre_peak,post_msec=self.doppler_post_peak, sampling_rate=self.doppler_sampling_rate) # Respiration resp_corrected_dzdt_matrix = Property(Array,depends_on="peak_indices") mea_resp_corrected_dzdt_matrix = Array @cached_property def _get_resp_corrected_dzdt_matrix(self): if self.peak_indices.size == 0 or self.resp_corrected_dzdt.size == 0: return np.array([]) return peak_stack(self.peak_indices,self.resp_corrected_dzdt, pre_msec=self.dzdt_pre_peak,post_msec=self.dzdt_post_peak, sampling_rate=self.dzdt_sampling_rate) # ECG ecg_included = CBool(False) ecg_winsor_min = CFloat(0.005) ecg_winsor_max = CFloat(0.005) ecg_winsorize = CBool(False) ecg_decimated = CBool(False) ecg_channel_name = CStr("") ecg_sampling_rate = CFloat(1000) ecg_sampling_rate_unit = CStr("Hz") ecg_unit = CStr("V") ecg_start_time = CFloat(0.) ecg_data = Array ecg_matrix = Property(Array,depends_on="peak_indices") mea_ecg_matrix = Array @cached_property def _get_ecg_matrix(self): if self.peak_indices.size == 0: return np.array([]) return peak_stack(self.peak_indices,self.ecg_data, pre_msec=self.ecg_pre_peak,post_msec=self.ecg_post_peak, sampling_rate=self.ecg_sampling_rate) # ECG Secondary (eg from EEG) ecg2_included = CBool(False) ecg2_winsor_min = CFloat(0.005) ecg2_winsor_max = CFloat(0.005) ecg2_winsorize = CBool(False) ecg2_decimated = CBool(False) ecg2_channel_name = CStr("") ecg2_sampling_rate = CFloat(1000) ecg2_sampling_rate_unit = CStr("Hz") ecg2_unit = CStr("V") ecg2_start_time = CFloat(0.) ecg2_data = Array ecg2_matrix = Property(Array,depends_on="peak_indices") mea_ecg2_matrix = Array @cached_property def _get_ecg2_matrix(self): if self.peak_indices.size == 0: return np.array([]) return peak_stack(self.peak_indices,self.ecg2_data, pre_msec=self.ecg_pre_peak,post_msec=self.ecg_post_peak, sampling_rate=self.ecg_sampling_rate) # Blood pressure might come from a CNAP using_continuous_bp = CBool(False) bp_included = CBool(False) bp_winsor_min = CFloat(0.005) bp_winsor_max = CFloat(0.005) bp_winsorize = CBool(False) bp_decimated = CBool(False) bp_channel_name = CStr("") bp_sampling_rate = CFloat(1000) bp_sampling_rate_unit = CStr("Hz") bp_unit = CStr("mmHg") bp_start_time = CFloat(0.) bp_data = Array bp_matrix = Property(Array,depends_on="peak_indices") mea_bp_matrix = Array @cached_property def _get_bp_matrix(self): return peak_stack(self.peak_indices,self.bp_data, pre_msec=self.bp_pre_peak,post_msec=self.bp_post_peak, sampling_rate=self.bp_sampling_rate) # Or two separate channels systolic_included = CBool(False) systolic_winsor_min = CFloat(0.005) systolic_winsor_max = CFloat(0.005) systolic_winsorize = CBool(False) systolic_decimated = CBool(False) systolic_channel_name = CStr("") systolic_sampling_rate = CFloat(1000) systolic_sampling_rate_unit = CStr("Hz") systolic_unit = CStr("mmHg") systolic_start_time = CFloat(0.) systolic_data = Array systolic_matrix = Property(Array, depends_on="peak_indices,bp_pre_peak,bp_post_peak") mea_systolic_matrix = Array @cached_property def _get_systolic_matrix(self): if self.peak_indices.size == 0 or not ("systolic" in self.contents): return np.array([]) return peak_stack(self.peak_indices,self.systolic_data, pre_msec=self.bp_pre_peak,post_msec=self.bp_post_peak, sampling_rate=self.bp_sampling_rate) diastolic_included = CBool(False) diastolic_winsor_min = CFloat(0.005) diastolic_winsor_max = CFloat(0.005) diastolic_winsorize = CBool(False) diastolic_decimated = CBool(False) diastolic_channel_name = CStr("") diastolic_sampling_rate = CFloat(1000) diastolic_sampling_rate_unit = CStr("Hz") diastolic_unit = CStr("Ohms") diastolic_start_time = CFloat(0.) diastolic_data = Array diastolic_matrix = Property(Array, depends_on="peak_indices,bp_pre_peak,bp_post_peak") mea_diastolic_matrix = Array @cached_property def _get_diastolic_matrix(self): if self.peak_indices.size == 0 or not ("diastolic" in self.contents): return np.array([]) return peak_stack(self.peak_indices,self.diastolic_data, pre_msec=self.bp_pre_peak,post_msec=self.bp_post_peak, sampling_rate=self.bp_sampling_rate) respiration_included = CBool(False) respiration_winsor_min = CFloat(0.005) respiration_winsor_max = CFloat(0.005) respiration_winsorize = CBool(False) respiration_decimated = CBool(False) respiration_channel_name = CStr("") respiration_sampling_rate = CFloat(1000) respiration_sampling_rate_unit = CStr("Hz") respiration_unit = CStr("Ohms") respiration_start_time = CFloat(0.) respiration_data = Array respiration_cycle = Array respiration_amount = Array resp_corrected_z0 = Array resp_corrected_dzdt = Array processed_respiration_data = Array processed_respiration_time = Array # -- Event marking signals (experiment and mri-related) mri_trigger_times = Array mri_trigger_included = CBool(False) mri_trigger_decimated = CBool(False) mri_trigger_channel_name = CStr("") mri_trigger_sampling_rate = CFloat(1000) mri_trigger_sampling_rate_unit = CStr("Hz") mri_trigger_unit = CStr("V") mri_trigger_start_time = CFloat(0.) event_names = List event_sampling_rate = CFloat(1000) event_included = CBool(True) event_decimated = CBool(False) event_start_time = CFloat(0.) event_sampling_rate_unit = "Hz" event_unit = CStr("Hz") # -- results of peak detection peak_times = Array peak_indices = CArray(dtype=np.int) # Non-markable heartbeats dne_peak_times = Array dne_peak_indices = CArray(dtype=np.int) # Any custom labels for heartbeats go here hand_labeled = Instance(np.ndarray) # An array of beat indices, each corresponding def _hand_labeled_default(self): return np.zeros_like(self.peak_indices) # Is the beat usable for analysis? usable = Instance(np.ndarray) def _usable_default(self): return np.ones(len(self.peak_indices),dtype=np.int) p_indices = Instance(np.ndarray) def _p_indices_default(self): return np.zeros_like(self.peak_indices) q_indices = Instance(np.ndarray) def _q_indices_default(self): return np.zeros_like(self.peak_indices) r_indices = Instance(np.ndarray) def _r_indices_default(self): return np.zeros_like(self.peak_indices) s_indices = Instance(np.ndarray) def _s_indices_default(self): return np.zeros_like(self.peak_indices) t_indices = Instance(np.ndarray) def _t_indices_default(self): return np.zeros_like(self.peak_indices) b_indices = Instance(np.ndarray) def _b_indices_default(self): return np.zeros_like(self.peak_indices) c_indices = Instance(np.ndarray) def _c_indices_default(self): return np.zeros_like(self.peak_indices) x_indices = Instance(np.ndarray) def _x_indices_default(self): return np.zeros_like(self.peak_indices) o_indices = Instance(np.ndarray) def _o_indices_default(self): return np.zeros_like(self.peak_indices) systole_indices = Instance(np.ndarray) def _systole_indices_default(self): return np.zeros_like(self.peak_indices) diastole_indices = Instance(np.ndarray) def _diastole_indices_default(self): return np.zeros_like(self.peak_indices) # Indices for doppler db_indices = Instance(np.ndarray) def _db_indices_default(self): return np.zeros_like(self.peak_indices) dx_indices = Instance(np.ndarray) def _dx_indices_default(self): return np.zeros_like(self.peak_indices) # Holds B points in the Karcher modes karcher_b_indices = Instance(np.ndarray) def _karcher_b_indices_default(self): return np.zeros(self.n_modes) # --- Subject information subject_age = CFloat(0.) subject_gender = Enum("M","F") subject_weight = CFloat(0.,label="Weight (lbs)") subject_height_ft = Int(0,label="Height (ft)", desc="Subject's height in feet") subject_height_in = Int(0,label = "Height (in)", desc="Subject's height in inches") subject_electrode_distance_front = CFloat(0., label="Impedance electrode distance (front)") subject_electrode_distance_back = CFloat(0., label="Impedance electrode distance (back)") subject_electrode_distance_right = CFloat(0., label="Impedance electrode distance (back)") subject_electrode_distance_left = CFloat(0., label="Impedance electrode distance (back)") subject_resp_max = CFloat(0.,label="Respiration circumference max (cm)") subject_resp_min = CFloat(0.,label="Respiration circumference min (cm)") subject_in_mri = CBool(False,label="Subject was in MRI scanner") subject_control_base_impedance = CFloat(0.,label="Control Imprdance", desc="If in MRI, store the z0 value from outside the MRI") subject_l = Property(CFloat,depends_on= "subject_electrode_distance_front," + \ "subject_electrode_distance_back," + \ "subject_electrode_distance_right," + \ "subject_electrode_distance_left," + \ "subject_height_ft" ) @cached_property def _get_subject_l(self): """ Uses information from the subject measurements to define the l variable for calculating stroke volume. if left and right electrode distances are provided, use the average if front and back electrode distances are provided, use the average if subject height in feet and inches is provided, use the estimate of l = 0.17 * height Otherwise return the first measurement found in front,back,left,right If nothing is found, returns 1 """ front = self.subject_electrode_distance_front back = self.subject_electrode_distance_back left = self.subject_electrode_distance_left right = self.subject_electrode_distance_right if left > 0 and right > 0: return (left + right) / 2. if front > 0 and back > 0: return (front + back) / 2. if self.subject_height_ft > 0: return (12*self.subject_height_ft + \ self.subject_height_in) * 2.54 * 0.17 for measure in (front, back, left, right): if measure > 0.: return measure return 1 # --- From the global configuration config = Instance(MEAPConfig) apply_ecg_smoothing = PrototypedFrom("config") ecg_smoothing_window_len = PrototypedFrom("config") apply_imp_smoothing = PrototypedFrom("config") imp_smoothing_window_len = PrototypedFrom("config") apply_bp_smoothing = PrototypedFrom("config") bp_smoothing_window_len = PrototypedFrom("config") regress_out_resp = PrototypedFrom("config") # parameters for processing the raw data before PT detecting subject_in_mri = PrototypedFrom("config") peak_detection_algorithm = PrototypedFrom("config") # PanTomkins parameters qrs_source_signal = Enum("ecg", "ecg2") bandpass_min = PrototypedFrom("config") bandpass_max =PrototypedFrom("config") smoothing_window_len = PrototypedFrom("config") smoothing_window = PrototypedFrom("config") pt_adjust = PrototypedFrom("config") peak_threshold = PrototypedFrom("config") apply_filter = PrototypedFrom("config") apply_diff_sq = PrototypedFrom("config") apply_smooth_ma = PrototypedFrom("config") peak_window = PrototypedFrom("config") # Parameters for waveform extraction ecg_pre_peak = PrototypedFrom("config") ecg_post_peak = PrototypedFrom("config") dzdt_pre_peak = PrototypedFrom("config") dzdt_post_peak = PrototypedFrom("config") bp_pre_peak = PrototypedFrom("config") bp_post_peak = PrototypedFrom("config") systolic_pre_peak = PrototypedFrom("config") systolic_post_peak = PrototypedFrom("config") diastolic_pre_peak = PrototypedFrom("config") diastolic_post_peak = PrototypedFrom("config") doppler_pre_peak = PrototypedFrom("config") doppler_post_peak = PrototypedFrom("config") stroke_volume_equation = PrototypedFrom("config") # parameters for respiration analysis process_respiration = PrototypedFrom("config") resp_polort = PrototypedFrom("config") resp_high_freq_cutoff = PrototypedFrom("config") resp_inhale_begin_times = Array resp_exhale_begin_times = Array # Time points of the global ensemble average ens_avg_ecg_signal = Array ens_avg_dzdt_signal = Array ens_avg_bp_signal = Array ens_avg_systolic_signal = Array ens_avg_diastolic_signal = Array ens_avg_doppler_signal = Array ens_avg_p_time = CFloat ens_avg_q_time = CFloat ens_avg_r_time = CFloat ens_avg_s_time = CFloat ens_avg_t_time = CFloat ens_avg_b_time = CFloat ens_avg_db_time = CFloat ens_avg_dx_time = CFloat ens_avg_c_time = CFloat ens_avg_x_time = CFloat ens_avg_y_time = CFloat ens_avg_o_time = CFloat ens_avg_systole_time = CFloat ens_avg_diastole_time = CFloat using_hand_marked_point_priors = CBool(False) censored_secs_before = Array # MEA Physio timeseries lvet = Array co = Array resp_corrected_co = Array pep = Array sv = Array resp_corrected_sv = Array map = Array systolic = Array diastolic = Array hr = Array mea_hr = Array tpr = Array resp_corrected_tpr = Array def _config_default(self): return MEAPConfig() # SRVF-warping parameters srvf_lambda = PrototypedFrom("config") srvf_max_karcher_iterations = PrototypedFrom("config") srvf_update_min = PrototypedFrom("config") srvf_karcher_mean_subset_size = PrototypedFrom("config") srvf_multi_mode_variance_cutoff = PrototypedFrom("config") srvf_use_moving_ensembled = PrototypedFrom("config") dzdt_num_inputs_to_group_warping = PrototypedFrom("config") srvf_t_min = PrototypedFrom("config") srvf_t_max = PrototypedFrom("config") bspline_before_warping = PrototypedFrom("config") dzdt_srvf_karcher_mean = Array dzdt_karcher_mean = Array dzdt_karcher_mean_time = Array dzdt_warping_functions = Array dzdt_functions_to_warp = Array # Holds data related to initial karcher mean dzdt_karcher_mean_inputs = Array dzdt_karcher_mean_over_iterations = Array srvf_iteration_distances = Array srvf_iteration_energy = Array # Data related to the multiple modes n_modes = PrototypedFrom("config") max_kmeans_iterations = PrototypedFrom("config") mode_dzdt_karcher_means = Array mode_cluster_assignment = Array mode_dzdt_srvf_karcher_means = Array # Storing and accessing the bpoint classifier bpoint_classifier_file = File def save(self,outfile): # Populate matfile-friendly data structures for censoring regions tmp = tempfile.NamedTemporaryFile() save_attrs = [] for k in self.editable_traits(): if k.endswith("ts"): continue if k == "available_widgets": continue if k == "bpoint_classifier": continue if k == "bpoint_classifier_file": continue if k in ("censored_regions","event_names"): continue v = getattr(self,k) if type(v) == np.ndarray: if v.size == 0: continue if type(v) is set: continue save_attrs.append(k) savedict = dict([(k,getattr(self,k)) \ for k in save_attrs if not (getattr(self,k) is None)]) savedict["censoring_sources"] = np.array(self.censoring_sources) for evt in self.event_names: savedict[evt] = getattr(self,evt) savedict["event_names"] = np.array(self.event_names) for k,v in savedict.iteritems(): try: savemat( tmp, {k:v}, long_field_names=True) except Exception, e: logger.warn("unable to save %s because of %s", k,e) tmp.close() try: savemat(outfile, savedict,long_field_names=True) except Exception,e: messagebox("Failed to save %s:\n\n%s"%(outfile,e))
class DataSourceWizard(HasTraits): data_sources = Dict _data_sources_names = Property(depends_on='data_sources') def _get__data_sources_names(self): names = [] for name in self.data_sources: try: self.data_sources[name] + 1 names.append(name) except TypeError: pass names.sort() return names # Dictionnary mapping the views data_type = Trait( 'point', { 'A surface': 'surface', 'A set of points, that can be connected by lines': 'point', 'A set of vectors': 'vector', 'Volumetric data': 'volumetric', }) position_type = Trait( 'image data', { 'Specified explicitly': 'explicit', 'Implicitely positioned on a regular grid': 'image data', 'On an orthogonal grid with varying spacing': 'orthogonal grid', }) # The array that is used for finding out the shape of the grid, # when creating an ImageData grid_shape_source = Property(depends_on='grid_shape_source_') def _get_grid_shape_source(self): if self.grid_shape_source_ == '': # catter for improperly initialized view keys = self._data_sources_names if not self.grid_shape.any(): self.grid_shape = \ self.data_sources[keys[0]].shape return keys[0] elif self.grid_shape_source_[:16] == 'Shape of array: ': return self.grid_shape_source_[17:-1] else: return "" # Shadow traits for grid_shape_source grid_shape_source_ = Str def _grid_shape_source_changed(self): if not self.grid_shape_source == '': array_shape = \ atleast_3d(self.data_sources[self.grid_shape_source]).shape grid_shape = ones((3, )) grid_shape[:len(array_shape)] = array_shape self.grid_shape = grid_shape _grid_shape_source_labels = Property(depends_on='_data_sources_names') def _get__grid_shape_source_labels(self): values = [ 'Shape of array: "%s"' % name for name in self._data_sources_names ] values.sort values.append('Specified explicitly') return values # The shape of the grid array. Used when position is implicit grid_shape = CArray(shape=(3, ), dtype='i') # Whether or not the data points should be connected. lines = false # The scalar data selection scalar_data = Str('', help="Select the array that gives the value of the " "scalars plotted.") position_x = Str(help="Select the array that gives the x " "position of the data points") position_y = Str(help="Select the array that gives the y " "position of the data points") position_z = Str(help="Select the array that gives the z " "position of the data points") connectivity_triangles = Str has_vector_data = false(help="""Do you want to plot vector components?""") # A boolean to ask the user if he wants to load scalar data has_scalar_data = false vector_u = Str vector_v = Str vector_w = Str #---------------------------------------------------------------------- # Public interface #---------------------------------------------------------------------- def init_arrays(self): # Force all the array names to be properly initialized array_names = set(self.data_sources.keys()) if len(array_names) == 0: # We should probably bail out here. return False for attr in ( 'position_x', 'position_y', 'position_z', 'scalar_data', 'vector_u', 'vector_v', 'vector_w', 'connectivity_triangles', ): if len(array_names) > 0: array_name = array_names.pop() setattr(self, attr, array_name) def guess_arrays(self): """ Do some guess work on the arrays to find sensible default. """ array_names = set(self._data_sources_names) found_some = False if set(('x', 'y', 'z')).issubset(array_names): self.position_x = 'x' self.position_y = 'y' self.position_z = 'z' array_names = array_names.difference(('x', 'y', 'z')) found_some = True elif set(('X', 'Y', 'Z')).issubset(array_names): self.position_x = 'X' self.position_y = 'Y' self.position_z = 'Z' array_names = array_names.difference(('X', 'Y', 'Z')) found_some = True if set(('u', 'v', 'w')).issubset(array_names): self.vector_u = 'u' self.vector_v = 'v' self.vector_w = 'w' array_names = array_names.difference(('u', 'v', 'w')) found_some = True elif set(('U', 'V', 'W')).issubset(array_names): self.vector_u = 'U' self.vector_v = 'V' self.vector_w = 'W' array_names = array_names.difference(('U', 'V', 'W')) found_some = True if found_some: # Need to re-attribute the guessed names. for attr in ('scalar_data', 'vector_u', 'vector_v', 'vector_w', 'connectivity_triangles'): if len(array_names) > 0: setattr(self, attr, array_names.pop()) else: break def build_data_source(self): """ This is where we apply the selections made by the user in in the wizard to build the data source. """ factory = DataSourceFactory() # Keep a reference to the factory to be able to replay it, say # on other data. self._factory = factory if self.data_type_ == 'point': # The user wants to explicitly position vector, # thus only sensible data structures for points is with # explicit positioning. self.position_type_ == 'explicit' # In addition, this view does not allow for # connectivity. factory.unstructured = True factory.connected = False else: factory.connected = True if (self.position_type_ == "image data" and not self.data_type_ == "point"): if not self.has_scalar_data and not self.vector_u == '': # With image data we need a scalar array always: factory.scalar_data = ones(self.grid_shape) factory.position_implicit = True else: factory.position_x = self.get_sdata(self.position_x) factory.position_y = self.get_sdata(self.position_y) factory.position_z = self.get_sdata(self.position_z) if self.position_type_ == "orthogonal grid": factory.orthogonal_grid = True if self.position_type_ == "explicit" and self.data_type_ == "surface": factory.connectivity_triangles = self.get_data( self.connectivity_triangles) if self.lines and self.data_type_ == "point": factory.lines = True if self.has_vector_data or self.data_type_ == 'vector': # In the vector view, the user is not explicitly asked to # Enable vectors. factory.has_vector_data = True factory.vector_u = self.get_sdata(self.vector_u) factory.vector_v = self.get_sdata(self.vector_v) factory.vector_w = self.get_sdata(self.vector_w) if self.has_scalar_data or self.data_type_ == 'volumetric': # In the volumetric view, the user is not explicitly asked to # Enable scalars. factory.scalar_data = self.get_sdata(self.scalar_data) if self.connectivity_triangles == '': factory.connectivity_triangles = None self.data_source = factory.build_data_source() if self.has_scalar_data: if hasattr(self.data_source, 'scalar_name'): self.data_source.scalar_name = self.scalar_data elif hasattr(self.data_source, 'point_scalar_name'): self.data_source.point_scalar_name = self.scalars #---------------------------------------------------------------------- # Private interface #---------------------------------------------------------------------- def get_data(self, name): return self.data_sources[name] def get_sdata(self, name): ary = self.data_sources[name] if not self.data_type_ == 'point': ary = resize(ary, self.grid_shape) return ary def active_arrays(self): """ Return the list of the active array-selection drop-downs. """ arrays = [] if self.data_type_ == 'point' or self.position_type_ == 'explicit': arrays.extend([ 'position_x', 'position_y', 'position_z', ]) if self.data_type_ == 'vector' or self.has_vector_data: arrays.extend(['vector_u', 'vector_v', 'vector_w']) if self.has_scalar_data or self.data_type_ == 'volumetric': arrays.extend(['scalar_data']) return arrays def check_arrays(self): """ Checks that all the array have the right size. """ arrays_to_check = self.active_arrays() if len(arrays_to_check) == 0: return True size = self.get_data(getattr(self, arrays_to_check.pop())).size for attr in arrays_to_check: if not self.get_data(getattr(self, attr)).size == size: return False if (self.data_type_ == 'surface' and self.position_type_ == "explicit"): if not self.connectivity_triangles.size / 3 == size: return False return True
class Calib(HasPrivateTraits): """ Container for calibration data in `*.xml` format This class serves as interface to load calibration data for the used microphone array. """ #: Name of the .xml file to be imported. from_file = File(filter=['*.xml'], desc="name of the xml file to import") #: Basename of the .xml-file. Readonly / is set automatically. basename = Property(depends_on='from_file', desc="basename of xml file") #: Number of microphones in the calibration data, #: is set automatically / read from file. num_mics = CLong(0, desc="number of microphones in the geometry") #: Array of calibration factors, #: is set automatically / read from file. data = CArray(desc="calibration data") # Internal identifier digest = Property(depends_on=[ 'basename', ]) @cached_property def _get_digest(self): return digest(self) @cached_property def _get_basename(self): if not path.isfile(self.from_file): return '' return path.splitext(path.basename(self.from_file))[0] @on_trait_change('basename') def import_data(self): """ Loads the calibration data from `*.xml` file . """ if not path.isfile(self.from_file): # empty calibration if self.basename == '': self.data = None self.num_mics = 0 # no file there else: self.data = array([ 1.0, ], 'd') self.num_mics = 1 return import xml.dom.minidom doc = xml.dom.minidom.parse(self.from_file) names = [] data = [] for element in doc.getElementsByTagName('pos'): names.append(element.getAttribute('Name')) data.append(float(element.getAttribute('factor'))) self.data = array(data, 'd') self.num_mics = self.data.shape[0]
class SourceMixer(SamplesGenerator): """ Mixes the signals from several sources. """ #: List of :class:`~acoular.tprocess.SamplesGenerator` objects #: to be mixed. sources = List(Instance(SamplesGenerator, ())) #: Sampling frequency of the signal. sample_freq = Property(depends_on=['ldigest']) #: Number of channels. numchannels = Property(depends_on=['ldigest']) #: Number of samples. numsamples = Property(depends_on=['ldigest']) #: Amplitude weight(s) for the sources as array. If not set, #: all source signals are equally weighted. #: Must match the number of sources in :attr:`sources`. weights = CArray(desc="channel weights") # internal identifier ldigest = Property(depends_on=[ 'sources.digest', ]) # internal identifier digest = Property(depends_on=['ldigest', 'weights', '__class__']) @cached_property def _get_ldigest(self): res = '' for s in self.sources: res += s.digest return res @cached_property def _get_digest(self): return digest(self) @cached_property def _get_sample_freq(self): if self.sources: sample_freq = self.sources[0].sample_freq else: sample_freq = 0 return sample_freq @cached_property def _get_numchannels(self): if self.sources: numchannels = self.sources[0].numchannels else: numchannels = 0 return numchannels @cached_property def _get_numsamples(self): if self.sources: numsamples = self.sources[0].numsamples else: numsamples = 0 return numsamples def validate_sources(self): """ Validates if sources fit together. """ for s in self.sources[1:]: if self.sample_freq != s.sample_freq: raise ValueError("Sample frequency of %s does not fit" % s) if self.numchannels != s.numchannels: raise ValueError("Channel count of %s does not fit" % s) if self.numsamples != s.numsamples: raise ValueError("Number of samples of %s does not fit" % s) def result(self, num): """ Python generator that yields the output block-wise. The outputs from the sources in the list are being added. Parameters ---------- num : integer This parameter defines the size of the blocks to be yielded (i.e. the number of samples per block). Returns ------- Samples in blocks of shape (num, numchannels). The last block may be shorter than num. """ self.validate_sources() gens = [i.result(num) for i in self.sources[1:]] weights = self.weights.copy() if weights.size == 0: weights = array([1. for j in range(len(self.sources))]) assert weights.shape[0] == len(self.sources) for temp in self.sources[0].result(num): temp *= weights[0] sh = temp.shape[0] for j, g in enumerate(gens): temp1 = next(g) * weights[j + 1] if temp.shape[0] > temp1.shape[0]: temp = temp[:temp1.shape[0]] temp += temp1[:temp.shape[0]] yield temp if sh > temp.shape[0]: break
class OpenJet(FlowField): """ Provides an analytical approximation of the flow field of an open jet, see :ref:`Albertson et al., 1950<Albertson1950>`. Notes ----- This is not a fully generic implementation and is limited to flow in the x-direction only. No other directions are possible at the moment and flow components in the other direction are zero. """ #: Exit velocity at jet origin, i.e. the nozzle. Defaults to 0. v0 = Float(0.0, desc="exit velocity") #: Location of the nozzle center, defaults to the co-ordinate origin. origin = CArray(dtype=float64, shape=(3, ), value=array((0., 0., 0.)), desc="center of nozzle") #: Diameter of the nozzle, defaults to 0.2 . D = Float(0.2, desc="nozzle diameter") # internal identifier digest = Property(depends_on=['v0', 'origin', 'D'], ) traits_view = View( [['v0{Exit velocity}', 'origin{Jet origin}', 'D{Nozzle diameter}'], '|[Open jet]']) @cached_property def _get_digest(self): return digest(self) def v(self, xx): """ Provides the flow field as a function of the location. This is implemented here only for a jet in x-direction and the y- and z-components are set to zero. Parameters ---------- xx : array of floats of shape (3, ) Location in the fluid for which to provide the data. Returns ------- tuple with two elements The first element in the tuple is the velocity vector and the second is the Jacobian of the velocity vector field, both at the given location. """ x, y, z = xx - self.origin r = sqrt(y * y + z * z) x1 = 0.081 * x h1 = r + x1 - 0.5 * self.D U = self.v0 * exp(-h1 * h1 / (2 * x1 * x1)) if h1 < 0.0: Udr = 0.0 U = self.v0 else: Udr = -h1 * U / (x1 * x1) if r > 0.0: Udy = y * Udr / r Udz = z * Udr / r else: Udy = Udz = 0.0 Udx = (h1 * h1 / (x * x1 * x1) - h1 / (x * x1)) * U if h1 < 0.0: Udx = 0 # flow field v = array((U, 0., 0.)) # Jacobi matrix dv = array(((Udx, 0., 0.), (Udy, 0., 0.), (Udz, 0., 0.))).T return v, dv
class MovingPointSourceDipole(PointSourceDipole, MovingPointSource): # internal identifier digest = Property( depends_on = ['mics.digest', 'signal.digest', 'loc', \ 'env.digest', 'start_t', 'start', 'up', 'direction', '__class__'], ) #: Reference vector, perpendicular to the x and y-axis of moving source. #: rotation source directivity around this axis rvec = CArray(dtype=float, shape=(3, ), value=array((0, 0, 0)), desc="reference vector") @cached_property def _get_digest(self): return digest(self) def get_emission_time(self, t, direction): eps = ones(self.mics.num_mics) epslim = 0.1 / self.up / self.sample_freq te = t.copy() # init emission time = receiving time j = 0 # Newton-Rhapson iteration while abs(eps).max() > epslim and j < 100: xs = array(self.trajectory.location(te)) loc = xs.copy() loc += direction rm = loc - self.mics.mpos # distance vectors to microphones rm = sqrt((rm * rm).sum(0)) # absolute distance loc /= sqrt((loc * loc).sum(0)) # distance unit vector der = array(self.trajectory.location(te, der=1)) Mr = (der * loc).sum(0) / self.env.c # radial Mach number eps = (te + rm / self.env.c - t) / (1 + Mr) # discrepancy in time te -= eps j += 1 #iteration count return te, rm, Mr, xs def get_moving_direction(self, direction, time=0): """ function that yields the moving coordinates along the trajectory """ trajg1 = array(self.trajectory.location(time, der=1))[:, 0][:, newaxis] rflag = (self.rvec == 0).all() #flag translation vs. rotation if rflag: return direction else: dx = array(trajg1.T) #direction vector (new x-axis) dy = cross(self.rvec, dx) # new y-axis dz = cross(dx, dy) # new z-axis RM = array((dx, dy, dz)).T # rotation matrix RM /= sqrt((RM * RM).sum(0)) # column normalized newdir = dot(RM, direction) return cross(newdir[:, 0].T, self.rvec.T).T def result(self, num=128): """ Python generator that yields the output at microphones block-wise. Parameters ---------- num : integer, defaults to 128 This parameter defines the size of the blocks to be yielded (i.e. the number of samples per block) . Returns ------- Samples in blocks of shape (num, numchannels). The last block may be shorter than num. """ #If signal samples are needed for te < t_start, then samples are taken #from the end of the calculated signal. mpos = self.mics.mpos # direction vector from tuple direc = array(self.direction, dtype=float) * 1e-5 direc_mag = sqrt(dot(direc, direc)) # normed direction vector direc_n = direc / direc_mag c = self.env.c # distance between monopoles as function of c, sample freq, direction vector dist = c / self.sample_freq * direc_mag * 2 # vector from dipole center to one of the monopoles dir2 = (direc_n * dist / 2.0).reshape((3, 1)) signal = self.signal.usignal(self.up) out = empty((num, self.numchannels)) # shortcuts and intial values m = self.mics t = self.start * ones(m.num_mics) i = 0 n = self.numsamples while n: n -= 1 te, rm, Mr, locs = self.get_emission_time(t, 0) t += 1. / self.sample_freq #location of the center loc = array(self.trajectory.location(te), dtype=float)[:, 0][:, newaxis] #distance of the dipoles from the center diff = self.get_moving_direction(dir2, te) # distance of sources rm1 = self.env._r(loc + diff, mpos) rm2 = self.env._r(loc - diff, mpos) ind = (te - self.start_t + self.start) * self.sample_freq if self.conv_amp: rm *= (1 - Mr)**2 rm1 *= (1 - Mr)**2 # assume that Mr is the same for both poles rm2 *= (1 - Mr)**2 try: # subtract the second signal b/c of phase inversion out[i] = rm / dist * \ (signal[array(0.5 + ind * self.up, dtype=int64)] / rm1 - \ signal[array(0.5 + ind * self.up, dtype=int64)] / rm2) i += 1 if i == num: yield out i = 0 except IndexError: break yield out[:i]
class RectGrid3D(RectGrid): """ Provides a cartesian 3D grid for the beamforming results. The grid has cubic or nearly cubic cells. It is defined by lower and upper x-, y- and z-limits. """ #: The lower z-limit that defines the grid, defaults to -1. z_min = Float(-1.0, desc="minimum z-value") #: The upper z-limit that defines the grid, defaults to 1. z_max = Float(1.0, desc="maximum z-value") #: Number of grid points along x-axis, readonly. nzsteps = Property(desc="number of grid points alog x-axis") #: Respective increments in x,y, and z-direction (in m), defaults #: to :attr:`~RectGrid.increment` for all three (whichever of the two #: increment parameters is set last replaces the other). increment3D = CArray(dtype=float, shape=(3, ), desc="3D step sizes") def _increment3D_default(self): return array([self.increment, self.increment, self.increment]) @on_trait_change('increment') def reset_increment3D(self): self.increment3D = array( [self.increment, self.increment, self.increment]) # internal identifier digest = Property( depends_on = ['x_min', 'x_max', 'y_min', 'y_max', 'z_min', 'z_max', \ 'increment3D'] ) # increment3D omitted in view for easier handling, can be added later traits_view = View( [ ['x_min', 'y_min', 'z_min', '|'], ['x_max', 'y_max', 'z_max', 'increment', \ 'size~{Grid size}', '|'], '-[Map extension]' ] ) @property_depends_on('nxsteps, nysteps, nzsteps') def _get_size(self): return self.nxsteps * self.nysteps * self.nzsteps @property_depends_on('nxsteps, nysteps, nzsteps') def _get_shape(self): return (self.nxsteps, self.nysteps, self.nzsteps) @property_depends_on('x_min, x_max, increment3D') def _get_nxsteps(self): i = abs(self.increment3D[0]) if i != 0: return int(round((abs(self.x_max - self.x_min) + i) / i)) return 1 @property_depends_on('y_min, y_max, increment3D') def _get_nysteps(self): i = abs(self.increment3D[1]) if i != 0: return int(round((abs(self.y_max - self.y_min) + i) / i)) return 1 @property_depends_on('z_min, z_max, increment3D') def _get_nzsteps(self): i = abs(self.increment3D[2]) if i != 0: return int(round((abs(self.z_max - self.z_min) + i) / i)) return 1 @cached_property def _get_digest(self): return digest(self) def pos(self): """ Calculates grid co-ordinates. Returns ------- array of floats of shape (3, :attr:`~Grid.size`) The grid point x, y, z-coordinates in one array. """ bpos = mgrid[self.x_min:self.x_max:self.nxsteps*1j, \ self.y_min:self.y_max:self.nysteps*1j, \ self.z_min:self.z_max:self.nzsteps*1j] bpos.resize((3, self.size)) return bpos def index(self, x, y, z): """ Queries the indices for a grid point near a certain co-ordinate. This can be used to query results or co-ordinates at/near a certain co-ordinate. Parameters ---------- x, y, z : float The co-ordinates for which the indices is queried. Returns ------- 3-tuple of integers The indices that give the grid point nearest to the given x, y, z co-ordinates from an array with the same shape as the grid. """ if x < self.x_min or x > self.x_max: raise ValueError, "x-value out of range %f (%f, %f)" % \ (x,self.x_min,self.x_max) if y < self.y_min or y > self.y_max: raise ValueError, "y-value out of range %f (%f, %f)" % \ (y,self.y_min,self.y_max) if z < self.z_min or z > self.z_max: raise ValueError, "z-value out of range %f (%f, %f)" % \ (z,self.z_min,self.z_max) xi = round((x - self.x_min) / self.increment3D[0]) yi = round((y - self.y_min) / self.increment3D[1]) zi = round((z - self.z_min) / self.increment3D[2]) return xi, yi, zi def indices(self, x1, y1, z1, x2, y2, z2): """ Queries the indices for a subdomain in the grid. Allows box-shaped subdomains. This can be used to mask or to query results from a certain sector or subdomain. Parameters ---------- x1, y1, z1, x2, y2, z2 : float A box-shaped sector is assumed that is given by two corners (x1,y1,z1) and (x2,y2,z2). Returns ------- 3-tuple of numpy slice objects The indices that can be used to mask/select the grid subdomain from an array with the same shape as the grid. """ xi1, yi1, zi1 = self.index(min(x1, x2), min(y1, y2), min(z1, z2)) xi2, yi2, zi2 = self.index(max(x1, x2), max(y1, y2), max(z1, z2)) return s_[xi1:xi2 + 1], s_[yi1:yi2 + 1], s_[zi1:zi2 + 1]
class MapViewer(SegmentViewer): name = "map" pretty_name = "Map" has_font = True valid_mouse_modes = [ SelectMode, PickTileMode, DrawMode, LineMode, SquareMode, FilledSquareMode ] mouse_mode_factory = SelectMode default_map_width = 16 draw_pattern = CArray(dtype=np.uint8, value=(0, )) @classmethod def create_control(cls, parent, linked_base): return MainFontMapScroller(parent, linked_base, cls.default_map_width, size=(500, 500), command=ChangeByteCommand) ##### Range operations def _get_range_processor(self): # Trait property getter return functools.partial(rect_ranges_to_indexes, self.control.bytes_per_row, 0) def get_optimized_selected_ranges(self, ranges): return ranges ##### SegmentViewer interface @property def window_title(self): return self.pretty_name + " " + self.machine.font_renderer.name + ", " + self.machine.font_mapping.name @on_trait_change('machine.font_change_event') def update_bitmap(self, evt): log.debug("MapViewer: machine font changed for %s" % self.control) if evt is not Undefined: self.control.recalc_view() def update_mouse_mode(self, mouse_handler=None): if mouse_handler is not None: self.mouse_mode_factory = mouse_handler log.debug("mouse mode: %s" % self.mouse_mode_factory) self.control.set_mouse_mode(self.mouse_mode_factory) @on_trait_change('linked_base.editor.task.segment_selected') def process_segment_selected(self, evt): log.debug("process_segment_selected for %s using %s; flags=%s" % (self.control, self.linked_base, str(evt))) if evt is not Undefined: self.control.bytes_per_row = self.linked_base.segment.map_width self.update_mouse_mode(SelectMode) def set_width(self, width): # also update the segment map width when changed SegmentViewer.set_width(self, width) self.linked_base.segment.map_width = self.width ##### Selections def highlight_selected_ranges(self): s = self.linked_base.segment s.clear_style_bits(selected=True) s.set_style_ranges_rect(self.linked_base.selected_ranges, self.control.bytes_per_row, selected=True) ##### Clipboard & Copy/Paste @property def clipboard_data_format(self): return "numpy,columns" def get_paste_command(self, serialized_data): print(serialized_data) print(serialized_data.source_data_format_name) if serialized_data.source_data_format_name == "numpy,columns": return PasteRectCommand return PasteCommand ##### toolbar def update_toolbar(self): self.update_mouse_mode() ##### Drawing pattern def set_draw_pattern(self, value): log.debug("new draw pattern: %s" % str(value)) self.draw_pattern = np.asarray(value, dtype=np.uint8) #### popup menus def common_popup_actions(self): return [ fa.CutAction, fa.CopyAction, fa.PasteAction, None, fa.SelectAllAction, fa.SelectNoneAction, fa.SelectInvertAction ]
class UncorrelatedNoiseSource(SamplesGenerator): """ Class to simulate white or pink noise as uncorrelated signal at each channel. The output is being generated via the :meth:`result` generator. """ #: Type of noise to generate at the channels. #: The `~acoular.signals.SignalGenerator`-derived class has to # feature the parameter "seed" (i.e. white or pink noise). signal = Trait(SignalGenerator, desc="type of noise") #: Array with seeds for random number generator. #: When left empty, arange(:attr:`numchannels`) + :attr:`signal`.seed #: will be used. seed = CArray(dtype=uint32, desc="random seed values") #: Number of channels in output; is set automatically / #: depends on used microphone geometry. numchannels = Delegate('mics', 'num_mics') #: :class:`~acoular.microphones.MicGeom` object that provides the microphone locations. mics = Trait(MicGeom, desc="microphone geometry") # --- List of backwards compatibility traits and their setters/getters ----------- # Microphone locations. # Deprecated! Use :attr:`mics` trait instead. mpos = Property() def _get_mpos(self): return self.mics def _set_mpos(self, mpos): warn("Deprecated use of 'mpos' trait. ", Warning, stacklevel=2) self.mics = mpos # --- End of backwards compatibility traits -------------------------------------- #: Start time of the signal in seconds, defaults to 0 s. start_t = Float(0.0, desc="signal start time") #: Start time of the data aquisition at microphones in seconds, #: defaults to 0 s. start = Float(0.0, desc="sample start time") #: Number of samples is set automatically / #: depends on :attr:`signal`. numsamples = Delegate('signal') #: Sampling frequency of the signal; is set automatically / #: depends on :attr:`signal`. sample_freq = Delegate('signal') # internal identifier digest = Property( depends_on = ['mics.digest', 'signal.rms', 'signal.numsamples', \ 'signal.sample_freq', 'signal.__class__' , 'seed', 'loc', \ 'start_t', 'start', '__class__'], ) @cached_property def _get_digest(self): return digest(self) def result(self, num=128): """ Python generator that yields the output at microphones block-wise. Parameters ---------- num : integer, defaults to 128 This parameter defines the size of the blocks to be yielded (i.e. the number of samples per block) . Returns ------- Samples in blocks of shape (num, numchannels). The last block may be shorter than num. """ Noise = self.signal.__class__ # create or get the array of random seeds if not self.seed: seed = arange(self.numchannels) + self.signal.seed elif self.seed.shape == (self.numchannels, ): seed = self.seed else: raise ValueError(\ "Seed array expected to be of shape (%i,), but has shape %s." \ % (self.numchannels, str(self.seed.shape)) ) # create array with [numchannels] noise signal tracks signal = array([Noise(seed = s, numsamples = self.numsamples, sample_freq = self.sample_freq, rms = self.signal.rms).signal() \ for s in seed]).T n = num while n <= self.numsamples: yield signal[n - num:n, :] n += num else: yield signal[n - num:, :]
class SphericalHarmonicSource(PointSource): """ Class to define a fixed Spherical Harmonic Source with an arbitrary signal. This can be used in simulations. The output is being generated via the :meth:`result` generator. """ #: Order of spherical harmonic source lOrder = Int(0, desc="Order of spherical harmonic") alpha = CArray( desc="coefficients of the (lOrder,) spherical harmonic mode") #: Vector to define the orientation of the SphericalHarmonic. direction = Tuple((1.0, 0.0, 0.0), desc="Spherical Harmonic orientation") prepadding = Enum('loop', desc="Behaviour for negative time indices.") # internal identifier digest = Property( depends_on = ['mics.digest', 'signal.digest', 'loc', \ 'env.digest', 'start_t', 'start', 'up', '__class__', 'alpha','lOrder', 'prepadding'], ) @cached_property def _get_digest(self): return digest(self) def transform(self, signals): Y_lm = get_modes(lOrder=self.lOrder, direction=self.direction, mpos=self.mics.mpos, sourceposition=array(self.loc)) return real(ifft(fft(signals, axis=0) * (Y_lm @ self.alpha), axis=0)) def result(self, num=128): """ Python generator that yields the output at microphones block-wise. Parameters ---------- num : integer, defaults to 128 This parameter defines the size of the blocks to be yielded (i.e. the number of samples per block) . Returns ------- Samples in blocks of shape (num, numchannels). The last block may be shorter than num. """ #If signal samples are needed for te < t_start, then samples are taken #from the end of the calculated signal. signal = self.signal.usignal(self.up) # emission time relative to start_t (in samples) for first sample rm = self.env._r(array(self.loc).reshape((3, 1)), self.mics.mpos) ind = (-rm / self.env.c - self.start_t + self.start) * self.sample_freq + pi / 30 i = 0 n = self.numsamples out = empty((num, self.numchannels)) while n: n -= 1 try: out[i] = signal[array(0.5 + ind * self.up, dtype=int64)] / rm ind += 1 i += 1 if i == num: yield self.transform(out) i = 0 except IndexError: #if no more samples available from the source break if i > 0: # if there are still samples to yield yield self.transform(out[:i])
class DataModuleFactory(ModuleFactory): """ Base class for all the module factories operating on data (ie not text and outline) """ reset_zoom = true(help="""Reset the zoom to accomodate the data newly added to the scene. Defaults to True.""") extent = CArray(shape=(6,), help="""[xmin, xmax, ymin, ymax, zmin, zmax] Default is the x, y, z arrays extent. Use this to change the extent of the object created.""", ) def _extent_changed(self): tools.set_extent(self._target, self.extent) transparent = false(help="""make the opacity of the actor depend on the scalar.""") def _transparent_changed(self): if self.transparent: data_range = \ self._target.module_manager.scalar_lut_manager.data_range self._target.module_manager.scalar_lut_manager.lut.alpha_range = \ (0.2, 0.8) data_range = ( numpy.mean(data_range) + 0.4 * ( data_range.max() - data_range.min()) * numpy.array([-1, 1])) self._target.module_manager.scalar_lut_manager.data_range = \ data_range colormap = Trait('blue-red', lut_mode_list(), help="""type of colormap to use.""") def _colormap_changed(self): colormap = self.colormap if colormap[-2:] == "_r": colormap = colormap[:-2] self._target.module_manager.scalar_lut_manager.reverse_lut = True self._target.module_manager.vector_lut_manager.reverse_lut = True self._target.module_manager.scalar_lut_manager.lut_mode = colormap self._target.module_manager.vector_lut_manager.lut_mode = colormap vmin = Trait(None, None, CFloat, help="""vmin is used to scale the colormap. If None, the min of the data will be used""") vmax = Trait(None, None, CFloat, help="""vmax is used to scale the colormap. If None, the max of the data will be used""") def _vmin_changed(self): if self.vmin == None and self.vmax == None: self._target.module_manager.scalar_lut_manager.use_default_range\ = True return self._target.module_manager.scalar_lut_manager.use_default_range \ = False vmin, vmax = \ self._target.module_manager.scalar_lut_manager.data_range if self.vmin is not None: vmin = self.vmin if self.vmax is not None: vmax = self.vmax self._target.module_manager.scalar_lut_manager.data_range = \ (vmin, vmax) _vmax_changed = _vmin_changed def __init__(self, *args, **kwargs): super(DataModuleFactory, self).__init__(*args, **kwargs) # We are adding data to the scene, reset the zoom: scene = self._scene.scene if scene is not None and self.reset_zoom: scene.reset_zoom()
class RotatingFlow(FlowField): """ Provides an analytical approximation of the flow field of a rotating fluid with constant flow. """ #: Exit velocity at jet origin, i.e. the nozzle. Defaults to 0. rpm = Float( 0.0, desc= "revolutions per minute of the virtual array; negative values for clockwise rotation" ) v0 = Float(0.0, desc="flow velocity") #: Location of the nozzle center, defaults to the co-ordinate origin. origin = CArray(dtype=float64, shape=(3, ), value=array((0., 0., 0.)), desc="center of nozzle") # internal identifier digest = Property(depends_on=['v0', 'origin', 'rpm'], ) # internal identifier omega = Property(depends_on=['rpm'], ) @cached_property def _get_omega(self): return 2 * pi * self.rpm / 60 @cached_property def _get_digest(self): return digest(self) def v(self, xx): """ Provides the rotating flow field around the z-Axis as a function of the location. Parameters ---------- xx : array of floats of shape (3, ) Location in the fluid for which to provide the data. Returns ------- tuple with two elements The first element in the tuple is the velocity vector and the second is the Jacobian of the velocity vector field, both at the given location. """ x, y, z = xx - self.origin # rotational speed omega = self.omega #velocity vector U = omega * y V = -omega * x W = self.v0 # flow field v = array((U, V, W)) # Jacobi matrix dv = array(((0, -omega, 0.), (omega, 0, 0.), (0., 0., 0.))).T return v, dv