package ua.stu.scplib.attribute;
import java.io.*;
// for test timing of routines
/**
* <p>A class that extends {@link java.io.InputStream InputStream} by adding
* a mechanism for unecapsulating an undefined length DICOM attribute, such as is used for
* compressed Pixel Data.</p>
*
* <p>The read methods hide the fact that the data is encapsulated by removing the Items and
* Item and Sequence delimiter tags, as well as skipping any Basic Offset Table
* that may be present in the first Item.</p>
*
* <p>Since an individual frame may be fragmented and padded woth 0xff bytes beyond the
* JPEG EOI marker (0xffd9), and since the codec used for decoding may be "reading ahead"
* this class also removes any padding 0xff bytes at the end of any fragment, back as
* far as the EOI marker. Note that this means that theorectically frames could span
* fragments as long as there was no padding between them.</p>
*
* @author dclunie
*/
public class EncapsulatedInputStream extends InputStream {
/***/
private BinaryInputStream i;
/***/
private boolean bigEndian;
/***/
private byte buffer[]; // just for read() one byte method
/***/
private boolean firstTime;
/***/
private byte fragment[];
/***/
private int fragmentSize;
/***/
private int fragmentOffset;
/***/
private int fragmentRemaining;
/***/
private boolean sequenceDelimiterEncountered;
/***/
private boolean endOfFrameEncountered;
/***/
private boolean currentFragmentContainsEndOfFrame;
/**
* @param i
* @exception IOException
*/
private AttributeTag readAttributeTag() throws IOException {
int group = i.readUnsigned16();
int element = i.readUnsigned16();
return new AttributeTag(group,element);
}
private long readItemTag() throws IOException {
AttributeTag tag = readAttributeTag();
//System.err.println("EncapsulatedInputStream.readItemTag: tag="+tag);
long vl = i.readUnsigned32(); // always implicit VR form for items and delimiters
if (tag.equals(TagFromName.SequenceDelimitationItem)) {
//System.err.println("EncapsulatedInputStream.readItemTag: SequenceDelimitationItem");
vl=0; // regardless of what was read
sequenceDelimiterEncountered=true;
}
else if (!tag.equals(TagFromName.Item)) {
throw new IOException("Unexpected DICOM tag "+tag+" (vl="+vl+") in encapsulated data whilst expecting Item or SequenceDelimitationItem");
}
//System.err.println("EncapsulatedInputStream.readItemTag: length="+vl);
return vl;
}
// private void readItemDelimiter() throws IOException {
// AttributeTag tag = readAttributeTag();
//System.err.println("EncapsulatedInputStream.readItemDelimiter: tag="+tag);
// i.readUnsigned32(); // always implicit VR form for items and delimiters
// if (!tag.equals(TagFromName.ItemDelimitationItem)) {
// throw new IOException("Expected DICOM Item Delimitation Item tag in encapsulated data");
// }
// }
// private void readSequenceDelimiter() throws IOException {
// AttributeTag tag = readAttributeTag();
//System.err.println("EncapsulatedInputStream.readSequenceDelimiter: tag="+tag);
// i.readUnsigned32(); // always implicit VR form for items and delimiters
// if (!tag.equals(TagFromName.SequenceDelimitationItem)) {
// throw new IOException("Expected DICOM Sequence Delimitation Item tag in encapsulated data");
// }
// }
/**
* <p>Construct a byte ordered stream from the supplied stream.</p>
*
* <p>The byte order may be changed later.</p>
*
* @param i the input stream to read from
*/
public EncapsulatedInputStream(BinaryInputStream i) {
this.i=i;
bigEndian=i.isBigEndian();
buffer=new byte[8];
fragment=null;
firstTime=true;
sequenceDelimiterEncountered=false;
endOfFrameEncountered=false;
}
/**
* <p>Skip to the start of a fragment, if not already there.</p>
*/
public void nextFrame() {
//System.err.println("EncapsulatedInputStream.nextFrame()");
// flush to start of next fragment unless already positioned at start of next fragment
if (fragment != null && fragmentOffset != 0) {
//System.err.println("EncapsulatedInputStream.nextFrame(): fragment was not already null");
fragment=null;
}
endOfFrameEncountered=false;
}
// Our own specific methods a la BinaryInputStream ...
/**
* <p>Read an array of unsigned integer 16 bit values.</p>
*
* @param w an array of sufficient size in which to return the values read
* @param offset the offset in the array at which to begin storing values
* @param len the number of 16 bit values to read
* @exception IOException
*/
public final void readUnsigned16(short[] w,int offset,int len) throws IOException {
int blen = len*2;
byte b[] = new byte[blen];
read(b,0,blen); // read the bytes from the fragment(s)
int bcount=0;
int wcount=0;
if (bigEndian) {
for (;wcount<len;++wcount) {
w[offset+wcount]=(short)((b[bcount++]<<8) + (b[bcount++]&0xff)); // assumes left to right evaluation
}
}
else {
for (;wcount<len;++wcount) {
w[offset+wcount]=(short)((b[bcount++]&0xff) + (b[bcount++]<<8)); // assumes left to right evaluation
}
}
}
// Override the necessary methods from InputStream ...
/**
* <p>Extracts the next byte of data from the current or
* subsequent fragments.</p>
*
* @return the next byte of data, or -1 if there is no more data because the end of the stream has been reached.
* @exception IOException if an I/O error occurs.
*/
public final int read() throws IOException {
int count = read(buffer,0,1);
return count == -1 ? -1 : (buffer[0]&0xff); // do not sign extend
}
/**
* <p>Extracts <code>byte.length</code> bytes of data from the current or
* subsequent fragments.</p>
* <p>This method simply performs the call <code>read(b, 0, b.length)</code> and returns
* the result.</p>
*
* @param b the buffer into which the data is read.
* @return the total number of bytes read into the buffer (always whatever was asked for), or -1 if there is no more data because the end of the stream has been reached.
* @exception IOException if an I/O error occurs.
* @see #read(byte[], int, int)
*/
public final int read(byte b[]) throws IOException {
return read(b, 0, b.length);
}
/**
* <p>Extracts <code>len</code> bytes of data from the current or
* subsequent fragments.</p>
*
* @param b the buffer into which the data is read.
* @param off the start offset of the data.
* @param len the number of bytes read.
* @return the total number of bytes read into the buffer (always whatever was asked for), or -1 if there is no more data because the end of a frame has been reached.
* @exception IOException if an I/O error occurs.
*/
public final int read(byte b[], int off, int len) throws IOException {
//System.err.println("EncapsulatedInputStream.read(byte [],"+off+","+len+")");
//System.err.println("EncapsulatedInputStream.read() at start, fragmentRemaining="+fragmentRemaining);
//System.err.println("EncapsulatedInputStream.read() at start, endOfFrameEncountered="+endOfFrameEncountered);
//System.err.println("EncapsulatedInputStream.read() at start, currentFragmentContainsEndOfFrame="+currentFragmentContainsEndOfFrame);
if (endOfFrameEncountered) {
//System.err.println("EncapsulatedInputStream.read() returning -1 since endOfFrameEncountered");
return -1; // i.e., won't advance until nextFrame() is called to reset this state
}
int count=0;
int remainingToDo = len;
while (remainingToDo > 0 && !sequenceDelimiterEncountered && !endOfFrameEncountered) {
//System.err.println("EncapsulatedInputStream.read() remainingToDo="+remainingToDo);
if (fragment == null) {
if (firstTime) {
//System.err.println("EncapsulatedInputStream.read() firstTime");
// first time ... skip offset table ...
long offsetTableLength = readItemTag();
if (sequenceDelimiterEncountered) {
throw new IOException("Expected offset table item tag; got sequence delimiter");
}
//System.err.println("EncapsulatedInputStream.read() skipping offsetTableLength="+offsetTableLength);
i.skipInsistently(offsetTableLength);
firstTime=false;
}
// load a new fragment ...
//System.err.println("EncapsulatedInputStream.read() loading a new fragment");
long vl = readItemTag(); // if sequenceDelimiterEncountered, vl will be zero and no more will be done
if (vl != 0) {
currentFragmentContainsEndOfFrame=false;
fragmentRemaining = fragmentSize = (int)vl;
fragment = new byte[fragmentSize];
i.readInsistently(fragment,0,fragmentSize);
fragmentOffset=0;
//System.err.println("EncapsulatedInputStream.read() fragmentRemaining initially="+fragmentRemaining);
//System.err.println("EncapsulatedInputStream.read() fragment = "+com.pixelmed.utils.HexDump.dump(fragment));
// Ignore everything between (the last) EOI marker and the end of the fragment
int positionOfEOI = fragmentRemaining-1;
while (--positionOfEOI > 0) {
int firstMarkerByte = fragment[positionOfEOI ]&0xff;
int secondMarkerByte = fragment[positionOfEOI +1]&0xff;
//System.err.println("EncapsulatedInputStream.read() fragment fragment["+positionOfEOI+"] = 0x"+Integer.toHexString(firstMarkerByte));
//System.err.println("EncapsulatedInputStream.read() fragment fragment["+(positionOfEOI+1)+"] = 0x"+Integer.toHexString(secondMarkerByte));
if (firstMarkerByte == 0xff && secondMarkerByte == 0xd9) {
currentFragmentContainsEndOfFrame=true;
break;
}
}
//System.err.println("EncapsulatedInputStream.read() positionOfEOI="+positionOfEOI);
if (positionOfEOI > 0) { // will be zero if we did not find one
fragmentRemaining = positionOfEOI+2; // effectively skips all (hopefully padding) bytes after the EOI
}
//System.err.println("EncapsulatedInputStream.read() fragmentRemaining after removing trailing padding="+fragmentRemaining);
}
}
int amountToCopyFromThisFragment = remainingToDo < fragmentRemaining ? remainingToDo : fragmentRemaining;
//System.err.println("EncapsulatedInputStream.read() amountToCopyFromThisFragment="+amountToCopyFromThisFragment);
if (amountToCopyFromThisFragment > 0) {
System.arraycopy(fragment,fragmentOffset,b,off,amountToCopyFromThisFragment);
off+=amountToCopyFromThisFragment;
fragmentOffset+=amountToCopyFromThisFragment;
fragmentRemaining-=amountToCopyFromThisFragment;
remainingToDo-=amountToCopyFromThisFragment;
count+=amountToCopyFromThisFragment;
}
if (fragmentRemaining <= 0) {
fragment=null;
if (currentFragmentContainsEndOfFrame) {
endOfFrameEncountered = true; // once EOI has been seen in a fragment, use the rest of the fragment including the EOI, but no further
}
}
}
//System.err.println("EncapsulatedInputStream.read() returning count="+count);
//System.err.println("EncapsulatedInputStream.read() returning="+com.pixelmed.utils.HexDump.dump(fragment,off,count));
return count == 0 ? -1 : count; // always returns more than 0 unless end, which is signalled by -1
}
}