Home | History | Annotate | Download | only in loaders
      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
     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