This file is indexed.

/usr/share/gnudatalanguage/lib/dicom/gdlffdicom__assoc__define.pro is in libgnudatalanguage0 0.9.7-2.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
;L+
; LICENSE:
;
; IDL user contributed source code
; Copyright (C) 2006 Robbie Barnett
;
;    This library is free software;
;    you can redistribute it and/or modify it under the
;    terms of the GNU Lesser General Public License as published
;    by the Free Software Foundation;
;    either version 2.1 of the License,
;    or (at your option) any later version.
;
;    This library is distributed in the hope that it will
;    be useful, but WITHOUT ANY WARRANTY;
;    without even the implied warranty of MERCHANTABILITY
;    or FITNESS FOR A PARTICULAR PURPOSE.
;    See the GNU Lesser General Public License for more details.
;
;    You should have received a copy of the GNU Lesser General Public License
;    along with this library; if not, write to the
;    Free Software Foundation, Inc.
;    51 Franklin Street, Suite 500
;    Boston, MA 02110-1335, USA
;
; Please send queries to:
; Robbie Barnett
; Nuclear Medicine and Ultrasound
; Westmead Hospital
; +61 2 9845 7223
;L-



;+
;<P>Definition of each DICOM tag in the buffer</P>
;@private
;-
pro GDLffDICOMTag__define, struct
struct = {GDLffDICOMTag, $
          group_number: 0u, $
          element_number: 0u, $
          index:0l,$
          vr:'', $
          len:0l, $
          description: '', $
          commit: 0b, $
          value: ptr_new()}
end

;+
;<P>Definition of each DICOM Dictionary entry</P>
;@private
;-
pro GDLffDICOMDictionary__define, struct, INIT=init
struct = replicate({GDLffDICOMDictionary, $
                    group_number: 0u, $
                    element_number: 0u,$
                    vr: '', $
                    vm: '', $
                    name: '', $
                    version: ''},2085)
if (keyword_set(init)) then begin
; The Dictionary procedure is generated code from dicom.dic in the GPL
; release of DCMTK by OFFIS software
gdlffdicom__dictionary, struct
endif
end

;+
;<P>Open a DICOM file and parse DICOM tags</P>
;@param filename {in}{required}{type=String} Open and parse this filename
;@keyword auto_syntax {in}{optional} Automatically determine the
;transfer syntax (recommended)
;@keyword raise {in}{optional} Raise an exception in the calling
;program if an error occurs (useful for debugging)
;@keyword index_tags {in}{optional} Index group and element numbers
;(necessary for accessing DICOM tags by group and element number)
;@keyword index_sequences {in}{optional} Index sequence information
;(necessary for accessing DICOM tags in nested sequences)
;@keyword implicit_vr {in}{optional} Parse using the Implicit VR transfer syntax
;@keyword explicit_vr {in}{optional} Parse using the Explicit VR transfer syntax
;@keyword big_endian {in}{optional} Parse as a Big Endian file
;@keyword little_endian {in}{optional} Parse as a Little Endian file
;@keyword limit {in}{optional}{type=long} Only parse a limited number of bytes
;@keyword meta_only {in}{optional} Only parse the meta header
;@keyword access_time {out}{optional} The number of seconds taken to open and
;parse the file.
;@keyword no_catch {private} Do not catch any exceptions
;@returns 1 if file was opened and parsed, 0 if there was an error
;-
function GDLffDICOM__assoc::Open, filename, IMPLICIT_VR=implicit_vr, EXPLICIT_VR=explicit_vr, ACCESS_TIME=access_time, BIG_ENDIAN=big_endian, LITTLE_ENDIAN=little_endian, $
LIMIT=limit, INDEX_TAGS=index_tags, META_ONLY=meta_only, AUTO_SYNTAX=auto_syntax, NO_CATCH=no_catch, RAISE=raise, INDEX_SEQUENCES=index_sequences, READ_ONLY=read_only

; Check keywords
if (keyword_set(auto_syntax) and keyword_set(meta_only)) then message, LEVEL=-1, "Auto syntax and meta only are mutually exclusive keywords."
if (~ keyword_set(auto_syntax)) then begin
    if (~keyword_set(little_endian) and ~keyword_set(big_endian)) then auto_syntax = 1b
    ; message, LEVEL=-1, "Neither big nor little endian is set"
    if (~keyword_set(implicit_vr) and ~keyword_set(explicit_vr)) then auto_syntax = 1b
    ; message, LEVEL=-1, "Neither explicit of implicit VR is set"
endif
if (keyword_set(little_endian) and keyword_set(big_endian)) then message, LEVEL=-1, "Cannot set both big and little endian"
if (keyword_set(big_endian) and keyword_set(implicit_vr)) then message, LEVEL=-1, "Implicit big endian is not a vlaid transfer syntax"

if (self.lun ne 0) then message, LEVEL=-1, "Cannot open another file while one is already open. Try GDLffDICOM__assoc::close"

if (~ keyword_set(no_catch)) then begin
    CATCH, Error_status
    IF Error_status NE 0 then begin
        ; Close the file and generate an error message
        Error_message = !ERR_STRING
        CATCH,/CANCEL
        self -> close
        if (keyword_set(raise)) then begin
            on_error, 2
            Message, Error_message, LEVEL=-1 ; Generate message to caller
        endif else return, 0b
    endif
endif

if (arg_present(access_time) or (n_elements(access_time) gt 0)) then start_time = systime(1)

if (keyword_set(auto_syntax)) then begin
    index_tags = 1
    ; Just open the meta header of the file at little endian
    if (self -> open(filename,  /IMPLICIT_VR, /LITTLE_ENDIAN, /META_ONLY, INDEX_TAGS=index_tags, /NO_CATCH, /READ_ONLY)) then begin
        ; Read the tansfer syntax tag
        if (self -> readelement('0002'x,'0010'x, TransferSyntaxUID, VR='UI')) then begin
            TransferSyntaxUID = (strtrim(gdlffdicom_trim(strjoin(TransferSyntaxUID,'')),2))
             case (TransferSyntaxUID) of
                '1.2.840.10008.1.2.2': begin
                    little_endian=0
                    implicit_vr=0
                end
                '1.2.840.10008.1.2.1': begin
                    little_endian=1
                    implicit_vr=0
                end
                '1.2.840.10008.1.2': begin
                    little_endian=1
                    implicit_vr=1
                end
                else: message, "Transfer syntax unknown '" + TransferSyntaxUID + "' for file " +  filename
            endcase
            big_endian = ~ little_endian
            explicit_vr = ~ implicit_vr
        endif else message, "Transfer Syntax UID not found"
    endif
    self -> close
endif

if(keyword_set(explicit_vr)) then self.explicit_vr = 1b
if(keyword_set(implicit_vr)) then self.explicit_vr = 0b

self.index_tags = keyword_set(index_tags)
self.index_sequences = keyword_set(index_sequences)

if (keyword_set(big_endian)) then self.big_endian = 1b
if (keyword_set(little_endian)) then self.big_endian = 0b

self.read_only = keyword_set(read_only)
if (self.read_only) then begin
   if (self.big_endian) then $
      openr, lun, filename, /get_lun, /swap_if_little_endian $ ; This is a big endian file
   else $
      openr, lun, filename, /get_lun, /swap_if_big_endian ; This is a little endian file
endif else begin
   if (self.big_endian) then $
      openu, lun, filename, /get_lun, /swap_if_little_endian $ ; This is a big endian file
   else $
      openu, lun, filename, /get_lun, /swap_if_big_endian ; This is a little endian file
endelse

self.lun = lun
; The standard DICOM header is 132 bytes
offset = 128l
point_lun, self.lun, offset
chars = '    '
readu, self.lun, chars
if (chars ne 'DICM') then message, "Not a valid DICOM file " + filename
offset = offset + 4l
inside_metadata = 1b
index = 0l
bufgrow = 1024l
bufsize = bufgrow
(*self.offsets) = lonarr(bufgrow)
(*self.lens) = lonarr(bufgrow)
(*self.vrs) = strarr(bufgrow)
if (self.index_tags) then begin
    (*self.group_numbers) = uintarr(bufgrow)
    (*self.element_numbers) = uintarr(bufgrow)
endif
if (self.index_sequences) then begin
    (*self.parent_sequences) = lonarr(bufgrow)
    (*self.item_numbers) = lonarr(bufgrow)
    (*self.parent_items) = lonarr(bufgrow)
    current_sequence = replicate(-1l,20)
    current_item = replicate(-1l,20)
    current_item_number = replicate(-1l,20)
    current_nesting = 0l
    stop_offset = replicate('FFFFFFFF'x,20)
endif
stat = fstat(lun)
if (n_elements(limit) eq 0) then limit =  stat.size
while (offset + 4l lt limit) do begin
    is_delimiter = 0b

    if (inside_metadata or self.index_tags) then begin
        point_lun, self.lun, offset
        tag = uintarr(2)
        readu, self.lun, tag
        is_delimiter = (tag[0] eq 'FFFE'x) and ((tag[1] eq 'E000'x) or (tag[1] eq 'E00D'x) or (tag[1] eq 'E0DD'x))

        if (inside_metadata) then begin
                                ; metadata is always little endian
                                ; a big endian file will need to be swapped again
            if (self.big_endian) then $
              swap_endian_inplace, tag

            ; No more 0002 group tags indicates the end of the meta-data set
            if (tag[0] ne '0002'x) then begin
                inside_metadata = 0b
                if (self.big_endian) then $
                  swap_endian_inplace, tag

                if (keyword_set(meta_only)) then begin
                    self.size = index
                    if (arg_present(access_time) or (n_elements(access_time) gt 0)) then access_time = systime(1) - start_time

                    return, 1b
                endif
            endif
        endif
    endif

    if (~is_delimiter and (self.explicit_vr or inside_metadata)) then begin

        offset = offset + 4l
        point_lun, self.lun, offset
        vr = '  '
        readu, self.lun, vr

        vr_inds = where(vr eq ['OB', 'OW', 'OF', 'SQ', 'UT', 'UN'],vr_count)
        if (vr_count gt 0) then begin
            offset = offset + 4l
            point_lun, self.lun, offset
            len = 0ul
            readu, self.lun, len
            offset = offset + 4l
        endif else begin
            offset = offset + 2l
            point_lun, self.lun, offset
            len = 0u
            readu, self.lun, len
            offset = offset + 2l
        endelse
    endif else begin
        vr = ''
        dict_inds = where((self.dictionary.group_number eq tag[0]) and (self.dictionary.element_number eq tag[1]),dict_count)
        if (dict_count gt 0) then vr =  self.dictionary[dict_inds[0]].vr
        offset = offset + 4l
        point_lun, self.lun, offset
        len = 0l
        readu, self.lun, len
        offset = offset + 4l
    endelse

    if (inside_metadata) then begin
        ; metadata is always little endian
        ; a big endian file will need to be swapped again
        if (self.big_endian) then $
          swap_endian_inplace, len
    endif

    (*self.offsets)[index] = offset
    (*self.lens)[index] = len
    (*self.vrs)[index] = vr
    if (self.index_tags) then begin
        (*self.group_numbers)[index] = tag[0]
        (*self.element_numbers)[index] = tag[1]
    endif
    if (self.index_sequences) then begin
        (*self.parent_sequences)[index] = current_sequence[current_nesting]
        if (is_delimiter and (tag[1] eq 'E000'x)) then begin
            len = 0l
            current_item_number[current_nesting] = current_item_number[current_nesting] + 1l
            (*self.item_numbers)[index] = current_item_number[current_nesting]
            current_item[current_nesting] = index
            if ((*self.parent_sequences)[index] ge 0) then $
              (*self.parent_items)[index] = (*self.parent_items)[(*self.parent_sequences)[index]]
        endif else begin
            (*self.parent_items)[index] = current_item[current_nesting]
            (*self.item_numbers)[index] = current_item_number[current_nesting]
            if (vr eq 'SQ') then begin
                current_nesting = current_nesting + 1
                if (current_nesting ge 20) then message, "No more than 20 sequences can be nested"
                current_sequence[current_nesting] = index
                current_item_number[current_nesting] = 0l
                if (len ne 'FFFFFFFF'x) then stop_offset[current_nesting] = offset + len $
                else stop_offset[current_nesting] = 'FFFFFFFF'x
                len = 0l
            endif
            if (is_delimiter and (tag[1] eq 'E0DD'x) and (stop_offset[current_nesting] eq 'FFFFFFFF'x)) then $
              stop_offset[current_nesting] = offset + len
        endelse
        while ((current_nesting gt 0) and (stop_offset[current_nesting] ne 'FFFFFFFF'x) and $
               (stop_offset[current_nesting] le offset + len)) do begin
            current_nesting = current_nesting -1
        endwhile
    endif

    if (len ne 'FFFFFFFF'x) then offset = offset + len


    index = index + 1
    if (index ge bufsize) then begin
        (*self.offsets) = [(*self.offsets),lonarr(bufgrow)]
        (*self.lens) = [(*self.lens),lonarr(bufgrow)]
        (*self.vrs) = [(*self.vrs),strarr(bufgrow)]
        if (self.index_tags) then begin
            (*self.group_numbers) = [(*self.group_numbers),bytarr(bufgrow)]
            (*self.element_numbers) =  [(*self.element_numbers),bytarr(bufgrow)]
        endif
        if (self.index_sequences) then begin
            (*self.parent_sequences) = [(*self.parent_sequences),lonarr(bufgrow)]
            (*self.parent_items) = [(*self.parent_items),lonarr(bufgrow)]
            (*self.item_numbers) = [(*self.item_numbers),lonarr(bufgrow)]
        endif
        bufsize = bufsize + bufgrow
    endif
endwhile
self.size = index
if (arg_present(access_time) or (n_elements(access_time) gt 0)) then access_time = systime(1) - start_time
return, 1b
end

;+
;<P>Dump all parsed information to the console</P>
;-
pro GDLffDICOM__assoc::Dump, LUN=lun

for index=0l,self.size-1l do begin
    ; Need to make it look like DumpElements
    str = string(index, (*self.offsets)[index], (*self.lens)[index], FORMAT="(I5,I10,I10)")
    if (self.index_tags) then $
      str = str + string((*self.group_numbers)[index], (*self.element_numbers)[index],FORMAT="(' [',Z04,',',Z04,'] ')")
    if (self.explicit_vr) then $
      str = str + ' ' + (*self.vrs)[index]
    if (self.index_sequences) then $
      str = str + string((*self.parent_sequences)[index],(*self.parent_items)[index], FORMAT="(I10,I10)")
    if (n_elements(lun) gt 0) then printf, lun, str $
    else print, str
endfor
end

;+
;<P>Find a DICOM tag by group and element numbers</P>
;@param group_number {in}{type=int}{required} An UINT specifying the group number of the
;tag
;@param element_number {in}{type=int}{required} An UINT specifying the element number of the
;tag
;@keyword count {out}{type=lonarr}{optional} The number of matching DICOM
;tags.
;@returns The indicies of the matching dicom tags in the file.
;-
function GDLffDICOM__assoc::findtaginds, group_number, element_number, COUNT=count
group_inds = where(group_number eq *self.group_numbers,count)
if (count gt 0) then begin
    element_inds = where(element_number eq (*self.element_numbers)[group_inds],count)
    if (count gt 0) then return, group_inds[element_inds]
endif
count = 0l
return, [-1l]
end

;+
;<P>Read a DICOM tag which has the given group and element number. The
;VR must be specified for an implicit VR file.</P>
;@param group_number {in}{type=int}{optional} An UINT specifying the group number of the
;tag
;@param element_number {in}{type=int}{optional} An UINT specifying the element number of the
;tag
;@keyword vr {in}{optional} Read field using this VR. This must be
;specified for an implicit VR file.
;@param value_out {out}{required} The value read from the DICOM file
;@keyword offset {out}{optional} The byte offset where the value was
;found
;@keyword index {in}{optional} Read the element given at this position
;rather than searching for the group, element pair
;@returns 1 if the element was read. 0 if it wasn't found
;-
function GDLffDICOM__assoc::readelement, group_number, element_number, value_out, OFFSET=offset, INDEX=index, VR=vr, SKIP_UNSUPPORTED=skip_unsupported

if ((n_elements(index) eq 0)) then begin
    inds = self -> findtaginds(group_number, element_number,COUNT=count)
    if ((count gt 0)) then index = inds[0]
endif
if (n_elements(index) gt 0) then begin
    offset = (*self.offsets)[index]
    point_lun, self.lun, offset
    len = (*self.lens)[index]
    if (len eq 0) then return, 0b
    vr = (*self.vrs)[index]
    if (n_elements(vr) gt 0) then begin
    	value_out = self -> generatevalue(vr, len,SKIP_UNSUPPORTED=skip_unsupported)
	    readu, self.lun, value_out
	    return, 1b
    endif else begin
    	if (~keyword_set(skip_unsupported)) then $
    		message, "VR keyword must be specified for implicit files"
		return, 0b
    endelse
endif
return, 0b
end


;+
;<P>Generate an IDL variable for the given VR and byte length</P>
;@private
;@param vr {required}{in} The Value Representation for the variable
;@param len {required}{in} The length of the value
;@returns The IDL variable
;-
function GDLffDICOM__assoc::generatevalue, vr, len, SKIP_UNSUPPORTED=skip_unsupported

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','xs']
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, 12]
; xs could be SS or US depending on context

vr_inds = where(vrs eq vr,count)
if (count gt 0) then begin
    type = types[vr_inds[0]]
    case (type) of
        1: return, bytarr(len)
        2: return, intarr(len/2l)
        3: return, lonarr(len/4l)
        4: return, fltarr(len/2l)
        5: return, dblarr(len/4l)
        7: return, strjoin(replicate(' ', len),'')
        12: return, uintarr(len/2l)
        13: return, ulonarr(len/4l)
        else: message, "Unsupported VR type " + vr
    endcase
endif else begin
	if (~keyword_set(skip_unsupported)) then message, 'Unsupported VR ' + vr
	return, 0b
endelse


end

function GDLffDICOM__assoc::NewSOPInstanceUID, group_number, element_number
if (n_elements(group_number) eq 0) then group_number = '0008'x
if (n_elements(element_number) eq 0) then element_number = '0018'x
return, self -> NewUID(group_number, element_number)
end

;+
;<P>Generate and write a new SOP Instance UID. This generates a UID
;such that it doesn't change the length of the file.</P>
;@returns The SOP Instance UID String or an empty string if no UID
;could be generated.
;-
function GDLffDICOM__assoc::NewUID, group_number, element_number

if (n_elements(time_multiplier) eq 0) then time_multiplier = 1l $
else time_multiplier = time_multiplier+1l


if (self -> readelement(group_number,element_number, SOPInstanceUID, OFFSET=offset, VR='UI')) then begin
    SOPInstanceUID = strtrim(SOPInstanceUID) ; Remove trailing blanks only
    SOPInstanceUID = GDLffDICOM__assoc_GenerateUID(strlen(SOPInstanceUID),/EMPTY_ON_ERROR,ERROR=error)
    if ((SOPInstanceUID ne '')) then begin
        point_lun, self.lun, offset
        if (~ self.read_only) then $
            writeu, self.lun, SOPInstanceUID $
        else return, ''
    endif else errm = dialog_message(["GDLffDICOM Error: Cannot set UID of " + (fstat(self.lun)).name,error])
    return, SOPInstanceUID
endif

return, ''
end

;+
;<P>Write a new SOP Instance UID. This generates a UID
;must be the same length such that it doesn't change the length of the file.</P>
;-
function GDLffDICOM__assoc::WriteElement, group_number, element_number, newValue, VR=vr
if (self -> readelement(group_number,element_number, value, OFFSET=offset, VR=vr)) then begin
    value = strtrim(value) ; Remove trailing blanks only
    if (strlen(value) ne strlen(newValue)) then return, 0b
    if ((value ne '')) then begin
        point_lun, self.lun, offset
        if (~ self.read_only) then $
            writeu, self.lun, newValue $
        else return, 0b
    endif else errm = dialog_message(["GDLffDICOM Error: Cannot set value of " + (fstat(self.lun)).name,error])
    return, 1b
endif

return, 0b
end

;+
;<P>Write a new SOP Instance UID. This generates a UID
;must be the same length such that it doesn't change the length of the file.</P>
;-
function GDLffDICOM__assoc::WriteUID, group_number, element_number, NewInstanceUID
if (self -> readelement(group_number,element_number, SOPInstanceUID, OFFSET=offset, VR='UI')) then begin
    SOPInstanceUID = strtrim(SOPInstanceUID) ; Remove trailing blanks only
    if (strlen(SOPInstanceUID) ne strlen(NewInstanceUID)) then return, 0b
    if ((NewInstanceUID ne '')) then begin
        point_lun, self.lun, offset
        if (~ self.read_only) then $
            writeu, self.lun, NewInstanceUID
    endif else errm = dialog_message(["GDLffDICOM Error: Cannot set UID of " + (fstat(self.lun)).name,error])
    return, 1b
endif

return, 0b
end



;+
; <P>Associate the contents of the pixeldata tag in the DICOM file with
; an image. This will automatically determine the location of the
; pixeldata tag and its dimensions.</P>
; @keyword count {out}{optional} The number of images contained in the associated variable
; @keyword index {in}{optional} The index of the pixeldata tag
; @keyword image {in}{optional} Associate using the dimensions of this
; value
; @keyword true {out}{optional} If greater than 1 use this value as an argument for
; rendering the image using tv
;@keyword access_time {out}{optional} The number of seconds taken to
;assocate the value.
;@keyword raise {in}{optional} Raise an exception in the calling
;program if an error occurs (useful for debugging)
;@keyword no_catch {private} Do not catch any exceptions
;@returns The associated variable
;-
function GDLffDICOM__assoc::assoc, INDEX=index, IMAGE=value, COUNT=count, ACCESS_TIME=access_time, $
                          NO_CATCH=no_catch, RAISE=raise, TRUE=true, OFFSET=offset


if (~ keyword_set(no_catch)) then begin
    CATCH, Error_status
    IF Error_status NE 0 then begin
        Error_message = !ERR_STRING
        CATCH,/CANCEL
        if (keyword_set(raise)) then begin
            on_error, 2
            Message, Error_message, LEVEL=-1 ; Generate message to caller
        endif else begin
            count = 0
            return, [0]
        endelse
    endif
endif

if (arg_present(access_time) or (n_elements(access_time) gt 0)) then start_time = systime(1)

; Useful DICOM elements
;(0028,0010) US 2, 1 Rows
;(0028,0011) US 2, 1 Columns
;(0028,0100) US 2, 1 BitsAllocated
;(0028,0101) US 2, 1 BitsStored
;(0028,0102) US 2, 1 HighBit
;(0028,0103) US 2, 1 PixelRepresentation

true = 0b


if (n_elements(index) eq 0) then begin
    inds = self -> findtaginds('7fe0'x,'0010'x,COUNT=count)
    if (count gt 0) then index = inds[0] else message, "No pixeldata tag found"
endif

if (n_elements(value) eq 0) then begin
    if (self -> readelement('0028'x,'0002'x,samples,VR='US') and $
        self -> readelement('0028'x,'0010'x,rows,VR='US') and $
        self -> readelement('0028'x,'0011'x,columns,VR='US') and $
        self -> readelement('0028'x,'0100'x,bitsallocated,VR='US') and $
        self -> readelement('0028'x,'0101'x,bitsstored,VR='US') and $
        self -> readelement('0028'x,'0103'x,pixelrepresentation,VR='US')) then begin

        ; if (bitsallocated ne bitsstored) then  message, "Only understands same number of stored and allocated bits" + string(bitsallocated,bitsstored)
        case (bitsallocated) of
            8: begin
                if (pixelrepresentation eq 1u) then  message, "Bytes cannot be signed"
                if (samples gt 1u) then begin
                    if (~ self -> readelement('0028'x,'0006'x,planar,VR='US')) then planar = 0u
                    if (planar) then begin
                        value = bytarr(columns,rows,samples)
                        if (samples eq 3u) then true = 3b
                    endif else begin
                        value = bytarr(samples,columns,rows)
                        if (samples eq 3u) then true = 1b
                    endelse
                endif else  $
                  value = bytarr(columns,rows)
            end
            16: begin
                if (pixelrepresentation eq 1u) then value = intarr(columns,rows) $
                else value = uintarr(columns,rows)
            end
            else: message, "Unknown image depth"
        endcase
    endif else message, "Image dimensions not found"
endif


s = size(value)
case (s[s[0l]+1l]) of
    1: nbytes = s[s[0l]+2l] ; byte
    2: nbytes = s[s[0l]+2l]*2l ; integer
    3: nbytes = s[s[0l]+2l]*4l ; long
    4: nbytes = s[s[0l]+2l]*2l ; float
    5: nbytes = s[s[0l]+2l]*4l ; double
    12: nbytes = s[s[0l]+2l]*2l ; unsigned integer
    13: nbytes = s[s[0l]+2l]*4l ; unsigned long
    14: nbytes = s[s[0l]+2l]*8l ; 64-bit int
    15: nbytes = s[s[0l]+2l]*8l ; 64-bit uint
    else: message, "Unsupported type"
endcase

if ((index ge 0) and (index lt self.size)) then begin
    count = long((*self.lens)[index] / nbytes)
    if (count * nbytes ne (*self.lens)[index]) then $
      message, "The size of the value doesn't wholly fit inside the dicom field. " + string( nbytes, (*self.lens)[index], FORMAT="('value[',I0,'], dicom[',I0,']')")
    if (arg_present(access_time) or (n_elements(access_time) gt 0)) then access_time = systime(1) - start_time
    offset = (*self.offsets)[index]
    return, assoc(self.lun, value, offset)
endif else count = 0


end


;+
; <P>Write a new DICOM file tag by tag. The output file will
; contain values specified in the argument, otherwise the values are
; copied from the existing file. The argument should be a
; struct array containing the following fields</P>
;  <UL>
;         <LI>group_number (UINT)
;         <LI>element_number (UINT)
;         <LI>index (LONG)
;         <LI>vr (STRING)
;         <LI>len (LONG)
;         <LI>value (PTR)
;  </UL>
; @param values {in}{optional} A struct array with new values to write
;-
pro GDLffDICOM__assoc::write, filename, values

if ((fstat(self.lun)).name eq filename) then message, "Cannot commit to a file from which we are reading"

if (self.big_endian) then $
  openw, write_lun, filename, /get_lun, /swap_if_little_endian $ ; This is a big endian file
else $
  openw, write_lun, filename, /get_lun, /swap_if_big_endian ; This is a little endian file

writeu, write_lun, bytarr(128)
writeu, write_lun, 'DICM'

inside_metadata = 1b
is_delimiter = 0b
adj_lens = (*self.lens)

; Adjust sequence lengths to handle modified elements
for i=0,n_elements(values)-1l do begin
    index = values[i].index
    ;help, values[i], /structure, adj_lens, index, adj_lens[index],  values[i].len
    ;print, adj_lens[index] ne values[i].len
    if (adj_lens[index] ne values[i].len) then begin
        diff =  values[i].len - adj_lens[index]
  ;      help, diff
        if (self.index_sequences) then begin
            parent_index = (*self.parent_sequences)[index]
            parent_item = (*self.parent_items)[index]
            while (parent_index ge 0) do begin
                if (adj_lens[parent_index] ne ulong('FFFFFFFF'x)) then $
                  adj_lens[parent_index] = adj_lens[parent_index] + diff
                if (parent_item ge 0) then begin
                    if (adj_lens[parent_item] ne ulong('FFFFFFFF'x)) then $
                      adj_lens[parent_item] = adj_lens[parent_item] + diff
                endif
                parent_index = (*self.parent_sequences)[parent_index]
            endwhile
        endif
    endif
endfor

; Write file tag by tag
for index=0l,self.size-1l do begin
    if (n_elements(values) gt 0) then $
      v_inds = where(index eq values.index,v_count) $
    else v_count = 0
    if (v_count gt 0) then begin
        i = v_inds[0]
        tag = uint([values[i].group_number,values[i].element_number])
        len = values[i].len
        vr = values[i].vr
    endif else begin
        tag = uint([(*self.group_numbers)[index],(*self.element_numbers)[index]])
        len = adj_lens[index]
        vr = (*self.vrs)[index]
    endelse

    ;inds = where((*self.parent_items) eq index,count)
    ;if (count gt 0) then begin
    ;    total_len = ulong(total(adj_lens[inds]))
    ;    if (total_len ne len) then print, "Length mismatch ", len, total_len,count
    ;    ;else  print, "Length match ", total_len, len
    ;endif

    is_delimiter = (tag[0] eq 'FFFE'x) and ((tag[1] eq 'E000'x) or (tag[1] eq 'E00D'x) or (tag[1] eq 'E0DD'x))
    if (~self.explicit_vr and inside_metadata) then begin
        case (tag[1]) of
            '0000'x: vr = 'UL'
            '0001'x: vr = 'OB'
            '0002'x: vr = 'UI'
            '0003'x: vr = 'UI'
            '0010'x: vr = 'UI'
            '0012'x: vr = 'UI'
            '0013'x: vr = 'SH'
            '0016'x: vr = 'AE'
            '0100'x: vr = 'UI'
            '0102'x: vr = 'OB'
            else: vr = 'UN'
        endcase
    endif
    if (inside_metadata) then begin
                                ; metadata is always little endian
                                ; a big endian file will need to be swapped again
                                ; No more 0002 group tags indicates the end of the meta-data set
        if (tag[0] ne '0002'x) then $
          inside_metadata = 0b
    endif
    if (inside_metadata) then begin
         if (self.big_endian) then $
          swap_endian_inplace, tag
    endif

    writeu, write_lun, tag[0:1]

    if (~is_delimiter and (self.explicit_vr or inside_metadata)) then begin

        writeu, write_lun, strmid(vr,0,2)

        vr_inds = where(vr eq ['OB', 'OW', 'OF', 'SQ', 'UT', 'UN'],vr_count)
        if (vr_count gt 0) then begin

            writeu, write_lun, uint('0000'x)

            len = ulong(len)
        endif else begin
            len = uint(len)
        endelse
    endif else begin
        len = ulong(len)
    endelse
        write_len  = len[0]
    if (inside_metadata) then begin
                                ; metadata is always little endian
                                ; a big endian file will need to be swapped again
        if (self.big_endian) then $
          swap_endian_inplace, write_len
    endif

    ;if (write_len eq ulong('FFFFFFFF'x)) then print, "undefined length" $
    ;else begin
    ;endelse


    writeu, write_lun, write_len


    if ((vr ne 'SQ') and ~is_delimiter and self.index_sequences) then begin
        if (v_count gt 0) then begin
            writeu, write_lun, *values[i].value
        endif else begin
            offset = (*self.offsets)[index]
            if (long64(offset) + len le (fstat(self.lun)).size) then begin
                point_lun, self.lun, offset
                gdlffdicom_copy_lun, self.lun, write_lun, len
            endif else begin
                print, "Warning, truncating bytes"
                point_lun, self.lun, offset
                gdlffdicom_copy_lun, self.lun, write_lun, (fstat(self.lun)).size - offset
            endelse
        endelse
    endif
    endfor
free_lun, write_lun
end


;+
;<P>Close the file and free the lun</P>
;-
pro GDLffDICOM__assoc::close
if (self.lun gt 0) then begin
    free_lun, self.lun
    self.lun = 0
endif
self.size = 0l
end


;+
;<P>Cleanup pointers</P>
;-
pro GDLffDICOM__assoc::cleanup
if (self.lun gt 0) then free_lun, self.lun
ptr_free, self.lens
ptr_free, self.offsets
ptr_free, self.vrs
ptr_free, self.group_numbers
ptr_free, self.element_numbers
ptr_free, self.parent_sequences
ptr_free, self.parent_items
ptr_free, self.item_numbers
end


;+
;<P>Initialise the object</P>
;-
function GDLffDICOM__assoc::init
GDLffDICOMDictionary__define, dictionary, /INIT
self.dictionary  = dictionary
self.lens = ptr_new(/ALLOCATE_HEAP)
self.offsets = ptr_new(/ALLOCATE_HEAP)
self.vrs = ptr_new(/ALLOCATE_HEAP)
self.group_numbers = ptr_new(/ALLOCATE_HEAP)
self.element_numbers = ptr_new(/ALLOCATE_HEAP)
self.parent_sequences = ptr_new(/ALLOCATE_HEAP)
self.parent_items = ptr_new(/ALLOCATE_HEAP)
self.item_numbers = ptr_new(/ALLOCATE_HEAP)
return, 1
end

;+
;<H3>The DICOM update object</H3>
;<P>This object allows you to parse a DICOM file and associate the
;pixel data with an IDL variable. This means that you can make changes
;to the data in a DICOM file easily and efficently without touching
;any other parts of the file. It is recommended that you use
; dcmodify from the DICOM toolkit (www.dcmtk.org) to change any
; non-pixel DICOM tags. A wrapper class for dcmtk is provided in this release.
;</P>
;-
pro GDLffDICOM__assoc__define, struct
GDLffDICOMDictionary__define, struct

struct = {GDLffDICOM__assoc, $
          dictionary: struct, $
          lun: 0l, $
          lens: ptr_new(), $
          offsets: ptr_new(), $
          vrs: ptr_new(), $
          group_numbers: ptr_new(), $
          element_numbers: ptr_new(), $
          parent_sequences: ptr_new(), $
          parent_items: ptr_new(), $
          item_numbers: ptr_new(), $
          index_sequences: 0B, $
          size: 0l, $
          index_tags: 0B, $
          explicit_vr: 0B, $
          big_endian: 0B, $
          read_only: 0b $
}
end