
ij.plugin.DICOM Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ij Show documentation
Show all versions of ij Show documentation
ImageJ is an open source Java image processing program inspired by NIH Image for the Macintosh.
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, [email protected]) 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.open(false);
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);
}
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);
}
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
© 2015 - 2025 Weber Informatics LLC | Privacy Policy