diff --git a/scripts/sigmf_gui.py b/scripts/sigmf_gui.py new file mode 100755 index 0000000..360d30c --- /dev/null +++ b/scripts/sigmf_gui.py @@ -0,0 +1,607 @@ +#!/usr/bin/env python3 +# Copyright 2020 The Johns Hopkins University Applied Physics Laboratory LLC. All Rights Reserved. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from PySimpleGUI import * +from sigmf.sigmffile import SigMFFile, fromarchive, dtype_info +from sigmf.archive import SIGMF_ARCHIVE_EXT +import os +import warnings + +warnings.filterwarnings("error") + +validate_button = Button('Update', bind_return_key=False, enable_events=True) +submit_button = Button('Save Archive', disabled=True, button_color=('white', '#D3D3D3')) +load_button = Button('Load', key='Load Archive') +combo_button = InputCombo((), size=(20, 1), enable_events=True, key='Capture Combo') + + +class Unit: + MHZ = 'MHz' + US = 'us' + DBI = 'dBi' + + @staticmethod + def convert(unit, value: float): + if unit is None: + return input + + if unit == Unit.MHZ: + return value * 1e6 + elif unit == Unit.US: + return value * 1e-6 + else: + return value + + +class WindowElementGroup(): + def __init__(self, element_list, sigmf_tags, req_tags, el_types, el_units, el_tooltip, el_text, el_help, + el_multiline, el_selector, el_checkbox, el_size): + self.element_list = element_list + self.sigmf_tags = sigmf_tags + self.req_tags = req_tags + self.el_types = el_types + self.el_units = el_units + self.el_tooltips = el_tooltip + self.el_text = el_text + self.el_help = el_help + self.el_multiline = el_multiline + self.el_selector = el_selector + self.el_checkbox = el_checkbox + self.el_size = el_size + + def iter(self): + for el in self.element_list: + yield el + + def iter_x(self, x): + next_tuple = () + for el in self.iter(): + next_tuple += (el,) + if len(next_tuple) == x: + yield next_tuple + next_tuple = () + if next_tuple != (): + yield next_tuple + tuple([None] * (x - len(next_tuple))) + + def get_tag(self, key): + return key if key not in self.sigmf_tags else self.sigmf_tags[key] + + def get_el(self, tag): + if tag not in self.sigmf_tags: + return tag + else: + for el, val in self.sigmf_tags: + if val == tag: + return el + raise show_error('Element {} is not in window group key,tag list'.format(tag)) + + +class WindowInput(WindowElementGroup): + DATA_FILE = 'Data File' + OUTPUT_FOLDER = 'Output Folder' + LOAD_PATH = 'Load saved archive' + DATA_TYPE_COMPLEX = 'Complex?' + DATA_TYPE_UNSIGNED = 'Unsigned?' + DATA_TYPE_FIXEDPOINT = 'Fixedpoint?' + DATA_SAMPLE_SIZE = 'Data Sample Size' + DATA_BYTE_ORDER = 'Data Byte Order' + + def __init__(self): + DATA_OFFSET = 'Data Offset' + DESCRIPTION = 'Description' + AUTHOR = 'Author' + DATE = 'Date' + HARDWARE = 'Hardware' + RECEIVER_RF = 'Receiver RF' + RECEIVER_LAT = 'Receiver Lat' + RECEIVER_LON = 'Receiver Lon' + NORM_TECH = 'Normalization Technique' + ANTEN_POL = 'Antenna Polarization' + ANTEN_GAIN = 'Antenna Gain' + + self.core_element_list = [WindowInput.DATA_TYPE_COMPLEX, WindowInput.DATA_TYPE_UNSIGNED, + WindowInput.DATA_TYPE_FIXEDPOINT, WindowInput.DATA_SAMPLE_SIZE, + WindowInput.DATA_BYTE_ORDER, DATA_OFFSET, DESCRIPTION, AUTHOR, DATE] + self.secondary_element_list = [HARDWARE, NORM_TECH, RECEIVER_LAT, ANTEN_POL, RECEIVER_LON, ANTEN_GAIN, + RECEIVER_RF] + self.file_element_list = [WindowInput.DATA_FILE, WindowInput.OUTPUT_FOLDER] + self.partial_component_list = [WindowInput.DATA_TYPE_COMPLEX, WindowInput.DATA_TYPE_UNSIGNED, + WindowInput.DATA_TYPE_FIXEDPOINT, WindowInput.DATA_SAMPLE_SIZE, + WindowInput.DATA_BYTE_ORDER] + sigmf_tags = {DESCRIPTION: SigMFFile.DESCRIPTION_KEY, + AUTHOR: SigMFFile.AUTHOR_KEY, DATE: SigMFFile.DATETIME_KEY, HARDWARE: SigMFFile.HW_KEY, + RECEIVER_RF: SigMFFile.FREQUENCY_KEY, RECEIVER_LAT: SigMFFile.LAT_KEY, + RECEIVER_LON: SigMFFile.LON_KEY} + req_tags = [WindowInput.DATA_FILE, WindowInput.DATA_SAMPLE_SIZE, WindowInput.DATA_BYTE_ORDER, RECEIVER_RF] + el_types = {WindowInput.DATA_TYPE_COMPLEX: bool, WindowInput.DATA_TYPE_UNSIGNED: bool, + WindowInput.DATA_TYPE_FIXEDPOINT: bool, DATA_OFFSET: int, RECEIVER_LAT: float, + RECEIVER_LON: float, ANTEN_GAIN: float, RECEIVER_RF: float} + el_units = {ANTEN_GAIN: Unit.DBI, RECEIVER_RF: Unit.MHZ} + el_tooltip = {DATE: 'YYYY-MM-DD', + DATA_OFFSET: 'int: bit offset from start of data file of first element'} + el_text = {} + el_help = {WindowInput.DATA_BYTE_ORDER: 'Data Type Help'} + el_multiline = [DESCRIPTION] + el_selector = {WindowInput.DATA_SAMPLE_SIZE: [8, 16, 32], + WindowInput.DATA_BYTE_ORDER: ['little endian', 'big endian']} + el_checkbox = [WindowInput.DATA_TYPE_COMPLEX, WindowInput.DATA_TYPE_UNSIGNED, WindowInput.DATA_TYPE_FIXEDPOINT] + el_size = {WindowInput.DATA_TYPE_COMPLEX: (3, 1), WindowInput.DATA_TYPE_UNSIGNED: (3, 1), + WindowInput.DATA_TYPE_FIXEDPOINT: (3, 1), WindowInput.DATA_SAMPLE_SIZE: (4, 1), + WindowInput.DATA_BYTE_ORDER: (15, 1)} + self.first_line_size = 5 + super().__init__(self.core_element_list + self.secondary_element_list, sigmf_tags, req_tags, el_types, el_units, + el_tooltip, el_text, el_help, el_multiline, el_selector, el_checkbox, el_size) + + def iter_core(self): + for el in self.core_element_list: + yield el + + def iter_secondary(self): + for el in self.secondary_element_list: + yield el + + def iter_x_secondary(self, x): + next_tuple = () + for el in self.iter_secondary(): + next_tuple += (el,) + if len(next_tuple) == x: + yield next_tuple + next_tuple = () + if next_tuple != (): + yield next_tuple + tuple([None] * (x - len(next_tuple))) + + +class CaptureData(WindowElementGroup): + START_INDEX = 'Start Index' + + def __init__(self): + SAMPLING_RATE = 'Sampling Rate' + EMITTER = 'Emitter' + MODE_OF_OPERATION = 'Mode(s) of Operation' + SIGNAL_RF = 'Signal RF' + SIGNAL_BANDWIDTH = 'Signal Bandwidth' + MODULATION = 'Modulation' + PRF = 'PRF' + STAGGER = 'Stagger' + FREQUENCY_HOPPING = 'Frequency Hopping' + PW = 'Pulse Width' + BEAM_PATTERN = 'Observed Beam Pattern' + SNR = 'Observed SNR' + CHIP_RATE = 'Chip Rate' + + COMMENT = 'Comment' + + self.annotation_element_list = [COMMENT] + element_list = [CaptureData.START_INDEX, SAMPLING_RATE, EMITTER, MODE_OF_OPERATION, SIGNAL_RF, SIGNAL_BANDWIDTH, + MODULATION, PRF, STAGGER, FREQUENCY_HOPPING, PW, BEAM_PATTERN, SNR, + CHIP_RATE] + self.annotation_element_list + + sigmf_tags = {CaptureData.START_INDEX: SigMFFile.START_INDEX_KEY, SAMPLING_RATE: SigMFFile.SAMPLE_RATE_KEY, + COMMENT: SigMFFile.COMMENT_KEY} + req_tags = [CaptureData.START_INDEX, SAMPLING_RATE] + el_types = {CaptureData.START_INDEX: int, SAMPLING_RATE: float, SIGNAL_RF: float, SIGNAL_BANDWIDTH: float, + PRF: float, PW: float, SNR: float, CHIP_RATE: float} + el_units = {SIGNAL_RF: Unit.MHZ, PW: Unit.US, SIGNAL_BANDWIDTH: Unit.MHZ} + el_tooltip = {CaptureData.START_INDEX: 'int: start index in file of capture'} + el_text = {} + el_help = {} + el_multiline = [] + el_selector = {} + el_checkbox = [] + el_size = {} + super().__init__(element_list, sigmf_tags, req_tags, el_types, el_units, el_tooltip, el_text, el_help, + el_multiline, el_selector, el_checkbox, el_size) + + +def update_dictionary(dictionary, key, val): + dictionary[key] = val + + +def add_sigmf_field(funct, values, field_name, *args, required=False, type=None, unit=None, **kwargs): + input = str(values[field_name]) if field_name in values else '' + print(args) + print(input) + print(kwargs) + if input != '': + if type == int: + if '.' in input: + show_error('Expected an integer for: {}'.format(field_name)) + return False + try: + input = int(input) + except: + show_error('Expected an integer for: {}'.format(field_name)) + return False + elif type == float: + try: + input = float(input) + except: + show_error('Expected a double for: {}'.format(field_name)) + return False + elif type == bool: + try: + if input != 'False' and input != 'True': + raise ValueError('Unexpected input for boolean') + input = True if input == 'True' else False + except: + show_error('Expected a bool for: {}'.format(field_name)) + return False + Unit.convert(unit, input) + try: + if kwargs: + funct(*args, input, **kwargs) + else: + funct(*args, input) + except UserWarning as w: + Popup('Warning: '.format(repr(w)), title='Warning') + except Exception as e: + show_error(repr(e)) + return False + elif required: + show_error('Missing required field: {}'.format(field_name)) + return False + return True + + +def show_error(message): + PopupError(message, title='Error', line_width=60) + + +def validate_data(file): + isValid = file.validate() + print('Valid: ', isValid) + if not isValid: + show_error('Metadata not valid: ' + isValid.error) + submit_button.Update(disabled=True) + return False + else: + PopupOK('Data is valid\n', file.dumps(pretty=True), title='') + return True + + +def update_capture_screen(capture_data_input, capture_text_blocks, capture_dict): + for el in capture_data_input.iter(): + tag = capture_data_input.get_tag(el) + if capture_dict is not None and tag in capture_dict: + capture_text_blocks[el].Update(value=capture_dict[tag]) + else: + capture_text_blocks[el].Update(value='') + + +def update_global_screen(window_data_input, window_text_blocks, window_dict, archive): + data_type = window_dict[SigMFFile.DATATYPE_KEY] + data_info = dtype_info(data_type) + sample_size = 32 if '32' in data_type else 16 if '16' in data_type else 8 if '8' in data_type else None + assert sample_size is not None + window_text_blocks[WindowInput.DATA_TYPE_FIXEDPOINT].Update(bool(data_info['is_fixedpoint'])) + window_text_blocks[WindowInput.DATA_TYPE_UNSIGNED].Update(bool(data_info['is_unsigned'])) + window_text_blocks[WindowInput.DATA_TYPE_COMPLEX].Update(bool(data_info['is_complex'])) + window_text_blocks[WindowInput.DATA_SAMPLE_SIZE].Update(sample_size) + window_text_blocks[WindowInput.DATA_BYTE_ORDER].Update( + 'little endian' if '<' in str(data_info['sample_dtype']) else 'big endian') + + for el in window_data_input.iter(): + if el in window_data_input.partial_component_list: + continue + tag = window_data_input.get_tag(el) + if window_dict is not None and tag in window_dict: + window_text_blocks[el].Update(value=window_dict[tag]) + else: + window_text_blocks[el].Update(value='') + window_text_blocks[WindowInput.DATA_FILE].Update(value=archive.data_file) + + +def add_capture(capture_data_input, values, capture_selector_dict, file_data, from_archive=False): + capture_dict = dict() + added = True + for el in capture_data_input.iter(): + req_field = True if el in capture_data_input.req_tags else False + el_type = capture_data_input.el_types.get(el, None) + el_unit = capture_data_input.el_units.get(el, None) + field = capture_data_input.get_tag(el) if from_archive else el + added = added and add_sigmf_field(update_dictionary, values, field, capture_dict, + capture_data_input.get_tag(el), + required=req_field, type=el_type, unit=el_unit) + if not added: + # requirement not given + return False + annotation_dict = {capture_data_input.get_tag(CaptureData.START_INDEX): capture_dict[ + capture_data_input.get_tag(CaptureData.START_INDEX)]} + tmp_capture_subset = {} + for field in capture_data_input.element_list + [CaptureData.START_INDEX]: + tag = capture_data_input.get_tag(field) + if tag in capture_dict: + if field in capture_data_input.annotation_element_list: + annotation_dict[tag] = capture_dict[tag] + else: + tmp_capture_subset[tag] = capture_dict[tag] + + add_capture = True + for capture in file_data.get_captures(): + if int(capture[capture_data_input.get_tag(CaptureData.START_INDEX)]) == int( + capture_dict[capture_data_input.get_tag(CaptureData.START_INDEX)]): + add_capture = False + break + if add_capture: + add_sigmf_field(SigMFFile.add_capture, tmp_capture_subset, capture_data_input.get_tag(CaptureData.START_INDEX), + file_data, + metadata=tmp_capture_subset, type=capture_data_input.el_types[CaptureData.START_INDEX]) + + capture_length = 0 + try: + capture_length = file_data._count_samples() + except Exception as e: + show_error('{}\n{}'.format(repr(e), 'In call - count_samples()')) + if capture_length < 1: + show_error('No samples in file. Make sure you have selected your input data file') + return False + + add_sigmf_field(SigMFFile.add_annotation, annotation_dict, capture_data_input.get_tag(CaptureData.START_INDEX), + file_data, + length=capture_length, metadata=annotation_dict, + type=capture_data_input.el_types[CaptureData.START_INDEX]) + + new_values = combo_button.Values + new_val = 'Capture {}'.format(capture_dict[capture_data_input.get_tag(CaptureData.START_INDEX)]) + capture_selector_dict.update({new_val: capture_dict}) + if new_val not in list(new_values): + new_values = list(new_values) + [new_val] + combo_button.Update(values=tuple(new_values), value=new_val) + + +def run_gui(): + window_input = WindowInput() + capture_data_input = CaptureData() + capture_text_blocks = dict() + window_text_blocks = dict() + f = SigMFFile() + capture_selector_dict = dict() + + layout = [[Text('This is the APL SIGMF tool to archive RF datasets', size=(80, 1))], + [Text('Enter your data and signal captures below. You must include', auto_size_text=True), + Text('required', text_color='red', font=DEFAULT_FONT + ('italic',), auto_size_text=True), + Text('fields.', size=(50, 1), auto_size_text=True)], + [Text('_' * 150, auto_size_text=True)]] + + layout.append([Text('Global Data', font=('Arial', 12, 'bold'))]) + num_components = 0 + line = [] + for el in window_input.iter_core(): + size = (30, 1) if len(line) == 0 else (None, None) + auto_size = True if len(line) == 0 else (10, 1) + line.extend([Text(el, justification='right', size=size, + text_color='red' if el in window_input.req_tags else None, auto_size_text=auto_size)]) + if el in window_input.el_multiline: + window_text_blocks.update({el: Multiline(window_input.el_text.get(el, ''), key=el, + tooltip=window_input.el_tooltips.get(el, None), size=(30, 2))}) + elif el in window_input.el_selector: + window_text_blocks.update({el: Combo(values=window_input.el_selector[el], key=el, + size=window_input.el_size.get(el, (None, None)))}) + elif el in window_input.el_checkbox: + window_text_blocks.update({el: Checkbox(window_input.el_text.get(el, ''), key=el, + size=window_input.el_size.get(el, (None, None)))}) + else: + window_text_blocks.update({el: InputText(window_input.el_text.get(el, ''), key=el, + tooltip=window_input.el_tooltips.get(el, None))}) + line.append(window_text_blocks[el]) + + if el in window_input.el_units: + line.append(Text(window_input.el_units[el])) + + num_components += 1 + if num_components < window_input.first_line_size: + continue + layout.append(line) + line = [] + + for el1, el2 in window_input.iter_x_secondary(2): + line = [] + for el, size in zip([el1, el2], [30, 22]): + if el is None: + continue + color = 'red' if el in window_input.req_tags else None + window_text_blocks.update({el: InputText(window_input.el_text.get(el, ''), key=el, + tooltip=window_input.el_tooltips.get(el, None))}) + line.extend([Text(el, justification='right', size=(size, 1), text_color=color), window_text_blocks[el]]) + if el in window_input.el_units: + line.append(Text(window_input.el_units[el], size=(5, 1))) + else: + line.append(Text('', size=(5, 1))) + layout.append(line) + + layout.extend( + [[Text('_' * 150, auto_size_text=True)], + [Text('Individual Capture Data', font=('Arial', 12, 'bold'))], + [Text('Capture Selector', auto_size_text=True), combo_button, Text('', size=(10, 1)), + Button('Add Capture', enable_events=True), Button('Remove Capture', enable_events=True, size=(15, 1)), + Button('Clear Capture', enable_events=True, size=(15, 1))]] + ) + + for el1, el2, el3 in capture_data_input.iter_x(3): + line = [] + for el in [el1, el2, el3]: + if el is None: + continue + capture_text_blocks.update({el: InputText(key=el, tooltip=capture_data_input.el_tooltips.get(el, None))}) + color = 'red' if el in capture_data_input.req_tags else None + line.extend([Text(el, justification='right', size=(20, 1), text_color=color), capture_text_blocks[el]]) + if el in capture_data_input.el_units: + line.append(Text(capture_data_input.el_units[el], size=(5, 1))) + else: + line.append(Text('', size=(5, 1))) + layout.append(line) + + window_text_blocks.update( + {WindowInput.DATA_FILE: InputText('', key=WindowInput.DATA_FILE)}) + layout.extend( + [[Text('_' * 150, auto_size_text=True)], + [Text('Data Location', font=('Arial', 12, 'bold'))], + [Text(WindowInput.DATA_FILE, size=(30, 1), justification='right', text_color='red'), + window_text_blocks[WindowInput.DATA_FILE], FileBrowse()], + [Text(WindowInput.OUTPUT_FOLDER, size=(30, 1), justification='right'), + InputText('', key=WindowInput.OUTPUT_FOLDER), FolderBrowse(), submit_button], + [Text(WindowInput.LOAD_PATH, size=(30, 1), justification='right'), InputText('', key=WindowInput.LOAD_PATH), + FileBrowse(file_types=(("Archive Files", "*.sigmf"),)), load_button], + [validate_button, Button('View Data')]] + ) + + window = Window('APL SigMF Archive Creator', + auto_size_buttons=False, + default_element_size=(20, 1), + auto_size_text=False, + default_button_element_size=(10, 1) + ).Layout(layout) + + while True: + validate_button.Update(text='Update') + load_button.Update(text='Load') + submit_button.Update(text='Save Archive') + + window.Refresh() + event, values = window.Read() + print(event, values) + if event == 'Load Archive': + load_path = values[WindowInput.LOAD_PATH] + if load_path == '': + show_error('No archive file provided') + continue + + load_button.Update(text='Loading...') + window.Refresh() + print('reading from ', values[WindowInput.LOAD_PATH]) + f = fromarchive(values[WindowInput.LOAD_PATH]) + update_global_screen(window_input, window_text_blocks, f.get_global_info(), f) + capture_selector_dict = {} + for capture in f.get_captures(): + add_capture(capture_data_input, capture, capture_selector_dict, f, from_archive=True) + + elif event == 'Data Type Help': + PopupOK( + 'Format: _\n\n' + '\tTypeCharacters:\n' + '\t\tUnsigned data: \"u\"\n' + '\t\tComplex data: \"c\"\n' + '\t\tFixedpoint data: \"f\"\n' + '\tElementBitSize:\n' + '\t\t32 bits, 16 bits, or 8 bits\n' + '\tEndianness:\n' + '\t\tl: Little Endian\n' + '\t\tb: Big Endian\n\n\n\n' + 'Example: \"uc32_l\"\n' + 'Unsigned complex data where each element is 32 bits, or 64 bits total, formatted in little endian.', + title='Data Type Help' + ) + elif event == 'Update': + validate_button.Update(text='Validating...') + window.Refresh() + window_data_type_dict = {} + added = True + for el in window_input.iter(): + req_field = True if el in window_input.req_tags else False + el_type = window_input.el_types.get(el, None) + el_unit = window_input.el_units.get(el, None) + if el in window_input.partial_component_list: + added = added and add_sigmf_field(update_dictionary, values, el, window_data_type_dict, + window_input.get_tag(el), required=req_field, + type=el_type, unit=el_unit) + else: + added = added and add_sigmf_field(SigMFFile.set_global_field, values, el, f, + window_input.get_tag(el), + required=req_field, type=el_type, unit=el_unit) + + data_type_str = '' + data_type_str += 'c' if bool(window_data_type_dict[WindowInput.DATA_TYPE_COMPLEX]) else '' + data_type_str += 'f' if not bool(window_data_type_dict[WindowInput.DATA_TYPE_FIXEDPOINT]) else '' + data_type_str += 'u' if bool(window_data_type_dict[WindowInput.DATA_TYPE_UNSIGNED]) else '' + data_type_str += str(window_data_type_dict[WindowInput.DATA_SAMPLE_SIZE]) + '_' + data_type_str += 'l' if window_data_type_dict[WindowInput.DATA_BYTE_ORDER] == 'little endian' else 'b' + data_type_dict = {SigMFFile.DATATYPE_KEY: data_type_str} + added = added and add_sigmf_field(SigMFFile.set_global_field, data_type_dict, SigMFFile.DATATYPE_KEY, f, + SigMFFile.DATATYPE_KEY, required=True) + print('HERE: ', window_data_type_dict) + added = added and add_sigmf_field(SigMFFile.set_data_file, values, WindowInput.DATA_FILE, f, + required=True) and added + if not added: + # requirement not given + continue + + if validate_data(f): + submit_button.Update(disabled=False, button_color=DEFAULT_BUTTON_COLOR) + elif event == 'Capture Combo': + capture_dict = capture_selector_dict[values['Capture Combo']] + update_capture_screen(capture_data_input, capture_text_blocks, capture_dict) + elif event == 'Add Capture': + add_capture(capture_data_input, values, capture_selector_dict, f) + + elif event == 'Remove Capture': + capture_dict = dict() + added = add_sigmf_field(update_dictionary, values, CaptureData.START_INDEX, capture_dict, + SigMFFile.START_INDEX_KEY, required=True, type=int) + if not added: + # requirement not given + continue + + captures = [] + for capture in f._metadata[SigMFFile.CAPTURE_KEY]: + if capture[SigMFFile.START_INDEX_KEY] != capture_dict[SigMFFile.START_INDEX_KEY]: + captures.append(capture) + f._metadata[SigMFFile.CAPTURE_KEY] = captures + + annotations = [] + for annotation in f._metadata[SigMFFile.ANNOTATION_KEY]: + if annotation[SigMFFile.START_INDEX_KEY] != capture_dict[SigMFFile.START_INDEX_KEY]: + annotations.append(annotation) + f._metadata[SigMFFile.ANNOTATION_KEY] = annotations + + new_values = list(combo_button.Values) + rm_val = 'Capture {}'.format(capture_dict[SigMFFile.START_INDEX_KEY]) + if rm_val in capture_selector_dict: + capture_selector_dict.pop(rm_val) + if rm_val in new_values: + new_values.remove(rm_val) + combo_button.Update(values=new_values, set_to_index=0) + capture_dict = capture_selector_dict.get(combo_button.DefaultValue, None) + update_capture_screen(capture_data_input, capture_text_blocks, capture_dict) + elif event == 'Clear Capture': + update_capture_screen(capture_data_input, capture_text_blocks, None) + elif event == 'View Data': + PopupOK('Current data:\n', f.dumps(pretty=True), title='') + elif event == 'Save Archive': + output_folder = values[WindowInput.OUTPUT_FOLDER] + if output_folder == '': + show_error('No output folder provided') + continue + elif len(capture_selector_dict.keys()) == 0: + show_error('No capture data specified') + submit_button.Update(text='Saving...') + window.Refresh() + archive_file = output_folder + '/' + os.path.basename(f.data_file).split('.')[0] + SIGMF_ARCHIVE_EXT + f.archive(archive_file) + PopupOK('Saved archive as \n', archive_file, title='') + elif event in ['Cancel', None, 'Exit']: + window.Close() + break + + window.Close() + + +if __name__ == '__main__': + run_gui() diff --git a/setup.py b/setup.py index b70352d..d0613b1 100644 --- a/setup.py +++ b/setup.py @@ -33,7 +33,7 @@ ], packages=['sigmf'], package_data = {'sigmf': ['*.json']}, - install_requires=['six', 'numpy'], + install_requires=['six', 'numpy', 'pysimplegui==4.0.0'], setup_requires=['pytest-runner'], tests_require=['pytest>3'], zip_safe=False