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