package ij.plugin;
import java.io.*;
import java.util.*;
import java.net.URL;
import ij.*;
import ij.io.*;
import ij.process.*;
import ij.util.Tools;
import ij.measure.Calibration;
/** This plugin decodes DICOM files. If 'arg' is empty, it
displays a file open dialog and opens and displays the
image selected by the user. If 'arg' is a path, it opens the
specified image and the calling routine can display it using
"((ImagePlus)IJ.runPlugIn("ij.plugin.DICOM", path)).show()".
*/
/* RAK (Richard Kirk, rak@cre.canon.co.uk) changes 14/7/99
InputStream.skip() looped to check the actual number of
bytes is read.
Big/little-endian options on element length.
Explicit check for each known VR to make mistaken identifications
of explicit VR's less likely.
Variables b1..b4 renamed as b0..b3.
Increment of 4 to offset on (7FE0,0010) tag removed.
Queries on some other unrecognized tags.
Anyone want to claim them?
RAK changes 15/7/99
Bug fix on magic values for explicit VRs with 32-bit lengths.
Various bits of tidying up, including...
'location' incremented on read using getByte() or getString().
simpler debug mode message generation (values no longer reported).
Added z pixel aspect ratio support for multi-slice DICOM volumes.
Michael Abramoff, 31-10-2000
Added DICOM tags to the dictionary (now contains about 2700 tags).
implemented getDouble() for VR = FD (Floating Double) and getFloat()
for VR = FL (Floating Single).
Extended case statement in getHeaderInfo to retrieve FD and FL values.
Johannes Hermen, Christian Moll, 25-04-2008
*/
public class DICOM extends ImagePlus implements PlugIn {
private boolean showErrors = true;
private boolean gettingInfo;
private BufferedInputStream inputStream;
private String info;
/** Default constructor. */
public DICOM() {
}
/** Constructs a DICOM reader that using an InputStream. Here
is an example that shows how to open and display a DICOM:
DICOM dcm = new DICOM(is);
dcm.run("Name");
dcm.show();
*/
public DICOM(InputStream is) {
this(new BufferedInputStream(is));
}
/** Constructs a DICOM reader that using an BufferredInputStream. */
public DICOM(BufferedInputStream bis) {
inputStream = bis;
}
public void run(String arg) {
OpenDialog od = new OpenDialog("Open Dicom...", arg);
String directory = od.getDirectory();
String fileName = od.getFileName();
if (fileName==null)
return;
//IJ.showStatus("Opening: " + directory + fileName);
DicomDecoder dd = new DicomDecoder(directory, fileName);
dd.inputStream = inputStream;
FileInfo fi = null;
try {
fi = dd.getFileInfo();
} catch (IOException e) {
String msg = e.getMessage();
IJ.showStatus("");
if (msg.indexOf("EOF")<0&&showErrors) {
IJ.error("DICOM Reader", e.getClass().getName()+"\n \n"+msg);
return;
} else if (!dd.dicmFound()&&showErrors) {
msg = "This does not appear to be a valid\n"
+ "DICOM file. It does not have the\n"
+ "characters 'DICM' at offset 128.";
IJ.error("DICOM Reader", msg);
return;
}
}
if (gettingInfo) {
info = dd.getDicomInfo();
return;
}
if (fi!=null && fi.width>0 && fi.height>0 && fi.offset>0) {
FileOpener fo = new FileOpener(fi);
ImagePlus imp = fo.openImage();
ImageProcessor ip = imp.getProcessor();
if (Prefs.openDicomsAsFloat) {
ip = ip.convertToFloat();
if (dd.rescaleSlope!=1.0)
ip.multiply(dd.rescaleSlope);
if (dd.rescaleIntercept!=0.0)
ip.add(dd.rescaleIntercept);
imp.setProcessor(ip);
} else if (fi.fileType==FileInfo.GRAY16_SIGNED) {
if (dd.rescaleIntercept!=0.0 && dd.rescaleSlope==1.0)
ip.add(dd.rescaleIntercept);
} else if (dd.rescaleIntercept!=0.0 && (dd.rescaleSlope==1.0||fi.fileType==FileInfo.GRAY8)) {
double[] coeff = new double[2];
coeff[0] = dd.rescaleIntercept;
coeff[1] = dd.rescaleSlope;
imp.getCalibration().setFunction(Calibration.STRAIGHT_LINE, coeff, "Gray Value");
}
if (dd.windowWidth>0.0) {
double min = dd.windowCenter-dd.windowWidth/2;
double max = dd.windowCenter+dd.windowWidth/2;
if (Prefs.openDicomsAsFloat) {
min -= dd.rescaleIntercept;
max -= dd.rescaleIntercept;
} else {
Calibration cal = imp.getCalibration();
min = cal.getRawValue(min);
max = cal.getRawValue(max);
}
ip.setMinAndMax(min, max);
if (IJ.debugMode) IJ.log("window: "+min+"-"+max);
}
if (imp.getStackSize()>1)
setStack(fileName, imp.getStack());
else
setProcessor(fileName, imp.getProcessor());
setCalibration(imp.getCalibration());
setProperty("Info", dd.getDicomInfo());
setFileInfo(fi); // needed for revert
if (arg.equals("")) show();
} else if (showErrors)
IJ.error("DICOM Reader","Unable to decode DICOM header.");
IJ.showStatus("");
}
/** Opens the specified file as a DICOM. Does not
display a message if there is an error.
Here is an example:
DICOM dcm = new DICOM();
dcm.open(path);
if (dcm.getWidth()==0)
IJ.log("Error opening '"+path+"'");
else
dcm.show();
*/
public void open(String path) {
showErrors = false;
run(path);
}
/** Returns the DICOM tags of the specified file as a string. */
public String getInfo(String path) {
showErrors = false;
gettingInfo = true;
run(path);
return info;
}
/** Convert 16-bit signed to unsigned if all pixels>=0. */
void convertToUnsigned(ImagePlus imp, FileInfo fi) {
ImageProcessor ip = imp.getProcessor();
short[] pixels = (short[])ip.getPixels();
int min = Integer.MAX_VALUE;
int value;
for (int i=0; i=32768) {
for (int i=0; i60)
s = s.substring(0,60);
return s;
}
int getByte() throws IOException {
int b = f.read();
if (b ==-1)
throw new IOException("unexpected EOF");
++location;
return b;
}
int getShort() throws IOException {
int b0 = getByte();
int b1 = getByte();
if (littleEndian)
return ((b1 << 8) + b0);
else
return ((b0 << 8) + b1);
}
int getSShort() throws IOException {
short b0 = (short)getByte();
short b1 = (short)getByte();
if (littleEndian)
return ((b1 << 8) + b0);
else
return ((b0 << 8) + b1);
}
final int getInt() throws IOException {
int b0 = getByte();
int b1 = getByte();
int b2 = getByte();
int b3 = getByte();
if (littleEndian)
return ((b3<<24) + (b2<<16) + (b1<<8) + b0);
else
return ((b0<<24) + (b1<<16) + (b2<<8) + b3);
}
long getUInt() throws IOException {
long b0 = getByte();
long b1 = getByte();
long b2 = getByte();
long b3 = getByte();
if (littleEndian)
return ((b3<<24) + (b2<<16) + (b1<<8) + b0);
else
return ((b0<<24) + (b1<<16) + (b2<<8) + b3);
}
double getDouble() throws IOException {
int b0 = getByte();
int b1 = getByte();
int b2 = getByte();
int b3 = getByte();
int b4 = getByte();
int b5 = getByte();
int b6 = getByte();
int b7 = getByte();
long res = 0;
if (littleEndian) {
res += b0;
res += ( ((long)b1) << 8);
res += ( ((long)b2) << 16);
res += ( ((long)b3) << 24);
res += ( ((long)b4) << 32);
res += ( ((long)b5) << 40);
res += ( ((long)b6) << 48);
res += ( ((long)b7) << 56);
} else {
res += b7;
res += ( ((long)b6) << 8);
res += ( ((long)b5) << 16);
res += ( ((long)b4) << 24);
res += ( ((long)b3) << 32);
res += ( ((long)b2) << 40);
res += ( ((long)b1) << 48);
res += ( ((long)b0) << 56);
}
return Double.longBitsToDouble(res);
}
float getFloat() throws IOException {
int b0 = getByte();
int b1 = getByte();
int b2 = getByte();
int b3 = getByte();
int res = 0;
if (littleEndian) {
res += b0;
res += ( ((long)b1) << 8);
res += ( ((long)b2) << 16);
res += ( ((long)b3) << 24);
} else {
res += b3;
res += ( ((long)b2) << 8);
res += ( ((long)b1) << 16);
res += ( ((long)b0) << 24);
}
return Float.intBitsToFloat(res);
}
byte[] getLut(int length) throws IOException {
if ((length&1)!=0) { // odd
String dummy = getString(length);
return null;
}
length /= 2;
byte[] lut = new byte[length];
for (int i=0; i>>8);
return lut;
}
int getLength() throws IOException {
int b0 = getByte();
int b1 = getByte();
int b2 = getByte();
int b3 = getByte();
// We cannot know whether the VR is implicit or explicit
// without the full DICOM Data Dictionary for public and
// private groups.
// We will assume the VR is explicit if the two bytes
// match the known codes. It is possible that these two
// bytes are part of a 32-bit length for an implicit VR.
vr = (b0<<8) + b1;
switch (vr) {
case OB: case OW: case SQ: case UN: case UT:
// Explicit VR with 32-bit length if other two bytes are zero
if ( (b2 == 0) || (b3 == 0) ) return getInt();
// Implicit VR with 32-bit length
vr = IMPLICIT_VR;
if (littleEndian)
return ((b3<<24) + (b2<<16) + (b1<<8) + b0);
else
return ((b0<<24) + (b1<<16) + (b2<<8) + b3);
case AE: case AS: case AT: case CS: case DA: case DS: case DT: case FD:
case FL: case IS: case LO: case LT: case PN: case SH: case SL: case SS:
case ST: case TM:case UI: case UL: case US: case QQ:
// Explicit vr with 16-bit length
if (littleEndian)
return ((b3<<8) + b2);
else
return ((b2<<8) + b3);
default:
// Implicit VR with 32-bit length...
vr = IMPLICIT_VR;
if (littleEndian)
return ((b3<<24) + (b2<<16) + (b1<<8) + b0);
else
return ((b0<<24) + (b1<<16) + (b2<<8) + b3);
}
}
int getNextTag() throws IOException {
int groupWord = getShort();
if (groupWord==0x0800 && bigEndianTransferSyntax) {
littleEndian = false;
groupWord = 0x0008;
}
int elementWord = getShort();
int tag = groupWord<<16 | elementWord;
elementLength = getLength();
// hack needed to read some GE files
// The element length must be even!
if (elementLength==13 && !oddLocations) elementLength = 10;
// "Undefined" element length.
// This is a sort of bracket that encloses a sequence of elements.
if (elementLength==-1) {
elementLength = 0;
inSequence = true;
}
//IJ.log("getNextTag: "+tag+" "+elementLength);
return tag;
}
FileInfo getFileInfo() throws IOException {
long skipCount;
FileInfo fi = new FileInfo();
int bitsAllocated = 16;
fi.fileFormat = fi.RAW;
fi.fileName = fileName;
if (directory.indexOf("://")>0) { // is URL
URL u = new URL(directory+fileName);
inputStream = new BufferedInputStream(u.openStream());
fi.inputStream = inputStream;
} else if (inputStream!=null)
fi.inputStream = inputStream;
else
fi.directory = directory;
fi.width = 0;
fi.height = 0;
fi.offset = 0;
fi.intelByteOrder = true;
fi.fileType = FileInfo.GRAY16_UNSIGNED;
fi.fileFormat = FileInfo.DICOM;
int samplesPerPixel = 1;
int planarConfiguration = 0;
String photoInterpretation = "";
if (inputStream!=null) {
// Use large buffer to allow URL stream to be reset after reading header
f = inputStream;
f.mark(400000);
} else
f = new BufferedInputStream(new FileInputStream(directory + fileName));
if (IJ.debugMode) {
IJ.log("");
IJ.log("DicomDecoder: decoding "+fileName);
}
int[] bytes = new int[ID_OFFSET];
for (int i=0; i-1||s.indexOf("1.2.5")>-1) {
f.close();
String msg = "ImageJ cannot open compressed DICOM images.\n \n";
msg += "Transfer Syntax UID = "+s;
throw new IOException(msg);
}
if (s.indexOf("1.2.840.10008.1.2.2")>=0)
bigEndianTransferSyntax = true;
break;
case MODALITY:
modality = getString(elementLength);
addInfo(tag, modality);
break;
case NUMBER_OF_FRAMES:
s = getString(elementLength);
addInfo(tag, s);
double frames = s2d(s);
if (frames>1.0)
fi.nImages = (int)frames;
break;
case SAMPLES_PER_PIXEL:
samplesPerPixel = getShort();
addInfo(tag, samplesPerPixel);
break;
case PHOTOMETRIC_INTERPRETATION:
photoInterpretation = getString(elementLength);
addInfo(tag, photoInterpretation);
break;
case PLANAR_CONFIGURATION:
planarConfiguration = getShort();
addInfo(tag, planarConfiguration);
break;
case ROWS:
fi.height = getShort();
addInfo(tag, fi.height);
break;
case COLUMNS:
fi.width = getShort();
addInfo(tag, fi.width);
break;
case IMAGER_PIXEL_SPACING: case PIXEL_SPACING:
String scale = getString(elementLength);
getSpatialScale(fi, scale);
addInfo(tag, scale);
break;
case SLICE_THICKNESS: case SLICE_SPACING:
String spacing = getString(elementLength);
fi.pixelDepth = s2d(spacing);
addInfo(tag, spacing);
break;
case BITS_ALLOCATED:
bitsAllocated = getShort();
if (bitsAllocated==8)
fi.fileType = FileInfo.GRAY8;
else if (bitsAllocated==32)
fi.fileType = FileInfo.GRAY32_UNSIGNED;
addInfo(tag, bitsAllocated);
break;
case PIXEL_REPRESENTATION:
int pixelRepresentation = getShort();
if (pixelRepresentation==1) {
fi.fileType = FileInfo.GRAY16_SIGNED;
signed = true;
}
addInfo(tag, pixelRepresentation);
break;
case WINDOW_CENTER:
String center = getString(elementLength);
int index = center.indexOf('\\');
if (index!=-1) center = center.substring(index+1);
windowCenter = s2d(center);
addInfo(tag, center);
break;
case WINDOW_WIDTH:
String width = getString(elementLength);
index = width.indexOf('\\');
if (index!=-1) width = width.substring(index+1);
windowWidth = s2d(width);
addInfo(tag, width);
break;
case RESCALE_INTERCEPT:
String intercept = getString(elementLength);
rescaleIntercept = s2d(intercept);
addInfo(tag, intercept);
break;
case RESCALE_SLOPE:
String slop = getString(elementLength);
rescaleSlope = s2d(slop);
addInfo(tag, slop);
break;
case RED_PALETTE:
fi.reds = getLut(elementLength);
addInfo(tag, elementLength/2);
break;
case GREEN_PALETTE:
fi.greens = getLut(elementLength);
addInfo(tag, elementLength/2);
break;
case BLUE_PALETTE:
fi.blues = getLut(elementLength);
addInfo(tag, elementLength/2);
break;
case PIXEL_DATA:
// Start of image data...
if (elementLength!=0) {
fi.offset = location;
addInfo(tag, location);
decodingTags = false;
} else
addInfo(tag, null);
break;
case 0x7F880010:
// What is this? - RAK
if (elementLength!=0) {
fi.offset = location+4;
decodingTags = false;
}
break;
default:
// Not used, skip over it...
addInfo(tag, null);
}
} // while(decodingTags)
if (fi.fileType==FileInfo.GRAY8) {
if (fi.reds!=null && fi.greens!=null && fi.blues!=null
&& fi.reds.length==fi.greens.length
&& fi.reds.length==fi.blues.length) {
fi.fileType = FileInfo.COLOR8;
fi.lutSize = fi.reds.length;
}
}
if (fi.fileType==FileInfo.GRAY32_UNSIGNED && signed)
fi.fileType = FileInfo.GRAY32_INT;
if (samplesPerPixel==3 && photoInterpretation.startsWith("RGB")) {
if (planarConfiguration==0)
fi.fileType = FileInfo.RGB;
else if (planarConfiguration==1)
fi.fileType = FileInfo.RGB_PLANAR;
} else if (photoInterpretation.endsWith("1 "))
fi.whiteIsZero = true;
if (!littleEndian)
fi.intelByteOrder = false;
if (IJ.debugMode) {
IJ.log("width: " + fi.width);
IJ.log("height: " + fi.height);
IJ.log("images: " + fi.nImages);
IJ.log("bits allocated: " + bitsAllocated);
IJ.log("offset: " + fi.offset);
}
if (inputStream!=null)
f.reset();
else
f.close();
return fi;
}
String getDicomInfo() {
String s = new String(dicomInfo);
char[] chars = new char[s.length()];
s.getChars(0, s.length(), chars, 0);
for (int i=0; i>>16;
//if (group!=previousGroup && (previousInfo!=null&&previousInfo.indexOf("Sequence:")==-1))
// dicomInfo.append("\n");
previousGroup = group;
previousInfo = info;
dicomInfo.append(tag2hex(tag)+info+"\n");
}
if (IJ.debugMode) {
if (info==null) info = "";
vrLetters[0] = (byte)(vr >> 8);
vrLetters[1] = (byte)(vr & 0xFF);
String VR = new String(vrLetters);
IJ.log("(" + tag2hex(tag) + VR
+ " " + elementLength
+ " bytes from "
+ (location-elementLength)+") "
+ info);
}
}
void addInfo(int tag, int value) throws IOException {
addInfo(tag, Integer.toString(value));
}
String getHeaderInfo(int tag, String value) throws IOException {
if (tag==ITEM_DELIMINATION || tag==SEQUENCE_DELIMINATION) {
inSequence = false;
if (!IJ.debugMode) return null;
}
String key = i2hex(tag);
//while (key.length()<8)
// key = '0' + key;
String id = (String)dictionary.get(key);
if (id!=null) {
if (vr==IMPLICIT_VR && id!=null)
vr = (id.charAt(0)<<8) + id.charAt(1);
id = id.substring(2);
}
if (tag==ITEM)
return id!=null?id+":":null;
if (value!=null)
return id+": "+value;
switch (vr) {
case FD:
if (elementLength==8)
value = Double.toString(getDouble());
else
for (int i=0; i44) value=null;
break;
case SQ:
value = "";
boolean privateTag = ((tag>>16)&1)!=0;
if (tag!=ICON_IMAGE_SEQUENCE && !privateTag)
break;
// else fall through and skip icon image sequence or private sequence
default:
long skipCount = (long)elementLength;
while (skipCount > 0) skipCount -= f.skip(skipCount);
location += elementLength;
value = "";
}
if (value!=null && id==null && !value.equals(""))
return "---: "+value;
else if (id==null)
return null;
else
return id+": "+value;
}
static char[] buf8 = new char[8];
/** Converts an int to an 8 byte hex string. */
String i2hex(int i) {
for (int pos=7; pos>=0; pos--) {
buf8[pos] = Tools.hexDigits[i&0xf];
i >>>= 4;
}
return new String(buf8);
}
char[] buf10;
String tag2hex(int tag) {
if (buf10==null) {
buf10 = new char[11];
buf10[4] = ',';
buf10[9] = ' ';
}
int pos = 8;
while (pos>=0) {
buf10[pos] = Tools.hexDigits[tag&0xf];
tag >>>= 4;
pos--;
if (pos==4) pos--; // skip coma
}
return new String(buf10);
}
double s2d(String s) {
if (s==null) return 0.0;
if (s.startsWith("\\"))
s = s.substring(1);
Double d;
try {d = new Double(s);}
catch (NumberFormatException e) {d = null;}
if (d!=null)
return(d.doubleValue());
else
return(0.0);
}
void getSpatialScale(FileInfo fi, String scale) {
double xscale=0, yscale=0;
int i = scale.indexOf('\\');
if (i>0) {
yscale = s2d(scale.substring(0, i));
xscale = s2d(scale.substring(i+1));
}
if (xscale!=0.0 && yscale!=0.0) {
fi.pixelWidth = xscale;
fi.pixelHeight = yscale;
fi.unit = "mm";
}
}
boolean dicmFound() {
return dicmFound;
}
}
class DicomDictionary {
Properties getDictionary() {
Properties p = new Properties();
for (int i=0; i
Ads help maintain this website.