]> git.parisson.com Git - timeside.git/commitdiff
refactor(analyzer.core): refactor AnalyzerResult
authorThomas Fillon <thomas@parisson.com>
Mon, 23 Jun 2014 22:47:14 +0000 (00:47 +0200)
committerThomas Fillon <thomas@parisson.com>
Mon, 23 Jun 2014 22:47:14 +0000 (00:47 +0200)
- Put frame_metadata and label_metadata inside data_object
- No more subclasses for analyzerResult
- Put subclasses in data_object (subclasses of DataObject: one per (data_mode, time_mode) combination
- Add 'y_value' and 'y_unit' key for data_object (to deal with 2D array where the y-value is a data and may have a unit like spectrogram (y_value = frequnecies, y_unit=Hz))

tests/test_AnalyzerResult.py
timeside/analyzer/aubio/aubio_temporal.py
timeside/analyzer/core.py
timeside/analyzer/irit_monopoly.py
timeside/analyzer/irit_speech_4hz.py
timeside/analyzer/irit_speech_entropy.py
timeside/analyzer/yaafe.py
timeside/grapher/render_analyzers.py

index 3a4b044aa0be3549a7c344881f8b871877b65971..d081f6212e3f9df026b599d8bca40f7c0b69d48d 100755 (executable)
@@ -14,7 +14,7 @@ class TestAnalyzerResult(unittest.TestCase):
     """ test AnalyzerResult """
 
     def setUp(self):
-        self.result = AnalyzerResult.factory(data_mode='value',
+        self.result = AnalyzerResult(data_mode='value',
                                              time_mode='framewise')
 
         from datetime import datetime
@@ -190,7 +190,10 @@ class TestAnalyzerResultYaml(TestAnalyzerResultGoodType):
             print '%15s' % 'from yaml:',
             print d_yaml
         #for i in range(len(d_yaml)):
-        self.assertEqual(results, d_yaml)
+        for res in results:
+            for key in res:
+                self.assertEqual(results[res][key],
+                                     d_yaml[res][key])
 
 
 class TestAnalyzerResultXml(TestAnalyzerResultGoodType):
@@ -229,7 +232,10 @@ class TestAnalyzerResultJson(TestAnalyzerResultGoodType):
             print '%15s' % 'from json:',
 
         #for i in range(len(d_json)):
-        self.assertEqual(d_json, results)
+        for res in results:
+            for key in res:
+                self.assertEqual(results[res][key],
+                                     d_json[res][key])
 
 
 class TestAnalyzerResultAsDict(TestAnalyzerResultGoodType):
index 6c9f697732e15fa5fc737295d5aa4e5323091b50..45426fff1d17660212ef3d169c49e64c7981408d 100644 (file)
@@ -96,7 +96,7 @@ class AubioTemporal(Analyzer):
         onsets.id_metadata.unit = 's'
         onsets.data_object.time = self.onsets
         onsets.data_object.label = numpy.ones(len(self.onsets))
-        onsets.label_metadata.label = {1: 'Onset'}
+        onsets.data_object.label_metadata.label = {1: 'Onset'}
 
         self.process_pipe.results.add(onsets)
 
@@ -128,7 +128,7 @@ class AubioTemporal(Analyzer):
         beats.id_metadata.unit = "s"
         beats.data_object.time = self.beats
         beats.data_object.label = numpy.ones(len(self.beats))
-        beats.label_metadata.label = {1: 'Beat'}
+        beats.data_object.label_metadata.label = {1: 'Beat'}
 
         self.process_pipe.results.add(beats)
 
index c0c17afdbfb508467feb949ebbe582d763f78703..d1ba38cc50fb7c3f2953effb1ec95b288879afbe 100644 (file)
@@ -117,7 +117,11 @@ class MetadataObject(object):
         if name not in self._default_value.keys():
             raise AttributeError("%s is not a valid attribute in %s" %
                                  (name, self.__class__.__name__))
-        super(MetadataObject, self).__setattr__(name, value)
+        try:
+            super(MetadataObject, self).__setattr__(name, value)
+        except AttributeError:
+            print name, value
+            raise AttributeError
 
     def __delattr__(self, name):
         if name in self._default_value.keys():
@@ -374,7 +378,7 @@ class DataObject(MetadataObject):
                     'Result Data can not accept type %s for %s' %
                     (value.dtype.type, name))
 
-        elif name in ['time', 'duration']:
+        elif name in ['time', 'duration', 'y_value', 'y_unit']:
             try:
                 value = numpy.asfarray(value)
             except ValueError:
@@ -387,6 +391,7 @@ class DataObject(MetadataObject):
         super(DataObject, self).__setattr__(name, value)
 
     def __eq__(self, other):
+        # TODO fix this
         try:
             return (isinstance(other, self.__class__) and
                     all([numpy.array_equal(self[key], other[key])
@@ -401,6 +406,14 @@ class DataObject(MetadataObject):
                    any([numpy.array_equal(self[key], other[key])
                         for key in self.keys()]))
 
+    def as_dict(self):
+        as_dict = super(DataObject, self).as_dict()
+        for key in ['frame_metadata', 'label_metadata']:
+            if key in as_dict and not isinstance(as_dict[key], dict):
+                as_dict[key] = as_dict[key].as_dict()
+
+        return as_dict
+
     def to_xml(self):
         import xml.etree.ElementTree as ET
         root = ET.Element('Metadata')
@@ -408,7 +421,9 @@ class DataObject(MetadataObject):
         for key in self.keys():
             child = ET.SubElement(root, key)
             value = getattr(self, key)
-            if value not in [None, []]:
+            if hasattr(value, 'to_xml'):
+                child = value.to_xml()
+            elif value not in [None, []]:
                 child.text = repr(value.tolist())
                 child.set('dtype', value.dtype.__str__())
 
@@ -429,7 +444,10 @@ class DataObject(MetadataObject):
         for key in self.keys():
             if self.__getattribute__(key) is None:
                 continue
-            if self.__getattribute__(key).dtype == 'object':
+            if hasattr(self.__getattribute__(key), 'to_hdf5'):
+                subgroup = h5group.create_group(key)
+                self.__getattribute__(key).to_hdf5(subgroup)
+            elif self.__getattribute__(key).dtype == 'object':
                 # Handle numpy type = object as vlen string
                 h5group.create_dataset(key,
                                        data=self.__getattribute__(
@@ -445,6 +463,9 @@ class DataObject(MetadataObject):
 
     def from_hdf5(self, h5group):
         for key, dataset in h5group.items():
+            if isinstance(dataset, h5py.Group):
+                self[key].from_hdf5(dataset)
+                continue
             # Load value from the hdf5 dataset and store in data
             # FIXME : the following conditional statement is to prevent
             # reading an empty dataset.
@@ -491,6 +512,25 @@ class AnalyzerParameters(dict):
         h5tools.dict_from_hdf5(self, h5group)
 
 
+def data_objet_class(data_mode='value', time_mode='framewise'):
+    """
+    Factory function for Analyzer result
+    """
+    classes_table = {('value', 'global'): GlobalValueObject,
+                     ('value', 'event'): EventValueObject,
+                     ('value', 'segment'): SegmentValueObject,
+                     ('value', 'framewise'): FrameValueObject,
+                     ('label', 'global'): GlobalLabelObject,
+                     ('label', 'event'): EventLabelObject,
+                     ('label', 'segment'): SegmentLabelObject,
+                     ('label', 'framewise'): FrameLabelObject}
+
+    try:
+        return classes_table[(data_mode, time_mode)]
+    except KeyError as e:
+        raise ValueError('Wrong arguments')
+
+
 class AnalyzerResult(MetadataObject):
 
     """
@@ -525,34 +565,21 @@ class AnalyzerResult(MetadataObject):
     _default_value = OrderedDict([('id_metadata', None),
                                   ('data_object', None),
                                   ('audio_metadata', None),
-                                  ('frame_metadata', None),
-                                  ('label_metadata', None),
                                   ('parameters', None)
                                   ])
 
-    def __init__(self, data_mode=None, time_mode=None):
+    def __init__(self, data_mode='value', time_mode='framewise'):
         super(AnalyzerResult, self).__init__()
 
+        self._data_mode = data_mode
+        self._time_mode = time_mode
         self.id_metadata = IdMetadata()
-        self.data_object = DataObject()
         self.audio_metadata = AudioMetadata()
-        self.frame_metadata = FrameMetadata()
-        self.label_metadata = LabelMetadata()
         self.parameters = AnalyzerParameters()
+        self.data_object = data_objet_class(data_mode, time_mode)()
+
+#        self.label_metadata = LabelMetadata()
 
-    @staticmethod
-    def factory(data_mode='value', time_mode='framewise'):
-        """
-        Factory function for Analyzer result
-        """
-        for result_cls in AnalyzerResult.__subclasses__():
-            if (hasattr(result_cls, '_time_mode') and
-                hasattr(result_cls, '_data_mode') and
-                (result_cls._data_mode, result_cls._time_mode) == (data_mode,
-                                                                   time_mode)):
-                return result_cls()
-        print data_mode, time_mode
-        raise ValueError('Wrong arguments')
 
     def __setattr__(self, name, value):
         if name in ['_data_mode', '_time_mode']:
@@ -605,7 +632,7 @@ class AnalyzerResult(MetadataObject):
 
         data_mode_child = root.find('data_mode')
         time_mode_child = root.find('time_mode')
-        result = AnalyzerResult.factory(data_mode=data_mode_child.text,
+        result = AnalyzerResult(data_mode=data_mode_child.text,
                                         time_mode=time_mode_child.text)
         for child in root:
             key = child.tag
@@ -629,7 +656,7 @@ class AnalyzerResult(MetadataObject):
     @staticmethod
     def from_hdf5(h5group):
         # Read Sub-Group
-        result = AnalyzerResult.factory(data_mode=h5group.attrs['data_mode'],
+        result = AnalyzerResult(data_mode=h5group.attrs['data_mode'],
                                         time_mode=h5group.attrs['time_mode'])
         for subgroup_name, h5subgroup in h5group.items():
             result[subgroup_name].from_hdf5(h5subgroup)
@@ -686,15 +713,21 @@ class AnalyzerResult(MetadataObject):
 
     @property
     def data(self):
-        raise NotImplementedError
+        return self.data_object.data
 
     @property
     def time(self):
-        raise NotImplementedError
+        if self._time_mode == 'global':
+            return self.audio_metadata.start
+        else:
+            return self.audio_metadata.start + self.data_object.time
 
     @property
     def duration(self):
-        raise NotImplementedError
+        if self._time_mode == 'global':
+            return self.audio_metadata.duration
+        else:
+            return self.data_object.duration
 
     @property
     def id(self):
@@ -709,17 +742,11 @@ class AnalyzerResult(MetadataObject):
         return self.id_metadata.unit
 
 
-class ValueObject(object):
-    _data_mode = 'value'
-
-    def __init__(self):
-        super(ValueObject, self).__init__()
-        del self.data_object.label
-        del self.label_metadata
+class ValueObject(DataObject):
 
     @property
     def data(self):
-        return self.data_object.value
+        return self.value
 
     @property
     def properties(self):
@@ -732,26 +759,18 @@ class ValueObject(object):
                     )
 
 
-class LabelObject(object):
-    _data_mode = 'label'
+class LabelObject(DataObject):
 
     def __init__(self):
         super(LabelObject, self).__init__()
-        del self.data_object.value
+        self.label_metadata = LabelMetadata()
 
     @property
     def data(self):
-        return self.data_object.label
-
+        return self.label
 
-class GlobalObject(object):
-    _time_mode = 'global'
 
-    def __init__(self):
-        super(GlobalObject, self).__init__()
-        del self.frame_metadata
-        del self.data_object.time
-        del self.data_object.duration
+class GlobalObject(DataObject):
 
     @property
     def time(self):
@@ -762,88 +781,94 @@ class GlobalObject(object):
         return self.audio_metadata.duration
 
 
-class FramewiseObject(object):
-    _time_mode = 'framewise'
+class FramewiseObject(DataObject):
 
     def __init__(self):
         super(FramewiseObject, self).__init__()
-        del self.data_object.time
-        del self.data_object.duration
+        self.frame_metadata = FrameMetadata()
 
     @property
     def time(self):
-        return (self.audio_metadata.start +
-                self.frame_metadata.stepsize /
-                self.frame_metadata.samplerate *
-                numpy.arange(0, len(self)))
+        return (numpy.arange(0, len(self.data)*self.frame_metadata.stepsize,
+                             self.frame_metadata.stepsize) /
+                self.frame_metadata.samplerate)
 
     @property
     def duration(self):
         return (self.frame_metadata.blocksize / self.frame_metadata.samplerate
-                * numpy.ones(len(self)))
+                * numpy.ones(len(self.data)))
 
 
-class EventObject(object):
-    _time_mode = 'event'
-
-    def __init__(self):
-        super(EventObject, self).__init__()
-        del self.frame_metadata
-        del self.data_object.duration
-
-    @property
-    def time(self):
-        return self.audio_metadata.start + self.data_object.time
+class EventObject(DataObject):
 
     @property
     def duration(self):
-        return numpy.zeros(len(self))
+        return numpy.zeros(len(self.data))
 
     def _render_plot(self, ax):
         ax.stem(self.time, self.data)
 
 
-class SegmentObject(EventObject):
-    _time_mode = 'segment'
-
-    def __init__(self):
-        super(EventObject, self).__init__()
-        del self.frame_metadata
-
-    @property
-    def duration(self):
-        return self.data_object.duration
+class SegmentObject(DataObject):
+    pass
 
 
-class GlobalValueResult(ValueObject, GlobalObject, AnalyzerResult):
-    pass
+class GlobalValueObject(ValueObject, GlobalObject):
+    # Define default values
+    _default_value = OrderedDict([('value', None),
+                                  ('y_value', None),
+                                  ('y_unit', None)])
 
 
-class GlobalLabelResult(LabelObject, GlobalObject, AnalyzerResult):
-    pass
+class GlobalLabelObject(LabelObject, GlobalObject):
+    # Define default values
+    _default_value = OrderedDict([('label', None),
+                                  ('label_metadata', None)])
 
 
-class FrameValueResult(ValueObject, FramewiseObject, AnalyzerResult):
+class FrameValueObject(ValueObject, FramewiseObject):
+    # Define default values
+    _default_value = OrderedDict([('value', None),
+                                  ('y_value', None),
+                                  ('y_unit', None),
+                                  ('frame_metadata', None)])
 
     def _render_plot(self, ax):
         ax.plot(self.time, self.data)
 
 
-class FrameLabelResult(LabelObject, FramewiseObject, AnalyzerResult):
+class FrameLabelObject(LabelObject, FramewiseObject):
+    # Define default values
+    _default_value = OrderedDict([('label', None),
+                                  ('label_metadata', None),
+                                  ('frame_metadata', None)])
 
     def _render_plot(self, ax):
         pass
 
 
-class EventValueResult(ValueObject, EventObject, AnalyzerResult):
-    pass
+class EventValueObject(ValueObject, EventObject):
+    # Define default values
+    _default_value = OrderedDict([('value', None),
+                                  ('y_value', None),
+                                  ('y_unit', None),
+                                  ('time', None)])
 
 
-class EventLabelResult(LabelObject, EventObject, AnalyzerResult):
-    pass
+class EventLabelObject(LabelObject, EventObject, DataObject):
+    # Define default values
+    _default_value = OrderedDict([('label', None),
+                                  ('label_metadata', None),
+                                  ('time', None)])
 
 
-class SegmentValueResult(ValueObject, SegmentObject, AnalyzerResult):
+class SegmentValueObject(ValueObject, SegmentObject):
+    # Define default values
+    _default_value = OrderedDict([('value', None),
+                                  ('y_value', None),
+                                  ('y_unit', None),
+                                  ('time', None),
+                                  ('duration', None)])
 
     def _render_plot(self, ax):
         for time, value in (self.time, self.data):
@@ -851,7 +876,12 @@ class SegmentValueResult(ValueObject, SegmentObject, AnalyzerResult):
             # TODO : check value shape !!!
 
 
-class SegmentLabelResult(LabelObject, SegmentObject, AnalyzerResult):
+class SegmentLabelObject(LabelObject, SegmentObject):
+    # Define default values
+    _default_value = OrderedDict([('label', None),
+                                  ('label_metadata', None),
+                                  ('time', None),
+                                  ('duration', None)])
 
     def _render_plot(self, ax):
         import itertools
@@ -874,7 +904,7 @@ class AnalyzerResultContainer(dict):
     >>> a = Analyzer()
     >>> (d|a).run()
     >>> a.new_result() #doctest: +ELLIPSIS
-    FrameValueResult(id_metadata=IdMetadata(id='analyzer', name='Generic analyzer', unit='', description='', date='...', version='...', author='TimeSide', uuid='...'), data_object=DataObject(value=array([], dtype=float64)), audio_metadata=AudioMetadata(uri='...', start=0.0, duration=8.0..., is_segment=False, sha1='...', channels=2, channelsManagement=''), frame_metadata=FrameMetadata(samplerate=44100, blocksize=8192, stepsize=8192), parameters={})
+    AnalyzerResult(id_metadata=IdMetadata(id='analyzer', name='Generic analyzer', unit='', description='', date='...', version='...', author='TimeSide', uuid='...'), data_object=FrameValueObject(value=array([], dtype=float64), y_value=array([], dtype=float64), y_unit=array([], dtype=float64), frame_metadata=FrameMetadata(samplerate=44100, blocksize=8192, stepsize=8192)), audio_metadata=AudioMetadata(uri='...', start=0.0, duration=8.0..., is_segment=False, sha1='...', channels=2, channelsManagement=''), parameters={})
     >>> resContainer = timeside.analyzer.core.AnalyzerResultContainer()
     '''
 
@@ -965,7 +995,7 @@ class AnalyzerResultContainer(dict):
         results = AnalyzerResultContainer()
         for res_json in results_json:
 
-            res = AnalyzerResult.factory(data_mode=res_json['data_mode'],
+            res = AnalyzerResult(data_mode=res_json['data_mode'],
                                          time_mode=res_json['time_mode'])
             for key in res_json.keys():
                 if key not in ['data_mode', 'time_mode']:
@@ -1006,7 +1036,7 @@ class AnalyzerResultContainer(dict):
         results_yaml = yaml.load(yaml_str)
         results = AnalyzerResultContainer()
         for res_yaml in results_yaml:
-            res = AnalyzerResult.factory(data_mode=res_yaml['data_mode'],
+            res = AnalyzerResult(data_mode=res_yaml['data_mode'],
                                          time_mode=res_yaml['time_mode'])
             for key in res_yaml.keys():
                 if key not in ['data_mode', 'time_mode']:
@@ -1108,7 +1138,7 @@ class Analyzer(Processor):
 
         from datetime import datetime
 
-        result = AnalyzerResult.factory(data_mode=data_mode,
+        result = AnalyzerResult(data_mode=data_mode,
                                         time_mode=time_mode)
 
         # Automatically write known metadata
@@ -1129,9 +1159,9 @@ class Analyzer(Processor):
         result.audio_metadata.channels = self.channels()
 
         if time_mode == 'framewise':
-            result.frame_metadata.samplerate = self.result_samplerate
-            result.frame_metadata.blocksize = self.result_blocksize
-            result.frame_metadata.stepsize = self.result_stepsize
+            result.data_object.frame_metadata.samplerate = self.result_samplerate
+            result.data_object.frame_metadata.blocksize = self.result_blocksize
+            result.data_object.frame_metadata.stepsize = self.result_stepsize
 
         return result
 
index 2afe821b9744873858a58a7ab508adc56b26c8e5..b1f82c22a0f2718336401347d27cf4da13bd6ebc 100644 (file)
@@ -118,7 +118,7 @@ class IRITMonopoly(Analyzer):
         segs.id_metadata.id += '.' + 'segments'
         segs.id_metadata.name += ' ' + 'Segments'
 
-        segs.label_metadata.label = label
+        segs.data_object.label_metadata.label = label
         segs.data_object.label = [convert[s[2]] for s in segList]
         segs.data_object.time = [(float(s[0]+0.5) * self.decisionLen)
                                  for s in segList]
index 73579f96a22367267571a8b13d05dcb6592c0d34..b6df6cf55b83af5876aa621048503aa07403b063 100644 (file)
@@ -152,7 +152,7 @@ class IRITSpeech4Hz(Analyzer):
         segs.id_metadata.id += '.' + 'segments'
         segs.id_metadata.name += ' ' + 'Segments'
 
-        segs.label_metadata.label = label
+        segs.data_object.label_metadata.label = label
 
         segs.data_object.label = [convert[s[2]] for s in segList]
         segs.data_object.time = [(float(s[0]) * self.blocksize() /
index febf1a4ddab24eb4a73ac741cefcc80a1488fa83..b33f7bfd704e342985fc0e181535b0a512231ac2 100644 (file)
@@ -95,7 +95,7 @@ class IRITSpeechEntropy(Analyzer):
         segs.id_metadata.id += '.' + 'segments'
         segs.id_metadata.name += ' ' + 'Segments'
 
-        segs.label_metadata.label = label
+        segs.data_object.label_metadata.label = label
 
         segs.data_object.label = [convert[s[2]] for s in segList]
         segs.data_object.time = [(float(s[0]) * self.blocksize() /
index 9f5c91968631681448fd1e5e2e42393b0d5c71e4..5106990cd2925f22d60d53da1248a909c08942ba 100644 (file)
@@ -114,9 +114,9 @@ class Yaafe(Analyzer):
             result.data_object.value = self.yaafe_engine.readOutput(featName)
 
             yaafe_metadata = self.yaafe_engine.getOutputs()[featName]
-            result.frame_metadata.blocksize = yaafe_metadata['frameLength']
-            result.frame_metadata.stepsize = yaafe_metadata['sampleStep']
-            result.frame_metadata.samplerate = yaafe_metadata['sampleRate']
+            result.data_object.frame_metadata.blocksize = yaafe_metadata['frameLength']
+            result.data_object.frame_metadata.stepsize = yaafe_metadata['sampleStep']
+            result.data_object.frame_metadata.samplerate = yaafe_metadata['sampleRate']
 
             # Store results in Container
             if len(result.data_object.value):
index 3ba9414dfad2b0faa506b379b341e7cdd801ed7e..627a0436546cea1edb2f17c991d23c2ace3e139f 100644 (file)
@@ -93,6 +93,12 @@ class DisplayAnalyzer(Grapher):
                     bg_analyzer = get_processor('waveform_analyzer')()
                     self._bg_id = bg_analyzer.id()
                     self.parents.append(bg_analyzer)
+                elif background == 'spectrogram':
+                    self._background = True
+                    bg_analyzer = get_processor('spectrogram_analyzer')()
+                    self._bg_id = bg_analyzer.id()
+                    self.parents.append(bg_analyzer)
+
                 else:
                     self._background = None
 
@@ -124,7 +130,8 @@ try:
         analyzer=aubiopitch,
         result_id='aubio_pitch.pitch',
         grapher_id='grapher_aubio_pitch',
-        grapher_name='Aubio Pitch')
+        grapher_name='Aubio Pitch',
+        background='spectrogram')
 except PIDError:
     pass