1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.example.android.rs.vr.loaders;
18 
19 import android.renderscript.Allocation;
20 import android.renderscript.RenderScript;
21 import android.renderscript.Type;
22 import android.util.Log;
23 
24 import com.example.android.rs.vr.engine.ScriptC_bricked;
25 import com.example.android.rs.vr.engine.Volume;
26 
27 import java.io.File;
28 import java.io.RandomAccessFile;
29 import java.nio.ByteOrder;
30 import java.nio.MappedByteBuffer;
31 import java.nio.channels.FileChannel.MapMode;
32 import java.util.Arrays;
33 import java.util.Comparator;
34 import java.util.HashMap;
35 import java.util.HashSet;
36 import java.util.Vector;
37 
38 /**
39  * The simplest possible DICOM Reader.
40  * Will only read raw 16 bit dicom slices (the most common type)
41  * If the volume is compressed (usually JPEG2000) you need a decompression tool
42  *
43  * Note: All constants 0xNNNN, 0xNNNN are DICOM TAGS
44  * (see online documentation of DICOM standard)
45  */
46 public class LoaderDicom {
47     private static final String LOGTAG = "ReadDicom";
48     String mName;
49     final boolean dbg = false;
50     private ByteOrder mByteOrder;
51     boolean explicit = true;
52     MappedByteBuffer mMappedByteBuffer;
53     long mFileLen;
54     private static final int MIN_VOLUME_SIZE = 20;
55     class Element {
56         int mGroup;
57         int mElement;
58         short mVR;
59         long mLength;
60         Object mValue;
61 
62         @Override
toString()63         public String toString() {
64             byte[] vrs = new byte[]{(byte) (mVR & 0xFF), (byte) ((mVR >> 8) & 0xFF)};
65             return Integer.toHexString(mGroup) + "," +
66                     Integer.toHexString(mElement) + "(" +
67                     new String(vrs) + ") [" + mLength + "] ";
68         }
69     }
70 
vr(String v)71     static short vr(String v) {
72         byte[] b = v.getBytes();
73         return (short) (((b[1] & 0xFF) << 8) | (b[0] & 0xFF));
74     }
75 
76     static final short OB = vr("OB");
77     static final short OW = vr("OW");
78     static final short OF = vr("OF");
79     static final short SQ = vr("SQ");
80     static final short UT = vr("UT");
81     static final short UN = vr("UN");
82     static final short DS = vr("DS");
83     static final short US = vr("US");
84     static final short AS = vr("AS");
85     static final short AT = vr("AT");
86     static final short CS = vr("CS");
87     static final short DA = vr("DA");
88     static final short DT = vr("DT");
89     static final short FL = vr("FL");
90     static final short FD = vr("FD");
91     static final short IS = vr("IS");
92     static final short LO = vr("LO");
93     static final short LT = vr("LT");
94     static final short PN = vr("PN");
95     static final short SH = vr("SH");
96     static final short SL = vr("SL");
97     static final short SS = vr("SS");
98     static final short ST = vr("ST");
99     static final short TM = vr("TM");
100     static final short UI = vr("UI");
101     static final short UL = vr("UL");
102     static final short AE = vr("AE");
103 
104     static HashSet<Short> strVRs = new HashSet<Short>();
105 
106     static {
107         short[] all = new short[]{
108                 AE, AS, CS, DA, DS, DT, IS, LO, LT, PN, SH, ST, TM, UT
109         };
110         for (short anAll : all) {
111             strVRs.add(anAll);
112         }
113     }
114 
str(short vr)115     boolean str(short vr) {
116         return strVRs.contains(vr);
117     }
118 
big(short vr)119     boolean big(short vr) {
120         return OB == vr || OW == vr || OF == vr || SQ == vr || UT == vr || UN == vr;
121     }
122 
123     class TagSet extends HashMap<Integer, Element> {
get(int group, int element)124         Element get(int group, int element) {
125             return get(tagInt(group, element));
126         }
127 
put(Element e)128         void put(Element e) {
129             put(tagInt(e.mGroup, e.mElement), e);
130         }
131     }
132 
tagInt(int g, int e)133     static int tagInt(int g, int e) {
134         return (g << 16) | (e & 0xFFFF);
135     }
136 
reverse(ByteOrder o)137     public static ByteOrder reverse(ByteOrder o) {
138         return (o == ByteOrder.LITTLE_ENDIAN) ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN;
139     }
140 
read(File file, int[] tags)141     public TagSet read(File file, int[] tags) throws Exception {
142         mName = file.getName();
143         TagSet set = new TagSet();
144         HashSet<Integer> toAdd = new HashSet<Integer>();
145         for (int n : tags) {
146             toAdd.add(n);
147         }
148         RandomAccessFile f = new RandomAccessFile(file, "r");
149 
150         mMappedByteBuffer = f.getChannel().map(MapMode.READ_ONLY, 0, mFileLen = f.length());
151         mMappedByteBuffer.position(132);
152         setOrder(ByteOrder.LITTLE_ENDIAN);
153         Element e = new Element();
154         boolean early = true;
155 
156         while (mMappedByteBuffer.position() < mFileLen) {
157             int pos = mMappedByteBuffer.position();
158             int jump = (int) readTag(e);
159 
160             if (early) {
161                 if (e.mGroup > 255) {
162                     setOrder((mByteOrder == ByteOrder.LITTLE_ENDIAN) ?
163                             ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);
164                     mMappedByteBuffer.position(mMappedByteBuffer.position() - jump);
165                     readTag(e);
166                 }
167             }
168 
169             if (early && e.mGroup >= 8) {
170 
171                 early = false;
172             }
173             if (toAdd.contains(tagInt(e.mGroup, e.mElement))) {
174                 readValue(e);
175                 set.put(e);
176                 if (e.mGroup == 0x7fe0 && e.mElement == 0x10) {
177                     return set;
178                 }
179                 e = new Element();
180 
181             } else {
182                 if (e.mGroup == 0x7fe0 && e.mElement == 0x10) {
183                     return set;
184                 }
185 
186                 skipValue(e);
187             }
188         }
189         return set;
190     }
191 
readTag(Element e)192     private long readTag(Element e) {
193         e.mGroup = mMappedByteBuffer.getShort() & 0xFFFF;
194         e.mElement = mMappedByteBuffer.getShort() & 0xFFFF;
195 
196         if (e.mGroup == 0xFFFE && e.mElement == 0xE000) {
197             e.mLength = mMappedByteBuffer.getInt();
198             if (e.mLength == -1) {
199                 e.mLength = 0;
200             }
201             e.mVR = vr("s<");
202             return 8;
203         }
204 
205         if (explicit) {
206             e.mVR = mMappedByteBuffer.getShort();
207 
208             if (big(e.mVR)) {
209                 mMappedByteBuffer.getShort();
210                 e.mLength = mMappedByteBuffer.getInt() & 0xFFFFFFFF;
211             } else {
212                 e.mLength = mMappedByteBuffer.getShort() & 0xFFFF;
213             }
214         } else {
215             e.mVR = 0;
216             int len = mMappedByteBuffer.getInt();
217             e.mLength = (len) & 0xFFFFFFFFL;
218             if (0xFFFFFFFF == e.mLength) {
219                 Log.v(LOGTAG, "undefined");
220                 e.mLength = 0;
221             }
222         }
223         if (e.mLength == -1 || e.mLength == 65535) {
224             e.mLength = 0;
225         }
226         return 8;
227     }
228 
skipValue(Element e)229     private void skipValue(Element e) {
230         if (e.mLength == 0) {
231             return;
232         }
233         if (dbg && str(e.mVR)) {
234             mMappedByteBuffer.get(readBuff, 0, (int) (e.mLength));
235             e.mValue = new String(readBuff, 0, (int) (e.mLength));
236             //    Log.v(LOGTAG, e + "  " + e.mValue);
237         } else {
238             mMappedByteBuffer.position((int) (mMappedByteBuffer.position() + e.mLength));
239         }
240     }
241 
242     byte[] readBuff = new byte[200];
243 
readValue(Element e)244     private void readValue(Element e) {
245         if (str(e.mVR)) {
246             mMappedByteBuffer.get(readBuff, 0, (int) (e.mLength));
247             e.mValue = new String(readBuff, 0, (int) (e.mLength));
248         } else if (e.mVR == US) {
249             e.mValue = new Short(mMappedByteBuffer.getShort());
250         } else if (e.mVR == OW) {
251             if (e.mLength == -1) {
252                 e.mLength = mFileLen - mMappedByteBuffer.position();
253             }
254             short[] s = new short[(int) (e.mLength / 2)];
255             mMappedByteBuffer.asShortBuffer().get(s);
256             e.mValue = s;
257         }
258 
259     }
260 
setOrder(ByteOrder order)261     private void setOrder(ByteOrder order) {
262         mByteOrder = order;
263         mMappedByteBuffer.order(mByteOrder);
264     }
265 
buildVolume(String dirName)266     public static Volume buildVolume(String dirName) {
267         return buildVolume(new File(dirName));
268     }
269 
buildVolume(File dir)270     public static Volume buildVolume(File dir) {
271         LoaderDicom d = new LoaderDicom();
272         int[] tags = new int[]{
273                 tagInt(0x20, 0x32),
274                 tagInt(0x20, 0x37),
275                 tagInt(0x28, 0x10),
276                 tagInt(0x28, 0x11),
277                 tagInt(0x7fe0, 0x10)
278         };
279 
280         File[] files = dir.listFiles();
281         Arrays.sort(files, new Comparator<File>() {
282 
283             @Override
284             public int compare(File o1, File o2) {
285 
286                 return o1.getName().compareTo(o2.getName());
287             }
288         });
289         Volume v = new Volume();
290         int count = 0;
291         for (File file : files) {
292             if (file.isDirectory()) {
293                 continue;
294             }
295             if (file.getName().equals(".DS_Store")) {
296                 continue;
297             }
298             count++;
299         }
300         if (count < MIN_VOLUME_SIZE) {
301             return null;
302         }
303         v.mData = new short[count][];
304         v.mDimz = count;
305         count = 0;
306         for (File file : files) {
307             if (file.isDirectory()) {
308                 continue;
309             }
310             if (file.getName().equals(".DS_Store")) {
311                 continue;
312             }
313             try {
314                 TagSet data = d.read(file, tags);
315                 v.mData[count] = (short[]) data.get(0x7fe0, 0x10).mValue;
316                 count++;
317                 v.mDimx = (Short) data.get(0x28, 0x10).mValue;
318                 v.mDimy = (Short) data.get(0x28, 0x11).mValue;
319             } catch (Exception e) {
320                 Log.e(LOGTAG, "Failed to parse " + file.getPath());
321                 e.printStackTrace();
322             }
323         }
324         return v;
325     }
326 
327     /**
328      * This is a multi threaded volume loaded
329      * It creates 2xthe number of cores
330      * @param rs The renderscript context
331      * @param dir The directory containing the DICOM files
332      * @param listener The Listener to provide feedback to the UI on loading
333      * @return The Volume object loaded with the volume
334      */
buildRSVolume(final RenderScript rs, File dir, final VolumeLoader.ProgressListener listener)335     public static Volume buildRSVolume(final RenderScript rs, File dir,
336                                        final VolumeLoader.ProgressListener listener) {
337         final int[] tags = new int[]{
338                 tagInt(0x20, 0x32),
339                 tagInt(0x20, 0x37),
340                 tagInt(0x28, 0x10),
341                 tagInt(0x28, 0x11),
342                 tagInt(0x28, 0x30),
343                 tagInt(0x7fe0, 0x10)
344         };
345 
346         File[] files = dir.listFiles();
347         Arrays.sort(files, new Comparator<File>() {
348 
349             @Override
350             public int compare(File o1, File o2) {
351 
352                 return o1.getName().compareTo(o2.getName());
353             }
354         });
355         final Volume v = new Volume();
356         int count = 0;
357 
358 
359         final Vector<File> toRun = new Vector<File>();
360         final HashMap<File, Integer> fileMap = new HashMap<File, Integer>();
361         for (File file : files) {
362             if (file.isDirectory()) {
363                 continue;
364             }
365             if (file.getName().equals(".DS_Store")) {
366                 continue;
367             }
368             toRun.add(file);
369             fileMap.put(file, count);
370             count++;
371         }
372         if (count < MIN_VOLUME_SIZE) {
373             return null;
374         }
375         v.mDimz = count;
376         if (listener != null) {
377             listener.progress(0, v.mDimx);
378         }
379         v.mVolumeAllocation = null;
380         final String []pixel_spacing = new String[count];
381         final String []slice_pos = new String[count];
382 
383         final ScriptC_bricked scriptC_bricked = new ScriptC_bricked(rs);
384         int number_of_threads = 2 * Runtime.getRuntime().availableProcessors();
385         Thread[] t = new Thread[number_of_threads];
386         for (int i = 0; i < number_of_threads; i++) {
387 
388             t[i] = new Thread() {
389                 LoaderDicom d = new LoaderDicom();
390 
391 
392                 private File getOne() {
393                     synchronized (toRun) {
394                         if (toRun.isEmpty()) {
395                             return null;
396                         }
397                         return toRun.remove(0);
398                     }
399                 }
400 
401                 public void run() {
402                     File file;
403 
404                     Allocation alloc_slice = null;
405 
406                     while ((file = getOne()) != null) {
407                         int z = fileMap.get(file);
408                         try {
409                             TagSet data = d.read(file, tags);
410                             short[] slice = (short[]) data.get(0x7fe0, 0x10).mValue;
411                             short dimX = (Short) data.get(0x28, 0x10).mValue;
412                             short dimY = (Short) data.get(0x28, 0x11).mValue;
413                             String val;
414                             val = (String) data.get(0x28,0x30).mValue;
415                             pixel_spacing[z] = val;
416 
417                             val = (String) data.get(0x20,0x32).mValue;
418                             slice_pos[z] = val;
419 
420                             if (v.mDimx == -1) {
421                                 v.mDimy = dimY;
422                                 v.mDimx = dimX;
423                             }
424                             synchronized (v) {
425                                 if (v.mVolumeAllocation == null) {
426                                     Type.Builder b = new Type.Builder(rs,
427                                             android.renderscript.Element.I16(rs));
428                                     b.setX(v.mDimx).setY(v.mDimy);
429                                     b.setZ(v.mDimz);
430                                     v.mVolumeAllocation = Allocation.createTyped(rs, b.create(),
431                                             Allocation.USAGE_SCRIPT);
432                                     scriptC_bricked.set_volume(v.mVolumeAllocation);
433                                 }
434                             }
435 
436                             if (alloc_slice == null) {
437                                 Type.Builder b = new Type.Builder(rs,
438                                         android.renderscript.Element.I16(rs));
439                                 b.setX(v.mDimx).setY(v.mDimy);
440                                 alloc_slice = Allocation.createTyped(rs, b.create(),
441                                         Allocation.USAGE_SCRIPT);
442                             }
443                             if (listener != null) {
444                                 listener.progress(z, v.mDimx);
445                             }
446                             int size = v.mDimy * v.mDimx;
447                             alloc_slice.copyFromUnchecked(slice);
448                             synchronized (v) {
449                                 scriptC_bricked.set_z(z);
450                                 scriptC_bricked.forEach_copy(alloc_slice);
451                             }
452 
453                         } catch (Exception e) {
454                             e.printStackTrace();
455                         }
456                     }
457                     alloc_slice.destroy();
458                 }
459             };
460             t[i].start();
461         }
462 
463         for (int i = 0; i < number_of_threads; i++) {
464             try {
465                 t[i].join();
466             } catch (InterruptedException e) {
467                 e.printStackTrace();
468             }
469         }
470         String[]pss = pixel_spacing[0].split("\\\\");
471         String[]s1ps = slice_pos[0].split("\\\\");
472         String[]s2ps = slice_pos[1].split("\\\\");
473         float sx = Float.parseFloat(pss[0]);
474         float sy = Float.parseFloat(pss[1]);
475         double dzx = Double.parseDouble(s1ps[0]) - Double.parseDouble(s2ps[0]);
476         double dzy = Double.parseDouble(s1ps[1]) - Double.parseDouble(s2ps[1]);
477         double dzz = Double.parseDouble(s1ps[2]) - Double.parseDouble(s2ps[2]);
478         float sz = (float) Math.hypot(dzx,Math.hypot(dzy,dzz));
479         float min = Math.min(sx,Math.min(sy,sz));
480         v.mVoxelDim[0] = sx/min;
481         v.mVoxelDim[1] = sy/min;
482         v.mVoxelDim[2] = sz/min;
483         Log.v(LOGTAG,"LOADING DONE ....");
484         scriptC_bricked.destroy();
485         return v;
486     }
487 
488     /**
489      * Single threaded version of the volume createor
490      * @param rs the renderscript context
491      * @param dir the directory containing the dicom files
492      * @param listener used to feed back status to progress listeners
493      * @return Built volume
494      */
buildRSVolume2(final RenderScript rs, File dir, VolumeLoader.ProgressListener listener)495     public static Volume buildRSVolume2(final RenderScript rs, File dir,
496                                         VolumeLoader.ProgressListener listener) {
497         final int[] tags = new int[]{
498                 tagInt(0x20, 0x32),
499                 tagInt(0x20, 0x37),
500                 tagInt(0x28, 0x10),
501                 tagInt(0x28, 0x11),
502                 tagInt(0x28, 0x30),
503                 tagInt(0x7fe0, 0x10)
504         };
505         File[] files = dir.listFiles();
506         Arrays.sort(files, new Comparator<File>() {
507 
508             @Override
509             public int compare(File o1, File o2) {
510 
511                 return o1.getName().compareTo(o2.getName());
512             }
513         });
514         Volume v = new Volume();
515         int count = 0;
516 
517 
518         final Vector<File> toRun = new Vector<File>();
519         final HashMap<File, Integer> fileMap = new HashMap<File, Integer>();
520         for (File file1 : files) {
521             if (file1.isDirectory()) {
522                 continue;
523             }
524             if (file1.getName().equals(".DS_Store")) {
525                 continue;
526             }
527             toRun.add(file1);
528             fileMap.put(file1, count);
529             count++;
530         }
531         if (count < 20) {
532             return null;
533         }
534         v.mDimz = count;
535         if (listener != null) {
536             listener.progress(0, v.mDimz);
537         }
538         v.mVolumeAllocation = null;
539         Allocation alloc_slice = null;
540         ScriptC_bricked scriptC_bricked = new ScriptC_bricked(rs);
541         LoaderDicom d = new LoaderDicom();
542         String pixel_spacing = null;
543         String slice1_pos = null;
544         String slice2_pos = null;
545         boolean slice_spacing_set = false;
546         int z = 0;
547         for (File file : toRun) {
548             try {
549                 TagSet data = d.read(file, tags);
550                 short[] slice = (short[]) data.get(0x7fe0, 0x10).mValue;
551                 short mDimx = (Short) data.get(0x28, 0x10).mValue;
552                 short mDimy = (Short) data.get(0x28, 0x11).mValue;
553                 String val;
554                 val = (String) data.get(0x28,0x30).mValue;
555                 if (val != null && pixel_spacing==null) {
556                     pixel_spacing = val;
557                 }
558                 val = (String) data.get(0x20,0x32).mValue;
559                 if (val != null) {
560                     if (slice1_pos == null) {
561                         slice1_pos = val;
562                     } else if (slice2_pos == null) {
563                         slice2_pos = val;
564                     }
565                 }
566                 if (v.mDimx == -1) {
567                     v.mDimy = mDimy;
568                     v.mDimx = mDimx;
569                 }
570 
571                 if (v.mVolumeAllocation == null) {
572                     Type.Builder b = new Type.Builder(rs, android.renderscript.Element.I16(rs));
573                     b.setX(v.mDimx).setY(v.mDimy);
574                     alloc_slice = Allocation.createTyped(rs, b.create(), Allocation.USAGE_SCRIPT);
575                     b.setZ(v.mDimz);
576                     v.mVolumeAllocation = Allocation.createTyped(rs, b.create(),
577                             Allocation.USAGE_SCRIPT);
578                     scriptC_bricked.set_volume(v.mVolumeAllocation);
579 
580                 }
581                 if (listener != null) {
582                     listener.progress(z, v.mDimz);
583                 }
584 
585                 int size = v.mDimy * v.mDimx;
586                 alloc_slice.copyFromUnchecked(slice);
587                 scriptC_bricked.set_z(z);
588                 scriptC_bricked.forEach_copy(alloc_slice);
589                 z++;
590                 if (!slice_spacing_set
591                         && pixel_spacing!=null
592                         && slice1_pos!=null
593                         && slice2_pos != null) {
594                     String[]pss = pixel_spacing.split("\\\\");
595                     String[]s1ps = slice1_pos.split("\\\\");
596                     String[]s2ps = slice2_pos.split("\\\\");
597                     float sx = Float.parseFloat(pss[0]);
598                     float sy = Float.parseFloat(pss[1]);
599                     double dzx = Double.parseDouble(s1ps[0]) - Double.parseDouble(s2ps[0]);
600                     double dzy = Double.parseDouble(s1ps[1]) - Double.parseDouble(s2ps[1]);
601                     double dzz = Double.parseDouble(s1ps[2]) - Double.parseDouble(s2ps[2]);
602                     float sz = (float) Math.hypot(dzx,Math.hypot(dzy,dzz));
603                     float min = Math.min(sx,Math.min(sy,sz));
604                     v.mVoxelDim[0] = sx/min;
605                     v.mVoxelDim[1] = sy/min;
606                     v.mVoxelDim[2] = sz/min;
607                     slice_spacing_set = true;
608                 }
609             } catch (Exception e) {
610                 e.printStackTrace();
611             }
612         }
613         Log.v(LOGTAG,"LOADING DONE ....");
614 
615         alloc_slice.destroy();
616 
617         scriptC_bricked.destroy();
618         return v;
619     }
620 }
621