/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.rs.vr.loaders;
import android.renderscript.Allocation;
import android.renderscript.RenderScript;
import android.renderscript.Type;
import android.util.Log;
import com.example.android.rs.vr.engine.ScriptC_bricked;
import com.example.android.rs.vr.engine.Volume;
import java.io.File;
import java.io.RandomAccessFile;
import java.nio.ByteOrder;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel.MapMode;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Vector;
/**
* The simplest possible DICOM Reader.
* Will only read raw 16 bit dicom slices (the most common type)
* If the volume is compressed (usually JPEG2000) you need a decompression tool
*
* Note: All constants 0xNNNN, 0xNNNN are DICOM TAGS
* (see online documentation of DICOM standard)
*/
public class LoaderDicom {
private static final String LOGTAG = "ReadDicom";
String mName;
final boolean dbg = false;
private ByteOrder mByteOrder;
boolean explicit = true;
MappedByteBuffer mMappedByteBuffer;
long mFileLen;
private static final int MIN_VOLUME_SIZE = 20;
class Element {
int mGroup;
int mElement;
short mVR;
long mLength;
Object mValue;
@Override
public String toString() {
byte[] vrs = new byte[]{(byte) (mVR & 0xFF), (byte) ((mVR >> 8) & 0xFF)};
return Integer.toHexString(mGroup) + "," +
Integer.toHexString(mElement) + "(" +
new String(vrs) + ") [" + mLength + "] ";
}
}
static short vr(String v) {
byte[] b = v.getBytes();
return (short) (((b[1] & 0xFF) << 8) | (b[0] & 0xFF));
}
static final short OB = vr("OB");
static final short OW = vr("OW");
static final short OF = vr("OF");
static final short SQ = vr("SQ");
static final short UT = vr("UT");
static final short UN = vr("UN");
static final short DS = vr("DS");
static final short US = vr("US");
static final short AS = vr("AS");
static final short AT = vr("AT");
static final short CS = vr("CS");
static final short DA = vr("DA");
static final short DT = vr("DT");
static final short FL = vr("FL");
static final short FD = vr("FD");
static final short IS = vr("IS");
static final short LO = vr("LO");
static final short LT = vr("LT");
static final short PN = vr("PN");
static final short SH = vr("SH");
static final short SL = vr("SL");
static final short SS = vr("SS");
static final short ST = vr("ST");
static final short TM = vr("TM");
static final short UI = vr("UI");
static final short UL = vr("UL");
static final short AE = vr("AE");
static HashSet<Short> strVRs = new HashSet<Short>();
static {
short[] all = new short[]{
AE, AS, CS, DA, DS, DT, IS, LO, LT, PN, SH, ST, TM, UT
};
for (short anAll : all) {
strVRs.add(anAll);
}
}
boolean str(short vr) {
return strVRs.contains(vr);
}
boolean big(short vr) {
return OB == vr || OW == vr || OF == vr || SQ == vr || UT == vr || UN == vr;
}
class TagSet extends HashMap<Integer, Element> {
Element get(int group, int element) {
return get(tagInt(group, element));
}
void put(Element e) {
put(tagInt(e.mGroup, e.mElement), e);
}
}
static int tagInt(int g, int e) {
return (g << 16) | (e & 0xFFFF);
}
public static ByteOrder reverse(ByteOrder o) {
return (o == ByteOrder.LITTLE_ENDIAN) ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN;
}
public TagSet read(File file, int[] tags) throws Exception {
mName = file.getName();
TagSet set = new TagSet();
HashSet<Integer> toAdd = new HashSet<Integer>();
for (int n : tags) {
toAdd.add(n);
}
RandomAccessFile f = new RandomAccessFile(file, "r");
mMappedByteBuffer = f.getChannel().map(MapMode.READ_ONLY, 0, mFileLen = f.length());
mMappedByteBuffer.position(132);
setOrder(ByteOrder.LITTLE_ENDIAN);
Element e = new Element();
boolean early = true;
while (mMappedByteBuffer.position() < mFileLen) {
int pos = mMappedByteBuffer.position();
int jump = (int) readTag(e);
if (early) {
if (e.mGroup > 255) {
setOrder((mByteOrder == ByteOrder.LITTLE_ENDIAN) ?
ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);
mMappedByteBuffer.position(mMappedByteBuffer.position() - jump);
readTag(e);
}
}
if (early && e.mGroup >= 8) {
early = false;
}
if (toAdd.contains(tagInt(e.mGroup, e.mElement))) {
readValue(e);
set.put(e);
if (e.mGroup == 0x7fe0 && e.mElement == 0x10) {
return set;
}
e = new Element();
} else {
if (e.mGroup == 0x7fe0 && e.mElement == 0x10) {
return set;
}
skipValue(e);
}
}
return set;
}
private long readTag(Element e) {
e.mGroup = mMappedByteBuffer.getShort() & 0xFFFF;
e.mElement = mMappedByteBuffer.getShort() & 0xFFFF;
if (e.mGroup == 0xFFFE && e.mElement == 0xE000) {
e.mLength = mMappedByteBuffer.getInt();
if (e.mLength == -1) {
e.mLength = 0;
}
e.mVR = vr("s<");
return 8;
}
if (explicit) {
e.mVR = mMappedByteBuffer.getShort();
if (big(e.mVR)) {
mMappedByteBuffer.getShort();
e.mLength = mMappedByteBuffer.getInt() & 0xFFFFFFFF;
} else {
e.mLength = mMappedByteBuffer.getShort() & 0xFFFF;
}
} else {
e.mVR = 0;
int len = mMappedByteBuffer.getInt();
e.mLength = (len) & 0xFFFFFFFFL;
if (0xFFFFFFFF == e.mLength) {
Log.v(LOGTAG, "undefined");
e.mLength = 0;
}
}
if (e.mLength == -1 || e.mLength == 65535) {
e.mLength = 0;
}
return 8;
}
private void skipValue(Element e) {
if (e.mLength == 0) {
return;
}
if (dbg && str(e.mVR)) {
mMappedByteBuffer.get(readBuff, 0, (int) (e.mLength));
e.mValue = new String(readBuff, 0, (int) (e.mLength));
// Log.v(LOGTAG, e + " " + e.mValue);
} else {
mMappedByteBuffer.position((int) (mMappedByteBuffer.position() + e.mLength));
}
}
byte[] readBuff = new byte[200];
private void readValue(Element e) {
if (str(e.mVR)) {
mMappedByteBuffer.get(readBuff, 0, (int) (e.mLength));
e.mValue = new String(readBuff, 0, (int) (e.mLength));
} else if (e.mVR == US) {
e.mValue = new Short(mMappedByteBuffer.getShort());
} else if (e.mVR == OW) {
if (e.mLength == -1) {
e.mLength = mFileLen - mMappedByteBuffer.position();
}
short[] s = new short[(int) (e.mLength / 2)];
mMappedByteBuffer.asShortBuffer().get(s);
e.mValue = s;
}
}
private void setOrder(ByteOrder order) {
mByteOrder = order;
mMappedByteBuffer.order(mByteOrder);
}
public static Volume buildVolume(String dirName) {
return buildVolume(new File(dirName));
}
public static Volume buildVolume(File dir) {
LoaderDicom d = new LoaderDicom();
int[] tags = new int[]{
tagInt(0x20, 0x32),
tagInt(0x20, 0x37),
tagInt(0x28, 0x10),
tagInt(0x28, 0x11),
tagInt(0x7fe0, 0x10)
};
File[] files = dir.listFiles();
Arrays.sort(files, new Comparator<File>() {
@Override
public int compare(File o1, File o2) {
return o1.getName().compareTo(o2.getName());
}
});
Volume v = new Volume();
int count = 0;
for (File file : files) {
if (file.isDirectory()) {
continue;
}
if (file.getName().equals(".DS_Store")) {
continue;
}
count++;
}
if (count < MIN_VOLUME_SIZE) {
return null;
}
v.mData = new short[count][];
v.mDimz = count;
count = 0;
for (File file : files) {
if (file.isDirectory()) {
continue;
}
if (file.getName().equals(".DS_Store")) {
continue;
}
try {
TagSet data = d.read(file, tags);
v.mData[count] = (short[]) data.get(0x7fe0, 0x10).mValue;
count++;
v.mDimx = (Short) data.get(0x28, 0x10).mValue;
v.mDimy = (Short) data.get(0x28, 0x11).mValue;
} catch (Exception e) {
Log.e(LOGTAG, "Failed to parse " + file.getPath());
e.printStackTrace();
}
}
return v;
}
/**
* This is a multi threaded volume loaded
* It creates 2xthe number of cores
* @param rs The renderscript context
* @param dir The directory containing the DICOM files
* @param listener The Listener to provide feedback to the UI on loading
* @return The Volume object loaded with the volume
*/
public static Volume buildRSVolume(final RenderScript rs, File dir,
final VolumeLoader.ProgressListener listener) {
final int[] tags = new int[]{
tagInt(0x20, 0x32),
tagInt(0x20, 0x37),
tagInt(0x28, 0x10),
tagInt(0x28, 0x11),
tagInt(0x28, 0x30),
tagInt(0x7fe0, 0x10)
};
File[] files = dir.listFiles();
Arrays.sort(files, new Comparator<File>() {
@Override
public int compare(File o1, File o2) {
return o1.getName().compareTo(o2.getName());
}
});
final Volume v = new Volume();
int count = 0;
final Vector<File> toRun = new Vector<File>();
final HashMap<File, Integer> fileMap = new HashMap<File, Integer>();
for (File file : files) {
if (file.isDirectory()) {
continue;
}
if (file.getName().equals(".DS_Store")) {
continue;
}
toRun.add(file);
fileMap.put(file, count);
count++;
}
if (count < MIN_VOLUME_SIZE) {
return null;
}
v.mDimz = count;
if (listener != null) {
listener.progress(0, v.mDimx);
}
v.mVolumeAllocation = null;
final String []pixel_spacing = new String[count];
final String []slice_pos = new String[count];
final ScriptC_bricked scriptC_bricked = new ScriptC_bricked(rs);
int number_of_threads = 2 * Runtime.getRuntime().availableProcessors();
Thread[] t = new Thread[number_of_threads];
for (int i = 0; i < number_of_threads; i++) {
t[i] = new Thread() {
LoaderDicom d = new LoaderDicom();
private File getOne() {
synchronized (toRun) {
if (toRun.isEmpty()) {
return null;
}
return toRun.remove(0);
}
}
public void run() {
File file;
Allocation alloc_slice = null;
while ((file = getOne()) != null) {
int z = fileMap.get(file);
try {
TagSet data = d.read(file, tags);
short[] slice = (short[]) data.get(0x7fe0, 0x10).mValue;
short dimX = (Short) data.get(0x28, 0x10).mValue;
short dimY = (Short) data.get(0x28, 0x11).mValue;
String val;
val = (String) data.get(0x28,0x30).mValue;
pixel_spacing[z] = val;
val = (String) data.get(0x20,0x32).mValue;
slice_pos[z] = val;
if (v.mDimx == -1) {
v.mDimy = dimY;
v.mDimx = dimX;
}
synchronized (v) {
if (v.mVolumeAllocation == null) {
Type.Builder b = new Type.Builder(rs,
android.renderscript.Element.I16(rs));
b.setX(v.mDimx).setY(v.mDimy);
b.setZ(v.mDimz);
v.mVolumeAllocation = Allocation.createTyped(rs, b.create(),
Allocation.USAGE_SCRIPT);
scriptC_bricked.set_volume(v.mVolumeAllocation);
}
}
if (alloc_slice == null) {
Type.Builder b = new Type.Builder(rs,
android.renderscript.Element.I16(rs));
b.setX(v.mDimx).setY(v.mDimy);
alloc_slice = Allocation.createTyped(rs, b.create(),
Allocation.USAGE_SCRIPT);
}
if (listener != null) {
listener.progress(z, v.mDimx);
}
int size = v.mDimy * v.mDimx;
alloc_slice.copyFromUnchecked(slice);
synchronized (v) {
scriptC_bricked.set_z(z);
scriptC_bricked.forEach_copy(alloc_slice);
}
} catch (Exception e) {
e.printStackTrace();
}
}
alloc_slice.destroy();
}
};
t[i].start();
}
for (int i = 0; i < number_of_threads; i++) {
try {
t[i].join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
String[]pss = pixel_spacing[0].split("\\\\");
String[]s1ps = slice_pos[0].split("\\\\");
String[]s2ps = slice_pos[1].split("\\\\");
float sx = Float.parseFloat(pss[0]);
float sy = Float.parseFloat(pss[1]);
double dzx = Double.parseDouble(s1ps[0]) - Double.parseDouble(s2ps[0]);
double dzy = Double.parseDouble(s1ps[1]) - Double.parseDouble(s2ps[1]);
double dzz = Double.parseDouble(s1ps[2]) - Double.parseDouble(s2ps[2]);
float sz = (float) Math.hypot(dzx,Math.hypot(dzy,dzz));
float min = Math.min(sx,Math.min(sy,sz));
v.mVoxelDim[0] = sx/min;
v.mVoxelDim[1] = sy/min;
v.mVoxelDim[2] = sz/min;
Log.v(LOGTAG,"LOADING DONE ....");
scriptC_bricked.destroy();
return v;
}
/**
* Single threaded version of the volume createor
* @param rs the renderscript context
* @param dir the directory containing the dicom files
* @param listener used to feed back status to progress listeners
* @return Built volume
*/
public static Volume buildRSVolume2(final RenderScript rs, File dir,
VolumeLoader.ProgressListener listener) {
final int[] tags = new int[]{
tagInt(0x20, 0x32),
tagInt(0x20, 0x37),
tagInt(0x28, 0x10),
tagInt(0x28, 0x11),
tagInt(0x28, 0x30),
tagInt(0x7fe0, 0x10)
};
File[] files = dir.listFiles();
Arrays.sort(files, new Comparator<File>() {
@Override
public int compare(File o1, File o2) {
return o1.getName().compareTo(o2.getName());
}
});
Volume v = new Volume();
int count = 0;
final Vector<File> toRun = new Vector<File>();
final HashMap<File, Integer> fileMap = new HashMap<File, Integer>();
for (File file1 : files) {
if (file1.isDirectory()) {
continue;
}
if (file1.getName().equals(".DS_Store")) {
continue;
}
toRun.add(file1);
fileMap.put(file1, count);
count++;
}
if (count < 20) {
return null;
}
v.mDimz = count;
if (listener != null) {
listener.progress(0, v.mDimz);
}
v.mVolumeAllocation = null;
Allocation alloc_slice = null;
ScriptC_bricked scriptC_bricked = new ScriptC_bricked(rs);
LoaderDicom d = new LoaderDicom();
String pixel_spacing = null;
String slice1_pos = null;
String slice2_pos = null;
boolean slice_spacing_set = false;
int z = 0;
for (File file : toRun) {
try {
TagSet data = d.read(file, tags);
short[] slice = (short[]) data.get(0x7fe0, 0x10).mValue;
short mDimx = (Short) data.get(0x28, 0x10).mValue;
short mDimy = (Short) data.get(0x28, 0x11).mValue;
String val;
val = (String) data.get(0x28,0x30).mValue;
if (val != null && pixel_spacing==null) {
pixel_spacing = val;
}
val = (String) data.get(0x20,0x32).mValue;
if (val != null) {
if (slice1_pos == null) {
slice1_pos = val;
} else if (slice2_pos == null) {
slice2_pos = val;
}
}
if (v.mDimx == -1) {
v.mDimy = mDimy;
v.mDimx = mDimx;
}
if (v.mVolumeAllocation == null) {
Type.Builder b = new Type.Builder(rs, android.renderscript.Element.I16(rs));
b.setX(v.mDimx).setY(v.mDimy);
alloc_slice = Allocation.createTyped(rs, b.create(), Allocation.USAGE_SCRIPT);
b.setZ(v.mDimz);
v.mVolumeAllocation = Allocation.createTyped(rs, b.create(),
Allocation.USAGE_SCRIPT);
scriptC_bricked.set_volume(v.mVolumeAllocation);
}
if (listener != null) {
listener.progress(z, v.mDimz);
}
int size = v.mDimy * v.mDimx;
alloc_slice.copyFromUnchecked(slice);
scriptC_bricked.set_z(z);
scriptC_bricked.forEach_copy(alloc_slice);
z++;
if (!slice_spacing_set
&& pixel_spacing!=null
&& slice1_pos!=null
&& slice2_pos != null) {
String[]pss = pixel_spacing.split("\\\\");
String[]s1ps = slice1_pos.split("\\\\");
String[]s2ps = slice2_pos.split("\\\\");
float sx = Float.parseFloat(pss[0]);
float sy = Float.parseFloat(pss[1]);
double dzx = Double.parseDouble(s1ps[0]) - Double.parseDouble(s2ps[0]);
double dzy = Double.parseDouble(s1ps[1]) - Double.parseDouble(s2ps[1]);
double dzz = Double.parseDouble(s1ps[2]) - Double.parseDouble(s2ps[2]);
float sz = (float) Math.hypot(dzx,Math.hypot(dzy,dzz));
float min = Math.min(sx,Math.min(sy,sz));
v.mVoxelDim[0] = sx/min;
v.mVoxelDim[1] = sy/min;
v.mVoxelDim[2] = sz/min;
slice_spacing_set = true;
}
} catch (Exception e) {
e.printStackTrace();
}
}
Log.v(LOGTAG,"LOADING DONE ....");
alloc_slice.destroy();
scriptC_bricked.destroy();
return v;
}
}