Beispiel #1
0
def process_bursts_from_database(
    packet_db, pairs, max_lookback_time=datetime.timedelta(hours=2)):
    ''' Decode bursts from the packet database, between a list of status packet tuples defined by pairs.
        pairs is generated by get_burst_pairs().
    '''
    logger = logging.getLogger('process_bursts_from_database')

    completed_bursts = []

    # Process packets between each set of headers:
    for index, (IA, IB) in enumerate(pairs):
        # logger.debug(f'doing {index}')

        tb = datetime.datetime.fromtimestamp(round(IB['header_timestamp']) + 1,
                                             tz=datetime.timezone.utc)
        if not IA:
            ta = tb - max_lookback_time
        else:
            ta = datetime.datetime.fromtimestamp(
                round(IA['header_timestamp']) - 1, tz=datetime.timezone.utc)

        # Load the rest of the packets within the time interval
        statcheck = get_packets_within_range(packet_db,
                                             dtype='I',
                                             t1=ta,
                                             t2=tb)
        E_packets = get_packets_within_range(packet_db,
                                             dtype='E',
                                             t1=ta,
                                             t2=tb)
        B_packets = get_packets_within_range(packet_db,
                                             dtype='B',
                                             t1=ta,
                                             t2=tb)
        G_packets = get_packets_within_range(packet_db,
                                             dtype='G',
                                             t1=ta,
                                             t2=tb)

        # Skip if there's no data to process
        if not E_packets and not B_packets and not G_packets:
            logger.debug('No data found!')
            continue

        logger.info(f'burst between {ta} and {tb} (dt = {tb - ta})')
        logger.info(
            f'loaded {len(E_packets)} E packets, {len(B_packets)} B packets, {len(G_packets)} GPS packets, and {len(statcheck)} status packets'
        )

        avail_exp_nums = np.unique(
            [x['exp_num'] for x in (E_packets + B_packets + G_packets)])

        logger.debug(f'Available experiment nums: {avail_exp_nums}')

        for e_num in avail_exp_nums:
            try:
                # Check echo'd command in the GPS packet
                for gg in filter(lambda p: p['exp_num'] == e_num, G_packets):
                    if gg['start_ind'] == 0:
                        cmd_gps = np.flip(gg['data'][0:3])
                        logger.debug(f'gps command: {cmd_gps}')
                        if (IB['prev_burst_command'] != cmd_gps).any():
                            logger.warning(
                                "GPS and status command echo mismatch")

                # Get burst configuration parameters:
                burst_config = decode_burst_command(IB['prev_burst_command'])
                burst_config['burst_pulses'] = IB['burst_pulses']

                logger.debug(f'burst configuration: {burst_config}')

                packets_to_process = list(
                    filter(lambda p: p['exp_num'] == e_num,
                           E_packets + B_packets + G_packets))
                processed = process_burst(packets_to_process, burst_config)

                processed['bbr_config'] = decode_uBBR_command(
                    IB['prev_bbr_command'])
                processed['footer_timestamp'] = IB['header_timestamp']
                if IA:
                    processed['status'] = [IA, IB]
                    processed['header_timestamp'] = IA['header_timestamp']
                else:
                    processed['status'] = [IB]
                    processed['header_timestamp'] = min([
                        x['header_timestamp']
                        for x in (E_packets + B_packets + G_packets)
                    ])
                processed['experiment_number'] = e_num

                completed_bursts.append(processed)
            except:
                logger.warning(
                    f'Problem decoding burst {index}, exp_num {e_num}')

    return completed_bursts
def plot_burst_FD(fig, burst, cal_data=None):
    logger = logging.getLogger('plot_burst_FD')

    logger.debug(burst['config'])
    cfg = burst['config']

    logger.info(f'burst configuration: {cfg}')

    system_delay_samps_TD = 73
    system_delay_samps_FD = 200
    fs = 80000
    # cm = plt.cm.jet
    cm = parula()
    # This is a mockup of the current Matlab colormap (which is proprietary)

    # Check if we have any status packets included -- we'll get
    # the uBBR configuration from these.
    if 'bbr_config' in burst:
        bbr_config = burst['bbr_config']
    elif 'I' in burst:
        logger.debug(f"Found {len(burst['I'])} status packets")
        # Get uBBR config command:
        if 'prev_bbr_command' in burst['I'][0]:
            bbr_config = decode_uBBR_command(burst['I'][0]['prev_bbr_command'])
        else:
            ps = decode_status([burst['I'][0]])
            bbr_config = decode_uBBR_command(ps[0]['prev_bbr_command'])
        logger.debug(f'bbr config is: {bbr_config}')
    else:
        logger.warning(f'No bbr configuration found')
        bbr_config = None

    # ---------- Calibration coefficients ------
    ADC_max_value = 32768.  # 16 bits, twos comp
    ADC_max_volts = 1.0  # ADC saturates at +- 1 volt

    E_coef = ADC_max_volts / ADC_max_value  # [Volts at ADC / ADC bin]
    B_coef = ADC_max_volts / ADC_max_value

    if cal_file and bbr_config:
        td_lims = [-1, 1]
        E_cal_curve = cal_data[('E', bool(bbr_config['E_FILT']),
                                bool(bbr_config['E_GAIN']))]
        B_cal_curve = cal_data[('B', bool(bbr_config['B_FILT']),
                                bool(bbr_config['B_GAIN']))]
        E_coef *= 1000.0 / max(E_cal_curve)  # [(mV/m) / Vadc]
        B_coef *= 1.0 / max(B_cal_curve)  # [(nT) / Vadc]
        E_unit_string = 'mV/m @ Antenna'
        B_unit_string = 'nT'

        logger.debug(f'E calibration coefficient is {E_coef} mV/m per bit')
        logger.debug(f'B calibration coefficient is {B_coef} nT per bit')
    else:
        E_unit_string = 'V @ ADC'
        B_unit_string = 'V @ ADC'

    # Scale the spectrograms -- A perfect sine wave will have ~-3dB amplitude.
    # Scaling covers 14 bits of dynamic range, with a maximum at each channel's theoretical peak
    clims = np.array([-6 * 14, -3])  #[-96, -20]
    e_clims = clims + 20 * np.log10(E_coef * ADC_max_value / ADC_max_volts)
    b_clims = clims + 20 * np.log10(B_coef * ADC_max_value / ADC_max_volts)

    if cfg['TD_FD_SELECT'] == 0:
        # --------- Frequency domain plots  -----------
        # fig = plt.figure()
        gs = GridSpec(2, 2, width_ratios=[20, 1], wspace=0.05, hspace=0.05)
        E_FD = fig.add_subplot(gs[0, 0])
        B_FD = fig.add_subplot(gs[1, 0], sharex=E_FD, sharey=E_FD)
        cb1 = fig.add_subplot(gs[0, 1])
        cb2 = fig.add_subplot(gs[1, 1])

        nfft = 1024

        # Frequency axis
        f_axis = []
        seg_length = nfft / 2 / 16

        for i, v in enumerate(cfg['BINS'][::-1]):
            if v == '1':
                f_axis.append([np.arange(seg_length) + seg_length * i])
        freq_inds = np.array(f_axis).ravel().astype(
            'int')  # stash the row indices here
        f_axis = (40000 / (nfft / 2)) * np.array(f_axis).ravel()
        f_axis_full = np.arange(512) * 40000 / 512

        logger.debug(f"f axis: {len(f_axis)}")

        # E and B are flattened vectors; we need to reshape them into 2d arrays (spectrograms)
        max_E = len(burst['E']) - np.mod(len(burst['E']), len(f_axis))
        E = burst['E'][0:max_E].reshape(int(max_E / len(f_axis)),
                                        len(f_axis)) * E_coef
        E = E.T
        max_B = len(burst['B']) - np.mod(len(burst['B']), len(f_axis))
        B = burst['B'][0:max_B].reshape(int(max_B / len(f_axis)),
                                        len(f_axis)) * B_coef
        B = B.T

        logger.debug(f"E dims: {np.shape(E)}, B dims: {np.shape(B)}")

        # Generate time axis
        scale_factor = nfft / 2. / 80000.

        sec_on = np.round(cfg['FFTS_ON'] * scale_factor)
        sec_off = np.round(cfg['FFTS_OFF'] * scale_factor)

        if cfg['FFTS_OFF'] == 0:
            # GPS packets are taken when stopping data capture -- e.g., at the end of the burst,
            # or transitioning to a "samples off" section. If we're doing back-to-back bursts
            # with no windowing, we'll only have one GPS timestamp instead of burst_pulses.
            max_t_ind = np.shape(E)[1]
            t_inds = np.arange(max_t_ind)
            t_axis_seconds = t_inds * scale_factor
            start_timestamp = datetime.datetime.utcfromtimestamp(
                burst['G'][0]['timestamp']) - datetime.timedelta(
                    seconds=np.round(t_axis_seconds[-1]))
            t_axis_full_seconds = np.arange(
                max_t_ind) * scale_factor + system_delay_samps_FD / fs
            t_axis_full_timestamps = burst['G'][0][
                'timestamp'] - max_t_ind * scale_factor + t_axis_full_seconds

        else:
            t_inds = np.array([(np.arange(cfg['FFTS_ON'])) +
                               (k * (cfg['FFTS_ON'] + cfg['FFTS_OFF']))
                               for k in range(cfg['burst_pulses'])]).ravel()
            max_t_ind = (cfg['FFTS_ON'] +
                         cfg['FFTS_OFF']) * cfg['burst_pulses']
            start_timestamp = datetime.datetime.utcfromtimestamp(
                burst['G'][0]['timestamp']) - datetime.timedelta(
                    seconds=np.round(cfg['FFTS_ON'] * scale_factor))
            t_axis_full_seconds = np.arange(
                max_t_ind) * scale_factor + system_delay_samps_FD / fs
            t_axis_full_timestamps = burst['G'][0]['timestamp'] - cfg[
                'FFTS_ON'] * scale_factor + t_axis_full_seconds

        # Spectrogram color limits
        clims = [-96, 0]

        # Log-scaled magnitudes
        Emag = 20 * np.log10(np.abs(E))
        Emag[np.isinf(Emag)] = -100
        Bmag = 20 * np.log10(np.abs(B))
        Bmag[np.isinf(Bmag)] = -100
        # print(np.max(Emag), np.max(Bmag))
        # Spaced spectrogram -- insert nans (or -120 for a blue background) in the empty spaces
        E_spec_full = -120 * np.ones([max_t_ind, 512])
        B_spec_full = -120 * np.ones([max_t_ind, 512])

        a, b = np.meshgrid(t_inds, freq_inds)

        E_spec_full[a, b] = Emag
        B_spec_full[a, b] = Bmag
        E_spec_full = E_spec_full.T
        B_spec_full = B_spec_full.T

        # Plots!
        pe = E_FD.pcolormesh(t_axis_full_timestamps,
                             f_axis_full / 1000,
                             E_spec_full,
                             cmap=cm,
                             vmin=e_clims[0],
                             vmax=e_clims[1])
        pb = B_FD.pcolormesh(t_axis_full_timestamps,
                             f_axis_full / 1000,
                             B_spec_full,
                             cmap=cm,
                             vmin=b_clims[0],
                             vmax=b_clims[1])

        # Axis labels and ticks. Label the burst start time, and the GPS timestamps.
        xtix = [t_axis_full_timestamps[0]]
        xtix.extend([x['timestamp'] for x in burst['G']])
        minorticks = np.arange(np.ceil(t_axis_full_timestamps[0]),
                               t_axis_full_timestamps[-1],
                               5)  # minor tick marks -- 5 seconds
        E_FD.set_xticks(xtix)
        E_FD.set_xticks(minorticks, minor=True)
        B_FD.set_xticks(xtix)
        B_FD.set_xticks(minorticks, minor=True)
        E_FD.set_xticklabels([])
        B_FD.set_xticklabels([
            datetime.datetime.utcfromtimestamp(x).strftime("%H:%M:%S")
            for x in xtix
        ])

        fig.autofmt_xdate()

        ce = fig.colorbar(pe, cax=cb1)
        cb = fig.colorbar(pb, cax=cb2)

        E_FD.set_ylim([0, 40])
        B_FD.set_ylim([0, 40])

        E_FD.set_ylabel('E\n Frequency [kHz]')
        B_FD.set_ylabel('B\n Frequency [kHz]')

        # ce.set_label('dBFS')
        # cb.set_label('dBFS')
        ce.set_label(f'dB[{E_unit_string}]')
        cb.set_label(f'dB[{B_unit_string}]')

        # B_FD.set_xlabel('Time [sec from start]')
        B_FD.set_xlabel("Time (H:M:S) on \n%s" %
                        start_timestamp.strftime("%Y-%m-%d"))

        # fig.suptitle(f'Burst {ind}\n{start_timestamp}')
        if bbr_config:
            fig.suptitle(
                'Frequency-Domain Burst\n%s - n = %d, %d on / %d off\nE gain = %s, E filter = %s, B gain = %s, B filter = %s'
                % (start_timestamp, cfg['burst_pulses'], sec_on, sec_off,
                   bbr_config['E_GAIN'], bbr_config['E_FILT'],
                   bbr_config['B_GAIN'], bbr_config['B_FILT']))
        else:
            fig.suptitle(
                'Frequency-Domain Burst\n%s - n = %d, %d on / %d off' %
                (start_timestamp, cfg['burst_pulses'], sec_on, sec_off))
def plot_burst_TD(fig, burst, cal_data=None):

    logger = logging.getLogger("plot_burst_TD")

    # # --------------- Latex Plot Beautification --------------------------
    # fig_width = 10
    # fig_height = 8
    # fig_size =  [fig_width+1,fig_height+1]
    # params = {'backend': 'ps',
    #           'axes.labelsize': 12,
    #           'font.size': 12,
    #           'legend.fontsize': 10,
    #           'xtick.labelsize': 10,
    #           'ytick.labelsize': 10,
    #           'text.usetex': False,
    #           'figure.figsize': fig_size}
    # plt.rcParams.update(params)
    # # --------------- Latex Plot Beautification --------------------------

    # if cal_file:
    #     try:
    #         with open(cal_file,'rb') as file:
    #             logger.debug(f'loading calibration file {cal_file}')
    #             cal_data = pickle.load(file)
    #     except:
    #         logger.warning(f'Failed to load calibration file {cal_file}')
    #         cal_file = None

    # A list of bursts!
    # for ind, burst in enumerate(B_data):
    # for burst in [B_data[1]]:
    cfg = burst['config']

    logger.info(f'burst configuration: {cfg}')

    system_delay_samps_TD = 73
    system_delay_samps_FD = 200
    fs = 80000
    # cm = plt.cm.jet
    cm = parula()
    # This is a mockup of the current Matlab colormap (which is proprietary)

    # Check if we have any status packets included -- we'll get
    # the uBBR configuration from these.
    if 'bbr_config' in burst:
        bbr_config = burst['bbr_config']
    elif 'I' in burst:
        logger.debug(f"Found {len(burst['I'])} status packets")
        # Get uBBR config command:
        if 'prev_bbr_command' in burst['I'][0]:
            bbr_config = decode_uBBR_command(burst['I'][0]['prev_bbr_command'])
        else:
            ps = decode_status([burst['I'][0]])
            bbr_config = decode_uBBR_command(ps[0]['prev_bbr_command'])
        logger.debug(f'bbr config is: {bbr_config}')
    else:
        logger.warning(f'No bbr configuration found')
        bbr_config = None

    # ---------- Calibration coefficients ------
    """
    ADC_max_value = 32768. # 16 bits, twos comp
    ADC_max_volts = 1.0    # ADC saturates at +- 1 volt

    E_coef = ADC_max_volts/ADC_max_value  # [Volts at ADC / ADC bin]
    B_coef = ADC_max_volts/ADC_max_value

    if cal_data and bbr_config:
        td_lims = [-1, 1]
        E_cal_curve = cal_data[('E',bool(bbr_config['E_FILT']), bool(bbr_config['E_GAIN']))]
        B_cal_curve = cal_data[('B',bool(bbr_config['B_FILT']), bool(bbr_config['B_GAIN']))]
        E_coef *= 1000.0/max(E_cal_curve) # [(mV/m) / Vadc]
        B_coef *= 1.0/max(B_cal_curve) # [(nT) / Vadc]
        E_unit_string = 'mV/m @ Antenna'
        B_unit_string = 'nT'

        logger.debug(f'E calibration coefficient is {E_coef} mV/m per bit')
        logger.debug(f'B calibration coefficient is {B_coef} nT per bit')
    else:
        E_unit_string = 'V @ ADC'
        B_unit_string = 'V @ ADC'
    """
    E_unit_string = 'uV/m'  # now calibrated to uV/m

    # Scale the spectrograms -- A perfect sine wave will have ~-3dB amplitude.
    # Scaling covers 14 bits of dynamic range, with a maximum at each channel's theoretical peak
    #clims = np.array([-6*14, -3]) #[-96, -20]
    #e_clims = clims + 20*np.log10(E_coef*ADC_max_value/ADC_max_volts)
    #b_clims = clims + 20*np.log10(B_coef*ADC_max_value/ADC_max_volts)

    e_clims = np.array([-40, 10])  # for newly calibrated data

    E_coef = burst['CAL']  # calibrate into uV/m units
    #print(E_coef)
    B_coef = 1  # just so we don't have to comment a bunch of stuff out

    # Generate time axis
    if cfg['TD_FD_SELECT'] == 1:
        # --------- Time domain plots  -----------
        # fig = plt.figure()
        fig.set_size_inches(10, 8)
        #gs = GridSpec(2, 2, height_ratios=[1.25,1], wspace = 0.2, hspace = 0.25)
        gs = GridSpec(1, 1)

        #E_TD = fig.add_subplot(gs[0,0])
        # B_TD = fig.add_subplot(gs[1,0], sharex=E_TD)
        E_FD = fig.add_subplot(gs[0])
        # B_FD = fig.add_subplot(gs[1,1], sharex=E_FD)
        # cb1  = fig.add_subplot(gs[0,2])
        # cb2  = fig.add_subplot(gs[1,2])

        # add in burst map to plot -- werid workaround but it fixed it
        #map_ax = fig.add_subplot(gs[1,0:2])
        #box = map_ax.get_position()
        #box.x0 = box.x0 - 0.13
        #box.x1 = box.x1 - 0.13
        #map_ax.set_position(box)
        #gstr = plot_burst_map(map_ax, burst['G'], burst)

        #fig.text(0.68, 0.12, gstr, fontsize='9') # ha='center', va='bottom')

        # Construct the appropriate time and frequency axes
        # Get the equivalent sample rate, if decimated
        if cfg['DECIMATE_ON'] == 1:
            fs_equiv = 80000. / cfg['DECIMATION_FACTOR']
        else:
            fs_equiv = 80000.

        if cfg['SAMPLES_OFF'] == 0:
            max_ind = max(len(burst['E']), len(burst['B']))
            t_axis = np.arange(max_ind) / fs_equiv
        else:

            # Seconds from the start of the burst
            t_axis = np.array([(np.arange(cfg['SAMPLES_ON']))/fs_equiv +\
                          (k*(cfg['SAMPLES_ON'] + cfg['SAMPLES_OFF']))/fs_equiv for k in range(cfg['burst_pulses'])]).ravel()
        #print(len(burst['E']))

        # Add in system delay
        t_axis += system_delay_samps_TD / fs_equiv

        # Get the timestamp at the beginning of the burst.
        # GPS timestamps are taken at the end of each contiguous recording.
        # (I think "samples on" is still undecimated, regardless if decimation is being used...)
        try:
            start_timestamp = datetime.datetime.utcfromtimestamp(
                burst['G'][0]['timestamp']) - datetime.timedelta(
                    seconds=float(cfg['SAMPLES_ON'] / fs))
            #print(start_timestamp)
        except:
            start_timestamp = datetime.datetime.utcfromtimestamp(
                burst['header_timestamp'])

        #if start_timestamp.year == 1980:
        #    # error in GPS?
        #    start_timestamp = datetime.datetime.utcfromtimestamp(burst['header_timestamp'])

        # the "samples on" and "samples off" values are counting at the full rate, not the decimated rate.
        sec_on = cfg['SAMPLES_ON'] / fs
        sec_off = cfg['SAMPLES_OFF'] / fs

        # add in a signal - 20 uV/m
        extra_signal = 0.3 * np.sin(np.array(t_axis) * 2 * np.pi * 3e3)
        #E_TD.plot(t_axis[0:len(burst['E'])], (E_coef*burst['E'])+extra_signal)
        #E_TD.plot(t_axis[0:len(burst['E'])], E_coef*burst['E'][0:len(t_axis)])
        # B_TD.plot(t_axis[0:len(burst['B'])], B_coef*burst['B'])

        # E_TD.set_ylim(td_lims)
        # B_TD.set_ylim(td_lims)

        nfft = 1024
        overlap = 0.5
        window = 'hanning'

        if cfg['SAMPLES_OFF'] == 0:
            E_td_spaced = E_coef * burst['E']
            B_td_spaced = B_coef * burst['B']
        else:
            # Insert nans into vector to account for "off" time sections
            E_td_spaced = []
            B_td_spaced = []

            for k in np.arange(cfg['burst_pulses']):
                #if k ==2:
                the_data = E_coef * burst['E'] + extra_signal[:len(burst['E'])]
                E_td_spaced.append(the_data[k * cfg['SAMPLES_ON']:(k + 1) *
                                            cfg['SAMPLES_ON']])
                E_td_spaced.append(np.ones(cfg['SAMPLES_OFF']) * np.nan)

                B_td_spaced.append(B_coef *
                                   burst['B'][k * cfg['SAMPLES_ON']:(k + 1) *
                                              cfg['SAMPLES_ON']])
                B_td_spaced.append(np.ones(cfg['SAMPLES_OFF']) * np.nan)

            E_td_spaced = np.concatenate(E_td_spaced).ravel()
            B_td_spaced = np.concatenate(B_td_spaced).ravel()

        # E spectrogram -- "spectrum" scaling -> V^2; "density" scaling -> V^2/Hz
        ff, tt, FE = scipy.signal.spectrogram(
            E_td_spaced,
            fs=fs_equiv,
            window=window,
            nperseg=nfft,
            noverlap=nfft * overlap,
            mode='psd',
            scaling='density')  # changed to density
        E_S_mag = 20 * np.log10(np.sqrt(FE))
        E_S_mag[np.isinf(E_S_mag)] = -100
        logger.debug(f'E data min/max: {np.min(E_S_mag)}, {np.max(E_S_mag)}')
        # what does pe do?
        pe = E_FD.pcolorfast(tt,
                             ff / 1000,
                             E_S_mag,
                             cmap=cm,
                             vmin=e_clims[0],
                             vmax=e_clims[1])
        cax_divider = make_axes_locatable(E_FD)
        ce_ax = cax_divider.append_axes('right', size='7%', pad='5%')
        ce = fig.colorbar(pe, cax=ce_ax)

        # save output
        #print('FE', np.shape(FE))
        #print('tt', np.shape(tt))
        #print('ff', np.shape(ff))

        #np.savetxt('burstE.txt', FE, delimiter=",")
        #np.savetxt('burstT.txt', tt, delimiter=",")
        #np.savetxt('burstF.txt', ff, delimiter=",")
        #print('E', np.shape(Eoutput))

        # B spectrogram
        ff, tt, FB = scipy.signal.spectrogram(B_td_spaced,
                                              fs=fs_equiv,
                                              window=window,
                                              nperseg=nfft,
                                              noverlap=nfft * overlap,
                                              mode='psd',
                                              scaling='spectrum')
        B_S_mag = 20 * np.log10(np.sqrt(FB))
        B_S_mag[np.isinf(B_S_mag)] = -100
        logger.debug(f'B data min/max: {np.min(B_S_mag)}, {np.max(B_S_mag)}')
        # pb = B_FD.pcolorfast(tt,ff/1000, B_S_mag, cmap = cm, vmin=b_clims[0], vmax=b_clims[1])
        # cb = fig.colorbar(pb, cax=cb2)

        #E_TD.set_ylabel(f'E Amplitude\n[{E_unit_string}]')
        # B_TD.set_ylabel(f'B Amplitude\n[{B_unit_string}]')
        E_FD.set_ylabel('Frequency [kHz]')
        # B_FD.set_ylabel('Frequency [kHz]')
        #E_TD.set_xlabel('Time [sec from start]')
        E_FD.set_xlabel('Time [sec from start]')
        #E_TD.set_xlim([0,20])

        ce.set_label(f'dB[(uV/m)^2/Hz]')
        # cb.set_label(f'dB[{B_unit_string}]')

        #f start_timestamp.year == 1980:
        #        start_timestamp = datetime.datetime.utcfromtimestamp(burst['header_timestamp'])
        start_timestamp = start_timestamp.replace(microsecond=0)
        if bbr_config:
            fig.suptitle(
                'VPM Burst Data\n%s - n = %d, %d on / %d off\nE gain = %s, E filter = %s'
                % (start_timestamp, cfg['burst_pulses'], sec_on, sec_off,
                   burst['GAIN'], burst['FILT']))
        else:
            fig.suptitle(
                'VPM Burst Data\n%s - n = %d, %d on / %d off' %
                (start_timestamp, cfg['burst_pulses'], sec_on, sec_off))
        #E_FD.set_xlim([0,6])
        fig.savefig('burst.svg', format='svg')
def plot_burst_TD(fig, burst, cal_data=None):

    logger = logging.getLogger("plot_burst_TD")

    # # --------------- Latex Plot Beautification --------------------------
    # fig_width = 10
    # fig_height = 8
    # fig_size =  [fig_width+1,fig_height+1]
    # params = {'backend': 'ps',
    #           'axes.labelsize': 12,
    #           'font.size': 12,
    #           'legend.fontsize': 10,
    #           'xtick.labelsize': 10,
    #           'ytick.labelsize': 10,
    #           'text.usetex': False,
    #           'figure.figsize': fig_size}
    # plt.rcParams.update(params)
    # # --------------- Latex Plot Beautification --------------------------

    # if cal_file:
    #     try:
    #         with open(cal_file,'rb') as file:
    #             logger.debug(f'loading calibration file {cal_file}')
    #             cal_data = pickle.load(file)
    #     except:
    #         logger.warning(f'Failed to load calibration file {cal_file}')
    #         cal_file = None

    # A list of bursts!
    # for ind, burst in enumerate(B_data):
    # for burst in [B_data[1]]:
    cfg = burst['config']

    logger.info(f'burst configuration: {cfg}')

    system_delay_samps_TD = 73
    system_delay_samps_FD = 200
    fs = 80000
    # cm = plt.cm.jet
    cm = parula()
    # This is a mockup of the current Matlab colormap (which is proprietary)

    # Check if we have any status packets included -- we'll get
    # the uBBR configuration from these.
    if 'bbr_config' in burst:
        bbr_config = burst['bbr_config']
    elif 'I' in burst:
        logger.debug(f"Found {len(burst['I'])} status packets")
        # Get uBBR config command:
        if 'prev_bbr_command' in burst['I'][0]:
            bbr_config = decode_uBBR_command(burst['I'][0]['prev_bbr_command'])
        else:
            ps = decode_status([burst['I'][0]])
            bbr_config = decode_uBBR_command(ps[0]['prev_bbr_command'])
        logger.debug(f'bbr config is: {bbr_config}')
    else:
        logger.warning(f'No bbr configuration found')
        bbr_config = None

    # ---------- Calibration coefficients ------
    ADC_max_value = 32768.  # 16 bits, twos comp
    ADC_max_volts = 1.0  # ADC saturates at +- 1 volt

    E_coef = ADC_max_volts / ADC_max_value  # [Volts at ADC / ADC bin]
    B_coef = ADC_max_volts / ADC_max_value

    if cal_data and bbr_config:
        td_lims = [-1, 1]
        E_cal_curve = cal_data[('E', bool(bbr_config['E_FILT']),
                                bool(bbr_config['E_GAIN']))]
        B_cal_curve = cal_data[('B', bool(bbr_config['B_FILT']),
                                bool(bbr_config['B_GAIN']))]
        E_coef *= 1000.0 / max(E_cal_curve)  # [(mV/m) / Vadc]
        B_coef *= 1.0 / max(B_cal_curve)  # [(nT) / Vadc]
        E_unit_string = 'mV/m @ Antenna'
        B_unit_string = 'nT'

        logger.debug(f'E calibration coefficient is {E_coef} mV/m per bit')
        logger.debug(f'B calibration coefficient is {B_coef} nT per bit')
    else:
        E_unit_string = 'V @ ADC'
        B_unit_string = 'V @ ADC'

    # Scale the spectrograms -- A perfect sine wave will have ~-3dB amplitude.
    # Scaling covers 14 bits of dynamic range, with a maximum at each channel's theoretical peak
    clims = np.array([-6 * 14, -3])  #[-96, -20]
    e_clims = clims + 20 * np.log10(E_coef * ADC_max_value / ADC_max_volts)
    b_clims = clims + 20 * np.log10(B_coef * ADC_max_value / ADC_max_volts)

    # Generate time axis
    if cfg['TD_FD_SELECT'] == 1:
        # --------- Time domain plots  -----------
        # fig = plt.figure()
        gs = GridSpec(2, 3, width_ratios=[20, 20, 1], wspace=0.2, hspace=0.1)
        E_TD = fig.add_subplot(gs[0, 0])
        B_TD = fig.add_subplot(gs[1, 0], sharex=E_TD)
        E_FD = fig.add_subplot(gs[0, 1], sharex=E_TD)
        B_FD = fig.add_subplot(gs[1, 1], sharex=E_FD)
        cb1 = fig.add_subplot(gs[0, 2])
        cb2 = fig.add_subplot(gs[1, 2])

        # Construct the appropriate time and frequency axes
        # Get the equivalent sample rate, if decimated
        if cfg['DECIMATE_ON'] == 1:
            fs_equiv = 80000. / cfg['DECIMATION_FACTOR']
        else:
            fs_equiv = 80000.

        if cfg['SAMPLES_OFF'] == 0:
            max_ind = max(len(burst['E']), len(burst['B']))
            t_axis = np.arange(max_ind) / fs_equiv
        else:

            # Seconds from the start of the burst
            t_axis = np.array([(np.arange(cfg['SAMPLES_ON']))/fs_equiv +\
                          (k*(cfg['SAMPLES_ON'] + cfg['SAMPLES_OFF']))/fs_equiv for k in range(cfg['burst_pulses'])]).ravel()

        # Add in system delay
        t_axis += system_delay_samps_TD / fs_equiv

        # Get the timestamp at the beginning of the burst.
        # GPS timestamps are taken at the end of each contiguous recording.
        # (I think "samples on" is still undecimated, regardless if decimation is being used...)
        start_timestamp = datetime.datetime.utcfromtimestamp(
            burst['G'][0]['timestamp']) - datetime.timedelta(
                seconds=float(cfg['SAMPLES_ON'] / fs))

        # the "samples on" and "samples off" values are counting at the full rate, not the decimated rate.
        sec_on = cfg['SAMPLES_ON'] / fs
        sec_off = cfg['SAMPLES_OFF'] / fs

        E_TD.plot(t_axis[0:len(burst['E'])], E_coef * burst['E'])
        B_TD.plot(t_axis[0:len(burst['B'])], B_coef * burst['B'])

        # E_TD.set_ylim(td_lims)
        # B_TD.set_ylim(td_lims)

        nfft = 1024
        overlap = 0.5
        window = 'hanning'

        if cfg['SAMPLES_OFF'] == 0:
            E_td_spaced = E_coef * burst['E']
            B_td_spaced = B_coef * burst['B']
        else:
            # Insert nans into vector to account for "off" time sections
            E_td_spaced = []
            B_td_spaced = []

            for k in np.arange(cfg['burst_pulses']):
                E_td_spaced.append(E_coef *
                                   burst['E'][k * cfg['SAMPLES_ON']:(k + 1) *
                                              cfg['SAMPLES_ON']])
                E_td_spaced.append(np.ones(cfg['SAMPLES_OFF']) * np.nan)
                B_td_spaced.append(B_coef *
                                   burst['B'][k * cfg['SAMPLES_ON']:(k + 1) *
                                              cfg['SAMPLES_ON']])
                B_td_spaced.append(np.ones(cfg['SAMPLES_OFF']) * np.nan)

            E_td_spaced = np.concatenate(E_td_spaced).ravel()
            B_td_spaced = np.concatenate(B_td_spaced).ravel()

        # E spectrogram -- "spectrum" scaling -> V^2; "density" scaling -> V^2/Hz
        ff, tt, FE = scipy.signal.spectrogram(E_td_spaced,
                                              fs=fs_equiv,
                                              window=window,
                                              nperseg=nfft,
                                              noverlap=nfft * overlap,
                                              mode='psd',
                                              scaling='spectrum')
        E_S_mag = 20 * np.log10(np.sqrt(FE))
        E_S_mag[np.isinf(E_S_mag)] = -100
        logger.debug(f'E data min/max: {np.min(E_S_mag)}, {np.max(E_S_mag)}')
        pe = E_FD.pcolorfast(tt,
                             ff / 1000,
                             E_S_mag,
                             cmap=cm,
                             vmin=e_clims[0],
                             vmax=e_clims[1])
        ce = fig.colorbar(pe, cax=cb1)

        # B spectrogram
        ff, tt, FB = scipy.signal.spectrogram(B_td_spaced,
                                              fs=fs_equiv,
                                              window=window,
                                              nperseg=nfft,
                                              noverlap=nfft * overlap,
                                              mode='psd',
                                              scaling='spectrum')
        B_S_mag = 20 * np.log10(np.sqrt(FB))
        B_S_mag[np.isinf(B_S_mag)] = -100
        logger.debug(f'B data min/max: {np.min(B_S_mag)}, {np.max(B_S_mag)}')
        pb = B_FD.pcolorfast(tt,
                             ff / 1000,
                             B_S_mag,
                             cmap=cm,
                             vmin=b_clims[0],
                             vmax=b_clims[1])
        cb = fig.colorbar(pb, cax=cb2)

        E_TD.set_ylabel(f'E Amplitude\n[{E_unit_string}]')
        B_TD.set_ylabel(f'B Amplitude\n[{B_unit_string}]')
        E_FD.set_ylabel('Frequency [kHz]')
        B_FD.set_ylabel('Frequency [kHz]')
        B_TD.set_xlabel('Time [sec from start]')
        B_FD.set_xlabel('Time [sec from start]')

        ce.set_label(f'dB[{E_unit_string}]')
        cb.set_label(f'dB[{B_unit_string}]')

        if bbr_config:
            fig.suptitle(
                'Time-Domain Burst\n%s - n = %d, %d on / %d off\nE gain = %s, E filter = %s, B gain = %s, B filter = %s'
                % (start_timestamp, cfg['burst_pulses'], sec_on, sec_off,
                   bbr_config['E_GAIN'], bbr_config['E_FILT'],
                   bbr_config['B_GAIN'], bbr_config['B_FILT']))
        else:
            fig.suptitle(
                'Time-Domain Burst\n%s - n = %d, %d on / %d off' %
                (start_timestamp, cfg['burst_pulses'], sec_on, sec_off))
def plot_burst_incomplete(fig, burst, cal_data=None):

    logger = logging.getLogger("plot_burst_TD")

    cfg = burst['config']

    logger.info(f'burst configuration: {cfg}')

    system_delay_samps_TD = 73
    system_delay_samps_FD = 200
    fs = 80000
    # cm = plt.cm.jet
    cm = parula()
    # This is a mockup of the current Matlab colormap (which is proprietary)

    # Check if we have any status packets included -- we'll get
    # the uBBR configuration from these.
    if 'bbr_config' in burst:
        bbr_config = burst['bbr_config']
    elif 'I' in burst:
        logger.debug(f"Found {len(burst['I'])} status packets")
        # Get uBBR config command:
        if 'prev_bbr_command' in burst['I'][0]:
            bbr_config = decode_uBBR_command(burst['I'][0]['prev_bbr_command'])
        else:
            ps = decode_status([burst['I'][0]])
            bbr_config = decode_uBBR_command(ps[0]['prev_bbr_command'])
        logger.debug(f'bbr config is: {bbr_config}')
    else:
        logger.warning(f'No bbr configuration found')
        bbr_config = None

    E_unit_string = 'uV/m'  # now calibrated to uV/m

    # Scale the spectrograms -- A perfect sine wave will have ~-3dB amplitude.
    # Scaling covers 14 bits of dynamic range, with a maximum at each channel's theoretical peak
    #clims = np.array([-6*14, -3]) #[-96, -20]
    #e_clims = clims + 20*np.log10(E_coef*ADC_max_value/ADC_max_volts)
    #b_clims = clims + 20*np.log10(B_coef*ADC_max_value/ADC_max_volts)

    e_clims = np.array([-40, 10])  # for newly calibrated data

    E_coef = burst['CAL']  # calibrate into uV/m units
    #print(E_coef)
    B_coef = 1  # just so we don't have to comment a bunch of stuff out

    # Generate time axis
    if cfg['TD_FD_SELECT'] == 1:
        # --------- Time domain plots  -----------
        # fig = plt.figure()
        fig.set_size_inches(10, 8)
        gs = GridSpec(2, 2, height_ratios=[1.25, 1], wspace=0.2, hspace=0.25)
        E_TD = fig.add_subplot(gs[0, 0])
        # B_TD = fig.add_subplot(gs[1,0], sharex=E_TD)
        E_FD = fig.add_subplot(gs[0, 1], sharex=E_TD)
        # B_FD = fig.add_subplot(gs[1,1], sharex=E_FD)
        # cb1  = fig.add_subplot(gs[0,2])
        # cb2  = fig.add_subplot(gs[1,2])

        # add in burst map to plot -- werid workaround but it fixed it
        map_ax = fig.add_subplot(gs[1, 0:2])
        box = map_ax.get_position()
        box.x0 = box.x0 - 0.13
        box.x1 = box.x1 - 0.13
        map_ax.set_position(box)
        gstr = plot_burst_map(map_ax, burst['G'], burst)

        fig.text(0.68, 0.25, gstr, fontsize='9')  # ha='center', va='bottom')

        # Construct the appropriate time and frequency axes
        # Get the equivalent sample rate, if decimated
        if cfg['DECIMATE_ON'] == 1:
            fs_equiv = 80000. / cfg['DECIMATION_FACTOR']
        else:
            fs_equiv = 80000.

        if cfg['SAMPLES_OFF'] == 0:
            max_ind = max(len(burst['E']), len(burst['B']))
            t_axis = np.arange(max_ind) / fs_equiv
        else:

            # Seconds from the start of the burst
            t_axis = np.array([(np.arange(cfg['SAMPLES_ON']))/fs_equiv +\
                          (k*(cfg['SAMPLES_ON'] + cfg['SAMPLES_OFF']))/fs_equiv for k in range(cfg['burst_pulses'])]).ravel()
        #print(len(burst['E']))

        # Add in system delay
        t_axis += system_delay_samps_TD / fs_equiv

        # Get the timestamp at the beginning of the burst.
        # GPS timestamps are taken at the end of each contiguous recording.
        # (I think "samples on" is still undecimated, regardless if decimation is being used...)
        try:
            start_timestamp = datetime.datetime.utcfromtimestamp(
                burst['G'][0]['timestamp']) - datetime.timedelta(
                    seconds=float(cfg['SAMPLES_ON'] / fs))
            #print(start_timestamp)
        except:
            start_timestamp = datetime.datetime.utcfromtimestamp(
                burst['header_timestamp'])

        #if start_timestamp.year == 1980:
        #    # error in GPS?
        #    start_timestamp = datetime.datetime.utcfromtimestamp(burst['header_timestamp'])

        # the "samples on" and "samples off" values are counting at the full rate, not the decimated rate.
        sec_on = cfg['SAMPLES_ON'] / fs
        sec_off = cfg['SAMPLES_OFF'] / fs

        #E_TD.plot(t_axis[0:len(burst['E'])], E_coef*burst['E'])
        E_TD.plot(t_axis[0:len(burst['E'])],
                  E_coef * burst['E'][0:len(t_axis)])
        # B_TD.plot(t_axis[0:len(burst['B'])], B_coef*burst['B'])

        # E_TD.set_ylim(td_lims)
        # B_TD.set_ylim(td_lims)

        nfft = 1024
        overlap = 0.5
        window = 'hanning'

        if cfg['SAMPLES_OFF'] == 0:
            E_td_spaced = E_coef * burst['E']
            B_td_spaced = B_coef * burst['B']
        else:
            # Insert nans into vector to account for "off" time sections
            E_td_spaced = []
            B_td_spaced = []

            for k in np.arange(cfg['burst_pulses']):
                E_td_spaced.append(E_coef *
                                   burst['E'][k * cfg['SAMPLES_ON']:(k + 1) *
                                              cfg['SAMPLES_ON']])
                E_td_spaced.append(np.ones(cfg['SAMPLES_OFF']) * np.nan)
                B_td_spaced.append(B_coef *
                                   burst['B'][k * cfg['SAMPLES_ON']:(k + 1) *
                                              cfg['SAMPLES_ON']])
                B_td_spaced.append(np.ones(cfg['SAMPLES_OFF']) * np.nan)

            E_td_spaced = np.concatenate(E_td_spaced).ravel()
            B_td_spaced = np.concatenate(B_td_spaced).ravel()

        # E spectrogram -- "spectrum" scaling -> V^2; "density" scaling -> V^2/Hz
        ff, tt, FE = scipy.signal.spectrogram(
            E_td_spaced,
            fs=fs_equiv,
            window=window,
            nperseg=nfft,
            noverlap=nfft * overlap,
            mode='psd',
            scaling='density')  # changed to density
        E_S_mag = 20 * np.log10(np.sqrt(FE))
        E_S_mag[np.isinf(E_S_mag)] = -100

        logger.debug(f'E data min/max: {np.min(E_S_mag)}, {np.max(E_S_mag)}')

        E_FD.plot(ff / 1000, E_S_mag)
        # what does pe do?
        #pe = E_FD.pcolorfast(tt,ff/1000,E_S_mag, cmap = cm,  vmin=e_clims[0], vmax=e_clims[1])
        #cax_divider = make_axes_locatable(E_FD)
        #ce_ax = cax_divider.append_axes('right', size='7%', pad='5%')
        #ce = fig.colorbar(pe, cax=ce_ax)

        E_TD.set_ylabel(f'E Amplitude\n[{E_unit_string}]')
        E_FD.set_ylabel(f'E Amplitude\n[{E_unit_string}]')
        E_TD.set_xlabel('Time [sec from start]')
        E_FD.set_xlabel('Frequency [kHz]')

        #ce.set_label(f'dB[(uV/m)^2/Hz]')

        #f start_timestamp.year == 1980:
        #        start_timestamp = datetime.datetime.utcfromtimestamp(burst['header_timestamp'])
        start_timestamp = start_timestamp.replace(microsecond=0)
        if bbr_config:
            fig.suptitle(
                'VPM Burst Data\n%s - n = %d, %d on / %d off\nE gain = %s, E filter = %s'
                % (start_timestamp, cfg['burst_pulses'], sec_on, sec_off,
                   burst['GAIN'], burst['FILT']))
        else:
            fig.suptitle(
                'VPM Burst Data\n%s - n = %d, %d on / %d off' %
                (start_timestamp, cfg['burst_pulses'], sec_on, sec_off))