diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 163e73c918005daa41d1ba0ba3fe54200b43dd89..65663b8d7d0186e3bdfcada34ebdbcc449b26367 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -54,4 +54,4 @@ python3.10: stage: Static Analysis and Test script: - python -m unittest - coverage: '/TOTAL.+ ([0-9]{1,3}%)/' + coverage: '/TOTAL.+ ([0-9]{1,3}%)/' \ No newline at end of file diff --git a/README.rst b/README.rst index 23aeee58c0cd2d5d92273a5f4e3e2b92d10754c8..055130e1cefc49a0dac0b32f6309aa975a896eaf 100644 --- a/README.rst +++ b/README.rst @@ -9,12 +9,13 @@ lemi2seed - ``lemi2seed -h`` - ``lemi2seed -d path2data -qc`` *(run lemi2seed in "quality control" mode)* - ``lemi2seed -d path2data -g`` *(run lemi2seed in "gui only" mode)* + - ``lemi2seed -d path2data -g -s path2savedmd`` *(run lemi2seed in "gui only" mode after loading previously saved metadata fields)* - ``lemi2seed -d path2data -m path2metadata`` *(run lemi2seed in "field sheet and gui" mode)* The "quality control" mode allows to quickly convert LEMI data to day-long mseed files. The GUI and handling of metadata are bypassed. - In the "gui only" mode, metadata are entered only using the GUI. + In the "gui only" mode, metadata are entered/modified using the GUI only. - In the "field sheet and gui" mode, metadata are prepopulated from parsing the provided field sheets. + In the "field sheet and gui" mode, metadata are prepopulated from parsing the user-provided field sheets and modified using the GUI. - **License**: GNU General Public License v3 (GPLv3) diff --git a/conda.recipe/meta.yaml b/conda.recipe/meta.yaml index da55d97f9aeeeb30cffb4e26904c4553a75a914b..b7a9211f68e78d7005b8ba2d3b7d5ae6a29b048f 100644 --- a/conda.recipe/meta.yaml +++ b/conda.recipe/meta.yaml @@ -1,6 +1,6 @@ package: name: lemi2seed - version: 2022.319 + version: 2023.1.3.1 source: path: ../ diff --git a/lemi2seed/__init__.py b/lemi2seed/__init__.py index ace4ceb11ea9b66810f00d2b4785b782692471bb..8b701fe61092b9575cf7aaf196a473bdb35980c3 100644 --- a/lemi2seed/__init__.py +++ b/lemi2seed/__init__.py @@ -2,6 +2,6 @@ """Top-level package for lemi2seed.""" -__author__ = """IRIS PASSCAL""" +__author__ = """EPIC""" __email__ = 'software-support@passcal.nmt.edu' -__version__ = '2022.319' +__version__ = '2023.1.3.1' diff --git a/lemi2seed/config/config.ini b/lemi2seed/config/config.ini index 1ff472e6fa2ce9d0210cccacb3dff677016ecda0..9b5c45837ea06ea25c6e59741816e71737f91b3e 100644 --- a/lemi2seed/config/config.ini +++ b/lemi2seed/config/config.ini @@ -1,7 +1,7 @@ [software] author = Maeva Pourpoint name = lemi2seed -version = 2022.319 +version = 2023.1.3.1 [sheets] header = A1 @@ -17,16 +17,13 @@ credential_sheet = Credential Sheet for Long-Period Magnetotelluric Instrument [[credential_sheet]] acq_by_author = B4 acq_by_comments = B5 - citation_dataset_doi = B6 - country = B7 - geo_name = B8 - name = B9 - project = B10 - project_lead_author = B11 - project_lead_email = B12 - project_lead_organization = B13 - restricted_status = B15 - summary = B14 + country = B6 + geo_name = B7 + name = B8 + project = B9 + project_lead_author = B10 + project_lead_organization = B11 + summary = B12 [Sta] [[install_sheet]] @@ -35,31 +32,27 @@ credential_sheet = Credential Sheet for Long-Period Magnetotelluric Instrument declination_model = H10 declination_val = H9 elev = G98 + end = B101 lat = C98 lon = E98 orientation_method = D11 orientation_reference_frame = B11 start = B100 [[credential_sheet]] - geo_name = B18 - restricted_status = B22 - submitter_author = B19 - submitter_email = B20 - submitter_organization = B21 - [[removal_sheet]] - comments = - end = + geo_name = B15 + submitter_author = B16 + submitter_organization = B17 [Run] [[install_sheet]] comps_rec = B12 datalogger_sn = B16 [[credential_sheet]] - acq_by_author = B25 - acq_by_comments = B26 - comments = B29 - md_by_author = B27 - md_by_comments = B28 + acq_by_author = B20 + acq_by_comments = B21 + comments = B24 + md_by_author = B22 + md_by_comments = B23 [Elec] [[install_sheet]] @@ -75,9 +68,6 @@ credential_sheet = Credential Sheet for Long-Period Magnetotelluric Instrument inst_specs = B26 meas_azimuth = F40, F41, F42, F43 meas_tilt = H40, H41, H42, H43 - [[removal_sheet]] - contact_resistance_end = - dc_end = [Mag] [[install_sheet]] diff --git a/lemi2seed/data_conversion.py b/lemi2seed/data_conversion.py index 260ddfdf62f691a1a04140efbaa4389756c7345b..27a1fbe9ff50c8f58865d8706d4688793cf7562a 100644 --- a/lemi2seed/data_conversion.py +++ b/lemi2seed/data_conversion.py @@ -90,9 +90,12 @@ def write_daylong_mseed(lemi_data: LemiData) -> None: filename = path2mseed.joinpath('.'.join([tr_day.stats.station, tr_day.stats.network, loc, cha, str(year), str(day).zfill(3)])) if filename.is_file(): - logger.info("The mseed file {} already exists. Merging to it.".format(filename)) + logger.info("The mseed file {} already exists. Merging to " + "it. There will be no duplication of existing data." + .format(filename)) st_day = handle_existing_mseed_file(st_day, filename) st_day.merge(-1) + logger.info("Writing day-long mseed file: {}.".format(filename)) st_day.write(filename, format='MSEED') logger.info("Writing of day-long mseed files successfully completed!") @@ -135,7 +138,7 @@ def write_log_file(lemi_data: LemiData) -> None: def files. """ log_file = get_log_filename(lemi_data) - logger.info("Writing calibration coefficients in log file {}.".format(log_file)) + logger.info("Writing calibration coefficients in log file: {}.".format(log_file)) def_info = parse_def_files(lemi_data.def_files) with open(log_file, 'w') as fout: fout.write(f"{config['software']['name']}: v{config['software']['version']} - " diff --git a/lemi2seed/gui_view.py b/lemi2seed/gui_view.py index f26a5995e2add9f28db9038063a6dc39b911534e..2b382b3213a11b528ac9a82a5ffb1fc554e3e06c 100644 --- a/lemi2seed/gui_view.py +++ b/lemi2seed/gui_view.py @@ -19,6 +19,7 @@ from lemi2seed.gui_widgets import (load_ui, BaseWidget, NetWidgets, get_channel_widgets) from lemi2seed.lemi_metadata import LemiMetadata from lemi2seed.logging import parse_config_ini, setup_logger +from lemi2seed.metadata_category import MSG_MAP from lemi2seed.metadata_conversion import write_stationxml from lemi2seed.utils import get_e_ids, get_run_list, NUM_E_CHA_MAX @@ -135,12 +136,15 @@ class LemiWindow(*load_ui('mainwindow.ui')): for run_id in run_list: self.update_elec_selection_forms(run_id) - def load_md(self): + def load_md(self, path2savedmd=None): """Load previously saved metadata model.""" - filename, _ = QtWidgets.QFileDialog.getOpenFileName(parent=self, - caption='Select metadata file to open', - dir=str(Path.cwd()), - filter='Metadata file (*.pkl)') + if path2savedmd: + filename = str(path2savedmd) + else: + filename, _ = QtWidgets.QFileDialog.getOpenFileName(parent=self, + caption='Select metadata file to open', + dir=str(Path.cwd()), + filter='Metadata file (*.pkl)') if filename: text = "Loading previously saved metadata..." details = ("If you are creating mseed and StationXML files for a " @@ -162,6 +166,12 @@ class LemiWindow(*load_ui('mainwindow.ui')): if filename: self.lemi_md.save_md(filename) + def get_run_widgets(self, run_id): + return getattr(self, f'run_{run_id}_widgets') + + def set_run_widgets(self, run_id, val): + setattr(self, f'run_{run_id}_widgets', val) + def set_num_runs_form(self): """ Set up number of runs form. There is one form for all runs. This form @@ -189,7 +199,7 @@ class LemiWindow(*load_ui('mainwindow.ui')): starttimes = [x['starttime'] for x in self.lemi_data.data_np] # Add 1 to ind_run since run_nums (number of given run) starts at 1 starttime = starttimes[run_nums.index(ind_run + 1)] - starttime_reformat = starttime.datetime.strftime("%m/%d/%Y %H:%M:%S") + starttime_reformat = starttime.datetime.strftime("%Y-%m-%d %H:%M:%S") # Set up layout form layout_run_starttime = QtWidgets.QFormLayout() uiRunStarttime = QtWidgets.QLineEdit() @@ -201,11 +211,78 @@ class LemiWindow(*load_ui('mainwindow.ui')): layout_run_starttime.setFormAlignment(QtCore.Qt.AlignLeft) return layout_run_starttime - def get_run_widgets(self, run_id): - return getattr(self, f'run_{run_id}_widgets') + def submit_run_cha_md(self, run_id, elec_bool=False, mag_bool=False): + """ + Submit run and channel metadata entries provided by the user up to this + point before checking validity of metadata fields to be copied. + Submit policy set in BaseWidget is Manual. + """ + # -- Run -- + run = self.get_run_widgets(run_id) + run.mapper.submit() + # -- Electric -- + if elec_bool: + name = f'run_{run_id}_num_e_pairs' + num_e_pairs_submit = getattr(self, name, NUM_E_CHA_MAX) + for ind in range(1, num_e_pairs_submit + 1): + e_pair = self.get_elec_pair_widgets(run_id, ind) + e_pair.mapper.submit() + # -- Magnetic -- + if mag_bool: + mag = self.get_mag_widgets(run_id) + mag.mapper.submit() - def set_run_widgets(self, run_id, val): - setattr(self, f'run_{run_id}_widgets', val) + def check_before_reset(self, run_id_same_as, run_id, type_): + """ + Check/Validate metadata fields for both runs before copying electric or + magnetic metadata fields from one run to the other. + """ + cha_type = MSG_MAP[type_.capitalize()] + logger.info(f"{'-'*20} Checking metadata fields for run '{run_id_same_as}' " + f"before copying {cha_type} metadata fields from run " + f"'{run_id_same_as}' to run '{run_id}'. Any incomplete or " + "invalid metadata fields may prevent the auto-filling " + "process from succesfully completing. Look for potential" + f"error messages below {'-'*20}.") + self.validate_run(run_id_same_as) + if cha_type != 'run': + validate_method = f"validate_{type_}" + methodcaller(validate_method, run_id_same_as)(self) + logger.info(f"{'-'*20} End of metadata field checks for run " + f"'{run_id_same_as}' {'-'*20}.") + + def reset_run_model(self, run_id_same_as, run_id): + """ + Reset current run metadata model based on a user-selected run id. + Called when selecting the "Same as run" option for run_id > a. + """ + self.submit_run_cha_md(run_id_same_as) + self.check_before_reset(run_id_same_as, run_id, 'run') + md = self.lemi_md.for_gui + key = f'Run_{run_id}' + key_same_as = f'Run_{run_id_same_as}' + md['Run'][key].update(md['Run'][key_same_as]) + model = TreeModel(("Field", "Value"), md) + run = self.get_run_widgets(run_id) + run.set_model(model, f'Run_{run_id}') + + def set_cha_same_as_forms(self, cha_type, run_id): + """ + Set up "same as run" form. + Called when there is more than one run and selecting the "Same as run option. + """ + run_ids = self.run_list[:self.run_list.index(run_id)] + layout_same_as = QtWidgets.QFormLayout() + uiRunIdSameAs = QtWidgets.QComboBox() + uiRunIdSameAs.addItems(run_ids) + for ind, id in enumerate(run_ids): + tip_msg = (f'Same {cha_type} metadata field values as Run {id}.') + uiRunIdSameAs.setItemData(ind, tip_msg, QtCore.Qt.ToolTipRole) + reset_method = f'reset_{cha_type}_model' + uiRunIdSameAs.textActivated.connect(lambda text, val=run_id: methodcaller(reset_method, text, val)(self)) + layout_same_as.addRow('Same as run', uiRunIdSameAs) + layout_same_as.setFormAlignment(QtCore.Qt.AlignLeft) + return layout_same_as def set_run_editor_forms(self): """Set up run editor form. There is one form per run id.""" @@ -217,9 +294,14 @@ class LemiWindow(*load_ui('mainwindow.ui')): self.sta_widgets.uiRunsEditor.addWidget(uiTabs) for run_id in self.run_list: uiRun = QtWidgets.QWidget() - layout_run = QtWidgets.QVBoxLayout() + layout_run_option = QtWidgets.QHBoxLayout() layout_run_starttime = self.set_run_starttime_form(run_id) - layout_run.addLayout(layout_run_starttime) + layout_run_option.addLayout(layout_run_starttime) + if run_id != 'a': + layout_run_same_as = self.set_cha_same_as_forms('run', run_id) + layout_run_option.addLayout(layout_run_same_as) + layout_run = QtWidgets.QVBoxLayout() + layout_run.addLayout(layout_run_option) layout_run.addSpacing(5) self.set_run_widgets(run_id, RunWidgets(self)) layout_run.addWidget(self.get_run_widgets(run_id)) @@ -319,56 +401,18 @@ class LemiWindow(*load_ui('mainwindow.ui')): electrode pairs can be infered from the 'components recorded' metadata field. """ - e_comps = self.lemi_md.get_comps_rec('E', run_id, msg=False) if self.lemi_md.run else [] + e_comps = self.lemi_md.get_comps_rec('E', run_id) if self.lemi_md.run else [] num_e_pairs = len(e_comps) uiNumEPairs = getattr(self, f'run_{run_id}_num_e_pairs_widgets') uiNumEPairs.setCurrentIndex(VALID_NUM_E_PAIRS.index(num_e_pairs)) self.update_elec_pair_forms(num_e_pairs, run_id) - def submit_run_cha_md(self, run_list, elec_bool=False, mag_bool=False): - """Submit run and channel metadata entries.""" - for run_id in run_list: - # -- Run -- - run = self.get_run_widgets(run_id) - run.mapper.submit() - # -- Electric -- - if elec_bool: - name = f'run_{run_id}_num_e_pairs' - num_e_pairs_submit = getattr(self, name, NUM_E_CHA_MAX) - for ind in range(1, num_e_pairs_submit + 1): - e_pair = self.get_elec_pair_widgets(run_id, ind) - e_pair.mapper.submit() - # -- Magnetic -- - if mag_bool: - mag = self.get_mag_widgets(run_id) - mag.mapper.submit() - - def check_before_reset(self, run_id_same_as, run_id, type_): - """ - Check/Validate metadata fields for both runs before copying electric or - magnetic metadata fields from one run to the other. - """ - cha_type = 'electric' if type_ == 'elec' else 'magnetic' - logger.info(f"{'-'*20} Checking metadata fields for runs '{run_id_same_as}' " - f"and '{run_id}' before copying {cha_type} metadata fields " - f"from run '{run_id_same_as}' to run '{run_id}'. Any " - "incomplete or invalid metadata fields may prevent the " - "auto-filling process from succesfully completing. Look " - f"for potential error messages below {'-'*20}.") - self.validate_run(run_id_same_as) - self.validate_run(run_id) - validate_method = f"validate_{type_}" - methodcaller(validate_method, run_id_same_as)(self) - logger.info(f"{'-'*20} End of metadata field checks for runs " - f"'{run_id_same_as}' and '{run_id}' {'-'*20}.") - def reset_elec_model(self, run_id_same_as, run_id): """ - Reset current electric metadata model for a given run id. - Called when there is more than one run and selecting the "Same as run" - option for run_id > a. + Reset current electric metadata model based on a user-selected run id. + Called when selecting the "Same as run" option for run_id > a. """ - self.submit_run_cha_md([run_id_same_as, run_id], elec_bool=True) + self.submit_run_cha_md(run_id_same_as, elec_bool=True) self.check_before_reset(run_id_same_as, run_id, 'elec') md = self.lemi_md.for_gui for ind_cha in range(1, NUM_E_CHA_MAX + 1): @@ -380,25 +424,6 @@ class LemiWindow(*load_ui('mainwindow.ui')): e_pair = self.get_elec_pair_widgets(run_id, ind_cha) e_pair.set_model(model, f'Run_{run_id}_Elec_Pair_{ind_cha}') - def set_cha_same_as_forms(self, cha_type, run_id): - """ - Set up "same as run" form. - Called when there is more than one run and selecting the "Same as run" - option for run id > a. - """ - run_ids = self.run_list[:self.run_list.index(run_id)] - layout_elec_same_as = QtWidgets.QFormLayout() - uiRunIdSameAs = QtWidgets.QComboBox() - uiRunIdSameAs.addItems(run_ids) - for ind, id in enumerate(run_ids): - tip_msg = (f'Same metadata field values as Run {id}.') - uiRunIdSameAs.setItemData(ind, tip_msg, QtCore.Qt.ToolTipRole) - reset_method = f'reset_{cha_type}_model' - uiRunIdSameAs.textActivated.connect(lambda text, val=run_id: methodcaller(reset_method, text, val)(self)) - layout_elec_same_as.addRow('Same as run', uiRunIdSameAs) - layout_elec_same_as.setFormAlignment(QtCore.Qt.AlignLeft) - return layout_elec_same_as - def set_elec_editor_forms(self, ElecWidgets): """ Set up the electric editor form for all runs. @@ -433,11 +458,10 @@ class LemiWindow(*load_ui('mainwindow.ui')): def reset_mag_model(self, run_id_same_as, run_id): """ - Reset current magnetic metadata model for a given run id. - Called when there is more than one run and selecting the "Same as run" - option for run_id > a. + Reset current magnetic metadata model based on a user-selected run id. + Called when selecting the "Same as run" option for run_id > a. """ - self.submit_run_cha_md([run_id_same_as, run_id], mag_bool=True) + self.submit_run_cha_md(run_id_same_as, mag_bool=True) self.check_before_reset(run_id_same_as, run_id, 'mag') md = self.lemi_md.for_gui key = f'Run_{run_id}_Mag' @@ -527,7 +551,6 @@ class LemiWindow(*load_ui('mainwindow.ui')): md_fields_s = self.sta_widgets.model self.lemi_md.populate_sta_props(md_fields_s) num_missing_invalid_s = self.flag_sta_md() - self.num_missing_invalid_s = num_missing_invalid_s return num_missing_invalid_s def flag_run_md(self, run_list): @@ -653,7 +676,7 @@ class LemiWindow(*load_ui('mainwindow.ui')): """ text = "Data and Metadata Conversion Status..." details = ("All required metadata are properly filled out.\n" - "You can now convert you data and metadata to an " + "You can now convert your data and metadata to an " "archivable format!\nConsider saving the inputted " "metadata (⌘S) before proceeding.") icon = QtWidgets.QMessageBox.Information diff --git a/lemi2seed/lemi2seed.py b/lemi2seed/lemi2seed.py index fbf8479632d6b13f821c96130449f091949b3b88..0c30a796db60fe2f2c82fe78cad0e1e0cfaec4c6 100644 --- a/lemi2seed/lemi2seed.py +++ b/lemi2seed/lemi2seed.py @@ -41,16 +41,20 @@ lemi2seed can run under three distinct modes: - gui only mode ('lemi2seed -d path2data -g) - field sheet and gui mode (lemi2seed -d path2data -m path2metadata) The 'quality control' mode allows to quickly convert LEMI data to mseed. The GUI and handling of metadata are bypassed. -In the 'gui only' mode, metadata are entered only using the GUI. -In the 'field sheet and gui' mode, metadata are prepopulated from parsing the provided field sheets.''') +In the 'gui only' mode, metadata are entered/modified using only the GUI. +In the 'field sheet and gui' mode, metadata are prepopulated from parsing the user-provided field sheets and modified using the GUI.''') # noqa: E501 parser.add_argument('-d', dest='path2data', metavar='path2data', - help='''Path to the station where all folders/files generated by the LEMI-424 are stored''') + help='''Path to the station directory where all folders/files generated by the LEMI-424 are stored''') # noqa: E501 parser.add_argument('-m', dest='path2md', metavar='path2md', - help='''Path to LEMI field spreadsheets''') + help='''Path to the directory where all LEMI field spreadsheets are stored''') + parser.add_argument('-s', + dest='path2savedmd', + metavar='path2savedmd', + help='''Path to the .pkl file where metadata were previously saved''') parser.add_argument('-od', dest='output_mseed', metavar='output_mseed', @@ -75,37 +79,36 @@ In the 'field sheet and gui' mode, metadata are prepopulated from parsing the pr dest='gui_only_mode', action='store_true', default=False, - help='''Runs lemi2seed in gui only mode. All metadata are entered using the GUI. -Bypassing the use of field sheets.''') + help='''Runs lemi2seed in gui only mode. All metadata are entered using the GUI +(bypassing the use of field sheets).''') return parser -def get_args() -> Tuple[Path, Optional[Path], bool, bool, Path, Path, Optional[Path]]: - """ - Parse command line arguments and perform a serie of checks on command line - arguments. - """ - # Set up command line interface and parse command line arguments - parser = setup_args() - args = parser.parse_args() - - # Path to LEMI ASCII files +def check_path2data(args: argparse.Namespace) -> Path: + """Check path to data files.""" if args.path2data and Path(args.path2data).is_dir(): path2data = Path(args.path2data).resolve() else: logger.error("Path to LEMI data not provided! Required input! " "Try --help option.") sys.exit(1) + return path2data + - # Running modes and path to LEMI field sheets +def check_running_mode(args: argparse.Namespace) -> Tuple[bool, bool]: + """Check running mode selected by end-user.""" qc_only = args.qc_mode gui_only = args.gui_only_mode if all([qc_only, gui_only]): logger.error("lemi2seed cannot run under both the 'quality control' " - "and 'gui only' mode at the same time. Try --help option " + "and 'gui only' mode at the same time! Try --help option " "or choose one or the other mode.") sys.exit(1) + return qc_only, gui_only + +def check_path2metadata(args: argparse.Namespace, qc_only: bool, gui_only: bool) -> Optional[Path]: + """Check path to metadata file(s).""" if args.path2md is None: if not any([qc_only, gui_only]): logger.error("No metadata path provided. Required input when " @@ -116,13 +119,12 @@ def get_args() -> Tuple[Path, Optional[Path], bool, bool, Path, Path, Optional[P else: if Path(args.path2md).is_dir(): if qc_only: - logger.info("Running lemi2seed in 'quality control' mode. " - "Handling of metadata will be bypassed.") + logger.info("Handling of the field sheets is bypassed when " + "running in 'quality control' mode.") path2md = None elif gui_only: - logger.info("Running lemi2seed in 'gui only' mode. All " - "metadata should be entered using the GUI. " - "Bypassing the use of field sheets.") + logger.info("All metadata should be entered using the GUI in " + "'gui only' mode. Bypassing the use of field sheets.") path2md = None else: path2md = Path(args.path2md).resolve() @@ -131,25 +133,38 @@ def get_args() -> Tuple[Path, Optional[Path], bool, bool, Path, Path, Optional[P "exist!") if any([qc_only, gui_only]): logger.info("In 'quality control' or 'gui only' mode, field " - "sheets are not parsed!") + "sheets are not parsed.") path2md = None else: logger.error("A correct metadata path is a required input " "when running in 'field sheet and gui' mode!") sys.exit(1) - - # Output directories - output_mseed = Path(args.output_mseed).resolve() - check_output_dir(output_mseed, 'day-long mseed') - output_log = Path(args.output_log).resolve() - check_output_dir(output_log, 'log') - if qc_only: - output_xml = None + return path2md + + +def check_path2savedmetadata(args: argparse.Namespace, qc_only: bool, gui_only: bool) -> Optional[Path]: + """Check path to saved metadata file(s).""" + if args.path2savedmd is not None: + if qc_only: + logger.info("Handling of previously saved metadata is bypassed when " + "running in 'quality control' mode.") + path2savedmd = None + elif gui_only: + if not Path(args.path2savedmd).is_file(): + logger.error("The provided path to a previously saved metadata " + "file (*.pkl) does not exist! Bypassing loading " + "of metadata fields before launching the GUI.") + path2savedmd = None + else: + path2savedmd = Path(args.path2savedmd).resolve() + else: + logger.info("The loading of a previously saved metadata file (*.pkl) " + "is bypassed when running in 'field sheet and gui' " + "mode. Try --help option for more information.") + path2savedmd = None else: - output_xml = Path(args.output_xml).resolve() - check_output_dir(output_xml, 'StationXML') - - return path2data, path2md, qc_only, gui_only, output_mseed, output_log, output_xml + path2savedmd = args.path2savedmd + return path2savedmd def check_output_dir(output_dir: Path, output_type: str) -> None: @@ -166,6 +181,36 @@ def check_output_dir(output_dir: Path, output_type: str) -> None: sys.exit(1) +def get_args() -> Tuple[Path, Optional[Path], Optional[Path], bool, bool, Path, Path, Optional[Path]]: + """ + Parse command line arguments and perform a serie of checks on command line + arguments. + """ + # Set up command line interface and parse command line arguments + parser = setup_args() + args = parser.parse_args() + + # Check path to data directory, running modes, path to Phoenix field sheets + # and path to .pkl file + path2data = check_path2data(args) + qc_only, gui_only = check_running_mode(args) + path2md = check_path2metadata(args, qc_only, gui_only) + path2savedmd = check_path2savedmetadata(args, qc_only, gui_only) + + # Check output directories + output_mseed = Path(args.output_mseed).resolve() + check_output_dir(output_mseed, 'day-long mseed') + output_log = Path(args.output_log).resolve() + check_output_dir(output_log, 'log') + if qc_only: + output_xml = None + else: + output_xml = Path(args.output_xml).resolve() + check_output_dir(output_xml, 'StationXML') + + return path2data, path2md, path2savedmd, qc_only, gui_only, output_mseed, output_log, output_xml + + def get_net_inputs() -> str: """Request user to enter 'network code'.""" while True: @@ -319,7 +364,7 @@ def reformat_user_inputs(inputs: Dict) -> str: return msg.strip("\n") -def launch_gui(lemi_data: LemiData, lemi_md: LemiMetadata) -> None: +def launch_gui(lemi_data: LemiData, lemi_md: LemiMetadata, path2savedmd: Path) -> None: """ Open GUI window, set the model data structure and handle metadata processing and archival steps. @@ -327,13 +372,15 @@ def launch_gui(lemi_data: LemiData, lemi_md: LemiMetadata) -> None: """ app = QtWidgets.QApplication(sys.argv) lemi2seed_gui = LemiWindow(lemi_data, lemi_md) + if path2savedmd: + lemi2seed_gui.load_md(path2savedmd) lemi2seed_gui.show() sys.exit(app.exec_()) def main(): # Read command line arguments - path2data, path2md, qc_only, gui_only, output_mseed, output_log, output_xml = get_args() + path2data, path2md, path2savedmd, qc_only, gui_only, output_mseed, output_log, output_xml = get_args() # Parse data files lemi_data = LemiData(path2data, output_mseed, output_log) @@ -361,11 +408,13 @@ def main(): logger.info("Running lemi2seed in 'field sheet and gui' mode.") md_fields = lemi_md.from_field_sheets if md_fields is not None: + logger.info("Pre-populating the GUI metadata fields using the " + "information provided in the field sheet(s).") lemi_md.populate_props(md_fields) else: logger.info("Running lemi2seed in 'gui only' mode.") logger.info("Launching the GUI.") - launch_gui(lemi_data, lemi_md) + launch_gui(lemi_data, lemi_md, path2savedmd) if __name__ == '__main__': diff --git a/lemi2seed/lemi_data.py b/lemi2seed/lemi_data.py index 105420fcb820d7e37bc27464c8d5fc61e5414bb4..f20b586f3a2af7f0bf49cb0f209a8ac1e5448f30 100644 --- a/lemi2seed/lemi_data.py +++ b/lemi2seed/lemi_data.py @@ -14,6 +14,7 @@ import typing from obspy import UTCDateTime from pathlib import Path +from statistics import mean from typing import Dict, List, Optional, TYPE_CHECKING from lemi2seed.logging import setup_logger @@ -286,9 +287,9 @@ class LemiData(): These stats info are used to set the header information for a ObsPy Trace object. """ - self.stats['lat'] = np.mean(self.data["Lat"]) - self.stats['lon'] = np.mean(self.data["Lon"]) - self.stats['elev'] = np.mean(self.data["Elev"]) + self.stats['lat'] = mean(self.data["Lat"]) + self.stats['lon'] = mean(self.data["Lon"]) + self.stats['elev'] = mean(self.data["Elev"]) self.stats['start'] = self.data["Time_stamp"][0] self.stats['end'] = self.data["Time_stamp"][-1] diff --git a/lemi2seed/lemi_metadata.py b/lemi2seed/lemi_metadata.py index 052b788ab3817322c9cfc1e5505701e7ae49f365..289ab57c9698e447a47814acb9ab0fb4397d4824 100644 --- a/lemi2seed/lemi_metadata.py +++ b/lemi2seed/lemi_metadata.py @@ -25,7 +25,7 @@ from typing import Dict, List, Optional, TYPE_CHECKING, Union from lemi2seed.lemi_data import CHA_NAMING_CONV from lemi2seed.logging import parse_config_ini, setup_logger -from lemi2seed.metadata_category import Aux, Elec, Mag, Run, Sta, Net +from lemi2seed.metadata_category import Aux, Elec, Mag, Run, Sta, Net, MSG_MAP from lemi2seed.utils import (is_empty, eval_loc_code, get_e_loc, get_e_ids, get_run_list, str2list, NUM_E_CHA_MAX) @@ -222,19 +222,19 @@ class LemiMetadata(): output[cat] = LemiMetadata.dc2dict(mprop) return output - def parse_field_sheet(self, sheet: Worksheet, sheet_type: str, md_fields: Dict) -> Dict: + def parse_field_sheet(self, filename: Path, sheet: Worksheet, sheet_type: str, md_fields: Dict) -> Dict: """ Parse field sheet using the cell numbers listed in the config file and return a nested dictionary of metadata entries organized based on the category under which a given metadata field falls. """ - logger.info("Parsing the {} {}".format(*sheet_type.split('_'))) + logger.info("Parsing the {} {} - {}.".format(*sheet_type.split('_'), filename)) for cat in self.cats[:-1]: if cat not in md_fields: md_fields[cat] = {} if sheet_type not in config[cat]: continue - logger.info("Parsing '{}' metadata".format(cat)) + logger.info("Parsing metadata fields at the {} level.".format(MSG_MAP[cat])) for key, val in config[cat][sheet_type].items(): if isinstance(val, list): field_val = [LemiMetadata.get_md_field(sheet, x) for x in val] @@ -282,10 +282,24 @@ class LemiMetadata(): md_fields['Mag'] = self.reformat_mag(md_fields['Mag']) return md_fields + @staticmethod + def check_field_sheets(sheet_types: List[Optional[str]]) -> None: + """ + Check that all valid sheet types have been found and parsed. If not, + warn user of missing sheet(s). + """ + valid_types = [key for key in config['sheets'].keys()[1:]] + for valid_type in valid_types: + if sheet_types and valid_type not in sheet_types: + logger.warning("No valid {0} sheet found. The {0} metadata " + "fields will have to be provided using the GUI." + .format(valid_type.split('_')[0])) + @property def from_field_sheets(self) -> Optional[Dict]: """Parse all field sheets provided by the user.""" md_fields: Dict = {} + sheet_types = [] parsed_sheets = [] for filename in self.filenames: try: @@ -298,6 +312,7 @@ class LemiMetadata(): continue sheet = workbook.active sheet_type = self.identify_field_sheet(sheet, filename) + sheet_types.append(sheet_type) if sheet_type is None: continue if sheet_type not in parsed_sheets: @@ -307,8 +322,9 @@ class LemiMetadata(): "more than one {0} {1} - Skipping {2}!" .format(*sheet_type.split('_'), filename)) continue - md_fields = self.parse_field_sheet(sheet, sheet_type, md_fields) + md_fields = self.parse_field_sheet(filename, sheet, sheet_type, md_fields) workbook.close() + LemiMetadata.check_field_sheets(sheet_types) return self.reformat_md_dict(md_fields) if not is_empty(md_fields) else None def populate(self, cat: DCS, md_fields: Dict, run_id: Optional[str] = None) -> None: @@ -363,6 +379,23 @@ class LemiMetadata(): self.populate(self.net, md_fields_n) LemiMetadata.flag_md_missing(self.net) + def check_sta_start_end(self) -> None: + """ + Check that the start and end times of a station fall within the + network start and end times. + """ + for time_field in ['start', 'end']: + time_net = getattr(self.net, time_field) + if time_field in self.sta.md_invalid or time_net is None: + continue + time_sta = getattr(self.sta, time_field) + valid = time_sta >= time_net if time_field == "start" else time_sta <= time_net + if not valid: + msg = "greater" if time_field == "start" else "lower" + logger.error("The {0} date of the station should be {1} than the {0} date " + "of the network.".format(time_field, msg)) + self.sta.md_invalid.add(time_field) + def populate_sta_props(self, md_fields_s: Dict) -> None: """ Populate properties of Sta data class based on user inputs from @@ -370,6 +403,7 @@ class LemiMetadata(): """ self.populate(self.sta, md_fields_s) self.sta.run_list = ', '.join(self.run_list) + self.check_sta_start_end() LemiMetadata.flag_md_missing(self.sta) def get_run(self, run_id: str) -> Run: @@ -391,20 +425,13 @@ class LemiMetadata(): self.populate(run, md_fields_r[name], run_id) LemiMetadata.flag_md_missing(run) - def get_comps_rec(self, type_: str, run_id: str, msg: Optional[bool] = True) -> list: + def get_comps_rec(self, type_: str, run_id: str) -> list: """ For a given channel type (electric or magnetic) and a given run_id, get components recorded. """ run = self.get_run(run_id) - cha_type = 'electric field' if type_ == 'E' else 'magnetic field' if run.comps_rec is None: - if msg: - logger.warning("No set of recorded components found for run " - "'{0}'. If you did record {1} data for that run, " - "please update the 'components recorded' " - "metadata field at the Station/Runs level!" - .format(run_id, cha_type)) return [] else: comps = [x for x in str2list(run.comps_rec, sep='-') if x.startswith(type_)] # type: ignore @@ -436,7 +463,7 @@ class LemiMetadata(): e_infos = {} for run in self.run: run_id = run.run_id - e_pairs = self.get_comps_rec('E', run_id, msg=False) + e_pairs = self.get_comps_rec('E', run_id) elec = self.filter_cha('elec', run_id) e_infos[run_id] = {x.cha_port: x.comp for x in elec if f'E{x.pair_num}' in e_pairs} # type: ignore return e_infos diff --git a/lemi2seed/lemi_response.py b/lemi2seed/lemi_response.py index 298f7d667f0bc90c421511391ea96e98df052468..a967f073ab7d56b952c4b8659f603b69ba62800d 100644 --- a/lemi2seed/lemi_response.py +++ b/lemi2seed/lemi_response.py @@ -66,18 +66,18 @@ logger = setup_logger(__name__) RESP_FILE = Path(__file__).resolve().parent.joinpath('response_file', 'LEMI-424_Response') SOH_STAGE_GAIN = 1.0 SOH_STAGE_GAIN_FREQUENCY = 0.0 -CHA_UNITS = {'E1': ['microvolt per meter', 'electric field units'], - 'E2': ['microvolt per meter', 'electric field units'], - 'E3': ['microvolt per meter', 'electric field units'], - 'E4': ['microvolt per meter', 'electric field units'], +CHA_UNITS = {'E1': ['uV/m', 'electric field units'], + 'E2': ['uV/m', 'electric field units'], + 'E3': ['uV/m', 'electric field units'], + 'E4': ['uV/m', 'electric field units'], 'Hx': ['nanotesla', 'magnetic field units'], 'Hy': ['nanotesla', 'magnetic field units'], 'Hz': ['nanotesla', 'magnetic field units'], 'Ui': ['V', 'electric potential in volts'], 'Te': ['celsius', 'temperature in degrees Celsius'], 'Tf': ['celsius', 'temperature in degrees Celsius'], - 'Sn': ['', 'unitless'], - 'Fq': ['', 'unitless'], + 'Sn': ['unitless', 'unitless'], + 'Fq': ['unitless', 'unitless'], 'Ce': ['s', 'second']} diff --git a/lemi2seed/metadata_category.py b/lemi2seed/metadata_category.py index eda89880dc37952fc5560aaaf273777e1573ed5e..9cffc84eb75774bd1e99897de4b4a14f55aaee45 100644 --- a/lemi2seed/metadata_category.py +++ b/lemi2seed/metadata_category.py @@ -19,9 +19,8 @@ from typing import Dict, Optional, Set from lemi2seed.lemi_data import SAMPLING_RATE, CHA_NAMING_CONV from lemi2seed.logging import parse_config_ini, setup_logger -from lemi2seed.utils import (check_email_format, check_inst_specs, check_sn, - is_valid_uri, str2list, ELEC_DEFAULT, - FLUXGATE_DEFAULT) +from lemi2seed.utils import (check_inst_specs, check_sn, str2list, + ELEC_DEFAULT, FLUXGATE_DEFAULT) # Read config.ini file and set up logging config = parse_config_ini() @@ -32,9 +31,10 @@ logger = setup_logger(__name__) CLOCKDRIFT = 1.0 E_COMP_NAMING_CONV = {'North-South': 'Ex', 'South-North': 'Ex', 'East-West': 'Ey', 'West-East': 'Ey'} -MAXIMUM_AZIMUTH = 99999 -MINIMUM_AZIMUTH = -99999 -MSG_MAP = {'Net': 'network', 'Sta': 'station'} +MAXIMUM_GEO = 99999 +MINIMUM_GEO = -99999 +MSG_MAP = {'Net': 'network', 'Sta': 'station', 'Run': 'run', 'Elec': 'electric', + 'Mag': 'magnetic'} VALID_COMPS = ['Hx - Hy - Hz', 'E1 - E2 - Hx - Hy - Hz', 'E1 - E2 - E3 - E4 - Hx - Hy - Hz'] @@ -57,9 +57,6 @@ class BaseNet(BaseMD): This class handles the metadata attributes shared at the network and station levels. """ - restricted_status: Optional[str] = field(default=None, - metadata={'xml_id': ['restricted_status'], - 'req': False, 'gui': True}) end: Optional[UTCDateTime] = field(default=None, metadata={'xml_id': ['end_date'], 'req': True, 'gui': True}) @@ -97,9 +94,6 @@ class Net(BaseNet): 'req': False, 'gui': True}) archive_net: Optional[str] = field(default=None, metadata={'req': True, 'gui': True}) - citation_dataset_doi: Optional[str] = field(default=None, - metadata={'xml_id': ['identifiers'], - 'req': False, 'gui': True}) comments: Optional[str] = field(default=None, metadata={'xml_id': ['comments', 'mt.network.comments'], 'req': False, 'gui': True}) @@ -118,9 +112,6 @@ class Net(BaseNet): project_lead_author: Optional[str] = field(default=None, metadata={'xml_id': ['operators.contacts.names'], 'req': False, 'gui': True}) - project_lead_email: Optional[str] = field(default=None, - metadata={'xml_id': ['operators.contacts.emails'], - 'req': False, 'gui': True}) project_lead_organization: Optional[str] = field(default=None, metadata={'xml_id': ['operators.agency'], 'req': False, 'gui': True}) @@ -141,37 +132,6 @@ class Net(BaseNet): "character long.") return bool(valid) - @staticmethod - def validate_citation_dataset_doi(md_input: str) -> bool: - """ - Proper formatting for DOI? - """ - if md_input is not None: - try: - dois = str2list(md_input) - except AttributeError: - logger.error('The DOI number(s) should be a string.') - return False - else: - if not all([is_valid_uri(doi) for doi in dois]): - logger.error("Invalid DOI(s). The DOI number(s) provided by " - "the archive should be strings formatted as " - "follows: 'scheme: path'.") - return False - return True - return True - - @staticmethod - def validate_project_lead_email(md_input: str) -> bool: - if md_input is not None: - try: - emails = str2list(md_input) - except AttributeError: - logger.error('The project lead email(s) should be a string.') - return False - return all([check_email_format(email) for email in emails]) - return True - @dataclass class BaseSta(BaseMD): @@ -203,11 +163,12 @@ class BaseSta(BaseMD): "{3} and {2}{3}.".format(geo, min_range, max_range, units)) return False - elif md_input < min_val or md_input > max_val: - logger.error("Unexpected {0}! Provided {0} should " - "roughly match the {0} recorded by the on-site " - "GPS ({1:.3f}{2}).".format(geo, val, units)) - return False + if md_input < min_val or md_input > max_val: + tolerance = max_val - val + logger.warning("Unexpected {0}! Provided {0} should " + "roughly match the {0} recorded by the on-site " + "GPS ({1:.3f} ± {2:.3f}{3})." + .format(geo, val, tolerance, units)) return True def validate_elev(self, md_input: float, data_input: float) -> bool: @@ -264,9 +225,6 @@ class Sta(BaseNet, BaseSta): submitter_author: Optional[str] = field(default=None, metadata={'xml_id': ['comments', 'mt.station.provenance.submitter.author'], 'req': False, 'gui': True}) - submitter_email: Optional[str] = field(default=None, - metadata={'xml_id': ['comments', 'mt.station.provenance.submitter.email'], - 'req': False, 'gui': True}) submitter_organization: Optional[str] = field(default=None, metadata={'xml_id': ['comments', 'mt.station.provenance.submitter.organization'], # noqa: E501 'req': False, 'gui': True}) @@ -285,15 +243,9 @@ class Sta(BaseNet, BaseSta): return bool(valid) def validate_declination_val(self, md_input: float) -> bool: - param = ("declination angle", -90, 90, MINIMUM_AZIMUTH, MAXIMUM_AZIMUTH, None, '°') + param = ("declination angle", -90, 90, MINIMUM_GEO, MAXIMUM_GEO, None, '°') return BaseSta.validate_geos(param, md_input) - @staticmethod - def validate_submitter_email(md_input: str) -> bool: - if md_input is not None: - return check_email_format(md_input) - return True - @dataclass class Run(BaseMD): @@ -358,8 +310,8 @@ class Run(BaseMD): comps = str2list(md_input, sep='-') if '-' in md_input else str2list(md_input) except (AttributeError, TypeError): - logger.error("The list of components recorded for run '{}' " - "should be a string. Example: {}" + logger.error("The 'components recorded' metadata field is empty " + "for run '{}'. Example of valid field: {}" .format(self.run_id, VALID_COMPS[0])) return False else: @@ -421,7 +373,7 @@ class BaseChannel(BaseSta): geo = "azimuth angle of electrode pair {0} (run '{1}')".format(self.pair_num, self.run_id) if isinstance(self, Mag): geo = "azimuth angle of fluxgate (run '{}')".format(self.run_id) - param = (geo, 0, 360, MINIMUM_AZIMUTH, MAXIMUM_AZIMUTH, None, '°') + param = (geo, 0, 360, MINIMUM_GEO, MAXIMUM_GEO, None, '°') return BaseSta.validate_geos(param, md_input) def validate_meas_tilt(self, md_input: float) -> bool: @@ -429,7 +381,7 @@ class BaseChannel(BaseSta): geo = "tilt angle of electrode pair {0} (run '{1}')".format(self.pair_num, self.run_id) if isinstance(self, Mag): geo = "tilt angle of fluxgate (run '{}')".format(self.run_id) - param = (geo, -90, 90, MINIMUM_AZIMUTH, MAXIMUM_AZIMUTH, None, '°') + param = (geo, -90, 90, MINIMUM_GEO, MAXIMUM_GEO, None, '°') return BaseSta.validate_geos(param, md_input) diff --git a/lemi2seed/metadata_conversion.py b/lemi2seed/metadata_conversion.py index 864441c0a3e5b0494ff3fbc7ed5514ebf31eaaef..fdbdeae2879bf4fbfc17f67fe43aa8cf7e09d102 100644 --- a/lemi2seed/metadata_conversion.py +++ b/lemi2seed/metadata_conversion.py @@ -81,11 +81,12 @@ def create_net(net: Net) -> Network: comments.append(comment) elif 'operators' in net_field_info[0]: if 'agency' in net_field_info[0]: + # Address agency related change in StationXML 1.1 + if not net_field_val: + net_field_val = ' ' operator = Operator(agency=net_field_val) - elif 'names' in net_field_info[0]: - person.names = str2list(net_field_val) else: - person.emails = str2list(net_field_val) + person.names = str2list(net_field_val) elif net_field_info[0] == 'identifiers': net_.identifiers = str2list(net_field_val) else: @@ -414,6 +415,7 @@ def write_stationxml(lemi_md: LemiMetadata) -> None: filename = '.'.join([lemi_md.data_stats['net'], lemi_md.data_stats['sta'], '_'.join([x.upper() for x in lemi_md.net.project.split(" ")]), # type: ignore UTCDateTime().strftime('%Y%j.%H%M%S'), 'xml']) + filename = str(lemi_md.output_xml.joinpath(filename)) logger.info("Writing StationXML file: {}.".format(filename)) inv = create_inventory() net = create_net(lemi_md.net) @@ -426,5 +428,5 @@ def write_stationxml(lemi_md: LemiMetadata) -> None: sta.equipments = loggers net.stations = [sta] inv.networks = [net] - inv.write(str(lemi_md.output_xml.joinpath(filename)), format="STATIONXML") + inv.write(filename, format="STATIONXML") logger.info("Writing of StationXML file successfully completed!") diff --git a/lemi2seed/ui_files/electricwidget.ui b/lemi2seed/ui_files/electricwidget.ui index 92192e5fdac005497d244012337a7d2ad330ba6b..bc45f7f934df60960b9f4bbd681a380ad018e854 100644 --- a/lemi2seed/ui_files/electricwidget.ui +++ b/lemi2seed/ui_files/electricwidget.ui @@ -235,7 +235,7 @@ Set to 1 Hz since the LEMI datalogger only has one sample rate for data recordin </font> </property> <property name="text"> - <string>Negative Electrode</string> + <string>Positive Electrode</string> </property> </widget> </item> @@ -251,7 +251,7 @@ By convention South or West.</string> </widget> </item> <item row="13" column="1"> - <widget class="QComboBox" name="uiNegElecDir"> + <widget class="QComboBox" name="uiPosElecDir"> <property name="sizePolicy"> <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> <horstretch>0</horstretch> @@ -294,7 +294,7 @@ By convention South or West.</string> </widget> </item> <item row="14" column="1"> - <widget class="QLineEdit" name="uiNegElecSn"/> + <widget class="QLineEdit" name="uiPosElecSn"/> </item> <item row="15" column="0"> <widget class="QLabel" name="label_11"> @@ -304,7 +304,7 @@ By convention South or West.</string> </font> </property> <property name="text"> - <string>Positive Electrode</string> + <string>Negative Electrode</string> </property> </widget> </item> @@ -320,7 +320,7 @@ By convention North or East.</string> </widget> </item> <item row="16" column="1"> - <widget class="QComboBox" name="uiPosElecDir"> + <widget class="QComboBox" name="uiNegElecDir"> <property name="sizePolicy"> <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> <horstretch>0</horstretch> @@ -363,7 +363,7 @@ By convention North or East.</string> </widget> </item> <item row="17" column="1"> - <widget class="QLineEdit" name="uiPosElecSn"/> + <widget class="QLineEdit" name="uiNegElecSn"/> </item> </layout> </widget> diff --git a/lemi2seed/ui_files/helpwidget.ui b/lemi2seed/ui_files/helpwidget.ui index 2250cbd00d14a1b2cde0dfeccced8aefb29da537..358d7e269a9a4288aac70e7ef17bb17f01a29906 100644 --- a/lemi2seed/ui_files/helpwidget.ui +++ b/lemi2seed/ui_files/helpwidget.ui @@ -6,10 +6,16 @@ <rect> <x>0</x> <y>0</y> - <width>1091</width> - <height>438</height> + <width>1200</width> + <height>721</height> </rect> </property> + <property name="minimumSize"> + <size> + <width>1100</width> + <height>600</height> + </size> + </property> <property name="windowTitle"> <string>Form</string> </property> @@ -21,8 +27,8 @@ <rect> <x>10</x> <y>10</y> - <width>1111</width> - <height>91</height> + <width>1130</width> + <height>100</height> </rect> </property> <property name="styleSheet"> @@ -33,11 +39,11 @@ <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'.AppleSystemUIFont'; font-size:13pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Description</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">lemi2seed is a GUI software tool developped by the PASSCAL Instrument Center to convert LEMI data and metadata into day-long mseed and StationXML files.</p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Usage</span></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">The tool bar offers the following options:</p></body></html></string> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:15pt; font-weight:600;">Description</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:15pt;">lemi2seed is a GUI software tool developped by the PASSCAL Instrument Center to convert LEMI data and metadata into day-long mseed and StationXML files.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:15pt;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:15pt; font-weight:600;">Usage</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:15pt;">The tool bar offers the following options:</span></p></body></html></string> </property> <property name="textInteractionFlags"> <set>Qt::NoTextInteraction</set> @@ -47,9 +53,9 @@ p, li { white-space: pre-wrap; } <property name="geometry"> <rect> <x>20</x> - <y>110</y> - <width>25</width> - <height>25</height> + <y>130</y> + <width>30</width> + <height>30</height> </rect> </property> <property name="styleSheet"> @@ -63,9 +69,9 @@ p, li { white-space: pre-wrap; } <property name="geometry"> <rect> <x>60</x> - <y>110</y> - <width>731</width> - <height>31</height> + <y>130</y> + <width>1080</width> + <height>30</height> </rect> </property> <property name="styleSheet"> @@ -76,7 +82,7 @@ p, li { white-space: pre-wrap; } <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'.AppleSystemUIFont'; font-size:13pt; font-weight:400; font-style:normal;"> -<p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Upload metadata previously saved with the GUI. The uploaded file has to be in a pickle format (.pkl file extension).</p></body></html></string> +<p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:15pt;">Upload metadata previously saved with the GUI. The uploaded file has to be in a pickle format (.pkl file extension).</span></p></body></html></string> </property> <property name="textInteractionFlags"> <set>Qt::NoTextInteraction</set> @@ -86,9 +92,9 @@ p, li { white-space: pre-wrap; } <property name="geometry"> <rect> <x>20</x> - <y>144</y> - <width>25</width> - <height>25</height> + <y>180</y> + <width>30</width> + <height>30</height> </rect> </property> <property name="styleSheet"> @@ -102,9 +108,9 @@ p, li { white-space: pre-wrap; } <property name="geometry"> <rect> <x>60</x> - <y>140</y> - <width>681</width> - <height>31</height> + <y>182</y> + <width>1081</width> + <height>30</height> </rect> </property> <property name="styleSheet"> @@ -115,7 +121,7 @@ p, li { white-space: pre-wrap; } <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'.AppleSystemUIFont'; font-size:13pt; font-weight:400; font-style:normal;"> -<p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Save inputted metadata. The metadata will be stored in a pickle format (.pkl file extension).</p></body></html></string> +<p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:15pt;">Save inputted metadata. The metadata will be stored in a pickle format (.pkl file extension).</span></p></body></html></string> </property> <property name="textInteractionFlags"> <set>Qt::NoTextInteraction</set> @@ -125,9 +131,9 @@ p, li { white-space: pre-wrap; } <property name="geometry"> <rect> <x>20</x> - <y>185</y> - <width>28</width> - <height>28</height> + <y>240</y> + <width>34</width> + <height>34</height> </rect> </property> <property name="styleSheet"> @@ -141,9 +147,9 @@ p, li { white-space: pre-wrap; } <property name="geometry"> <rect> <x>60</x> - <y>170</y> - <width>1051</width> - <height>61</height> + <y>220</y> + <width>1080</width> + <height>80</height> </rect> </property> <property name="styleSheet"> @@ -154,9 +160,9 @@ p, li { white-space: pre-wrap; } <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'.AppleSystemUIFont'; font-size:13pt; font-weight:400; font-style:normal;"> -<p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Write day-long mseed files in user defined output directory or default directory (./MSEED).</p> -<p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Write calibration information in log file under user defined output directory or default directory (./LOGS) if calibration coefficient files (.def) were provided by the user. </p> -<p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">This option is only enabled if all user provided metadata are valid and all required metadata are filled.</p></body></html></string> +<p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:15pt;">Write day-long mseed files in user defined output directory or default directory (./MSEED).</span></p> +<p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:15pt;">Write calibration information in log file under user defined output directory or default directory (./LOGS) if calibration coefficient files (.def) were provided by the user. </span></p> +<p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:15pt;">This option is only enabled if all user provided metadata are valid and all required metadata are filled.</span></p></body></html></string> </property> <property name="textInteractionFlags"> <set>Qt::NoTextInteraction</set> @@ -166,9 +172,9 @@ p, li { white-space: pre-wrap; } <property name="geometry"> <rect> <x>20</x> - <y>238</y> - <width>25</width> - <height>25</height> + <y>315</y> + <width>30</width> + <height>30</height> </rect> </property> <property name="styleSheet"> @@ -182,9 +188,9 @@ p, li { white-space: pre-wrap; } <property name="geometry"> <rect> <x>60</x> - <y>230</y> - <width>681</width> - <height>41</height> + <y>310</y> + <width>1080</width> + <height>50</height> </rect> </property> <property name="styleSheet"> @@ -195,8 +201,8 @@ p, li { white-space: pre-wrap; } <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'.AppleSystemUIFont'; font-size:13pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Write StationXML file in user defined output directory or default directory (./STATIONXML).</p> -<p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">This option will only be enabled if all user provided metadata are valid and all required metadata are filled.</p></body></html></string> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:15pt;">Write StationXML file in user defined output directory or default directory (./STATIONXML).</span></p> +<p align="justify" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:15pt;">This option will only be enabled if all user provided metadata are valid and all required metadata are filled.</span></p></body></html></string> </property> <property name="textInteractionFlags"> <set>Qt::NoTextInteraction</set> @@ -206,9 +212,9 @@ p, li { white-space: pre-wrap; } <property name="geometry"> <rect> <x>10</x> - <y>290</y> - <width>1061</width> - <height>151</height> + <y>370</y> + <width>1130</width> + <height>160</height> </rect> </property> <property name="styleSheet"> @@ -219,14 +225,14 @@ p, li { white-space: pre-wrap; } <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'.AppleSystemUIFont'; font-size:13pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">The main window has four panels: <span style=" font-weight:600;">Network</span>, <span style=" font-weight:600;">Station</span>, <span style=" font-weight:600;">Electric</span>, <span style=" font-weight:600;">Magnetic</span>. To navigate from one panel to another, just select the panel of interest.</p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">In each panel, the required metadata are identified with the asterisk symbol (<span style=" font-weight:600;">*</span>).</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">For more information about a metadata field and its expected format, hover your mouse on its label.</p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">To validate the inputted metadata, select the &quot;<span style=" font-weight:600;">Validate Metadata</span>&quot; button in the bottom right corner of the main window.</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Invalid metadata or unfilled required metadata will be circled in red. The name of the panels for which metadata are invalid or missing will also be highlighted in red.</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">For more information about why a given metadata field is flagged as invalid, check the logging outputs in the terminal window.</p></body></html></string> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:15pt;">The main window has four panels: </span><span style=" font-size:15pt; font-weight:600;">Network</span><span style=" font-size:15pt;">, </span><span style=" font-size:15pt; font-weight:600;">Station</span><span style=" font-size:15pt;">, </span><span style=" font-size:15pt; font-weight:600;">Electric</span><span style=" font-size:15pt;">, </span><span style=" font-size:15pt; font-weight:600;">Magnetic</span><span style=" font-size:15pt;">. To navigate from one panel to another, just select the panel of interest.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:15pt;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:15pt;">In each panel, the required metadata are identified with the asterisk symbol (</span><span style=" font-size:15pt; font-weight:600;">*</span><span style=" font-size:15pt;">).</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:15pt;">For more information about a metadata field and its expected format, hover your mouse on its label.</span></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:15pt;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:15pt;">To validate the inputted metadata, select the &quot;</span><span style=" font-size:15pt; font-weight:600;">Validate Metadata</span><span style=" font-size:15pt;">&quot; button in the bottom right corner of the main window.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:15pt;">Invalid metadata or unfilled required metadata will be circled in red. The name of the panels for which metadata are invalid or missing will also be highlighted in red.</span></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:15pt;">For more information about why a given metadata field is flagged as invalid, check the logging outputs in the terminal window.</span></p></body></html></string> </property> <property name="textInteractionFlags"> <set>Qt::NoTextInteraction</set> diff --git a/lemi2seed/ui_files/magneticwidget.ui b/lemi2seed/ui_files/magneticwidget.ui index 5a4f35335d5e8a5cca603b5c47f78e6f440c63ba..86213fd2287cb16e195c95b32e0e469a92935e2d 100644 --- a/lemi2seed/ui_files/magneticwidget.ui +++ b/lemi2seed/ui_files/magneticwidget.ui @@ -81,10 +81,10 @@ If the fluxgate is installed perfectly vertically, the provided tilt angle shoul <item row="3" column="0"> <widget class="QLabel" name="label_4"> <property name="toolTip"> - <string>Magnetometer specifications (Manufacturer - Model - Type).</string> + <string>Fluxgate specifications (Manufacturer - Model - Type).</string> </property> <property name="text"> - <string>Magnetometer - Specs *</string> + <string>Fluxgate - Specs *</string> </property> </widget> </item> @@ -108,10 +108,10 @@ If the fluxgate is installed perfectly vertically, the provided tilt angle shoul <item row="4" column="0"> <widget class="QLabel" name="label_5"> <property name="toolTip"> - <string>Serial number of magnetometer.</string> + <string>Serial number of fluxgate.</string> </property> <property name="text"> - <string>Magnetometer - Serial Number *</string> + <string>Fluxgate - Serial Number *</string> </property> </widget> </item> diff --git a/lemi2seed/ui_files/networkwidget.ui b/lemi2seed/ui_files/networkwidget.ui index e4c69966bb26022cd1761a29a076ad4fed257da6..c797e6e5682f02fe6451ceeceb10e99d68af295b 100644 --- a/lemi2seed/ui_files/networkwidget.ui +++ b/lemi2seed/ui_files/networkwidget.ui @@ -106,16 +106,23 @@ Should be two characters long.</string> </property> <property name="date"> <date> - <year>2021</year> - <month>1</month> - <day>1</day> + <year>1999</year> + <month>12</month> + <day>31</day> </date> </property> + <property name="time"> + <time> + <hour>17</hour> + <minute>0</minute> + <second>0</second> + </time> + </property> <property name="currentSection"> <enum>QDateTimeEdit::YearSection</enum> </property> <property name="displayFormat"> - <string>yyyy-MM-dd</string> + <string>yyyy-MM-dd HH:mm:ss</string> </property> <property name="calendarPopup"> <bool>true</bool> @@ -157,16 +164,23 @@ Should be two characters long.</string> </property> <property name="date"> <date> - <year>2021</year> - <month>1</month> - <day>1</day> + <year>2999</year> + <month>12</month> + <day>31</day> </date> </property> + <property name="time"> + <time> + <hour>16</hour> + <minute>59</minute> + <second>59</second> + </time> + </property> <property name="currentSection"> <enum>QDateTimeEdit::YearSection</enum> </property> <property name="displayFormat"> - <string>yyyy-MM-dd</string> + <string>yyyy-MM-dd HH:mm:ss</string> </property> <property name="calendarPopup"> <bool>true</bool> @@ -177,56 +191,6 @@ Should be two characters long.</string> </widget> </item> <item row="3" column="0"> - <widget class="QLabel" name="label_5"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="toolTip"> - <string>Are the access to the data at the network level open, partially open or closed?</string> - </property> - <property name="text"> - <string>Restricted status</string> - </property> - </widget> - </item> - <item row="3" column="1"> - <widget class="QComboBox" name="uiRestrictedStatus"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="font"> - <font> - <weight>50</weight> - <bold>false</bold> - </font> - </property> - <property name="focusPolicy"> - <enum>Qt::StrongFocus</enum> - </property> - <item> - <property name="text"> - <string>open</string> - </property> - </item> - <item> - <property name="text"> - <string>partial</string> - </property> - </item> - <item> - <property name="text"> - <string>closed</string> - </property> - </item> - </widget> - </item> - <item row="4" column="0"> <widget class="QLabel" name="label_4"> <property name="sizePolicy"> <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> @@ -251,7 +215,7 @@ Should be two characters long.</string> </property> </widget> </item> - <item row="4" column="1"> + <item row="3" column="1"> <widget class="QPlainTextEdit" name="uiComments"> <property name="sizePolicy"> <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> @@ -328,34 +292,6 @@ Should be two characters long.</string> <widget class="QLineEdit" name="uiAcqByComments"/> </item> <item row="2" column="0"> - <widget class="QLabel" name="label_7"> - <property name="font"> - <font> - <weight>50</weight> - <bold>false</bold> - </font> - </property> - <property name="toolTip"> - <string>DOI number provided by the archive. -Ex: DOI:10.17611/DP/EMTF/USGS/GEOMAG/FL15. -If multiple, input as comma separated. </string> - </property> - <property name="text"> - <string>Digital Object Identifier(s)</string> - </property> - </widget> - </item> - <item row="2" column="1"> - <widget class="QLineEdit" name="uiCitationDatasetDoi"> - <property name="font"> - <font> - <weight>50</weight> - <bold>false</bold> - </font> - </property> - </widget> - </item> - <item row="3" column="0"> <widget class="QLabel" name="label_8"> <property name="font"> <font> @@ -373,7 +309,7 @@ Ex: USA, Canada</string> </property> </widget> </item> - <item row="3" column="1"> + <item row="2" column="1"> <widget class="QLineEdit" name="uiCountry"> <property name="font"> <font> @@ -383,7 +319,7 @@ Ex: USA, Canada</string> </property> </widget> </item> - <item row="4" column="0"> + <item row="3" column="0"> <widget class="QLabel" name="label_9"> <property name="font"> <font> @@ -401,7 +337,7 @@ Ex: Eastern Mojave, Southwestern USA</string> </property> </widget> </item> - <item row="4" column="1"> + <item row="3" column="1"> <widget class="QLineEdit" name="uiGeoName"> <property name="font"> <font> @@ -411,7 +347,7 @@ Ex: Eastern Mojave, Southwestern USA</string> </property> </widget> </item> - <item row="5" column="0"> + <item row="4" column="0"> <widget class="QLabel" name="label_10"> <property name="font"> <font> @@ -428,7 +364,7 @@ Ex: MT Characterization of Yukon Terrane</string> </property> </widget> </item> - <item row="5" column="1"> + <item row="4" column="1"> <widget class="QLineEdit" name="uiName"> <property name="font"> <font> @@ -438,7 +374,7 @@ Ex: MT Characterization of Yukon Terrane</string> </property> </widget> </item> - <item row="6" column="0"> + <item row="5" column="0"> <widget class="QLabel" name="label_11"> <property name="font"> <font> @@ -457,7 +393,7 @@ Ex: GEOMAG</string> </property> </widget> </item> - <item row="6" column="1"> + <item row="5" column="1"> <widget class="QLineEdit" name="uiProject"> <property name="font"> <font> @@ -467,7 +403,7 @@ Ex: GEOMAG</string> </property> </widget> </item> - <item row="7" column="0"> + <item row="6" column="0"> <widget class="QLabel" name="label_12"> <property name="font"> <font> @@ -485,7 +421,7 @@ If multiple, input as comma separated.</string> </property> </widget> </item> - <item row="7" column="1"> + <item row="6" column="1"> <widget class="QLineEdit" name="uiProjectLeadAuthor"> <property name="font"> <font> @@ -495,34 +431,7 @@ If multiple, input as comma separated.</string> </property> </widget> </item> - <item row="8" column="0"> - <widget class="QLabel" name="label_13"> - <property name="font"> - <font> - <weight>50</weight> - <bold>false</bold> - </font> - </property> - <property name="toolTip"> - <string>Email of the project lead. -If multiple, input as comma separated.</string> - </property> - <property name="text"> - <string>Project Lead - Email(s)</string> - </property> - </widget> - </item> - <item row="8" column="1"> - <widget class="QLineEdit" name="uiProjectLeadEmail"> - <property name="font"> - <font> - <weight>50</weight> - <bold>false</bold> - </font> - </property> - </widget> - </item> - <item row="9" column="0"> + <item row="7" column="0"> <widget class="QLabel" name="label_14"> <property name="font"> <font> @@ -538,7 +447,7 @@ If multiple, input as comma separated.</string> </property> </widget> </item> - <item row="9" column="1"> + <item row="7" column="1"> <widget class="QLineEdit" name="uiProjectLeadOrganization"> <property name="font"> <font> @@ -548,7 +457,7 @@ If multiple, input as comma separated.</string> </property> </widget> </item> - <item row="10" column="0"> + <item row="8" column="0"> <widget class="QLabel" name="label_15"> <property name="font"> <font> @@ -568,7 +477,7 @@ If multiple, input as comma separated.</string> </property> </widget> </item> - <item row="10" column="1"> + <item row="8" column="1"> <widget class="QPlainTextEdit" name="uiSummary"> <property name="font"> <font> diff --git a/lemi2seed/ui_files/stationwidget.ui b/lemi2seed/ui_files/stationwidget.ui index fc7f75ae4250b8dcb1a5bf72ba8d026f2afacd45..1d4a1beb27d9ca3f51cf5f446c6cb62d44b4ba8b 100644 --- a/lemi2seed/ui_files/stationwidget.ui +++ b/lemi2seed/ui_files/stationwidget.ui @@ -109,36 +109,71 @@ Per SEED format requirement, this is a 5-character long string (a-z;A-Z;0-9).</s </property> <property name="dateTime"> <datetime> - <hour>13</hour> + <hour>17</hour> <minute>0</minute> <second>0</second> - <year>2021</year> - <month>1</month> - <day>6</day> + <year>1999</year> + <month>12</month> + <day>31</day> </datetime> </property> <property name="date"> <date> - <year>2021</year> - <month>1</month> - <day>6</day> + <year>1999</year> + <month>12</month> + <day>31</day> </date> </property> + <property name="time"> + <time> + <hour>17</hour> + <minute>0</minute> + <second>0</second> + </time> + </property> <property name="maximumDateTime"> <datetime> - <hour>19</hour> + <hour>11</hour> <minute>59</minute> <second>59</second> - <year>7999</year> + <year>2999</year> <month>12</month> - <day>6</day> + <day>31</day> </datetime> </property> + <property name="maximumDate"> + <date> + <year>2999</year> + <month>12</month> + <day>31</day> + </date> + </property> + <property name="minimumDate"> + <date> + <year>1999</year> + <month>12</month> + <day>31</day> + </date> + </property> + <property name="maximumTime"> + <time> + <hour>11</hour> + <minute>59</minute> + <second>59</second> + </time> + </property> + <property name="minimumTime"> + <time> + <hour>17</hour> + <minute>0</minute> + <second>0</second> + </time> + </property> <property name="currentSection"> <enum>QDateTimeEdit::YearSection</enum> </property> <property name="displayFormat"> - <string>yyyy-MM-dd HH-mm-ss</string> + <string>yyyy-MM-dd HH:mm:ss</string> </property> <property name="calendarPopup"> <bool>true</bool> @@ -180,26 +215,40 @@ Per SEED format requirement, this is a 5-character long string (a-z;A-Z;0-9).</s </property> <property name="dateTime"> <datetime> - <hour>13</hour> - <minute>0</minute> - <second>0</second> - <year>2021</year> - <month>1</month> - <day>7</day> + <hour>16</hour> + <minute>59</minute> + <second>59</second> + <year>2999</year> + <month>12</month> + <day>31</day> </datetime> </property> <property name="date"> <date> - <year>2021</year> - <month>1</month> - <day>7</day> + <year>2999</year> + <month>12</month> + <day>31</day> </date> </property> + <property name="time"> + <time> + <hour>16</hour> + <minute>59</minute> + <second>59</second> + </time> + </property> + <property name="maximumTime"> + <time> + <hour>16</hour> + <minute>59</minute> + <second>59</second> + </time> + </property> <property name="currentSection"> <enum>QDateTimeEdit::YearSection</enum> </property> <property name="displayFormat"> - <string>yyyy-MM-dd HH-mm-ss</string> + <string>yyyy-MM-dd HH:mm:ss</string> </property> <property name="calendarPopup"> <bool>true</bool> @@ -210,56 +259,6 @@ Per SEED format requirement, this is a 5-character long string (a-z;A-Z;0-9).</s </widget> </item> <item row="3" column="0"> - <widget class="QLabel" name="label_5"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="toolTip"> - <string>Are the access to the data at the station level open, partially open or closed?</string> - </property> - <property name="text"> - <string>Restricted status</string> - </property> - </widget> - </item> - <item row="3" column="1"> - <widget class="QComboBox" name="uiRestrictedStatus"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="font"> - <font> - <weight>50</weight> - <bold>false</bold> - </font> - </property> - <property name="focusPolicy"> - <enum>Qt::StrongFocus</enum> - </property> - <item> - <property name="text"> - <string>open</string> - </property> - </item> - <item> - <property name="text"> - <string>partial</string> - </property> - </item> - <item> - <property name="text"> - <string>closed</string> - </property> - </item> - </widget> - </item> - <item row="4" column="0"> <widget class="QLabel" name="label_8"> <property name="toolTip"> <string>Closest geographic name to the station. @@ -271,7 +270,7 @@ Ex: Whitehorse, YK.</string> </property> </widget> </item> - <item row="4" column="1"> + <item row="3" column="1"> <widget class="QLineEdit" name="uiGeoName"> <property name="font"> <font> @@ -281,7 +280,7 @@ Ex: Whitehorse, YK.</string> </property> </widget> </item> - <item row="5" column="0"> + <item row="4" column="0"> <widget class="QLabel" name="label_16"> <property name="toolTip"> <string>Latitude of station location in decimal degrees.</string> @@ -291,7 +290,7 @@ Ex: Whitehorse, YK.</string> </property> </widget> </item> - <item row="5" column="1"> + <item row="4" column="1"> <widget class="QLineEdit" name="uiLat"> <property name="font"> <font> @@ -301,7 +300,7 @@ Ex: Whitehorse, YK.</string> </property> </widget> </item> - <item row="6" column="0"> + <item row="5" column="0"> <widget class="QLabel" name="label_17"> <property name="toolTip"> <string>Longitude of station location in decimal degrees.</string> @@ -311,7 +310,7 @@ Ex: Whitehorse, YK.</string> </property> </widget> </item> - <item row="6" column="1"> + <item row="5" column="1"> <widget class="QLineEdit" name="uiLon"> <property name="font"> <font> @@ -321,7 +320,7 @@ Ex: Whitehorse, YK.</string> </property> </widget> </item> - <item row="7" column="0"> + <item row="6" column="0"> <widget class="QLabel" name="label_18"> <property name="toolTip"> <string>Elevation of station location in meters.</string> @@ -331,7 +330,7 @@ Ex: Whitehorse, YK.</string> </property> </widget> </item> - <item row="7" column="1"> + <item row="6" column="1"> <widget class="QLineEdit" name="uiElev"> <property name="font"> <font> @@ -341,7 +340,7 @@ Ex: Whitehorse, YK.</string> </property> </widget> </item> - <item row="8" column="0"> + <item row="7" column="0"> <widget class="QLabel" name="label_19"> <property name="toolTip"> <string>Declination angle relative to geographic north. @@ -353,7 +352,7 @@ In decimal degrees.</string> </property> </widget> </item> - <item row="8" column="1"> + <item row="7" column="1"> <widget class="QLineEdit" name="uiDeclinationVal"> <property name="font"> <font> @@ -363,7 +362,7 @@ In decimal degrees.</string> </property> </widget> </item> - <item row="9" column="0"> + <item row="8" column="0"> <widget class="QLabel" name="label_20"> <property name="toolTip"> <string>Name of the geomagnetic reference model.</string> @@ -373,7 +372,7 @@ In decimal degrees.</string> </property> </widget> </item> - <item row="9" column="1"> + <item row="8" column="1"> <widget class="QComboBox" name="uiDeclinationModel"> <property name="sizePolicy"> <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> @@ -425,7 +424,7 @@ In decimal degrees.</string> </item> </widget> </item> - <item row="10" column="0"> + <item row="9" column="0"> <widget class="QLabel" name="label_21"> <property name="toolTip"> <string>Reference frame for station layout. @@ -437,7 +436,7 @@ Both assume a right-handed coordinate system with North=0, E=90 and vertical pos </property> </widget> </item> - <item row="10" column="1"> + <item row="9" column="1"> <widget class="QComboBox" name="uiOrientationReferenceFrame"> <property name="sizePolicy"> <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> @@ -466,7 +465,7 @@ Both assume a right-handed coordinate system with North=0, E=90 and vertical pos </item> </widget> </item> - <item row="11" column="0"> + <item row="10" column="0"> <widget class="QLabel" name="label_22"> <property name="toolTip"> <string>Method for orienting station channels.</string> @@ -476,7 +475,7 @@ Both assume a right-handed coordinate system with North=0, E=90 and vertical pos </property> </widget> </item> - <item row="11" column="1"> + <item row="10" column="1"> <widget class="QComboBox" name="uiOrientationMethod"> <property name="sizePolicy"> <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> @@ -520,7 +519,7 @@ Both assume a right-handed coordinate system with North=0, E=90 and vertical pos </item> </widget> </item> - <item row="12" column="0"> + <item row="11" column="0"> <widget class="QLabel" name="label_4"> <property name="sizePolicy"> <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> @@ -545,7 +544,7 @@ Both assume a right-handed coordinate system with North=0, E=90 and vertical pos </property> </widget> </item> - <item row="12" column="1"> + <item row="11" column="1"> <widget class="QPlainTextEdit" name="uiComments"> <property name="sizePolicy"> <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> @@ -615,32 +614,6 @@ Both assume a right-handed coordinate system with North=0, E=90 and vertical pos </widget> </item> <item row="1" column="0"> - <widget class="QLabel" name="label_13"> - <property name="font"> - <font> - <weight>50</weight> - <bold>false</bold> - </font> - </property> - <property name="toolTip"> - <string>Email of the person submitting the data to the archive.</string> - </property> - <property name="text"> - <string>Submitter - Email</string> - </property> - </widget> - </item> - <item row="1" column="1"> - <widget class="QLineEdit" name="uiSubmitterEmail"> - <property name="font"> - <font> - <weight>50</weight> - <bold>false</bold> - </font> - </property> - </widget> - </item> - <item row="2" column="0"> <widget class="QLabel" name="label_14"> <property name="font"> <font> @@ -656,7 +629,7 @@ Both assume a right-handed coordinate system with North=0, E=90 and vertical pos </property> </widget> </item> - <item row="2" column="1"> + <item row="1" column="1"> <widget class="QLineEdit" name="uiSubmitterOrganization"> <property name="font"> <font> diff --git a/setup.py b/setup.py index bab8320bd5c61db377b71fc49632424d81240727..9d33164fb59ea6db67f35ff89dced2681ef174fb 100644 --- a/setup.py +++ b/setup.py @@ -52,6 +52,6 @@ setup( name='lemi2seed', packages=find_packages(include=['lemi2seed']), url='https://git.passcal.nmt.edu/software_public/mt/lemi2seed', - version='2022.319', + version='2023.1.3.1', zip_safe=False, ) diff --git a/tests/test_data/METADATA/LEMI_Install_Sheet.xlsx b/tests/test_data/METADATA/LEMI_Install_Sheet.xlsx index 36b7792a40696fc45beaa11c7c6dfaef597d5925..9f8a57ff8fc57beebc5a840a980a0f497f1e5c3f 100644 Binary files a/tests/test_data/METADATA/LEMI_Install_Sheet.xlsx and b/tests/test_data/METADATA/LEMI_Install_Sheet.xlsx differ diff --git a/tests/test_data/METADATA/gui_metadata_not_populated.pkl b/tests/test_data/METADATA/gui_metadata_not_populated.pkl index e6a7f8dbfcde02a46758bbc906ee099b756bf4b3..2ca79bc00cc11c3d00a664b352bf11e2e28b0f97 100644 Binary files a/tests/test_data/METADATA/gui_metadata_not_populated.pkl and b/tests/test_data/METADATA/gui_metadata_not_populated.pkl differ diff --git a/tests/test_data/METADATA/gui_metadata_populated.pkl b/tests/test_data/METADATA/gui_metadata_populated.pkl index 19a3fbf69f55b432a525d6d22db7f1159e731798..5656ca7504f27f654b7bd0a75ac1ecf1ba26b3c8 100644 Binary files a/tests/test_data/METADATA/gui_metadata_populated.pkl and b/tests/test_data/METADATA/gui_metadata_populated.pkl differ diff --git a/tests/test_data/METADATA/metadata_fields.pkl b/tests/test_data/METADATA/metadata_fields.pkl index 53bfb233cfd4b63fc9071f754b0aa48148545974..bac33a8640224dd089a173dd215d98a3107f32e8 100644 Binary files a/tests/test_data/METADATA/metadata_fields.pkl and b/tests/test_data/METADATA/metadata_fields.pkl differ diff --git a/tests/test_data/METADATA/metadata_fields_reformatted.pkl b/tests/test_data/METADATA/metadata_fields_reformatted.pkl index 9e4d38933281204ec66ce3b6451280d66ed13825..ce9312bcd6cce54754e8d5521c729c40bd613b9b 100644 Binary files a/tests/test_data/METADATA/metadata_fields_reformatted.pkl and b/tests/test_data/METADATA/metadata_fields_reformatted.pkl differ diff --git a/tests/test_data/STATIONXML/network.pkl b/tests/test_data/STATIONXML/network.pkl index 10c3921db9b93a38c2eb14232e882f436d10baa1..7d8a0d13b98fe90e0c52ebcc4ca400751d4056aa 100644 Binary files a/tests/test_data/STATIONXML/network.pkl and b/tests/test_data/STATIONXML/network.pkl differ diff --git a/tests/test_data/STATIONXML/station.pkl b/tests/test_data/STATIONXML/station.pkl index c4126cce9e4df1b47f1d6e5b6a1036a2dfb59a55..134a2b8b715cd5ae7222bb85e861c712b4452c9c 100644 Binary files a/tests/test_data/STATIONXML/station.pkl and b/tests/test_data/STATIONXML/station.pkl differ diff --git a/tests/test_data/STATIONXML/updated_electric_channel.pkl b/tests/test_data/STATIONXML/updated_electric_channel.pkl index 6fb8190bf1ce5ed71638cf24de19a07bf40d1902..01b7ebca8497fc36085bc662878aa2a262f29cb2 100644 Binary files a/tests/test_data/STATIONXML/updated_electric_channel.pkl and b/tests/test_data/STATIONXML/updated_electric_channel.pkl differ diff --git a/tests/test_lemi2seed.py b/tests/test_lemi2seed.py index 1ba447e4feeb4f861458ccb89f2602790bb4d129..5fb2ef306108a73a409ebba2a8b1e56103deeb1f 100644 --- a/tests/test_lemi2seed.py +++ b/tests/test_lemi2seed.py @@ -27,6 +27,7 @@ class TestArgsFunctions(unittest.TestCase): """Set up test fixtures.""" self.path2data = TEST_DIR.joinpath("EM", "TEST1") self.path2metadata = TEST_DIR.joinpath("METADATA") + self.path2savedmd = TEST_DIR.joinpath("METADATA", "gui_metadata_populated.pkl") self.output_mseed = Path(__file__).resolve().parent.joinpath('MSEED') def test_check_output_dir_creation(self): @@ -64,7 +65,7 @@ class TestArgsFunctions(unittest.TestCase): with self.assertRaises(SystemExit) as cmd2: get_args() msg = ("lemi2seed cannot run under both the 'quality control' and " - "'gui only' mode at the same time. Try --help option or choose " + "'gui only' mode at the same time! Try --help option or choose " "one or the other mode.") self.assertEqual(cmd1.output, [":".join(['ERROR', SCR_DIR, msg])]) self.assertEqual(cmd2.exception.code, 1) @@ -87,8 +88,8 @@ class TestArgsFunctions(unittest.TestCase): with self.assertLogs(logger, level='INFO') as cmd: with patch("lemi2seed.lemi2seed.Path.mkdir"): _, path2metadata, *other = get_args() - msg = ("Running lemi2seed in 'quality control' mode. Handling of " - "metadata will be bypassed.") + msg = ("Handling of the field sheets is bypassed when running in " + "'quality control' mode.") self.assertEqual(cmd.output, [":".join(['INFO', SCR_DIR, msg])]) self.assertIsNone(path2metadata) @@ -99,8 +100,8 @@ class TestArgsFunctions(unittest.TestCase): with self.assertLogs(logger, level='INFO') as cmd: with patch("lemi2seed.lemi2seed.Path.mkdir"): _, path2metadata, *other = get_args() - msg = ("Running lemi2seed in 'gui only' mode. All metadata should be " - "entered using the GUI. Bypassing the use of field sheets.") + msg = ("All metadata should be entered using the GUI in 'gui only' " + "mode. Bypassing the use of field sheets.") self.assertEqual(cmd.output, [":".join(['INFO', SCR_DIR, msg])]) self.assertIsNone(path2metadata) @@ -124,7 +125,7 @@ class TestArgsFunctions(unittest.TestCase): _, path2metadata, *other = get_args() msg1 = "The path to the LEMI field spreadsheets does not exist!" msg2 = ("In 'quality control' or 'gui only' mode, field sheets are not " - "parsed!") + "parsed.") self.assertEqual(cmd.output[0], ":".join(['ERROR', SCR_DIR, msg1])) self.assertEqual(cmd.output[1], ":".join(['INFO', SCR_DIR, msg2])) self.assertIsNone(path2metadata) @@ -138,7 +139,7 @@ class TestArgsFunctions(unittest.TestCase): _, path2metadata, *other = get_args() msg1 = "The path to the LEMI field spreadsheets does not exist!" msg2 = ("In 'quality control' or 'gui only' mode, field sheets are not " - "parsed!") + "parsed.") self.assertEqual(cmd.output[0], ":".join(['ERROR', SCR_DIR, msg1])) self.assertEqual(cmd.output[1], ":".join(['INFO', SCR_DIR, msg2])) self.assertIsNone(path2metadata) @@ -157,6 +158,49 @@ class TestArgsFunctions(unittest.TestCase): self.assertEqual(cmd1.output[1], ":".join(['ERROR', SCR_DIR, msg2])) self.assertEqual(cmd2.exception.code, 1) + def test_get_args_path2savedmd_qc_only(self): + """Test basic functionality of get_args.""" + with patch("sys.argv", ["lemi2seed", "-d" + str(self.path2data), + "-s" + str(self.path2savedmd), "-qc"]): + with self.assertLogs(logger, level='INFO') as cmd: + _, _, path2savedmd, *other = get_args() + msg = ("Handling of previously saved metadata is bypassed when running " + "in 'quality control' mode.") + self.assertEqual(cmd.output[0], ":".join(['INFO', SCR_DIR, msg])) + self.assertIsNone(path2savedmd) + + def test_get_args_path2savedmd_do_not_exist(self): + """Test basic functionality of get_args.""" + with patch("sys.argv", ["lemi2seed", "-d" + str(self.path2data), + "-s" + str(self.path2savedmd) + "_", "-g"]): + with self.assertLogs(logger, level='ERROR') as cmd: + _, _, path2savedmd, *other = get_args() + msg = ("The provided path to a previously saved metadata file (*.pkl) " + "does not exist! Bypassing loading of metadata fields " + "before launching the GUI.") + self.assertEqual(cmd.output[0], ":".join(['ERROR', SCR_DIR, msg])) + self.assertIsNone(path2savedmd) + + def test_get_args_path2savedmd_gui_only(self): + """Test basic functionality of get_args.""" + with patch("sys.argv", ["lemi2seed", "-d" + str(self.path2data), + "-s" + str(self.path2savedmd), "-g"]): + _, _, path2savedmd, *other = get_args() + self.assertEqual(path2savedmd, self.path2savedmd) + + def test_get_args_path2savedmd_field_sheet_and_gui(self): + """Test basic functionality of get_args.""" + with patch("sys.argv", ["lemi2seed", "-d" + str(self.path2data), + "-m" + str(self.path2metadata), + "-s" + str(self.path2savedmd)]): + with self.assertLogs(logger, level='INFO') as cmd: + _, _, path2savedmd, *other = get_args() + msg = ("The loading of a previously saved metadata file (*.pkl) is " + "bypassed when running in 'field sheet and gui' mode. Try " + "--help option for more information.") + self.assertEqual(cmd.output[0], ":".join(['INFO', SCR_DIR, msg])) + self.assertIsNone(path2savedmd) + def test_get_args_output_xml_none(self): """Test basic functionality of get_args.""" with patch("sys.argv", ["lemi2seed", "-d" + str(self.path2data), "-qc"]): @@ -298,6 +342,8 @@ class TestMain(unittest.TestCase): main() expected_msg = ["INFO:lemi2seed.lemi2seed:Running lemi2seed in " "'field sheet and gui' mode.", "INFO:lemi2seed." - "lemi2seed:Launching the GUI."] + "lemi2seed:Pre-populating the GUI metadata fields " + "using the information provided in the field sheet(s).", + "INFO:lemi2seed.lemi2seed:Launching the GUI."] self.assertEqual(cmd.output, expected_msg) self.assertEqual(mock_gui.call_count, 1) diff --git a/tests/test_lemi_metadata.py b/tests/test_lemi_metadata.py index 6995212124da364359c7fc93efc43a88b1f550b6..84403520ff398deb8bff61207e0cdcb51d29bdaf 100644 --- a/tests/test_lemi_metadata.py +++ b/tests/test_lemi_metadata.py @@ -98,7 +98,6 @@ class TestLemiMetadata(unittest.TestCase): def test_reformat_run(self): """Test basic functionality of reformat_run.""" run_fields = self.md_fields_no_reformat['Run'] - file_ = self.path2md.joinpath('run_fields_reformatted.pkl') with open(file_, 'rb') as fin: reformatted = pickle.load(fin) @@ -141,7 +140,7 @@ class TestLemiMetadata(unittest.TestCase): self.md.from_field_sheets msg = ("Already parsed the install sheet - You may have more than one " "install sheet - Skipping {}!".format(self.file)) - self.assertEqual(cmd.output, [":".join(['WARNING', SCR_DIR, msg])]) + self.assertEqual(cmd.output[0], ":".join(['WARNING', SCR_DIR, msg])) def test_from_field_sheets_expected_workflow(self): """Test basic functionality of from_field_sheets""" @@ -162,14 +161,14 @@ class TestLemiMetadata(unittest.TestCase): """Test basic functionality of populate_sta_props.""" self.md.populate_sta_props(self.md_fields['Sta']) self.assertSetEqual(self.md.sta.md_missing, - {'end', 'elev', 'lon', 'start', 'geo_name'}) - self.assertSetEqual(self.md.sta.md_invalid, {'elev', 'lon', 'start'}) + {'end', 'start', 'geo_name'}) + self.assertSetEqual(self.md.sta.md_invalid, {'end', 'start'}) for key, val in self.md_fields['Sta'].items(): if key not in self.md.sta.md_invalid: self.assertEqual(getattr(self.md.sta, key), val) self.assertEqual(self.md.sta.run_list, 'a, b, c, d') - self.assertIsNone(self.md.sta.elev) self.assertIsNone(self.md.sta.start) + self.assertIsNone(self.md.sta.end) def test_init_run_props(self): """Test basic functionality of init_run_props.""" @@ -311,13 +310,8 @@ class TestLemiMetadata(unittest.TestCase): type_ = 'E' run_id = 'b' self.md.populate_run_props(self.md_fields['Run']) - with self.assertLogs(logger, level='WARNING') as cmd: - self.md.get_comps_rec(type_, run_id) - msg = (f"No set of recorded components found for run '{run_id}'. " - "If you did record electric field data for that run, please " - "update the 'components recorded' metadata field at the " - "Station/Runs level!") - self.assertEqual(cmd.output, [":".join(['WARNING', SCR_DIR, msg])]) + comps_rec = self.md.get_comps_rec(type_, run_id) + self.assertListEqual(comps_rec, []) def test_get_comps_rec_no_elec_but_mag(self): """Test basic functionality of get_comps_rec.""" diff --git a/tests/test_metadata_category.py b/tests/test_metadata_category.py index 27306d517f77dc7e77310aff40a25e5841fb9440..c93df95b96fc2a15819e2ddb0bf1eb24c9a8bf86 100644 --- a/tests/test_metadata_category.py +++ b/tests/test_metadata_category.py @@ -104,76 +104,6 @@ class TestNet(unittest.TestCase): """Test basic functionality of validate_archive_net.""" self.assertTrue(Net.validate_archive_net('EM')) - def test_validate_citation_dataset_doi_undefined(self): - """Test basic functionality of validate_citation_dataset_doi.""" - doi = None - self.assertTrue(Net.validate_citation_dataset_doi(doi)) - - def test_validate_citation_dataset_doi_erroneous_type(self): - """Test basic functionality of validate_citation_dataset_doi.""" - with self.assertLogs(logger, level='ERROR') as cmd: - Net.validate_citation_dataset_doi(10.7914) - msg = "The DOI number(s) should be a string." - self.assertEqual(cmd.output, [":".join(['ERROR', SCR_DIR, msg])]) - - def test_validate_citation_dataset_doi_invalid_doi(self): - """Test basic functionality of validate_citation_dataset_doi.""" - with self.assertLogs(logger, level='ERROR') as cmd: - Net.validate_citation_dataset_doi('10.7914/SN/EM') - msg = ("Invalid DOI(s). The DOI number(s) provided by the archive " - "should be strings formatted as follows: 'scheme: path'.") - self.assertEqual(cmd.output, [":".join(['ERROR', SCR_DIR, msg])]) - - def test_validate_citation_dataset_doi_invalid_dois(self): - """Test basic functionality of validate_citation_dataset_doi.""" - dois = '10.7914/SN/EM, DOI:10.3421/SN/EG' - with self.assertLogs(logger, level='ERROR') as cmd: - Net.validate_citation_dataset_doi(dois) - msg = ("Invalid DOI(s). The DOI number(s) provided by the archive " - "should be strings formatted as follows: 'scheme: path'.") - self.assertEqual(cmd.output, [":".join(['ERROR', SCR_DIR, msg])]) - - def test_validate_citation_dataset_doi_valid_doi(self): - """Test basic functionality of validate_citation_dataset_doi.""" - doi = 'DOI:10.7914/SN/EM' - self.assertTrue(Net.validate_citation_dataset_doi(doi)) - - def test_validate_citation_dataset_doi_valid_dois(self): - """Test basic functionality of validate_citation_dataset_doi.""" - dois = 'DOI:10.7914/SN/EM, DOI:10.3421/SN/EG' - self.assertTrue(Net.validate_citation_dataset_doi(dois)) - - def test_validate_project_lead_email_undefined(self): - """Test basic functionality of validate_project_lead_email.""" - self.assertTrue(Net.validate_project_lead_email(None)) - - def test_validate_project_lead_email_erroneous_type(self): - """Test basic functionality of validate_project_lead_email.""" - with self.assertLogs(logger, level='ERROR') as cmd: - Net.validate_project_lead_email(12) - msg = "The project lead email(s) should be a string." - self.assertEqual(cmd.output, [":".join(['ERROR', SCR_DIR, msg])]) - - def test_validate_project_lead_email_invalid_email(self): - """Test basic functionality of validate_project_lead_email.""" - email = 'mpasscal.edu' - self.assertFalse(Net.validate_project_lead_email(email)) - - def test_validate_project_lead_email_invalid_emails(self): - """Test basic functionality of validate_project_lead_email.""" - emails = 'mpasscal.edu, d@passcal.edu' - self.assertFalse(Net.validate_project_lead_email(emails)) - - def test_validate_project_lead_email_valid_email(self): - """Test basic functionality of validate_citation_dataset_doi.""" - email = 'm@passcal.edu' - self.assertTrue(Net.validate_project_lead_email(email)) - - def test_validate_project_lead_email_valid_emails(self): - """Test basic functionality of validate_citation_dataset_doi.""" - emails = 'm@passcal.edu, d@passcal.edu' - self.assertTrue(Net.validate_project_lead_email(emails)) - class TestBaseSta(unittest.TestCase): """Test suite for BaseSta data class.""" @@ -181,7 +111,7 @@ class TestBaseSta(unittest.TestCase): def setUp(self): """Set up test fixtures""" data_input = 2201.77 - min_elev, max_elev = [abs(data_input) * x for x in [0.99, 1.01]] + min_elev, max_elev = [data_input + x for x in [-100, 100]] param = ("station elevation", -500, 8500, min_elev, max_elev, data_input, 'm') self.param = param @@ -210,12 +140,14 @@ class TestBaseSta(unittest.TestCase): def test_validate_geos_do_not_match_logger_recorded_metadata(self): """Test basic functionality of validate_geos.""" - geo, _, _, _, _, val, units = self.param - with self.assertLogs(logger, level='ERROR') as cmd: - BaseSta.validate_geos(self.param, 2250) + geo, _, _, _, max_val, val, units = self.param + with self.assertLogs(logger, level='WARNING') as cmd: + BaseSta.validate_geos(self.param, 2350) + tolerance = max_val - val msg = ("Unexpected {0}! Provided {0} should roughly match the {0} " - "recorded by the on-site GPS ({1:.3f}{2}).".format(geo, val, units)) - self.assertEqual(cmd.output, [":".join(['ERROR', SCR_DIR, msg])]) + "recorded by the on-site GPS ({1:.3f} ± {2:.3f}{3})." + .format(geo, val, tolerance, units)) + self.assertEqual(cmd.output, [":".join(['WARNING', SCR_DIR, msg])]) class TestSta(unittest.TestCase): @@ -247,10 +179,6 @@ class TestSta(unittest.TestCase): """Test basic functionality of validate_archive_id.""" self.assertTrue(Sta.validate_archive_id('KELLY')) - def test_validate_submitter_email_undefined(self): - """Test basic functionality of validate_submitter_email.""" - self.assertTrue(Sta.validate_submitter_email(None)) - class TestRun(unittest.TestCase): """Test suite for Run data class.""" @@ -266,16 +194,16 @@ class TestRun(unittest.TestCase): """Test basic functionality of validate_comps_rec.""" with self.assertLogs(logger, level='ERROR') as cmd: self.run.validate_comps_rec(None) - msg = ("The list of components recorded for run '{}' should be a " - "string. Example: {}".format(self.run.run_id, VALID_COMPS[0])) + msg = ("The 'components recorded' metadata field is empty for run '{}'. " + "Example of valid field: {}".format(self.run.run_id, VALID_COMPS[0])) self.assertEqual(cmd.output, [":".join(['ERROR', SCR_DIR, msg])]) def test_validate_comps_rec_erroneous_type(self): """Test basic functionality of validate_comps_rec.""" with self.assertLogs(logger, level='ERROR') as cmd: self.run.validate_comps_rec(12) - msg = ("The list of components recorded for run '{}' should be a " - "string. Example: {}".format(self.run.run_id, VALID_COMPS[0])) + msg = ("The 'components recorded' metadata field is empty for run '{}'. " + "Example of valid field: {}".format(self.run.run_id, VALID_COMPS[0])) self.assertEqual(cmd.output, [":".join(['ERROR', SCR_DIR, msg])]) def test_validate_comps_rec_invalid_magnetic_channel(self): diff --git a/tests/test_metadata_conversion.py b/tests/test_metadata_conversion.py index 0ee9b81843936d9795739ca9747ac459965c97f5..fb79efc2bd5b0b02b0b5e1f79476fcea594d72d5 100644 --- a/tests/test_metadata_conversion.py +++ b/tests/test_metadata_conversion.py @@ -50,16 +50,16 @@ class TestGetXmlFieldGetComment(unittest.TestCase): def test_get_xml_field(self): """Test basic functionality of get_xml_field.""" - field_info, field_value = get_xml_field(self.md.net, self.net_fields[1]) + field_info, field_value = get_xml_field(self.md.net, self.net_fields[0]) self.assertListEqual(field_info, ['end_date']) self.assertEqual(field_value, UTCDateTime('2021-03-02T00:00:00.000000Z')) - field_info, field_value = get_xml_field(self.md.net, self.net_fields[6]) + field_info, field_value = get_xml_field(self.md.net, self.net_fields[4]) self.assertListEqual(field_info, ['comments', 'mt.network.comments']) self.assertEqual(field_value, 'Network Test') def test_get_comment_no_run_id_considered(self): """Test basic functionality of get_comment.""" - field_info, field_value = get_xml_field(self.md.net, self.net_fields[6]) + field_info, field_value = get_xml_field(self.md.net, self.net_fields[4]) comment = get_comment(field_info, field_value) self.assertEqual(comment.value, 'Network Test') self.assertEqual(comment.subject, 'mt.network.comments') @@ -103,13 +103,10 @@ class TestCreateInventoryObjects(unittest.TestCase): """Test basic functionality of create_net.""" net = self.md.net net.start = UTCDateTime(2020, 10, 1, 0, 0) - net.restricted_status = 'open' net.project = 'PIS_MT' net.project_lead_author = 'Maeva Pourpoint' - net.project_lead_email = 'm@passcal.edu' net.project_lead_organization = 'PASSCAL' net.acquired_by_author = 'Maeva Pourpoint' - net.citation_dataset_doi = 'DOI:10.7914/SN/EM, DOI:10.3421/SN/EG' net.country = 'USA' net.geo_name = 'Southwest' net.name = 'MT experiment in Kelly ranch' @@ -123,12 +120,10 @@ class TestCreateInventoryObjects(unittest.TestCase): sta = self.md.sta sta.start = UTCDateTime(2020, 9, 30, 0, 0) sta.end = UTCDateTime(2020, 10, 2, 0, 0) - sta.restricted_status = 'open' sta.lon = -107.13 sta.elev = 2201 - sta.software_version = '2021.320' + sta.software_version = '2022.332' sta.submitter_author = 'David Goldak' - sta.submitter_email = 'dgoldak@passcal.nmt.edu' sta.submitter_organization = 'PASSCAL' runs = self.md.run runs[0].md_by_author = 'Tom'