From 892baef50cb60e30d44bcbf84a420eff57a7b0ad Mon Sep 17 00:00:00 2001 From: Alberto Barradas Date: Wed, 8 Jan 2025 15:23:20 +0100 Subject: [PATCH 01/18] Add Autoformatting with Blue style --- .JuliaFormatter.toml | 2 ++ README.md | 1 + src/XDF.jl | 47 +++++++++++++++++++++++--------------------- test/runtests.jl | 24 +++++++++++----------- 4 files changed, 41 insertions(+), 33 deletions(-) create mode 100644 .JuliaFormatter.toml diff --git a/.JuliaFormatter.toml b/.JuliaFormatter.toml new file mode 100644 index 0000000..d808d22 --- /dev/null +++ b/.JuliaFormatter.toml @@ -0,0 +1,2 @@ +# See https://domluna.github.io/JuliaFormatter.jl/stable/ for a list of options +style = "blue" diff --git a/README.md b/README.md index 5c2a98f..2550650 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ ![License](https://img.shields.io/github/license/cbrnr/XDF.jl) +[![Code Style: Blue](https://img.shields.io/badge/code%20style-blue-4495d1.svg)](https://github.com/invenia/BlueStyle) XDF.jl ====== diff --git a/src/XDF.jl b/src/XDF.jl index 53d0b6c..18ce950 100644 --- a/src/XDF.jl +++ b/src/XDF.jl @@ -7,22 +7,24 @@ export read_xdf using Logging: @info, @debug - -CHUNK_TYPE = Dict(1=>"FileHeader", - 2=>"StreamHeader", - 3=>"Samples", - 4=>"ClockOffset", - 5=>"Boundary", - 6=>"StreamFooter") - -DATA_TYPE = Dict("int8"=>Int8, - "int16"=>Int16, - "int32"=>Int32, - "int64"=>Int64, - "float32"=>Float32, - "double64"=>Float64, - "string"=>String) - +CHUNK_TYPE = Dict( + 1 => "FileHeader", + 2 => "StreamHeader", + 3 => "Samples", + 4 => "ClockOffset", + 5 => "Boundary", + 6 => "StreamFooter", +) + +DATA_TYPE = Dict( + "int8" => Int8, + "int16" => Int16, + "int32" => Int32, + "int64" => Int64, + "float32" => Float32, + "double64" => Float64, + "string" => String, +) """ read_xdf(filename::AbstractString, sync::Bool=true) @@ -30,7 +32,7 @@ DATA_TYPE = Dict("int8"=>Int8, Read XDF file and optionally sync streams (default true). """ function read_xdf(filename::AbstractString, sync::Bool=true) - streams = Dict{Int, Any}() + streams = Dict{Int,Any}() counter = Dict(zip(keys(CHUNK_TYPE), zeros(Int, length(CHUNK_TYPE)))) # count chunks open(filename) do io @@ -123,9 +125,13 @@ function read_xdf(filename::AbstractString, sync::Bool=true) streams[id]["time"][index[id]] = previous + delta end if streams[id]["dtype"] === String - streams[id]["data"][index[id], :] .= String(read(io, read_varlen_int(io))) + streams[id]["data"][index[id], :] .= String( + read(io, read_varlen_int(io)) + ) else - streams[id]["data"][index[id], :] = reinterpret(dtype, read(io, sizeof(dtype) * nchannels)) + streams[id]["data"][index[id], :] = reinterpret( + dtype, read(io, sizeof(dtype) * nchannels) + ) end index[id] += 1 end @@ -152,7 +158,6 @@ function read_xdf(filename::AbstractString, sync::Bool=true) return streams end - "Read variable-length integer." function read_varlen_int(io::IO) nbytes = read(io, Int8) @@ -165,7 +170,6 @@ function read_varlen_int(io::IO) end end - "Find XML tag and return its content (optionally converted to specified type)." function findtag(xml::AbstractString, tag::AbstractString, type=String::DataType) m = match(Regex("<$tag>(.*)"), xml) @@ -173,7 +177,6 @@ function findtag(xml::AbstractString, tag::AbstractString, type=String::DataType return isnothing(content) || type == String ? content : parse(type, content) end - "Synchronize clock values by their given offsets." function sync_clock(time::Array{Float64,1}, offsets::Array{Float64,2}) x = hcat(ones(size(offsets, 1), 1), offsets[:, 1]) diff --git a/test/runtests.jl b/test/runtests.jl index b3b3b32..8b10b00 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -14,15 +14,17 @@ using XDF, Downloads, Test @test endswith(streams[0]["header"], "") @test startswith(streams[0]["footer"], "") @test endswith(streams[0]["footer"], "") - @test streams[0]["data"] == [192 255 238 - 12 22 32 - 13 23 33 - 14 24 34 - 15 25 35 - 12 22 32 - 13 23 33 - 14 24 34 - 15 25 35] + @test streams[0]["data"] == [ + 192 255 238 + 12 22 32 + 13 23 33 + 14 24 34 + 15 25 35 + 12 22 32 + 13 23 33 + 14 24 34 + 15 25 35 + ] @test 46202862 in keys(streams) @test streams[46202862]["name"] == "SendDataString" @@ -37,8 +39,8 @@ using XDF, Downloads, Test @test size(streams[46202862]["data"]) == (9, 1) @test startswith(streams[46202862]["data"][1, 1], "") - @test streams[46202862]["data"][2:end, 1] == ["Hello", "World", "from", "LSL", "Hello", - "World", "from", "LSL"] + @test streams[46202862]["data"][2:end, 1] == + ["Hello", "World", "from", "LSL", "Hello", "World", "from", "LSL"] end @testset "XDF file with clock resets" begin From c1f0591f51f38fc63243657c91d17aca13a477c8 Mon Sep 17 00:00:00 2001 From: Alberto Barradas Date: Wed, 8 Jan 2025 15:31:12 +0100 Subject: [PATCH 02/18] Add CI workflow --- .github/workflows/CI.yml | 41 ++++++++++++++++++++++++++++++ .github/workflows/CompatHelper.yml | 16 ++++++++++++ .github/workflows/TagBot.yml | 16 ++++++++++++ .gitignore | 1 + README.md | 2 +- 5 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/CI.yml create mode 100644 .github/workflows/CompatHelper.yml diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml new file mode 100644 index 0000000..872de76 --- /dev/null +++ b/.github/workflows/CI.yml @@ -0,0 +1,41 @@ +name: CI +on: + push: + branches: + - main + tags: ['*'] + pull_request: + workflow_dispatch: +concurrency: + # Skip intermediate builds: always. + # Cancel intermediate builds: only if it is a pull request build. + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }} +jobs: + test: + name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} + runs-on: ${{ matrix.os }} + timeout-minutes: 60 + permissions: # needed to allow julia-actions/cache to proactively delete old caches that it has created + actions: write + contents: read + strategy: + fail-fast: false + matrix: + version: + - '1.11' + - '1.6' + - 'pre' + os: + - ubuntu-latest + arch: + - x64 + steps: + - uses: actions/checkout@v4 + - uses: julia-actions/setup-julia@v2 + with: + version: ${{ matrix.version }} + arch: ${{ matrix.arch }} + - uses: julia-actions/cache@v2 + - uses: julia-actions/julia-buildpkg@v1 + - uses: julia-actions/julia-runtest@v1 diff --git a/.github/workflows/CompatHelper.yml b/.github/workflows/CompatHelper.yml new file mode 100644 index 0000000..cba9134 --- /dev/null +++ b/.github/workflows/CompatHelper.yml @@ -0,0 +1,16 @@ +name: CompatHelper +on: + schedule: + - cron: 0 0 * * * + workflow_dispatch: +jobs: + CompatHelper: + runs-on: ubuntu-latest + steps: + - name: Pkg.add("CompatHelper") + run: julia -e 'using Pkg; Pkg.add("CompatHelper")' + - name: CompatHelper.main() + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + COMPATHELPER_PRIV: ${{ secrets.DOCUMENTER_KEY }} + run: julia -e 'using CompatHelper; CompatHelper.main()' diff --git a/.github/workflows/TagBot.yml b/.github/workflows/TagBot.yml index f49313b..0cd3114 100644 --- a/.github/workflows/TagBot.yml +++ b/.github/workflows/TagBot.yml @@ -4,6 +4,22 @@ on: types: - created workflow_dispatch: + inputs: + lookback: + default: "3" +permissions: + actions: read + checks: read + contents: write + deployments: read + issues: read + discussions: read + packages: read + pages: read + pull-requests: read + repository-projects: read + security-events: read + statuses: read jobs: TagBot: if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot' diff --git a/.gitignore b/.gitignore index ba39cc5..06969da 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ Manifest.toml +Dockerfile \ No newline at end of file diff --git a/README.md b/README.md index 5c2a98f..6062e57 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ ![License](https://img.shields.io/github/license/cbrnr/XDF.jl) +[![Build Status](https://github.com/cbrnr/XDF.jl/actions/workflows/CI.yml/badge.svg?branch=main)](https://github.com/cbrnr/XDF.jl/actions/workflows/CI.yml?query=branch%3Amain) XDF.jl ====== @@ -18,7 +19,6 @@ streams = read_xdf("minimal.xdf") ## Current status This package is currently in an early stage, so here's an overview of what doesn't work (yet): -- [ ] Dejittering of streams with regular sampling rates is not available yet - [ ] Loading only specific streams does not work yet If you have a feature request or found a bug, please open a new issue and let me know. I'd be especially interested in making the code more efficient, because this is basically my first Julia project. Currently, the function is passing through the file twice: the first pass reads everything except sample chunks, whereas the second pass reads samples into preallocated arrays. I'm not sure if this is ideal, the code would be much simpler if it used just a simple pass (but then sample arrays will need to be concatenated). From d442a1a052e7249babd6bd7af5cac88e8a0952b1 Mon Sep 17 00:00:00 2001 From: Alberto Barradas Date: Wed, 8 Jan 2025 15:33:53 +0100 Subject: [PATCH 03/18] Add stream dejitter function --- src/XDF.jl | 59 ++++++++++++++++++++++++++++++++++++++++++++++-- test/runtests.jl | 4 ++++ 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/src/XDF.jl b/src/XDF.jl index 53d0b6c..d352037 100644 --- a/src/XDF.jl +++ b/src/XDF.jl @@ -1,4 +1,4 @@ -# Authors: Clemens Brunner +# Authors: Clemens Brunner, Alberto Barradas # License: BSD (3-clause) module XDF @@ -182,4 +182,59 @@ function sync_clock(time::Array{Float64,1}, offsets::Array{Float64,2}) return time .+ (coefs[1] .+ coefs[2] .* time) end -end \ No newline at end of file +""" + dejitter(stream::Dict, max_time::Float64=1, max_samples::Int=500) + Calculate timestamps assuming constant intervals within each continuous segment in a stream. Chooses the minimum of the time difference and the number of samples as indicator for a new segment. + args: + stream: Dict + Stream dictionary. + max_time: Float64 + Maximum time difference between two consecutive samples (default: 1 second). + max_samples: Int + Maximum number of samples in a segment (default: 500 samples). + return: + Dict: Stream dictionary with updated timestamps. + +Example: +```julia +stream = read_xdf(Downloads.download("https://github.com/xdf-modules/example-files/blob/master/data_with_clock_resets.xdf?raw=true"))[2] +stream = dejitter(stream, 1.0, 500) # process segments with a maximum time difference of 1 second or 500 samples +stream["segments"] # list of segments +stream["nominal_srate"] # recalculated nominal sampling rate +``` +""" +function dejitter(stream::Dict; max_time::Float64=1.0, max_samples::Int=500) + srate = stream["srate"] + if srate == 0 + @warn "Attempting to dejitter marker streams or streams with zero sampling rate. Skipping." + return stream + end + nsamples = size(stream["data"], 1) + if nsamples == 0 + @warn "Attempting to dejitter empty stream. Skipping." + return stream + end + stream["nominal_srate"] = 0 # Recalculated if possible + stream["segments"] = [] + time = stream["time"] + breaks = [1; findall(diff(time) .> min.(max_time, max_samples .* (1 / srate)))] + seg_starts = breaks + seg_ends = [breaks[2:end] .- 1; nsamples] + for (start, stop) in zip(seg_starts, seg_ends) + push!(stream["segments"], (start, stop)) + idx = [start:stop;] + X = hcat(ones(length(idx)), time[idx]) + y = time[idx] + coefs = X \ y + stream["time"][idx] = coefs[1] .+ coefs[2] .* time[idx] + end + # Recalculate nominal sampling rate + counts = (seg_ends .- seg_starts) .+ 1 + durations = diff([time[seg_starts]; time[seg_ends[end]]]) + stream["nominal_srate"] = sum(counts) / sum(durations) + if stream["srate"] != 0 && abs(stream["srate"] - stream["nominal_srate"]) > 1e-1 + @warn "After dejittering: Nominal sampling rate differs from specified rate: $(stream["nominal_srate"]) vs. $(stream["srate"]) Hz" + end + return stream +end +end diff --git a/test/runtests.jl b/test/runtests.jl index b3b3b32..3100282 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -68,4 +68,8 @@ end @test startswith(streams[2]["footer"], "") @test endswith(streams[2]["footer"], "") @test size(streams[2]["data"]) == (27815, 8) + d_stream = XDF.dejitter(streams[2]) + @test d_stream["segments"][1] == (1, 12875) + @test d_stream["segments"][2] == (12876, 27815) +end end From fdeabdc76590d856e1994d5bc0814b82e2018843 Mon Sep 17 00:00:00 2001 From: Alberto Barradas Date: Wed, 8 Jan 2025 15:45:04 +0100 Subject: [PATCH 04/18] Add tests for string markers --- test/Project.toml | 1 + test/runtests.jl | 35 ++++++++++++++++++++++++++++++++++ test/testdata/test_chunk3.xdf | Bin 0 -> 1482 bytes 3 files changed, 36 insertions(+) create mode 100644 test/testdata/test_chunk3.xdf diff --git a/test/Project.toml b/test/Project.toml index a9fed57..dc7f079 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -1,3 +1,4 @@ [deps] Downloads = "f43a241f-c20a-4ad4-852c-f6b1247861c6" +SHA = "ea8e919c-243c-51af-8825-aaa63cd721ce" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" diff --git a/test/runtests.jl b/test/runtests.jl index b3b3b32..cad490f 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -69,3 +69,38 @@ end @test endswith(streams[2]["footer"], "") @test size(streams[2]["data"]) == (27815, 8) end + +@testset "strings" begin + file = "./test/testdata/test_chunk3.xdf" + @testset "strings.sha256" begin + open(file) do f + @test bytes2hex(sha256(f)) == + "c730991efa078906117aa2accdca5f0ea11c54f43c3884770eba21e5a72edb82" + end + end + @testset "strings.read_xdf" begin + using XDF: XDF + streams = XDF.read_xdf(file) + end + # @testset "strings.dejitter" begin + # using XDF: XDF + # streams = XDF.read_xdf(file) + # s1 = streams[3735928559] + # @test s1["type"] == "Marker" + # @test s1["nchannels"] == 2 + # @test s1["srate"] == 1000.0 + # @test s1["dtype"] == String + # @test size(s1["data"]) == (1, 2) + # @test s1["data"] == ["Marker 0A" "Marker 0A"] + # s2 = streams[46202862] + # @test s2["type"] == "EEG" + # @test s2["nchannels"] == 64 + # @test s2["srate"] == 1000.0 + # @test s2["dtype"] == Float64 + # @test size(s2["data"]) == (1, 64) + # @test sum(s2["data"]) == 0.0 + # sgs = [XDF.dejitter(streams[k])["segments"] for k in keys(streams)] + # @test sgs[1] == [(1, 1)] + # @test sgs[2] == [(1, 1)] + # end +end diff --git a/test/testdata/test_chunk3.xdf b/test/testdata/test_chunk3.xdf new file mode 100644 index 0000000000000000000000000000000000000000..eebfbb2c80eacfc78517b4e92fade704e311b1fd GIT binary patch literal 1482 zcma!WakFByVq~zfugJ|&C`&CW&dkrVRWj5wP_nnP$;?a3x3htY+5rV^^kEzueXu0s zH7164{|_+X)1Q}^n`#%FnwR2|Sd!>$qYviWlvEa^+PS*A+vtN?Hpv-@d3mWh@yYq6 zc_nscCN}!0A~t#XxtV#1Iq}6si6yCah6V;e*Px2Q%}C2H%1tb>OUW-y%1K2w3Myff zT$BnlBqbiG$Pj1@5*OyL>r4#q_pQB0l)rovi?UOTP&@_`!sa(4DtN89q$o2l9bpjh z9us0^a9-+m=k!-c{>^+>-HMw}KVifo#v;PRz`zWSTNXwJ1_llw1_DC|Ff~ey*brdU zVg~07)KtO92}$t^295}(6SjoKD8>R#RcT-#;V^3^2<+Ss5(nit4Dm^91WTB-%%b9w z_>#=r)Z&uF+yZc(Mi;ZmNkmhlXJBAxqmL$RQw%g9Cly)@0p(Hnu+jnOCPJkHy8WPX K23?GrZUO)<^ILEL literal 0 HcmV?d00001 From 0c70a9ed51f29ed97b7c3b9206aea5af5c3504d9 Mon Sep 17 00:00:00 2001 From: Alberto Barradas Date: Wed, 8 Jan 2025 15:54:35 +0100 Subject: [PATCH 05/18] Update version and changelog --- CHANGELOG.md | 5 +++++ Project.toml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a8ba32e..171dbfb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## [UNRELEASED] - 2025-01-08 + - Add CI workflow for versions 1.11, 1.6 (LTS), and "pre". + - Format [blue](https://github.com/JuliaDiff/BlueStyle) + - Add dejitter function + ## [0.2.0] - 2022-02-23 - Add support for string markers and string streams ([#2](https://github.com/cbrnr/XDF.jl/pull/2) by [Alberto Barradas](https://github.com/abcsds) and [Clemens Brunner](https://github.com/cbrnr)) - Make header and footer XML available in "xml" key ([#4](https://github.com/cbrnr/XDF.jl/pull/4) by [Alberto Barradas](https://github.com/abcsds)) diff --git a/Project.toml b/Project.toml index 0f300dd..10d2ef7 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "XDF" uuid = "31bc19ec-0089-417f-990e-a2b5e7515868" authors = ["Clemens Brunner "] -version = "0.2.0" +version = "1.0.0-DEV" [deps] Logging = "56ddb016-857b-54e1-b83d-db4d58db5568" From cd98e712565b63c458a20253e3365b965b768e46 Mon Sep 17 00:00:00 2001 From: Alberto Barradas Date: Wed, 8 Jan 2025 16:11:23 +0100 Subject: [PATCH 06/18] Fix tests --- test/runtests.jl | 47 +++++++++++++++++++++++------------------------ 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index 4db7fb6..3d5eb1b 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,4 +1,4 @@ -using XDF, Downloads, Test +using XDF, Downloads, Test, SHA @testset "Minimal XDF file" begin url = "https://github.com/xdf-modules/example-files/blob/master/minimal.xdf?raw=true" @@ -74,10 +74,9 @@ end @test d_stream["segments"][1] == (1, 12875) @test d_stream["segments"][2] == (12876, 27815) end -end @testset "strings" begin - file = "./test/testdata/test_chunk3.xdf" + file = "testdata/test_chunk3.xdf" @testset "strings.sha256" begin open(file) do f @test bytes2hex(sha256(f)) == @@ -88,25 +87,25 @@ end using XDF: XDF streams = XDF.read_xdf(file) end - # @testset "strings.dejitter" begin - # using XDF: XDF - # streams = XDF.read_xdf(file) - # s1 = streams[3735928559] - # @test s1["type"] == "Marker" - # @test s1["nchannels"] == 2 - # @test s1["srate"] == 1000.0 - # @test s1["dtype"] == String - # @test size(s1["data"]) == (1, 2) - # @test s1["data"] == ["Marker 0A" "Marker 0A"] - # s2 = streams[46202862] - # @test s2["type"] == "EEG" - # @test s2["nchannels"] == 64 - # @test s2["srate"] == 1000.0 - # @test s2["dtype"] == Float64 - # @test size(s2["data"]) == (1, 64) - # @test sum(s2["data"]) == 0.0 - # sgs = [XDF.dejitter(streams[k])["segments"] for k in keys(streams)] - # @test sgs[1] == [(1, 1)] - # @test sgs[2] == [(1, 1)] - # end + @testset "strings.dejitter" begin + using XDF: XDF + streams = XDF.read_xdf(file) + s1 = streams[3735928559] + @test s1["type"] == "Marker" + @test s1["nchannels"] == 2 + @test s1["srate"] == 1000.0 + @test s1["dtype"] == String + @test size(s1["data"]) == (1, 2) + @test s1["data"] == ["Marker 0A" "Marker 0A"] + s2 = streams[46202862] + @test s2["type"] == "EEG" + @test s2["nchannels"] == 64 + @test s2["srate"] == 1000.0 + @test s2["dtype"] == Float64 + @test size(s2["data"]) == (1, 64) + @test sum(s2["data"]) == 0.0 + sgs = [XDF.dejitter(streams[k])["segments"] for k in keys(streams)] + @test sgs[1] == [(1, 1)] + @test sgs[2] == [(1, 1)] + end end From 0c5cd2736f9a89b31eb35a81402b3c8681265c38 Mon Sep 17 00:00:00 2001 From: Alberto Barradas Date: Wed, 8 Jan 2025 16:14:07 +0100 Subject: [PATCH 07/18] Update tests --- test/runtests.jl | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index cad490f..dc11187 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,4 +1,4 @@ -using XDF, Downloads, Test +using XDF, Downloads, Test, SHA @testset "Minimal XDF file" begin url = "https://github.com/xdf-modules/example-files/blob/master/minimal.xdf?raw=true" @@ -82,25 +82,25 @@ end using XDF: XDF streams = XDF.read_xdf(file) end - # @testset "strings.dejitter" begin - # using XDF: XDF - # streams = XDF.read_xdf(file) - # s1 = streams[3735928559] - # @test s1["type"] == "Marker" - # @test s1["nchannels"] == 2 - # @test s1["srate"] == 1000.0 - # @test s1["dtype"] == String - # @test size(s1["data"]) == (1, 2) - # @test s1["data"] == ["Marker 0A" "Marker 0A"] - # s2 = streams[46202862] - # @test s2["type"] == "EEG" - # @test s2["nchannels"] == 64 - # @test s2["srate"] == 1000.0 - # @test s2["dtype"] == Float64 - # @test size(s2["data"]) == (1, 64) - # @test sum(s2["data"]) == 0.0 - # sgs = [XDF.dejitter(streams[k])["segments"] for k in keys(streams)] - # @test sgs[1] == [(1, 1)] - # @test sgs[2] == [(1, 1)] - # end + @testset "strings.markers" begin + using XDF: XDF + streams = XDF.read_xdf(file) + s1 = streams[3735928559] + @test s1["type"] == "Marker" + @test s1["nchannels"] == 2 + @test s1["srate"] == 1000.0 + @test s1["dtype"] == String + @test size(s1["data"]) == (1, 2) + @test s1["data"] == ["Marker 0A" "Marker 0A"] + s2 = streams[46202862] + @test s2["type"] == "EEG" + @test s2["nchannels"] == 64 + @test s2["srate"] == 1000.0 + @test s2["dtype"] == Float64 + @test size(s2["data"]) == (1, 64) + @test sum(s2["data"]) == 0.0 + # sgs = [XDF.dejitter(streams[k])["segments"] for k in keys(streams)] + # @test sgs[1] == [(1, 1)] + # @test sgs[2] == [(1, 1)] + end end From 0f58bbe83e5ca34656f20bc07fd96a595b69f5a9 Mon Sep 17 00:00:00 2001 From: Alberto Barradas Date: Wed, 8 Jan 2025 19:47:03 +0100 Subject: [PATCH 08/18] Update .JuliaFormatter.toml Co-authored-by: Clemens Brunner --- .JuliaFormatter.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/.JuliaFormatter.toml b/.JuliaFormatter.toml index d808d22..323237b 100644 --- a/.JuliaFormatter.toml +++ b/.JuliaFormatter.toml @@ -1,2 +1 @@ -# See https://domluna.github.io/JuliaFormatter.jl/stable/ for a list of options style = "blue" From 79b09f927d186d3aa5cd0dd94ba57890859d8919 Mon Sep 17 00:00:00 2001 From: Alberto Barradas Date: Wed, 8 Jan 2025 19:47:14 +0100 Subject: [PATCH 09/18] Update README.md Co-authored-by: Clemens Brunner --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2550650..7ff7671 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ ![License](https://img.shields.io/github/license/cbrnr/XDF.jl) -[![Code Style: Blue](https://img.shields.io/badge/code%20style-blue-4495d1.svg)](https://github.com/invenia/BlueStyle) +[![Code Style Blue](https://img.shields.io/badge/code%20style-blue-4495d1.svg)](https://github.com/invenia/BlueStyle) XDF.jl ====== From ca47223e2276cffe180a695703a53c7b4a891b88 Mon Sep 17 00:00:00 2001 From: Alberto Barradas Date: Wed, 8 Jan 2025 19:48:39 +0100 Subject: [PATCH 10/18] Line end in XDF.jl --- src/XDF.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XDF.jl b/src/XDF.jl index 18ce950..cfec946 100644 --- a/src/XDF.jl +++ b/src/XDF.jl @@ -185,4 +185,4 @@ function sync_clock(time::Array{Float64,1}, offsets::Array{Float64,2}) return time .+ (coefs[1] .+ coefs[2] .* time) end -end \ No newline at end of file +end From 4f16d08782a5df31435fd87e3d93fdeb6f6de7e9 Mon Sep 17 00:00:00 2001 From: Alberto Barradas Date: Wed, 8 Jan 2025 19:50:23 +0100 Subject: [PATCH 11/18] Update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a8ba32e..684f52d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## [UNRELEASED] - 2025-01-08 + - Format [blue](https://github.com/JuliaDiff/BlueStyle) + + ## [0.2.0] - 2022-02-23 - Add support for string markers and string streams ([#2](https://github.com/cbrnr/XDF.jl/pull/2) by [Alberto Barradas](https://github.com/abcsds) and [Clemens Brunner](https://github.com/cbrnr)) - Make header and footer XML available in "xml" key ([#4](https://github.com/cbrnr/XDF.jl/pull/4) by [Alberto Barradas](https://github.com/abcsds)) From b9a4ecf4f3aa358e77222e47d2ebc5a811307624 Mon Sep 17 00:00:00 2001 From: Alberto Barradas Date: Wed, 8 Jan 2025 20:21:35 +0100 Subject: [PATCH 12/18] Raise error with different bytesizes than allowed --- src/XDF.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/XDF.jl b/src/XDF.jl index 766afb9..80737ca 100644 --- a/src/XDF.jl +++ b/src/XDF.jl @@ -167,6 +167,8 @@ function read_varlen_int(io::IO) read(io, UInt32) elseif nbytes == 8 read(io, UInt64) + else + error("Invalid number of bytes for variable-length integer: $nbytes") end end From ab5c59661ecb32c0e5b77bbbd71bd6814e768ecf Mon Sep 17 00:00:00 2001 From: Alberto Barradas Date: Wed, 8 Jan 2025 19:50:23 +0100 Subject: [PATCH 13/18] Update changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a8ba32e..2782b6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## [UNRELEASED] - 2025-01-08 + +### 🌀 Changed +- Reformat sources with [blue](https://github.com/JuliaDiff/BlueStyle) style + ## [0.2.0] - 2022-02-23 - Add support for string markers and string streams ([#2](https://github.com/cbrnr/XDF.jl/pull/2) by [Alberto Barradas](https://github.com/abcsds) and [Clemens Brunner](https://github.com/cbrnr)) - Make header and footer XML available in "xml" key ([#4](https://github.com/cbrnr/XDF.jl/pull/4) by [Alberto Barradas](https://github.com/abcsds)) From 843a3bd9767c465f347e475cd8efadd37cc1bfb8 Mon Sep 17 00:00:00 2001 From: Alberto Barradas Date: Wed, 8 Jan 2025 19:56:32 +0100 Subject: [PATCH 14/18] Add changelog entry --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a8ba32e..5070866 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,8 @@ -## [0.2.0] - 2022-02-23 +## [UNRELEASED] - 2025-01-08 +### ✨ Added + - Add dejitter function (by ([#13](https://github.com/cbrnr/XDF.jl/pull/13) by [Alberto Barradas](https://github.com/abcsds)) + + ## [0.2.0] - 2022-02-23 - Add support for string markers and string streams ([#2](https://github.com/cbrnr/XDF.jl/pull/2) by [Alberto Barradas](https://github.com/abcsds) and [Clemens Brunner](https://github.com/cbrnr)) - Make header and footer XML available in "xml" key ([#4](https://github.com/cbrnr/XDF.jl/pull/4) by [Alberto Barradas](https://github.com/abcsds)) From bc6b6e8fa4f1a19f37d4d0568bf5faf6a4313f88 Mon Sep 17 00:00:00 2001 From: Alberto Barradas Date: Thu, 9 Jan 2025 13:39:27 +0100 Subject: [PATCH 15/18] Add changelog entry --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a8ba32e..047ee8a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## [UNRELEASED] - 2025-01-08 +### ✨ Added + - Add tests for string markers from issue xdf-modules/libxdf#19 (by ([#13](https://github.com/cbrnr/XDF.jl/pull/13) by [Alberto Barradas](https://github.com/abcsds))) + ## [0.2.0] - 2022-02-23 - Add support for string markers and string streams ([#2](https://github.com/cbrnr/XDF.jl/pull/2) by [Alberto Barradas](https://github.com/abcsds) and [Clemens Brunner](https://github.com/cbrnr)) - Make header and footer XML available in "xml" key ([#4](https://github.com/cbrnr/XDF.jl/pull/4) by [Alberto Barradas](https://github.com/abcsds)) From 852b893d00e37a6c3a54d16a6723f55b7ab16c5d Mon Sep 17 00:00:00 2001 From: Alberto Barradas Date: Thu, 9 Jan 2025 13:50:16 +0100 Subject: [PATCH 16/18] Add changelog entry --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a8ba32e..4a9fc6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## [UNRELEASED] - 2025-01-08 +### ✨ Added + - Add CI workflow for versions 1.11, 1.6 (LTS), and pre-release. (by ([#11](https://github.com/cbrnr/XDF.jl/pull/11) by [Alberto Barradas](https://github.com/abcsds))) + ## [0.2.0] - 2022-02-23 - Add support for string markers and string streams ([#2](https://github.com/cbrnr/XDF.jl/pull/2) by [Alberto Barradas](https://github.com/abcsds) and [Clemens Brunner](https://github.com/cbrnr)) - Make header and footer XML available in "xml" key ([#4](https://github.com/cbrnr/XDF.jl/pull/4) by [Alberto Barradas](https://github.com/abcsds)) From d6d38e6fa19aec32f8a26ab8af0c5e8af57b8c40 Mon Sep 17 00:00:00 2001 From: Alberto Barradas Date: Thu, 9 Jan 2025 13:39:27 +0100 Subject: [PATCH 17/18] Add changelog entry --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a8ba32e..805f71c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## [UNRELEASED] - 2025-01-08 +### ✨ Added +- Add tests for string markers from issue xdf-modules/libxdf#19 (([#13](https://github.com/cbrnr/XDF.jl/pull/13) by [Alberto Barradas](https://github.com/abcsds))) + ## [0.2.0] - 2022-02-23 - Add support for string markers and string streams ([#2](https://github.com/cbrnr/XDF.jl/pull/2) by [Alberto Barradas](https://github.com/abcsds) and [Clemens Brunner](https://github.com/cbrnr)) - Make header and footer XML available in "xml" key ([#4](https://github.com/cbrnr/XDF.jl/pull/4) by [Alberto Barradas](https://github.com/abcsds)) From 981cc5061a4ddca28ead08871f1ce39cafffc094 Mon Sep 17 00:00:00 2001 From: Alberto Barradas Date: Thu, 9 Jan 2025 22:45:35 +0100 Subject: [PATCH 18/18] Fix #10 --- .gitignore | 3 +++ src/XDF.jl | 4 +++- test/runtests.jl | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index ba39cc5..02c521c 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ Manifest.toml +Dockerfile +.vscode/ +.venv/ diff --git a/src/XDF.jl b/src/XDF.jl index 53d0b6c..1c1627d 100644 --- a/src/XDF.jl +++ b/src/XDF.jl @@ -123,7 +123,9 @@ function read_xdf(filename::AbstractString, sync::Bool=true) streams[id]["time"][index[id]] = previous + delta end if streams[id]["dtype"] === String - streams[id]["data"][index[id], :] .= String(read(io, read_varlen_int(io))) + for j in 1:nchannels + streams[id]["data"][index[id], j] = String(read(io, read_varlen_int(io))) + end else streams[id]["data"][index[id], :] = reinterpret(dtype, read(io, sizeof(dtype) * nchannels)) end diff --git a/test/runtests.jl b/test/runtests.jl index dc11187..c41b6fa 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -91,7 +91,7 @@ end @test s1["srate"] == 1000.0 @test s1["dtype"] == String @test size(s1["data"]) == (1, 2) - @test s1["data"] == ["Marker 0A" "Marker 0A"] + @test s1["data"] == ["Marker 0A" "Marker 0B"] s2 = streams[46202862] @test s2["type"] == "EEG" @test s2["nchannels"] == 64