Skip to content

Commit

Permalink
Merge pull request #31 from timholy/teh/stimuli
Browse files Browse the repository at this point in the history
Allow stimulus-related metadata
  • Loading branch information
timholy authored Oct 9, 2019
2 parents 2a0afe7 + a615525 commit 2a0f565
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 23 deletions.
72 changes: 50 additions & 22 deletions src/ImagineFormat.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -256,7 +261,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
Expand Down Expand Up @@ -318,34 +323,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)
Expand Down Expand Up @@ -398,21 +425,22 @@ 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),
"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')),
"readout rate" => (io,x)->(writeMHz(io,x); print(io,'\n')),
)

function __init__()
Base.rehash!(nrrd_endian_dict)
end

end
5 changes: 4 additions & 1 deletion test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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["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["stimulus scan hi"] == [3, 5]
rm(ifn)
img2 = 0
GC.gc(); GC.gc(); GC.gc()
Expand Down Expand Up @@ -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)

Expand Down

0 comments on commit 2a0f565

Please sign in to comment.