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

Pass aux_vr to SQ elements in dcm_write #88

Merged
merged 6 commits into from
May 23, 2024
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
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
Loading