Skip to content

Commit

Permalink
Add functions for getting parts of a file as struct (#97)
Browse files Browse the repository at this point in the history
* Add functions for getting parts of a file as struct

* Add tests and further functions

* Fix tests
  • Loading branch information
jonschumacher authored Jul 31, 2024
1 parent d230d0e commit 462bf80
Show file tree
Hide file tree
Showing 3 changed files with 200 additions and 3 deletions.
2 changes: 1 addition & 1 deletion src/MDF.jl
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,7 @@ measFramePermutation(f::MDFFileV1)::Union{Vector{Int64}, Nothing} = nothing
measFramePermutation(f::MDFFileV2)::Union{Vector{Int64}, Nothing} = @keyoptional f["/measurement/framePermutation"]
measSparsityTransformation(f::MDFFileV1)::Union{String, Nothing} = nothing
measSparsityTransformation(f::MDFFileV2)::Union{String, Nothing} = @keyoptional f["/measurement/sparsityTransformation"]
measSubsamplingIndices(f::MDFFileV2)::Union{Array{Integer, 4}, Nothing} = @keyoptional f["/measurement/subsamplingIndices"]
measSubsamplingIndices(f::MDFFileV2)::Union{Array{Int64, 4}, Nothing} = @keyoptional f["/measurement/subsamplingIndices"]

fullFramePermutation(f::MDFFile) = fullFramePermutation(f, calibIsMeanderingGrid(f))

Expand Down
53 changes: 52 additions & 1 deletion src/MDFInMemory.jl
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,7 @@ mutable struct MDFv2Measurement <: MDFv2InMemoryPart
"Name of the applied sparsity transformation; optional if !isSparsityTransformed"
sparsityTransformation::Union{String, Nothing}
"Subsampling indices \\beta{j,c,k,b}; optional if !isSparsityTransformed"
subsamplingIndices::Union{Array{Integer, 4}, Nothing}
subsamplingIndices::Union{Array{Int64, 4}, Nothing}

function MDFv2Measurement(;
data = missing,
Expand Down Expand Up @@ -1303,6 +1303,57 @@ for (fieldname, fieldtype) in zip(fieldnames(MDFv2InMemory), fieldtypes(MDFv2InM
end
end

# Extract individual parts of an MDFFile as in-memory parts
for (partFieldnameStr, partPrefix) prefixes
partFieldnameSymbol = Symbol(partFieldnameStr)
partInMemorySymbol = Symbol(lowercase(partFieldnameStr[6:end]))
type_ = getfield(MPIFiles, partFieldnameSymbol)
fields = fieldnames(type_)
functionNames = [let fieldString=string(field_); partPrefix != "" ? Symbol(partPrefix*uppercase(fieldString[1])*fieldString[2:end]) : Symbol(fieldString) end for field_ fields if field_ [:drivefield, :receiver]]

@eval begin
@doc $"""
$partFieldnameStr(file::MDFFile)
Create a `$partFieldnameStr` from the respective section in the given `file`.
"""
function $partFieldnameSymbol(file::MDFFile)::$type_
instance = $partFieldnameSymbol()
for (fieldname_, functionName_) in zip($fields, $functionNames)
setfield!(instance, fieldname_, eval(functionName_)(file))
end

return instance
end
end

if !(partFieldnameStr == "MDFv2Drivefield" || partFieldnameStr == "MDFv2Receiver")
@eval begin
@doc $"""
$partFieldnameStr(file::MDFv2InMemory)
Create a `$partFieldnameStr` from the respective section in the given `file`.
"""
function $partFieldnameSymbol(file::MDFv2InMemory)::$type_
return file.$partInMemorySymbol
end
end
else
@eval begin
@doc $"""
$partFieldnameStr(file::MDFv2InMemory)
Create a `$partFieldnameStr` from the respective section in the given `file`.
"""
function $partFieldnameSymbol(file::MDFv2InMemory)::$type_
return file.acquisition.$partInMemorySymbol
end
end
end
end
MDFv2Drivefield(part::MDFv2Acquisition) = part.drivefield
MDFv2Receiver(part::MDFv2Acquisition) = part.receiver

# And some utility functions
measIsCalibProcessed(mdf::MDFv2InMemory)::Union{Bool, Missing} = measIsFramePermutation(mdf) &&
measIsFourierTransformed(mdf) &&
Expand Down
148 changes: 147 additions & 1 deletion test/MDFInMemory.jl
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ end

fnMeasV2_converted = joinpath(tmpdir,"mdfim","measurement_V2_converted.mdf")
fnSMV2_converted = joinpath(tmpdir,"mdfim","systemMatrix_V2_converted.mdf")
fnPart_converted = joinpath(tmpdir,"mdfim","imparts.mdf")

@testset "Conversion" begin
@testset "Fields" begin
Expand Down Expand Up @@ -396,7 +397,8 @@ end
order = "xyz",
positions = fill(0.0, (3, O)),
size = [1, 1, 1],
snr = fill(0.0, (K, C, J))
snr = fill(0.0, (K, C, J)),
isMeanderingGrid = false # Not in specs for MDF v2!
)
mdf.reconstruction = MDFv2Reconstruction(
data = fill(0, (S, P, Q)),
Expand Down Expand Up @@ -582,4 +584,148 @@ end
test_mdf_replacement(mdf, :recoSize, [1, 1, 2], "The product of `size` with `[1, 1, 2]` must equal P.")
end
end

@testset "Part retrieval" begin
A = 1 # tracer materials/injections for multi-color MPI
N = 2 # acquired frames (N = O + E), same as a spatial position for calibration
E = 1 # acquired background frames (E = N − O)
O = 1 # acquired foreground frames (O = N − E)
B = 1 # coefficients stored after sparsity transformation (B \\le O)
J = 1 # periods within one frame
Y = 1 # partitions of each patch position
C = 1 # receive channels
D = 1 # drive-field channels
F = 1 # frequencies describing the drive-field waveform
V = 800 # points sampled at receiver during one drive-field period
W = V # sampling points containing processed data (W = V if no frequency selection or bandwidth reduction has been applied)
K = Int(V/2 + 1)# frequencies describing the processed data (K = V/2 + 1 if no frequency selection or bandwidth reduction has been applied)
Q = 1 # frames in the reconstructed MPI data set
P = 1 # voxels in the reconstructed MPI data set
S = 1 # channels in the reconstructed MPI data set

mdf = MDFv2InMemory()
mdf.root = MDFv2Root(
time=DateTime("2021-04-26T17:12:21.686"),
uuid=UUID("946a039e-48de-47ee-957e-a15af437e0be"),
version=VersionNumber("2.1.0")
)
mdf.study = MDFv2Study(
description = "n.a.",
name = "n.a.",
number = 1,
time = DateTime("2021-04-26T17:12:21.686"),
uuid = UUID("f2f3ae66-2fc3-49f7-91f9-71c1c2dc15e7")
)
mdf.experiment = MDFv2Experiment(
description = "n.a.",
isSimulation = true,
name = "n.a.",
number = 1,
subject = "n.a.",
uuid = UUID("3076d4bc-a2ef-46ef-8a99-dcd047379b8a")
)
mdf.tracer = MDFv2Tracer(
batch = fill("n.a.", A),
concentration = fill(1.0, A),
injectionTime = fill(DateTime("2021-04-26T17:12:21.686"), A),
name = fill("MyAwesomeTracer", A),
solute = fill("n.a.", A),
vendor = fill("Me", A),
volume = fill(1.0, A),
)
mdf.scanner = MDFv2Scanner(
boreSize = 0.3,
facility = "MyAwesomeInstitute",
manufacturer = "MeMyselfAndI",
name = "MyAwesomeScanner",
operator = "JustMe",
topology = "FFL" # Of course ;)
)
drivefield = MDFv2Drivefield(
baseFrequency = 40e3,
cycle = 1600/40e3,
divider = fill(1600, (F, D)),
numChannels = D,
phase = fill(0.0, (F, D, J)),
strength = fill(1.0, (F, D, J)),
waveform = fill("sine", (F, D)),
)
receiver = MDFv2Receiver(
bandwidth = 20e3,
dataConversionFactor = repeat([1/2^16 0]', outer=(1, C)),
inductionFactor = fill(1.0, C),
numChannels = C,
numSamplingPoints = V,
transferFunction = fill(1+0.5im, (K, C)),
unit = "V"
)
mdf.acquisition = MDFv2Acquisition(
gradient = fill(0.0, (3, 3, Y, J)),
numAverages = 1,
numFrames = N,
numPeriodsPerFrame = J,
offsetField = fill(0.0, (3, Y, J)),
startTime = DateTime("2021-04-26T17:12:21.686"),
drivefield = drivefield,
receiver = receiver
)
mdf.measurement = MDFv2Measurement(;
data = fill(0, (K, C, J, N)),
# framePermutation = fill(0, N),
# frequencySelection = collect(1:K),
isBackgroundCorrected = false,
isBackgroundFrame = vcat(fill(true, E), fill(false, O)),
isFastFrameAxis = false,
isFourierTransformed = false,
isFramePermutation = false,
isFrequencySelection = false,
isSparsityTransformed = false,
isSpectralLeakageCorrected = false,
isTransferFunctionCorrected = false,
# sparsityTransformation = "DCT-I",
# subsamplingIndices = fill(0, (B, K, C, J))
)
mdf.calibration = MDFv2Calibration(
deltaSampleSize = fill(0.001, 3),
fieldOfView = fill(0.2, 3),
fieldOfViewCenter = fill(0.0, 3),
method = "robot",
offsetFields = fill(0.0, (3, O)),
order = "xyz",
positions = fill(0.0, (3, O)),
size = [1, 1, 1],
snr = fill(0.0, (K, C, J)),
isMeanderingGrid = false
)
mdf.reconstruction = MDFv2Reconstruction(
data = fill(0, (S, P, Q)),
fieldOfView = fill(0.2, 3),
fieldOfViewCenter = fill(0.0, 3),
isOverscanRegion = fill(false, P),
order = "xyz",
positions = fill(0.0, (3, P)),
size = [1, 1, 1]
)

saveasMDF(fnPart_converted, mdf)
file = MPIFile(fnPart_converted)

for partFieldnameStr in keys(MPIFiles.prefixes)
partInMemorySymbol = Symbol(lowercase(partFieldnameStr[6:end]))

partim = eval(Symbol(partFieldnameStr))(mdf)
part = eval(Symbol(partFieldnameStr))(file)

if !(partFieldnameStr == "MDFv2Drivefield" || partFieldnameStr == "MDFv2Receiver")
@test partim == getfield(mdf, partInMemorySymbol)
@test all([getfield(part, fieldname_) == getfield(getfield(mdf, partInMemorySymbol), fieldname_) for fieldname_ fieldnames(typeof(part)) if fieldname_ [:drivefield, :receiver]])
else
@test partim == getfield(mdf.acquisition, partInMemorySymbol)
@test all([getfield(part, fieldname_) == getfield(getfield(mdf.acquisition, partInMemorySymbol), fieldname_) for fieldname_ fieldnames(typeof(part))])
end
end

@test MDFv2Drivefield(MDFv2Acquisition(mdf)) == drivefield
@test MDFv2Receiver(MDFv2Acquisition(mdf)) == receiver
end
end

0 comments on commit 462bf80

Please sign in to comment.