Skip to content

Commit

Permalink
Merge pull request #202 from MagneticResonanceImaging/read_bruker_dat…
Browse files Browse the repository at this point in the history
…asets

WIP : solve reading bruker shared datasets + implements FLAGS
  • Loading branch information
aTrotier authored Dec 10, 2024
2 parents 03c0aeb + 5471056 commit d6c17da
Show file tree
Hide file tree
Showing 14 changed files with 566 additions and 242 deletions.
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ jobs:
matrix:
version:
- '1' # Leave this line unchanged. '1' will automatically expand to the latest stable 1.x release of Julia.
- '1.10' #LTS
#- 'nightly'
os:
- ubuntu-latest
Expand Down
200 changes: 200 additions & 0 deletions MRIBase/src/Datatypes/Flags.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
export FLAGS, create_flag_bitmask, flag_is_set, flag_set!, flag_remove!, flag_remove_all!, flags_of

"""
FLAGS::Dict{String, Int}
A dictionary mapping string keys (representing flag names) to bitmask values.
Flags are used to indicate specific attributes of the corresponding Profile. Example flag names include "ACQ_FIRST_IN_ENCODE_STEP1", "ACQ_LAST_IN_SLICE", etc.
"""
FLAGS = Dict(
"ACQ_FIRST_IN_ENCODE_STEP1" => 1,
"ACQ_LAST_IN_ENCODE_STEP1" => 2,
"ACQ_FIRST_IN_ENCODE_STEP2" => 3,
"ACQ_LAST_IN_ENCODE_STEP2" => 4,
"ACQ_FIRST_IN_AVERAGE" => 5,
"ACQ_LAST_IN_AVERAGE" => 6,
"ACQ_FIRST_IN_SLICE" => 7,
"ACQ_LAST_IN_SLICE" => 8,
"ACQ_FIRST_IN_CONTRAST" => 9,
"ACQ_LAST_IN_CONTRAST" => 10,
"ACQ_FIRST_IN_PHASE" => 11,
"ACQ_LAST_IN_PHASE" => 12,
"ACQ_FIRST_IN_REPETITION" => 13,
"ACQ_LAST_IN_REPETITION" => 14,
"ACQ_FIRST_IN_SET" => 15,
"ACQ_LAST_IN_SET" => 16,
"ACQ_FIRST_IN_SEGMENT" => 17,
"ACQ_LAST_IN_SEGMENT" => 18,
"ACQ_IS_NOISE_MEASUREMENT" => 19,
"ACQ_IS_PARALLEL_CALIBRATION" => 20,
"ACQ_IS_PARALLEL_CALIBRATION_AND_IMAGING" => 21,
"ACQ_IS_REVERSE" => 22,
"ACQ_IS_NAVIGATION_DATA" => 23,
"ACQ_IS_PHASECORR_DATA" => 24,
"ACQ_LAST_IN_MEASUREMENT" => 25,
"ACQ_IS_HPFEEDBACK_DATA" => 26,
"ACQ_IS_DUMMYSCAN_DATA" => 27,
"ACQ_IS_RTFEEDBACK_DATA" => 28,
"ACQ_IS_SURFACECOILCORRECTIONSCAN_DATA" => 29,
"ACQ_COMPRESSION1" => 53,
"ACQ_COMPRESSION2" => 54,
"ACQ_COMPRESSION3" => 55,
"ACQ_COMPRESSION4" => 56,
"ACQ_USER1" => 57,
"ACQ_USER2" => 58,
"ACQ_USER3" => 59,
"ACQ_USER4" => 60,
"ACQ_USER5" => 61,
"ACQ_USER6" => 62,
"ACQ_USER7" => 63,
"ACQ_USER8" => 64
)

function bitshift(A, k)
k >= 0 ? out = A << k : out = A >> abs(k)
return out
end

"""
create_flag_bitmask(flag::AbstractString) -> UInt64
create_flag_bitmask(flag::Integer) -> UInt64
Creates a bitmask for the given flag.
- If the flag is a string, its integer value is looked up in `FLAGS`.
- If the flag is an integer, it must be positive. The bitmask is created by left-shifting `1` to the corresponding bit position.
# Arguments
- `flag`: A string representing the flag name or a positive integer.
# Returns
- A `UInt64` bitmask where only the bit corresponding to the flag is set.
# Throws
- `KeyError` if the string flag is not found in `FLAGS`.
- `DomainError` if the integer flag is not positive.
# Example
```julia
create_flag_bitmask("ACQ_FIRST_IN_ENCODE_STEP1") # 0x0000000000000001
create_flag_bitmask(2) # 0x0000000000000002
```
"""
create_flag_bitmask(flag::T) where T = error("Unexpected type for bitmask, expected String or positive Integer, found $T")
create_flag_bitmask(flag::AbstractString) = create_flag_bitmask(FLAGS[flag])
function create_flag_bitmask(flag::Integer)
flag > 0 || throw(DomainError(flag, "Bitmask can only be created for positive integers"))
b = UInt64(flag)
return bitshift(UInt64(1), b - 1)
end

"""
flag_is_set(obj::Profile, flag) -> Bool
Checks whether a specific flag is set in the given `Profile` object.
# Arguments
- `obj`: A `Profile` object with a `head` field containing a `flags` attribute (as a bitmask).
- `flag`: A string (flag name) or an integer (bit position).
# Returns
- `true` if the flag is set in the `obj.head.flags` bitmask; otherwise, `false`.
# Example
```julia
flag_is_set(profile, "ACQ_FIRST_IN_ENCODE_STEP1") # true or false
```
"""
function flag_is_set(obj::Profile, flag)
bitmask = create_flag_bitmask(flag)
ret = obj.head.flags & bitmask > 0
return ret
end

"""
flag_set!(obj::Profile, flag)
Sets the specified flag in the given `Profile` object.
# Arguments
- `obj`: A `Profile` object with a `head` field containing a `flags` attribute (as a bitmask).
- `flag`: A string (flag name) or an integer (bit position).
# Modifies
- `obj.head.flags` by setting the bit corresponding to the flag.
# Example
```julia
flag_set!(profile, "ACQ_FIRST_IN_SLICE")
```
"""
function flag_set!(obj::Profile, flag)
bitmask = create_flag_bitmask(flag)
obj.head.flags = obj.head.flags | bitmask
end

"""
flag_remove!(obj::Profile, flag)
Removes (clears) the specified flag in the given `Profile` object.
# Arguments
- `obj`: A `Profile` object with a `head` field containing a `flags` attribute (as a bitmask).
- `flag`: A string (flag name) or an integer (bit position).
# Modifies
- `obj.head.flags` by clearing the bit corresponding to the flag.
# Example
```julia
flag_remove!(profile, "ACQ_LAST_IN_PHASE")
```
"""
function flag_remove!(obj::Profile, flag)
bitmask = create_flag_bitmask(flag)
obj.head.flags = obj.head.flags & ~bitmask
end

"""
flag_remove_all!(obj::Profile)
Clears all flags in the given `Profile` object.
# Arguments
- `obj`: A `Profile` object with a `head` field containing a `flags` attribute (as a bitmask).
# Modifies
- `obj.head.flags`, setting it to `0` (all flags cleared).
# Example
```julia
flag_remove_all!(profile)
```
"""
function flag_remove_all!(obj::Profile)
obj.head.flags = UInt64(0);
end

"""
flags_of(obj::Profile) -> Vector{String}
Returns a list of all flags that are set in the given `Profile` object.
# Arguments
- `obj`: A `Profile` object with a `head` field containing a `flags` attribute (as a bitmask).
# Returns
- An array of strings representing the names of the flags that are currently set.
# Example
```julia
flags = flags_of(profile) # ["ACQ_FIRST_IN_ENCODE_STEP1", "ACQ_LAST_IN_SLICE"]
```
"""
function flags_of(obj::Profile)
flags_ = String[]
for f in FLAGS
flag_is_set(obj::Profile, f.first) ? push!(flags_,f.first) : nothing
end
return flags_
end
5 changes: 3 additions & 2 deletions MRIBase/src/Datatypes/RawAcqData.jl
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ function subsampleIndices(f::RawAcquisitionData; slice::Int=1, contrast::Int=1,
i1 = tr_center_idx - center_sample + 1 + f.profiles[i].head.discard_pre
i2 = tr_center_idx - center_sample + numSamp - f.profiles[i].head.discard_post
# convert to linear index
lineIdx = collect(i1:i2) .+ numSamp*((encSt2[i]-1)*numProf + (encSt1[i]-1))
lineIdx = collect(i1:i2) .+ numEncSamp *((encSt2[i]-1)*numProf + (encSt1[i]-1))
append!(idx, lineIdx)
end

Expand Down Expand Up @@ -264,8 +264,9 @@ function rawdata(f::RawAcquisitionData; slice::Int=1, contrast::Int=1, repetitio
i1 = f.profiles[l].head.discard_pre + 1
i2 = i1+numSampPerProfile-1
kdata[:,cnt,:] .= f.profiles[l].data[i1:i2, :]
if f.profiles[l].head.read_dir[1] < 0
if flag_is_set(f.profiles[l],"ACQ_IS_REVERSE")
kdata[:,cnt,:] .= reverse(kdata[:,cnt,:], dims=1)
flag_remove!(f.profiles[l],"ACQ_IS_REVERSE")
end
cnt += 1
end
Expand Down
3 changes: 1 addition & 2 deletions MRIBase/src/MRIBase.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,5 @@ using NFFTTools # for density compensation weights in trajectory

include("Trajectories/Trajectories.jl")
include("Datatypes/Datatypes.jl")


include("Datatypes/Flags.jl")
end # module
3 changes: 2 additions & 1 deletion MRIBase/test/runtests.jl
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using Test, MRIBase

include("testTrajectories.jl")
include("testTrajectories.jl")
include("testFlags.jl")
34 changes: 34 additions & 0 deletions MRIBase/test/testFlags.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@

@testset "Flags" begin
head = AcquisitionHeader()
traj = Matrix{Float32}(undef,0,0)
dat = Matrix{ComplexF32}(undef,0,0)
p = Profile(head,traj,dat)

p2 = deepcopy(p)
@test ~flag_is_set(p,"ACQ_IS_REVERSE")
flag_set!(p,"ACQ_IS_REVERSE")
flag_set!(p2,22)
@test flag_is_set(p,"ACQ_IS_REVERSE")
@test p2.head.flags == p.head.flags
flag_remove!(p,"ACQ_IS_REVERSE")
@test ~flag_is_set(p,22)

flag_set!(p,"ACQ_IS_REVERSE")
flag_set!(p,"ACQ_IS_NAVIGATION_DATA")

fl = flags_of(p)

@test isequal(fl[1],"ACQ_IS_NAVIGATION_DATA") && isequal(fl[2],"ACQ_IS_REVERSE")
@test flag_is_set(p,"ACQ_IS_REVERSE") & flag_is_set(p,"ACQ_IS_NAVIGATION_DATA")
flag_remove_all!(p)
@test p.head.flags == UInt64(0)

@test_throws Exception flag_set!(p, -1)
@test_throws Exception flag_set!(p, "NOT_A_VALID_FLAG")
@test_throws Exception flag_is_set(p, -1)
@test_throws Exception flag_is_set(p, "NOT_A_VALID_FLAG")
@test_throws Exception flag_remove!(p, -1)
@test_throws Exception flag_remove!(p, "NOT_A_VALID_FLAG")
end

Loading

0 comments on commit d6c17da

Please sign in to comment.