Exemplo n.º 1
0
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:]
Exemplo n.º 2
0
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, ())
Exemplo n.º 3
0
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
Exemplo n.º 4
0
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
Exemplo n.º 5
0
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
Exemplo n.º 6
0
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
        ]
Exemplo n.º 7
0
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)
Exemplo n.º 8
0
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
Exemplo n.º 9
0
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"""
Exemplo n.º 10
0
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
Exemplo n.º 11
0
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)
Exemplo n.º 12
0
# 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.
Exemplo n.º 13
0
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]
Exemplo n.º 14
0
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)
Exemplo n.º 15
0
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]
Exemplo n.º 16
0
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]
Exemplo n.º 17
0
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)
Exemplo n.º 18
0
 def validate(self, object, name, value):
     if operator.isNumberType(value):
         value = np.atleast_1d(value)
     return CArray.validate(self, object, name, value)
Exemplo n.º 19
0
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))
Exemplo n.º 20
0
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
Exemplo n.º 21
0
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]
Exemplo n.º 22
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
Exemplo n.º 23
0
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
Exemplo n.º 24
0
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]
Exemplo n.º 25
0
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]
Exemplo n.º 26
0
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
        ]
Exemplo n.º 27
0
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:, :]
Exemplo n.º 28
0
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])
Exemplo n.º 29
0
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()
Exemplo n.º 30
0
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