Skip to content

API Reference ​

This page gives all the exported functions and types, in groups by their purpose.

Contents ​

Data model ​

Timekeepers.TimekeeperChannel Type
julia
TimekeeperChannel

One component of a recording. It holds the samples and all the data that you need to put them in time and to interpret them.

Fields

  • component::Symbol – the standard component name (:bx, :by, :bz, :e1, :e2, ...).

  • data::Vector{Float64} – the samples, in units.

  • sample_rate::Float64 – samples per second.

  • start::DateTime – timestamp of data[1].

  • units::String – physical units, e.g. "nT" or "mV/km".

  • source_file::String – the file that the samples came from (empty for synthetic data).

  • header::Dict{String, Any} – the header fields of the format, kept for the writer.

See also TimekeeperRun, end_time.

source
Timekeepers.TimekeeperRun Type
julia
TimekeeperRun

One continuous recording. It is a set of TimekeeperChannels with the same sample rate and start time. It also holds the run metadata that a writer needs to make the original file again.

Fields

  • site::String – site name, usually from the file or directory name.

  • instrument::String – instrument description, e.g. "Metronix ADU".

  • source_format::Symbol – :lemi424, :geomag or :metronix.

  • channels::Dict{Symbol, TimekeeperChannel} – channels, with the component as key.

  • metadata::Dict{Symbol, Any} – run metadata: position, sample rate, header values and, for Metronix, the paths of the XML templates for the writer.

The readers (read_timekeeper, read_lemi424, read_geomag, read_metronix) return a run. The writers use it. Use to_timearray to change it to a TimeSeries.TimeArray.

source
Timekeepers.components Function
julia
components(run::TimekeeperRun) -> Vector{Symbol}

All the component names in run, in alphabetical order.

See also default_components.

source
Timekeepers.default_components Function
julia
default_components(run::TimekeeperRun) -> Vector{Symbol}

The components of run in the usual magnetotelluric plot order (bx, by, bz, e1, e2). Components that are not in the run are not in the list. If the run has none of these names, the function returns components.

source
Timekeepers.sampling_rate Function
julia
sampling_rate(run::TimekeeperRun) -> Float64

The sample rate of run in Hz. If the channels have different rates, the function gives an error, because a TimekeeperRun holds one rate. To separate a Metronix site with more than one rate, use metronix_site_rates and the procedure that splits a site by rate.

source
Timekeepers.start_time Function
julia
start_time(run::TimekeeperRun) -> Union{DateTime, Nothing}

The earliest start of the channels in run, or nothing if the run has no channels.

source
Timekeepers.end_time Function
julia
end_time(ch::TimekeeperChannel) -> DateTime
end_time(run::TimekeeperRun) -> Union{DateTime, Nothing}

The timestamp of the last sample. The function calculates it from the start, the number of samples and the sample rate. For a run, it is the latest end of its channels.

source
Timekeepers.duration_seconds Function
julia
duration_seconds(run::TimekeeperRun) -> Float64

The length of run in seconds (nsamples / sample_rate), or 0.0 if the run is empty.

source

TimeArray interop ​

Timekeepers.to_timearray Function
julia
to_timearray(run::TimekeeperRun; components = default_components(run), axis = :auto) -> TimeArray

Put the selected channels of run into a TimeSeries.TimeArray. Each component is one column, in the order that you give.

axis sets the timestamp type:

  • :datetime gives DateTime stamps with millisecond resolution.

  • :time gives Time stamps with nanosecond resolution (necessary above 1 kHz).

  • :auto selects :time if the rate is more than 1 kHz.

If the channels have different lengths, the function cuts them to the shortest length and gives a warning.

The metadata holds the site, the instrument, the source format, the sample rate, the start time and the units of each component. These values replace the same keys in run.metadata.

source
Timekeepers.from_timearray Function
julia
from_timearray(ta::TimeArray; site, instrument, source_format, units, metadata) -> TimekeeperRun

The opposite of to_timearray. It wraps each column of ta as a TimekeeperChannel. The sample rate and the start time come from the metadata of ta. If the metadata does not have them, the function calculates them from the timestamps. The default unit is the standard unit for each component name.

source

Format-agnostic I/O ​

Timekeepers.read_timekeeper Function
julia
read_timekeeper(path; format = :auto, kwargs...) -> TimekeeperRun

Read a supported format into a TimekeeperRun. The function calls read_lemi424, read_geomag or read_metronix.

With format = :auto, the function finds the format:

  • A .ats file, or a directory that contains one, is Metronix.

  • A .txt file with a GEOMAG header is GEOMAG. Other .txt files are LEMI-424.

To skip the detection, give format = :lemi424, :geomag or :metronix. The function gives the other keywords to the selected reader.

source
Timekeepers.write_timekeeper Function
julia
write_timekeeper(path, run::TimekeeperRun; format = :auto) -> String

Write run in its native format. The function calls write_lemi424, write_geomag or write_metronix.

With format = :auto, the source_format of the run selects the writer. Thus, you can write a file that you read with read_timekeeper without more arguments.

source
Timekeepers.default_data_dir Function
julia
default_data_dir() -> String

The path that the app and the examples use if you do not give a data directory. It is data/ next to the package root if that directory exists. If not, it is examples/data/. The path that the function returns possibly does not exist.

source

LEMI-424 ​

Timekeepers.LEMI424_COLUMNS Constant
julia
LEMI424_COLUMNS

The names of the 24 fields of a LEMI-424 record, in the file order. The fields are separated by whitespace. There are six date and time fields, then the magnetic, electric, temperature, GPS and housekeeping columns. The reader uses the names to find the tokens. The writer uses them to make the original layout again.

source
Timekeepers.read_lemi424 Function
julia
read_lemi424(path; site, include_aux=true) -> TimekeeperRun

Read a LEMI-424 text file into a run with one channel for each component. The function accepts the file path, an optional site name and a flag for the auxiliary channels (temperatures, GPS, battery). It returns a TimekeeperRun at 1 Hz. It makes a channel for each known data column. If the file does not have the column, the channel contains NaN.

source
Timekeepers.load_lemi424 Function
julia
load_lemi424(path; components, site, include_aux=true) -> TimeArray

Load a LEMI-424 text file as a TimeArray. The function accepts:

  • the file path;

  • the components to keep (from LEMI424_DEFAULT_COMPONENTS);

  • an optional site name;

  • a flag to add the auxiliary columns to the metadata.

It returns a TimeArray at 1 Hz. If include_aux is set, the metadata holds :aux_columns.

source
Timekeepers.write_lemi424 Function
julia
write_lemi424(path, run; timestamps=nothing, aux=nothing) -> String

Write a run as a LEMI-424 text file. The function accepts the destination path, the run, an optional timestamp vector and optional auxiliary columns. If you do not give the timestamps, the function makes them at 1 Hz. It returns the path. If the run is not at 1 Hz, it gives an error. The number of records is the length of the shortest channel.

source
julia
write_lemi424(path, ta; kwargs...) -> String

Write a TimeArray as a LEMI-424 text file. The function accepts the destination path and the array. It gives kwargs to from_timearray, and it returns the path. It uses the auxiliary columns in the metadata again. Thus, a cycle of read and write keeps the original GPS and housekeeping fields.

source

GEOMAG ​

Timekeepers.read_geomag Function
julia
read_geomag(path; site, include_aux=true) -> TimekeeperRun

Read a GEOMAG text file into a run with one channel for each component. The function accepts the file path, an optional site name and a flag for the temperature channels. It returns a TimekeeperRun.

source
Timekeepers.load_geomag Function
julia
load_geomag(path; components, site, include_aux=true) -> TimeArray

Load a GEOMAG text file as a TimeArray. The function accepts:

  • the file path;

  • the components to read (keys of GEOMAG_COLUMN_INDEX);

  • an optional site name;

  • a flag to add the other temperature columns to the metadata as :aux_columns.

It returns the array. The sample rate comes from the header. If the header does not give it, the rate comes from the first gap between timestamps.

source
Timekeepers.write_geomag Function
julia
write_geomag(path, ta) -> String

Write a TimeArray as a GEOMAG text file. The function accepts the destination path and the array, and returns the path. If the array does not have one of :bx, :by, :bz, :e1 and :e2, the function writes 0 for it. Thus, the fixed column layout of the format stays the same. The temperatures come from the metadata :aux_columns. If the metadata does not have them, they are NaN.

source
julia
write_geomag(path, run) -> String

Write a run as a GEOMAG text file. The function accepts the destination path and the run, and returns the path. It first keeps only the GEOMAG_DEFAULT_COMPONENTS of the run.

source

Metronix ATS ​

Timekeepers.METRONIX_CHANNEL_MAP Constant
julia
METRONIX_CHANNEL_MAP

The map from Metronix ATS channel types to Timekeepers component names: "Ex" => :e1, "Ey" => :e2, "Hx" => :bx, "Hy" => :by, "Hz" => :bz. A channel type that is not in this table stays as a symbol with the same name.

source
Timekeepers.read_metronix Function
julia
read_metronix(path; site, components = nothing, include_aux = true) -> TimekeeperRun

Read one Metronix ADU run (its .ats binary files and their .xml sidecar) into a TimekeeperRun. The XML is optional. All the data that the function reads comes from the .ats headers. Thus, the function can read a run without its XML. It then gives a warning, and write_metronix writes only the .ats files of the run.

A meas_* directory can hold more than one run, for example a 128 Hz run and 4096 Hz bursts. The run number and the rate in the filenames identify each run. path selects one run: the .xml of the run, one of its .ats files, or a meas_* directory that holds one run. metronix_site_runs gives the runs of a site.

The function multiplies the samples by the LSB value of each file. It changes the channel types with METRONIX_CHANNEL_MAP. To read only some components, give components. The default site is the name of the directory above the meas_* directory.

The metadata keeps the XML path and the filename tokens. Thus, write_metronix can use the original names again.

source
Timekeepers.load_metronix Function
julia
load_metronix(meas_dir; components = nothing, kwargs...) -> TimeArray

read_metronix and then to_timearray. Use this function to get a TimeArray in one step, not a run.

source
Timekeepers.write_metronix Function
julia
write_metronix(dest_meas_dir, run::TimekeeperRun) -> String

Write run as a Metronix measurement directory. The directory gets one .ats file for each channel and an .xml sidecar. The sidecar comes from the template that the reader used. The run must come from read_metronix. If the run has no XML, the function writes it without an XML and gives a warning. The function returns dest_meas_dir.

source
Timekeepers.is_metronix_site Function
julia
is_metronix_site(dir) -> Bool

Tells if dir holds Metronix data: its own .ats files, or meas_* directories with .ats files.

source
Timekeepers.metronix_site_rates Function
julia
metronix_site_rates(dir) -> Vector{Float64}

The different sampling rates in a Metronix site, in increasing order. The list is empty if dir is not a site.

source
Timekeepers.load_metronix_site Function
julia
load_metronix_site(site_dir; rate = nothing) -> TimeArray

Read the runs of one sampling rate from a Metronix site, or from one meas_* directory (which can hold runs at more than one rate). The function reads them in time order, as one record.

  • Up to 1 kHz, the runs use one time grid, and NaN fills the gaps between them.

  • Above 1 kHz, the function joins the runs end to end. The gaps stay as jumps in time.

A site with one rate does not need rate. For a site with more than one rate, you must give rate. metronix_site_rates gives the rates and reads no samples.

The function reads the runs of the selected rate from the meas_* directories of the site, and writes nothing. If the site has directories for each rate next to it (DF002.128, made by a masked write), it reads from those, so that the cuts show.

source
Timekeepers.metronix_site_runs Function
julia
metronix_site_runs(dir) -> Dict{Float64, Vector{String}}

All the runs in a Metronix site, in groups by sampling rate, in order of start time. The name of each run is the path of its .xml, which read_metronix accepts.

A run is one run number at one rate. It is not one meas_* directory: an ADU can write a long 128 Hz run and some 4096 Hz bursts into the same directory. The result does not include XMLs without recorded .ats data. dir is a site of meas_* directories or one meas_* directory. The function reads only the .ats headers.

source
Timekeepers.write_metronix_site Function
julia
write_metronix_site(run; mask = nothing, dest = nothing, min_samples = 1) -> (String, Vector{String})

Cut run at the masked intervals. Write the parts into dest, a meas_* directory, in place of the files of the run there.

  • The default dest is the meas_* directory of the run in its rate directory. For example, it is DF002.128/meas_2021-09-25_14-02-01 for a 128 Hz run of DF002. A run that the reader read from there is replaced in the same location.

  • The first good part keeps the run number. Each subsequent part gets the next free run number at the rate of the run in dest.

  • If the rate needs it, the function moves the start of a segment to a whole second.

  • Some segments are too short to keep as a run: shorter than one whole second (the ATS header and the XML cannot describe them), or shorter than min_samples. The function ignores such a segment and gives a warning.

  • With mask = nothing, the function writes the full run.

The function returns dest and the runs that it wrote. The name of each run is a name that read_metronix accepts. For a full site, use write_metronix_site_masked.

source
Timekeepers.write_metronix_site_masked Function
julia
write_metronix_site_masked(site_dir; intervals=[], rate_intervals=Dict(), min_samples=1, only=nothing, format=:default) -> Vector{String}

Cut the masked intervals out of a Metronix site. The cuts occur in the rate directories of the site, <site>.128, <site>.4096, ... next to it. If these directories are missing, split_metronix_site first makes them. The site itself never changes. site_dir is the site or one of its rate directories. A rate directory stands for the full site.

The intervals are DateTime tuples, usually the mask intervals from the app. rate_intervals is a map from a sampling rate to the intervals for its runs. intervals cuts the runs at all the other rates. Keep the intervals for each rate if you drew the masks at one rate. For example, a 128 Hz run that recorded in the same hours as 4096 Hz bursts must not lose the parts that you masked on the bursts.

If no interval touches a run, the run stays as it is. If an interval touches a run, the good parts of the run replace it in its meas_* directory. The first part keeps the run number. Each subsequent part gets the next free run number at that rate in that directory, after all the runs and scheduled jobs there. Thus, the function does not rename other files:

julia
DF002.4096/meas_2021-09-26_00-00-00/   R000 up to the cut, R002 after it,
                                       R001 untouched, the .kml

The function cuts the .ats files of a part from the run. The XML of the part is the XML of the run. Only the start, the stop, the numbers of samples and the file size change, and the .ats names when the part gets a new number. Each .ats header that gives the name of its XML changes to the new XML. The .kml stays. The masked time is not in the data, and nothing fills it.

Some parts are too short to keep as a run: shorter than one whole second, or shorter than min_samples. The function ignores such a part and gives a warning. If no run stays in a meas_* directory, the function removes the directory. If a run had no XML, the function writes it without an XML. The function returns the rate directories that it cut.

Each rate directory with a cut gets a mask.csv. The file gives each removed part, one row for each part. The rows of all the writes stay in the file, in time order:

julia
start_sample,end_sample,start_time,end_time
17,40,2025-04-01T07:00:08.000,2025-04-01T07:00:10.875

The samples have numbers from 1 at the start of the run, as the site recorded it. The two ends are included. Thus, the numbers stay the same after more cuts. The times are the times of the first and the last removed sample. The file includes the parts moved to a whole second and the parts that were too short. Thus, the file describes the data as written. A short README.md next to the file tells where the directory came from.

format = :default gives the layout above. :MTH5 is reserved for MTH5 output, which is not available yet. To cut only some runs, give only: run names from metronix_site_runs, or meas_* directories, in the rate directories.

source
Timekeepers.split_metronix_site Function
julia
split_metronix_site(site_dir; dest = nothing, on_run = nothing) -> Vector{String}

Copy a Metronix site into one directory for each sampling rate. The name of each directory is <site>.<rate>, and it is in dest (default: next to the site). Each directory is a usual site of meas_* directories with the runs at that rate:

julia
DF002/meas_2021-09-25_14-02-01/   ->   DF002.128/meas_2021-09-25_14-02-01/
                                       DF002.4096/meas_2021-09-25_14-02-01/

The .ats files and the .xml of each run go to its rate. The XML of a job that did not record goes to the rate in its filename. Each other file of a meas_* directory (the .kml) goes to each rate that the directory holds. The function copies the files byte for byte and does not change the source.

write_metronix_site_masked cuts the masked intervals in the rate directories. Thus, the function never copies over a rate directory that exists. It makes only the missing rates. It copies each rate directory fully under a temporary name and then renames it. Thus, a split that stops before the end leaves no partial rate directory. To start a rate again from the site, delete its rate directory. The function calls on_run(i, n, run_name) before it copies each run. It returns the rate directories, the lowest rate first.

source
Timekeepers.metronix_site_is_split Function
julia
metronix_site_is_split(site_dir; dest = nothing) -> Bool

Tells if site_dir is already split by rate: each rate of the site has its rate directory in dest (default: next to the site). The function makes a rate directory only as a full directory. Thus, a rate directory that exists is complete.

source

Masking and cleaning ​

Timekeepers.TimekeeperMask Type
julia
TimekeeperMask(ta::TimeArray)
TimekeeperMask(timestamps, masked, intervals)

A good or bad flag for each sample of a time series, with the list of the contiguous bad intervals.

A mask that you make from a TimeArray has all its samples good. Edit it with mask_interval!, unmask_interval! and clear_mask!. The intervals field always agrees with the flags.

Fields

  • timestamps::Vector{T} – the time axis of the mask.

  • masked::BitVector – true marks a bad sample.

  • intervals::Vector{Tuple{T, T}} – the contiguous masked spans.

To make outputs, use cleaned_timearray, good_segments and sample_weights. To save the mask and read it again, use write_mask and read_mask.

source
Timekeepers.mask_interval! Function
julia
mask_interval!(mask, start_time, end_time) -> TimekeeperMask

Mark each sample with a timestamp in [start_time, end_time] as bad. You can give the two limits in either order. The function changes mask and returns it.

julia
mask = TimekeeperMask(ta)
mask_interval!(mask, DateTime(2020, 10, 4, 0, 10), DateTime(2020, 10, 4, 0, 20))
source
Timekeepers.unmask_interval! Function
julia
unmask_interval!(mask, start_time, end_time) -> TimekeeperMask

The opposite of mask_interval!. Mark each sample in the closed interval as good again. The function changes mask and returns it.

source
Timekeepers.clear_mask! Function
julia
clear_mask!(mask::TimekeeperMask) -> TimekeeperMask

Mark all the samples as good and remove all the intervals. The function changes mask and returns it.

source
Timekeepers.masked_samples Function
julia
masked_samples(mask::TimekeeperMask) -> Int

The number of samples that are bad now.

source
Timekeepers.combine_masks Function
julia
combine_masks(first_mask, masks...) -> TimekeeperMask

The union of masks on the same time axis. A sample is bad if it is bad in one or more inputs. All the masks must have the same length. The function returns a new mask and does not change the inputs.

source
Timekeepers.cleaned_timearray Function
julia
cleaned_timearray(ta::TimeArray, mask::TimekeeperMask; mode = :nan) -> TimeArray
cleaned_timearray(app::TKApp; mode = :nan) -> TimeArray

Apply mask to ta.

  • mode = :nan (the default) keeps the full time axis and writes NaN in the masked rows. Thus, the gaps stay visible and the interval between samples stays the same.

  • mode = :drop removes the masked rows. The time axis is then not uniform.

The metadata of the result holds :mask_intervals, :masked_samples and :cleaning_mode. If the length of the mask is not the length of ta, the function gives an error.

source
Timekeepers.good_segments Function
julia
good_segments(ta::TimeArray, mask::TimekeeperMask; min_samples = 1) -> Vector{TimeArray}
good_segments(app::TKApp; min_samples = 1) -> Vector{TimeArray}

Cut ta into the contiguous parts without a mask. The function removes each part that is shorter than min_samples. Use this function to give clean data to a processing step that needs windows without interruptions. For example, use min_samples = 256 for an FFT of 256 points.

source
Timekeepers.sample_weights Function
julia
sample_weights(mask::TimekeeperMask; good = 1.0, bad = 0.0) -> Vector
sample_weights(app::TKApp; good = 1.0, bad = 0.0) -> Vector

A weight for each sample, for robust processing. The weight is good where the mask is clear and bad where the mask is set. Use the weights to give a mask to a weighted regression without removing samples.

source
Timekeepers.write_cleaned Function
julia
write_cleaned(path, ta::TimeArray, mask::TimekeeperMask; mode = :nan, delimiter = ',') -> String
write_cleaned(path, app::TKApp; mode = :nan, delimiter = ',') -> String

Write cleaned_timearray to a delimited text file. The file has a timestamp column, then one column for each component. The function returns path.

To keep the format of the instrument, use write_timekeeper or the writer of the format.

source
Timekeepers.write_mask Function
julia
write_mask(path, mask::TimekeeperMask; delimiter = ',') -> String
write_mask(path, app::TKApp; delimiter = ',') -> String

Write the masked intervals to a file with the two columns start,stop and a header row. The function returns path. To read the file, use read_mask.

source
Timekeepers.read_mask Function
julia
read_mask(path, ta::TimeArray; delimiter = ',') -> TimekeeperMask

Make a mask on the time axis of ta from an interval file that write_mask wrote. The function applies the intervals with mask_interval!. Thus, you can apply a mask that you saved for one series to a different series that covers the same times.

source

Interactive explorer ​

Timekeepers.TKApp Type
julia
TKApp(; size = (1600, 900))
TKApp(path::AbstractString; kwargs...)
TKApp(ta::TimeArray; size = (1600, 900), source_format = :lemi424, source_path = "")

Make the interactive Timekeepers explorer for a time series. The function does not open a window. If you give a path, the function first loads the data: one file, or a site directory. For a site directory, it joins the runs and fills the gaps. If you give nothing, the app starts empty and waits for Load.

The app owns the data, a TimekeeperMask and the GLMakie figure. To get the results, use cleaned_timearray, good_segments, sample_weights, write_cleaned and write_mask. Each of these functions accepts a TKApp directly.

Use run_tkapp to make and show the app in one step. display(app) opens the window and does not wait.

The app needs a desktop session with OpenGL 3.3 or newer.

source
Timekeepers.run_tkapp Function
julia
run_tkapp(; kwargs...)
run_tkapp(path::AbstractString; kwargs...)
run_tkapp(ta::TimeArray; kwargs...)
run_tkapp(app::TKApp)

Open the Timekeepers explorer window and wait until you close it. Then return the TKApp. Thus, you keep the mask after the session.

julia
using Timekeepers
app = run_tkapp("data/LEMI090.txt")
segments = good_segments(app; min_samples = 256)

In the window:

  • Use Load Run… or Load Site… to open data.

  • Use the Window menu and the slider to scroll the record.

  • Left-drag to select an interval.

  • Use Mask, Unmask or Clear to edit the interval.

  • Use Write to export in the source format.

  • The View menu adds a PSD panel for each channel.

The app needs a desktop session with OpenGL 3.3 or newer. To make the app without showing it, refer to TKApp.

source

Survey dashboard ​

Timekeepers.SurveyRun Type
julia
SurveyRun

One continuous recording that scan_survey found. The scan reads only the headers.

Fields

  • path::String – the XML (Metronix) or the data file of the run.

  • sample_rate::Float64 – samples per second.

  • start::DateTime – time of the first sample.

  • stop::DateTime – time immediately after the last sample.

  • n_samples::Int – samples for each channel.

  • components::Vector{Symbol} – the recorded channels (:e1, :e2, :bx, :by, :bz). For a Metronix run, these are all its .ats files. For LEMI-424 and GEOMAG, these are the columns that are not zero or NaN at the two ends of the file.

source
Timekeepers.SurveySite Type
julia
SurveySite

A site that scan_survey found: its runs and its position.

Fields

  • name::String – the name of the site directory.

  • path::String – the site directory.

  • format::Symbol – :metronix, :lemi424 or :geomag.

  • latitude::Float64, longitude::Float64 – decimal degrees, NaN if the headers give no position.

  • elevation::Float64 – metres, NaN if not known.

  • runs::Vector{SurveyRun} – in order of start time.

source
Timekeepers.Survey Type
julia
Survey

All the sites in a survey directory, as scan_survey returns them. Use a position or a site name as the index: survey["site004"].

source
Timekeepers.scan_survey Function
julia
scan_survey(root; include_split = false, maxdepth = 4, progress = nothing) -> Survey

Find all the sites in root and make an index of their runs. The function reads only the headers. It does not read the samples.

  • A site is a directory that holds Metronix meas_* directories (or .ats files), or LEMI-424 or GEOMAG files.

  • The function looks into the directories that are not sites, down to maxdepth levels.

  • The position of each site comes from its headers: the .ats header of a Metronix run, the GPS columns of a LEMI-424 record, or the header block of a GEOMAG file.

The function ignores the rate directories that a split of a Metronix site makes (site002.128 next to site002). Thus, it does not count a site two times. To make an index of them as separate sites, give include_split = true.

The function uses the first and the last line of a LEMI-424 or GEOMAG file as the start and the end of the recording. Thus, it does not see gaps in one file.

progress, when given, is a function that receives a line of text for each directory that the function looks into and for each site that it finds.

See also site_references, reference_plan, run_tkdash.

source
Timekeepers.site_rates Function
julia
site_rates(site) -> Vector{Float64}

The different sampling rates of a SurveySite, in increasing order.

source
Timekeepers.site_components Function
julia
site_components(site) -> Vector{Symbol}

All the channels that a run of a SurveySite recorded, with the electric channels first.

source
Timekeepers.has_magnetic Function
julia
has_magnetic(site) -> Bool

Tells if a SurveySite recorded the two horizontal magnetic channels, Hx and Hy. A base or remote site must supply these channels. A site with only electric channels (a telluric site) can have base and remote sites, but it can never be one.

source
Timekeepers.survey_rates Function
julia
survey_rates(survey) -> Vector{Float64}

The different sampling rates of all the sites of a Survey, in increasing order.

source
Timekeepers.recording_intervals Function
julia
recording_intervals(site; rate = nothing) -> Vector{Tuple{DateTime, DateTime}}

The spans that site recorded, at rate, or at all rates if rate is nothing or :all. Runs that touch become one span.

source
Timekeepers.recording_seconds Function
julia
recording_seconds(site; rate = nothing) -> Float64

The total time that site recorded, at rate, or at all rates if rate is nothing or :all.

source
Timekeepers.overlap_intervals Function
julia
overlap_intervals(a, b; rate = nothing) -> Vector{Tuple{DateTime, DateTime}}

The spans in which the sites a and b both recorded. rate sets which recordings make a pair:

  • a number – the two sites at that sampling rate;

  • nothing (the default) – the two sites at the same rate, at a rate that they share;

  • :all – at all rates. Use it for a survey with different instruments (for example, a LEMI-424 at 1 Hz and a Metronix at 128 Hz). The processing will resample these records.

source
Timekeepers.overlap_seconds Function
julia
overlap_seconds(a, b; rate = nothing) -> Float64

The total length of overlap_intervals(a, b; rate).

source
Timekeepers.overlap_matrix Function
julia
overlap_matrix(survey; rate = nothing) -> Matrix{Float64}

The hours that each pair of sites recorded together, in the order of survey.sites. The diagonal holds the recording hours of each site.

source
Timekeepers.site_distance Function
julia
site_distance(a, b) -> Float64

The great-circle distance between two sites, in kilometres. NaN if one of the sites has no position.

source
Timekeepers.site_references Function
julia
site_references(survey, site; rate = nothing, base_km = 5.0, remote_km = 0.1,
                min_overlap_hours = 1.0, exclude = ()) -> (base, remote)

The sites that recorded together with site (a SurveySite, its name or its index) for min_overlap_hours or more, at a common rate (refer to overlap_intervals):

  • base sites: within base_km of the site;

  • remote sites: remote_km or more from the site.

The two lists are independent: with the defaults, a site 2 km away is in both. A remote site as near as 100 m is useful in a test array or a dense survey; for a remote reference that is free of local noise, select a site far away, or give a larger remote_km.

rate has the same meaning as for overlap_intervals. Base and remote sites supply their magnetic field. Thus, only sites that recorded Hx and Hy count (refer to has_magnetic). These sites are in neither list: sites beyond base_km and nearer than remote_km (when remote_km > base_km), sites without a position in their headers, and sites with only electric channels.

Each list is in order of the time recorded together, longest first. If two sites have the same time, the nearer site comes first. An entry is a NamedTuple:

  • site – the name of the site

  • distance_km – the great-circle distance from site

  • overlap_hours – the time recorded together

  • overlap_fraction – that time as a fraction of the recording of site

  • windows – the overlap spans, as (start, stop) pairs

  • excluded – tells if its name is in exclude. Excluded sites stay in the lists with this flag. Thus, a window can show them. reference_plan does not include them

source
Timekeepers.common_window Function
julia
common_window(survey, site, others; rate = nothing) -> Vector{Tuple{DateTime, DateTime}}

The spans in which site and all the sites in others (sites or names) recorded together: at rate, at one rate that they all share (nothing), or at all rates (:all). Processing can use this part of the data when it uses site with all of others at the same time. The result is empty if others is empty or if the sites never recorded together.

source
Timekeepers.reference_plan Function
julia
reference_plan(survey; rate = nothing, base_km = 5.0, remote_km = 0.1,
               min_overlap_hours = 1.0, exclude = Dict()) -> Vector{NamedTuple}

All the sites of survey with their base and remote sites from site_references. The result has one NamedTuple for each site: (site, base, base_hours, remote, remote_hours, common).

  • base and remote are vectors of site names, with the longest overlap first.

  • base_hours and remote_hours are the hours that each of these sites recorded with the site, in the same order.

  • common is the common_window of the site with all of them.

exclude[site] is a set of names that the lists of that site must not include.

See also write_reference_plan, read_reference_plan.

source
Timekeepers.write_reference_plan Function
julia
write_reference_plan(path, survey; kwargs...) -> String
write_reference_plan(path, plan) -> String

Write a reference_plan as a plain text table. The table has one header line and one row for each site. The columns are aligned:

julia
site      base                       overlap (h)          remote              overlap (h)
site002   site004, site006           11.77, 9.00          site099, site100    11.77, 11.77
site099   -                          -                    site100, site004    22.98, 12.00

The base and remote sites are in order of longest overlap first. The overlaps below overlap (h) are in the same order. A - shows an empty list. The keywords are the same as for reference_plan. Use read_reference_plan to read the table. The function returns path.

source
Timekeepers.read_reference_plan Function
julia
read_reference_plan(path) -> Vector{NamedTuple}

Read a table that write_reference_plan (or Export in TKDash) wrote. The result has one (site, base, base_hours, remote, remote_hours) for each row. The sites are vectors of names, and the hours are vectors of numbers, in the order of the table.

source
Timekeepers.TKDash Type
julia
TKDash

The survey dashboard. It holds:

  • a Survey;

  • the site in focus (0 for the overview);

  • the rate and the limits on the screen;

  • the base and remote sites that you dropped by hand;

  • the GLMakie figure.

To make one, use TKDash(root) and display it. Or use run_tkdash, which opens it and waits.

After you close the window, your choices stay. reference_plan(dash) returns the plan as the window shows it, and write_reference_plan(path, dash) writes it.

source
Timekeepers.run_tkdash Function
julia
run_tkdash(root; kwargs...) -> TKDash
run_tkdash(survey::Survey; kwargs...) -> TKDash
run_tkdash(; kwargs...) -> TKDash

Open the survey dashboard and wait until you close its window. Then return the TKDash with the choices that you made in it. The function scans root with scan_survey. It gives the keywords to TKDash. If you do not give root, a folder dialog asks for it.

julia
dash = run_tkdash("data/survey")       # pick sites, drop references, close the window
write_reference_plan("plan.txt", dash)
source

Calibration ​

Timekeepers.SensorCalibration Type
julia
SensorCalibration

The calibration of one induction coil. A Metronix table gives the response normalized by frequency: magnitude in V/(nT·Hz) and phase in degrees. Thus, the response of the coil is magnitude · f · exp(i·phase) in V/nT. The file has one table with the chopper on and one with the chopper off.

Fields

  • sensor::String – the sensor type, e.g. "MFS07e".

  • serial::Int – the serial number.

  • path::String – the calibration file.

  • on, off – (f, magnitude, phase) tables in increasing frequency, empty if the file has no table for that state.

See also read_calibration, sensor_response.

source
Timekeepers.read_calibration Function
julia
read_calibration(path; sensor = "", serial = 0) -> SensorCalibration

Read a Metronix calibration text file. It has a Chopper On and a Chopper Off block, each with rows of frequency, magnitude and phase.

The function examines each table. A coil has a smooth response. Thus, a jump by a factor of more than 5 in the normalized magnitude from one row to the next is a part of a table that does not belong to the coil (some files hold rows of a different model at their low end). The function keeps the longest smooth part of each table and gives a warning about the rows that it removed.

source
Timekeepers.find_calibration Function
julia
find_calibration(dirs, sensor, serial) -> Union{String, Nothing}

The calibration file of a coil in dirs. The name of a Metronix file is the sensor type and the serial number in three digits, e.g. MFS07e160.txt. If no file has that name, a file with the same serial number and a sensor family that agrees (the type of an unknown sensor agrees with each family) is the file, but only if it is the only one.

source
Timekeepers.read_dipoles Function
julia
read_dipoles(path) -> Dict{String, NTuple{4, Float64}}

Read a dipole table, d/dipoles.dat of a survey. The first line is the header. Each other line holds a site name and the distances in metres from the centre of the site to the N, S, E and W electrodes:

julia
site       N      S      E      W
site002   24.5   48.5   50.9   51.5

Ex is N + S long and Ey is E + W. - is a distance that is not known. The result maps a site name to (N, S, E, W), with NaN for -.

source
Timekeepers.sensor_response Function
julia
sensor_response(cal::SensorCalibration, f; chopper = true) -> ComplexF64

The response of the coil at the frequency f in Hz, in V/nT. The function uses the table for the chopper state. If that table does not cover f but the other table does, the function uses the other table. Out of both tables, the normalized response keeps its value at the nearest end. That is the response of a coil far below its corner frequency, where its output is proportional to f.

source

Declination ​

Timekeepers.igrf_field Function
julia
igrf_field(latitude, longitude, t; altitude_km = 0.0) -> NamedTuple

The main geomagnetic field of IGRF-14 at a geodetic (WGS84) position and time t (a DateTime, a Date or a decimal year, 1900-2030). The result has the north, east and down components (nT), declination (degrees east of true north), inclination (degrees, down positive) and intensity (nT).

See also igrf_declination, survey_declination.

source
Timekeepers.igrf_declination Function
julia
igrf_declination(latitude, longitude, t; altitude_km = 0.0) -> Float64

The declination of IGRF-14 (degrees east of true north) at a geodetic position and time. Refer to igrf_field.

source
Timekeepers.survey_declination Function
julia
survey_declination(survey) -> NamedTuple

The declination that IGRF-14 gives for a survey: at the median position of its sites with a position, at the middle of its recordings. The result has declination (degrees east), latitude, longitude and time, or declination = NaN if no site has a position.

source

Sensor orientation ​

Timekeepers.read_orientations Function
julia
read_orientations(path) -> (declination, rows)

Read the orientation table of a survey, d/orientations.dat: the declination, and for each site the polarity of each channel.

julia
# comments start with #
declination 10.49
site   Ex   Ey   Hx   Hy
1      +1   +1   +1   +1
2      +1   -1   +1   +1
  • +1 is a channel wired as planned, -1 a channel wired the other way round in the field: the processing reverses it. flip_channel! changes the sign. - is +1.

  • Ex is the N-S line (red at geographic north), Ey the E-W line (red at east).

  • The x axis of a fluxgate (LEMI-424, GEOMAG) comes from its static field: declination − atan2(By, Bx); y is 90° more and z is down. Metronix coils are taken as Hx north and Hy east, and Metronix electric lines in the directions of the electrode positions of their .ats headers.

The result is the declination (NaN if the file has none; the processing then takes IGRF-14 at the position of the run) and a Dict from a site name to its row as text (Dict{Symbol, String} keyed by the channels :e1 (Ex), :e2 (Ey), :bx (Hx), :by (Hy)). Hz has no column: a vertical sensor cannot be wired the other way round. See also write_orientations, flip_channel!.

source
Timekeepers.write_orientations Function
julia
write_orientations(path, declination, rows)

Write an orientation table (see read_orientations), with the sites in the order of their names. declination can be NaN (no line).

source
Timekeepers.orientation_file Function
julia
orientation_file(site_dir) -> Union{String, Nothing}

d/orientations.dat in site_dir or in up to three directories above it.

source
Timekeepers.site_orientation Function
julia
site_orientation(run; site_dir) -> NamedTuple

The orientation of the channels of run: (e1, e2, bx, by, signs, notes, file). The azimuths are in degrees east of true north: for a fluxgate the x axis from its static field and the declination (NaN keeps the header or nominal direction). signs[c] is the polarity of each channel from the orientation table of the survey (refer to read_orientations), notes a short note for each channel and file the table ("" if none).

source
Timekeepers.flip_channel! Function
julia
flip_channel!(path, site, comp) -> Dict{Symbol, String}

Change the polarity of one channel (:e1 Ex, :e2 Ey, :bx Hx or :by Hy) of site in the orientation table path (made if it is not there), +1 ↔ -1, and return the new row.

source

Transfer functions ​

Timekeepers.TransferFunction Type
julia
TransferFunction

The impedance and the tipper of one site, with their errors, as estimate_tf returns them.

The fields are in geographic coordinates: x is north and y is east. Z is in (mV/km)/nT and has the time dependence exp(+iωt). Thus, the phase of Zxy over a uniform half-space is +45°.

Fields

  • site::String – the site.

  • mode::Symbol – :single, :base, :remote or :base_remote.

  • base::String – the site that gave the inputs Hx, Hy ("" for the site itself).

  • remote::Vector{String} – the reference sites.

  • latitude, longitude, elevation – the position of the site.

  • periods::Vector{Float64} – seconds, increasing.

  • Z::Array{ComplexF64, 3} – 2 × 2 × nperiods. NaN where there is no estimate.

  • Z_var::Array{Float64, 3} – the variance E|δZ|² of each element.

  • T::Matrix{ComplexF64} – 2 × nperiods: Tzx, Tzy.

  • T_var::Matrix{Float64} – the variance of each tipper element.

  • coherence::Matrix{Float64} – 3 × nperiods: the multiple coherence of Ex, Ey and Hz with the inputs, with the robust weights.

  • ref_coherence::Matrix{Float64} – 2 × nperiods: the coherence of the inputs Hx, Hy with the reference (NaN without a reference).

  • n_windows::Vector{Int} – the windows in each band.

  • n_obs::Vector{Int} – the Fourier coefficients in each band.

  • sample_rate::Vector{Float64} – the rate that gave each period.

  • metadata::Dict{Symbol, Any} – the options, the sensors and the data spans.

See also apparent_resistivity, impedance_phase, write_modem.

source
Timekeepers.estimate_tf Function
julia
estimate_tf(site; base = nothing, remote = nothing, rate = :all, kwargs...) -> TransferFunction
estimate_tf(survey::Survey, site; base = nothing, remote = nothing, kwargs...) -> TransferFunction

Estimate the impedance and the tipper of site. A site is a site directory (Metronix, LEMI-424 or GEOMAG), a data file, a SurveySite, a TimekeeperRun or a vector of runs. With a Survey, give the sites by their names; :auto then selects the base or the remote site with the longest overlap (refer to site_references).

The set-up:

  • no base, no remote – single site: the inputs are Hx, Hy of the site;

  • base – the inputs are Hx, Hy of the base site. Use it for a site without magnetic channels, or with poor ones;

  • remote – one site or a vector of sites. Their Hx, Hy are the reference. In a vector of runs, the runs of each site name are one remote site. With more than one remote site, the reference has all their channels (a two-stage least squares fit);

  • base and remote together.

The method is in the header of Processing.jl. The keywords:

  • rate – the sampling rate in Hz, or :all (each rate that all the sites recorded, then the best estimate at each period).

  • window = 256 – the section length: samples in each data section (Chave & Thomson 2004), at each decimation level.

  • overlap = 0.5 – the section overlap.

  • bands_per_decade = 8 – the bands at the periods 10^(j/8) s.

  • prewhiten = 3 – the terms of the least-squares AR pre-whitening filter (0: none).

  • min_windows = 8 – the data sections that a level needs. Fewer gives longer periods with larger errors.

  • min_harmonic = 4 – the lowest frequency index of a section at the last level. A lower one gives longer periods, closer to the trend of each section.

  • nyquist_fraction = :auto – the highest frequency as a fraction of the Nyquist frequency of the record (0.5 is a quarter of the rate). :auto finds where the anti-alias filter of the recorder ends the signal: the lowest frequency above half the Nyquist frequency where the coherence of E with H falls below 0.5. The top is 5 % below it, from 0.5 to 0.9. A number sets it. A higher one gives shorter periods, closer to the filter.

  • max_levels = 12 – the most decimation levels.

  • min_period = 0, max_period = Inf – the periods to estimate.

  • method = :ct2004 – :ct2004 (two stages with bounded influence, after Chave & Thomson 2004) or :eb1986 (one robust M-estimate with Huber weights, after Egbert & Booker 1986). Step 5 of the header of Processing.jl compares them.

  • huber = 1.5 – the parameter a of the Huber weights (Chave & Thomson 2004, eq. 7).

  • leverage = true – the leverage weights of the bounded influence estimator (Chave & Thomson 2004, eq. 26; :ct2004 only).

  • jackknife_groups = 50 – the blocks of data sections that the jackknife deletes one at a time.

  • calibration – a directory (or a vector of directories) with the coil calibration files. If you do not give it, the function looks in the site directory and up to three directories above it, in s (the calibration directory of a survey) and in directories with "cal" in their names.

  • dipole – Dict(:e1 => L1, :e2 => L2) in metres. If you do not give it, the lengths come from the row of the site in d/dipoles.dat of the survey (refer to read_dipoles), then from the electrode positions in the .ats headers. Without either, each electrode is 50 m from the centre.

  • azimuths – Dict(component => degrees east of north) to replace the directions of the sensors.

  • masks – Dict(site name => [(start, stop), …]): intervals to leave out.

  • span – (start, stop): use only this time span.

  • progress – a function that receives a line of text at each step. The function can call it from more than one thread.

source
Timekeepers.default_references Function
julia
default_references(survey, site; base_km = 5.0, remote_km = 0.1) -> (base, remote)

The set-up that TKProc selects when it loads site: names or nothing.

  • A site with Hx and Hy: no base site, and the remote site (none if no site is far enough).

  • A site without them: the base site with the longest overlap, and the remote site.

The remote site is the farthest of the sites that recorded with site at least 90 % as long as the best one: its local noise is the least like that of the site.

source
Timekeepers.apparent_resistivity Function
julia
apparent_resistivity(tf::TransferFunction) -> (rho, rho_err)

The apparent resistivity ρ = 0.2 T |Z|² of each element in Ω·m, and its error, both 2 × 2 × nperiods. The error comes from the standard deviation of Z along one axis, σ = √(var/2): δρ = 2ρσ/|Z|.

source
Timekeepers.impedance_phase Function
julia
impedance_phase(tf::TransferFunction) -> (phi, phi_err)

The phase of each element of Z in degrees, in (-180°, 180°], and its error δφ = σ/|Z| in degrees, both 2 × 2 × nperiods. Refer to apparent_resistivity for σ.

source
Timekeepers.rotate_tf Function
julia
rotate_tf(tf::TransferFunction, angle) -> TransferFunction

The transfer function in axes that are turned clockwise by angle degrees (east of north): Z' = R Z Rᵀ and T' = T Rᵀ. The variances turn with the same rule, for independent elements.

source

Transfer function I/O ​

Timekeepers.write_edi Function
julia
write_edi(path, tf::TransferFunction) -> String

Write tf to a SEG EDI file. The frequencies are in decreasing order. The file holds Z (ZXX … ZYY: real, imaginary, variance), the tipper (TX, TY: real, imaginary, variance) if the site has Hz, and the coherences of Ex, Ey and Hz with the inputs. The >INFO block lists the set-up (base and remote sites), the sensors and the options of the estimate. A missing value is 1.0E+32. The function returns path.

source
Timekeepers.read_edi Function
julia
read_edi(path) -> TransferFunction

Read the impedance and the tipper of a SEG EDI file (the blocks that write_edi writes, and the same blocks from other programs). The values equal to the EMPTY value of the file are NaN.

source
Timekeepers.write_modem Function
julia
write_modem(path, tfs; components = :full, units = :field, sign = :plus, origin = nothing,
            elevation = false, description = "") -> String

Write TransferFunctions (one or a vector) to a ModEM data file.

  • components – :full (the four elements of Z and the tipper), :offdiagonal (only Zxy, Zyx and the tipper) or :impedance (no tipper).

  • units – :field writes [mV/km]/[nT]. :ohm writes Z in Ω ([V/m]/[A/m]).

  • sign – :plus writes exp(+i\omega t), the convention of the estimate. :minus writes the complex conjugate with exp(-i\omega t).

  • origin – (lat, lon) of the grid origin. The default is the centre of the sites.

  • elevation – true writes z = -elevation. With false (the default), z = 0, and the mesh puts the sites on its surface.

The error of an element is its estimated standard deviation along one axis, √(var/2), with no floor: set the error floors of an inversion in its own set-up. The file leaves out the elements without an estimate or with an error that is not finite. The function returns path.

source
Timekeepers.read_modem Function
julia
read_modem(path) -> Vector{TransferFunction}

Read the Full_Impedance, Off_Diagonal_Impedance and Full_Vertical_Components blocks of a ModEM data file. The result has one TransferFunction for each site, in the order of the file, in (mV/km)/nT with the convention exp(+iωt). The variance of each element is 2·error². The fields that a data file does not hold (coherences, counts) are NaN or zero.

source
Timekeepers.plot_tf Function
julia
plot_tf(tf::TransferFunction; full_tensor = false, errors = true, wrap_phase = false,
        size = (1300, 800)) -> Figure

A figure of tf: the apparent resistivity above the phase on the left, the tipper Tzx above Tzy on the right, against the period, with the error bars. Zxy is red and Zyx blue. With full_tensor, Zxx (green) and Zyy (lilac) are there too. With errors = false, no error bars. With wrap_phase, the phases are wrapped to 0°-90° (modulo 180°). The real tipper is red and the imaginary tipper is blue. Save it with save("site.png", plot_tf(tf)).

source
Timekeepers.export_tf Function
julia
export_tf(dir, tf::TransferFunction; full_tensor = false, errors = true, wrap_phase = false,
          rotate = 0) -> Vector{String}

Write the estimate of one site into dir as <name>.edi (write_edi) and <name>.png (plot_tf, with or without Zxx and Zyy and the error bars). <name> is tf_filename: the site, its base and remote sites and the values of the options. The INFO block of the EDI file holds each sensor with its calibration file or its dipole length and where the length came from. The errors are the estimated errors, with no floor. With rotate (degrees east of north), the plot shows the tensor turned by that angle; the EDI file stays in geographic axes. The function returns the paths.

source
Timekeepers.tf_filename Function
julia
tf_filename(tf::TransferFunction) -> String

The name (without extension) of the export of tf: the site, the base site, the remote sites, the rate and the values of the options, joined by -:

julia
site-base-remote-rate-window-overlap-bands-prewhiten-top-harmonic-windows-levels-method-huber-leverage-jackknife

e.g. site002-site002-site099-all-256-0.5-8-3-auto-4-8-12-ct2004-1.5-1-50. The base is the site itself when the site gives its own Hx, Hy; the remote is none without a remote site, and remote sites join with +. rate is all or the rate that was processed (128Hz, 16s); top is auto or the fraction of Nyquist; leverage is 1 or 0. The name holds all you need to give estimate_tf to make the estimate again. A period range other than all periods adds minperiod-maxperiod at the end.

source
Timekeepers.write_tf_report Function
julia
write_tf_report(path, tf::TransferFunction; files = String[]) -> String

Write a Markdown record of how tf was made: the site and its set-up (base and remote sites), the data of each rate (hours, spans, start and end, the decimation levels), each sensor with its calibration file or its dipole length and where the length came from (d/dipoles.dat, the header or the default), the calibration files and dipole tables that the estimate used, all the processing options, the files of the export and a table of the estimates (apparent resistivity and phase with their errors, the tipper, the coherences and the windows at each period). A PNG among files shows in the record. The function returns path.

source

Site processing ​

Timekeepers.parse_span Function
julia
parse_span(s) -> Union{Tuple{DateTime, DateTime}, Nothing}

A time span from the two date-times in s, as "2026-10-20 14:00 to 2026-10-21 07:20" (also with T, with seconds, or with -, – or / between them). nothing for "all", an empty text, or a text without two date-times in order.

source
Timekeepers.read_procset Function
julia
read_procset(path) -> Dict{Symbol, String}
write_procset(path, settings; site = "")

The processing set-up of one site, as TKProc saves it (Save set-up) and reads it when you select the site. One key value line each; # starts a comment:

julia
base        -
remote      10
section_length  256
method          eb1986
bands_per_decade 6
min_period  8
max_period  3000
span        2026-10-20 14:00 to 2026-10-21 07:20

The keys: base, remote, min_distance, rate, section_length, section_overlap, method, bands_per_decade, ar_terms, top_nyquist, min_frequency_index, min_sections, decimation_levels, huber_a, jackknife_blocks, leverage_weights, min_period, max_period, span and rotate. Their names are the terms of Chave & Thomson (2004). A key that the file leaves out keeps the default of a new TKProc window, and so does every key of a site without the file. write_procset writes the keys of settings in that order.

source
Timekeepers.procset_file Function
julia
procset_file(site_dir) -> String

The set-up file of a site: procset.dat in its directory. For the card folder of a logger (DATA0247) or a corrected copy, it is in the folder of the site above it. The file need not be there.

source
Timekeepers.procset_options Function
julia
procset_options(settings) -> NamedTuple

The keywords of estimate_tf from the text values of procset.dat (read_procset). A key that settings leaves out takes the default of a new TKProc window. base, remote, min_distance and rotate are not keywords of the estimate: refer to procset_references.

source
Timekeepers.procset_references Function
julia
procset_references(survey, site, settings) -> (base, remote)

The base site and the remote sites of procset.dat: - is none (for the base, the site itself), all is remote_sites at min_distance metres (100 if the file has none), and other values are site names (remote sites joined with + or ,).

source
Timekeepers.remote_sites Function
julia
remote_sites(survey, site; min_distance = 100.0, min_overlap = 0.75) -> Vector{String}

The sites with Hx and Hy at min_distance metres or farther that recorded with site for min_overlap (a fraction) or more of its recording, nearest first: "all ≥ …" of the Remote menu of TKProc, and remote all in procset.dat. An estimate with several remote sites uses only the time that all of them recorded: a site that recorded a short part of the time would cut the data of the others, so it is left out (select it by itself to use it). A site without a position is never in the list.

source
Timekeepers.process_site Function
julia
process_site(survey, site; procset = procset_file(site.path), progress = nothing) -> TransferFunction

The transfer function of site (a name, an index or a SurveySite) with its set-up in procset.dat: the same estimate as Process in TKProc with that set-up. procset can also be a Dict of the values. The result keeps the values in tf.metadata[:procset] and the file in tf.metadata[:procset_file].

See also export_site, read_procset.

source
Timekeepers.export_site Function
julia
export_site(survey, tf; dir = joinpath(site.path, "proc"), full_tensor = false, errors = true,
            wrap_phase = false, rotate = nothing) -> Vector{String}

Write the estimate tf of a site of survey into dir, the proc folder of the site (made new: the files of an earlier export there are removed):

  • <site>.edi – the transfer function in geographic axes;

  • <site>_tf.png – paper_tf, turned by rotate degrees (the rotate of its procset.dat if nothing);

  • <site>_qc.png – paper_qc;

  • procnotes.md – how the estimate was made (write_tf_report), with the set-up of procset.dat and the steps that make it again in TKProc.

The function returns the paths.

source
Timekeepers.paper_tf Function
julia
paper_tf(tf; full_tensor = false, errors = true, wrap_phase = false, rotate = 0.0,
         size = (1100, 720)) -> Figure

The figure of plot_tf for a paper: thick axes without a grid, panel labels (a)-(d), the set-up in the title. rotate turns tf first (degrees). Save it as a PNG with save(path, fig; px_per_unit = 4).

source
Timekeepers.paper_qc Function
julia
paper_qc(tf; size = (1100, 820)) -> Figure

The check of an estimate for a paper: (a) the relative error of Zxy and Zyx, (b) the coherence of Ex, Ey (and Hz) with the inputs and of the inputs with the reference, (c) the windows in each band, (d) the data that each rate used.

source

Transfer function window ​

Timekeepers.TKProc Type
julia
TKProc

The transfer function window. It holds:

  • the loaded Survey;

  • the selected site;

  • the TransferFunction of each site that you processed, in results;

  • the GLMakie figure.

To make one, use TKProc(survey_dir) and display it, or use run_tkproc. After you close the window, proc.results holds the estimates.

source
Timekeepers.run_tkproc Function
julia
run_tkproc(survey_dir, site = nothing; kwargs...) -> TKProc
run_tkproc(survey::Survey, site; kwargs...) -> TKProc
run_tkproc(; kwargs...) -> TKProc

Open the transfer function window and wait until you close it. Then return the TKProc with the estimates that you made in it (proc.results). With survey_dir, the window opens on that survey with site (or its first site) and its base and remote sites selected (refer to TKProc). Select the other sites in the Site menu. Without it, the window opens empty: use Survey…. Start Julia with more than one thread (julia -t auto). Thus, the window stays live while a site is processed.

julia
proc = run_tkproc("data/survey", "site004")   # process, export, close the window
export_tf("out", proc.results["site004"])
source

Index ​