diff --git a/python/digital_rf/digital_metadata.py b/python/digital_rf/digital_metadata.py index 18469cf..e78ae97 100644 --- a/python/digital_rf/digital_metadata.py +++ b/python/digital_rf/digital_metadata.py @@ -38,7 +38,7 @@ from six.moves import urllib, zip # local imports -from . import list_drf +from . import list_drf, util from ._version import get_versions try: @@ -199,6 +199,10 @@ def __init__( raise ValueError(errstr % str(sample_rate_denominator)) self._sample_rate_denominator = int(sample_rate_denominator) + self._sample_rate = util.get_samplerate_frac( + sample_rate_numerator, sample_rate_denominator + ) + # have to go to uint64 before longdouble to ensure correct conversion # from int self._samples_per_second = np.longdouble( @@ -214,6 +218,10 @@ def __init__( self._fields = None # No data written yet self._write_properties() + def get_sample_rate(self): + """Return the sample rate in Hz as a fractions.Fraction.""" + return self._sample_rate + def get_samples_per_second(self): """Return the sample rate in Hz as a np.longdouble.""" return self._samples_per_second @@ -338,7 +346,7 @@ def _sample_group_generator(self, samples): Digital Metadata file and takes its name from the sample index. """ - samples_per_file = self._file_cadence_secs * self._samples_per_second + samples_per_file = self._file_cadence_secs * self._sample_rate for file_idx, sample_group in itertools.groupby( samples, lambda s: np.uint64(s / samples_per_file) ): @@ -470,7 +478,7 @@ def __str__(self): attr_list = ( "_subdir_cadence_secs", "_file_cadence_secs", - "_samples_per_second", + "_sample_rate", "_file_name", ) for attr in attr_list: @@ -585,6 +593,9 @@ def __init__(self, metadata_dir, accept_empty=True): self._samples_per_second = np.longdouble( np.uint64(self._sample_rate_numerator) ) / np.longdouble(np.uint64(self._sample_rate_denominator)) + self._sample_rate = util.get_samplerate_frac( + self._sample_rate_numerator, self._sample_rate_denominator + ) fname = f.attrs["file_name"] if isinstance(fname, bytes): # for convenience and forward-compatibility with h5py>=2.9 @@ -712,6 +723,10 @@ def get_fields(self): # _fields is an internal data structure, so make a copy for the user return copy.deepcopy(self._fields) + def get_sample_rate(self): + """Return the sample rate in Hz as a fractions.Fraction.""" + return self._sample_rate + def get_sample_rate_numerator(self): """Return the numerator of the sample rate in Hz.""" return self._sample_rate_numerator @@ -1018,9 +1033,8 @@ def _get_file_list(self, sample0, sample1): scheme. """ - # need to go through numpy uint64 to prevent conversion to float - start_ts = int(np.uint64(np.uint64(sample0) / self._samples_per_second)) - end_ts = int(np.uint64(np.uint64(sample1) / self._samples_per_second)) + start_ts, picoseconds = util.sample_to_time_floor(sample0, self._sample_rate) + end_ts, picoseconds = util.sample_to_time_floor(sample1, self._sample_rate) # convert ts to be divisible by self._file_cadence_secs start_ts = (start_ts // self._file_cadence_secs) * self._file_cadence_secs @@ -1203,7 +1217,7 @@ def __str__(self): attr_list = ( "_subdir_cadence_secs", "_file_cadence_secs", - "_samples_per_second", + "_sample_rate", "_file_name", ) for attr in attr_list: diff --git a/python/digital_rf/digital_rf_hdf5.py b/python/digital_rf/digital_rf_hdf5.py index 8a7056a..e378da7 100644 --- a/python/digital_rf/digital_rf_hdf5.py +++ b/python/digital_rf/digital_rf_hdf5.py @@ -20,7 +20,6 @@ import collections import datetime -import fractions import glob import os import re @@ -972,13 +971,11 @@ def read(self, start_sample, end_sample, channel_name, sub_channel=None): # first get the names of all possible files with data subdir_cadence_secs = file_properties["subdir_cadence_secs"] file_cadence_millisecs = file_properties["file_cadence_millisecs"] - sample_rate_numerator = file_properties["sample_rate_numerator"] - sample_rate_denominator = file_properties["sample_rate_denominator"] + sample_rate = file_properties["sample_rate"] filepaths = self._get_file_list( start_sample, end_sample, - sample_rate_numerator, - sample_rate_denominator, + sample_rate, subdir_cadence_secs, file_cadence_millisecs, ) @@ -1107,14 +1104,12 @@ def get_properties(self, channel_name, sample=None): subdir_cadence_secs = global_properties["subdir_cadence_secs"] file_cadence_millisecs = global_properties["file_cadence_millisecs"] - sample_rate_numerator = global_properties["sample_rate_numerator"] - sample_rate_denominator = global_properties["sample_rate_denominator"] + sample_rate = global_properties["sample_rate"] file_list = self._get_file_list( sample, sample, - sample_rate_numerator, - sample_rate_denominator, + sample_rate, subdir_cadence_secs, file_cadence_millisecs, ) @@ -1310,13 +1305,11 @@ def get_continuous_blocks(self, start_sample, end_sample, channel_name): file_properties = self.get_properties(channel_name) subdir_cadence_secs = file_properties["subdir_cadence_secs"] file_cadence_millisecs = file_properties["file_cadence_millisecs"] - sample_rate_numerator = file_properties["sample_rate_numerator"] - sample_rate_denominator = file_properties["sample_rate_denominator"] + sample_rate = file_properties["sample_rate"] filepaths = self._get_file_list( start_sample, end_sample, - sample_rate_numerator, - sample_rate_denominator, + sample_rate, subdir_cadence_secs, file_cadence_millisecs, ) @@ -1356,13 +1349,11 @@ def get_last_write(self, channel_name): file_properties = self.get_properties(channel_name) subdir_cadence_seconds = file_properties["subdir_cadence_secs"] file_cadence_millisecs = file_properties["file_cadence_millisecs"] - sample_rate_numerator = file_properties["sample_rate_numerator"] - sample_rate_denominator = file_properties["sample_rate_denominator"] + sample_rate = file_properties["sample_rate"] file_list = self._get_file_list( last_sample - 1, last_sample, - sample_rate_numerator, - sample_rate_denominator, + sample_rate, subdir_cadence_seconds, file_cadence_millisecs, ) @@ -1615,8 +1606,7 @@ def read_vector_c81d( def _get_file_list( sample0, sample1, - sample_rate_numerator, - sample_rate_denominator, + sample_rate, subdir_cadence_seconds, file_cadence_millisecs, ): @@ -1636,11 +1626,8 @@ def _get_file_list( Sample index for end of read (inclusive), given in the number of samples since the epoch (time_since_epoch*sample_rate). - sample_rate_numerator : int - Numerator of sample rate in Hz. - - sample_rate_denominator : int - Denominator of sample rate in Hz. + sample_rate : fractions.Fraction | first argument to ``util.get_samplerate_frac`` + Sample rate in Hz. subdir_cadence_secs : int Number of seconds of data found in one subdir. For example, 3600 @@ -1661,13 +1648,9 @@ def _get_file_list( if (sample1 - sample0) > 1e12: warnstr = "Requested read size, %i samples, is very large" warnings.warn(warnstr % (sample1 - sample0), RuntimeWarning) - start_ts, picoseconds = _py_rf_write_hdf5.get_timestamp_floor( - sample0, sample_rate_numerator, sample_rate_denominator - ) + start_ts, picoseconds = util.sample_to_time_floor(sample0, sample_rate) start_msts = start_ts * 1000 + picoseconds // 1000000000 - end_ts, picoseconds = _py_rf_write_hdf5.get_timestamp_floor( - sample1, sample_rate_numerator, sample_rate_denominator - ) + end_ts, picoseconds = util.sample_to_time_floor(sample1, sample_rate) end_msts = end_ts * 1000 + picoseconds // 1000000000 # get subdirectory start and end ts diff --git a/python/examples/benchmark_rf_write_hdf5.py b/python/examples/benchmark_rf_write_hdf5.py index 5b34bd6..60be108 100644 --- a/python/examples/benchmark_rf_write_hdf5.py +++ b/python/examples/benchmark_rf_write_hdf5.py @@ -6,9 +6,8 @@ # # The full license is in the LICENSE file, distributed with this software. # ---------------------------------------------------------------------------- -"""Benchmark I/O of Digital RF write in different configurations. +"""Benchmark I/O of Digital RF write in different configurations.""" -""" from __future__ import absolute_import, division, print_function import os @@ -24,14 +23,14 @@ N_WRITES = int(1e9 // WRITE_BLOCK_SIZE) SAMPLE_RATE_NUMERATOR = int(1e9) SAMPLE_RATE_DENOMINATOR = 1 -sample_rate = np.longdouble(np.uint64(SAMPLE_RATE_NUMERATOR)) / np.longdouble( - np.uint64(SAMPLE_RATE_DENOMINATOR) +sample_rate = digital_rf.util.get_samplerate_frac( + SAMPLE_RATE_NUMERATOR, SAMPLE_RATE_DENOMINATOR ) subdir_cadence_secs = 3600 file_cadence_millisecs = 10 # start 2014-03-09 12:30:30 plus one sample -start_global_index = int(np.uint64(1394368230 * sample_rate)) + 1 +start_global_index = digital_rf.util.time_to_sample_ceil(1394368230, sample_rate) + 1 # data to write data_int16 = np.zeros((WRITE_BLOCK_SIZE, 2), dtype="i2") diff --git a/python/examples/example_read_digital_metadata.py b/python/examples/example_read_digital_metadata.py index bc888b1..7495370 100644 --- a/python/examples/example_read_digital_metadata.py +++ b/python/examples/example_read_digital_metadata.py @@ -11,13 +11,13 @@ Assumes the example Digital Metadata write script has already been run. """ + from __future__ import absolute_import, division, print_function import os import tempfile import digital_rf -import numpy as np metadata_dir = os.path.join(tempfile.gettempdir(), "example_metadata") stime = 1447082580 @@ -29,7 +29,7 @@ raise print("init okay") -start_idx = int(np.uint64(stime * dmr.get_samples_per_second())) +start_idx = digital_rf.util.time_to_sample_ceil(stime, dmr.get_sample_rate()) first_sample, last_sample = dmr.get_bounds() print("bounds are %i to %i" % (first_sample, last_sample)) @@ -55,6 +55,10 @@ latest_meta = dmr.read_latest() print(latest_meta) +print("test of get_sample_rate") +sr = dmr.get_sample_rate() +print(sr) + print("test of get_samples_per_second") sps = dmr.get_samples_per_second() print(sps) diff --git a/python/examples/example_write_digital_metadata.py b/python/examples/example_write_digital_metadata.py index 661b7d0..cc0b308 100644 --- a/python/examples/example_write_digital_metadata.py +++ b/python/examples/example_write_digital_metadata.py @@ -12,6 +12,7 @@ number of levels. """ + from __future__ import absolute_import, division, print_function import os @@ -24,8 +25,8 @@ metadata_dir = os.path.join(tempfile.gettempdir(), "example_metadata") subdirectory_cadence_seconds = 3600 file_cadence_seconds = 60 -samples_per_second_numerator = 10 -samples_per_second_denominator = 9 +sample_rate_numerator = 10 +sample_rate_denominator = 9 file_name = "rideout" stime = 1447082580 @@ -36,14 +37,14 @@ metadata_dir, subdirectory_cadence_seconds, file_cadence_seconds, - samples_per_second_numerator, - samples_per_second_denominator, + sample_rate_numerator, + sample_rate_denominator, file_name, ) print("first create okay") data_dict = {} -start_idx = int(np.uint64(stime * dmw.get_samples_per_second())) +start_idx = digital_rf.util.time_to_sample_ceil(stime, dmw.get_sample_rate()) # To save an array of data, make sure the first axis has the same length # as the samples index idx_arr = np.arange(70, dtype=np.int64) + start_idx @@ -95,8 +96,8 @@ metadata_dir, subdirectory_cadence_seconds, file_cadence_seconds, - samples_per_second_numerator, - samples_per_second_denominator, + sample_rate_numerator, + sample_rate_denominator, file_name, ) print("second create okay") diff --git a/python/examples/example_write_digital_rf.py b/python/examples/example_write_digital_rf.py index 3b1a3ec..d572bf7 100644 --- a/python/examples/example_write_digital_rf.py +++ b/python/examples/example_write_digital_rf.py @@ -11,6 +11,7 @@ Writes continuous complex short data. """ + from __future__ import absolute_import, division, print_function import os @@ -26,7 +27,9 @@ # writing parameters sample_rate_numerator = int(100) # 100 Hz sample rate - typically MUCH faster sample_rate_denominator = 1 -sample_rate = np.longdouble(sample_rate_numerator) / sample_rate_denominator +sample_rate = digital_rf.util.get_samplerate_frac( + sample_rate_numerator, sample_rate_denominator +) dtype_str = "i2" # short int sub_cadence_secs = ( 4 # Number of seconds of data in a subdirectory - typically MUCH larger @@ -50,7 +53,7 @@ arr_data[i]["i"] = 3 * i # start 2014-03-09 12:30:30 plus one sample -start_global_index = int(np.uint64(1394368230 * sample_rate)) + 1 +start_global_index = digital_rf.util.time_to_sample_ceil(1394368230, sample_rate) + 1 # set up top level directory shutil.rmtree(chdir, ignore_errors=True) diff --git a/python/examples/sounder/prc_analyze.py b/python/examples/sounder/prc_analyze.py index 4612d0a..e4406e9 100755 --- a/python/examples/sounder/prc_analyze.py +++ b/python/examples/sounder/prc_analyze.py @@ -16,6 +16,7 @@ doi:10.5194/amt-9-829-2016, 2016. """ + from __future__ import absolute_import, division, print_function import datetime @@ -224,7 +225,7 @@ def analyze_prc( os.remove(f) d = drf.DigitalRFReader(op.datadir) - sr = d.get_properties(op.ch)["samples_per_second"] + sr = d.get_properties(op.ch)["sample_rate"] b = d.get_bounds(op.ch) idx = np.array(b[0]) if os.path.isfile(datpath): diff --git a/python/examples/sounder/tx.py b/python/examples/sounder/tx.py index 1f0a94e..6bfaf23 100755 --- a/python/examples/sounder/tx.py +++ b/python/examples/sounder/tx.py @@ -8,6 +8,7 @@ # The full license is in the LICENSE file, distributed with this software. # ---------------------------------------------------------------------------- """Transmit waveforms with synchronized USRPs.""" + from __future__ import absolute_import, division, print_function import math @@ -26,7 +27,6 @@ import numpy as np import pytz from gnuradio import analog, blocks, gr, uhd - from six.moves import configparser @@ -406,15 +406,11 @@ def _usrp_setup(self): u.set_samp_rate(float(op.samplerate)) # read back actual value samplerate = u.get_samp_rate() - # calculate longdouble precision sample rate + # calculate rational sample rate # (integer division of clock rate) cr = u.get_clock_rate() srdec = int(round(cr / samplerate)) - samplerate_ld = np.longdouble(cr) / srdec - op.samplerate = samplerate_ld - sr_rat = Fraction(cr).limit_denominator() / srdec - op.samplerate_num = sr_rat.numerator - op.samplerate_den = sr_rat.denominator + op.samplerate = drf.util.get_samplerate_frac(cr, srdec) # set per-channel options # set command time so settings are synced @@ -629,14 +625,14 @@ def run(self, starttime=None, endtime=None, duration=None, period=10): now = pytz.utc.localize(datetime.utcnow()) # launch on integer second by default for convenience (ceil + 1) lt = now.replace(microsecond=0) + timedelta(seconds=2) - ltts = (lt - drf.util.epoch).total_seconds() + lttd = lt - drf.util.epoch # adjust launch time forward so it falls on an exact sample since epoch - lt_samples = np.ceil(ltts * op.samplerate) - ltts = lt_samples / op.samplerate - lt = drf.util.sample_to_datetime(lt_samples, op.samplerate) + lt_rsamples = drf.util.time_to_sample_ceil(lttd, op.samplerate) + lt = drf.util.sample_to_datetime(lt_rsamples, op.samplerate) if op.verbose: ltstr = lt.strftime("%a %b %d %H:%M:%S.%f %Y") - print("Launch time: {0} ({1})".format(ltstr, repr(ltts))) + msg = "Launch time: {0} ({1})" + print(msg.format(ltstr, repr(lt.timestamp()))) # command launch time ct_td = lt - drf.util.epoch ct_secs = ct_td.total_seconds() // 1.0 diff --git a/python/gr_digital_rf/digital_rf_sink.py b/python/gr_digital_rf/digital_rf_sink.py index f8e7338..eb8a34b 100644 --- a/python/gr_digital_rf/digital_rf_sink.py +++ b/python/gr_digital_rf/digital_rf_sink.py @@ -7,6 +7,7 @@ # The full license is in the LICENSE file, distributed with this software. # ---------------------------------------------------------------------------- """Module defining a Digital RF Source block.""" + from __future__ import absolute_import, division, print_function import os @@ -19,19 +20,18 @@ import numpy as np import pmt import six +from digital_rf import DigitalMetadataWriter, DigitalRFWriter, _py_rf_write_hdf5, util from gnuradio import gr from six.moves import zip -from digital_rf import DigitalMetadataWriter, DigitalRFWriter, _py_rf_write_hdf5, util - -def parse_time_pmt(val, samples_per_second): +def parse_time_pmt(val, sample_rate): """Get (sec, frac, idx) from an rx_time pmt value.""" - tsec = np.uint64(pmt.to_uint64(pmt.tuple_ref(val, 0))) + tsec = int(np.uint64(pmt.to_uint64(pmt.tuple_ref(val, 0)))) tfrac = pmt.to_double(pmt.tuple_ref(val, 1)) # calculate sample index of time and floor to uint64 - tidx = np.uint64(tsec * samples_per_second + tfrac * samples_per_second) - return int(tsec), tfrac, int(tidx) + tidx = util.time_to_sample_ceil((tsec, int(tfrac * 1e12)), sample_rate) + return tsec, tfrac, tidx def translate_rx_freq(tag): @@ -319,12 +319,12 @@ def __init__( self._work_done = False - self._samples_per_second = np.longdouble( - np.uint64(sample_rate_numerator) - ) / np.longdouble(np.uint64(sample_rate_denominator)) + self._sample_rate = util.get_samplerate_frac( + self._sample_rate_numerator, self._sample_rate_denominator + ) if min_chunksize is None: - self._min_chunksize = max(int(self._samples_per_second // 1000), 1) + self._min_chunksize = max(int(self._sample_rate // 1000), 1) else: self._min_chunksize = min_chunksize @@ -346,7 +346,7 @@ def __init__( # will be None if start is None or '' self._start_sample = util.parse_identifier_to_sample( - start, self._samples_per_second, None + start, self._sample_rate, None ) if self._start_sample is None: if self._ignore_tags: @@ -360,9 +360,8 @@ def __init__( self._next_rel_sample = 0 if self._debug: tidx = self._start_sample - timedelta = util.samples_to_timedelta(tidx, self._samples_per_second) - tsec = int(timedelta.total_seconds() // 1) - tfrac = timedelta.microseconds / 1e6 + tsec, picoseconds = util.sample_to_time_floor(tidx, self._sample_rate) + tfrac = picoseconds / 1e12 tagstr = ("|{0}|start @ sample 0: {1}+{2} ({3})\n").format( self._channel_name, tsec, tfrac, tidx ) @@ -462,7 +461,7 @@ def _read_tags(self, nsamples): # separate data into blocks to be written for tag in time_tags: offset = tag.offset - tsec, tfrac, tidx = parse_time_pmt(tag.value, self._samples_per_second) + tsec, tfrac, tidx = parse_time_pmt(tag.value, self._sample_rate) # index into data block for this tag bidx = offset - nread diff --git a/python/gr_digital_rf/digital_rf_source.py b/python/gr_digital_rf/digital_rf_source.py index 3f9cd44..56545d7 100644 --- a/python/gr_digital_rf/digital_rf_source.py +++ b/python/gr_digital_rf/digital_rf_source.py @@ -205,7 +205,7 @@ def __init__( itemsize = self._properties["H5Tget_size"] is_complex = self._properties["is_complex"] vlen = self._properties["num_subchannels"] - sr = self._properties["samples_per_second"] + sr = self._properties["sample_rate"] self._itemsize = itemsize self._sample_rate = sr @@ -289,9 +289,9 @@ def _queue_tags(self, sample, tags): tag_dict = self._tag_queue.get(sample, {}) if not tag_dict: # add time and rate tags - time = np.uint64(sample) / self._sample_rate + seconds, picoseconds = util.sample_to_time_floor(sample, self._sample_rate) tag_dict["rx_time"] = pmt.make_tuple( - pmt.from_uint64(int(np.uint64(time))), pmt.from_double(float(time % 1)) + pmt.from_uint64(seconds), pmt.from_double(picoseconds / 1e12) ) tag_dict["rx_rate"] = self._sample_rate_pmt for k, v in tags.items(): diff --git a/python/tools/drf_cross_sti.py b/python/tools/drf_cross_sti.py index c2ec778..5ffb705 100644 --- a/python/tools/drf_cross_sti.py +++ b/python/tools/drf_cross_sti.py @@ -16,7 +16,6 @@ """ - import datetime import itertools as it import optparse @@ -131,12 +130,8 @@ def plot(self): print(("pair is : ", xidx, yidx)) # sample rate - xsr = self.dio[xidx].get_properties(self.channel[xidx])[ - "samples_per_second" - ] - ysr = self.dio[yidx].get_properties(self.channel[yidx])[ - "samples_per_second" - ] + xsr = self.dio[xidx].get_properties(self.channel[xidx])["sample_rate"] + ysr = self.dio[yidx].get_properties(self.channel[yidx])["sample_rate"] if self.control.verbose: print(("sample rate, x: ", xsr, " y: ", ysr)) @@ -158,19 +153,15 @@ def plot(self): if self.control.start: dtst0 = dateutil.parser.parse(self.control.start) - st0 = ( - dtst0 - datetime.datetime(1970, 1, 1, tzinfo=pytz.utc) - ).total_seconds() - st0 = int(st0 * sr) + st0 = dtst0 - datetime.datetime(1970, 1, 1, tzinfo=pytz.utc) + st0 = drf.util.time_to_sample_ceil(st0, sr) else: st0 = int(b[0]) if self.control.end: dtst0 = dateutil.parser.parse(self.control.end) - et0 = ( - dtst0 - datetime.datetime(1970, 1, 1, tzinfo=pytz.utc) - ).total_seconds() - et0 = int(et0 * sr) + et0 = dtst0 - datetime.datetime(1970, 1, 1, tzinfo=pytz.utc) + et0 = drf.util.time_to_sample_ceil(et0, sr) else: et0 = int(b[1]) @@ -389,9 +380,9 @@ def plot(self): print("last ", start_sample) # create a time stamp - start_time = st0 / sr + start_time, picoseconds = drf.util.sample_to_time_floor(st0, self.sr) srt_time = time.gmtime(start_time) - sub_second = int(round((start_time - int(start_time)) * 100)) + sub_second = int(round(picoseconds / 1e10)) timestamp = "%d-%02d-%02d %02d:%02d:%02d.%02d UT" % ( srt_time[0], diff --git a/python/tools/drf_plot.py b/python/tools/drf_plot.py index 14a0d5a..f4763ea 100644 --- a/python/tools/drf_plot.py +++ b/python/tools/drf_plot.py @@ -8,14 +8,14 @@ # The full license is in the LICENSE file, distributed with this software. # ---------------------------------------------------------------------------- """ - drf_plot.py +drf_plot.py - $Id$ +$Id$ - Simple program to load 16 bit IQ data and make some basic plots. Command - line options are supported and data frames may be filtered from the output. The - program can offset into a data file to limit the memory usage when plotting - a subset of a data file. +Simple program to load 16 bit IQ data and make some basic plots. Command +line options are supported and data frames may be filtered from the output. The +program can offset into a data file to limit the memory usage when plotting +a subset of a data file. """ @@ -1170,8 +1170,7 @@ def usage(): print("loading metadata") drf_properties = drf.get_properties(chans[chidx]) - sfreq_ld = drf_properties["samples_per_second"] - sfreq = float(sfreq_ld) + sfreq = drf_properties["sample_rate"] toffset = start_sample print(toffset) @@ -1179,7 +1178,7 @@ def usage(): if atime == 0: atime = ustart else: - atime = int(np.uint64(atime * sfreq_ld)) + atime = drf.util.time_to_sample_ceil(atime, sfreq) sstart = atime + int(toffset) dlen = stop_sample - start_sample diff --git a/python/tools/drf_sound.py b/python/tools/drf_sound.py index 9bfead7..3f86706 100644 --- a/python/tools/drf_sound.py +++ b/python/tools/drf_sound.py @@ -15,6 +15,7 @@ directly to sounddevice or through a wave file save out. """ + from __future__ import absolute_import, division, print_function import datetime @@ -57,7 +58,7 @@ def makeasound(self): Iterate over the data set and output a sound through sounddevice. """ - sr = self.dio.get_properties(self.channel)["samples_per_second"] + sr = self.dio.get_properties(self.channel)["sample_rate"] if self.control.verbose: print("sample rate: ", sr) @@ -69,19 +70,15 @@ def makeasound(self): if self.control.start: dtst0 = dateutil.parser.parse(self.control.start) - st0 = ( - dtst0 - datetime.datetime(1970, 1, 1, tzinfo=pytz.utc) - ).total_seconds() - st0 = int(st0 * sr) + st0 = dtst0 - datetime.datetime(1970, 1, 1, tzinfo=pytz.utc) + st0 = drf.util.time_to_sample_ceil(st0, sr) else: st0 = int(bound[0]) if self.control.end: dtst0 = dateutil.parser.parse(self.control.end) - et0 = ( - dtst0 - datetime.datetime(1970, 1, 1, tzinfo=pytz.utc) - ).total_seconds() - et0 = int(et0 * sr) + et0 = dtst0 - datetime.datetime(1970, 1, 1, tzinfo=pytz.utc) + et0 = drf.util.time_to_sample_ceil(et0, sr) else: et0 = int(bound[1]) diff --git a/python/tools/drf_sti.py b/python/tools/drf_sti.py index 5557c57..4881e41 100644 --- a/python/tools/drf_sti.py +++ b/python/tools/drf_sti.py @@ -9,9 +9,8 @@ # ---------------------------------------------------------------------------- """Create a spectral time intensity summary plot for a data set.""" - -import datetime import argparse +import datetime import os import sys import time @@ -86,17 +85,15 @@ def __init__(self, opt): # open digital RF path self.dio = drf.DigitalRFReader(self.opt.path) - self.sr = self.dio.get_properties(self.channels[0])["samples_per_second"] + self.sr = self.dio.get_properties(self.channels[0])["sample_rate"] self.bounds = self.dio.get_bounds(self.channels[0]) - self.dt_start = datetime.datetime.utcfromtimestamp( - int(self.bounds[0] / self.sr) - ) - self.dt_stop = datetime.datetime.utcfromtimestamp(int(self.bounds[1] / self.sr)) + self.dt_start = drf.util.sample_to_datetime(self.bounds[0], self.sr) + self.dt_stop = drf.util.sample_to_datetime(self.bounds[1], self.sr) print( "bound times {0} to {1} UTC".format( - self.dt_start.utcnow().isoformat(), self.dt_stop.utcnow().isoformat() + self.dt_start.isoformat(), self.dt_stop.isoformat() ) ) @@ -147,19 +144,15 @@ def plot(self): if self.opt.start: dtst0 = dateutil.parser.parse(self.opt.start) - st0 = ( - dtst0 - datetime.datetime(1970, 1, 1, tzinfo=pytz.utc) - ).total_seconds() - st0 = int(st0 * self.sr) + st0 = dtst0 - datetime.datetime(1970, 1, 1, tzinfo=pytz.utc) + st0 = drf.util.time_to_sample_ceil(st0, self.sr) else: st0 = int(b[0]) if self.opt.end: dtst0 = dateutil.parser.parse(self.opt.end) - et0 = ( - dtst0 - datetime.datetime(1970, 1, 1, tzinfo=pytz.utc) - ).total_seconds() - et0 = int(et0 * self.sr) + et0 = dtst0 - datetime.datetime(1970, 1, 1, tzinfo=pytz.utc) + et0 = drf.util.time_to_sample_ceil(et0, self.sr) else: et0 = int(b[1]) @@ -394,9 +387,9 @@ def plot(self): print("last {0}".format(start_sample)) # create a time stamp - start_time = int(st0 / self.sr) + start_time, picoseconds = drf.util.sample_to_time_floor(st0, self.sr) srt_time = time.gmtime(start_time) - sub_second = int(round((start_time - int(start_time)) * 100)) + sub_second = int(round(picoseconds / 1e10)) timestamp = "%d-%02d-%02d %02d:%02d:%02d.%02d UT" % ( srt_time[0], diff --git a/python/tools/thor.py b/python/tools/thor.py index 770242a..e933910 100644 --- a/python/tools/thor.py +++ b/python/tools/thor.py @@ -8,6 +8,7 @@ # The full license is in the LICENSE file, distributed with this software. # ---------------------------------------------------------------------------- """Record data from synchronized USRPs in Digital RF format.""" + from __future__ import absolute_import, division, print_function import argparse @@ -21,15 +22,14 @@ from fractions import Fraction from itertools import chain, cycle, islice, repeat from subprocess import call -from textwrap import dedent, fill, TextWrapper +from textwrap import TextWrapper, dedent, fill import digital_rf as drf import gr_digital_rf as gr_drf import numpy as np import pytz -from gnuradio import blocks +from gnuradio import blocks, gr, uhd from gnuradio import filter as grfilter -from gnuradio import gr, uhd def equiripple_lpf(cutoff=0.9, transition_width=0.2, attenuation=80, pass_ripple=None): @@ -577,13 +577,11 @@ def _usrp_setup(self): # read back actual sample rate value samplerate = u.get_samp_rate() - # calculate longdouble precision/rational sample rate + # calculate rational sample rate # (integer division of clock rate) cr = op.clock_rates[0] srdec = int(round(cr / samplerate)) - op.samplerate_frac = Fraction(cr).limit_denominator(2**32) / srdec - samplerate_ld = np.longdouble(cr) / srdec - op.samplerate = samplerate_ld + op.samplerate = drf.util.get_samplerate_frac(cr, srdec) # set per-channel options # set command time so settings are synced @@ -705,7 +703,7 @@ def _usrp_setup(self): info["lo_off"] = op.lo_offsets[ch_num] info["lo_source"] = op.lo_sources[ch_num] info["lo_export"] = op.lo_exports[ch_num] - info["sr"] = op.samplerate_frac + info["sr"] = op.samplerate print(chinfo.format(**info)) print("-" * 78) @@ -714,7 +712,7 @@ def _usrp_setup(self): def _finalize_options(self): op = self.op - op.ch_samplerates_frac = [] + op.ch_samplerates = [] op.resampling_ratios = [] op.resampling_filter_taps = [] op.resampling_filter_delays = [] @@ -722,16 +720,16 @@ def _finalize_options(self): op.channelizer_filter_delays = [] for ko, (osr, nsc) in enumerate(zip(op.ch_samplerates, op.ch_nsubchannels)): # get output resampling ratio - # (op.samplerate_frac final value is set in _usrp_setup + # (op.samplerate final value is set in _usrp_setup # so can't get output sample rate until after that is done) if osr is None: ratio = Fraction(1) else: - ratio = (Fraction(osr) / op.samplerate_frac).limit_denominator(2**16) + ratio = (Fraction(osr) / op.samplerate).limit_denominator(2**16) op.resampling_ratios.append(ratio) # get output samplerate fraction - op.ch_samplerates_frac.append(op.samplerate_frac * ratio) + op.ch_samplerates.append(op.samplerate * ratio) # get resampling low-pass filter taps if ratio == 1: @@ -885,15 +883,14 @@ def run(self, starttime=None, endtime=None, duration=None, period=10): now = pytz.utc.localize(datetime.utcnow()) # launch on integer second by default for convenience (ceil + 2) lt = now.replace(microsecond=0) + timedelta(seconds=3) - ltts = (lt - drf.util.epoch).total_seconds() + lttd = lt - drf.util.epoch # adjust launch time forward so it falls on an exact sample since epoch - lt_rsamples = int(np.ceil(ltts * op.samplerate)) - ltts = lt_rsamples / op.samplerate + lt_rsamples = drf.util.time_to_sample_ceil(lttd, op.samplerate) lt = drf.util.sample_to_datetime(lt_rsamples, op.samplerate) if op.verbose: ltstr = lt.strftime("%a %b %d %H:%M:%S.%f %Y") msg = "Launch time: {0} ({1})\nSample index: {2}" - print(msg.format(ltstr, repr(ltts), lt_rsamples)) + print(msg.format(ltstr, repr(lt.timestamp()), lt_rsamples)) # command launch time ct_td = lt - drf.util.epoch ct_secs = ct_td.total_seconds() // 1.0 @@ -917,7 +914,7 @@ def run(self, starttime=None, endtime=None, duration=None, period=10): mbnum = op.mboardnum_bychan[kr] # output settings that get modified depending on processing - ch_samplerate_frac = op.ch_samplerates_frac[ko] + ch_samplerate = op.ch_samplerates[ko] ch_centerfreq = op.ch_centerfreqs[ko] start_sample_adjust = 0 @@ -976,7 +973,7 @@ def run(self, starttime=None, endtime=None, duration=None, period=10): # make frequency shift block if necessary if ch_centerfreq is not False: f_shift = ch_centerfreq - op.centerfreqs[kr] - phase_inc = -2 * np.pi * f_shift / ch_samplerate_frac + phase_inc = -2 * np.pi * f_shift / ch_samplerate rotator = blocks.rotator_cc(phase_inc) else: ch_centerfreq = op.centerfreqs[kr] @@ -1020,9 +1017,9 @@ def run(self, starttime=None, endtime=None, duration=None, period=10): # modify output settings accordingly ch_centerfreq = ch_centerfreq + np.fft.fftfreq( - nsc, 1 / float(ch_samplerate_frac) + nsc, 1 / float(ch_samplerate) ) - ch_samplerate_frac = ch_samplerate_frac / nsc + ch_samplerate = ch_samplerate / nsc else: channelizer = None @@ -1042,10 +1039,9 @@ def run(self, starttime=None, endtime=None, duration=None, period=10): convert = None # get start sample - ch_samplerate_ld = np.longdouble( - ch_samplerate_frac.numerator - ) / np.longdouble(ch_samplerate_frac.denominator) - start_sample = int(np.uint64(ltts * ch_samplerate_ld)) + start_sample_adjust + start_sample = ( + drf.util.time_to_sample_ceil(lttd, ch_samplerate) + start_sample_adjust + ) # create digital RF sink dst = gr_drf.digital_rf_channel_sink( @@ -1053,8 +1049,8 @@ def run(self, starttime=None, endtime=None, duration=None, period=10): dtype=op.ch_out_specs[ko]["dtype"], subdir_cadence_secs=op.subdir_cadence_s, file_cadence_millisecs=op.file_cadence_ms, - sample_rate_numerator=ch_samplerate_frac.numerator, - sample_rate_denominator=ch_samplerate_frac.denominator, + sample_rate_numerator=ch_samplerate.numerator, + sample_rate_denominator=ch_samplerate.denominator, start=start_sample, ignore_tags=False, is_complex=True, @@ -1091,7 +1087,7 @@ def run(self, starttime=None, endtime=None, duration=None, period=10): resampling_filter_taps=op.resampling_filter_taps[ko], scaling=op.ch_scalings[ko], ), - **op.metadata + **op.metadata, ), is_continuous=True, compression_level=0,