Changeset 4110


Ignore:
Timestamp:
06/10/08 14:30:10 (12 years ago)
Author:
melissa
Message:

Updated OME-XML reader to use significantly less memory. Initializing an OME-XML file should now require less than 10 MB of memory, regardless of the file size.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/loci/formats/in/OMEXMLReader.java

    r4048 r4110  
    2727import java.util.*; 
    2828import java.util.zip.*; 
     29import javax.xml.parsers.*; 
    2930import loci.formats.*; 
    3031import loci.formats.codec.Base64Codec; 
     
    3233import loci.formats.meta.MetadataRetrieve; 
    3334import loci.formats.meta.MetadataStore; 
     35import org.xml.sax.Attributes; 
     36import org.xml.sax.SAXException; 
     37import org.xml.sax.helpers.DefaultHandler; 
    3438 
    3539/** 
     
    5054    "obtain ome-java.jar from http://loci.wisc.edu/ome/formats.html"; 
    5155 
     56  private static final SAXParserFactory SAX_FACTORY = 
     57    SAXParserFactory.newInstance(); 
     58 
    5259  // -- Static fields -- 
    5360 
     
    6673  // -- Fields -- 
    6774 
    68   /** Number of bits per pixel. */ 
    69   protected int[] bpp; 
    70  
    71   /** Offset to each plane's data. */ 
    72   protected Vector[] offsets; 
    73  
    74   /** String indicating the compression type. */ 
    75   protected String[] compression; 
     75  // compression value and offset for each BinData element 
     76  private Vector binDataOffsets; 
     77  private Vector binDataLengths; 
     78  private Vector compression; 
     79 
     80  private String omexml; 
    7681 
    7782  // -- Constructor -- 
     
    98103    FormatTools.checkBufferSize(this, buf.length, w, h); 
    99104 
    100     long offset = ((Long) offsets[series].get(no)).longValue(); 
    101  
    102     in.seek(offset); 
    103  
    104     int len = 0; 
    105     if (no < getImageCount() - 1) { 
    106       len = (int) (((Long) offsets[series].get(no + 1)).longValue() - offset); 
    107     } 
    108     else { 
    109       len = (int) (in.length() - offset); 
    110     } 
    111     String data = in.readString(len); 
     105    int index = no; 
     106    for (int i=0; i<series; i++) { 
     107      index += core.imageCount[series]; 
     108    } 
     109 
     110    long offset = ((Long) binDataOffsets.get(index)).longValue(); 
     111    long length = ((Long) binDataLengths.get(index)).longValue(); 
     112    String compress = (String) compression.get(index); 
     113 
     114    in.seek(offset - 64); 
     115 
     116    // offset is approximate, we will need to skip a few bytes 
     117    boolean foundBinData = false; 
     118    byte[] check = new byte[8192]; 
     119    int overlap = 14; 
     120    int n = in.read(check, 0, overlap); 
     121 
     122    while (!foundBinData) { 
     123      n += in.read(check, overlap, check.length - overlap); 
     124      String checkString = new String(check); 
     125      if (checkString.indexOf("<Bin") != -1) { 
     126        int idx = checkString.indexOf("<Bin") + 4; 
     127        foundBinData = true; 
     128        in.seek(in.getFilePointer() - n + idx); 
     129        while (in.read() != '>'); 
     130      } 
     131      else { 
     132        System.arraycopy(check, check.length - overlap, check, 0, overlap); 
     133        n = overlap; 
     134      } 
     135    } 
     136 
     137    if (length < 0 && index + 1 < binDataOffsets.size()) { 
     138      length = ((Long) binDataOffsets.get(index + 1)).longValue() - offset; 
     139    } 
     140    else if (length < 0) { 
     141      length = in.length() - offset; 
     142    } 
     143 
     144    String data = in.readString((int) length * 2); 
    112145 
    113146    // retrieve the compressed pixel data 
    114147 
    115     int dataStart = data.indexOf(">") + 1; 
    116     String pix = data.substring(dataStart); 
    117     if (pix.indexOf("<") > 0) { 
    118       pix = pix.substring(0, pix.indexOf("<")); 
    119     } 
     148    int dataEnd = data.indexOf("<"); 
     149    if (dataEnd != -1) data = data.substring(0, dataEnd); 
     150 
     151    Base64Codec e = new Base64Codec(); 
     152    byte[] pixels = e.base64Decode(data); 
    120153    data = null; 
    121154 
    122     Base64Codec e = new Base64Codec(); 
    123     byte[] pixels = e.base64Decode(pix); 
    124     pix = null; 
    125  
    126     if (compression[series].equals("bzip2")) { 
     155    int planeSize = 
     156      getSizeX() * getSizeY() * FormatTools.getBytesPerPixel(getPixelType()); 
     157 
     158    if (compress.equals("bzip2")) { 
    127159      byte[] tempPixels = pixels; 
    128160      pixels = new byte[tempPixels.length - 2]; 
     
    131163      ByteArrayInputStream bais = new ByteArrayInputStream(pixels); 
    132164      CBZip2InputStream bzip = new CBZip2InputStream(bais); 
    133       pixels = new byte[core.sizeX[series]*core.sizeY[series]*bpp[series]]; 
     165      pixels = new byte[planeSize]; 
    134166      bzip.read(pixels, 0, pixels.length); 
    135167      tempPixels = null; 
     
    138170      bzip = null; 
    139171    } 
    140     else if (compression[series].equals("zlib")) { 
     172    else if (compress.equals("zlib")) { 
    141173      try { 
    142174        Inflater decompressor = new Inflater(); 
    143175        decompressor.setInput(pixels, 0, pixels.length); 
    144         pixels = new byte[core.sizeX[series]*core.sizeY[series]*bpp[series]]; 
     176        pixels = new byte[planeSize]; 
    145177        decompressor.inflate(pixels); 
    146178        decompressor.end(); 
    147179      } 
    148180      catch (DataFormatException dfe) { 
    149         throw new FormatException("Error uncompressing zlib data."); 
     181        throw new FormatException("Error uncompressing zlib data.", dfe); 
    150182      } 
    151183    } 
     
    157189    } 
    158190 
     191    pixels = null; 
     192 
    159193    return buf; 
    160194  } 
     
    165199  public void close() throws IOException { 
    166200    super.close(); 
    167     bpp = null; 
    168     offsets = null; 
    169201    compression = null; 
     202    binDataOffsets = null; 
     203    binDataLengths = null; 
    170204  } 
    171205 
     
    180214    in = new RandomAccessStream(id); 
    181215 
    182     in.seek(0); 
    183     String s = in.readString((int) in.length()); 
    184     in.seek(200); 
     216    binDataOffsets = new Vector(); 
     217    binDataLengths = new Vector(); 
     218    compression = new Vector(); 
     219 
     220    OMEXMLHandler handler = new OMEXMLHandler(); 
     221    try { 
     222      SAXParser saxParser = SAX_FACTORY.newSAXParser(); 
     223      saxParser.parse(in, handler); 
     224    } 
     225    catch (ParserConfigurationException exc) { 
     226      throw new FormatException(exc); 
     227    } 
     228    catch (SAXException exc) { 
     229      throw new FormatException(exc); 
     230    } 
     231 
     232    status("Populating metadata"); 
    185233 
    186234    MetadataRetrieve omexmlMeta = (MetadataRetrieve) 
    187       MetadataTools.createOMEXMLMetadata(s); 
    188  
    189     status("Determining endianness"); 
    190  
    191     int numDatasets = 0; 
    192     Vector endianness = new Vector(); 
    193     Vector bigEndianPos = new Vector(); 
    194  
    195     byte[] buf = new byte[8192]; 
    196     in.read(buf, 0, 9); 
    197  
    198     while (in.getFilePointer() < in.length()) { 
    199       // read a block of 8192 characters, looking for the "BigEndian" pattern 
    200       boolean found = false; 
    201       while (!found) { 
    202         if (in.getFilePointer() < in.length()) { 
    203           int read = in.read(buf, 9, buf.length - 9); 
    204           String test = new String(buf); 
    205  
    206           int ndx = test.indexOf("BigEndian"); 
    207           if (ndx != -1) { 
    208             found = true; 
    209             String endian = test.substring(ndx + 11).trim(); 
    210             if (endian.startsWith("\"")) endian = endian.substring(1); 
    211             endianness.add(new Boolean(!endian.toLowerCase().startsWith("t"))); 
    212             bigEndianPos.add(new Long(in.getFilePointer() - read - 9 + ndx)); 
    213             numDatasets++; 
    214           } 
    215         } 
    216         else if (numDatasets == 0) { 
    217           throw new FormatException("Pixel data not found."); 
    218         } 
    219         else found = true; 
    220         System.arraycopy(buf, buf.length - 9, buf, 0, 9); 
    221       } 
    222     } 
    223  
    224     offsets = new Vector[numDatasets]; 
    225  
    226     for (int i=0; i<numDatasets; i++) { 
    227       offsets[i] = new Vector(); 
    228     } 
    229  
    230     status("Finding image offsets"); 
    231  
    232     // look for the first BinData element in each series 
    233  
    234     for (int i=0; i<numDatasets; i++) { 
    235       in.seek(((Long) bigEndianPos.get(i)).longValue()); 
    236       boolean found = false; 
    237       in.read(buf, 0, 14); 
    238  
    239       while (!found) { 
    240         if (in.getFilePointer() < in.length()) { 
    241           int numRead = in.read(buf, 14, buf.length - 14); 
    242  
    243           String test = new String(buf); 
    244  
    245           int ndx = test.indexOf("<Bin"); 
    246           if (ndx == -1) { 
    247             System.arraycopy(buf, buf.length - 14, buf, 0, 14); 
    248           } 
    249           else { 
    250             while (!((ndx != -1) && (ndx != test.indexOf("<Bin:External")) && 
    251               (ndx != test.indexOf("<Bin:BinaryFile")))) 
    252             { 
    253               ndx = test.indexOf("<Bin", ndx + 1); 
    254             } 
    255             found = true; 
    256             numRead += 14; 
    257             offsets[i].add(new Long(in.getFilePointer() - (numRead - ndx))); 
    258           } 
    259           test = null; 
    260         } 
    261         else { 
    262           throw new FormatException("Pixel data not found"); 
    263         } 
    264       } 
    265     } 
    266  
    267     in.seek(0); 
    268  
    269     for (int i=0; i<numDatasets; i++) { 
    270       if (i == 0) { 
    271         buf = new byte[((Long) offsets[i].get(0)).intValue()]; 
    272       } 
    273       else { 
    274         // look for the next Image element 
    275  
    276         boolean found = false; 
    277         buf = new byte[8192]; 
    278         in.read(buf, 0, 14); 
    279         while (!found) { 
    280           if (in.getFilePointer() < in.length()) { 
    281             in.read(buf, 14, buf.length - 14); 
    282  
    283             String test = new String(buf); 
    284  
    285             int ndx = test.indexOf("<Image "); 
    286             if (ndx == -1) { 
    287               System.arraycopy(buf, buf.length - 14, buf, 0, 14); 
    288             } 
    289             else { 
    290               found = true; 
    291               in.seek(in.getFilePointer() - 8192 + ndx); 
    292             } 
    293             test = null; 
    294           } 
    295           else { 
    296             throw new FormatException("Pixel data not found"); 
    297           } 
    298         } 
    299  
    300         int bufSize = (int) (((Long) offsets[i].get(0)).longValue() - 
    301           in.getFilePointer()); 
    302         buf = new byte[bufSize]; 
    303       } 
    304       in.read(buf); 
    305     } 
    306     buf = null; 
    307  
    308     status("Populating metadata"); 
     235      MetadataTools.createOMEXMLMetadata(omexml); 
     236 
     237    int numDatasets = omexmlMeta.getImageCount(); 
    309238 
    310239    core = new CoreMetadata(numDatasets); 
    311  
    312     bpp = new int[numDatasets]; 
    313     compression = new String[numDatasets]; 
    314240 
    315241    int oldSeries = getSeries(); 
     
    317243    for (int i=0; i<numDatasets; i++) { 
    318244      setSeries(i); 
    319  
    320       core.littleEndian[i] = ((Boolean) endianness.get(i)).booleanValue(); 
    321245 
    322246      Integer w = omexmlMeta.getPixelsSizeX(i, 0); 
     
    325249      Integer z = omexmlMeta.getPixelsSizeZ(i, 0); 
    326250      Integer c = omexmlMeta.getPixelsSizeC(i, 0); 
     251      Boolean endian = omexmlMeta.getPixelsBigEndian(i, 0); 
    327252      String pixType = omexmlMeta.getPixelsPixelType(i, 0); 
    328253      core.currentOrder[i] = omexmlMeta.getPixelsDimensionOrder(i, 0); 
     
    332257      core.sizeZ[i] = z.intValue(); 
    333258      core.sizeC[i] = c.intValue(); 
     259      core.imageCount[i] = core.sizeZ[i] * core.sizeC[i] * core.sizeT[i]; 
     260      core.littleEndian[i] = endian == null ? false : !endian.booleanValue(); 
    334261      core.rgb[i] = false; 
    335262      core.interleaved[i] = false; 
     
    340267      boolean signed = type.charAt(0) != 'u'; 
    341268      if (type.endsWith("16")) { 
    342         bpp[i] = 2; 
    343269        core.pixelType[i] = signed ? FormatTools.INT16 : FormatTools.UINT16; 
    344270      } 
    345271      else if (type.endsWith("32")) { 
    346         bpp[i] = 4; 
    347272        core.pixelType[i] = signed ? FormatTools.INT32 : FormatTools.UINT32; 
    348273      } 
    349274      else if (type.equals("float")) { 
    350         bpp[i] = 4; 
    351275        core.pixelType[i] = FormatTools.FLOAT; 
    352276      } 
    353277      else { 
    354         bpp[i] = 1; 
    355278        core.pixelType[i] = signed ? FormatTools.INT8 : FormatTools.UINT8; 
    356279      } 
    357  
    358       // calculate the number of raw bytes of pixel data that we are expecting 
    359       int expected = core.sizeX[i] * core.sizeY[i] * bpp[i]; 
    360  
    361       // find the compression type and adjust 'expected' accordingly 
    362       in.seek(((Long) offsets[i].get(0)).longValue()); 
    363       String data = in.readString(256); 
    364  
    365       int compressionStart = data.indexOf("Compression") + 13; 
    366       int compressionEnd = data.indexOf("\"", compressionStart); 
    367       if (compressionStart != -1 && compressionEnd != -1) { 
    368         compression[i] = data.substring(compressionStart, compressionEnd); 
    369       } 
    370       else compression[i] = "none"; 
    371  
    372       expected /= 2; 
    373  
    374       in.seek(((Long) offsets[i].get(0)).longValue()); 
    375  
    376       int planes = core.sizeZ[i] * core.sizeC[i] * core.sizeT[i]; 
    377  
    378       searchForData(expected, planes); 
    379       core.imageCount[i] = offsets[i].size(); 
    380       if (core.imageCount[i] < planes) { 
    381         // hope this doesn't happen too often 
    382         in.seek(((Long) offsets[i].get(0)).longValue()); 
    383         searchForData(0, planes); 
    384         core.imageCount[i] = offsets[i].size(); 
    385       } 
    386       buf = null; 
    387280    } 
    388281    setSeries(oldSeries); 
     
    396289  } 
    397290 
    398   // -- Helper methods -- 
    399  
    400   /** Searches for BinData elements, skipping 'safe' bytes in between. */ 
    401   private void searchForData(int safe, int numPlanes) throws IOException { 
    402     int iteration = 0; 
    403     boolean found = false; 
    404     if (offsets[series].size() > 1) { 
    405       Object zeroth = offsets[series].get(0); 
    406       offsets[series].clear(); 
    407       offsets[series].add(zeroth); 
    408     } 
    409  
    410     in.skipBytes(1); 
    411     while (((in.getFilePointer() + safe) < in.length()) && 
    412       (offsets[series].size() < numPlanes)) 
     291  // -- Helper class -- 
     292 
     293  class OMEXMLHandler extends DefaultHandler { 
     294    private StringBuffer xmlBuffer; 
     295    private long nextBinDataOffset; 
     296    private String currentQName; 
     297    private boolean hadCharData; 
     298    private int binDataChars; 
     299 
     300    public OMEXMLHandler() { 
     301      xmlBuffer = new StringBuffer(); 
     302      nextBinDataOffset = 0; 
     303    } 
     304 
     305    public void characters(char[] ch, int start, int length) { 
     306      if (currentQName.indexOf("BinData") != -1) { 
     307        binDataChars += length; 
     308      } 
     309      nextBinDataOffset += length; 
     310      hadCharData = true; 
     311    } 
     312 
     313    public void endElement(String uri, String localName, String qName) { 
     314      if (qName.indexOf("BinData") == -1) { 
     315        xmlBuffer.append("</"); 
     316        xmlBuffer.append(qName); 
     317        xmlBuffer.append(">"); 
     318      } 
     319      else { 
     320        binDataOffsets.add(new Long(nextBinDataOffset - binDataChars)); 
     321      } 
     322 
     323      nextBinDataOffset += 2; 
     324      if (!qName.equals(currentQName) || hadCharData) { 
     325        nextBinDataOffset += qName.length(); 
     326      } 
     327    } 
     328 
     329    public void ignorableWhitespace(char[] ch, int start, int length) { 
     330      nextBinDataOffset += length; 
     331    } 
     332 
     333    public void startElement(String ur, String localName, String qName, 
     334      Attributes attributes) 
    413335    { 
    414       in.skipBytes(safe); 
    415  
    416       // look for next BinData element 
    417       found = false; 
    418       byte[] buf = new byte[8192]; 
    419       while (!found) { 
    420         if (in.getFilePointer() < in.length()) { 
    421           int numRead = in.read(buf, 20, buf.length - 20); 
    422           String test = new String(buf); 
    423  
    424           // datasets with small planes could have multiple sets of pixel data 
    425           // in this block 
    426           int ndx = test.indexOf("<Bin"); 
    427           while (ndx != -1) { 
    428             found = true; 
    429             if (numRead == buf.length - 20) numRead = buf.length; 
    430             offsets[series].add(new Long(in.getFilePointer() - numRead + ndx)); 
    431             ndx = test.indexOf("<Bin", ndx+1); 
    432           } 
    433           test = null; 
     336      hadCharData = false; 
     337      currentQName = qName; 
     338 
     339      if (qName.indexOf("BinData") == -1) { 
     340        xmlBuffer.append("<"); 
     341        xmlBuffer.append(qName); 
     342        for (int i=0; i<attributes.getLength(); i++) { 
     343          String key = attributes.getQName(i); 
     344          String value = attributes.getValue(i); 
     345          xmlBuffer.append(" "); 
     346          xmlBuffer.append(key); 
     347          xmlBuffer.append("=\""); 
     348          xmlBuffer.append(value); 
     349          xmlBuffer.append("\""); 
    434350        } 
    435         else { 
    436           found = true; 
     351        xmlBuffer.append(">"); 
     352      } 
     353      else { 
     354        String length = attributes.getValue("Length"); 
     355        if (length == null) { 
     356          binDataLengths.add(new Long(-1)); 
    437357        } 
    438       } 
    439       buf = null; 
    440  
    441       iteration++; 
    442     } 
     358        else binDataLengths.add(new Long(length)); 
     359        String compress = attributes.getValue("Compression"); 
     360        compression.add(compress == null ? "" : compress); 
     361        binDataChars = 0; 
     362      } 
     363 
     364      nextBinDataOffset += 2 + qName.length() + 4*attributes.getLength(); 
     365      for (int i=0; i<attributes.getLength(); i++) { 
     366        nextBinDataOffset += attributes.getQName(i).length(); 
     367        nextBinDataOffset += attributes.getValue(i).length(); 
     368      } 
     369    } 
     370 
     371    public void endDocument() { 
     372      omexml = xmlBuffer.toString(); 
     373    } 
     374 
    443375  } 
    444376 
Note: See TracChangeset for help on using the changeset viewer.