From 52760dc70db09b51991b688442fbf83744a0be17 Mon Sep 17 00:00:00 2001 From: Adam Brewster Date: Wed, 12 Jul 2017 10:12:53 -0400 Subject: [PATCH 01/12] read big-endian data --- src/DICOM.jl | 71 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 41 insertions(+), 30 deletions(-) diff --git a/src/DICOM.jl b/src/DICOM.jl index 0e5f99c..3658f03 100644 --- a/src/DICOM.jl +++ b/src/DICOM.jl @@ -101,11 +101,11 @@ function undefined_length(st, vr) takebuf_array(data) end -function sequence_item(st, evr, sz) +function sequence_item(st, evr, sz, endian) item = Any[] endpos = position(st) + sz while position(st) < endpos - elt = element(st, evr) + elt = element(st, evr, endian) if isequal(elt.tag, (0xFFFE,0xE00D)) break end @@ -121,19 +121,19 @@ function sequence_item_write(st, evr, item) write(st, UInt16[0xFFFE, 0xE00D, 0x0000, 0x0000]) end -function sequence_parse(st, evr, sz) +function sequence_parse(st, evr, sz, endian) sq = Any[] while sz > 0 - grp = read(st, UInt16) - elt = read(st, UInt16) - itemlen = read(st, UInt32) + grp = order(read(st, UInt16), endian) + elt = order(read(st, UInt16), endian) + itemlen = order(read(st, UInt32), endian) if grp==0xFFFE && elt==0xE0DD return sq end if grp != 0xFFFE || elt != 0xE000 error("dicom: expected item tag in sequence") end - push!(sq, sequence_item(st, evr, itemlen)) + push!(sq, sequence_item(st, evr, itemlen, endian)) sz -= 8 + (itemlen != 0xffffffff) * itemlen end return sq @@ -147,8 +147,7 @@ function sequence_write(st, evr, item) end # always little-endian, "encapsulated" iff sz==0xffffffff -pixeldata_parse(st, sz, vr) = pixeldata_parse(st, sz, vr, false) -function pixeldata_parse(st, sz, vr, dcm) +function pixeldata_parse(st, sz, vr, dcm=false) yr=1 zr=1 if vr=="OB" @@ -249,8 +248,14 @@ end numeric_parse(st, T, sz) = [read(st, T) for i=1:div(sz,sizeof(T))] -element(st, evr) = element(st, evr, false) -function element(st, evr, dcm) +if ENDIAN_BOM == 0x04030201 + order(x, endian) = endian == :little ? x : bswap.(x) +else + order(x, endian) = endian == :big ? x : bswap.(x) +end + + +function element(st, evr, endian=:little, dcm=false) lentype = UInt32 diffvr = false local grp @@ -259,7 +264,11 @@ function element(st, evr, dcm) catch return false end - elt = read(st, UInt16) + if grp == 2 + endian = :little + end + grp = order(grp, endian) + elt = order(read(st, UInt16), endian) gelt = (grp,elt) if evr && !always_implicit(grp,elt) vr = String(read(st, UInt8, 2)) @@ -283,36 +292,36 @@ function element(st, evr, dcm) error("dicom: unknown tag ", gelt) end - sz = read(st,lentype) + sz = order(read(st,lentype), endian) data = vr=="ST" || vr=="LT" || vr=="UT" ? string(read(st, UInt8, sz)) : sz==0 || vr=="XX" ? Any[] : - vr == "SQ" ? sequence_parse(st, evr, sz) : + vr == "SQ" ? sequence_parse(st, evr, sz, endian) : gelt == (0x7FE0,0x0010) ? pixeldata_parse(st, sz, vr, dcm) : sz == 0xffffffff ? undefined_length(st, vr) : - vr == "FL" ? numeric_parse(st, Float32, sz) : - vr == "FD" ? numeric_parse(st, Float64, sz) : - vr == "SL" ? numeric_parse(st, Int32 , sz) : - vr == "SS" ? numeric_parse(st, Int16 , sz) : - vr == "UL" ? numeric_parse(st, UInt32 , sz) : - vr == "US" ? numeric_parse(st, UInt16 , sz) : + vr == "FL" ? order(numeric_parse(st, Float32, sz), endian) : + vr == "FD" ? order(numeric_parse(st, Float64, sz), endian) : + vr == "SL" ? order(numeric_parse(st, Int32 , sz), endian) : + vr == "SS" ? order(numeric_parse(st, Int16 , sz), endian) : + vr == "UL" ? order(numeric_parse(st, UInt32 , sz), endian) : + vr == "US" ? order(numeric_parse(st, UInt16 , sz), endian) : - vr == "OB" ? read(st, UInt8 , sz) : - vr == "OF" ? read(st, Float32, div(sz,4)) : - vr == "OW" ? read(st, UInt16 , div(sz,2)) : + vr == "OB" ? order(read(st, UInt8 , sz),endian) : + vr == "OF" ? order(read(st, Float32, div(sz,4)), endian) : + vr == "OW" ? order(read(st, UInt16 , div(sz,2)), endian) : - vr == "AT" ? [ read(st,UInt16,2) for n=1:div(sz,4) ] : + vr == "AT" ? [ order(read(st,UInt16,2), endian) for n=1:div(sz,4) ] : vr == "AS" ? String(read(st,UInt8,4)) : - vr == "DS" ? map(x->parse(Float64,x), string_parse(st, sz, 16, false)) : - vr == "IS" ? map(x->parse(Int,x), string_parse(st, sz, 12, false)) : + vr == "DS" ? map(x->x=="" ? 0. : parse(Float64,x), string_parse(st, sz, 16, false)) : + vr == "IS" ? map(x->x=="" ? 0 : parse(Int,x), string_parse(st, sz, 12, false)) : vr == "AE" ? string_parse(st, sz, 16, false) : vr == "CS" ? string_parse(st, sz, 16, false) : @@ -395,8 +404,10 @@ function dcm_parse(fn) evr = sig in VR_names skip(st, -6) data = Any[] + endian = :little while true - fld = element(st, evr, data) + fld = element(st, evr, endian, data) + if is(fld,false) return data else @@ -404,13 +415,13 @@ function dcm_parse(fn) end # look for transfer syntax UID if fld.tag == (0x0002,0x0010) - fld = get(meta_uids, fld.data[1], false) + fld = get(meta_uids, fld.data[1][1], false) if !is(fld,false) evr = fld[2] if fld[1] - # todo: set byte order to big + endian = :big else - # todo: set byte order to little + endian = :little end end end From 37811e75a23a4671f2d029e7addb92c4997ddda4 Mon Sep 17 00:00:00 2001 From: Adam Brewster Date: Wed, 12 Jul 2017 10:13:34 -0400 Subject: [PATCH 02/12] limit string length --- src/DICOM.jl | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/DICOM.jl b/src/DICOM.jl index 3658f03..fa35b91 100644 --- a/src/DICOM.jl +++ b/src/DICOM.jl @@ -215,13 +215,15 @@ function pixeldata_write(st, evr, el) end end -function skip_spaces(st) - while true +function skip_spaces(st, limit) + while limit > 0 c = read(st,Char) + limit -= 1 if c != ' ' return c end end + return '\0' end function string_parse(st, sz, maxlen, spaces) @@ -229,7 +231,7 @@ function string_parse(st, sz, maxlen, spaces) data = [ "" ] first = true while position(st) < endpos - c = !first||spaces ? read(st,Char) : skip_spaces(st) + c = !first||spaces ? read(st,Char) : skip_spaces(st, sz) # ignore maxlen? if c == '\\' push!(data, "") first = true From 6428fa29add9e9b566bb5c080d0bae3ba40a8c85 Mon Sep 17 00:00:00 2001 From: Zaki A Date: Mon, 21 Oct 2019 01:52:59 -0400 Subject: [PATCH 03/12] Refactor parsing code into smaller functions --- src/DICOM.jl | 852 ++++++++++++++++++++++++++------------------------- 1 file changed, 427 insertions(+), 425 deletions(-) diff --git a/src/DICOM.jl b/src/DICOM.jl index 67b0b4c..dd691ce 100644 --- a/src/DICOM.jl +++ b/src/DICOM.jl @@ -74,377 +74,403 @@ function lookup(d::Dict{Tuple{UInt16,UInt16},Any}, fieldnameString::String) return(get(d, fieldname_dict[fieldnameString], nothing)) end +if ENDIAN_BOM == 0x04030201 + order(x, endian) = endian == :little ? x : bswap.(x) +else + order(x, endian) = endian == :big ? x : bswap.(x) +end + always_implicit(grp, elt) = (grp == 0xFFFE && (elt == 0xE0DD||elt == 0xE000|| elt == 0xE00D)) +""" + dcm_parse(fn::AbstractString) -dcm_store(st::IO, gelt::Tuple{UInt16,UInt16}, writef::Function) = dcm_store(st, gelt, writef, emptyVR) -function dcm_store(st::IO, gelt::Tuple{UInt16,UInt16}, writef::Function, vr::String) - lentype = UInt32 - write(st, UInt16(gelt[1])) # Grp - write(st, UInt16(gelt[2])) # Elt - if vr !== emptyVR - write(st, vr) - if vr in ("OB", "OW", "OF", "SQ", "UT", "UN") - write(st, UInt16(0)) - else - lentype = UInt16 - end - end - # Write data first, then calculate length, then go back to write length - p = position(st) - write(st, zero(lentype)) # Placeholder for the data length - writef(st) - endp = position(st) - # Remove placeholder's length, either 2 (UInt16) or 4 (UInt32) steps (1 step = 8 bits) - if lentype == UInt32 - sz = endp-p-4 - else - sz = endp-p-2 - end - szWasOdd = isodd(sz) # If length is odd, round up - UInt8(0) will be written at end - if szWasOdd - sz+=1 - end - seek(st, p) - write(st, convert(lentype, max(0,sz))) - seek(st, endp) - if szWasOdd - write(st, UInt8(0)) - end +Reads file fn and returns a Dict +""" +function dcm_parse(fn::AbstractString; kwargs...) + st = open(fn) + dcm = dcm_parse(st; kwargs...) + close(st) + dcm end -function undefined_length(st, vr) - data = IOBuffer() - w1 = w2 = 0 - while true - # read until 0xFFFE 0xE0DD - w1 = w2 - w2 = read(st, UInt16) - if w1 == 0xFFFE - if w2 == 0xE0DD - break - end - write(data, w1) - end - if w2 != 0xFFFE - write(data, w2) - end - end - skip(st, 4) - take!(data) -end +""" + dcm_parse(st::IO) -<<<<<<< HEAD -function sequence_item(st::IO, evr, sz) -======= - -function sequence_item(st::IOStream, evr, sz,endian) ->>>>>>> 5f0a7db7c88445881eeab22fda25863c32a41292 - item = Dict{Tuple{UInt16,UInt16},Any}() - endpos = position(st) + sz - while position(st) < endpos - (gelt, data, vr) = element(st, evr,endian) - if isequal(gelt, (0xFFFE,0xE00D)) - break - end - item[gelt] = data - end - return item +Reads IO st and returns a Dict +""" +function dcm_parse(st::IO; return_vr=false, header=true, max_group=0xffff, aux_vr=Dict{Tuple{UInt16,UInt16},String}()) + if header + check_header(st) + end + dcm = read_preamble(st) + is_explicit, endian = determine_explicitness_and_endianness(dcm) + file_properties = (is_explicit=is_explicit, endian=endian, aux_vr=aux_vr) + (dcm, vr) = read_body(st, dcm, file_properties; max_group=max_group) + if return_vr + return dcm, vr + else + return dcm + end end -function sequence_item_write(st::IO, evr::Bool, items::Dict{Tuple{UInt16,UInt16},Any}) - for gelt in sort(collect(keys(items))) - element_write(st, evr, gelt, items[gelt]) - end - write(st, UInt16[0xFFFE, 0xE00D, 0x0000, 0x0000]) +function check_header(st) + # First 128 bytes are preamble - should be skipped + skip(st, 128) + # "DICM" identifier must be after preamble + sig = String(read!(st,Array{UInt8}(undef, 4))) + if sig != "DICM" + error("dicom: invalid file header") + end + return end -function sequence_parse(st, evr, sz,endian) - sq = Array{Dict{Tuple{UInt16,UInt16},Any},1}() - while sz > 0 - grp = order(read(st, UInt16), endian) - elt = order(read(st, UInt16), endian) - itemlen = order(read(st, UInt32), endian) - if grp==0xFFFE && elt==0xE0DD - return sq - end - if grp != 0xFFFE || elt != 0xE000 - error("dicom: expected item tag in sequence") - end - push!(sq, sequence_item(st, evr, itemlen, endian)) - sz -= 8 + (itemlen != 0xffffffff) * itemlen - end - return sq +# Pre-ample is always explicit VR / little endian +function read_preamble(st::IO) + dcm = Dict{Tuple{UInt16,UInt16},Any}() + is_explicit = true + endian = :little + while true + pos = position(st) + (gelt, data, vr) = read_element(st, (is_explicit, endian, emptyDcmDict)) + grp = gelt[1] + if grp > 0x0002 || gelt == emptyTag + seek(st, pos) + return dcm + else + dcm[gelt] = data + end + end + error("Unexpected break from loop while reading preamble") end -function sequence_write(st::IO, evr::Bool, items::Array{Dict{Tuple{UInt16,UInt16},Any},1}) - for subitem in items - if length(subitem) > 0 - dcm_store(st, (0xFFFE,0xE000), s->sequence_item_write(s, evr, subitem)) - end - end - write(st, UInt16[0xFFFE, 0xE0DD, 0x0000, 0x0000]) +function determine_explicitness_and_endianness(dcm) + # Default is implicit_vr & little-endian + if !haskey(dcm, (0x0002, 0x0010)) + return (false, :little) + end + metaInfo = get(meta_uids, dcm[(0x0002,0x0010)], (false, true)) + explicitness = metaInfo[2] + if metaInfo[1] + endianness = :big + else + endianness = :little + end + return explicitness, endianness end -# always little-endian, "encapsulated" iff sz==0xffffffff -function pixeldata_parse(st::IO, sz, vr::String, dcm=emptyDcmDict) - # (0x0028,0x0103) defines Pixel Representation - isSigned = false - f = get(dcm, (0x0028,0x0103), nothing) - if f !== nothing - # Data is signed if f==1 - isSigned = f == 1 - end - # (0x0028,0x0100) defines Bits Allocated - bitType = 16 - f = get(dcm, (0x0028,0x0100), nothing) - if f !== nothing - bitType = Int(f) - else - f = get(dcm, (0x0028,0x0101), nothing) - bitType = f !== nothing ? Int(f) : - vr == "OB" ? 8 : 16 - end - if bitType == 8 - dtype = isSigned ? Int8 : UInt8 - else - dtype = isSigned ? Int16 : UInt16 - end - yr=1 - zr=1 - # (0028,0010) defines number of rows - f = get(dcm, (0x0028,0x0010), nothing) - if f !== nothing - xr = Int(f) - end - # (0028,0011) defines number of columns - f = get(dcm, (0x0028,0x0011), nothing) - if f !== nothing - yr = Int(f) - end - # (0028,0012) defines number of planes - f = get(dcm, (0x0028,0x0012), nothing) - if f !== nothing - zr = Int(f) - end - # (0028,0008) defines number of frames - f = get(dcm, (0x0028,0x0008), nothing) - if f !== nothing - zr *= Int(f) - end - if sz != 0xffffffff - data = - zr > 1 ? Array{dtype}(undef, xr, yr, zr) : Array{dtype}(undef, xr, yr) - read!(st, data) - else - # start with Basic Offset Table Item - data = Array{Any,1}(element(st, false)[2]) - while true - grp = read(st, UInt16) - elt = read(st, UInt16) - xr = read(st, UInt32) - if grp == 0xFFFE && elt == 0xE0DD - return data - end - if grp != 0xFFFE || elt != 0xE000 - error("dicom: expected item tag in encapsulated pixel data") - end - if dtype === UInt16; xr = div(xr,2); end - push!(data, read!(st, Array{dtype}(undef, xr))) - end - end - return data +function read_body(st, dcm, props; max_group) + vrs = Dict{Tuple{UInt16,UInt16},String}() + while true + (gelt, data, vr) = read_element(st, props, dcm) + if gelt == emptyTag || gelt[1] > max_group + break + else + dcm[gelt] = data + vrs[gelt] = vr + end + end + return dcm, vrs end -function pixeldata_write(st, evr, d) - # if length(el) > 1 - # error("dicom: compression not supported") - # end - nt = eltype(d) - vr = nt === UInt8 || nt === Int8 ? "OB" : - nt === UInt16 || nt === Int16 ? "OW" : - nt === Float32 ? "OF" : - error("dicom: unsupported pixel format") - if evr !== false - dcm_store(st, (0x7FE0,0x0010), s->write(s,d), vr) - elseif vr != "OW" - error("dicom: implicit VR only supports 16-bit pixels") - else - dcm_store(st, (0x7FE0,0x0010), s->write(s,d)) - end +function read_element(st::IO, props, dcm=emptyDcmDict) + (is_explicit, endian, aux_vr) = props + local grp + try + grp = read_group_tag(st, endian) + catch + return(emptyTag,0,emptyVR) + end + elt = read_element_tag(st, endian) + gelt = (grp, elt) + vr, lentype = determine_vr_and_lentype(st, gelt, is_explicit, aux_vr) + sz = read_element_size(st, lentype, endian) + # Empty VR can be supplied in aux_vr to skip an element + if isempty(vr) + sz = isodd(sz) ? sz+1 : sz + skip(st,sz) + return(read_element(st::IO, props, dcm)) + end + + data = + vr=="ST" || vr=="LT" || vr=="UT" || vr=="AS" ? String(read!(st, Array{UInt8}(undef, sz))) : + + sz==0 || vr=="XX" ? Any[] : + + vr == "SQ" ? sequence_parse(st, sz, props) : + + gelt == (0x7FE0,0x0010) ? pixeldata_parse(st, sz, vr, dcm, endian) : + + sz == 0xffffffff ? undefined_length(st, vr) : + + vr == "FL" ? numeric_parse(st, Float32, sz, endian) : + vr == "FD" ? numeric_parse(st, Float64, sz, endian) : + vr == "SL" ? numeric_parse(st, Int32 , sz, endian) : + vr == "SS" ? numeric_parse(st, Int16 , sz, endian) : + vr == "UL" ? numeric_parse(st, UInt32 , sz, endian) : + vr == "US" ? numeric_parse(st, UInt16 , sz, endian) : + + vr == "OB" ? order(read!(st, Array{UInt8}(undef, sz)), endian) : + vr == "OF" ? order(read!(st, Array{Float32}(undef, div(sz,4))), endian) : + vr == "OW" ? order(read!(st, Array{UInt16}(undef, div(sz,2))), endian) : + + vr == "AT" ? [ order(read!(st, Array{UInt16}(undef, 2)), endian) for n=1:div(sz,4) ] : + + vr == "DS" ? map(x->x == "" ? 0.0 : parse(Float64,x), string_parse(st, sz, 16, false)) : + vr == "IS" ? map(x->x == "" ? 0 : parse(Int,x), string_parse(st, sz, 12, false)) : + + vr == "AE" ? string_parse(st, sz, 16, false) : + vr == "CS" ? string_parse(st, sz, 16, false) : + vr == "SH" ? string_parse(st, sz, 16, false) : + vr == "LO" ? string_parse(st, sz, 64, false) : + vr == "UI" ? string_parse(st, sz, 64, false) : + vr == "PN" ? string_parse(st, sz, 64, true) : + + vr == "DA" ? string_parse(st, sz, 10, true) : + vr == "DT" ? string_parse(st, sz, 26, false) : + vr == "TM" ? string_parse(st, sz, 16, false) : + order(read!(st, Array{UInt8}(undef, sz)), endian) + + if isodd(sz) && sz != 0xffffffff + skip(st, 1) + end + + # For convenience, get rid of array if it is just acting as a container + # Exception is "SQ", where array is part of structure + if length(data) == 1 && vr != "SQ" + data = data[1] + # Sometimes it is necessary to go one level deeper + if length(data) == 1 + data = data[1] + end + end + + return(gelt, data, vr) end -function skip_spaces(st, endpos) - while true - c = read(st,Char) - if c != ' ' || position(st) == endpos - return c - end - end - return '\0' +read_group_tag(st, endian) = order(read(st, UInt16), endian) +read_element_tag = read_group_tag +read_element_size(st, lentype, endian) = order(read(st,lentype), endian) + +function determine_vr_and_lentype(st, gelt, is_explicit, aux_vr) + (grp, elt) = gelt + lentype = UInt32 + if is_explicit && !always_implicit(grp, elt) + vr = String(read!(st, Array{UInt8}(undef, 2))) + if vr in ("OB", "OW", "OF", "SQ", "UT", "UN") + skip(st, 2) + else + lentype = UInt16 + end + diffvr = !isequal(vr, lookup_vr(gelt)) + else + vr = elt == 0x0000 ? "UL" : lookup_vr(gelt) + end + if isodd(grp) && grp > 0x0008 && 0x0010 <= elt <+ 0x00FF + # Private creator + vr = "LO" + elseif isodd(grp) && grp > 0x0008 + # Assume private + vr = "UN" + end + if haskey(aux_vr, gelt) + vr = aux_vr[gelt] + end + if vr === emptyVR + if haskey(aux_vr, (0x0000,0x0000)) + vr = aux_vr[(0x0000,0x0000)] + elseif !haskey(aux_vr, gelt) + error("dicom: unknown tag ", gelt) + end + end + return vr, lentype end +numeric_parse(st::IO, T::DataType, sz, endian) = order(T[read(st, T) for i=1:div(sz,sizeof(T))], endian) + function string_parse(st, sz, maxlen, spaces) - endpos = position(st)+sz - data = [ "" ] - first = true - while position(st) < endpos - c = !first||spaces ? read(st,Char) : skip_spaces(st, endpos) - if c == '\\' - push!(data, "") - first = true - elseif c == '\0' - break - else - data[end] = string(data[end],c) # TODO: inefficient - first = false - end - end - if !spaces - return map(rstrip,data) - end - return data + endpos = position(st)+sz + data = [ "" ] + first = true + while position(st) < endpos + c = !first||spaces ? read(st,Char) : skip_spaces(st, endpos) + if c == '\\' + push!(data, "") + first = true + elseif c == '\0' + break + else + data[end] = string(data[end],c) # TODO: inefficient + first = false + end + end + if !spaces + return map(rstrip,data) + end + return data end -numeric_parse(st::IO, T::DataType, sz) = T[read(st, T) for i=1:div(sz,sizeof(T))] +function skip_spaces(st, endpos) + while true + c = read(st,Char) + if c != ' ' || position(st) == endpos + return c + end + end +end -<<<<<<< HEAD -function element(st::IO, evr::Bool, dcm=emptyDcmDict, dVR=Dict{Tuple{UInt16,UInt16},String}()) -======= +function sequence_parse(st, sz, props) + (is_explicit, endian, aux_vr) = props + sq = Array{Dict{Tuple{UInt16,UInt16},Any},1}() + while sz > 0 + grp = read_group_tag(st, endian) + elt = read_element_tag(st, endian) + itemlen = read_element_size(st, UInt32, endian) + if grp==0xFFFE && elt==0xE0DD + return sq + end + if grp != 0xFFFE || elt != 0xE000 + error("dicom: expected item tag in sequence") + end + push!(sq, sequence_item(st, itemlen, props)) + sz -= 8 + (itemlen != 0xffffffff) * itemlen + end + return sq +end -if ENDIAN_BOM == 0x04030201 - order(x, endian) = endian == :little ? x : bswap.(x) -else - order(x, endian) = endian == :big ? x : bswap.(x) +function sequence_item(st::IO, sz, props) + item = Dict{Tuple{UInt16,UInt16},Any}() + endpos = position(st) + sz + while position(st) < endpos + (gelt, data, vr) = read_element(st, props) + if isequal(gelt, (0xFFFE,0xE00D)) + break + end + item[gelt] = data + end + return item end -function element(st::IOStream, evr::Bool, endian=:little, dcm=emptyDcmDict, dVR=Dict{Tuple{UInt16,UInt16},String}()) ->>>>>>> 5f0a7db7c88445881eeab22fda25863c32a41292 - lentype = UInt32 - diffvr = false - local grp - try - grp = read(st, UInt16) - catch - return(emptyTag,0,emptyVR) - end - if grp <= 0x0002 -<<<<<<< HEAD -======= - endian = :little ->>>>>>> 5f0a7db7c88445881eeab22fda25863c32a41292 - evr = true - end - grp = order(grp, endian) - elt = order(read(st, UInt16), endian) - gelt = (grp,elt) - if evr && !always_implicit(grp,elt) - vr = String(read!(st, Array{UInt8}(undef, 2))) - if vr in ("OB", "OW", "OF", "SQ", "UT", "UN") - skip(st, 2) - else - lentype = UInt16 +# always little-endian, "encapsulated" iff sz==0xffffffff +function pixeldata_parse(st::IO, sz, vr::String, dcm, endian) + # (0x0028,0x0103) defines Pixel Representation + isSigned = false + f = get(dcm, (0x0028,0x0103), nothing) + if f !== nothing + # Data is signed if f==1 + isSigned = f == 1 + end + # (0x0028,0x0100) defines Bits Allocated + bitType = 16 + f = get(dcm, (0x0028,0x0100), nothing) + if f !== nothing + bitType = Int(f) + else + f = get(dcm, (0x0028,0x0101), nothing) + bitType = f !== nothing ? Int(f) : + vr == "OB" ? 8 : 16 + end + if bitType == 8 + dtype = isSigned ? Int8 : UInt8 + else + dtype = isSigned ? Int16 : UInt16 + end + + yr=1 + zr=1 + # (0028,0010) defines number of rows + f = get(dcm, (0x0028,0x0010), nothing) + if f !== nothing + xr = Int(f) + end + # (0028,0011) defines number of columns + f = get(dcm, (0x0028,0x0011), nothing) + if f !== nothing + yr = Int(f) + end + # (0028,0012) defines number of planes + f = get(dcm, (0x0028,0x0012), nothing) + if f !== nothing + zr = Int(f) + end + # (0028,0008) defines number of frames + f = get(dcm, (0x0028,0x0008), nothing) + if f !== nothing + zr *= Int(f) + end + if sz != 0xffffffff + data = + zr > 1 ? Array{dtype}(undef, xr, yr, zr) : Array{dtype}(undef, xr, yr) + read!(st, data) + else + # start with Basic Offset Table Item + data = Array{Any,1}(element(st, false)[2]) + while true + grp = read_group_tag(st, endian) + elt = read_element_tag(st, endian) + xr = read_element_size(st, UInt32, endian) + if grp == 0xFFFE && elt == 0xE0DD + return data + end + if grp != 0xFFFE || elt != 0xE000 + error("dicom: expected item tag in encapsulated pixel data") + end + if dtype === UInt16; xr = div(xr,2); end + push!(data, read!(st, Array{dtype}(undef, xr))) + end + end + return order.(data, endian) +end + +function undefined_length(st, vr) + data = IOBuffer() + w1 = w2 = 0 + while true + # read until 0xFFFE 0xE0DD + w1 = w2 + w2 = read(st, UInt16) + if w1 == 0xFFFE + if w2 == 0xE0DD + break + end + write(data, w1) end - diffvr = !isequal(vr, lookup_vr(gelt)) - else - vr = elt == 0x0000 ? "UL" : lookup_vr(gelt) - end - if isodd(grp) && grp > 0x0008 && 0x0010 <= elt <+ 0x00FF - # Private creator - vr = "LO" - elseif isodd(grp) && grp > 0x0008 - # Assume private - vr = "UN" - end - if haskey(dVR, gelt) - vr = dVR[gelt] - end - if vr === emptyVR - if haskey(dVR, (0x0000,0x0000)) - vr = dVR[(0x0000,0x0000)] - elseif !haskey(dVR, gelt) - error("dicom: unknown tag ", gelt) + if w2 != 0xFFFE + write(data, w2) end end + skip(st, 4) + take!(data) +end - sz = order(read(st,lentype), endian) - - # Empty VR can be supplied in dVR to skip an element - if vr == "" - sz = isodd(sz) ? sz+1 : sz - skip(st,sz) - return(element(st,evr,endian,dcm,dVR)) - end - - data = - vr=="ST" || vr=="LT" || vr=="UT" || vr=="AS" ? String(read!(st, Array{UInt8}(undef, sz))) : - - sz==0 || vr=="XX" ? Any[] : - - vr == "SQ" ? sequence_parse(st, evr, sz, endian) : - - gelt == (0x7FE0,0x0010) ? pixeldata_parse(st, sz, vr, dcm) : - - sz == 0xffffffff ? undefined_length(st, vr) : - - vr == "FL" ? order(numeric_parse(st, Float32, sz), endian) : - vr == "FD" ? order(numeric_parse(st, Float64, sz), endian) : - vr == "SL" ? order(numeric_parse(st, Int32 , sz), endian) : - vr == "SS" ? order(numeric_parse(st, Int16 , sz), endian) : - vr == "UL" ? order(numeric_parse(st, UInt32 , sz), endian) : - vr == "US" ? order(numeric_parse(st, UInt16 , sz), endian) : - - vr == "OB" ? order(read(st, UInt8 , sz),endian) : - vr == "OF" ? order(read(st, Float32, div(sz,4)), endian) : - vr == "OW" ? order(read(st, UInt16 , div(sz,2)), endian) : - - vr == "AT" ? [ order(read(st,UInt16,2), endian) for n=1:div(sz,4) ] : - - vr == "AT" ? [ read!(st, Array{UInt16}(undef, 2)) for n=1:div(sz,4) ] : - - vr == "DS" ? map(x->x=="" ? 0. : parse(Float64,x), string_parse(st, sz, 16, false)) : - vr == "IS" ? map(x->x=="" ? 0 : parse(Int,x), string_parse(st, sz, 12, false)) : - - vr == "AE" ? string_parse(st, sz, 16, false) : - vr == "CS" ? string_parse(st, sz, 16, false) : - vr == "SH" ? string_parse(st, sz, 16, false) : - vr == "LO" ? string_parse(st, sz, 64, false) : - vr == "UI" ? string_parse(st, sz, 64, false) : - vr == "PN" ? string_parse(st, sz, 64, true) : - vr == "DA" ? string_parse(st, sz, 10, true) : - vr == "DT" ? string_parse(st, sz, 26, false) : - vr == "TM" ? string_parse(st, sz, 16, false) : - read!(st, Array{UInt8}(undef, sz)) +function dcm_write(fn::String, d::Dict{Tuple{UInt16,UInt16},Any}; kwargs...) + st = open(fn,"w+") + dcm_write(st, d; kwargs...) + close(st) + return fn +end - if isodd(sz) && sz != 0xffffffff - skip(st, 1) +function dcm_write(st::IO, dcm::Dict{Tuple{UInt16,UInt16},Any}; header=true, aux_vr=Dict{Tuple{UInt16,UInt16},String}()) + if header + write(st, zeros(UInt8, 128)) + write(st, "DICM") end + (is_explicit, endian) = determine_explicitness_and_endianness(dcm) - # For convenience, get rid of array if it is just acting as a container - # Exception is "SQ", where array is part of structure - if length(data) == 1 && vr != "SQ" - data = data[1] - if length(data) == 1 - data = data[1] - end + for gelt in sort(collect(keys(dcm))) + write_element(st, gelt, dcm[gelt], is_explicit, aux_vr) end - - # Return vr by default - return(gelt, data, vr) + return end -# todo: support maxlen -string_write(vals::Array{SubString{String}}, maxlen) = string_write(convert(Array{String}, vals), maxlen) -string_write(vals::SubString{String}, maxlen) = string_write(convert(String, vals), maxlen) -string_write(vals::Tuple{String, String}, maxlen) = string_write(collect(vals), maxlen) -string_write(vals::Char, maxlen) = string_write(string(vals), maxlen) -string_write(vals::String, maxlen) = string_write([vals], maxlen) -string_write(vals::Array{String,1}, maxlen) = join(vals, '\\') - -element_write(st::IO, evr::Bool, gelt::Tuple{UInt16,UInt16}, data::Any) = element_write(st,evr,gelt,data,lookup_vr(gelt)) -function element_write(st::IO, evr::Bool, gelt::Tuple{UInt16,UInt16}, data::Any, vr::String) +function write_element(st::IO, gelt::Tuple{UInt16,UInt16}, data, is_explicit, aux_vr) + if haskey(aux_vr, gelt) + vr = aux_vr[gelt] + else + vr = lookup_vr(gelt) + end if vr === emptyVR # Element tags ending in 0x0000 are not included in dcm_dicm.jl, their vr is UL if gelt[2] == 0x0000 @@ -460,17 +486,17 @@ function element_write(st::IO, evr::Bool, gelt::Tuple{UInt16,UInt16}, data::Any, end end if gelt == (0x7FE0, 0x0010) - return pixeldata_write(st, evr, data) + return pixeldata_write(st, data, is_explicit) end if vr == "SQ" - vr = evr ? vr : emptyVR + vr = is_explicit ? vr : emptyVR return dcm_store(st, gelt, - s->sequence_write(s, evr, data), vr) + s->sequence_write(s, data, is_explicit), vr) end - # Pack data into array container. This is to undo "data = data[1]" from element(). - if !isa(data,Array) && vr in ("FL","FD","SL","SS","UL","US") + # Pack data into array container. This is to undo "data = data[1]" from read_element(). + if !isa(data, Array) && vr in ("FL","FD","SL","SS","UL","US") data = [data] end @@ -489,112 +515,88 @@ function element_write(st::IO, evr::Bool, gelt::Tuple{UInt16,UInt16}, data::Any, vr in ("DS","IS") ? string_write(map(string,data), 0) : data - if evr === false && gelt[1]>0x0002 + if !is_explicit && gelt[1]>0x0002 vr = emptyVR end dcm_store(st, gelt, s->write(s, data), vr) end -""" - dcm_parse(fn::AbstractString) - -Reads file fn and returns a Dict -""" -function dcm_parse(fn::AbstractString, giveVR=false; header=true, maxGrp=0xffff, dVR=Dict{Tuple{UInt16,UInt16},String}()) - st = open(fn) - dcm = dcm_parse(st, giveVR; header=header, maxGrp=maxGrp, dVR=dVR) - close(st) - dcm -end - -""" - dcm_parse(st::IO) +string_write(vals::Array{SubString{String}}, maxlen) = string_write(convert(Array{String}, vals), maxlen) +string_write(vals::SubString{String}, maxlen) = string_write(convert(String, vals), maxlen) +string_write(vals::Tuple{String, String}, maxlen) = string_write(collect(vals), maxlen) +string_write(vals::Char, maxlen) = string_write(string(vals), maxlen) +string_write(vals::String, maxlen) = string_write([vals], maxlen) +string_write(vals::Array{String,1}, maxlen) = join(vals, '\\') -Reads IO st and returns a Dict -""" -function dcm_parse(st::IO, giveVR=false; header=true, maxGrp=0xffff, dVR=Dict{Tuple{UInt16,UInt16},String}()) - if header - # First 128 bytes are preamble - should be skipped - skip(st, 128) - # "DICM" identifier must be after preamble - sig = String(read!(st,Array{UInt8}(undef, 4))) - if sig != "DICM" - error("dicom: invalid file header") - # seek(st, 0) - end - end - # a bit of a hack to detect explicit VR. seek past the first tag, - # and check to see if a valid VR name is there - skip(st, 4) - sig = String(read!(st,Array{UInt8}(undef, 2))) - evr = sig in VR_names - skip(st, -6) - dcm = Dict{Tuple{UInt16,UInt16},Any}() - if giveVR - dcmVR = Dict{Tuple{UInt16,UInt16},String}() - end - endian = :little - while true - (gelt, data, vr) = element(st, evr, endian, dcm, dVR) # element(st, evr, dcm, dVR) - if gelt === emptyTag || gelt[1] > maxGrp - break +dcm_store(st::IO, gelt::Tuple{UInt16,UInt16}, writef::Function) = dcm_store(st, gelt, writef, emptyVR) +function dcm_store(st::IO, gelt::Tuple{UInt16,UInt16}, writef::Function, vr::String) + lentype = UInt32 + write(st, UInt16(gelt[1])) # Grp + write(st, UInt16(gelt[2])) # Elt + if vr !== emptyVR + write(st, vr) + if vr in ("OB", "OW", "OF", "SQ", "UT", "UN") + write(st, UInt16(0)) else - dcm[gelt] = data - if giveVR - dcmVR[gelt] = vr - end - end - # look for transfer syntax UID - if gelt == (0x0002,0x0010) - # Default is endian=little, explicitVR=true - metaInfo = get(meta_uids, data, (false, true)) - evr = metaInfo[2] - if metaInfo[1] - endian = :big - else - endian = :little - end + lentype = UInt16 end end - if giveVR - return(dcm,dcmVR) + # Write data first, then calculate length, then go back to write length + p = position(st) + write(st, zero(lentype)) # Placeholder for the data length + writef(st) + endp = position(st) + # Remove placeholder's length, either 2 (UInt16) or 4 (UInt32) steps (1 step = 8 bits) + if lentype == UInt32 + sz = endp-p-4 else - return(dcm) + sz = endp-p-2 + end + szWasOdd = isodd(sz) # If length is odd, round up - UInt8(0) will be written at end + if szWasOdd + sz+=1 + end + seek(st, p) + write(st, convert(lentype, max(0,sz))) + seek(st, endp) + if szWasOdd + write(st, UInt8(0)) end end -dcm_write(fn::String, d::Dict{Tuple{UInt16,UInt16},Any}, dVR=Dict{Tuple{UInt16,UInt16},String}()) = dcm_write(open(fn,"w+"),d,dVR) -function dcm_write(st::IO, d::Dict{Tuple{UInt16,UInt16},Any}, dVR=Dict{Tuple{UInt16,UInt16},String}()) - write(st, zeros(UInt8, 128)) - write(st, "DICM") - # If no dictionary containing VRs is provided, then assume implicit VR - at first - evr = !isempty(dVR) - if !haskey(d,(0x0002,0x0010)) - # Insert UID for our transfer syntax, if it doesn't exist - if evr - element_write(st, evr, (0x0002,0x0010), "1.2.840.10008.1.2.1", "UI") - else - element_write(st, evr, (0x0002,0x0010), "1.2.840.10008.1.2", "UI") +function sequence_write(st::IO, items::Array{Dict{Tuple{UInt16,UInt16},Any},1}, evr) + for subitem in items + if length(subitem) > 0 + dcm_store(st, (0xFFFE,0xE000), s->sequence_item_write(s, subitem, evr)) end - else - # Otherwise, use existing transfer UID, and overwrite evr accordingly - metaInfo = get(meta_uids, d[(0x0002,0x0010)], (false, true)) - evr = metaInfo[2] end - # dVR is only used if it isn't empty and evr=true - if evr && !isempty(dVR) - for gelt in sort(collect(keys(d))) - # dVR only needs to contain keys for cases where lookup_vr() fails - haskey(dVR, gelt) ? element_write(st, evr, gelt, d[gelt], dVR[gelt]) : - element_write(st, evr, gelt, d[gelt]) - end + write(st, UInt16[0xFFFE, 0xE0DD, 0x0000, 0x0000]) +end + +function sequence_item_write(st::IO, items::Dict{Tuple{UInt16,UInt16},Any}, evr) + for gelt in sort(collect(keys(items))) + write_element(st, gelt, items[gelt], evr, emptyDcmDict) + end + write(st, UInt16[0xFFFE, 0xE00D, 0x0000, 0x0000]) +end + +function pixeldata_write(st, d, evr) + # if length(el) > 1 + # error("dicom: compression not supported") + # end + nt = eltype(d) + vr = nt === UInt8 || nt === Int8 ? "OB" : + nt === UInt16 || nt === Int16 ? "OW" : + nt === Float32 ? "OF" : + error("dicom: unsupported pixel format") + if evr !== false + dcm_store(st, (0x7FE0,0x0010), s->write(s,d), vr) + elseif vr != "OW" + error("dicom: implicit VR only supports 16-bit pixels") else - for gelt in sort(collect(keys(d))) - element_write(st, evr, gelt, d[gelt]) - end + dcm_store(st, (0x7FE0,0x0010), s->write(s,d)) end - close(st) end end From eedc6c6eb673acbbcaf03a18ca1eec46803bed78 Mon Sep 17 00:00:00 2001 From: Zaki A Date: Mon, 21 Oct 2019 01:56:52 -0400 Subject: [PATCH 04/12] Use snake_case instead of camelCase for variable names --- src/DICOM.jl | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/DICOM.jl b/src/DICOM.jl index dd691ce..1a005b1 100644 --- a/src/DICOM.jl +++ b/src/DICOM.jl @@ -35,10 +35,10 @@ include("dcm_dict.jl") # const dcm_dict = ... fieldname_dict = Dict(val[1] => key for (key, val) in dcm_dict) # These "empty" values are used internally. They are returned if a search fails. -const emptyVR = "" # Can be any VR that doesn't exist -const emptyVR_lookup = ["", emptyVR, ""] # Used in lookup_vr as failure state -const emptyTag = (0x0000,0x0000) -const emptyDcmDict = Dict(DICOM.emptyTag => nothing) +const empty_vr = "" # Can be any VR that doesn't exist +const empty_vr_lookup = ["", empty_vr, ""] # Used in lookup_vr as failure state +const empty_tag = (0x0000,0x0000) +const empty_dcm_dict = Dict(DICOM.empty_tag => nothing) const VR_names = [ "AE","AS","AT","CS","DA","DS","DT","FL","FD","IS","LO","LT","OB","OF", "OW","PN","SH","SL","SQ","SS","ST","TM","UI","UL","UN","US","UT" ] @@ -66,7 +66,7 @@ function lookup_vr(gelt::Tuple{UInt16,UInt16}) elseif gelt[1]&0xff00 == 0x6000 gelt = (0x6000,gelt[2]) end - r = get(dcm_dict, gelt, emptyVR_lookup) + r = get(dcm_dict, gelt, empty_vr_lookup) return(r[2]) end @@ -126,16 +126,16 @@ function check_header(st) return end -# Pre-ample is always explicit VR / little endian +# Preamble is always explicit VR / little endian function read_preamble(st::IO) dcm = Dict{Tuple{UInt16,UInt16},Any}() is_explicit = true endian = :little while true pos = position(st) - (gelt, data, vr) = read_element(st, (is_explicit, endian, emptyDcmDict)) + (gelt, data, vr) = read_element(st, (is_explicit, endian, empty_dcm_dict)) grp = gelt[1] - if grp > 0x0002 || gelt == emptyTag + if grp > 0x0002 || gelt == empty_tag seek(st, pos) return dcm else @@ -164,7 +164,7 @@ function read_body(st, dcm, props; max_group) vrs = Dict{Tuple{UInt16,UInt16},String}() while true (gelt, data, vr) = read_element(st, props, dcm) - if gelt == emptyTag || gelt[1] > max_group + if gelt == empty_tag || gelt[1] > max_group break else dcm[gelt] = data @@ -174,13 +174,13 @@ function read_body(st, dcm, props; max_group) return dcm, vrs end -function read_element(st::IO, props, dcm=emptyDcmDict) +function read_element(st::IO, props, dcm=empty_dcm_dict) (is_explicit, endian, aux_vr) = props local grp try grp = read_group_tag(st, endian) catch - return(emptyTag,0,emptyVR) + return(empty_tag,0,empty_vr) end elt = read_element_tag(st, endian) gelt = (grp, elt) @@ -277,7 +277,7 @@ function determine_vr_and_lentype(st, gelt, is_explicit, aux_vr) if haskey(aux_vr, gelt) vr = aux_vr[gelt] end - if vr === emptyVR + if vr === empty_vr if haskey(aux_vr, (0x0000,0x0000)) vr = aux_vr[(0x0000,0x0000)] elseif !haskey(aux_vr, gelt) @@ -471,7 +471,7 @@ function write_element(st::IO, gelt::Tuple{UInt16,UInt16}, data, is_explicit, au else vr = lookup_vr(gelt) end - if vr === emptyVR + if vr === empty_vr # Element tags ending in 0x0000 are not included in dcm_dicm.jl, their vr is UL if gelt[2] == 0x0000 vr = "UL" @@ -490,7 +490,7 @@ function write_element(st::IO, gelt::Tuple{UInt16,UInt16}, data, is_explicit, au end if vr == "SQ" - vr = is_explicit ? vr : emptyVR + vr = is_explicit ? vr : empty_vr return dcm_store(st, gelt, s->sequence_write(s, data, is_explicit), vr) end @@ -516,7 +516,7 @@ function write_element(st::IO, gelt::Tuple{UInt16,UInt16}, data, is_explicit, au data if !is_explicit && gelt[1]>0x0002 - vr = emptyVR + vr = empty_vr end dcm_store(st, gelt, s->write(s, data), vr) @@ -529,12 +529,12 @@ string_write(vals::Char, maxlen) = string_write(string(vals), maxlen) string_write(vals::String, maxlen) = string_write([vals], maxlen) string_write(vals::Array{String,1}, maxlen) = join(vals, '\\') -dcm_store(st::IO, gelt::Tuple{UInt16,UInt16}, writef::Function) = dcm_store(st, gelt, writef, emptyVR) +dcm_store(st::IO, gelt::Tuple{UInt16,UInt16}, writef::Function) = dcm_store(st, gelt, writef, empty_vr) function dcm_store(st::IO, gelt::Tuple{UInt16,UInt16}, writef::Function, vr::String) lentype = UInt32 write(st, UInt16(gelt[1])) # Grp write(st, UInt16(gelt[2])) # Elt - if vr !== emptyVR + if vr !== empty_vr write(st, vr) if vr in ("OB", "OW", "OF", "SQ", "UT", "UN") write(st, UInt16(0)) @@ -576,7 +576,7 @@ end function sequence_item_write(st::IO, items::Dict{Tuple{UInt16,UInt16},Any}, evr) for gelt in sort(collect(keys(items))) - write_element(st, gelt, items[gelt], evr, emptyDcmDict) + write_element(st, gelt, items[gelt], evr, empty_dcm_dict) end write(st, UInt16[0xFFFE, 0xE00D, 0x0000, 0x0000]) end From aae3c1105e53cd491081f23e1328f76594c4a632 Mon Sep 17 00:00:00 2001 From: Zaki A Date: Mon, 21 Oct 2019 03:19:18 -0400 Subject: [PATCH 05/12] Fix bug with row/column ordering. Support RGB parsing. --- src/DICOM.jl | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/DICOM.jl b/src/DICOM.jl index 1a005b1..5457c4a 100644 --- a/src/DICOM.jl +++ b/src/DICOM.jl @@ -382,12 +382,12 @@ function pixeldata_parse(st::IO, sz, vr::String, dcm, endian) # (0028,0010) defines number of rows f = get(dcm, (0x0028,0x0010), nothing) if f !== nothing - xr = Int(f) + yr = Int(f) end # (0028,0011) defines number of columns f = get(dcm, (0x0028,0x0011), nothing) if f !== nothing - yr = Int(f) + xr = Int(f) end # (0028,0012) defines number of planes f = get(dcm, (0x0028,0x0012), nothing) @@ -399,9 +399,17 @@ function pixeldata_parse(st::IO, sz, vr::String, dcm, endian) if f !== nothing zr *= Int(f) end + # (0x0028, 0x0002) defines number of samples per pixel + f = get(dcm, (0x0028, 0x0002), nothing) + if f !== nothing + samples_per_pixel = Int(f) + else + samples_per_pixel = 1 + end if sz != 0xffffffff - data = - zr > 1 ? Array{dtype}(undef, xr, yr, zr) : Array{dtype}(undef, xr, yr) + data_dims = [xr, yr, zr, samples_per_pixel] + data_dims = data_dims[data_dims .> 1] + data = Array{dtype}(undef, data_dims...) read!(st, data) else # start with Basic Offset Table Item From 5c21a1e26f6cb530052c390ab25e4b0415b34562 Mon Sep 17 00:00:00 2001 From: Zaki A Date: Mon, 21 Oct 2019 03:22:45 -0400 Subject: [PATCH 06/12] Update function arguments --- src/DICOM.jl | 18 +++++++++--------- test/runtests.jl | 24 ++++++++++++------------ 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/DICOM.jl b/src/DICOM.jl index 5457c4a..ae3ba26 100644 --- a/src/DICOM.jl +++ b/src/DICOM.jl @@ -100,11 +100,11 @@ end Reads IO st and returns a Dict """ -function dcm_parse(st::IO; return_vr=false, header=true, max_group=0xffff, aux_vr=Dict{Tuple{UInt16,UInt16},String}()) - if header - check_header(st) +function dcm_parse(st::IO; return_vr=false, preamble=true, max_group=0xffff, aux_vr=Dict{Tuple{UInt16,UInt16},String}()) + if preamble + check_preamble(st) end - dcm = read_preamble(st) + dcm = read_meta(st) is_explicit, endian = determine_explicitness_and_endianness(dcm) file_properties = (is_explicit=is_explicit, endian=endian, aux_vr=aux_vr) (dcm, vr) = read_body(st, dcm, file_properties; max_group=max_group) @@ -115,10 +115,10 @@ function dcm_parse(st::IO; return_vr=false, header=true, max_group=0xffff, aux_v end end -function check_header(st) - # First 128 bytes are preamble - should be skipped +function check_preamble(st) + # First 128 can be skipped skip(st, 128) - # "DICM" identifier must be after preamble + # "DICM" identifier must be after the first 128 bytes sig = String(read!(st,Array{UInt8}(undef, 4))) if sig != "DICM" error("dicom: invalid file header") @@ -126,8 +126,8 @@ function check_header(st) return end -# Preamble is always explicit VR / little endian -function read_preamble(st::IO) +# Meta is always explicit VR / little endian +function read_meta(st::IO) dcm = Dict{Tuple{UInt16,UInt16},Any}() is_explicit = true endian = :little diff --git a/test/runtests.jl b/test/runtests.jl index fc33bda..7a86c86 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -31,10 +31,10 @@ end fileCT = download_dicom("CT_Explicit_Little.dcm") fileMG = download_dicom("MG_Explicit_Little.dcm") - dcmMR_partial = dcm_parse(fileMR, maxGrp=0x0008) + dcmMR_partial = dcm_parse(fileMR, max_group=0x0008) dcmMR = dcm_parse(fileMR) dcmCT = dcm_parse(fileCT) - (dcmMG, vrMG) = dcm_parse(fileMG, true) + (dcmMG, vrMG) = dcm_parse(fileMG, return_vr = true) @test dcmMR_partial[(0x0008,0x0060)] == "MR" @test haskey(dcmMR_partial, (0x7FE0,0x0010)) == false @@ -59,7 +59,7 @@ end dcmMR = dcm_parse(fileMR) dcmCT = dcm_parse(fileCT) - (dcmMG, vrMG) = dcm_parse(fileMG, true) + (dcmMG, vrMG) = dcm_parse(fileMG, return_vr=true) # Define two output files for each dcm - data will be saved, reloaded, then saved again outMR1 = joinpath(data_folder,"outMR1.dcm") @@ -73,16 +73,16 @@ end # Write DICOM files outIO = open(outMR1, "w+"); dcm_write(outIO,dcmMR); close(outIO) outIO = open(outCT1, "w+"); dcm_write(outIO,dcmCT); close(outIO) - outIO = open(outMG1, "w+"); dcm_write(outIO,dcmMG,vrMG); close(outIO) - dcm_write(outMG1b,dcmMG,vrMG) + outIO = open(outMG1, "w+"); dcm_write(outIO,dcmMG,aux_vr=vrMG); close(outIO) + dcm_write(outMG1b,dcmMG,aux_vr=vrMG) # Reading DICOM files which were written from previous step dcmMR1 = dcm_parse(outMR1) dcmCT1 = dcm_parse(outCT1) - (dcmMG1, vrMG1) = dcm_parse(outMG1, true) + (dcmMG1, vrMG1) = dcm_parse(outMG1, return_vr=true) # Write DICOM files which were re-read from previous step outIO = open(outMR2, "w+"); dcm_write(outIO,dcmMR1); close(outIO) outIO = open(outCT2, "w+"); dcm_write(outIO,dcmCT1); close(outIO) - outIO = open(outMG2, "w+"); dcm_write(outIO,dcmMG1, vrMG1); close(outIO) + outIO = open(outMG2, "w+"); dcm_write(outIO,dcmMG1, aux_vr=vrMG1); close(outIO) # Test consistency of written files after the write-read-write cycle @test read(outMR1)==read(outMR2) @@ -104,12 +104,12 @@ end end @testset "Uncommon DICOM" begin - # 1. DICOM file with missing header + # 1. DICOM file with missing preamble fileOT = download_dicom("OT_Implicit_Little_Headless.dcm") - dcmOT = dcm_parse(fileOT, header=false) + dcmOT = dcm_parse(fileOT, preamble=false) @test dcmOT[(0x0008,0x0060)] == "OT" - # 2. DICOM file with missing header and retired DICOM elements + # 2. DICOM file with missing preamble and retired DICOM elements fileCT = download_dicom("CT_Implicit_Little_Headless_Retired.dcm") # 2a. Read with user-supplied VRs dVR_CTa = Dict( @@ -124,12 +124,12 @@ end (0x0028,0x0005) => "US", (0x0028,0x0040) => "CS", (0x0028,0x0200) => "US") - dcmCTa = dcm_parse(fileCT, header=false, dVR=dVR_CTa); + dcmCTa = dcm_parse(fileCT, preamble=false, aux_vr=dVR_CTa); # 2b. Read with a master VR which skips elements # Here we skip any element where lookup_vr() fails # And we also force (0x0018,0x1170) to be read as float instead of integer dVR_CTb = Dict( (0x0000,0x0000) => "", (0x0018,0x1170) => "DS") - dcmCTb = dcm_parse(fileCT, header=false, dVR=dVR_CTb); + dcmCTb = dcm_parse(fileCT, preamble=false, aux_vr=dVR_CTb); @test dcmCTa[(0x0008,0x0060)] == "CT" @test dcmCTb[(0x0008,0x0060)] == "CT" @test haskey(dcmCTa, (0x0028,0x0040)) # dcmCTa should contain retired element From 01dedf5daebc8b03cbc04d15520dc22b6d39687b Mon Sep 17 00:00:00 2001 From: Zaki A Date: Mon, 21 Oct 2019 03:23:20 -0400 Subject: [PATCH 07/12] Add big endian & RGB file to tests --- test/runtests.jl | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/test/runtests.jl b/test/runtests.jl index 7a86c86..cf98554 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -13,7 +13,8 @@ const dicom_samples = Dict( "MR_Explicit_Little_MultiFrame.dcm" => "https://github.com/notZaki/DICOMSamples/raw/master/DICOMSamples/MR_Explicit_Little_MultiFrame.dcm", "MR_Implicit_Little.dcm" => "https://github.com/notZaki/DICOMSamples/raw/master/DICOMSamples/MR_Implicit_Little.dcm", "MR_UnspecifiedLength.dcm" => "https://github.com/notZaki/DICOMSamples/raw/master/DICOMSamples/MR_UnspecifiedLength.dcm", - "OT_Implicit_Little_Headless.dcm" => "https://github.com/notZaki/DICOMSamples/raw/master/DICOMSamples/OT_Implicit_Little_Headless.dcm" + "OT_Implicit_Little_Headless.dcm" => "https://github.com/notZaki/DICOMSamples/raw/master/DICOMSamples/OT_Implicit_Little_Headless.dcm", + "US_Explicit_Big_RGB.dcm" => "https://github.com/notZaki/DICOMSamples/raw/master/DICOMSamples/US_Explicit_Big_RGB.dcm" ) function download_dicom(filename; folder = data_folder) @@ -146,6 +147,13 @@ end @test size(dcmMR_UnspecifiedLength[tag"Pixel Data"]) === (256, 256, 27) end +@testset "Test big endian" begin + fileUS = download_dicom("US_Explicit_Big_RGB.dcm") + dcmUS = dcm_parse(fileUS) + @test Int(dcmUS[(0x7fe0, 0x0000)]) == 921612 + @test size(dcmUS[(0x7fe0, 0x0010)]) == (640, 480, 3) +end + @testset "Test tag macro" begin @test tag"Modality" === (0x0008, 0x0060) === DICOM.fieldname_dict["Modality"] From 1294a9d92c01f777cf66fbc4ebade9475fc191e9 Mon Sep 17 00:00:00 2001 From: Zaki A Date: Mon, 21 Oct 2019 03:43:58 -0400 Subject: [PATCH 08/12] Update syntax in readme examples --- README.md | 45 +++++++++++++++++++++++---------------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index ca4eb8a..8df4245 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Julia interface for parsing/writing DICOM files ## Usage -**Installation** +**Installation** To install the package: ``` @@ -24,54 +24,55 @@ julia> using DICOM Read a DICOM file by ``` -julia> dcmData = dcm_parse("path/to/dicom/file") +julia> dcm_data = dcm_parse("path/to/dicom/file") ``` -The data in `dcmData` is structured as a dictionary, and individual DICOM elements can be accessed by their hex tag. -For example, the hex tag of "Pixel Data" is `7FE0,0010`, and it can be accessed in Julia by `dcmData[(0x7FE0,0x0010)]` or by `dcmData[tag"Pixel Data"]`. +The data in `dcm_data` is structured as a dictionary, and individual DICOM elements can be accessed by their hex tag. +For example, the hex tag of "Pixel Data" is `7FE0,0010`, and it can be accessed in Julia by `dcm_data[(0x7FE0,0x0010)]` or by `dcm_data[tag"Pixel Data"]`. **Writing Data** Data can be written to a DICOM file by ``` -julia> dcm_write("path/to/output/file", dcmData) +julia> dcm_write("path/to/output/file", dcm_data) ``` **Additional Notes** +DICOM files should begin with a 128-bytes (which are ignored) followed by the string `DICM`. +If this preamble is missing, then the file can be parsed by `dcm_parse(path/to/file, preamble = false)`. + DICOM files use either explicit or implicit value representation (VR). For implicit files, `DICOM.jl` will use a lookup table to guess the VR from the DICOM element's hex tag. For explicit files, `DICOM.jl` will read the VRs from the file. -- A user-defined dictionary can be supplied to override the default lookup table +- An auxiliary user-defined dictionary can be supplied to override the default lookup table For example, the "Instance Number" - tag `(0x0020,0x0013)` - is an integer (default VR = "IS"). We can read this as a float by setting the VR to "DS" by: ``` - myVR = Dict( (0x0020,0x0013) => "DS" ) - dcmData = dcm_parse("path/to/dicom/file", dVR = myVR) + my_vrs = Dict( (0x0020,0x0013) => "DS" ) + dcm_data = dcm_parse("path/to/dicom/file", aux_vr = my_vrs) ``` - Now `dcmData[(0x0020,0x0013)]` will return a float instead of an integer. + Now `dcm_data[(0x0020,0x0013)]` will return a float instead of an integer. -- It is possible to skip an element by setting its VR to `""`. +- It is possible to skip an element by setting its VR to `""`. For example, we can skip reading the Instance Number by ``` - myVR = Dict( (0x0020,0x0013) => "" ) - dcmData = dcm_parse("path/to/dicom/file", dVR = myVR) + my_vrs = Dict( (0x0020,0x0013) => "" ) + dcm_data = dcm_parse("path/to/dicom/file", aux_vr = my_vr) ``` - and now `dcmData[(0x0020,0x0013)]` will return an error because the key `(0x0020,0x0013)` doesn't exist - it was skipped during reading. + and now `dcm_data[(0x0020,0x0013)]` will return an error because the key `(0x0020,0x0013)` doesn't exist - it was skipped during reading. - The user-supplied VR can contain a master VR with the tag `(0x0000,0x0000)` which will be used whenever `DICOM.jl` is unable to guess the VR on its own. This is convenient for reading older dicom files and skipping retired elements - i.e. where the VR lookup fails - by: ``` - myVR = Dict( (0x0000,0x0000) => "" ) - dcmData = dcm_parse("path/to/dicom/file", dVR = myVR) + my_vrs = Dict( (0x0000,0x0000) => "" ) + dcm_data = dcm_parse("path/to/dicom/file", aux_vr = my_vrs) ``` - A user-supplied VR can also be supplied during writing, e.g.: ``` - # Note that dcm_write doesn't use a named input, unlike dcm_parse with "dVR =" - julia> dcm_write("path/to/output/file", dcmData, dcmVR) + julia> dcm_write("path/to/output/file", dcm_data, aux_vr = user_defined_vr) ``` - where `dcmVR` is a dictionary which maps the hex tag to the VR. + where `user_defined_vr` is a dictionary which maps the hex tag to the VR. -- A dictionary of VRs can be obtained by passing `true` as a 2nd argument to `dcm_parse()`, e.g.: +- A dictionary of VRs can be obtained by passing `return_vr = true` as an argument to `dcm_parse()`, e.g.: ``` - julia> (dcmData, dcmVR) = dcm_parse("path/to/dicom/file", true) + julia> (dcm_data, dcm_vr) = dcm_parse("path/to/dicom/file", return_vr = true) ``` - and `dcmVR` will contain a dictionary of VRs for all of the elements in `dcmData` - + and `dcm_vr` will contain a dictionary of VRs for the elements in `dcm_data` From 6689d2e44fdecc9575bd071c9f46fb4b368d991c Mon Sep 17 00:00:00 2001 From: Zaki A Date: Mon, 21 Oct 2019 06:09:05 -0400 Subject: [PATCH 09/12] Update travis tests: add windows, allow failures, simplify coverage code --- .travis.yml | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1cfb326..a5a2e01 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,17 @@ language: julia os: - - linux - - osx + - linux + - osx + - windows julia: - - 1.0 - - 1.2 - - nightly + - 1.0 + - 1.2 + - nightly +matrix: + allow_failures: + - julia: nightly + fast_finish: true notifications: - email: false + email: false after_success: - - julia -e 'using Pkg; import DICOM; cd(joinpath(dirname(pathof(DICOM)),"../")); Pkg.add("Coverage"); using Coverage; Codecov.submit(Codecov.process_folder())' \ No newline at end of file + - julia -e 'using Pkg; Pkg.add("Coverage"); using Coverage; Codecov.submit(process_folder())' From 3dc496b24daf3841105c37112dfb01ad56032c6a Mon Sep 17 00:00:00 2001 From: Zaki A Date: Wed, 13 Nov 2019 16:11:27 -0500 Subject: [PATCH 10/12] Change keyword arg in dcm_write: header -> preamble --- src/DICOM.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/DICOM.jl b/src/DICOM.jl index ae3ba26..9b2287d 100644 --- a/src/DICOM.jl +++ b/src/DICOM.jl @@ -460,8 +460,8 @@ function dcm_write(fn::String, d::Dict{Tuple{UInt16,UInt16},Any}; kwargs...) return fn end -function dcm_write(st::IO, dcm::Dict{Tuple{UInt16,UInt16},Any}; header=true, aux_vr=Dict{Tuple{UInt16,UInt16},String}()) - if header +function dcm_write(st::IO, dcm::Dict{Tuple{UInt16,UInt16},Any}; preamble=true, aux_vr=Dict{Tuple{UInt16,UInt16},String}()) + if preamble write(st, zeros(UInt8, 128)) write(st, "DICM") end From 46616e7c2d7e7344e859e024cb0176507610d3e8 Mon Sep 17 00:00:00 2001 From: Zaki A Date: Wed, 13 Nov 2019 16:19:58 -0500 Subject: [PATCH 11/12] Add spacing between elements for consistent code style --- test/runtests.jl | 120 +++++++++++++++++++++++------------------------ 1 file changed, 60 insertions(+), 60 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index cf98554..540d378 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,7 +1,7 @@ using Test using DICOM -const data_folder = joinpath(@__DIR__,"testdata") +const data_folder = joinpath(@__DIR__, "testdata") if !isdir(data_folder) mkdir(data_folder) end @@ -32,25 +32,25 @@ end fileCT = download_dicom("CT_Explicit_Little.dcm") fileMG = download_dicom("MG_Explicit_Little.dcm") - dcmMR_partial = dcm_parse(fileMR, max_group=0x0008) + dcmMR_partial = dcm_parse(fileMR, max_group = 0x0008) dcmMR = dcm_parse(fileMR) dcmCT = dcm_parse(fileCT) (dcmMG, vrMG) = dcm_parse(fileMG, return_vr = true) - @test dcmMR_partial[(0x0008,0x0060)] == "MR" - @test haskey(dcmMR_partial, (0x7FE0,0x0010)) == false + @test dcmMR_partial[(0x0008, 0x0060)] == "MR" + @test haskey(dcmMR_partial, (0x7FE0, 0x0010)) == false - @test dcmMR[(0x0008,0x0060)] == "MR" - @test dcmCT[(0x0008,0x0060)] == "CT" - @test dcmMG[(0x0008,0x0060)] == "MG" + @test dcmMR[(0x0008, 0x0060)] == "MR" + @test dcmCT[(0x0008, 0x0060)] == "CT" + @test dcmMG[(0x0008, 0x0060)] == "MG" - @test length(dcmMR[(0x7FE0,0x0010)]) == 65536 - @test length(dcmCT[(0x7FE0,0x0010)]) == 262144 - @test length(dcmMG[(0x7FE0,0x0010)]) == 262144 + @test length(dcmMR[(0x7FE0, 0x0010)]) == 65536 + @test length(dcmCT[(0x7FE0, 0x0010)]) == 262144 + @test length(dcmMG[(0x7FE0, 0x0010)]) == 262144 # Test lookup-by-fieldname - @test dcmMR[(0x0008,0x0060)] == lookup(dcmMR, "Modality") - @test dcmMR[(0x7FE0,0x0010)] == lookup(dcmMR, "Pixel Data") + @test dcmMR[(0x0008, 0x0060)] == lookup(dcmMR, "Modality") + @test dcmMR[(0x7FE0, 0x0010)] == lookup(dcmMR, "Pixel Data") end @testset "Writing DICOM" begin @@ -60,86 +60,86 @@ end dcmMR = dcm_parse(fileMR) dcmCT = dcm_parse(fileCT) - (dcmMG, vrMG) = dcm_parse(fileMG, return_vr=true) + (dcmMG, vrMG) = dcm_parse(fileMG, return_vr = true) # Define two output files for each dcm - data will be saved, reloaded, then saved again - outMR1 = joinpath(data_folder,"outMR1.dcm") - outMR2 = joinpath(data_folder,"outMR2.dcm") - outCT1 = joinpath(data_folder,"outCT1.dcm") - outCT2 = joinpath(data_folder,"outCT2.dcm") - outMG1 = joinpath(data_folder,"outMG1.dcm") - outMG1b = joinpath(data_folder,"outMG1b.dcm") - outMG2 = joinpath(data_folder,"outMG2.dcm") + outMR1 = joinpath(data_folder, "outMR1.dcm") + outMR2 = joinpath(data_folder, "outMR2.dcm") + outCT1 = joinpath(data_folder, "outCT1.dcm") + outCT2 = joinpath(data_folder, "outCT2.dcm") + outMG1 = joinpath(data_folder, "outMG1.dcm") + outMG1b = joinpath(data_folder, "outMG1b.dcm") + outMG2 = joinpath(data_folder, "outMG2.dcm") # Write DICOM files - outIO = open(outMR1, "w+"); dcm_write(outIO,dcmMR); close(outIO) - outIO = open(outCT1, "w+"); dcm_write(outIO,dcmCT); close(outIO) - outIO = open(outMG1, "w+"); dcm_write(outIO,dcmMG,aux_vr=vrMG); close(outIO) - dcm_write(outMG1b,dcmMG,aux_vr=vrMG) + outIO = open(outMR1, "w+"); dcm_write(outIO, dcmMR); close(outIO) + outIO = open(outCT1, "w+"); dcm_write(outIO, dcmCT); close(outIO) + outIO = open(outMG1, "w+"); dcm_write(outIO, dcmMG, aux_vr = vrMG); close(outIO) + dcm_write(outMG1b, dcmMG, aux_vr = vrMG) # Reading DICOM files which were written from previous step dcmMR1 = dcm_parse(outMR1) dcmCT1 = dcm_parse(outCT1) - (dcmMG1, vrMG1) = dcm_parse(outMG1, return_vr=true) + (dcmMG1, vrMG1) = dcm_parse(outMG1, return_vr = true) # Write DICOM files which were re-read from previous step - outIO = open(outMR2, "w+"); dcm_write(outIO,dcmMR1); close(outIO) - outIO = open(outCT2, "w+"); dcm_write(outIO,dcmCT1); close(outIO) - outIO = open(outMG2, "w+"); dcm_write(outIO,dcmMG1, aux_vr=vrMG1); close(outIO) + outIO = open(outMR2, "w+"); dcm_write(outIO, dcmMR1); close(outIO) + outIO = open(outCT2, "w+"); dcm_write(outIO, dcmCT1); close(outIO) + outIO = open(outMG2, "w+"); dcm_write(outIO, dcmMG1, aux_vr = vrMG1); close(outIO) # Test consistency of written files after the write-read-write cycle - @test read(outMR1)==read(outMR2) - @test read(outCT1)==read(outCT2) - @test read(outMG1)==read(outMG2) + @test read(outMR1) == read(outMR2) + @test read(outCT1) == read(outCT2) + @test read(outMG1) == read(outMG2) # Repeat first testset on written data - @test dcmMR1[(0x0008,0x0060)] == "MR" - @test dcmCT1[(0x0008,0x0060)] == "CT" - @test dcmMG1[(0x0008,0x0060)] == "MG" + @test dcmMR1[(0x0008, 0x0060)] == "MR" + @test dcmCT1[(0x0008, 0x0060)] == "CT" + @test dcmMG1[(0x0008, 0x0060)] == "MG" - @test length(dcmMR1[(0x7FE0,0x0010)]) == 65536 - @test length(dcmCT1[(0x7FE0,0x0010)]) == 262144 - @test length(dcmMG1[(0x7FE0,0x0010)]) == 262144 + @test length(dcmMR1[(0x7FE0, 0x0010)]) == 65536 + @test length(dcmCT1[(0x7FE0, 0x0010)]) == 262144 + @test length(dcmMG1[(0x7FE0, 0x0010)]) == 262144 # Test lookup-by-fieldname; cross-compare dcmMR with dcmMR1 - @test dcmMR1[(0x0008,0x0060)] == lookup(dcmMR, "Modality") - @test dcmMR1[(0x7FE0,0x0010)] == lookup(dcmMR, "Pixel Data") + @test dcmMR1[(0x0008, 0x0060)] == lookup(dcmMR, "Modality") + @test dcmMR1[(0x7FE0, 0x0010)] == lookup(dcmMR, "Pixel Data") end @testset "Uncommon DICOM" begin # 1. DICOM file with missing preamble fileOT = download_dicom("OT_Implicit_Little_Headless.dcm") - dcmOT = dcm_parse(fileOT, preamble=false) - @test dcmOT[(0x0008,0x0060)] == "OT" + dcmOT = dcm_parse(fileOT, preamble = false) + @test dcmOT[(0x0008, 0x0060)] == "OT" # 2. DICOM file with missing preamble and retired DICOM elements fileCT = download_dicom("CT_Implicit_Little_Headless_Retired.dcm") # 2a. Read with user-supplied VRs dVR_CTa = Dict( - (0x0008,0x0010) => "SH", - (0x0008,0x0040) => "US", - (0x0008,0x0041) => "LO", - (0x0018,0x1170) => "DS", - (0x0020,0x0030) => "DS", - (0x0020,0x0035) => "DS", - (0x0020,0x0050) => "DS", - (0x0020,0x0070) => "LO", - (0x0028,0x0005) => "US", - (0x0028,0x0040) => "CS", - (0x0028,0x0200) => "US") - dcmCTa = dcm_parse(fileCT, preamble=false, aux_vr=dVR_CTa); + (0x0008, 0x0010) => "SH", + (0x0008, 0x0040) => "US", + (0x0008, 0x0041) => "LO", + (0x0018, 0x1170) => "DS", + (0x0020, 0x0030) => "DS", + (0x0020, 0x0035) => "DS", + (0x0020, 0x0050) => "DS", + (0x0020, 0x0070) => "LO", + (0x0028, 0x0005) => "US", + (0x0028, 0x0040) => "CS", + (0x0028, 0x0200) => "US") + dcmCTa = dcm_parse(fileCT, preamble = false, aux_vr = dVR_CTa); # 2b. Read with a master VR which skips elements # Here we skip any element where lookup_vr() fails # And we also force (0x0018,0x1170) to be read as float instead of integer - dVR_CTb = Dict( (0x0000,0x0000) => "", (0x0018,0x1170) => "DS") - dcmCTb = dcm_parse(fileCT, preamble=false, aux_vr=dVR_CTb); - @test dcmCTa[(0x0008,0x0060)] == "CT" - @test dcmCTb[(0x0008,0x0060)] == "CT" - @test haskey(dcmCTa, (0x0028,0x0040)) # dcmCTa should contain retired element - @test !haskey(dcmCTb, (0x0028,0x0040)) # dcmCTb skips retired elements + dVR_CTb = Dict( (0x0000, 0x0000) => "", (0x0018, 0x1170) => "DS") + dcmCTb = dcm_parse(fileCT, preamble = false, aux_vr = dVR_CTb); + @test dcmCTa[(0x0008, 0x0060)] == "CT" + @test dcmCTb[(0x0008, 0x0060)] == "CT" + @test haskey(dcmCTa, (0x0028, 0x0040)) # dcmCTa should contain retired element + @test !haskey(dcmCTb, (0x0028, 0x0040)) # dcmCTb skips retired elements # 3. DICOM file containing multiple frames fileMR_multiframe = download_dicom("MR_Explicit_Little_MultiFrame.dcm") dcmMR_multiframe = dcm_parse(fileMR_multiframe) - @test dcmMR_multiframe[(0x0008,0x0060)] == "MR" + @test dcmMR_multiframe[(0x0008, 0x0060)] == "MR" # 4. DICOM with unspecified_length() fileMR_UnspecifiedLength = download_dicom("MR_UnspecifiedLength.dcm") From 2fc78783280d96cea5f378e3d89ceeb8f08ac0bd Mon Sep 17 00:00:00 2001 From: Zaki A Date: Wed, 13 Nov 2019 16:20:15 -0500 Subject: [PATCH 12/12] Delete unused test file --- test/DICOM_test.jl | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 test/DICOM_test.jl diff --git a/test/DICOM_test.jl b/test/DICOM_test.jl deleted file mode 100644 index b86f232..0000000 --- a/test/DICOM_test.jl +++ /dev/null @@ -1,3 +0,0 @@ -using DICOM - -# todo