Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow stimulus-related metadata #31

Merged
merged 4 commits into from
Oct 9, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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