Dear list
I'm writing (or trying) a plugin to import a proprietary medical image format. There is no trouble importing the image proper, but I'm having problems reading some of the header information, which contains demographic and image information as "keys" and "values". The structure of the header is as follows: 1. 10 bytes of preamble, which includes number of keys 2. Keys and value-offsets (up to the number obtained above) in chunks of 6 bytes (2 for key number, 1 for data type, 1 unused and 2 for the byte offset of the value). 3. Values of keys (The size is determined by type or, for strings, by a hand-typed look-up table referenced to the key number) 4. Image In my plugin I read to the number of keys. I can then loop to query the 6 bytes key/offset block to get the value offset, use a lookup table to get more information about the key. I then add the key and value to the image info (with the later intention of doing something useful, like set dimensions). Loop until reaching the last value. This works for some keys and not for others. Many values are strings, so I can read the whole header into IJ.log and inspect it for readable characters. I have found that when the offset reported (in the 6 byte block) is incorrect, it is always by 256 bytes. It seems very odd to me that when it's wrong it's by the same amount and can't be random. I must be doing something wrong and would be grateful for any help with this. Here are a couple of code snippets that demonstrated the offset reported by some keys being 256 bytes away from the values as read in as a string from the header: ------------------------------------------------ private boolean littleEndian = false; private int location = 0, offset = 0; private static final int imageOffset = 2048; BufferedInputStream inputStream; ... inputstream.read(header, 0, imageOffset); String hdr = getString(6) + "\n"; IJ.log(hdr); // says adac01 short labels = getShort(); IJ.log(Integer.toString(labels)); // Number of labels in header IJ.log(Integer.toString(getByte())); // Number of sub-headers IJ.log(Integer.toString(getByte())); // Unused byte // Should have read 10 bytes offset = location; // Test header output 20 chars at a time for (int i=0; i<1956; i++){ location=i; IJ.log(""+i + ": " + getString(20)); } location = offset; for (short i = 0; i < labels; i++) { getKeys(); IJ.log("Key: " + keynum + " Type: " + datTyp + " offset: " + fieldOffset + "\n"); } ....... void getKeys() throws IOException { keynum = getShort(); datTyp = getByte(); unused = getByte(); fieldOffset = getShort(); } String getString(int length) throws IOException { byte[] buf = new byte[length]; System.arraycopy(header, location, buf, 0, length); location += length; return new String(buf); } byte getByte() throws IOException { byte b = header[location]; ++location; return b; } short getShort() throws IOException { byte b0 = getByte(); byte b1 = getByte(); if (littleEndian) { return (short) ((b1 << 8) + b0); } else { return (short)((b0 << 8) + b1); } } ------------------------------------ Many thanks in advance, Neil |
The simplest thing might be to translate your files into FITS, which
has a very simple but rich keyword-value functionality (and which ImageJ can process: our astronomical plugins even use present and create new content). Rick On 6 Aug 2010, at 12:24, Neil Thomson wrote: > Dear list > > I'm writing (or trying) a plugin to import a proprietary medical > image format. There is no trouble importing the image proper, but > I'm having problems reading some of the header information, which > contains demographic and image information as "keys" and "values". > The structure of the header is as follows: > > 1. 10 bytes of preamble, which includes number of keys > 2. Keys and value-offsets (up to the number obtained above) in > chunks of 6 bytes (2 for key number, 1 for data type, 1 unused and 2 > for the byte offset of the value). > 3. Values of keys (The size is determined by type or, for strings, > by a hand-typed look-up table referenced to the key number) > 4. Image > > In my plugin I read to the number of keys. I can then loop to query > the 6 bytes key/offset block to get the value offset, use a lookup > table to get more information about the key. I then add the key and > value to the image info (with the later intention of doing something > useful, like set dimensions). Loop until reaching the last value. > > This works for some keys and not for others. Many values are > strings, so I can read the whole header into IJ.log and inspect it > for readable characters. I have found that when the offset reported > (in the 6 byte block) is incorrect, it is always by 256 bytes. It > seems very odd to me that when it's wrong it's by the same amount > and can't be random. > > I must be doing something wrong and would be grateful for any help > with this. Here are a couple of code snippets that demonstrated the > offset reported by some keys being 256 bytes away from the values as > read in as a string from the header: > > ------------------------------------------------ > private boolean littleEndian = false; > private int location = 0, offset = 0; > private static final int imageOffset = 2048; > > BufferedInputStream inputStream; > ... > > inputstream.read(header, 0, imageOffset); > > String hdr = getString(6) + "\n"; > IJ.log(hdr); // says adac01 > short labels = getShort(); > IJ.log(Integer.toString(labels)); // Number of labels in header > IJ.log(Integer.toString(getByte())); // Number of sub-headers > IJ.log(Integer.toString(getByte())); // Unused byte > // Should have read 10 bytes > > offset = location; > > // Test header output 20 chars at a time > for (int i=0; i<1956; i++){ > location=i; > IJ.log(""+i + ": " + getString(20)); > } > location = offset; > > for (short i = 0; i < labels; i++) { > getKeys(); > IJ.log("Key: " + keynum + " Type: " + datTyp + " offset: " + > fieldOffset + "\n"); > } > ....... > void getKeys() throws IOException { > keynum = getShort(); > datTyp = getByte(); > unused = getByte(); > fieldOffset = getShort(); > } > > String getString(int length) throws IOException { > byte[] buf = new byte[length]; > System.arraycopy(header, location, buf, 0, length); > location += length; > return new String(buf); > } > > byte getByte() throws IOException { > byte b = header[location]; > ++location; > return b; > } > > short getShort() throws IOException { > byte b0 = getByte(); > byte b1 = getByte(); > if (littleEndian) { > return (short) ((b1 << 8) + b0); > } else { > return (short)((b0 << 8) + b1); > } > } > > ------------------------------------ > > Many thanks in advance, Neil |
In reply to this post by Thomson Neil (EAST KENT HOSPITALS UNIVERSITY NHS FOUNDATION
TRUST)
Hi Neil,
I think in getshort(), b0 and especially b1 must already be short, otherwise b1 <<8 makes no sense and will not do what you expect, also adding the supposedly shifted b1 to b0, the casting (short) only works AFTER the operation, when the upper bits are already lost IMHO! Mit freundlichen Grüßen / Best regards Joachim Wesner ____________________________________________ Leica Microsystems CMS GmbH | GmbH mit Sitz in Wetzlar | Amtsgericht Wetzlar HRB 2432 Geschäftsführer: Dr. Stefan Traeger | Dr. David Roy Martyr | Colin Davis www.leica-microsystems.com Neil Thomson <neil.thomson2@NH S.NET> An Gesendet von: [hidden email] ImageJ Interest Kopie Group <[hidden email]. Thema GOV> Image header - 256 bytes out 06.08.2010 12:24 Bitte antworten an ImageJ Interest Group <[hidden email]. GOV> Dear list I'm writing (or trying) a plugin to import a proprietary medical image format. There is no trouble importing the image proper, but I'm having problems reading some of the header information, which contains demographic and image information as "keys" and "values". The structure of the header is as follows: 1. 10 bytes of preamble, which includes number of keys 2. Keys and value-offsets (up to the number obtained above) in chunks of 6 bytes (2 for key number, 1 for data type, 1 unused and 2 for the byte offset of the value). 3. Values of keys (The size is determined by type or, for strings, by a hand-typed look-up table referenced to the key number) 4. Image In my plugin I read to the number of keys. I can then loop to query the 6 bytes key/offset block to get the value offset, use a lookup table to get more information about the key. I then add the key and value to the image info (with the later intention of doing something useful, like set dimensions). Loop until reaching the last value. This works for some keys and not for others. Many values are strings, so I can read the whole header into IJ.log and inspect it for readable characters. I have found that when the offset reported (in the 6 byte block) is incorrect, it is always by 256 bytes. It seems very odd to me that when it's wrong it's by the same amount and can't be random. I must be doing something wrong and would be grateful for any help with this. Here are a couple of code snippets that demonstrated the offset reported by some keys being 256 bytes away from the values as read in as a string from the header: ------------------------------------------------ private boolean littleEndian = false; private int location = 0, offset = 0; private static final int imageOffset = 2048; BufferedInputStream inputStream; ... inputstream.read(header, 0, imageOffset); String hdr = getString(6) + "\n"; IJ.log(hdr); // says adac01 short labels = getShort(); IJ.log(Integer.toString(labels)); // Number of labels in header IJ.log(Integer.toString(getByte())); // Number of sub-headers IJ.log(Integer.toString(getByte())); // Unused byte // Should have read 10 bytes offset = location; // Test header output 20 chars at a time for (int i=0; i<1956; i++){ location=i; IJ.log(""+i + ": " + getString(20)); } location = offset; for (short i = 0; i < labels; i++) { getKeys(); IJ.log("Key: " + keynum + " Type: " + datTyp + " offset: " + fieldOffset + "\n"); } ....... void getKeys() throws IOException { keynum = getShort(); datTyp = getByte(); unused = getByte(); fieldOffset = getShort(); } String getString(int length) throws IOException { byte[] buf = new byte[length]; System.arraycopy(header, location, buf, 0, length); location += length; return new String(buf); } byte getByte() throws IOException { byte b = header[location]; ++location; return b; } short getShort() throws IOException { byte b0 = getByte(); byte b1 = getByte(); if (littleEndian) { return (short) ((b1 << 8) + b0); } else { return (short)((b0 << 8) + b1); } } ------------------------------------ Many thanks in advance, Neil ______________________________________________________________________ This email has been scanned by the MessageLabs Email Security System. For more information please visit http://www.messagelabs.com/email ______________________________________________________________________ |
In reply to this post by Thomson Neil (EAST KENT HOSPITALS UNIVERSITY NHS FOUNDATION
TRUST)
On 08/06/2010 06:24 AM, Neil Thomson wrote: > Dear list > > I'm writing (or trying) a plugin to import a proprietary medical image format. There is no trouble importing the image proper, but I'm having problems reading some of the header information, which contains demographic and image information as "keys" and "values". The structure of the header is as follows: > > 1. 10 bytes of preamble, which includes number of keys > 2. Keys and value-offsets (up to the number obtained above) in chunks of 6 bytes (2 for key number, 1 for data type, 1 unused and 2 for the byte offset of the value). > 3. Values of keys (The size is determined by type or, for strings, by a hand-typed look-up table referenced to the key number) > 4. Image > > In my plugin I read to the number of keys. I can then loop to query the 6 bytes key/offset block to get the value offset, use a lookup table to get more information about the key. I then add the key and value to the image info (with the later intention of doing something useful, like set dimensions). Loop until reaching the last value. > > This works for some keys and not for others. Many values are strings, so I can read the whole header into IJ.log and inspect it for readable characters. I have found that when the offset reported (in the 6 byte block) is incorrect, it is always by 256 bytes. It seems very odd to me that when it's wrong it's by the same amount and can't be random. > > I must be doing something wrong and would be grateful for any help with this. Here are a couple of code snippets that demonstrated the offset reported by some keys being 256 bytes away from the values as read in as a string from the header: > > ------------------------------------------------ > private boolean littleEndian = false; > private int location = 0, offset = 0; > private static final int imageOffset = 2048; > > BufferedInputStream inputStream; > ... > > inputstream.read(header, 0, imageOffset); > > String hdr = getString(6) + "\n"; > IJ.log(hdr); // says adac01 > short labels = getShort(); > IJ.log(Integer.toString(labels)); // Number of labels in header > IJ.log(Integer.toString(getByte())); // Number of sub-headers > IJ.log(Integer.toString(getByte())); // Unused byte > // Should have read 10 bytes > > offset = location; > > // Test header output 20 chars at a time > for (int i=0; i<1956; i++){ > location=i; > IJ.log(""+i + ": " + getString(20)); > } > location = offset; > > for (short i = 0; i< labels; i++) { > getKeys(); > IJ.log("Key: " + keynum + " Type: " + datTyp + " offset: " + fieldOffset + "\n"); > } > ....... > void getKeys() throws IOException { > keynum = getShort(); > datTyp = getByte(); > unused = getByte(); > fieldOffset = getShort(); > } > > String getString(int length) throws IOException { > byte[] buf = new byte[length]; > System.arraycopy(header, location, buf, 0, length); > location += length; > return new String(buf); > } > > byte getByte() throws IOException { > byte b = header[location]; > ++location; > return b; > } > > short getShort() throws IOException { > byte b0 = getByte(); > byte b1 = getByte(); > if (littleEndian) { > return (short) ((b1<< 8) + b0); > } else { > return (short)((b0<< 8) + b1); > } > } > > ------------------------------------ > > Many thanks in advance, Neil > Neil, I don't know about your particular format but have you considered converting this data into NRRD: http://teem.sourceforge.net/nrrd/ Seems like a nice popular open source graphics format that from what you are saying should handle all you are trying to handle leaving room for further enhancement is necessary. Boris. The information in this e-mail is intended only for the person to whom it is addressed. If you believe this e-mail was sent to you in error and the e-mail contains patient information, please contact the Partners Compliance HelpLine at http://www.partners.org/complianceline . If the e-mail was sent to you in error but does not contain patient information, please contact the sender and properly dispose of the e-mail. |
Hi Neil,
I think writing a Plugin to read your fileformat is definitely the right way to go. If your plugin is extends ImagePlus you can even add it to the HandleExtraFileTypes.java in your plugin directory. Then your ImageJ will become fully compatible with your format and you can even use the drag and drop functionality in ImageJ. I completely agree with Joachim Wesner's comment. You should cast to short before you shift your bits: short getShort() throws IOException { byte b0 = getByte(); byte b1 = getByte(); if (littleEndian) { return ((((short)b1)<< 8) + b0); } else { return ((((short)b0)<< 8) + b1); } } Another thing which may be wrong, is that your current index in your header points to the wrong position for some reason. Without example files and the complete code, it's difficult to find the issue. Best, Andreas Am 8/6/2010 7:01 AM, schrieb Boris Epstein: > > > On 08/06/2010 06:24 AM, Neil Thomson wrote: >> Dear list >> >> I'm writing (or trying) a plugin to import a proprietary medical >> image format. There is no trouble importing the image proper, but I'm >> having problems reading some of the header information, which >> contains demographic and image information as "keys" and "values". >> The structure of the header is as follows: >> >> 1. 10 bytes of preamble, which includes number of keys >> 2. Keys and value-offsets (up to the number obtained above) in >> chunks of 6 bytes (2 for key number, 1 for data type, 1 unused and 2 >> for the byte offset of the value). >> 3. Values of keys (The size is determined by type or, for strings, >> by a hand-typed look-up table referenced to the key number) >> 4. Image >> >> In my plugin I read to the number of keys. I can then loop to query >> the 6 bytes key/offset block to get the value offset, use a lookup >> table to get more information about the key. I then add the key and >> value to the image info (with the later intention of doing something >> useful, like set dimensions). Loop until reaching the last value. >> >> This works for some keys and not for others. Many values are strings, >> so I can read the whole header into IJ.log and inspect it for >> readable characters. I have found that when the offset reported (in >> the 6 byte block) is incorrect, it is always by 256 bytes. It seems >> very odd to me that when it's wrong it's by the same amount and can't >> be random. >> >> I must be doing something wrong and would be grateful for any help >> with this. Here are a couple of code snippets that demonstrated the >> offset reported by some keys being 256 bytes away from the values as >> read in as a string from the header: >> >> ------------------------------------------------ >> private boolean littleEndian = false; >> private int location = 0, offset = 0; >> private static final int imageOffset = 2048; >> >> BufferedInputStream inputStream; >> ... >> >> inputstream.read(header, 0, imageOffset); >> >> String hdr = getString(6) + "\n"; >> IJ.log(hdr); // says adac01 >> short labels = getShort(); >> IJ.log(Integer.toString(labels)); // Number of labels in header >> IJ.log(Integer.toString(getByte())); // Number of sub-headers >> IJ.log(Integer.toString(getByte())); // Unused byte >> // Should have read 10 bytes >> >> offset = location; >> >> // Test header output 20 chars at a time >> for (int i=0; i<1956; i++){ >> location=i; >> IJ.log(""+i + ": " + getString(20)); >> } >> location = offset; >> >> for (short i = 0; i< labels; i++) { >> getKeys(); >> IJ.log("Key: " + keynum + " Type: " + datTyp + " offset: " + >> fieldOffset + "\n"); >> } >> ....... >> void getKeys() throws IOException { >> keynum = getShort(); >> datTyp = getByte(); >> unused = getByte(); >> fieldOffset = getShort(); >> } >> >> String getString(int length) throws IOException { >> byte[] buf = new byte[length]; >> System.arraycopy(header, location, buf, 0, length); >> location += length; >> return new String(buf); >> } >> >> byte getByte() throws IOException { >> byte b = header[location]; >> ++location; >> return b; >> } >> >> short getShort() throws IOException { >> byte b0 = getByte(); >> byte b1 = getByte(); >> if (littleEndian) { >> return (short) ((b1<< 8) + b0); >> } else { >> return (short)((b0<< 8) + b1); >> } >> } >> >> ------------------------------------ >> >> Many thanks in advance, Neil > > Neil, > > I don't know about your particular format but have you considered > converting this data into NRRD: > > http://teem.sourceforge.net/nrrd/ > > Seems like a nice popular open source graphics format that from what > you are saying should handle all you are trying to handle leaving room > for further enhancement is necessary. > > Boris. > > > The information in this e-mail is intended only for the person to whom > it is > addressed. If you believe this e-mail was sent to you in error and the > contains patient information, please contact the Partners Compliance > HelpLine at > http://www.partners.org/complianceline . If the e-mail was sent to you > in error > but does not contain patient information, please contact the sender > and properly > dispose of the e-mail. -- Dr.-Ing. Andreas Maier Stanford University Department of Radiology The Lucas Center for Imaging Mail Code 5488, Route 8 Stanford, CA 94305 http://med.stanford.edu/profiles/Andreas_Maier/ |
Hi Neil,
it is not a matter of typecast, it is cast to int anyhow: in Java, there are no byte or short operations like '+' or '<<'. It rather is a problem of Java having only signed data types. E.g., byte 0xff is interpreted as -1, and converted to int -1 = 0xffffffff. Thus, at least the lower byte needs an '&' operation to mask out the lower 8 bits: (b1 << 8) + (b0 & 0xff); You don't need to mask out the upper byte because the typecast to short will discard the higher bytes. Michael _________________________________________________________________ On Fri, August 6, 2010 19:13, Andreas Maier wrote: > Hi Neil, > > I think writing a Plugin to read your fileformat is definitely the right > way to go. If your plugin is extends ImagePlus you can even add it to > the HandleExtraFileTypes.java in your plugin directory. Then your ImageJ > will become fully compatible with your format and you can even use the > drag and drop functionality in ImageJ. > > I completely agree with Joachim Wesner's comment. You should cast to > short before you shift your bits: > > short getShort() throws IOException { > byte b0 = getByte(); > byte b1 = getByte(); > if (littleEndian) { > return ((((short)b1)<< 8) + b0); > } else { > return ((((short)b0)<< 8) + b1); > } > } > > Another thing which may be wrong, is that your current index in your > header points to the wrong position for some reason. Without example > files and the complete code, it's difficult to find the issue. > > Best, > > Andreas > > Am 8/6/2010 7:01 AM, schrieb Boris Epstein: >> >> >> On 08/06/2010 06:24 AM, Neil Thomson wrote: >>> Dear list >>> >>> I'm writing (or trying) a plugin to import a proprietary medical >>> image format. There is no trouble importing the image proper, but I'm >>> having problems reading some of the header information, which >>> contains demographic and image information as "keys" and "values". >>> The structure of the header is as follows: >>> >>> 1. 10 bytes of preamble, which includes number of keys >>> 2. Keys and value-offsets (up to the number obtained above) in >>> chunks of 6 bytes (2 for key number, 1 for data type, 1 unused and 2 >>> for the byte offset of the value). >>> 3. Values of keys (The size is determined by type or, for strings, >>> by a hand-typed look-up table referenced to the key number) >>> 4. Image >>> >>> In my plugin I read to the number of keys. I can then loop to query >>> the 6 bytes key/offset block to get the value offset, use a lookup >>> table to get more information about the key. I then add the key and >>> value to the image info (with the later intention of doing something >>> useful, like set dimensions). Loop until reaching the last value. >>> >>> This works for some keys and not for others. Many values are strings, >>> so I can read the whole header into IJ.log and inspect it for >>> readable characters. I have found that when the offset reported (in >>> the 6 byte block) is incorrect, it is always by 256 bytes. It seems >>> very odd to me that when it's wrong it's by the same amount and can't >>> be random. >>> >>> I must be doing something wrong and would be grateful for any help >>> with this. Here are a couple of code snippets that demonstrated the >>> offset reported by some keys being 256 bytes away from the values as >>> read in as a string from the header: >>> >>> ------------------------------------------------ >>> private boolean littleEndian = false; >>> private int location = 0, offset = 0; >>> private static final int imageOffset = 2048; >>> >>> BufferedInputStream inputStream; >>> ... >>> >>> inputstream.read(header, 0, imageOffset); >>> >>> String hdr = getString(6) + "\n"; >>> IJ.log(hdr); // says adac01 >>> short labels = getShort(); >>> IJ.log(Integer.toString(labels)); // Number of labels in header >>> IJ.log(Integer.toString(getByte())); // Number of sub-headers >>> IJ.log(Integer.toString(getByte())); // Unused byte >>> // Should have read 10 bytes >>> >>> offset = location; >>> >>> // Test header output 20 chars at a time >>> for (int i=0; i<1956; i++){ >>> location=i; >>> IJ.log(""+i + ": " + getString(20)); >>> } >>> location = offset; >>> >>> for (short i = 0; i< labels; i++) { >>> getKeys(); >>> IJ.log("Key: " + keynum + " Type: " + datTyp + " offset: " + >>> fieldOffset + "\n"); >>> } >>> ....... >>> void getKeys() throws IOException { >>> keynum = getShort(); >>> datTyp = getByte(); >>> unused = getByte(); >>> fieldOffset = getShort(); >>> } >>> >>> String getString(int length) throws IOException { >>> byte[] buf = new byte[length]; >>> System.arraycopy(header, location, buf, 0, length); >>> location += length; >>> return new String(buf); >>> } >>> >>> byte getByte() throws IOException { >>> byte b = header[location]; >>> ++location; >>> return b; >>> } >>> >>> short getShort() throws IOException { >>> byte b0 = getByte(); >>> byte b1 = getByte(); >>> if (littleEndian) { >>> return (short) ((b1<< 8) + b0); >>> } else { >>> return (short)((b0<< 8) + b1); >>> } >>> } >>> >>> ------------------------------------ >>> >>> Many thanks in advance, Neil >> >> Neil, >> >> I don't know about your particular format but have you considered >> converting this data into NRRD: >> >> http://teem.sourceforge.net/nrrd/ >> >> Seems like a nice popular open source graphics format that from what >> you are saying should handle all you are trying to handle leaving room >> for further enhancement is necessary. >> >> Boris. >> >> >> The information in this e-mail is intended only for the person to whom >> it is >> addressed. If you believe this e-mail was sent to you in error and the >> contains patient information, please contact the Partners Compliance >> HelpLine at >> http://www.partners.org/complianceline . If the e-mail was sent to you >> in error >> but does not contain patient information, please contact the sender >> and properly >> dispose of the e-mail. > > > -- > Dr.-Ing. Andreas Maier > Stanford University > Department of Radiology > The Lucas Center for Imaging > Mail Code 5488, Route 8 > Stanford, CA 94305 > http://med.stanford.edu/profiles/Andreas_Maier/ > |
In reply to this post by Thomson Neil (EAST KENT HOSPITALS UNIVERSITY NHS FOUNDATION
TRUST)
Thanks to all who replied,
(b1 << 8) + (b0 & 0xff) fixed the problem. This now raises another question; I based my original code on the DICOM.java plugin. The implication is that the getShort() method is incorrect in that class, but I haven't noticed any problems using it. Neil |
Free forum by Nabble | Edit this page |