예제 #1
0
파일: sb2guano.py 프로젝트: riggsd/guano-py
def sonobat2guano(fname):
    """Convert a file with Sonobat metadata to GUANO metadata"""
    print '\n', fname
    sb_md = extract_sonobat_metadata(fname)
    if not sb_md:
        print >> sys.stderr, 'Skipping non-Sonobat file: ' + fname
        return False
    pprint(sb_md)

    gfile = GuanoFile(fname)
    gfile['GUANO|Version'] = 1.0
    if 'timestamp' in sb_md:
        gfile['Timestamp'] = sb_md['timestamp']
    if sb_md.get('te', 1) != 1:
        gfile['TE'] = sb_md['te']
    gfile['Length'] = sb_md['length']
    gfile['Note'] = sb_md['note'].strip().replace('\r\n', '\\n').replace('\n', '\\n')
    if sb_md.get('species', None):
        gfile['Species Auto ID'] = sb_md['species']

    if 'd500x' in sb_md:
        for k, v in sb_md['d500x'].items():
            gfile['PET', k] = v

    if 'ar125' in sb_md:
        for k, v in sb_md['ar125'].items():
            gfile['BAT', k] = v

    print gfile._as_string()

    gfile.write()
    def save(self):
        fname = self.ui.file_name.text()
        try:
            g = GuanoFile(fname)
        except:
            msg = f"There was a problem loading the Guano MD from:\n{fname}\n\nPlease verify that it is a valid wav file"
            QMessageBox.warning(self, "File Error", msg)
            return None

        for namespace_name, namespace_group in self.namespaces.items():
            print(namespace_name)
            namespace_data = namespace_group.get_data()

            if namespace_name == 'guano_base':
                namespace_name = ''

            for k, v in namespace_data.items():
                if v == '':
                    try:
                        del g[f"{namespace_name}|{k}"]
                    except KeyError:
                        pass
                else:
                    g[f"{namespace_name}|{k}"] = v

        g.write(make_backup=False)
예제 #3
0
def wamd2guano(fname):
    """Convert a Wildlife Acoustics WAMD metadata file to GUANO metadata format"""
    wamd_md = wamd(fname)
    pprint(wamd_md)

    gfile = GuanoFile(fname)
    gfile['GUANO|Version'] = 1.0

    gfile['Timestamp'] = wamd_md.pop('timestamp')
    gfile['Note'] = wamd_md.pop('notes', '')

    gfile['Make'] = 'Wildlife Acoustics'
    gfile['Model'] = wamd_md.pop('model', '')
    gfile['Firmware Version'] = wamd_md.pop('firmware', '')

    gfile['Species Auto ID'] = wamd_md.pop('auto_id', '')
    gfile['Species Manual ID'] = wamd_md.pop('manual_id', '')

    gfile['TE'] = wamd_md.pop('time_expansion', 1)
    gfile['Samplerate'] = gfile.wav_params.framerate * gfile['TE']
    gfile['Length'] = gfile.wav_params.nframes / float(gfile.wav_params.framerate) * gfile['TE']

    if 'gpsfirst' in wamd_md:
        lat, lon, alt = wamd_md.pop('gpsfirst')
        gfile['Loc Position'] = lat, lon
        gfile['Loc Elevation'] = alt

    for k, v in wamd_md.items():
        gfile['WA', k] = v

    print(gfile.to_string())

    gfile.write()
예제 #4
0
def batlogger2guano(fname):
    """Convert an Elekon BatLogger .WAV with sidecar .XML to GUANO metadata"""
    xmlfname = os.path.splitext(fname)[0] + '.xml'
    if not os.path.exists(xmlfname):
        raise ValueError('Unable to find XML metadata file for %s' % fname)
    g = GuanoFile(fname)
    with open(xmlfname, 'rt') as f:
        xml = ElementTree.parse(f)

    g['Timestamp'] = get(xml, 'DateTime', lambda x: datetime.strptime(x, '%d.%m.%Y %H:%M:%S'))
    g['Firmware Version'] = get(xml, 'Firmware')
    g['Make'] = 'Elekon'
    g['Model'] = 'BatLogger'
    g['Serial'] = get(xml, 'SN')
    g['Samplerate'] = get(xml, 'Samplerate', lambda x: int(x.split()[0]))
    g['Length'] = get(xml, 'Duration', lambda x: float(x.split()[0]))
    g['Original Filename'] = get(xml, 'Filename')
    g['Temperature Ext'] = get(xml, 'Temperature', lambda x: float(x.split()[0]))
    g['Loc Position'] = get(xml, 'GPS/Position', lambda x: tuple(map(float, x.split())))
    g['Loc Elevation'] = get(xml, 'GPS/Altitude', lambda x: float(x.split()[0]))

    g['Elekon|BattVoltage'] = get(xml, 'BattVoltage')
    for node in xml.find('Trigger'):
        g['Elekon|Trigger|%s' % node.tag] = node.text
    for node in xml.find('GPS'):
        g['Elekon|GPS|%s' % node.tag] = node.text

    # for k, v in g.items():
    #     print('%s:\t%s' % (k, v))

    print(g.to_string())
    g.write()
    os.remove(xmlfname)

    return g
예제 #5
0
def wamd2guano(fname):
    """Convert a Wildlife Acoustics WAMD metadata file to GUANO metadata format"""
    wamd_md = wamd(fname)
    pprint(wamd_md)

    gfile = GuanoFile(fname)
    gfile['GUANO|Version'] = 1.0

    gfile['Timestamp'] = wamd_md.pop('timestamp')
    gfile['Note'] = wamd_md.pop('notes', '')

    gfile['Make'] = 'Wildlife Acoustics'
    gfile['Model'] = wamd_md.pop('model', '')
    gfile['Firmware Version'] = wamd_md.pop('firmware', '')

    gfile['Species Auto ID'] = wamd_md.pop('auto_id', '')
    gfile['Species Manual ID'] = wamd_md.pop('manual_id', '')

    gfile['TE'] = wamd_md.pop('time_expansion', 1)
    gfile['Samplerate'] = gfile.wav_params.framerate * gfile['TE']
    gfile['Length'] = gfile.wav_params.nframes / float(gfile.wav_params.framerate) * gfile['TE']

    if 'gpsfirst' in wamd_md:
        lat, lon, alt = wamd_md.pop('gpsfirst')
        gfile['Loc Position'] = lat, lon
        gfile['Loc Elevation'] = alt

    for k, v in wamd_md.items():
        gfile['WAC', k] = v

    print gfile._as_string()

    gfile.write()
예제 #6
0
def sonobat2guano(fname):
    """Convert a file with Sonobat metadata to GUANO metadata"""
    print('\n', fname)
    sb_md = extract_sonobat_metadata(fname)
    if not sb_md:
        print('Skipping non-Sonobat file: ' + fname, file=sys.stderr)
        return False
    pprint(sb_md)

    gfile = GuanoFile(fname)
    gfile['GUANO|Version'] = 1.0
    if 'timestamp' in sb_md:
        gfile['Timestamp'] = sb_md['timestamp']
    if sb_md.get('te', 1) != 1:
        gfile['TE'] = sb_md['te']
    gfile['Length'] = sb_md['length']
    gfile['Note'] = sb_md['note'].strip().replace('\r\n',
                                                  '\\n').replace('\n', '\\n')
    if sb_md.get('species', None):
        gfile['Species Auto ID'] = sb_md['species']

    if 'd500x' in sb_md:
        for k, v in sb_md['d500x'].items():
            gfile['PET', k] = v

    if 'ar125' in sb_md:
        for k, v in sb_md['ar125'].items():
            gfile['BAT', k] = v

    print(gfile.to_string())

    gfile.write()
예제 #7
0
def update_single_md(fname, guano_md={}, project_md={}, site_md={}):
    g = GuanoFile(fname)

    parts = parse_nabat_fname(fname)
    # content from the NABat filename
    g['NABat', 'Grid Cell GRTS ID'] = parts['GrtsId']
    g['NABat', 'Site Name'] = parts['SiteName']

    #content from the site_md
    if guano_md is None:
        g['GUANO', 'time_to_time'] = str(parts['datetime'])
    else:
        for k, v in guano_md.items():
            g['GUANO', k] = v

    #content from the project_md
    if project_md is not None:
        for k, v in project_md.items():
            g['NABat', k] = v

    #content from the site_md
    if site_md is not None:
        for k, v in site_md.items():
            g['NABat', k] = v

    g.write(make_backup=False)
    def load_file(self):
        for i in reversed(range(self.namespace_layout.count() - 1)):
            self.namespace_layout.itemAt(i).widget().setParent(None)

        for namespce_chk in self.namespace_chks.values():
            namespce_chk.setChecked(False)

        self.namespaces = OrderedDict()

        fname = self.ui.file_name.text()
        f = Path(fname)
        try:
            exists = f.exists()
        except:
            exists = False

        if exists:
            try:
                g = GuanoFile(fname)
            except:
                msg = f"There was a problem loading the Guano MD from:\n{fname}\n\nPlease verify that it is a valid wav file"
                QMessageBox.warning(self, "File Error", msg)
                return None

            self.guano_content = {key: {} for key in g.get_namespaces()}

            for item in g.items_namespaced():
                self.guano_content[item[0]][item[1]] = item[2]

            for namespace in g.get_namespaces():

                if namespace == '':
                    namespace = 'guano_base'
                namespace_fname = utils.resource_path(
                    f"../resources/specs/{namespace}.csv")

                if Path(namespace_fname).exists():
                    spec = utils.read_namespace(namespace_fname)
                else:
                    # if we have a namespace we've never seen, load it up as if it was a complete spec
                    spec = [{
                        'tag': tag
                    } for tag in self.guano_content[namespace].keys()]

                this_namespace = NamespaceGroup(namespace, spec)

                if namespace == 'guano_base':
                    this_namespace.load_data(self.guano_content[''])
                else:
                    this_namespace.load_data(self.guano_content[namespace])

                index = self.ui.scrollAreaWidgetContents_2.layout().count() - 1
                self.ui.scrollAreaWidgetContents_2.layout().insertWidget(
                    index, this_namespace)
                self.namespaces[namespace] = this_namespace
                try:
                    self.namespace_chks[namespace].setChecked(True)
                except KeyError:
                    pass
예제 #9
0
 def test_strict_mode(self):
     md = '''GUANO|Version: 1.0
     TE: no
     Loc Position: 10N 567288E 4584472N
     '''
     try:
         GuanoFile.from_string(md, strict=True)
     except ValueError as e:
         pass
     g = GuanoFile.from_string(md, strict=False)
     self.assertEqual(g.get('TE', None), 'no')
     self.assertEqual(g.get('Loc Position', None), '10N 567288E 4584472N')
예제 #10
0
    def save(self):

        d = Path(self.ui.directory_name.text())
        wavs = list(d.glob('**\*.wav'))
        wavs += list(d.glob('**\*.zc'))

        self.ui.progressBar.setVisible(True)
        self.ui.progressBar.setMinimum(1)
        self.ui.progressBar.setMaximum(len(wavs))
        self.ui.progressBar.setVisible(1)

        if self.ui.directory_name_output.text():
            if not Path(self.ui.directory_name_output.text()).exists():
                msq = r"The output directory specified does not exist!  Please point to an existing directory."
                QMessageBox.warning(self, "Output directory does not exist",
                                    msg)
                return None
            out_dir = Path(self.ui.directory_name_output.text())
            make_copy = True
        else:
            out_dir = Path(self.ui.directory_name.text())
            make_copy = False

        for f in wavs:
            original_fname = f.name

            new_fname = self.change_fname_function(original_fname, f=f)

            full_name = f.parent.joinpath(new_fname)
            full_name = str(full_name).replace(str(Path(self.ui.directory_name.text())), \
                                                   str(out_dir))
            if not Path(full_name).exists():
                Path(full_name).parent.mkdir(parents=True, exist_ok=True)
                if make_copy:
                    shutil.copy(str(f), full_name)
                else:
                    f.rename(full_name)

            if original_fname != new_fname:
                try:
                    g = GuanoFile(full_name)
                    g['Original Filename'] = original_fname
                    g.write(make_backup=False)
                except:
                    pass

            self.ui.progressBar.setValue(self.ui.progressBar.value() + 1)

        msg = f"Finished renaming files in directory:\n\n{out_dir}"
        QMessageBox.information(self, "Renaming Process Complete", msg)

        self.ui.progressBar.setVisible(False)
예제 #11
0
    def test_file_roundtrip(self):
        """Write a GUANO .WAV file containing Unicode data, re-read it and confirm value is identical"""
        fname = 'test_guano.wav'

        # write a fake .WAV file
        g = GuanoFile.from_string(self.MD)
        g.filename = fname
        g.wav_params = wavparams(1, 2, 500000, 2, 'NONE', None)
        g._wav_data = b'\01\02'  # faking it, don't try this at home!
        g._wav_data_size = 2
        g.write()

        # read it back in
        g2 = GuanoFile(fname)

        self.assertEqual(self.NOTE, g2['Note'])
예제 #12
0
    def process_file(self, filename):
        filepath = os.path.realpath(filename)
        filestem = os.path.basename(filename).split('.')[0]
        print(f'Loading {filepath}')

        existing_audio = AudioRecording.objects.filter(audio_file=filepath)
        result_count = len(existing_audio)
        if result_count:
            if result_count > 1:
                print(f'Got duplicate records ({result_count}) for {filepath}')
            audio = existing_audio[0]
            audio.identifier = filestem
            if audio.processed and not self.force:
                print("Already processed this file, skipping")
                return
            print('Found incomplete existing record, trying to update')
        else:
            audio = AudioRecording(audio_file=filepath, identifier=filestem)

        self.populate_audio_from_identifier(audio)

        try:
            # print(f'Looking for GUANO data for {filename}')
            guano_file = GuanoFile(filepath)
            if not guano_file:
                # print(f'Empty GUANO data for {filename}')
                guano_file = None
        except ValueError:
            print(f'Unable to load GUANO data for {filename}')
            guano_file = None

        wamd_file = None
        if guano_file is not None:
            self.populate_audio_from_guano(audio, guano_file)
        else:
            # print(f'Looking for WAMD data for {filename}')
            try:
                wamd_file = WamdFile(filepath)
                # print(f'Loaded WAMD data for {filename}')
            except ValueError:
                print(f'Unable to load WAMD data for {filename}')

        if wamd_file is not None:
            try:
                self.populate_audio_from_wamd(audio, wamd_file)
            except ValueError:
                print("Could not load fallback WAMD data")

        if not audio.duration:
            self.read_file_duration(filename, audio)

        if self.subsample:
            self.subsample_file(audio)

        if self.spectrogram:
            self.generate_spectrogram_file(audio)

        audio.save()
예제 #13
0
    def test_new_empty(self):
        """Verify that "new" GUANO file metadata is "falsey" but populated metadata is "truthy"."""
        g = GuanoFile('nonexistent_file.wav')
        self.assertFalse(g)
        self.assertFalse('GUANO|Version' in g)

        g['Foo'] = 'bar'
        self.assertTrue(g)
        self.assertTrue('GUANO|Version' in g)
예제 #14
0
def d500x2guano(fname):
    """Convert a file with raw D500X metadata to use GUANO metadata instead"""
    print('\n', fname)
    md = extract_d500x_metadata(fname)
    if not md:
        print('Skipping non-D500X file: ' + fname, file=sys.stderr)
        return False
    pprint(md)

    gfile = GuanoFile(fname)
    gfile['GUANO|Version'] = 1.0

    gfile['Make'] = 'Pettersson'
    gfile['Model'] = 'D500X'
    gfile['Timestamp'] = md.pop('File Time')
    gfile['Original Filename'] = md.pop('File Name')
    gfile['Samplerate'] = md.pop('Samplerate')
    gfile['Length'] = md.pop('Length')

    if md.get('Profile HP', None) == 'Y':
        gfile['Filter HP'] = 20

    lat, lon = md.pop('LAT', None), md.pop('LON', None)
    if lat and lon:
        gfile['Loc Position'] = dms2decimal(lat), dms2decimal(lon)

    for k, v in md.items():
        gfile['PET', k] = v

    print(gfile.to_string())

    gfile.wav_data = gfile.wav_data[D500X_DATA_SKIP_BYTES:]  # throw out the metadata bytes from 'data' chunk

    unlock(fname)  # D500X "locks" files as unwriteable, we must unlock before we can modify
    gfile.write()
예제 #15
0
def d500x2guano(fname):
    """Convert a file with raw D500X metadata to use GUANO metadata instead"""
    print "\n", fname
    md = extract_d500x_metadata(fname)
    if not md:
        print >> sys.stderr, "Skipping non-D500X file: " + fname
        return False
    pprint(md)

    gfile = GuanoFile(fname)
    gfile["GUANO|Version"] = 1.0

    gfile["Make"] = "Pettersson"
    gfile["Model"] = "D500X"
    gfile["Timestamp"] = md.pop("File Time")
    gfile["Samplerate"] = md.pop("Samplerate")
    gfile["Length"] = md.pop("Length")

    if md.get("Profile HP", None) == "Y":
        gfile["Filter HP"] = 20

    lat, lon = md.pop("LAT", None), md.pop("LON", None)
    if lat and lon:
        gfile["Loc Position"] = dms2decimal(lat), dms2decimal(lon)

    for k, v in md.items():
        gfile["PET", k] = v

    print gfile._as_string()

    gfile.wav_data = gfile.wav_data[D500X_DATA_SKIP_BYTES:]  # throw out the metadata bytes from 'data' chunk

    unlock(fname)  # D500X "locks" files as unwriteable, we must unlock before we can modify
    gfile.write()
예제 #16
0
    def test_delete_namespaced(self):
        """Verify that we can delete namespaced fields"""
        g = GuanoFile()
        g['Foo|Bar'] = 'xyz'
        self.assertTrue('Foo|Bar' in g)
        self.assertTrue('Foo' in g.get_namespaces())

        del g['Foo|Bar']
        self.assertFalse('Foo|Bar' in g)
        self.assertFalse('Foo' in g.get_namespaces())

        try:
            del g['Foo|Bar']
            self.fail('Deleting a deleted key should throw KeyError')
        except KeyError:
            pass

        g['Foo|Bar1'] = 'xyz'
        g['Foo|Bar2'] = 'abc'
        del g['Foo|Bar1']
        self.assertFalse('Foo|Bar1' in g)
        self.assertTrue('Foo|Bar2' in g)
        self.assertTrue('Foo' in g.get_namespaces())
예제 #17
0
    def test_delete_simple(self):
        """Verify that we can delete fields"""
        g = GuanoFile()
        g['Foo'] = 'xyz'
        self.assertTrue('Foo' in g)

        del g['Foo']
        self.assertFalse('Foo' in g)

        try:
            del g['Foo']
            self.fail('Deleting a deleted key should throw KeyError')
        except KeyError:
            pass
예제 #18
0
    def run(self):
        md = self.zc.metadata
        timestamp = md.get('timestamp', None)
        species = md.get('species', '')
        note1 = md.get('note1', '')
        if note1:
            note2 = 'Myotisoft ZCANT'
        else:
            note1, note2 = 'Myotisoft ZCANT', ''
        if self.zc.supports_amplitude:
            log.debug('Adding GUANO metadata :-)')
            guano = GuanoFile()
            guano['ZCANT|Amplitudes'] = self.zc.amplitudes
        else:
            log.debug('Not adding GUANO metadata :-(')
            guano = None

        log.debug('Saving %s ...', self.fname)

        outdir = os.path.dirname(self.fname)
        if not os.path.exists(outdir):
            log.debug('Creating outdir %s ...', outdir)
            os.makedirs(outdir)

        with AnabatFileWriter(self.fname) as out:
            out.write_header(timestamp,
                             self.divratio,
                             species=species,
                             note1=note1,
                             note2=note2,
                             guano=guano)
            time_indexes_us = self.zc.times * 1000000
            intervals_us = np.diff(time_indexes_us)
            intervals_us = intervals_us.astype(
                int
            )  # TODO: round before int cast; consider casting before diff for performance
            out.write_intervals(intervals_us)
예제 #19
0
    def populate_audio_from_guano(audio: AudioRecording,
                                  guano_file: GuanoFile):
        guano_dict = {k: v for k, v in guano_file.items()}
        for k, v in guano_dict.items():
            if isinstance(v, datetime):
                guano_dict[k] = v.strftime('%Y-%m-%d %H:%M:%S')
        audio.guano_data = json.dumps(guano_dict)

        # Species Auto ID: PIPPYG
        # Species Manual ID:
        if 'Species Manual ID' in guano_dict and len(
                guano_dict['Species Manual ID']) > 0:
            populate_audio_identification(audio,
                                          guano_dict['Species Manual ID'])
        elif 'Species Auto ID' in guano_dict and len(
                guano_dict['Species Auto ID']) > 0:
            populate_audio_identification(audio, guano_dict['Species Auto ID'])

        if 'Loc Position' in guano_dict:
            audio.latitude, audio.longitude = guano_dict['Loc Position']
        if 'Serial' in guano_file:
            audio.recorder_serial = guano_file['Serial']
        if 'Length' in guano_file:
            audio.duration = guano_file['Length']
        try:
            if 'Timestamp' in guano_file and guano_file[
                    'Timestamp'] is not None:
                if guano_file['Timestamp'].utcoffset() is None:
                    print('Timestamp with no tzinfo from guano '
                          'cannot be saved')
                else:
                    audio.set_recording_time(guano_file['Timestamp'])
        except ValueError:
            print('Cannot use timestamp from guano - no valid TZ?')
        finally:
            audio.processed = True
예제 #20
0
 def setUp(self):
     GuanoFile.register('User', 'Answer', int)
     self.md = GuanoFile.from_string(self.MD)
예제 #21
0
 def setUp(self):
     self.g = g = GuanoFile()
     g['A'] = 'A value'
     g['Foo Bar'] = 'Foo Bar value'
     g['NS|C'] = 'C value'
     g['NS|Foo Bar'] = 'Namespaced Foo Bar value'
예제 #22
0
def extract_anabat(fname, hpfilter_khz=8.0, **kwargs):
    """Extract (times, frequencies, amplitudes, metadata) from Anabat sequence file"""
    amplitudes = None
    with open(fname, 'rb') as f, contextlib.closing(
            mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)) as m:
        size = len(m)

        # parse header
        data_info_pointer, file_type, tape, date, loc, species, spec, note1, note2 = struct.unpack_from(
            ANABAT_129_HEAD_FMT, m)
        data_pointer, res1, divratio, vres = struct.unpack_from(
            ANABAT_129_DATA_INFO_FMT, m, data_info_pointer)
        species = [_s(species).split('(', 1)[0]] if '(' in species else [
            s.strip() for s in _s(species).split(',')
        ]  # remove KPro junk
        metadata = dict(date=date,
                        loc=_s(loc),
                        species=species,
                        spec=_s(spec),
                        note1=_s(note1),
                        note2=_s(note2),
                        divratio=divratio)
        if file_type >= 132:
            year, month, day, hour, minute, second, second_hundredths, microseconds, id_code, gps_data = struct.unpack_from(
                ANABAT_132_ADDL_DATA_INFO_FMT, m, 0x120)
            try:
                timestamp = datetime(year, month, day, hour, minute, second,
                                     second_hundredths * 10000 + microseconds)
            except ValueError as e:
                log.exception('Failed extracting timestamp')
                timestamp = None
            metadata.update(
                dict(timestamp=timestamp, id=_s(id_code), gps=_s(gps_data)))
            if data_pointer - 0x150 > 12:  # and m[pos:pos+5] == 'GUANO':
                try:
                    guano = GuanoFile.from_string(m[0x150:data_pointer])
                    log.debug(guano.to_string())
                    amplitudes = guano.get('ZCANT|Amplitudes', None)
                except:
                    log.exception('Failed parsing GUANO metadata block')
            else:
                log.debug('No GUANO metadata found')
        log.debug(
            'file_type: %d\tdata_info_pointer: 0x%3x\tdata_pointer: 0x%3x',
            file_type, data_info_pointer, data_pointer)
        log.debug(metadata)
        if res1 != 25000:
            raise ValueError(
                'Anabat files with non-standard RES1 (%s) not yet supported!' %
                res1)

        # parse actual sequence data
        i = data_pointer  # byte index as we scan through the file (data starts at 0x150 for v132, 0x120 for older files)
        intervals_us = np.empty(2**14, np.dtype('uint32'))
        offdots = OrderedDict()  # dot index -> number of subsequent dots
        int_i = 0  # interval index

        while i < size:

            if int_i >= len(intervals_us):
                # Anabat files were formerly capped at 16384 dots, but may now be larger; grow
                intervals_us = np.concatenate(
                    (intervals_us, np.empty(2**14, np.dtype('uint32'))))

            byte = Byte.unpack_from(m, i)[0]

            if byte <= 0x7F:
                # Single byte is a 7-bit signed two's complement offset from previous interval
                offset = byte if byte < 2**6 else byte - 2**7  # clever two's complement unroll
                if int_i > 0:
                    intervals_us[int_i] = intervals_us[int_i - 1] + offset
                    int_i += 1
                else:
                    log.warning(
                        'Sequence file starts with a one-byte interval diff! Skipping byte %x',
                        byte)
                    #intervals.append(offset)  # ?!

            elif 0x80 <= byte <= 0x9F:
                # time interval is contained in 13 bits, upper 5 from the remainder of this byte, lower 8 bits from the next byte
                accumulator = (byte & 0b00011111) << 8
                i += 1
                accumulator |= Byte.unpack_from(m, i)[0]
                intervals_us[int_i] = accumulator
                int_i += 1

            elif 0xA0 <= byte <= 0xBF:
                # interval is contained in 21 bits, upper 5 from the remainder of this byte, next 8 from the next byte and the lower 8 from the byte after that
                accumulator = (byte & 0b00011111) << 16
                i += 1
                accumulator |= Byte.unpack_from(m, i)[0] << 8
                i += 1
                accumulator |= Byte.unpack_from(m, i)[0]
                intervals_us[int_i] = accumulator
                int_i += 1

            elif 0xC0 <= byte <= 0xDF:
                # interval is contained in 29 bits, the upper 5 from the remainder of this byte, the next 8 from the following byte etc.
                accumulator = (byte & 0b00011111) << 24
                i += 1
                accumulator |= Byte.unpack_from(m, i)[0] << 16
                i += 1
                accumulator |= Byte.unpack_from(m, i)[0] << 8
                i += 1
                accumulator |= Byte.unpack_from(m, i)[0]
                intervals_us[int_i] = accumulator
                int_i += 1

            elif 0xE0 <= byte <= 0xFF:
                # status byte which applies to the next n dots
                status = byte & 0b00011111
                i += 1
                dotcount = Byte.unpack_from(m, i)[0]
                if status == DotStatus.OFF:
                    offdots[int_i] = dotcount
                else:
                    log.debug(
                        'UNSUPPORTED: Status %X for %d dots at dot %d (file offset 0x%X)',
                        status, dotcount, int_i, i)

            else:
                raise Exception('Unknown byte %X at offset 0x%X' % (byte, i))

            i += 1

    intervals_us = intervals_us[:int_i]  # TODO: should we free unused memory?

    intervals_s = intervals_us * 1e-6
    times_s = np.cumsum(intervals_s)
    freqs_hz = 1 / (times_s[2:] - times_s[:-2]) * divratio
    freqs_hz[freqs_hz == np.inf] = 0  # TODO: fix divide-by-zero
    freqs_hz[freqs_hz < 4000] = 0
    freqs_hz[freqs_hz > 250000] = 0

    if offdots:
        n_offdots = sum(offdots.values())
        log.debug('Throwing out %d off-dots of %d (%.1f%%)', n_offdots,
                  len(times_s),
                  float(n_offdots) / len(times_s) * 100)
        off_mask = np.zeros(len(intervals_us), dtype=bool)
        for int_i, dotcount in offdots.items():
            off_mask[int_i:int_i + dotcount] = True
        times_s = masked_array(times_s, mask=off_mask).compressed()
        freqs_hz = masked_array(freqs_hz, mask=off_mask).compressed()

    min_, max_ = min(freqs_hz) if any(freqs_hz) else 0, max(freqs_hz) if any(
        freqs_hz) else 0
    log.debug('%s\tDots: %d\tMinF: %.1f\tMaxF: %.1f', basename(fname),
              len(freqs_hz), min_ / 1000.0, max_ / 1000.0)

    times_s, freqs_hz, amplitudes = hpf_zc(times_s, freqs_hz, amplitudes,
                                           hpfilter_khz * 1000)

    assert (len(times_s) == len(freqs_hz) == len(amplitudes or freqs_hz))
    return times_s, freqs_hz, amplitudes, metadata
예제 #23
0
def dump(fname):
    print()
    print(fname)
    gfile = GuanoFile(fname, strict=False)
    print(gfile.to_string())
예제 #24
0
 def test_sb42_bad_timestamp(self):
     """SonoBat 4.2 blank timestamp"""
     md = '''GUANO|Version: 1.0
     Timestamp:
     '''
     GuanoFile.from_string(md)
예제 #25
0
 def test_sb41_bad_key(self):
     """SonoBat 4.1 disembodied colon"""
     md = '''GUANO|Version: 1.0
     :
     '''
     self.assertEqual(1, len(list(GuanoFile.from_string(md).items())))
예제 #26
0
 def test_sb42_bad_encoding(self):
     """SonoBat 4.2 doesn't actually encode as UTF-8. At least try not to blow up when reading."""
     # SonoBat *probably* uses mac-roman on OS X and windows-1252 on Windows... in the US at least.
     md = b'GUANO|Version:  1.0\nNote:  Mobile transect with mic 4\xd5 above roof.\n\x00\x00'
     GuanoFile.from_string(md)
예제 #27
0
def dump(fname):
    print
    print fname
    gfile = GuanoFile(fname)
    print gfile._as_string()
예제 #28
0
 def test_sb42_bad_guano_version(self):
     """Some version of SonoBat 4.2 writes a GUANO|Version of "1.0:" by accident."""
     md = b'GUANO|Version:  1.0:\n1.0:\n'
     GuanoFile.from_string(md)
예제 #29
0
 def test_from_string(self):
     """Parse a GUANO metadata block containing Unicode data"""
     g = GuanoFile.from_string(self.MD)
     self.assertEqual(self.NOTE, g['Note'])
예제 #30
0
from guano import GuanoFile, base64decode, base64encode

from zcant import print_timing

import logging
log = logging.getLogger(__name__)

Byte = struct.Struct('< B')

ANABAT_129_HEAD_FMT = '< H x B 2x 8s 8s 40s 50s 16s 73s 80s'  # 0x0: data_info_pointer, file_type, tape, date, loc, species, spec, note1, note2
ANABAT_129_DATA_INFO_FMT = '< H H B B'  # 0x11a: data_pointer, res1, divratio, vres
ANABAT_132_ADDL_DATA_INFO_FMT = '< H B B B B B B H 6s 32s'  # 0x120: year, month, day, hour, minute, second, second_hundredths, microseconds, id_code, gps_data

GuanoFile.register('ZCANT', 'Amplitudes',
                   lambda b64data: np.frombuffer(base64decode(b64data)),
                   lambda data: base64encode(data.tobytes()))


class DotStatus:
    """Enumeration of dot status types"""
    OUT_OF_RANGE = 0
    OFF = 1
    NORMAL = 2
    MAIN = 3


def _s(s):
    """Strip whitespace and null bytes from string"""
    return s.strip('\00\t ')
예제 #31
0
def get_row_from_guano(fname):

    row = get_empty_row()

    try:
        g = GuanoFile(fname)
        for i, keyvalue in row_lookup.iterrows():
            value = g.get(keyvalue.guano_tag2, '')
            if value.lower() == 'nan':
                value = ''
            row[keyvalue.df_columns] = value

        if g.get('NABat|Site coordinates'):
            #     print('.')
            lat, long = g.get('NABat|Site coordinates').split()
            row['latitude'] = lat
            row['longitude'] = long

        # parse the software type from vendor namespaces
        if 'SB' in g.get_namespaces():
            software = 'Sonobat '
            if g.get('SB|Version').startswith('4.2'):
                software += '4.2'
            elif g.get('SB|Version').startswith('4.'):
                software += '4.x'
            elif g.get('SB|Version').startswith('3.'):
                software += '3.x'
            row['software_type'] = software
        else:
            # TODO: add logic for Kaleidoscope
            pass

        # Make sure we're using a auto/manual ID if available
        if row['auto_id'] == '':
            row['auto_id'] = g.get('GUANO|Species Auto ID', '')

        if row['manual_id'] == '':
            row['manual_id'] = g.get('GUANO|Species Manual ID', '')

        # convert nan to empty string
        for which in ['auto', 'manual']:
            if row[f'{which}_id'].lower() == 'nan':
                row[f'{which}_id'] = ''
    except:
        # Something went dreadfully wrong. We'll populate with what we have
        parts = parse_nabat_fname(fname)
        row['grts_cell_id'] = parts['GrtsId']
        row['location_name'] = parts['SiteName']
        row['detector'] = "Problem extracting row from Guano"

    row['audio_recording_name'] = Path(fname).name

    return row
예제 #32
0
 def test_sb41_bad_te(self):
     """SonoBat 4.1 "optional" TE value"""
     md = '''GUANO|Version: 1.0
     TE:
     '''
     GuanoFile.from_string(md)