Skip to content

Commit

Permalink
Merge branch 'master' into dependabot/github_actions/codecov/codecov-…
Browse files Browse the repository at this point in the history
…action-5
  • Loading branch information
palday authored Jan 31, 2025
2 parents cbd95fb + 19b6ea6 commit 4832f26
Show file tree
Hide file tree
Showing 14 changed files with 124 additions and 55 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
*.jl.*.cov
*.jl.mem
Manifest.toml
Manifest-v*.toml
docs/build/
docs/site/
.DS_Store
Expand Down
2 changes: 1 addition & 1 deletion OndaEDFSchemas.jl/Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "OndaEDFSchemas"
uuid = "9c87d999-769b-4741-85b2-6f554d09e731"
authors = ["Beacon Biosignals, Inc."]
version = "0.2.2"
version = "0.2.3"

[deps]
Legolas = "741b9549-f6ed-4911-9fbf-4a1c0c97f0cd"
Expand Down
47 changes: 42 additions & 5 deletions OndaEDFSchemas.jl/src/OndaEDFSchemas.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ using Onda: LPCM_SAMPLE_TYPE_UNION, onda_sample_type_from_julia_type,
AnnotationV1
using UUIDs

export PlanV1, PlanV2, FilePlanV1, FilePlanV2, EDFAnnotationV1
export PlanV1, PlanV2, PlanV3, FilePlanV1, FilePlanV2, FilePlanV3, EDFAnnotationV1

@schema "ondaedf.plan" Plan

Expand Down Expand Up @@ -65,7 +65,33 @@ end
error::Union{Nothing,String} = coalesce(error, nothing)
end


@version PlanV3 begin
# EDF.SignalHeader fields
label::String
transducer_type::String
physical_dimension::String
physical_minimum::Float32
physical_maximum::Float32
digital_minimum::Float32
digital_maximum::Float32
prefilter::String
samples_per_record::Int32
# EDF.FileHeader field
seconds_per_record::Float64
# Onda.SignalV2 fields (channels -> channel), may be missing
recording::Union{UUID,Missing} = lift(UUID, recording)
sensor_type::Union{Missing,AbstractString} = lift(_validate_signal_sensor_type, sensor_type)
sensor_label::Union{Missing,AbstractString} = lift(_validate_signal_sensor_label,
coalesce(sensor_label, sensor_type))
channel::Union{Missing,AbstractString} = lift(_validate_signal_channel, channel)
sample_unit::Union{Missing,AbstractString} = lift(String, sample_unit)
sample_resolution_in_unit::Union{Missing,Float64}
sample_offset_in_unit::Union{Missing,Float64}
sample_type::Union{Missing,AbstractString} = lift(onda_sample_type_from_julia_type, sample_type)
sample_rate::Union{Missing,Float64}
# errors, use `nothing` to indicate no error
error::Union{Nothing,String} = coalesce(error, nothing)
end

const PLAN_DOC_TEMPLATE = """
@version PlanV{{ VERSION }} begin
Expand All @@ -78,7 +104,7 @@ const PLAN_DOC_TEMPLATE = """
digital_minimum::Float32
digital_maximum::Float32
prefilter::String
samples_per_record::Int16
samples_per_record::{{ SAMPLES_PER_RECORD_TYPE }}
# EDF.FileHeader field
seconds_per_record::Float64
# Onda.SignalV{{ VERSION }} fields (channels -> channel), may be missing
Expand Down Expand Up @@ -108,19 +134,22 @@ conversion. The columns are the union of
function _plan_doc(v)
uniques = if v == 1
["kind::Union{Missing,AbstractString}"]
elseif v == 2
elseif v == 2 || v == 3
["sensor_type::Union{Missing,AbstractString}",
"sensor_label::Union{Missing,AbstractString}"]
else
throw(ArgumentError("Invalid version"))
end
samples_per_record_type = v in (1,2) ? "Int16" : "Int32"
unique_lines = join(map(s -> " $s", uniques), "\n")
s = replace(PLAN_DOC_TEMPLATE, "{{ VERSION }}" => v)
s = replace(s, "{{ SAMPLES_PER_RECORD_TYPE }}" => samples_per_record_type)
return replace(s, "{{ SAMPLES_INFO_UNIQUE_FIELDS }}" => unique_lines)
end

@doc _plan_doc(1) PlanV1
@doc _plan_doc(2) PlanV2
@doc _plan_doc(3) PlanV3

@schema "ondaedf.file-plan" FilePlan

Expand All @@ -134,6 +163,11 @@ end
onda_signal_index::Int
end

@version FilePlanV3 > PlanV3 begin
edf_signal_index::Int
onda_signal_index::Int
end

const FILE_PLAN_DOC_TEMPLATE = """
@version FilePlanV{{ VERSION }} > PlanV{{ VERSION }} begin
edf_signal_index::Int
Expand All @@ -158,8 +192,11 @@ end

@doc _file_plan_doc(1) FilePlanV1
@doc _file_plan_doc(2) FilePlanV2
@doc _file_plan_doc(3) FilePlanV3

const OndaEDFSchemaVersions = Union{PlanV1SchemaVersion,PlanV2SchemaVersion,FilePlanV1SchemaVersion,FilePlanV2SchemaVersion}
const OndaEDFSchemaVersions = Union{PlanV1SchemaVersion,FilePlanV1SchemaVersion,
PlanV2SchemaVersion,FilePlanV2SchemaVersion,
PlanV3SchemaVersion,FilePlanV3SchemaVersion}
Legolas.accepted_field_type(::OndaEDFSchemaVersions, ::Type{String}) = AbstractString
# we need this because Arrow write can introduce a Missing for the error column
# (I think because of how missing/nothing sentinels are handled?)
Expand Down
22 changes: 17 additions & 5 deletions OndaEDFSchemas.jl/test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,20 @@ function mock_plan(; v, rng=GLOBAL_RNG)
ingested = rand(rng, Bool)
specific_kwargs = if v == 1
(; kind=ingested ? "eeg" : missing)
elseif v == 2
elseif v in (2, 3)
(; sensor_type=ingested ? "eeg" : missing,
sensor_label=ingested ? "eeg" : missing)
else
error("Invalid version")
end
errored = !ingested && rand(rng, Bool)
PlanVersion = v == 1 ? PlanV1 : PlanV2
PlanVersion = if v == 1
PlanV1
elseif v == 2
PlanV2
else
PlanV3
end
return PlanVersion(; label="EEG CZ-M1",
transducer_type="Ag-Cl electrode",
physical_dimension="uV",
Expand Down Expand Up @@ -55,15 +61,21 @@ end

function mock_file_plan(; v, rng=GLOBAL_RNG)
plan = mock_plan(; v, rng)
PlanVersion = v == 1 ? FilePlanV1 : FilePlanV2
PlanVersion = if v == 1
FilePlanV1
elseif v == 2
FilePlanV2
else
FilePlanV3
end
return PlanVersion(Tables.rowmerge(plan;
edf_signal_index=rand(rng, Int),
onda_signal_index=rand(rng, Int)))
end

@testset "Schema version $v" for v in (1, 2)
@testset "Schema version $v" for v in (1, 2, 3)
SamplesInfo = v == 1 ? Onda.SamplesInfoV1 : SamplesInfoV2

@testset "ondaedf.plan@$v" begin
rng = StableRNG(10)
plans = mock_plan(30; v, rng)
Expand Down
10 changes: 6 additions & 4 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "OndaEDF"
uuid = "e3ed2cd1-99bf-415e-bb8f-38f4b42a544e"
authors = ["Beacon Biosignals, Inc."]
version = "0.12.4"
version = "0.13.0"

[deps]
Compat = "34da2185-b29b-5c13-b0c7-acf172513d20"
Expand All @@ -18,12 +18,13 @@ UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"

[compat]
Compat = "3.32, 4"
EDF = "0.7"
EDF = "0.8"
FilePathsBase = "0.9"
Legolas = "0.5"
Onda = "0.15"
OndaEDFSchemas = "0.2.1"
OndaEDFSchemas = "0.2.3"
PrettyTables = "1.3, 2"
SparseArrays = "1"
StableRNGs = "1"
StatsBase = "0.33, 0.34"
Tables = "1.4"
Expand All @@ -33,9 +34,10 @@ julia = "1.6"
[extras]
FilePathsBase = "48062228-2e41-5def-b9a4-89aafe57970f"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
StableRNGs = "860ef19b-820b-49d6-a774-d7a799459cd3"
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["FilePathsBase", "Test", "Random", "StableRNGs", "Statistics"]
test = ["FilePathsBase", "Test", "Random", "SparseArrays", "StableRNGs", "Statistics"]
2 changes: 1 addition & 1 deletion docs/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"

[compat]
Documenter = "0.26"
Documenter = "1"
16 changes: 15 additions & 1 deletion docs/src/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ CurrentModule = OndaEDF
OndaEDF.jl prefers "self-service" import over "automagic", and provides
functionality to extract
[`Onda.Samples`](https://beacon-biosignals.github.io/Onda.jl/stable/#Samples-1)
and [`EDFAnnotationV1`](@ref)s (which extend
and [`EDFAnnotationV1`](@ref)s (which extend
[`Onda.AnnotationV1`](https://beacon-biosignals.github.io/Onda.jl/stable/#Onda.AnnotationV1)s)
from an `EDF.File`. These can be written to disk (with
[`Onda.store`](https://beacon-biosignals.github.io/Onda.jl/stable/#Onda.store) /
Expand All @@ -33,9 +33,17 @@ EDFAnnotationV1

### Import plan table schemas

!!! note "Plan version is dependent on EDF.jl version"
The utilized plan version is dependent on the EDF.jl version.
For EDF.jl 0.8+, V3 are used, while V2 is used for for EDF.jl 0.7.
The change from V2 to V3 reflects the change from [`Int16` to `Int32`
in EDF.jl's `samples_per_record`](https://github.com/beacon-biosignals/EDF.jl/releases/tag/v0.8.0).

```@docs
PlanV2
PlanV3
FilePlanV2
FilePlanV3
write_plan
```

Expand Down Expand Up @@ -63,6 +71,12 @@ OndaEDF.promote_encodings
onda_to_edf
```

#### Internal export utilities

```@docs
OndaEDF.reencode_samples
```

## Deprecations

To support deserializing plan tables generated with old versions of OndaEDF +
Expand Down
10 changes: 5 additions & 5 deletions docs/src/convert-to-onda.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ To break this label specification down:
- `["emg", "chin"]`: indicates that this specification is for `sensor_type` of `"emg"`, for which `"chin"` is accepted as a non-canonical alternative.
- `["chin" => ["emg"]]` specifies that there's _one_ possible channel for this sensor type, whose canonical label (used in the output) is `"chin"`, but for which an alternative of `"emg"` is accepted.

This essentially tricks OndaEDF into treating `CHIN EMG` as sensor type of "`CHIN` (alternative for sensor type of `EMG`), and channel label "`EMG` (alternative way to specify `CHIN`)". For more details on how this matching is carried out, see [`plan_edf_to_onda_samples`](@ref) and [`match_labels`](@ref).
This essentially tricks OndaEDF into treating `CHIN EMG` as sensor type of "`CHIN` (alternative for sensor type of `EMG`), and channel label "`EMG` (alternative way to specify `CHIN`)". For more details on how this matching is carried out, see [`plan_edf_to_onda_samples`](@ref) and the internal [`OndaEDF.match_edf_label`](@ref).

#### Preprocess signal headers

Expand All @@ -117,8 +117,8 @@ edf = EDF.File(my_edf_file_path)

function corrected_header(signal::EDF.Signal)
header = signal.header
return Tables.rowmerge(header;
label=header.transducer_type,
return Tables.rowmerge(header;
label=header.transducer_type,
transducer_type=header.label)
end

Expand Down Expand Up @@ -200,7 +200,7 @@ If any errors _were_ encountered, you may need to iterate further.

The final step is to store both the `Onda.Samples` and the executed plan in some persistent storage.
For storing `Onda.Samples`, see [`Onda.store`](https://beacon-biosignals.github.io/Onda.jl/stable/#Onda.store), which supports serializing LPCM-encoded samples to [any "path-like" type](https://beacon-biosignals.github.io/Onda.jl/stable/#Support-For-Generic-Path-Like-Types) (i.e., anything that provides a method for `write`).
For storing the plan, use [`OndaEDF.write_plan`](@ref) (or `Legolas.write(file_path, plan, FilePlanV2SchemaVersion())` (see the documentation for [`Legolas.write`](https://beacon-biosignals.github.io/Legolas.jl/stable/#Legolas.write) and [`FilePlanV2`](@ref).
For storing the plan, use [`OndaEDF.write_plan`](@ref) (or `Legolas.write(file_path, plan, FilePlanV3SchemaVersion())` (see the documentation for [`Legolas.write`](https://beacon-biosignals.github.io/Legolas.jl/stable/#Legolas.write) and [`FilePlanV3`](@ref OndaEDF.FilePlanV3).

## Batch conversion of many EDFs

Expand Down Expand Up @@ -262,7 +262,7 @@ function plan_all(edf_paths, files; kwargs...)
plan = DataFrame(plan)
# make sure this is the same every time this function is re-run!
recording = uuid5(NAMESPACE, string(origin_uri))
return insertcols!(plan,
return insertcols!(plan,
:origin_uri => origin_uri,
:recording => recording)
end
Expand Down
2 changes: 1 addition & 1 deletion src/OndaEDF.jl
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Write a plan table to `io_or_path` using `Legolas.write`, using the
"""
function write_plan(io_or_path, plan_table; kwargs...)
return Legolas.write(io_or_path, plan_table,
Legolas.SchemaVersion("ondaedf.file-plan", 2);
Legolas.SchemaVersion("ondaedf.file-plan", 3);
kwargs...)
end

Expand Down
2 changes: 1 addition & 1 deletion src/export_edf.jl
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ end
const DATA_RECORD_SIZE_LIMIT = 30720
const EDF_BYTE_LIMIT = 8

edf_sample_count_per_record(samples::Samples, seconds_per_record::Float64) = Int16(samples.info.sample_rate * seconds_per_record)
edf_sample_count_per_record(samples::Samples, seconds_per_record::Float64) = Int32(samples.info.sample_rate * seconds_per_record)

_rationalize(x) = rationalize(x)
_rationalize(x::Int) = x // 1
Expand Down
Loading

0 comments on commit 4832f26

Please sign in to comment.