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