Skip to content

Commit

Permalink
Merge pull request #88 from AaronGhost/PassSQAuxVR
Browse files Browse the repository at this point in the history
Pass aux_vr to SQ elements in dcm_write
  • Loading branch information
notZaki authored May 23, 2024
2 parents cfee5eb + eb31cab commit 5b98994
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 19 deletions.
5 changes: 2 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,8 @@ jobs:
fail-fast: false
matrix:
version:
- '1.0'
- '1'
- 'nightly'
- "1"
- "nightly"
os:
- ubuntu-latest
- macOS-latest
Expand Down
7 changes: 4 additions & 3 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
name = "DICOM"
uuid = "a26e6606-dd52-5f6a-a97f-4f611373d757"
version = "0.10.1"
version = "0.11.0"

[compat]
julia = "0.7, 1"
julia = "1.6"

[extras]
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
Downloads = "f43a241f-c20a-4ad4-852c-f6b1247861c6"

[targets]
test = ["Test"]
test = ["Test", "Downloads"]
18 changes: 9 additions & 9 deletions src/DICOM.jl
Original file line number Diff line number Diff line change
Expand Up @@ -592,7 +592,7 @@ function write_element(st::IO, gelt::Tuple{UInt16,UInt16}, data, is_explicit, au

if vr == "SQ"
vr = is_explicit ? vr : empty_vr
return dcm_store(st, gelt, s -> sequence_write(s, map(d -> d.meta, data), is_explicit), vr)
return dcm_store(st, gelt, s -> sequence_write(s, map(d -> d.meta, data), is_explicit, aux_vr), vr)
end

# Pack data into array container. This is to undo "data = data[1]" from read_element().
Expand Down Expand Up @@ -658,27 +658,27 @@ function dcm_store(st::IO, gelt::Tuple{UInt16,UInt16}, writef::Function, vr::Str
sz += 1
end
seek(st, p)
write(st, convert(lentype, max(0, sz)))
vr == "SQ" || gelt == (0xFFFE, 0xE000) ? write(st, convert(lentype, 0xffffffff)) : write(st, convert(lentype, max(0, sz)))
seek(st, endp)
if szWasOdd
write(st, UInt8(0))
vr in ("AE", "CS", "SH", "LO", "PN", "DA", "DT", "TM") ? write(st, UInt8(0x20)) : write(st, UInt8(0))
end
end

sequence_write(st::IO, items::Array{Any,1}, evr::Bool) =
sequence_write(st, convert(Array{Dict{Tuple{UInt16,UInt16},Any},1}, items), evr)
function sequence_write(st::IO, items::Array{Dict{Tuple{UInt16,UInt16},Any},1}, evr)
sequence_write(st::IO, items::Array{Any,1}, evr::Bool, aux_vr::Dict{Tuple{UInt16, UInt16}}) =
sequence_write(st, convert(Array{Dict{Tuple{UInt16,UInt16},Any},1}, items), evr, aux_vr)
function sequence_write(st::IO, items::Array{Dict{Tuple{UInt16,UInt16},Any},1}, evr, aux_vr::Dict{Tuple{UInt16, UInt16}})
for subitem in items
if length(subitem) > 0
dcm_store(st, (0xFFFE, 0xE000), s -> sequence_item_write(s, subitem, evr))
dcm_store(st, (0xFFFE, 0xE000), s -> sequence_item_write(s, subitem, evr, aux_vr))
end
end
write(st, UInt16[0xFFFE, 0xE0DD, 0x0000, 0x0000])
end

function sequence_item_write(st::IO, items::Dict{Tuple{UInt16,UInt16},Any}, evr)
function sequence_item_write(st::IO, items::Dict{Tuple{UInt16,UInt16},Any}, evr, aux_vr::Dict{Tuple{UInt16, UInt16}})
for gelt in sort(collect(keys(items)))
write_element(st, gelt, items[gelt], evr, empty_dcm_dict)
write_element(st, gelt, items[gelt], evr, aux_vr)
end
write(st, UInt16[0xFFFE, 0xE00D, 0x0000, 0x0000])
end
Expand Down
59 changes: 55 additions & 4 deletions test/runtests.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Test
using DICOM
using Downloads

const data_folder = joinpath(@__DIR__, "testdata")
if !isdir(data_folder)
Expand All @@ -26,15 +27,15 @@ const dicom_samples = Dict(
"US_Explicit_Big_RGB.dcm" =>
"https://github.com/notZaki/DICOMSamples/raw/master/DICOMSamples/US_Explicit_Big_RGB.dcm",
"DX_Implicit_Little_Interleaved.dcm" =>
"https://github.com/OHIF/viewer-testdata/raw/master/dcm/zoo-exotic/5.dcm",
"https://github.com/notZaki/DICOMSamples/raw/master/DICOMSamples/DX_Implicit_Little_Interleaved.dcm",
)

function download_dicom(filename; folder = data_folder)
@assert haskey(dicom_samples, filename)
url = dicom_samples[filename]
filepath = joinpath(folder, filename)
if !isfile(filepath)
download(url, filepath)
Downloads.download(url, filepath)
end
return filepath
end
Expand Down Expand Up @@ -233,8 +234,8 @@ end
end

# First, test the isdicom() function
fileDX = download_dicom("DX_Implicit_Little_Interleaved.dcm")
@test DICOM.isdicom(fileDX) == true
fileCT = download_dicom("CT_JPEG70.dcm")
@test DICOM.isdicom(fileCT) == true
@test DICOM.isdicom(notdicomfile) == false

# Second, test if all valid dicom file can be parsed
Expand All @@ -255,3 +256,53 @@ end
@test macroexpand(Main, :(tag"Modality")) === (0x0008, 0x0060)
@test_throws LoadError macroexpand(Main, :(tag"nonsense"))
end

@testset "Test VR in Sequence" begin
# Test auxillary VR passed to nested tags
fileMG = download_dicom("MG_Explicit_Little.dcm")
dcmMG = dcm_parse(fileMG)
# Set value of CodeMeaning for both tags to something arbitrary
codemeaning_vr = Dict{Tuple{UInt16, UInt16}, String}((0x008, 0x104) => "US")
dcmMG[(0x0008, 0x2218)][1][(0x0008, 0x0104)] = 0x0001
dcmMG[(0x0054, 0x0220)][1][(0x0008, 0x0104)] = 0x0002

outMG3 = joinpath(data_folder, "outMG3.dcm")
dcm_write(outMG3, dcmMG; aux_vr = codemeaning_vr)
dcmMG3 = dcm_parse(outMG3)
@test dcmMG3[(0x0008, 0x2218)][1][(0x0008, 0x0104)] == 0x0001
@test dcmMG3[(0x0054, 0x0220)][1][(0x0008, 0x0104)] == 0x0002
end

function write_to_string(writef)
io = IOBuffer()
writef(io)
return String(take!(io))
end

@testset "Padding string values" begin
empty_vr_dict = Dict{Tuple{UInt16, UInt16}, String}()
# Test that UI strings are padded with '\0' to even length
SOPClassUID = write_to_string(io -> DICOM.write_element(io, (0x0008, 0x0016), "1.2.840.10008.5.1.4.1.1.4", true, empty_vr_dict))
@test length(SOPClassUID) % 2 == 0
@test SOPClassUID[end] == '\0'
# Test that SH strings are padded with ' ' to even length
StudyDescription = write_to_string(io -> DICOM.write_element(io, (0x0008, 0x1030), "BestImageEver", true, empty_vr_dict))
@test length(StudyDescription) % 2 == 0
@test StudyDescription[end] == ' '
end

@testset "Writing Sequence" begin
empty_vr_dict = Dict{Tuple{UInt16, UInt16}, String}()
# Setup internal SQ DICOMData
sq_meta = Dict{Tuple{UInt16, UInt16}, Any}([(0x0008, 0x0100) => "UNDEFINED", (0x0008, 0x010b) => 'N', (0x0008, 0x0104) => "UNDEFINED"])
sq_el = DICOM.DICOMData(sq_meta, :little, true, empty_vr_dict)

# Setup external SQ DICOMData
meta = Dict{Tuple{UInt16, UInt16}, Any}((0x0040, 0x0260) => [sq_el])
el = DICOM.DICOMData(meta, :little, true, empty_vr_dict)

PerformedProtocolSequence = write_to_string(io -> DICOM.write_element(io, (0x0040, 0x0260), el[(0x0040, 0x0260)], true, empty_vr_dict))
# Check that the length is (0xffffffff) for undefined length SQ
@test PerformedProtocolSequence[9:12] == "\xff\xff\xff\xff"
@test PerformedProtocolSequence[end-7:end] == "\xfe\xff\xdd\xe0\x00\x00\x00\x00"
end

0 comments on commit 5b98994

Please sign in to comment.