gnssrefl.tracks_qc module

Quality-control edits for tracks.json / vwc_tracks.json files.

Available operations:

  • split_epoch / merge_epochs: subdivide one epoch into two at a chosen MJD, or combine two adjacent epochs back into one. Use when hardware (receiver or satellite) or significant environmental changes occur that motivate a new apriori RH on the same geometric track.

  • ignore_range / unignore_range: mark (or un-mark) a time window within an epoch so its arcs are excluded from fits and stats.

  • deactivate_epoch: turn an epoch off without deleting it, so downstream tools skip it.

  • delete_track: drop a track entirely from this file onward.

  • save_tracks: write the JSON back after refitting every active epoch and (for vwc_tracks files) recomputing apriori_RH / RH_std from the arcs. Read with tracks.load_tracks_json.

Edits are applied to an in-memory dict and only become a self- consistent file after save_tracks runs the refit + stats pass. Structurally invalid edits raise ValueError.

gnssrefl.tracks_qc.apply_auto_removal(tracks_json, fr, good_keys)

Drop bad tracks/epochs at frequency fr via the QC primitives.

For every track whose freq equals fr, walks its active epochs. Epochs whose (track_id, track_epoch) is not in good_keys are removed: the whole track goes via delete_track when none of its active epochs survive, otherwise each bad epoch goes via deactivate_epoch. Tracks on other frequencies are untouched.

Parameters:
  • tracks_json (dict) – vwc_tracks.json document (mutated in place).

  • fr (int) – Frequency to filter. Only tracks with track[‘freq’] == fr are considered.

  • good_keys (set of (int, int)) – (track_id, track_epoch) pairs that passed the run’s QC.

Returns:

{‘tracks_removed’, ‘tracks_total’, ‘epochs_deactivated’, ‘epochs_total’}. Totals count only tracks/epochs at fr.

Return type:

dict

gnssrefl.tracks_qc.check_track_exists(tracks_json, track_id)
gnssrefl.tracks_qc.compute_tracks_stats(tracks_json, arcs_df, apriori_rh_ndays)

Compute n_qc_arcs and apriori_RH/RH_std on active epochs.

Inactive epochs are skipped; their n_qc_arcs is zeroed at deactivation time.

gnssrefl.tracks_qc.deactivate_epoch(tracks_json, track_id, epoch_id)

Mark an active epoch inactive. Refit/stats will skip it on save.

Zeros n_arcs and n_qc_arcs so inactive epochs carry the invariant “0 arcs live”. epoch_id is scoped to this saved tracks_json; see tracks.py module docstring.

gnssrefl.tracks_qc.delete_track(tracks_json, track_id)

Remove a track entirely from tracks_json[‘tracks’].

track_id is the geometric identity and is stable across saves (see tracks.py module docstring); deleting it means that id is gone from this snapshot onward.

gnssrefl.tracks_qc.filter_by_ignored_ranges(sub, ranges)

Return sub with rows whose mjd falls in any ignored range removed.

gnssrefl.tracks_qc.find_active_epoch_containing(track, mjd)
gnssrefl.tracks_qc.ignore_range(tracks_json, track_id, epoch_id, mjd_start, mjd_end)

Append [mjd_start, mjd_end] to the target active epoch’s ignored ranges.

Range must lie inside the epoch window and satisfy mjd_end > mjd_start. epoch_id is scoped to this saved tracks_json; see tracks.py module docstring.

gnssrefl.tracks_qc.merge_epochs(tracks_json, track_id, epoch_id_a, epoch_id_b)

Merge two adjacent active epochs with matching constellation / match_T.

Window becomes the union; ignored_ranges are concatenated. anchor_time / repeat_interval_d inherit from the earlier epoch and are refit on save. Renumbers all epochs after the merged pair (epoch_id is scoped to this saved tracks_json; see tracks.py module docstring).

gnssrefl.tracks_qc.recompute_derived_fields(tracks_json, arcs_df)

Refit active epochs and refresh every derived per-epoch field.

For file_type == 'vwc_tracks' JSON, also recomputes each epoch’s apriori_RH / RH_std / n_qc_arcs. The order matters: recompute_n_arcs and compute_tracks_stats share the same ignored-range mask logic and must agree on every epoch.

gnssrefl.tracks_qc.recompute_durations(tracks_json)

Refresh duration_d for every epoch from current start/end times.

gnssrefl.tracks_qc.recompute_metadata_aggregates(tracks_json, arcs_df)

Refresh top-level metadata totals and the data time range.

Writes n_tracks, n_epochs, n_arcs (and n_qc_arcs for vwc_tracks) and start_time / end_time / duration_d on metadata. Rebuilds metadata key order so the totals sit together. Removes the legacy nested time_range field.

gnssrefl.tracks_qc.recompute_n_arcs(tracks_json, arcs_df)

Set n_arcs on every active epoch from arcs_df.

Counts arcs whose (track_id, track_epoch) matches the epoch and whose mjd is outside any ignored_ranges. Writes 0 when no arcs match. Inactive epochs are skipped; their n_arcs is zeroed at deactivation time.

gnssrefl.tracks_qc.refit_active_epochs(tracks_json, arcs_df)

For each active epoch, refit anchor_time / repeat_interval_d via fit_segment.

gnssrefl.tracks_qc.renumber_epoch_ids(track)
gnssrefl.tracks_qc.reorder_epoch_keys(epoch)

Rewrite epoch in place with keys in canonical order.

gnssrefl.tracks_qc.require_active_epoch(track, track_id, epoch_id)
gnssrefl.tracks_qc.save_tracks(tracks_json, path, tool, note='', arcs_df=None)

Append a history entry, refresh every derived field, and write the JSON.

For file_type == 'vwc_tracks' JSON, also recomputes each epoch’s apriori_RH and RH_std from arcs in that epoch’s trailing apriori_rh_ndays window. Writes atomically (temp file + rename).

arcs_df can be passed by callers that already have the walked arcs (avoids re-reading the results files). If None, arcs are loaded via load_gnssir_results_from_tracks.

gnssrefl.tracks_qc.split_epoch(tracks_json, track_id, split_mjd)

Split one active epoch into two adjacent actives at split_mjd.

split_mjd must lie strictly inside the target epoch’s window. Both halves inherit the original epoch’s fit parameters (anchor_time, repeat_interval_d, az_avg_minel, az_drift_rate); fresh values come from save_tracks’ refit pass.

gnssrefl.tracks_qc.unignore_range(tracks_json, track_id, epoch_id, mjd_start, mjd_end)

Subtract [mjd_start, mjd_end] from the epoch’s ignored ranges.

Raises if the subtraction leaves every existing range unchanged (i.e. no overlap anywhere). epoch_id is scoped to this saved tracks_json; see tracks.py module docstring.

gnssrefl.tracks_qc.validate_epoch_ids(tracks_json)

Enforce that each track’s epoch ids are exactly 0..N-1 in order.

Raises ValueError on the first violation.