From 15cd842b23807828cc202744ba14758d184b5079 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Mon, 7 Oct 2019 12:33:55 -0500 Subject: [PATCH 1/4] Support saving extra information in header sections --- src/ImagineFormat.jl | 48 ++++++++++++++++++++++++++++++++------------ test/runtests.jl | 5 ++++- 2 files changed, 39 insertions(+), 14 deletions(-) diff --git a/src/ImagineFormat.jl b/src/ImagineFormat.jl index a01dd26..1eeaced 100644 --- a/src/ImagineFormat.jl +++ b/src/ImagineFormat.jl @@ -256,7 +256,7 @@ function parse_header(s::IOStream) end headerdict[k] = thisdict else - func = field_key_dict[k] + func = get(field_key_dict, k, identity) try headerdict[k] = func(v) catch err @@ -318,34 +318,56 @@ function imagine2nrrd(nrrdname::AbstractString, h::Dict{String, Any}, datafilena end """ -`save_header(filename, header)` writes a header dictionary in Imagine format. + save_header(filename, header; section=()) -`save_header(destname, srcname, img::AbstractArray, [T::Type = -eltype(img)])` writes a `.imagine` file with name `destname`, using -the `.imagine` file `srcname` as a template. Size and element type -fields are updated from `img` and `T`, respectively. +Write a `header` dictionary in Imagine format. +`section` can be one of `general`, `misc`, `ai`, or `camera`, to write additional entries +in the specified section. For example, + + header["stimulus info"] = "here is some info" + Imagine.save_header(filename, header; misc=("stimulus info",)) + +would allow one to insert "stimulus info" into the "[misc params]" section of the `.imagine` +file. """ -function save_header(filename::AbstractString, h::Dict{String, Any}) +function save_header(filename::AbstractString, h::Dict{String}; + general=(), + misc=(), + ai=(), + camera=()) open(filename, "w") do io write(io, magic(format"Imagine")) println(io, "\n[general]") - writekv(io, h, ("header version", "app version", "date and time", "byte order", "rig")) + writekv(io, h, ("header version", "app version", "date and time", "byte order", "rig", general...)) println(io, "\n[misc params]") - writekv(io, h, ("stimulus file content", "comment", "ai data file","image data file", "piezo")) + writekv(io, h, ("stimulus file content", "comment", "ai data file","image data file", "piezo", misc...)) println(io, "\n[ai]") - writekv(io, h, ("nscans", "channel list", "label list", "scan rate", "min sample", "max sample", "min input", "max input")) + writekv(io, h, ("nscans", "channel list", "label list", "scan rate", "min sample", "max sample", "min input", "max input", ai...)) println(io, "\n[camera]") - writekv(io, h, ("original image depth", "saved image depth", "image width", "image height", "number of frames requested", "nStacks", "idle time between stacks", "pre amp gain", "gain", "exposure time", "vertical shift speed", "vertical clock vol amp", "readout rate", "pixel order", "frame index offset", "frames per stack", "pixel data type", "camera", "um per pixel", "binning", "angle from horizontal (deg)", "bidirectional")) + writekv(io, h, ("original image depth", "saved image depth", "image width", + "image height", "number of frames requested", "nStacks", + "idle time between stacks", "pre amp gain", "gain", "exposure time", + "vertical shift speed", "vertical clock vol amp", "readout rate", + "pixel order", "frame index offset", "frames per stack", + "pixel data type", "camera", "um per pixel", "binning", + "angle from horizontal (deg)", "bidirectional", camera...)) end nothing end -function save_header(dest::AbstractString, src::AbstractString, img::AbstractArray, T::Type = eltype(img)) +""" + save_header(destname, srcname, img::AbstractArray, [T::Type = eltype(img)]) + +Write an `.imagine` file with name `destname`, using +the `.imagine` file `srcname` as a template. Size and element type +fields are updated from `img` and `T`, respectively. +""" +function save_header(dest::AbstractString, src::AbstractString, img::AbstractArray, T::Type = eltype(img); kwargs...) h = parse_header(src) fillsize!(h, img) h["pixel data type"] = lowercase(string(T)) h["byte order"] = ENDIAN_BOM == 0x04030201 ? "l" : "b" - save_header(dest, h) + save_header(dest, h; kwargs...) end function fillsize!(h, img::AxisArray) diff --git a/test/runtests.jl b/test/runtests.jl index 5c575f4..698e46f 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -40,10 +40,12 @@ MHz = u"MHz" h = ImagineFormat.parse_header("test_noshift.imagine") h["byte order"] = ENDIAN_BOM == 0x04030201 ? "l" : "b" -ImagineFormat.save_header(ifn, h) +h["extra stuff"] = "Tuesday" +ImagineFormat.save_header(ifn, h; misc=("extra stuff",)) h2 = ImagineFormat.parse_header(ifn) @test isnan(h["readout rate"]) && isnan(h2["readout rate"]) @test isnan(h["vertical shift speed"]) && isnan(h2["vertical shift speed"]) +@test h2["extra stuff"] == "Tuesday" rm(ifn) img2 = 0 GC.gc(); GC.gc(); GC.gc() @@ -83,6 +85,7 @@ img = load("no_z.imagine") @test pixelspacing(img) == (-1μm, -1μm) # Fix deprecations in loading imagine-cam file incompatibility +@info "Two warnings are expected" img = load("test_nocam.imagine") @test isempty(img) From b2321b33f5091ef7931f28d6dc49b4307a56434f Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Mon, 7 Oct 2019 16:41:51 -0500 Subject: [PATCH 2/4] Fix writing of endianness There was no good reason to encode this via types, values are easier. --- src/ImagineFormat.jl | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/ImagineFormat.jl b/src/ImagineFormat.jl index 1eeaced..93b8658 100644 --- a/src/ImagineFormat.jl +++ b/src/ImagineFormat.jl @@ -82,10 +82,10 @@ function load(io::Stream{format"Imagine"}; mode="r") end abstract type Endian end -mutable struct LittleEndian <: Endian; end -mutable struct BigEndian <: Endian; end -const endian_dict = Dict("l"=>LittleEndian, "b"=>BigEndian) -const nrrd_endian_dict = Dict(LittleEndian=>"little",BigEndian=>"big") +struct LittleEndian <: Endian; end +struct BigEndian <: Endian; end +const endian_dict = Dict("l"=>LittleEndian(), "b"=>BigEndian()) +const nrrd_endian_dict = Dict(LittleEndian()=>"little",BigEndian()=>"big") parse_endian(s::AbstractString) = endian_dict[lowercase(s)] function parse_vector_int(s::AbstractString) @@ -420,11 +420,15 @@ function writefield(io, fn, dct::Dict) end end +writefield(io, fn, ::LittleEndian) = println(io, "byte order=l") +writefield(io, fn, ::BigEndian) = println(io, "byte order=b") + writeum(io,x) = print(io, x/μm, " um") writeus(io,x::Unitful.Time) = print(io, x/μs, " us") writeus(io,x) = nothing writeMHz(io,x::Unitful.Frequency) = print(io, x/MHz, " MHz") writeMHz(io,x) = nothing + const write_dict = Dict{String,Function}( "bidirectional" => (io,x)->x ? print(io, 1) : print(io, 0), "start position" => writeum, @@ -433,8 +437,4 @@ const write_dict = Dict{String,Function}( "readout rate" => (io,x)->(writeMHz(io,x); print(io,'\n')), ) -function __init__() - Base.rehash!(nrrd_endian_dict) -end - end From f267dd6eb9a6344978b60ebf8092bea06d5e2583 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Mon, 7 Oct 2019 16:42:29 -0500 Subject: [PATCH 3/4] Add support for "stimulus sequence", "stimulus scan hi/lo", and "stimulus frame hi/lo" --- src/ImagineFormat.jl | 7 ++++++- test/runtests.jl | 6 +++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/ImagineFormat.jl b/src/ImagineFormat.jl index 93b8658..bb2c58d 100644 --- a/src/ImagineFormat.jl +++ b/src/ImagineFormat.jl @@ -222,7 +222,12 @@ const field_key_dict = Dict{AbstractString,Function}( "angle from horizontal (deg)" => float64_or_empty, "x translation in pixels" => x->parse(Int,x) != 0, "y translation in pixels" => x->parse(Int,x) != 0, - "rotation angle in degree" => x->parse(Float64,x) != 0) + "rotation angle in degree" => x->parse(Float64,x) != 0, + "stimulus scan hi" => parse_vector_int, + "stimulus scan lo" => parse_vector_int, + "stimulus frame hi" => parse_vector_int, + "stimulus frame lo" => parse_vector_int, + "stimulus sequence" => identity) function parse_header(s::IOStream) diff --git a/test/runtests.jl b/test/runtests.jl index 698e46f..ced3dfb 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -40,12 +40,12 @@ MHz = u"MHz" h = ImagineFormat.parse_header("test_noshift.imagine") h["byte order"] = ENDIAN_BOM == 0x04030201 ? "l" : "b" -h["extra stuff"] = "Tuesday" -ImagineFormat.save_header(ifn, h; misc=("extra stuff",)) +h["stimulus scan hi"] = [3, 5] +ImagineFormat.save_header(ifn, h; misc=("stimulus scan hi",)) h2 = ImagineFormat.parse_header(ifn) @test isnan(h["readout rate"]) && isnan(h2["readout rate"]) @test isnan(h["vertical shift speed"]) && isnan(h2["vertical shift speed"]) -@test h2["extra stuff"] == "Tuesday" +@test h2["stimulus scan hi"] == [3, 5] rm(ifn) img2 = 0 GC.gc(); GC.gc(); GC.gc() From a615525450818ac70fe8f1860eab5f0aacd9caf3 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Wed, 9 Oct 2019 10:48:45 -0500 Subject: [PATCH 4/4] Support both "bidirection" and "bidirectional" as keys We should switch to just "bidirectional" but old files may be written this way. --- src/ImagineFormat.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ImagineFormat.jl b/src/ImagineFormat.jl index bb2c58d..91a023c 100644 --- a/src/ImagineFormat.jl +++ b/src/ImagineFormat.jl @@ -436,6 +436,7 @@ writeMHz(io,x) = nothing const write_dict = Dict{String,Function}( "bidirectional" => (io,x)->x ? print(io, 1) : print(io, 0), + "bidirection" => (io,x)->x ? print(io, 1) : print(io, 0), "start position" => writeum, "stop position" => writeum, "vertical shift speed" => (io,x)->(writeus(io,x); print(io,'\n')),