package ua.stu.scplib.attribute;
import java.util.*;
import java.io.*;
import javax.imageio.*;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.spi.*;
import javax.imageio.event.IIOReadProgressListener;
import java.awt.image.*;
import java.util.zip.*;
/**
* <p>AttributeList class maintains a list of individual DICOM attributes.</p>
*
* <p>Instances of the class may be used for entire composite storage SOP instances, or fragments of such instances
* such as meta information headers, or simply as lists of attributes to be passed to other
* methods (e.g. lists of attributes to add or remove from another list).</p>
*
* <p>The class is actually implemented by extending {@link java.util.TreeMap java.util.TreeMap}
* as a map of {@link com.pixelmed.dicom.AttributeTag AttributeTag} keys to
* {@link com.pixelmed.dicom.Attribute Attribute} values. Consequently, all the methods
* of the underlying collection are available, including adding key-value pairs and
* extracting values by key. Iteration through the list of key-value pairs in
* the map is also supported, and the iterator returns values in the ascending numerical
* order of the {@link com.pixelmed.dicom.AttributeTag AttributeTag} keys, since
* that is how {@link com.pixelmed.dicom.AttributeTag AttributeTag} implements
* {@link java.lang.Comparable Comparable}.</p>
*
* <p>Note that large attribute values such as Pixel Data may be left on disk rather
* than actually read in when the list is created, and loaded on demand; extreme
* caution should be taken if the underlying file from which an AttributeList has
* been read is moved or renamed; a specific method, {@link #setFileUsedByOnDiskAttributes(File file) setFileUsedByOnDiskAttributes()},
* is provided to address this concern.</p>
*
* <p>The class provides methods for reading entire objects as a list of attributes,
* from files or streams. For example, the following fragment will read an entire
* object from the specified file and dump the contents of the attribute list:</p>
*
* <pre>
* AttributeList list = new AttributeList();
* list.read(arg[0],null,true,true);
* System.err.print(list);
* </pre>
*
* <p>Similarly, methods are provided for writing entire objects. For example, the
* previous fragment could be extended to write the list to a file unchanged as follows:</p>
*
* <pre>
* list.write(arg[1],TransferSyntax.ExplicitVRLittleEndian,true,true);
* </pre>
*
*<p>Note that in general, one would want to perform significantly more cleaning
* up before writing an object that has just been read, and a number of such
* methods are provided either in this class or on related classes
* as illustrated in this example:</p>
*
* <pre>
* AttributeList list = new AttributeList();
* list.read(arg[0],null,true,true);
* //list.removePrivateAttributes();
* list.removeGroupLengthAttributes();
* list.removeMetaInformationHeaderAttributes();
* list.remove(TagFromName.DataSetTrailingPadding);
* FileMetaInformation.addFileMetaInformation(list,TransferSyntax.ExplicitVRLittleEndian,"OURAETITLE");
* list.write(arg[1],TransferSyntax.ExplicitVRLittleEndian,true,true);
* </pre>
*
* <p>Note that this example is essentially the functionality of the {@link #main(String[]) main()} method
* of this class, which may be used as a copying utility when invoked with input and output file arguments.</p>
*
* <p>Individual attributes can be added or deleted as desired, either using a newly created
* list or one which has been read in from an existing object. For example, to zero out the
* patient's name one might do something like the following:</p>
*
* <pre>
* list.replaceWithZeroLengthIfPresent(TagFromName.PatientName);
* </pre>
*
* <p> or to replace it with a particular value one might do the following:</p>
* <pre>
* Attribute a = new PersonNameAttribute(TagFromName.PatientName);
* a.addValue(value);
* list.put(TagFromName.PatientName,a); // one could list.remove(TagFromName.PatientName) first, but this is implicit in the put
* </pre>
*
* <p>A more compact shorthand method for adding new (or replacing existing) attributes (if they are in the dictionary so that the VR can be determined) is also supplied:</p>
*
* <pre>
* list.putNewAttribute(TagFromName.PatientName);
* </pre>
*
* <p>and if a specific character set other than the default is in use:</p>
*
* <pre>
* list.putNewAttribute(TagFromName.PatientName,specificCharacterSet);
* </pre>
*
* <p>and since this method returns the generated attribute, values can easily be added as:</p>
*
* <pre>
* list.putNewAttribute(TagFromName.PatientName,specificCharacterSet).addValue("Blo^Joe");
* </pre>
*
*
* <p>Note also that the {@link com.pixelmed.dicom.Attribute Attribute} class provides some useful
* static methods for extracting and manipulating individual attributes within a list. For example:</p>
*
* <pre>
* String patientName=Attribute.getSingleStringValueOrNull(list,TagFromName.PatientName);
* </pre>
*
* <p>Ideally one should take care when adding or manipulating lists of attributes to handle
* the specific character set correctly and consistently when there is a possibility that it
* may be other than the default. The previous example of replacing the patient's name
* could be more properly rewritten as:</p>
*
* <pre>
* SpecificCharacterSet specificCharacterSet = new SpecificCharacterSet(Attribute.getStringValues(list,TagFromName.SpecificCharacterSet));
* Attribute a = new PersonNameAttribute(TagFromName.PatientName,specificCharacterSet);
* a.addValue(value);
* list.put(TagFromName.PatientName,a);
* </pre>
*
* <p>Note that in this example if the SpecificCharacterSet attribute were not found or was present but empty
* the various intervening methods would return null and the
* {@link com.pixelmed.dicom.SpecificCharacterSet#SpecificCharacterSet(String[]) SpecificCharacterSet()}
* constructor would use the default (ascii) character set.</p>
*
* <p>When an attribute list is read in, the SpecificCharacterSet attribute is automatically detected
* and set and applied to all string attributes as they are read in and converted to the internal
* string form which is used by Java (Unicode). The same applies when they are written, with some
* limitations on which character sets are supported.</p>
*
* @see com.pixelmed.dicom.Attribute
* @see com.pixelmed.dicom.AttributeTag
* @see com.pixelmed.dicom.FileMetaInformation
* @see com.pixelmed.dicom.SpecificCharacterSet
* @see com.pixelmed.dicom.TagFromName
* @see com.pixelmed.dicom.TransferSyntax
*
* @author dclunie
*/
public class AttributeList extends TreeMap {
/***/
private static final String identString = "@(#) $Header: /userland/cvs/pixelmed/imgbook/com/pixelmed/dicom/AttributeList.java,v 1.73 2007/08/01 11:41:24 dclunie Exp $";
//private final long maximumSaneFixedValueLength = 1000000000l; // 1GB seems large enough
private static boolean haveScannedForCodecs;
/***/
private static DicomDictionary dictionary;
/***/
private void createDictionaryifNecessary() {
if (dictionary == null) {
//System.err.println("AttributeList.createDictionaryifNecessary(): creating static dictionary");
dictionary = new DicomDictionary();
}
}
private void dumpListOfAllAvailableReaders(PrintStream out) {
String[] formats=ImageIO.getReaderFormatNames();
for (int i=0; formats != null && i<formats.length; ++i) {
out.println(formats[i]+":");
Iterator readers = ImageIO.getImageReadersByFormatName(formats[i]);
while (readers.hasNext()) {
ImageReader reader = (ImageReader)readers.next();
ImageReaderSpi spi = reader.getOriginatingProvider();
out.println("\t"+spi.getDescription(Locale.US)+" "+spi.getVendorName()+" "+spi.getVersion());
}
}
}
private class OurIIOReadProgressListener implements IIOReadProgressListener {
public void imageComplete(ImageReader source) {
//System.out.println("OurIIOReadProgressListener:imageComplete()");
}
public void imageProgress(ImageReader source,float percentageDone) {
//System.out.println("OurIIOReadProgressListener:imageProgress(): percentageDone="+percentageDone);
}
public void imageStarted(ImageReader source,int imageIndex) {
//System.out.println("OurIIOReadProgressListener:imageStarted(): imageIndex="+imageIndex);
}
public void readAborted(ImageReader source) {
//System.out.println("OurIIOReadProgressListener:readAborted()");
}
public void sequenceComplete(ImageReader source) {
//System.out.println("OurIIOReadProgressListener:sequenceComplete()");
}
public void sequenceStarted(ImageReader source,int minIndex) {
//System.out.println("OurIIOReadProgressListener:sequenceStarted(): minIndex="+minIndex);
}
public void thumbnailComplete(ImageReader source) {
//System.out.println("OurIIOReadProgressListener:thumbnailComplete()");
}
public void thumbnailProgress(ImageReader source,float percentageDone) {
//System.out.println("OurIIOReadProgressListener:thumbnailProgress(): percentageDone="+percentageDone);
}
public void thumbnailStarted(ImageReader source,int imageIndex,int thumbnailIndex) {
//System.out.println("OurIIOReadProgressListener:thumbnailStarted(): imageIndex="+imageIndex+" thumbnailIndex="+thumbnailIndex);
}
}
/**
* @param i
* @exception IOException
*/
private AttributeTag readAttributeTag(DicomInputStream i) throws IOException {
int group = i.readUnsigned16();
int element = i.readUnsigned16();
return new AttributeTag(group,element);
}
/**
* @param a
* @param i
* @param byteOffset
* @param lengthToRead
* @param specificCharacterSet
* @exception IOException
* @exception DicomException
*/
private long readNewSequenceAttribute(Attribute a,DicomInputStream i,long byteOffset,long lengthToRead,SpecificCharacterSet specificCharacterSet) throws IOException, DicomException {
boolean undefinedLength = lengthToRead == 0xffffffffl;
long endByteOffset=(undefinedLength) ? 0xffffffffl : byteOffset+lengthToRead-1;
//System.err.println("readNewSequenceAttribute: start byteOffset="+byteOffset+" lengthToRead="+lengthToRead+" endByteOffset="+endByteOffset);
try {
// CBZip2InputStream.available() always returns zero, and since we terminate
// on exceptions anyway, just forget about it
while (/*i.available() > 0 && */(undefinedLength || byteOffset < endByteOffset)) {
//System.err.println("readNewSequenceAttribute: loop byteOffset="+byteOffset);
long itemStartOffset=byteOffset;
AttributeTag tag = readAttributeTag(i);
byteOffset+=4;
//System.err.println("readNewSequenceAttribute: tag="+tag);
long vl = i.readUnsigned32(); // always implicit VR form for items and delimiters
byteOffset+=4;
//System.err.println(byteOffset+" "+tag+" VL=<0x"+Long.toHexString(vl)+">");
if (tag.equals(TagFromName.SequenceDelimitationItem)) {
//System.err.println("readNewSequenceAttribute: SequenceDelimitationItem");
break;
}
else if (tag.equals(TagFromName.Item)) {
//System.err.println("readNewSequenceAttribute: Item byteOffset="+byteOffset);
AttributeList list = new AttributeList();
byteOffset=list.read(i,byteOffset,vl,false,specificCharacterSet);
//System.err.println("readNewSequenceAttribute: back from reading Item byteOffset="+byteOffset);
((SequenceAttribute)a).addItem(list,itemStartOffset);
}
else {
throw new DicomException("Bad tag "+tag+"(not Item or Sequence Delimiter) in Sequence at byte offset "+byteOffset);
}
}
}
catch (EOFException e) {
//System.err.println("Closing on "+e);
if (!undefinedLength) throw new EOFException();
}
catch (IOException e) {
//System.err.println("Closing on "+e);
if (!undefinedLength) throw new IOException(); // InflaterInputStream seems to throw IOException rather than EOFException
}
//System.err.println("readNewSequenceAttribute: return byteOffset="+byteOffset);
return byteOffset;
}
/**
* @param i
* @param byteOffset
* @param lengthToRead
* @param stopAfterMetaInformationHeader
* @param specificCharacterSet
* @exception IOException
* @exception DicomException
*/
private long read(DicomInputStream i,long byteOffset,long lengthToRead,boolean stopAfterMetaInformationHeader,SpecificCharacterSet specificCharacterSet) throws IOException, DicomException {
return read(i,byteOffset,lengthToRead,stopAfterMetaInformationHeader,specificCharacterSet,null);
}
/**
* @param i
* @param byteOffset
* @param lengthToRead
* @param stopAfterMetaInformationHeader
* @param specificCharacterSet
* @param stopAtTag the tag (in the top level data set) at which to stop
* @exception IOException
* @exception DicomException
*/
private long read(DicomInputStream i,long byteOffset,long lengthToRead,boolean stopAfterMetaInformationHeader,
SpecificCharacterSet specificCharacterSet,AttributeTag stopAtTag) throws IOException, DicomException {
//System.err.println("read: Stop tag is "+stopAtTag);
if (i.areReadingDataSet()) {
// Test to see whether or not a codec needs to be pushed on the stream ... after the first time, the TransferSyntax will always be ExplicitVRLittleEndian
//System.err.println("Testing for deflate and bzip2 TS");
if (i.getTransferSyntaxToReadDataSet().isDeflated()) {
// insert deflate into input stream and make a new DicomInputStream
//System.err.println("Creating new DicomInputStream from deflate");
i = new DicomInputStream(new InflaterInputStream(i,new Inflater(true)),TransferSyntax.ExplicitVRLittleEndian,false);
byteOffset=0;
}
else if (i.getTransferSyntaxToReadDataSet().isBzip2ed()) {
// insert bzip2 into input stream and make a new DicomInputStream
//System.err.println("Creating new DicomInputStream from bzip2");
try {
Class classToUse = Thread.currentThread().getContextClassLoader().loadClass("org.apache.excalibur.bzip2.CBZip2InputStream");
Class [] argTypes = {InputStream.class};
Object[] argValues = {i};
InputStream bzipInputStream = (InputStream)(classToUse.getConstructor(argTypes).newInstance(argValues));
i = new DicomInputStream(bzipInputStream,TransferSyntax.ExplicitVRLittleEndian,false);
byteOffset=0;
}
catch (java.lang.reflect.InvocationTargetException e) {
throw new DicomException("Not a correctly encoded bzip2 bitstream - "+e);
}
catch (Exception e) { // may be ClassNotFoundException,NoSuchMethodException,InstantiationException
throw new DicomException("Could not instantiate bzip2 codec - "+e);
}
}
}
createDictionaryifNecessary();
boolean undefinedLength = lengthToRead == 0xffffffffl;
long endByteOffset=(undefinedLength) ? 0xffffffffl : byteOffset+lengthToRead-1;
//System.err.println("read: start byteOffset="+byteOffset+" endByteOffset="+endByteOffset+" lengthToRead="+lengthToRead);
byte vrBuffer[] = new byte[2];
boolean explicit = i.getTransferSyntaxInUse().isExplicitVR();
// keep track of pixel data size in case need VL for encapsulated data ...
int rows = 0;
int columns = 0;
int frames = 1;
int samplesPerPixel = 1;
int bytesPerSample = 0;
AttributeTag tag = null;
try {
// CBZip2InputStream.available() always returns zero, and since we terminate
// on exceptions anyway, just forget about it
while (/*i.available() > 0 && */(undefinedLength || byteOffset < endByteOffset)) {
//System.err.println("read: i.available()="+i.available());
//System.err.println("read: loop byteOffset="+byteOffset+" endByteOffset="+endByteOffset);
tag = readAttributeTag(i);
byteOffset+=4;
//System.err.println("read: tag="+tag);
if (stopAtTag != null && tag.equals(stopAtTag)) {
//System.err.println("read: stopped at "+tag);
return byteOffset; // stop now, since we have reached the tag at which we were told to stop
}
if (tag.equals(TagFromName.ItemDelimitationItem)) {
//System.err.println("read: ItemDelimitationItem");
// Read and discard value length
i.readUnsigned32();
byteOffset+=4;
return byteOffset; // stop now, since we must have been called to read an item's dataset
}
if (tag.equals(TagFromName.Item)) {
// this is bad ... there shouldn't be Items here since they should
// only be found during readNewSequenceAttribute()
// however, try to work around Philips bug ...
long vl = i.readUnsigned32(); // always implicit VR form for items and delimiters
byteOffset+=4;
System.err.println("Ignoring bad Item at "+byteOffset+" "+tag+" VL=<0x"+Long.toHexString(vl)+">");
// let's just ignore it for now
continue;
}
byte vr[];
if (explicit) {
vr=vrBuffer;
i.readInsistently(vr,0,2);
byteOffset+=2;
}
else {
vr = dictionary.getValueRepresentationFromTag(tag);
if (vr == null) {
vr=vrBuffer;
vr[0]='U';
vr[1]='N';
}
}
long vl;
if (explicit) {
if (ValueRepresentation.isShortValueLengthVR(vr)) {
vl=i.readUnsigned16();
byteOffset+=2;
}
else {
i.readUnsigned16(); // reserved bytes
vl=i.readUnsigned32();
byteOffset+=6;
}
}
else {
vl=i.readUnsigned32();
byteOffset+=4;
}
if (explicit) {
// do not do this until AFTER the value length has been read, since explicit UN uses the long form of length
if (ValueRepresentation.isUnknownVR(vr)) {
byte vrd[] = dictionary.getValueRepresentationFromTag(tag);
if (vrd != null && vrd.length >= 2) {
//System.err.println("AttributeList.read(): For tag "+tag+" consider overriding explicit VR "+ValueRepresentation.getAsString(vr)+" with "+ValueRepresentation.getAsString(vrd));
if (!ValueRepresentation.isSequenceVR(vrd)) {
//System.err.println("AttributeList.read(): For tag "+tag+" overriding explicit VR "+ValueRepresentation.getAsString(vr)+" with "+ValueRepresentation.getAsString(vrd));
vr[0] = vrd[0];
vr[1] = vrd[1];
}
}
}
}
//System.err.println(byteOffset+" "+tag+" VR=<"+ValueRepresentation.getAsString(vr)+"> VL=<0x"+Long.toHexString(vl)+">");
Attribute a = null;
if (ValueRepresentation.isSequenceVR(vr) || (ValueRepresentation.isUnknownVR(vr) && vl == 0xffffffffl)) {
a=new SequenceAttribute(tag);
byteOffset=readNewSequenceAttribute(a,i,byteOffset,vl,specificCharacterSet);
}
else if (vl != 0xffffffffl) {
//if (vl > maximumSaneFixedValueLength) throw new DicomException("unlikely fixed VL ("+vl+" dec, 0x"+Long.toHexString(vl)+") - probably incorrect dataset");
a = AttributeFactory.newAttribute(tag,vr,vl,i,specificCharacterSet,explicit,bytesPerSample,byteOffset); // creates and reads the attribute
byteOffset+=vl;
}
else if (vl == 0xffffffffl && tag.equals(TagFromName.PixelData)/* && i.getTransferSyntaxInUse().isEncapsulated()*/) { // assume encapsulated in case TS is not recognized
int wordsPerFrame = rows*columns*samplesPerPixel;
//System.err.println("Undefined length encapsulated Pixel Data: words per frame "+wordsPerFrame);
String tsuid = i.getTransferSyntaxInUse().getUID();
//System.err.println("Undefined length encapsulated Pixel Data: TransferSyntax UID "+tsuid);
boolean doneReadingEncapsulatedData = false;
EncapsulatedInputStream ei = new EncapsulatedInputStream(i);
//try {
{
if (tsuid.equals(TransferSyntax.PixelMedEncapsulatedRawLittleEndian)) {
if (bytesPerSample == 1) {
byte[] values = new byte[wordsPerFrame*frames];
for (int f=0; f<frames; ++f) {
ei.read(values,f*wordsPerFrame,wordsPerFrame);
//ei.nextFrame();
}
a = new OtherByteAttribute(tag);
a.setValues(values);
doneReadingEncapsulatedData=true;
}
else if (bytesPerSample == 2) {
short[] values = new short[wordsPerFrame*frames];
for (int f=0; f<frames; ++f) {
ei.readUnsigned16(values,f*wordsPerFrame,wordsPerFrame);
//ei.nextFrame();
}
a = new OtherWordAttribute(tag);
a.setValues(values);
doneReadingEncapsulatedData=true;
}
else {
throw new DicomException("Encapsulated data of more than 2 bytes per sample not supported (got "+bytesPerSample+")");
}
}
else {
if (!haveScannedForCodecs) {
System.err.println("AttributeList.read(): Scanning for ImageIO plugin codecs");
ImageIO.scanForPlugins();
haveScannedForCodecs=true;
}
String readerWanted = null;
if (tsuid.equals(TransferSyntax.JPEGBaseline) || tsuid.equals(TransferSyntax.JPEGExtended)) {
readerWanted="JPEG";
//System.err.println("Undefined length encapsulated Pixel Data in JPEG Baseline");
}
else if (tsuid.equals(TransferSyntax.JPEG2000) || tsuid.equals(TransferSyntax.JPEG2000Lossless)) {
readerWanted="JPEG2000";
//System.err.println("Undefined length encapsulated Pixel Data in JPEG 2000");
}
else if (tsuid.equals(TransferSyntax.JPEGLossless) || tsuid.equals(TransferSyntax.JPEGLosslessSV1)) {
readerWanted="jpeg-lossless";
//System.err.println("Undefined length encapsulated Pixel Data in JPEG Lossless");
}
else if (tsuid.equals(TransferSyntax.JPEGLS) || tsuid.equals(TransferSyntax.JPEGNLS)) {
readerWanted="jpeg-ls";
//System.err.println("Undefined length encapsulated Pixel Data in JPEG-LS");
}
if (readerWanted != null) {
ImageReader reader = null;
ImageReaderSpi spi = null;
try {
reader = (ImageReader)(ImageIO.getImageReadersByFormatName(readerWanted).next());
spi = reader.getOriginatingProvider();
System.out.println("Using reader from "+spi.getDescription(Locale.US)+" "+spi.getVendorName()+" "+spi.getVersion());
OurIIOReadProgressListener progressListener = new OurIIOReadProgressListener();
reader.addIIOReadProgressListener(progressListener);
//System.out.println("Back from reader.addIIOReadProgressListener()");
}
catch (Exception e) {
dumpListOfAllAvailableReaders(System.err);
throw new DicomException("No reader for "+readerWanted+" available for Transfer Syntax "+tsuid);
}
if (reader != null) {
byte[] bytePixelData = null; // lazy instantiation of one or the other
short[] shortPixelData = null;
int pixelsPerFrame = columns*rows*samplesPerPixel;
int pixelsPerMultiFrameImage = pixelsPerFrame*frames;
for (int f=0; f<frames; ++f) {
//System.out.println("Starting frame "+f);
BufferedImage image = null;
ImageInputStream iiois = ImageIO.createImageInputStream(ei);
reader.setInput(iiois,true/*seekForwardOnly*/,true/*ignoreMetadata*/);
image = reader.read(0);
//System.out.println("Back from frame "+f+" reader.read(), BufferedImage="+image);
if (image == null) {
throw new DicomException("Reader "+spi.getDescription(Locale.US)+" "+spi.getVendorName()+" "+spi.getVersion()
+" returned null image for Transfer Syntax "+tsuid);
}
else {
Raster raster = image.getData();
int numDataElements = raster.getNumDataElements();
//System.out.println("getNumDataElements="+numDataElements);
if (numDataElements == samplesPerPixel) {
int transferType = raster.getTransferType();
//System.out.println("getTransferType="+transferType);
if (transferType == DataBuffer.TYPE_BYTE) {
//System.out.println("Getting "+(samplesPerPixel > 1 ? "interleaved " : "")+samplesPerPixel+" channel byte data");
byte[] vPixelData = (byte[])(raster.getDataElements(0,0,columns,rows,null));
//System.out.println("Decompressed byte array length "+vPixelData.length+" expected "+pixelsPerFrame);
if (bytePixelData == null) {
if (frames == 1) {
bytePixelData = vPixelData;
}
else {
bytePixelData = new byte[pixelsPerMultiFrameImage];
}
}
if (vPixelData != null) {
System.arraycopy(vPixelData,0,bytePixelData,pixelsPerFrame*f,pixelsPerFrame);
}
}
else if (transferType == DataBuffer.TYPE_SHORT
|| transferType == DataBuffer.TYPE_USHORT) {
//System.out.println("Getting "+(samplesPerPixel > 1 ? "interleaved " : "")+samplesPerPixel+" channel byte data");
short[] vPixelData = (short[])(raster.getDataElements(0,0,columns,rows,null));
//System.out.println("Decompressed short array length "+vPixelData.length+" expected "+pixelsPerFrame);
if (shortPixelData == null) {
if (frames == 1) {
shortPixelData = vPixelData;
}
else {
shortPixelData = new short[pixelsPerMultiFrameImage];
}
}
if (vPixelData != null) {
System.arraycopy(vPixelData,0,shortPixelData,pixelsPerFrame*f,pixelsPerFrame);
}
}
}
}
ei.nextFrame();
}
if (bytePixelData != null) {
a = new OtherByteAttribute(tag);
a.setValues(bytePixelData);
}
else if (shortPixelData != null) {
a = new OtherWordAttribute(tag);
a.setValues(shortPixelData);
}
doneReadingEncapsulatedData=true;
}
}
else {
throw new DicomException("Unrecognized Transfer Syntax "+tsuid);
}
}
}
//catch (Exception e) {
// e.printStackTrace(System.err);
//}
if (!doneReadingEncapsulatedData) {
//System.out.println("Skipping encapsulated pixel data");
while (ei.skip(1024) > 0); // it is appropriate to use skip() rather than use skipInsistently() here
}
}
if (a != null) {
//System.err.println(a.toString());
put(tag,a);
if (tag.equals(TagFromName.FileMetaInformationGroupLength)) {
if (i.areReadingMetaHeader()) {
//System.err.println("Found meta-header");
//System.err.println("Length attribute class="+a.getClass());
long metaLength=a.getSingleIntegerValueOrDefault(0);
byteOffset=read(i,byteOffset,metaLength,false,null,stopAtTag); // detects and sets transfer syntax for reading dataset
i.setReadingDataSet();
if (stopAfterMetaInformationHeader) {
//System.err.println("Stopping after meta-header");
break;
}
else {
//System.err.println("Calling read");
byteOffset=read(i,byteOffset,0xffffffffl,false,null,stopAtTag); // read to end (will detect and set own SpecificCharacterSet)
//System.err.println("Back from read after metaheader: now undefinedLength="+undefinedLength+" byteOffset="+byteOffset+" endByteOffset="+endByteOffset);
break; // ... no plausible reason to continue past this point
}
}
else {
// ignore it, e.g. nested within a sequence item (GE bug).
//System.err.println("Ignoring unexpected FileMetaInformationGroupLength outside meta information header");
}
}
else if (tag.equals(TagFromName.TransferSyntaxUID)) {
if (i.areReadingMetaHeader()) {
i.setTransferSyntaxToReadDataSet(new TransferSyntax(a.getSingleStringValueOrDefault(TransferSyntax.ExplicitVRLittleEndian)));
}
else {
// ignore it, e.g. nested within a sequence item (GE bug).
//System.err.println("Ignoring unexpected TransferSyntaxUID outside meta information header");
}
}
else if (tag.equals(TagFromName.SpecificCharacterSet)) {
specificCharacterSet = new SpecificCharacterSet(a.getStringValues(),a.getByteValues());
}
else if (tag.equals(TagFromName.Columns)) {
columns = a.getSingleIntegerValueOrDefault(0);
}
else if (tag.equals(TagFromName.Rows)) {
rows = a.getSingleIntegerValueOrDefault(0);
}
else if (tag.equals(TagFromName.NumberOfFrames)) {
frames = a.getSingleIntegerValueOrDefault(1);
}
else if (tag.equals(TagFromName.SamplesPerPixel)) {
samplesPerPixel = a.getSingleIntegerValueOrDefault(1);
}
else if (tag.equals(TagFromName.BitsAllocated)) {
bytesPerSample = (a.getSingleIntegerValueOrDefault(16)-1)/8+1;
}
}
}
}
catch (EOFException e) {
//System.err.println("Closing on "+e);
if (!undefinedLength) throw new EOFException();
}
catch (IOException e) {
//System.err.println("Closing on "+e);
if (!undefinedLength) throw new IOException(); // InflaterInputStream seems to throw IOException rather than EOFException
}
return byteOffset;
}
/**
* <p>Read the meta information header (if present) and then stop.</p>
*
* <p>Leaves the stream opened and positioned at the start of the data set.</p>
*
* @param i the stream to read from
* @exception IOException
* @exception DicomException
*/
public void readOnlyMetaInformationHeader(DicomInputStream i) throws IOException, DicomException {
read(i,i.getByteOffsetOfStartOfData(),0xffffffffl,true,null);
//System.err.println("readOnlyMetaInformationHeader(): afterwards i.areReadingDataSet()="+i.areReadingDataSet());
// important that i.areReadingDataSet() be true at this point ... triggers check for codec if read (or copied) further
}
/**
* <p>Read all the DICOM attributes in the stream until the specified tag is encountered.</p>
*
* <p>Does not read beyond the group element pair of the specified stop tag.</p>
*
* <p>Leaves the stream open.</p>
*
* @param i the stream to read from
* @param tag the tag (in the top level data set) at which to stop
* @exception IOException
* @exception DicomException
*/
public void read(DicomInputStream i,AttributeTag tag) throws IOException, DicomException {
//System.err.println("read(DicomInputStream i,AttributeTag tag="+tag+"):");
read(i,i.getByteOffsetOfStartOfData(),0xffffffffl,false,null,tag);
}
/**
* <p>Read all the DICOM attributes in the stream until there are no more.</p>
*
* <p>Leaves the stream open.</p>
*
* @param i the stream to read from
* @exception IOException
* @exception DicomException
*/
public void read(DicomInputStream i) throws IOException, DicomException {
read(i,i.getByteOffsetOfStartOfData(),0xffffffffl,false,null);
}
/**
* <p>Read an entire DICOM object in the specified file.</p>
*
* <p>Returns the attributes of both the meta information header (if present) and data set.</p>
*
* @param name the input file name
* @param transferSyntaxUID the transfer syntax to use for the data set (leave null for autodetection)
* @param hasMeta look for a meta information header
* @param useBufferedStream buffer the input for better performance
* @exception IOException
* @exception DicomException
*/
public void read(String name,String transferSyntaxUID,boolean hasMeta,boolean useBufferedStream) throws IOException, DicomException {
InputStream i = new FileInputStream(name);
if (useBufferedStream) i=new BufferedInputStream(i);
DicomInputStream di = new DicomInputStream(i,transferSyntaxUID,hasMeta);
try {
read(di);
}
finally {
di.close();
}
}
/**
* <p>Read an entire DICOM object in the specified file.</p>
*
* <p>Returns the attributes of both the meta information header (if present) and data set.</p>
*
* <p>Always tries to automatically detect the meta information header or transfer syntax
* if no meta information header and buffers the input for better performance.</p>
*
* @param name the input file name
* @exception IOException
* @exception DicomException
*/
public void read(String name) throws IOException, DicomException {
read(name,null,true,true);
}
/**
* <p>Associates the specified value (attribute) with the specified key (tag).</p>
*
* <p>If the map previously contained a mapping for this key, the old value is replaced.</p>
*
* <p>This untyped method is present to over ride the super class method to be sure that
* null values are ever inserted.</p>
*
* @param key key (tag) with which the specified value (attribute) is to be associated
* @param value value (attribute) to be associated with the specified key (tag)
* @return previous value associated with specified key, or null if there was no mapping for key
* @exception NullPointerException thrown if a or t is null
*/
public Object put(Object key, Object value) {
if (key == null || value == null) {
throw new NullPointerException();
}
else {
return super.put(key,value);
}
}
/**
* <p>Associates the specified value (attribute) with the specified key (tag).</p>
*
* <p>If the map previously contained a mapping for this key, the old value is replaced.</p>
*
* @see java.util.TreeMap#put(Object,Object)
*
* @param t key (tag) with which the specified value (attribute) is to be associated
* @param a value (attribute) to be associated with the specified key (tag)
* @return previous value (attribute) associated with specified key (tag), or null if there was no mapping for key (tag)
* @exception NullPointerException thrown if a or t is null
*/
public Attribute put(AttributeTag t, Attribute a) {
if (a == null || t == null) {
throw new NullPointerException();
}
else {
return (Attribute)(super.put(t,a));
}
}
/**
* <p>Associates the specified value (attribute) with the key that is the existing tag of the attribute.</p>
*
* <p>If the map previously contained a mapping for this key, the old value is replaced.</p>
*
* @see #put(AttributeTag,Attribute)
*
* @param a value (attribute) to be associated with the specified key (tag)
* @return previous value (attribute) associated with specified key (tag), or null if there was no mapping for key (tag)
* @exception NullPointerException thrown if a or t is null
*/
public Attribute put(Attribute a) {
if (a == null) {
throw new NullPointerException();
}
else {
return put(a.getTag(),a);
}
}
/**
* <p>Returns the value (attribute) to which this map maps the specified key (tag).</p>
*
* <p>Returns null if the map contains no mapping for this key. A return value of null
* does indicate that the map contains no mapping for the key, unlike {@link java.util.TreeMap#get(Object) java.util.get(Object)}
* since the put operation checks for and disallows null insertions. This contract will hold
* true unless one goes to great effort to insert a key that maps to a null value
* by using one of the other insertion methods of the super class, in which case
* other operations (like writing) may fail later with a NullPointerException.</p>
*
* @param t key (tag) whose associated value (attribute) is to be returned
*/
public Attribute get(AttributeTag t) {
return (Attribute)(super.get(t));
}
/**
* <p>Determine whether or not this list is an image.</p>
*
* <p>An image is defined to be something with a PixelData attribute at the top level.</p>
*
* @return true if an image
*/
public boolean isImage() {
return get(TagFromName.PixelData) != null;
}
/**
* <p>Determine whether or not this list is an SR Document.</p>
*
* <p>An SR Document is defined to be something with a ContentSequence attribute at the top level.</p>
*
* @return true if an SR Document
*/
public boolean isSRDocument() {
return get(TagFromName.ContentSequence) != null;
}
/**
* <p>Get the dictionary in use for this list.</p>
*
* <p>Creates one if necessary.</p>
*
* @return the dictionary
*/
public DicomDictionary getDictionary() {
createDictionaryifNecessary();
return dictionary;
}
/**
* <p>Removes the mapping for this key (tag), if present.</p>
*
* @param tag key (tag) for which mapping should be removed
* @return previous value (attribute) associated with specified key (tag), or null if there was no mapping for key (tag)
*/
public Attribute remove(AttributeTag tag) {
return (Attribute)(super.remove(tag));
}
// useful list handling routines beyond those inherited from TreeMap
/**
* <p>Replaces an attribute with a zero length attribute, if present in the list.</p>
*
* <p>Does nothing if the attribute was not already present.</p>
*
* @param tag key (tag) for which the attribute should be replaced
* @exception DicomException thrown if there is any difficulty creating the new zero length attribute
*/
public void replaceWithZeroLengthIfPresent(AttributeTag tag) throws DicomException {
Object o=get(tag);
if (o != null) {
//remove(tag);
Attribute a = AttributeFactory.newAttribute(tag,getDictionary().getValueRepresentationFromTag(tag));
put(tag,a);
}
}
// list management methods ...
/**
* <p>Remove any private attributes present in the list.</p>
*
* <p>Private attributes are all those with an odd group number.</p>
*/
public void removePrivateAttributes() {
Iterator i = values().iterator();
while (i.hasNext()) {
Attribute a = (Attribute)i.next();
if (a.getTag().isPrivate()) i.remove();
}
}
/**
* <p>Remove any meta information header attributes present in the list.</p>
*
* <p>Meta information header attributes are all those in group 0x0002.</p>
*
* <p>Note that this should always be done when modifying the SOP Class or
* Instance UID of an attribute list what has been read before writing,
* since it is vital that the corresponding meta information header attributes
* match those in the data set.</p>
*
* @see com.pixelmed.dicom.FileMetaInformation
*/
public void removeMetaInformationHeaderAttributes() {
Iterator i = values().iterator();
while (i.hasNext()) {
Attribute a = (Attribute)i.next();
if (a.getTag().getGroup() == 0x0002) i.remove();
}
}
/**
* <p>Remove any group length attributes present in the list, except the meta information header length, as well as LengthToEnd.</p>
*
* <p>Group length attributes are all those with an element of 0x0000.</p>
*
* <p>LengthToEnd (0x0008,0x0001) is always removed if present as well.</p>
*
* <p>These have never been required in DICOM and are a holdover from the old
* ACR-NEMA days, and are a source of constant problems, so should always
* be removed.</p>
*
* <p>The meta information header length is left alone, since it is mandatory.</p>
*
* @see com.pixelmed.dicom.FileMetaInformation
*/
public void removeGroupLengthAttributes() {
Iterator i = values().iterator();
while (i.hasNext()) {
Attribute a = (Attribute)i.next();
AttributeTag t = a.getTag();
if (t.getElement() == 0x0000 && t.getGroup() != 0x0002) i.remove(); // leave metaheader alone
}
remove(TagFromName.LengthToEnd);
}
// Miscellaneous methods ...
/**
* <p>Dump the contents of the attribute list as a human-readable string.</p>
*
* <p>Each attribute is written to a separate line, in the form defined
* for {@link com.pixelmed.dicom.Attribute#toString() com.pixelmed.dicom.Attribute.toString()}.</p>
*
* @return the string
*/
public String toString() {
StringBuffer str = new StringBuffer();
Iterator i = values().iterator();
while (i.hasNext()) {
str.append(((Attribute)i.next()).toString(dictionary));
str.append("\n");
}
return str.toString();
}
/**
* <p>Dump the contents of the attribute list as a human-readable string.</p>
*
* <p>Each attribute is written to a separate line, in the form defined
* for {@link com.pixelmed.dicom.Attribute#toString(DicomDictionary dictionary) com.pixelmed.dicom.Attribute.toString(DicomDictionary dictionary)}.</p>
*
* @param dictionary the dictionary to use to look up the name
* @return the string
*/
public String toString(DicomDictionary dictionary) {
StringBuffer str = new StringBuffer();
Iterator i = values().iterator();
while (i.hasNext()) {
str.append(((Attribute)i.next()).toString(dictionary));
str.append("\n");
}
return str.toString();
}
/**
* <p>Change the file containing the data used by any attribute whose values are left on disk, for example if the file has been renamed.</p>
*
* @param file the new file containing the data
*/
public void setFileUsedByOnDiskAttributes(File file) {
//System.err.println("AttributeList.setFileUsedByOnDiskAttributes(): file = "+file);
Iterator i = values().iterator();
while (i.hasNext()) {
Attribute a = (Attribute)i.next();
//System.err.println("AttributeList.setFileUsedByOnDiskAttributes(): checking "+a.getClass()+" - "+a.toString(dictionary));
if (a instanceof OtherByteAttributeOnDisk) {
//System.err.println("AttributeList.setFileUsedByOnDiskAttributes(): setting OtherByteAttributeOnDisk to file = "+file);
((OtherByteAttributeOnDisk)a).setFile(file);
}
else if (a instanceof OtherWordAttributeOnDisk) {
//System.err.println("AttributeList.setFileUsedByOnDiskAttributes(): setting OtherWordAttributeOnDisk to file = "+file);
((OtherWordAttributeOnDisk)a).setFile(file);
}
}
}
/**
* <p>Create a new attribute with the specified tag and insert it in the map associating the generated attribute with the specified tag as the key.</p>
*
* <p>If the map previously contained a mapping for the tag (key), the old value is replaced.</p>
*
* @param t key ({@link com.pixelmed.dicom.AttributeTag AttributeTag} tag) with which the generated attribute is to be associated
* @param specificCharacterSet the {@link com.pixelmed.dicom.SpecificCharacterSet SpecificCharacterSet} to be used text values
* @return the newly created attribute
* @exception DicomException if cannot create attribute, such as if cannot find tag in dictionary
*/
public Attribute putNewAttribute(AttributeTag t,SpecificCharacterSet specificCharacterSet) throws DicomException {
Attribute a = null;
byte[] vr = getDictionary().getValueRepresentationFromTag(t);
if (vr == null) {
throw new DicomException("No such data element as "+t+" in dictionary");
}
else {
a = AttributeFactory.newAttribute(t,vr,specificCharacterSet);
if (a == null) {
throw new DicomException("Could not create attribute for tag "+t);
}
else {
super.put(t,a);
}
}
return a;
}
/**
* <p>Create a new attribute with the specified tag and insert it in the map associating the generated attribute with the specified tag as the key.</p>
*
* <p>If the map previously contained a mapping for the tag (key), the old value is replaced.</p>
*
* @param t key ({@link com.pixelmed.dicom.AttributeTag AttributeTag} tag) with which the generated attribute is to be associated
* @return the newly created attribute
* @exception DicomException if cannot create attribute, such as if cannot find tag in dictionary
*/
public Attribute putNewAttribute(AttributeTag t) throws DicomException {
return putNewAttribute(t,null);
}
}