All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ij.plugin.DICOM Maven / Gradle / Ivy

Go to download

ImageJ is an open source Java image processing program inspired by NIH Image for the Macintosh.

There is a newer version: 1.54m
Show newest version
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