示例#1
0
文件: sources.py 项目: linomp/acoular
class TimeSamples(SamplesGenerator):
    """
    Container for time data in `*.h5` format.
    
    This class loads measured data from h5 files and
    and provides information about this data.
    It also serves as an interface where the data can be accessed
    (e.g. for use in a block chain) via the :meth:`result` generator.
    """

    #: Full name of the .h5 file with data.
    name = File(filter=['*.h5'], desc="name of data file")

    #: Basename of the .h5 file with data, is set automatically.
    basename = Property(
        depends_on='name',  #filter=['*.h5'], 
        desc="basename of data file")

    #: Calibration data, instance of :class:`~acoular.calib.Calib` class, optional .
    calib = Trait(Calib, desc="Calibration data")

    #: Number of channels, is set automatically / read from file.
    numchannels = CLong(0, desc="number of input channels")

    #: Number of time data samples, is set automatically / read from file.
    numsamples = CLong(0, desc="number of samples")

    #: The time data as array of floats with dimension (numsamples, numchannels).
    data = Any(transient=True, desc="the actual time data array")

    #: HDF5 file object
    h5f = Instance(H5FileBase, transient=True)

    # Checksum over first data entries of all channels
    _datachecksum = Property()

    # internal identifier
    digest = Property(depends_on=['basename', 'calib.digest', '_datachecksum'])

    def _get__datachecksum(self):
        return self.data[0, :].sum()

    @cached_property
    def _get_digest(self):
        return digest(self)

    @cached_property
    def _get_basename(self):
        return path.splitext(path.basename(self.name))[0]

    @on_trait_change('basename')
    def load_data(self):
        """ 
        Open the .h5 file and set attributes.
        """
        if not path.isfile(self.name):
            # no file there
            self.numsamples = 0
            self.numchannels = 0
            self.sample_freq = 0
            raise IOError("No such file: %s" % self.name)
        if self.h5f != None:
            try:
                self.h5f.close()
            except IOError:
                pass
        file = _get_h5file_class()
        self.h5f = file(self.name)
        self.data = self.h5f.get_data_by_reference('time_data')
        self.sample_freq = self.h5f.get_node_attribute(self.data,
                                                       'sample_freq')
        (self.numsamples, self.numchannels) = self.data.shape

    def result(self, num=128):
        """
        Python generator that yields the output 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 self.numsamples == 0:
            raise IOError("no samples available")
        self._datachecksum  # trigger checksum calculation
        i = 0
        if self.calib:
            if self.calib.num_mics == self.numchannels:
                cal_factor = self.calib.data[newaxis]
            else:
                raise ValueError("calibration data not compatible: %i, %i" % \
                            (self.calib.num_mics, self.numchannels))
            while i < self.numsamples:
                yield self.data[i:i + num] * cal_factor
                i += num
        else:
            while i < self.numsamples:
                yield self.data[i:i + num]
                i += num
示例#2
0
文件: sources.py 项目: linomp/acoular
class MaskedTimeSamples(TimeSamples):
    """
    Container for time data in `*.h5` format.
    
    This class loads measured data from h5 files 
    and provides information about this data.
    It supports storing information about (in)valid samples and (in)valid channels
    It also serves as an interface where the data can be accessed
    (e.g. for use in a block chain) via the :meth:`result` generator.
    
    """

    #: Index of the first sample to be considered valid.
    start = CLong(0, desc="start of valid samples")

    #: Index of the last sample to be considered valid.
    stop = Trait(None, None, CLong, desc="stop of valid samples")

    #: Channels that are to be treated as invalid.
    invalid_channels = List(desc="list of invalid channels")

    #: Channel mask to serve as an index for all valid channels, is set automatically.
    channels = Property(depends_on=['invalid_channels', 'numchannels_total'],
                        desc="channel mask")

    #: Number of channels (including invalid channels), is set automatically.
    numchannels_total = CLong(0, desc="total number of input channels")

    #: Number of time data samples (including invalid samples), is set automatically.
    numsamples_total = CLong(0, desc="total number of samples per channel")

    #: Number of valid channels, is set automatically.
    numchannels = Property(depends_on = ['invalid_channels', \
        'numchannels_total'], desc="number of valid input channels")

    #: Number of valid time data samples, is set automatically.
    numsamples = Property(depends_on=['start', 'stop', 'numsamples_total'],
                          desc="number of valid samples per channel")

    # internal identifier
    digest = Property( depends_on = ['basename', 'start', 'stop', \
        'calib.digest', 'invalid_channels','_datachecksum'])

    @cached_property
    def _get_digest(self):
        return digest(self)

    @cached_property
    def _get_basename(self):
        return path.splitext(path.basename(self.name))[0]

    @cached_property
    def _get_channels(self):
        if len(self.invalid_channels) == 0:
            return slice(0, None, None)
        allr = [
            i for i in range(self.numchannels_total)
            if i not in self.invalid_channels
        ]
        return array(allr)

    @cached_property
    def _get_numchannels(self):
        if len(self.invalid_channels) == 0:
            return self.numchannels_total
        return len(self.channels)

    @cached_property
    def _get_numsamples(self):
        sli = slice(self.start, self.stop).indices(self.numsamples_total)
        return sli[1] - sli[0]

    @on_trait_change('basename')
    def load_data(self):
        #""" open the .h5 file and set attributes
        #"""
        if not path.isfile(self.name):
            # no file there
            self.numsamples_total = 0
            self.numchannels_total = 0
            self.sample_freq = 0
            raise IOError("No such file: %s" % self.name)
        if self.h5f != None:
            try:
                self.h5f.close()
            except IOError:
                pass
        file = _get_h5file_class()
        self.h5f = file(self.name)
        self.data = self.h5f.get_data_by_reference('time_data')
        self.sample_freq = self.h5f.get_node_attribute(self.data,
                                                       'sample_freq')
        (self.numsamples_total, self.numchannels_total) = self.data.shape

    def result(self, num=128):
        """
        Python generator that yields the output 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.
        """
        sli = slice(self.start, self.stop).indices(self.numsamples_total)
        i = sli[0]
        stop = sli[1]
        cal_factor = 1.0
        if i >= stop:
            raise IOError("no samples available")
        self._datachecksum  # trigger checksum calculation
        if self.calib:
            if self.calib.num_mics == self.numchannels_total:
                cal_factor = self.calib.data[self.channels][newaxis]
            elif self.calib.num_mics == self.numchannels:
                cal_factor = self.calib.data[newaxis]
            else:
                raise ValueError("calibration data not compatible: %i, %i" % \
                            (self.calib.num_mics, self.numchannels))
        while i < stop:
            yield self.data[i:min(i + num, stop)][:,
                                                  self.channels] * cal_factor
            i += num
示例#3
0
class MaskedTimeInOut ( TimeInOut ):
    """
    Signal processing block for channel and sample selection.
    
    This class serves as intermediary to define (in)valid 
    channels and samples for any 
    :class:`~acoular.sources.SamplesGenerator` (or derived) object.
    It gets samples from :attr:`~acoular.tprocess.TimeInOut.source` 
    and generates output via the generator :meth:`result`.
    """
        
    #: Index of the first sample to be considered valid.
    start = CLong(0, 
        desc="start of valid samples")
    
    #: Index of the last sample to be considered valid.
    stop = Trait(None, None, CLong, 
        desc="stop of valid samples")
    
    #: Channels that are to be treated as invalid.
    invalid_channels = List(
        desc="list of invalid channels")
    
    #: Channel mask to serve as an index for all valid channels, is set automatically.
    channels = Property(depends_on = ['invalid_channels', 'source.numchannels'], 
        desc="channel mask")
    
    #: Number of channels in input, as given by :attr:`~acoular.tprocess.TimeInOut.source`.
    numchannels_total = Delegate('source', 'numchannels')
               
    #: Number of samples in input, as given by :attr:`~acoular.tprocess.TimeInOut.source`.
    numsamples_total = Delegate('source', 'numsamples')

    #: Number of valid channels, is set automatically.
    numchannels = Property(depends_on = ['invalid_channels', \
        'source.numchannels'], desc="number of valid input channels")

    #: Number of valid time samples, is set automatically.
    numsamples = Property(depends_on = ['start', 'stop', 'source.numsamples'], 
        desc="number of valid samples per channel")

    #: Name of the cache file without extension, readonly.
    basename = Property( depends_on = 'source.digest', 
        desc="basename for cache file")

    # internal identifier
    digest = Property( depends_on = ['source.digest', 'start', 'stop', \
        'invalid_channels'])

    @cached_property
    def _get_digest( self ):
        return digest(self)

    @cached_property
    def _get_basename( self ):
        if 'basename' in self.source.all_trait_names():
            return self.source.basename
        else: 
            return self.source.__class__.__name__ + self.source.digest
    
    @cached_property
    def _get_channels( self ):
        if len(self.invalid_channels)==0:
            return slice(0, None, None)
        allr=[i for i in range(self.numchannels_total) if not (i in self.invalid_channels)]
        return array(allr)
    
    @cached_property
    def _get_numchannels( self ):
        if len(self.invalid_channels)==0:
            return self.numchannels_total
        return len(self.channels)
    
    @cached_property
    def _get_numsamples( self ):
        sli = slice(self.start, self.stop).indices(self.numsamples_total)
        return sli[1]-sli[0]

    def result(self, num):
        """ 
        Python generator that yields the output block-wise.
        
        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, :attr:`numchannels`). 
            The last block may be shorter than num.
        """
        sli = slice(self.start, self.stop).indices(self.numsamples_total)
        start = sli[0]
        stop = sli[1]
        if start >= stop:
            raise IOError("no samples available")
        
        if start != 0 or stop != self.numsamples_total:

            stopoff = -stop % num
            offset = -start % num
            if offset == 0: offset = num      
            buf = empty((num + offset , self.numchannels), dtype=float) # buffer array
            i = 0
            for block in self.source.result(num):
                i += num
                if i > start and i <= stop+stopoff:
                    ns = block.shape[0] # numbers of samples
                    buf[offset:offset+ns] = block[:, self.channels]
                    if i > start + num:
                        yield buf[:num]
                    buf[:offset] = buf[num:num+offset]
            if offset-stopoff != 0:
                yield buf[:(offset-stopoff)]
        
        else: # if no start/stop given, don't do the resorting thing
            for block in self.source.result(num):
                yield block[:, self.channels]
示例#4
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]
示例#5
0
class CoercibleLongTrait(HasTraits):
    value = CLong(LONG_TYPE(99))
示例#6
0
class GenericSignalGenerator(SignalGenerator):
    """
    Generate signal from output of :class:`~acoular.tprocess.SamplesGenerator` object.
    """
    #: Data source; :class:`~acoular.tprocess.SamplesGenerator` or derived object.
    source = Trait(SamplesGenerator)

    #: Sampling frequency of output signal, as given by :attr:`source`.
    sample_freq = Delegate('source')

    _numsamples = CLong(0)

    #: Number of samples to generate. Is set to source.numsamples by default.
    numsamples = Property()

    def _get_numsamples(self):
        if self._numsamples:
            return self._numsamples
        else:
            return self.source.numsamples

    def _set_numsamples(self, numsamples):
        self._numsamples = numsamples

    #: Boolean flag, if 'True' (default), signal track is repeated if requested
    #: :attr:`numsamples` is higher than available sample number
    loop_signal = Bool(True)

    # internal identifier
    digest = Property(
        depends_on = ['source.digest', 'loop_signal', 'numsamples', \
        'rms', '__class__'],
        )

    @cached_property
    def _get_digest(self):
        return digest(self)

    def signal(self):
        """
        Deliver the signal.

        Returns
        -------
        array of floats
            The resulting signal as an array of length :attr:`~GenericSignalGenerator.numsamples`.
        """
        block = 1024
        if self.source.numchannels > 1:
            warn(
                "Signal source has more than one channel. Only channel 0 will be used for signal.",
                Warning,
                stacklevel=2)
        nums = self.numsamples
        track = zeros(nums)

        # iterate through source generator to fill signal track
        for i, temp in enumerate(self.source.result(block)):
            start = block * i
            stop = start + len(temp[:, 0])
            if nums > stop:
                track[start:stop] = temp[:, 0]
            else:  # exit loop preliminarily if wanted signal samples are reached
                track[start:nums] = temp[:nums - start, 0]
                break

        # if the signal should be repeated after finishing and there are still samples open
        if self.loop_signal and (nums > stop):

            # fill up empty track with as many full source signals as possible
            nloops = nums // stop
            if nloops > 1:
                track[stop:stop * nloops] = tile(track[:stop], nloops - 1)
            # fill up remaining empty track
            res = nums % stop  # last part of unfinished loop
            if res > 0: track[stop * nloops:] = track[:res]

        # The rms value is just an amplification here
        return self.rms * track