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
17package com.example.android.rs.vr.loaders;
18
19import android.renderscript.Allocation;
20import android.renderscript.RenderScript;
21import android.renderscript.Type;
22import android.util.Log;
23
24import com.example.android.rs.vr.engine.ScriptC_bricked;
25import com.example.android.rs.vr.engine.Volume;
26
27import java.io.File;
28import java.io.RandomAccessFile;
29import java.nio.ByteOrder;
30import java.nio.MappedByteBuffer;
31import java.nio.channels.FileChannel.MapMode;
32import java.util.Arrays;
33import java.util.Comparator;
34import java.util.HashMap;
35import java.util.HashSet;
36import 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 */
46public 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
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
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
115    boolean str(short vr) {
116        return strVRs.contains(vr);
117    }
118
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> {
124        Element get(int group, int element) {
125            return get(tagInt(group, element));
126        }
127
128        void put(Element e) {
129            put(tagInt(e.mGroup, e.mElement), e);
130        }
131    }
132
133    static int tagInt(int g, int e) {
134        return (g << 16) | (e & 0xFFFF);
135    }
136
137    public static ByteOrder reverse(ByteOrder o) {
138        return (o == ByteOrder.LITTLE_ENDIAN) ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN;
139    }
140
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
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
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
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
261    private void setOrder(ByteOrder order) {
262        mByteOrder = order;
263        mMappedByteBuffer.order(mByteOrder);
264    }
265
266    public static Volume buildVolume(String dirName) {
267        return buildVolume(new File(dirName));
268    }
269
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     */
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     */
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