1;L+
2; LICENSE:
3;
4; IDL user contributed source code
5; Copyright (C) 2006 Robbie Barnett
6;
7;    This library is free software;
8;    you can redistribute it and/or modify it under the
9;    terms of the GNU Lesser General Public License as published
10;    by the Free Software Foundation;
11;    either version 2.1 of the License,
12;    or (at your option) any later version.
13;
14;    This library is distributed in the hope that it will
15;    be useful, but WITHOUT ANY WARRANTY;
16;    without even the implied warranty of MERCHANTABILITY
17;    or FITNESS FOR A PARTICULAR PURPOSE.
18;    See the GNU Lesser General Public License for more details.
19;
20;    You should have received a copy of the GNU Lesser General Public License
21;    along with this library; if not, write to the
22;    Free Software Foundation, Inc.
23;    51 Franklin Street, Suite 500
24;    Boston, MA 02110-1335, USA
25;
26; Please send queries to:
27; Robbie Barnett
28; Nuclear Medicine and Ultrasound
29; Westmead Hospital
30; +61 2 9845 7223
31;L-
32
33
34
35;+
36;<P>Convert a GDLffDICOM References into GDLffDICOM__assoc indexes</P>
37;@private
38;-
39function GDLffDICOM::Indexes, references
40if (self.size lt self.ntags) then begin
41    inds = where(references gt self.size,count)
42    if (count gt 0) then references[inds] = self.pixel_index
43endif
44return, references
45end
46
47;+
48;<P>Find the indexes of DICOM tags which match the ith Dictionary entry</P>
49;@private
50;@param inds {in}{required} A subset of indexes to search
51;@param find_inds {out}{required} The indicies to the indexes which
52;match
53;@param i {in}{required} The dictionary entry to search
54;-
55pro GDLffDICOM::FindDefinedElement, inds, find_inds, i
56group_inds = where((self.dictionary[i]).group_number eq (*self.group_numbers)[inds],group_count)
57if (group_count gt 0) then begin
58    element_inds = where((self.dictionary[i]).element_number eq (*self.element_numbers)[inds[group_inds]],element_count)
59    if (element_count gt 0) then begin
60        if (n_elements(find_inds) gt 0) then $
61          find_inds = [find_inds,group_inds[element_inds]] $
62        else $
63          find_inds = [group_inds[element_inds]]
64    endif
65endif
66end
67
68;+
69;<P>Find the Dictionary entries for and matching DICOM tags</P>
70;@private
71;-
72function GDLffDICOM::GetDictionary, group_number, element_number, REFERENCE=references
73if (n_elements(references) eq 0) then begin
74    references = self -> GetReference(group_number, element_number)
75endif
76if (references[0] eq -1) then return, [-1]
77dictionaries = replicate({GDLffDICOMDictionary},n_elements(references))
78for i=0l,n_elements(references)-1l do begin
79    group_number = (*self.group_numbers)[self -> indexes(references[i])]
80    element_number = (*self.element_numbers)[self -> indexes(references[i])]
81    group_inds = where(self.dictionary.group_number eq group_number,group_count)
82    if (group_count gt 0) then begin
83        element_inds = where((self.dictionary.element_number)[group_inds] eq element_number,element_count)
84        if (element_count gt 0) then begin
85            dictionaries[i] = (self.dictionary)[group_inds[element_inds[0]]]
86        endif
87    endif
88endfor
89return, dictionaries
90end
91
92;+
93;<P>Open the file for updating and initialise dicom tag buffer</P>
94;@private
95;-
96function GDLffDICOM::Open2, filename, _REF_EXTRA=ex
97self -> reset
98result = self -> GDLffDICOM__assoc::Open(filename,_EXTRA=ex, /INDEX_SEQUENCES, /INDEX_TAGS)
99if (self.size gt 0) then begin
100    *self.pixel_assoc = self -> assoc(INDEX=index, COUNT=count)
101    if (count gt 0) then begin
102        self.pixel_index = index
103        self.frame_count = count
104        self.ntags = self.size + count -1l
105    endif else self.ntags = self.size
106    (*self.dicom_tags) = replicate({GDLffDICOMTag},self.ntags)
107endif
108; Set up the extra pixel indicies here
109return, result
110end
111
112
113;+
114;<P>Destroy the object</P>
115;-
116pro GDLffDICOM::Cleanup
117if (n_elements(*self.dicom_tags) gt 0) then  $
118  for i=0l,self.ntags-1l do ptr_free, (*self.dicom_tags)[i].value
119ptr_free, self.dicom_tags
120ptr_free, self.pixel_assoc
121self -> GDLffDICOM__assoc::Cleanup
122end
123
124;+
125;<P>Dump a description of all elements</P>
126;-
127pro GDLffDICOM::DumpElements, filename
128fmt = "(I4,' : (',Z04,',',Z04,') : ',A2,' : ',A,' : ',I0,' : ',A)"
129
130if (n_elements(filename) gt 0) then openw, lun, filename, /GET_LUN
131values = self -> getValue(/NO_COPY)
132for i=0l,self.ntags-1l do begin
133    dicom_tag = (*self.dicom_tags)[i]
134    if ((ptr_valid(dicom_tag.value) && (n_elements(*dicom_tag.value) gt 0))) then value = *dicom_tag.value $
135    else value = ""
136    case (size(value,/type)) of
137        else: begin
138            if (n_elements(value) gt 12) then $
139              value = strjoin(strtrim((string(value[0:11])),2),' ') + ' ...' $
140            else $
141              value = strjoin(strtrim((string(value)),2),' ')
142        end
143    endcase
144    if (n_elements(lun) gt 0) then begin
145	    printf, lun, i, dicom_tag.group_number, dicom_tag.element_number, $
146	           dicom_tag.vr, dicom_tag.description, dicom_tag.len, value, $
147	           FORMAT=fmt
148	endif else begin
149	    print, i, dicom_tag.group_number, dicom_tag.element_number, $
150	           dicom_tag.vr, dicom_tag.description, dicom_tag.len, value, $
151	           FORMAT=fmt
152	endelse
153endfor
154if (n_elements(lun) gt 0) then free_lun, lun
155end
156
157;+
158;<P>Get references to all child elements of this reference.</P>
159;-
160function GDLffDICOM::GetChildren, reference
161if (self.index_sequences and (n_elements(reference) gt 0)) then begin
162    references = where((*self.parent_sequences) eq self -> indexes(reference[0]),count)
163    if (count gt 0) then return, references $
164    else return, -1
165endif else $
166  return, -1
167end
168
169;+
170;<P>Return an array of string descriptions, as defined in the DICOM
171;dictionary of DCMTK by OFFIS software</P>
172;-
173function GDLffDICOM::GetDescription, group_number, element_number, REFERENCE=references
174dictionaries = self -> GetDictionary(group_number, element_number, REFERENCE=references)
175if (size(dictionaries,/type) eq 8) then return, [dictionaries.name]
176return, [-1]
177end
178
179;+
180;<P>Return an array of DICOM element numbers</P>
181;-
182function GDLffDICOM::GetElement, group_number, element_number, REFERENCE=references
183if (n_elements(references) eq 0) then begin
184    references = self -> GetReference(group_number, element_number)
185endif else references = [references]
186if (references[0] eq -1) then return, [-1]
187return, long((*self.element_numbers)[self -> indexes(references)])
188end
189
190;+
191;<P>Return an array of DICOM group numbers</P>
192;-
193function GDLffDICOM::GetGroup, group_number, element_number, REFERENCE=references
194if (n_elements(references) eq 0) then begin
195    references = self -> GetReference(group_number, element_number)
196endif else references = [references]
197if (references[0] eq -1) then return, [-1]
198return, long((*self.group_numbers)[self -> indexes(references)])
199end
200
201;+
202;<P>Return an array of the length of elements in bytes</P>
203;-
204function GDLffDICOM::GetLength, group_number, element_number, REFERENCE=references
205if (n_elements(references) eq 0) then begin
206    references = self -> GetReference(group_number, element_number)
207endif else references = [references]
208if (references[0] eq -1) then return, [-1]
209return, (*self.lens)[self -> indexes(references)]
210end
211
212;+
213;<P>Get references to all parent element of this reference.</P>
214;-
215function GDLffDICOM::GetParent, references
216if (self.index_sequences) then $
217    return, [(*self.parent_sequences)[self -> indexes(references)]] $
218else $
219  return, [-1]
220end
221
222;+
223;<P>Return the preamble of the DICOM file</P>
224;-
225function GDLffDICOM::GetPreamble
226preamble = bytarr(128)
227if (self.lun gt 0) then begin
228    point_lun, self.lun, 0
229    readu, self.lun, preamble
230    return, preamble
231endif
232end
233
234;+
235;<P>Return an array of references which match the arguments</P>
236;-
237function GDLffDICOM::GetReference, group_number, element_number, DESCRIPTION=description, VR=vr
238inds = indgen(self.ntags)
239if (n_elements(group_number) gt 0) then begin
240    group_inds = where(group_number eq (*self.group_numbers)[inds],group_count)
241    if (group_count gt 0) then inds = inds[group_inds] else return, -1
242endif
243if (n_elements(element_number) gt 0) then begin
244    element_inds = where(element_number eq (*self.element_numbers)[inds],element_count)
245    if (element_count gt 0) then inds = inds[element_inds] else return, -1
246endif
247if ((n_elements(description) gt 0)) then begin
248    for i=0,n_elements(self.dictionary)-1l do begin
249        if (strpos(strlowcase((self.dictionary[i]).name),strlowcase(description)) ge 0) then begin
250            self -> FindDefinedElement, inds, desc_inds, i
251        endif
252    endfor
253    if (n_elements(desc_inds) gt 0) then inds = inds[desc_inds] else return, -1
254endif
255if ((n_elements(vr) gt 0)) then begin
256;    if (self.explicit_vr) then begin
257    vr_inds = where(vr eq (*self.vrs)[inds],vr_count)
258    if (vr_count gt 0) then inds = inds[vr_inds] else return, -1
259;   endif else begin
260;    dict_inds = where((self.dictionary.vr) eq vr,dict_count)
261;    for j=0l,n_elements(dict_inds)-1l do $
262;      self -> FindDefinedElement, inds, vr_inds, dict_inds[j]
263;    if (n_elements(vr_inds) gt 0) then inds = inds[vr_inds] else return, -1
264;    endelse
265endif
266pixel_inds = where(inds eq self.pixel_index, pixel_count)
267if ((pixel_count gt 0) and (self.ntags gt self.size)) then begin
268    inds = [inds,self.size+indgen(self.ntags-self.size)]
269endif
270return, inds
271end
272
273;+
274;<P>Return an array of pointers to DICOM tag values</P>
275;@keyword pixeldata Use this keyword to return all the pixeldat tags
276;-
277function GDLffDICOM::GetValue, group_number, element_number, REFERENCE=references, NO_COPY=no_copy
278
279
280if (n_elements(references) eq 0) then begin
281    references = self -> GetReference(group_number, element_number)
282endif else references = [references]
283if (references[0] eq -1) then return, [-1]
284
285;help, references
286dictionaries = self -> GetDictionary(REFERENCE=references)
287;help, references, *self.dicom_tags
288if (self.explicit_vr) then vrs = (*self.vrs)[references] $
289else vrs =  dictionaries.vr
290for i=0,n_elements(references)-1l do begin
291     if (~ptr_valid((*self.dicom_tags)[references[i]].value)) then begin
292         (*self.dicom_tags)[references[i]].description = dictionaries[i].name
293         (*self.dicom_tags)[references[i]].vr = vrs[i]
294         (*self.dicom_tags)[references[i]].group_number = (*self.group_numbers)[references[i]]
295         (*self.dicom_tags)[references[i]].element_number = (*self.element_numbers)[references[i]]
296         (*self.dicom_tags)[references[i]].index = references[i]
297         (*self.dicom_tags)[references[i]].commit = 0b
298         if ((references[i] eq self.pixel_index)) then begin ;  and (self.ntags gt self.size)
299             (*self.dicom_tags)[references[i]].len = (*self.lens)[references[i]]/self.frame_count
300             (*self.dicom_tags)[references[i]].value = ptr_new((*self.pixel_assoc)[0])
301             for j=self.size,self.ntags-1l do begin
302                 (*self.dicom_tags)[j] = (*self.dicom_tags)[references[i]]
303                 (*self.dicom_tags)[j].value = ptr_new((*self.pixel_assoc)[j-self.size+1l])
304             endfor
305         endif else begin
306             inds = where(vrs[i] eq ['','SQ','DL'],unsupported_count)
307             if (unsupported_count eq 0) then begin
308                 if (self -> readelement(group_number, element_number, value_out, OFFSET=offset, INDEX=references[i], VR=vrs[i], /SKIP_UNSUPPORTED)) then begin
309                     (*self.dicom_tags)[references[i]].len = (*self.lens)[references[i]]
310                     (*self.dicom_tags)[references[i]].value = ptr_new(value_out)
311                 endif else begin
312                     (*self.dicom_tags)[references[i]].len = 0
313                     (*self.dicom_tags)[references[i]].value = ptr_new(/ALLOCATE_HEAP)
314                 endelse
315             endif else begin
316                 (*self.dicom_tags)[references[i]].len = (*self.lens)[references[i]]
317                 (*self.dicom_tags)[references[i]].value = ptr_new(/ALLOCATE_HEAP)
318             endelse
319         endelse
320    endif
321;    help,  (*self.dicom_tags)[references[i]].value
322;    help,  *(*self.dicom_tags)[references[i]].value
323endfor
324if (keyword_set(no_copy)) then begin
325    return, [((*self.dicom_tags)[references]).value]
326endif else begin
327    ptrcopy = ptrarr(n_elements(references),/allocate_heap)
328    for i=0l,n_elements(references)-1l do begin
329        ptr = ((*self.dicom_tags)[references[i]]).value
330        if (n_elements(*ptr) gt 0) then *ptrcopy[i] = *ptr
331    endfor
332    return, ptrcopy
333endelse
334end
335
336;+
337;<P>Return an array of the VR or DICOM tags</P>
338;-
339function GDLffDICOM::GetVR, group_number, element_number, REFERENCE=references
340if (self.explicit_vr) then begin
341    if (n_elements(references) eq 0) then begin
342        references = self -> GetReference(group_number, element_number)
343    endif else references = [references]
344    if (references[0] eq -1) then return, [-1]
345   return, (*self.vrs)[self -> indexes(references)]
346endif else begin
347    dictionaries = self -> GetDictionary(group_number, element_number,REFERENCE=references)
348    if (size(dictionaries,/type) eq 8) then return, (dictionaries.vr)
349    return, [-1]
350endelse
351end
352
353;+
354;<P>Initialise the object</P>
355;-
356function GDLffDICOM::Init, filename, VERBOSE=verbose
357if (~ self -> GDLffDICOM__assoc::Init()) then return, 0
358self.dicom_tags = ptr_new(/ALLOCATE_HEAP)
359self.pixel_assoc = ptr_new(/ALLOCATE_HEAP)
360if (n_elements(filename)) then return, self -> Read(filename)
361return, 1
362end
363
364;+
365;<P>Open a file for reading and writing</P>
366;-
367function GDLffDICOM::Read, filename, ENDIAN=endian, _REF_EXTRA=ex
368if (n_elements(endian) gt 0) then begin
369    case (endian) of
370        1: return, self -> Open2(filename, /IMPLICIT_VR, /LITTLE_ENDIAN, _EXTRA=ex)
371        2: return, self -> Open2(filename, /EXPLICIT_VR, /LITTLE_ENDIAN, _EXTRA=ex)
372        3: return, self -> Open2(filename, /IMPLICIT_VR, /BIG_ENDIAN, _EXTRA=ex)
373        4: return, self -> Open2(filename, /EXPLICIT_VR, /BIG_ENDIAN, _EXTRA=ex)
374    endcase
375endif else return, self -> Open2(filename, _EXTRA=ex)
376end
377
378;+
379;<P>Close the file and reset all buffers</P>
380;-
381pro GDLffDICOM::Reset
382if (n_elements(*self.dicom_tags) gt 0) then  $
383  for i=0l,self.ntags-1l do ptr_free, (*self.dicom_tags)[i].value
384self -> close
385end
386
387
388;+
389;<P>Get the byte offset of DICOM tags in the file</P>
390;-
391function GDLffDICOM::GetOffset, group_number, element_number, REFERENCE=references
392if (n_elements(references) eq 0) then begin
393    references = self -> GetReference(group_number, element_number)
394endif else references = [references]
395if (references[0] eq -1) then return, [-1]
396return, (*self.offsets)[self -> indexes(references)]
397end
398
399;+
400;<P>Set the value the first matching DICOM element if it exists, but do not write it until the commit
401;method is called</P>
402;@returns The reference for the element written
403;-
404function GDLffDICOM::SetElement, group_number, element_number, value, REFERENCE=reference
405if (n_elements(value) eq 0) then message, "Setting element to a null value is not supported"
406if (n_elements(reference) eq 1) then begin
407    self -> SetValue, reference, value
408    return, reference
409endif else begin
410    references = self -> GetReference(group_number, element_number)
411    if (references[0] gt 0) then $
412      self -> SetValue, references[0], value
413    return, references[0]
414endelse
415end
416
417
418;+
419;<P>Copy a values of a group from the specified dicom object into this
420;object. The tag must exist in this object for it to be copied.
421; All elements in the group are copied unless a list of element
422;numbers is explicitly provided.
423;</P>
424;-
425
426pro GDLffDICOM::CopyGroup, dicom_obj, group_number, element_numbers
427
428if (n_elements(element_numbers) gt 0) then begin
429    src_references = lonarr(n_elements(element_numbers))
430    dst_references = lonarr(n_elements(element_numbers))
431    for i=0l,n_elements(element_numbers)-1l do begin
432        src_references[i] = (dicom_obj -> GetReference(group_number, element_numbers[i]))[0]
433        dst_references[i] = (self -> GetReference(group_number, element_numbers[i]))[0]
434    endfor
435endif else begin
436    dst_references = self -> GetReference(group_number)
437    element_numbers = self -> GetElement(REFERENCE=dst_references)
438    src_references = lonarr(n_elements(element_numbers))
439    for i=0l,n_elements(element_numbers)-1l do begin
440        src_references[i] = (dicom_obj -> GetReference(group_number, element_numbers[i]))[0]
441    endfor
442endelse
443
444for i=0l,n_elements(dst_references)-1l do begin
445;    print, "Copying element ", src_references[i], " to ", dst_references[i]
446    if ((dst_references[i] ge 0) and (src_references[i] ge 0)) then begin
447        ptr = (dicom_obj -> GetValue(REFERENCE=src_references[i]))[0]
448        if (n_elements(*ptr) gt 0) then self -> SetValue, dst_references[i], ptr,/USE_PTR
449    endif
450endfor
451
452end
453
454;+
455;<P>Set the value of a DICOM tag, but do not write it until the commit
456;method is called</P>
457;-
458pro GDLffDICOM::SetValue, reference, value, USE_PTR=use_ptr
459
460if (n_elements(reference) ne 1) then message, "Must specify one and only one reference"
461
462if ((reference gt 0) and (reference lt self.size) and (reference ne self.pixel_index)) then begin
463    if (~ptr_valid((*self.dicom_tags)[reference].value)) then (*self.dicom_tags)[reference].value = ptr_new(/ALLOCATE_HEAP)
464    vr = (self -> GetVR(REFERENCE=reference))[0]
465    group_number = (self -> GetGroup(REFERENCE=reference))[0]
466    element_number = (self -> GetElement(REFERENCE=reference))[0]
467    vrs =   ['AE','AS','AT','CS','DA','DL','DS','DT','FL','FD','IS','LO','LT','OB','OF','OW','PN','SH','SL','SQ','SS','ST','TM','UI','UL','UN','US','UT']
468    types = [7   ,7   ,13  ,7   ,7   ,0   ,7   ,7   ,4   ,5   ,7   ,7   , 7  ,1   ,5   ,2   ,7   ,7   ,3   ,0   ,2   ,7   ,7   ,7   ,13  ,1   ,12  ,7]
469    vr_inds = where(vrs eq vr,count)
470    if (count gt 0) then begin
471        type = types[vr_inds[0]]
472        ; Temporarily dereference the pointer without copying the data
473        if (keyword_set(use_ptr)) then vvalue = temporary(*value) $
474        else vvalue = temporary(value)
475        if (type ne size(vvalue,/type)) then message, "IDL type does not match VR"
476        case (type) of
477            1: len = n_elements(vvalue)
478            2: len = n_elements(vvalue) * 2l
479            3: len = n_elements(vvalue) * 4l
480            4: len = n_elements(vvalue) * 2l
481            5: len = n_elements(vvalue) * 4l
482            7: begin
483                len = strlen(vvalue)
484                if (len mod 2 eq 1) then begin
485                    vvalue = vvalue + ' '
486                    len = len + 1l
487                endif
488            end
489            12: len = n_elements(vvalue) * 2l
490            13: len = n_elements(vvalue) * 4l
491            else: message, "Unsupported VR type"
492        endcase
493        if (keyword_set(use_ptr)) then *value = temporary(vvalue) $
494        else value = temporary(vvalue)
495    endif else message, 'Unsupported VR'
496    (*self.dicom_tags)[reference].group_number = group_number
497    (*self.dicom_tags)[reference].element_number = element_number
498    (*self.dicom_tags)[reference].vr = vr
499    (*self.dicom_tags)[reference].len = len
500    if (keyword_set(use_ptr)) then (*self.dicom_tags)[reference].value = value $
501    else *(*self.dicom_tags)[reference].value = value
502    (*self.dicom_tags)[reference].commit = 1b
503    (*self.dicom_tags)[reference].index = reference
504endif else message, 'Cannot set value'
505end
506
507;+
508;<P>Write all DICOM tags to a new file</P>
509;-
510function GDLffDICOM::Commit, filename
511inds = where(((*self.dicom_tags)[0:self.size-1l]).commit,count)
512if (count gt 0) then values = (*self.dicom_tags)[inds]
513self -> write, filename, values
514return, 1
515end
516
517
518;+
519;<P>A DICOM reader and writer</P>
520;-
521pro GDLffDICOM__define, struct
522struct = {GDLffDICOM, inherits GDLffDICOM__assoc, $
523          dicom_tags: ptr_new(), $
524          pixel_index: 0l, $
525          pixel_assoc: ptr_new(), $
526          frame_count: 0l, $
527          ntags: 0l $
528}
529end
530