private void readTags(RandomAccessInputStream vsi) throws IOException {
    // read the VSI header
    long fp = vsi.getFilePointer();
    if (fp + 24 >= vsi.length()) {
      return;
    }
    int headerSize = vsi.readShort(); // should always be 24
    int version = vsi.readShort(); // always 21321
    int volumeVersion = vsi.readInt();
    long dataFieldOffset = vsi.readLong();
    int flags = vsi.readInt();
    vsi.skipBytes(4);

    int tagCount = flags & 0xfffffff;

    if (fp + dataFieldOffset < 0) {
      return;
    }

    vsi.seek(fp + dataFieldOffset);
    if (vsi.getFilePointer() >= vsi.length()) {
      return;
    }

    for (int i = 0; i < tagCount; i++) {
      if (vsi.getFilePointer() + 16 >= vsi.length()) {
        break;
      }

      // read the data field

      int fieldType = vsi.readInt();
      int tag = vsi.readInt();
      long nextField = vsi.readInt() & 0xffffffffL;
      int dataSize = vsi.readInt();

      boolean extraTag = ((fieldType & 0x8000000) >> 27) == 1;
      boolean extendedField = ((fieldType & 0x10000000) >> 28) == 1;
      boolean inlineData = ((fieldType & 0x40000000) >> 30) == 1;
      boolean array = (!inlineData && !extendedField) && ((fieldType & 0x20000000) >> 29) == 1;
      boolean newVolume = ((fieldType & 0x80000000) >> 31) == 1;

      int realType = fieldType & 0xffffff;
      int secondTag = -1;

      if (extraTag) {
        secondTag = vsi.readInt();
      }

      if (extendedField && realType == NEW_VOLUME_HEADER) {
        if (tag == 2007) {
          dimensionTag = secondTag;
          inDimensionProperties = true;
        }
        long endPointer = vsi.getFilePointer() + dataSize;
        while (vsi.getFilePointer() < endPointer && vsi.getFilePointer() < vsi.length()) {
          long start = vsi.getFilePointer();
          readTags(vsi);
          long end = vsi.getFilePointer();
          if (start == end) {
            break;
          }
        }
        if (tag == 2007) {
          inDimensionProperties = false;
          foundChannelTag = false;
        }
      }

      if (inDimensionProperties) {
        if (tag == 2012 && !dimensionOrdering.containsValue(dimensionTag)) {
          dimensionOrdering.put("Z", dimensionTag);
        } else if ((tag == 2100 || tag == 2027) && !dimensionOrdering.containsValue(dimensionTag)) {
          dimensionOrdering.put("T", dimensionTag);
        } else if (tag == 2039 && !dimensionOrdering.containsValue(dimensionTag)) {
          dimensionOrdering.put("L", dimensionTag);
        } else if (tag == 2008
            && foundChannelTag
            && !dimensionOrdering.containsValue(dimensionTag)) {
          dimensionOrdering.put("C", dimensionTag);
        } else if (tag == 2008) {
          foundChannelTag = true;
        }
      }

      if (nextField == 0) {
        return;
      }

      if (fp + nextField < vsi.length() && fp + nextField >= 0) {
        vsi.seek(fp + nextField);
      } else break;
    }
  }
Example #2
0
  /**
   * Constructs a new SDT header by reading values from the given input source, populating the given
   * metadata table.
   */
  public SDTInfo(RandomAccessInputStream in, Hashtable meta) throws IOException {
    // read bhfileHeader
    revision = in.readShort();
    infoOffs = in.readInt();
    infoLength = in.readShort();
    setupOffs = in.readInt();
    setupLength = in.readShort();
    dataBlockOffs = in.readInt();
    noOfDataBlocks = in.readShort();
    dataBlockLength = in.readInt();
    measDescBlockOffs = in.readInt();
    noOfMeasDescBlocks = in.readShort();
    measDescBlockLength = in.readShort();
    headerValid = in.readUnsignedShort();
    reserved1 = (0xffffffffL & in.readInt()); // unsigned
    reserved2 = in.readUnsignedShort();
    chksum = in.readUnsignedShort();

    // save bhfileHeader to metadata table
    if (meta != null) {
      final String bhfileHeader = "bhfileHeader.";
      meta.put(bhfileHeader + "revision", new Short(revision));
      meta.put(bhfileHeader + "infoOffs", new Integer(infoOffs));
      meta.put(bhfileHeader + "infoLength", new Short(infoLength));
      meta.put(bhfileHeader + "setupOffs", new Integer(setupOffs));
      meta.put(bhfileHeader + "dataBlockOffs", new Integer(dataBlockOffs));
      meta.put(bhfileHeader + "noOfDataBlocks", new Short(noOfDataBlocks));
      meta.put(bhfileHeader + "dataBlockLength", new Integer(dataBlockLength));
      meta.put(bhfileHeader + "measDescBlockOffs", new Integer(measDescBlockOffs));
      meta.put(bhfileHeader + "noOfMeasDescBlocks", new Short(noOfMeasDescBlocks));
      meta.put(bhfileHeader + "measDescBlockLength", new Integer(measDescBlockLength));
      meta.put(bhfileHeader + "headerValid", new Integer(headerValid));
      meta.put(bhfileHeader + "reserved1", new Long(reserved1));
      meta.put(bhfileHeader + "reserved2", new Integer(reserved2));
      meta.put(bhfileHeader + "chksum", new Integer(chksum));
    }

    // read file info
    in.seek(infoOffs);
    byte[] infoBytes = new byte[infoLength];
    in.readFully(infoBytes);
    info = new String(infoBytes, Constants.ENCODING);

    StringTokenizer st = new StringTokenizer(info, "\n");
    int count = st.countTokens();
    st.nextToken();
    String key = null, value = null;
    for (int i = 1; i < count - 1; i++) {
      String token = st.nextToken().trim();
      if (token.indexOf(":") == -1) continue;
      key = token.substring(0, token.indexOf(":")).trim();
      value = token.substring(token.indexOf(":") + 1).trim();
      meta.put(key, value);
    }

    // read setup
    in.seek(setupOffs);
    byte[] setupBytes = new byte[setupLength];
    in.readFully(setupBytes);
    setup = new String(setupBytes, Constants.ENCODING);

    int textEnd = setup.indexOf(BINARY_SETUP);
    if (textEnd > 0) {
      setup = setup.substring(0, textEnd);
      textEnd += BINARY_SETUP.length();
      in.seek(setupOffs + textEnd);
    }

    // variables to hold height & width read from header string for measMode 13
    int mode13width = 0;
    int mode13height = 0;

    st = new StringTokenizer(setup, "\n");
    while (st.hasMoreTokens()) {
      String token = st.nextToken().trim();

      if (token.startsWith("#SP")
          || token.startsWith("#DI")
          || token.startsWith("#PR")
          || token.startsWith("#MP")) {
        int open = token.indexOf("[");
        key = token.substring(open + 1, token.indexOf(",", open));
        value = token.substring(token.lastIndexOf(",") + 1, token.length() - 1);
      } else if (token.startsWith("#TR") || token.startsWith("#WI")) {
        key = token.substring(0, token.indexOf("[")).trim();
        value = token.substring(token.indexOf("[") + 1, token.indexOf("]"));
      }

      if (key != null && value != null) meta.put(key, value);

      if (token.indexOf(X_STRING) != -1) {
        int ndx = token.indexOf(X_STRING) + X_STRING.length();
        int end = token.indexOf("]", ndx);
        width = Integer.parseInt(token.substring(ndx, end));
      } else if (token.indexOf(Y_STRING) != -1) {
        int ndx = token.indexOf(Y_STRING) + Y_STRING.length();
        int end = token.indexOf("]", ndx);
        height = Integer.parseInt(token.substring(ndx, end));
      } else if (token.indexOf(T_STRING) != -1) {
        int ndx = token.indexOf(T_STRING) + T_STRING.length();
        int end = token.indexOf("]", ndx);
        timeBins = Integer.parseInt(token.substring(ndx, end));
      } else if (token.indexOf(C_STRING) != -1) {
        int ndx = token.indexOf(C_STRING) + C_STRING.length();
        int end = token.indexOf("]", ndx);
        channels = Integer.parseInt(token.substring(ndx, end));
      } else if (token.indexOf(X_IMG_STRING) != -1) {
        int ndx = token.indexOf(X_IMG_STRING) + X_IMG_STRING.length();
        int end = token.indexOf("]", ndx);
        mode13width = Integer.parseInt(token.substring(ndx, end));
      } else if (token.indexOf(Y_IMG_STRING) != -1) {
        int ndx = token.indexOf(Y_IMG_STRING) + Y_IMG_STRING.length();
        int end = token.indexOf("]", ndx);
        mode13height = Integer.parseInt(token.substring(ndx, end));
      }
    }

    if (in.getFilePointer() < setupOffs + setupLength) {
      // BHBinHdr

      in.skipBytes(4);
      long baseOffset = in.getFilePointer();
      long softwareRevision = readUnsignedLong(in);
      long paramLength = readUnsignedLong(in);
      long reserved1 = readUnsignedLong(in);
      int reserved2 = in.readShort() & 0xffff;

      // SPCBinHdr

      long fcsOldOffset = readUnsignedLong(in);
      long fcsOldSize = readUnsignedLong(in);
      long gr1Offset = readUnsignedLong(in);
      long gr1Size = readUnsignedLong(in);
      long fcsOffset = readUnsignedLong(in);
      long fcsSize = readUnsignedLong(in);
      long fidaOffset = readUnsignedLong(in);
      long fidaSize = readUnsignedLong(in);
      long fildaOffset = readUnsignedLong(in);
      long fildaSize = readUnsignedLong(in);
      long gr2Offset = readUnsignedLong(in);
      int grNo = in.readShort() & 0xffff;
      int hstNo = in.readShort() & 0xffff;
      long hstOffset = readUnsignedLong(in);
      long gvdOffset = readUnsignedLong(in);
      int gvdSize = in.readShort() & 0xffff;
      int fitOffset = in.readShort() & 0xffff;
      int fitSize = in.readShort() & 0xffff;
      int extdevOffset = in.readShort() & 0xffff;
      int extdevSize = in.readShort() & 0xffff;
      long binhdrextOffset = readUnsignedLong(in);
      int binhdrextSize = in.readShort() & 0xffff;

      if (binhdrextOffset != 0) {
        in.seek(baseOffset + binhdrextOffset);
        long mcsImgOffset = readUnsignedLong(in);
        long mcsImgSize = readUnsignedLong(in);
        int momNo = in.readShort() & 0xffff;
        int momSize = in.readShort() & 0xffff;
        long momOffset = readUnsignedLong(in);
        long sysparExtOffset = readUnsignedLong(in);
        long sysparExtSize = readUnsignedLong(in);
        long mosaicOffset = readUnsignedLong(in);
        long mosaicSize = readUnsignedLong(in);
        // 52 longs reserved

        if (mcsImgOffset != 0) {
          in.seek(baseOffset + mcsImgOffset);

          int mcsActive = in.readInt();
          in.skipBytes(4); // Window
          mcstaPoints = in.readShort() & 0xffff;
          int mcstaFlags = in.readShort() & 0xffff;
          int mcstaTimePerPoint = in.readShort() & 0xffff;
          float mcsOffset = in.readFloat();
          float mcsTpp = in.readFloat();

          if (meta != null) {
            meta.put("MCS_TA.active", mcsActive);
            meta.put("MCS_TA.points", mcstaPoints);
            meta.put("MCS_TA.flags", mcstaFlags);
            meta.put("MCS_TA.time per point", mcstaTimePerPoint);
          }
        }
      }
    }

    // read measurement data
    if (noOfMeasDescBlocks > 0) {
      in.seek(measDescBlockOffs);

      hasMeasureInfo = measDescBlockLength >= 211;
      hasMeasStopInfo = measDescBlockLength >= 211 + 60;
      hasMeasFCSInfo = measDescBlockLength >= 211 + 60 + 38;
      hasExtendedMeasureInfo = measDescBlockLength >= 211 + 60 + 38 + 26;
      hasMeasHISTInfo = measDescBlockLength >= 211 + 60 + 38 + 26 + 24;

      if (hasMeasureInfo) {
        time = in.readString(9).trim();
        date = in.readString(11).trim();
        modSerNo = in.readString(16).trim();

        measMode = in.readShort();
        cfdLL = in.readFloat();
        cfdLH = in.readFloat();
        cfdZC = in.readFloat();
        cfdHF = in.readFloat();
        synZC = in.readFloat();
        synFD = in.readShort();
        synHF = in.readFloat();
        tacR = in.readFloat();
        tacG = in.readShort();
        tacOF = in.readFloat();
        tacLL = in.readFloat();
        tacLH = in.readFloat();
        adcRE = in.readShort();
        ealDE = in.readShort();
        ncx = in.readShort();
        ncy = in.readShort();
        page = in.readUnsignedShort();
        colT = in.readFloat();
        repT = in.readFloat();
        stopt = in.readShort();
        overfl = in.readUnsignedByte();
        useMotor = in.readShort();
        steps = in.readUnsignedShort();
        offset = in.readFloat();
        dither = in.readShort();
        incr = in.readShort();
        memBank = in.readShort();

        modType = in.readString(16).trim();

        synTH = in.readFloat();
        deadTimeComp = in.readShort();
        polarityL = in.readShort();
        polarityF = in.readShort();
        polarityP = in.readShort();
        linediv = in.readShort();
        accumulate = in.readShort();
        flbckY = in.readInt();
        flbckX = in.readInt();
        bordU = in.readInt();
        bordL = in.readInt();
        pixTime = in.readFloat();
        pixClk = in.readShort();
        trigger = in.readShort();
        scanX = in.readInt();
        scanY = in.readInt();
        scanRX = in.readInt();
        scanRY = in.readInt();
        fifoTyp = in.readShort();
        epxDiv = in.readInt();
        modTypeCode = in.readUnsignedShort();
        modFpgaVer = in.readUnsignedShort();
        overflowCorrFactor = in.readFloat();
        adcZoom = in.readInt();
        cycles = in.readInt();

        timepoints = stopt;

        // save MeasureInfo to metadata table
        if (meta != null) {
          final String measureInfo = "MeasureInfo.";
          meta.put(measureInfo + "time", time);
          meta.put(measureInfo + "date", date);
          meta.put(measureInfo + "modSerNo", modSerNo);
          meta.put(measureInfo + "measMode", new Short(measMode));
          meta.put(measureInfo + "cfdLL", new Float(cfdLL));
          meta.put(measureInfo + "cfdLH", new Float(cfdLH));
          meta.put(measureInfo + "cfdZC", new Float(cfdZC));
          meta.put(measureInfo + "cfdHF", new Float(cfdHF));
          meta.put(measureInfo + "synZC", new Float(synZC));
          meta.put(measureInfo + "synFD", new Short(synFD));
          meta.put(measureInfo + "synHF", new Float(synHF));
          meta.put(measureInfo + "tacR", new Float(tacR));
          meta.put(measureInfo + "tacG", new Short(tacG));
          meta.put(measureInfo + "tacOF", new Float(tacOF));
          meta.put(measureInfo + "tacLL", new Float(tacLL));
          meta.put(measureInfo + "tacLH", new Float(tacLH));
          meta.put(measureInfo + "adcRE", new Short(adcRE));
          meta.put(measureInfo + "ealDE", new Short(ealDE));
          meta.put(measureInfo + "ncx", new Short(ncx));
          meta.put(measureInfo + "ncy", new Short(ncy));
          meta.put(measureInfo + "page", new Integer(page));
          meta.put(measureInfo + "colT", new Float(colT));
          meta.put(measureInfo + "repT", new Float(repT));
          meta.put(measureInfo + "stopt", new Short(stopt));
          meta.put(measureInfo + "overfl", new Integer(overfl));
          meta.put(measureInfo + "useMotor", new Short(useMotor));
          meta.put(measureInfo + "steps", new Integer(steps));
          meta.put(measureInfo + "offset", new Float(offset));
          meta.put(measureInfo + "dither", new Short(dither));
          meta.put(measureInfo + "incr", new Short(incr));
          meta.put(measureInfo + "memBank", new Short(memBank));
          meta.put(measureInfo + "modType", modType);
          meta.put(measureInfo + "synTH", new Float(synTH));
          meta.put(measureInfo + "deadTimeComp", new Short(deadTimeComp));
          meta.put(measureInfo + "polarityL", new Short(polarityL));
          meta.put(measureInfo + "polarityF", new Short(polarityF));
          meta.put(measureInfo + "polarityP", new Short(polarityP));
          meta.put(measureInfo + "linediv", new Short(linediv));
          meta.put(measureInfo + "accumulate", new Short(accumulate));
          meta.put(measureInfo + "flbckY", new Integer(flbckY));
          meta.put(measureInfo + "flbckX", new Integer(flbckX));
          meta.put(measureInfo + "bordU", new Integer(bordU));
          meta.put(measureInfo + "bordL", new Integer(bordL));
          meta.put(measureInfo + "pixTime", new Float(pixTime));
          meta.put(measureInfo + "pixClk", new Short(pixClk));
          meta.put(measureInfo + "trigger", new Short(trigger));
          meta.put(measureInfo + "scanX", new Integer(scanX));
          meta.put(measureInfo + "scanY", new Integer(scanY));
          meta.put(measureInfo + "scanRX", new Integer(scanRX));
          meta.put(measureInfo + "scanRY", new Integer(scanRY));
          meta.put(measureInfo + "fifoTyp", new Short(fifoTyp));
          meta.put(measureInfo + "epxDiv", new Integer(epxDiv));
          meta.put(measureInfo + "modTypeCode", new Integer(modTypeCode));
          meta.put(measureInfo + "modFpgaVer", new Integer(modFpgaVer));
          meta.put(measureInfo + "overflowCorrFactor", new Float(overflowCorrFactor));
          meta.put(measureInfo + "adcZoom", new Integer(adcZoom));
          meta.put(measureInfo + "cycles", new Integer(cycles));
        }

        // extract dimensional parameters from measure info
        if (scanX > 0) width = scanX;
        if (scanY > 0) height = scanY;
        if (adcRE > 0) timeBins = adcRE;
        if (scanRX > 0) channels = scanRX;

        // measurement mode 0 and 1 are both single-point data
        if (measMode == 0 || measMode == 1) {
          width = 1;
          height = 1;
        }

        // for measurement_mode 13 one channel is stored in each block
        // & width & height are not in scanX & scanY
        if (measMode == 13) {
          width = mode13width;
          height = mode13height;
          channels = noOfMeasDescBlocks;
        }
      }

      if (hasMeasStopInfo) {
        // MeasStopInfo - information collected when measurement is finished
        status = in.readUnsignedShort();
        flags = in.readUnsignedShort();
        stopTime = in.readFloat();
        curStep = in.readInt();
        curCycle = in.readInt();
        curPage = in.readInt();
        minSyncRate = in.readFloat();
        minCfdRate = in.readFloat();
        minTacRate = in.readFloat();
        minAdcRate = in.readFloat();
        maxSyncRate = in.readFloat();
        maxCfdRate = in.readFloat();
        maxTacRate = in.readFloat();
        maxAdcRate = in.readFloat();
        mReserved1 = in.readInt();
        mReserved2 = in.readFloat();

        // save MeasStopInfo to metadata table
        if (meta != null) {
          final String measStopInfo = "MeasStopInfo.";
          meta.put(measStopInfo + "status", new Integer(status));
          meta.put(measStopInfo + "flags", new Integer(flags));
          meta.put(measStopInfo + "stopTime", new Float(stopTime));
          meta.put(measStopInfo + "curStep", new Integer(curStep));
          meta.put(measStopInfo + "curCycle", new Integer(curCycle));
          meta.put(measStopInfo + "curPage", new Integer(curPage));
          meta.put(measStopInfo + "minSyncRate", new Float(minSyncRate));
          meta.put(measStopInfo + "minCfdRate", new Float(minCfdRate));
          meta.put(measStopInfo + "minTacRate", new Float(minTacRate));
          meta.put(measStopInfo + "minAdcRate", new Float(minAdcRate));
          meta.put(measStopInfo + "maxSyncRate", new Float(maxSyncRate));
          meta.put(measStopInfo + "maxCfdRate", new Float(maxCfdRate));
          meta.put(measStopInfo + "maxTacRate", new Float(maxTacRate));
          meta.put(measStopInfo + "maxAdcRate", new Float(maxAdcRate));
          meta.put(measStopInfo + "reserved1", new Integer(mReserved1));
          meta.put(measStopInfo + "reserved2", new Float(mReserved2));
        }
      }

      if (hasMeasFCSInfo) {
        // MeasFCSInfo - information collected when FIFO measurement is finished
        chan = in.readUnsignedShort();
        fcsDecayCalc = in.readUnsignedShort();
        mtResol = (0xffffffffL & in.readInt()); // unsigned
        cortime = in.readFloat();
        calcPhotons = (0xffffffffL & in.readInt()); // unsigned
        fcsPoints = in.readInt();
        endTime = in.readFloat();
        overruns = in.readUnsignedShort();
        fcsType = in.readUnsignedShort();
        crossChan = in.readUnsignedShort();
        mod = in.readUnsignedShort();
        crossMod = in.readUnsignedShort();
        crossMtResol = (0xffffffffL & in.readInt()); // unsigned

        // save MeasFCSInfo to metadata table
        if (meta != null) {
          final String measFCSInfo = "MeasFCSInfo.";
          meta.put(measFCSInfo + "chan", new Integer(chan));
          meta.put(measFCSInfo + "fcsDecayCalc", new Integer(fcsDecayCalc));
          meta.put(measFCSInfo + "mtResol", new Long(mtResol));
          meta.put(measFCSInfo + "cortime", new Float(cortime));
          meta.put(measFCSInfo + "calcPhotons", new Long(calcPhotons));
          meta.put(measFCSInfo + "fcsPoints", new Integer(fcsPoints));
          meta.put(measFCSInfo + "endTime", new Float(endTime));
          meta.put(measFCSInfo + "overruns", new Integer(overruns));
          meta.put(measFCSInfo + "fcsType", new Integer(fcsType));
          meta.put(measFCSInfo + "crossChan", new Integer(crossChan));
          meta.put(measFCSInfo + "mod", new Integer(mod));
          meta.put(measFCSInfo + "crossMod", new Integer(crossMod));
          meta.put(measFCSInfo + "crossMtResol", new Float(crossMtResol));
        }
      }

      if (hasExtendedMeasureInfo) {
        imageX = in.readInt();
        imageY = in.readInt();
        imageRX = in.readInt();
        imageRY = in.readInt();
        xyGain = in.readShort();
        masterClock = in.readShort();
        adcDE = in.readShort();
        detType = in.readShort();
        xAxis = in.readShort();

        // save extra MeasureInfo to metadata table
        if (meta != null) {
          final String measureInfo = "MeasureInfo.";
          meta.put(measureInfo + "imageX", new Integer(imageX));
          meta.put(measureInfo + "imageY", new Integer(imageY));
          meta.put(measureInfo + "imageRX", new Integer(imageRX));
          meta.put(measureInfo + "imageRY", new Integer(imageRY));
          meta.put(measureInfo + "xyGain", new Short(xyGain));
          meta.put(measureInfo + "masterClock", new Short(masterClock));
          meta.put(measureInfo + "adcDE", new Short(adcDE));
          meta.put(measureInfo + "detType", new Short(detType));
          meta.put(measureInfo + "xAxis", new Short(xAxis));
        }
      }

      if (hasMeasHISTInfo) {
        // MeasHISTInfo - extension of FCSInfo, valid only for FIFO meas
        // extension of MeasFCSInfo for other histograms (FIDA, FILDA, MCS)
        fidaTime = in.readFloat();
        fildaTime = in.readFloat();
        fidaPoints = in.readInt();
        fildaPoints = in.readInt();
        mcsTime = in.readFloat();
        mcsPoints = in.readInt();

        // save MeasHISTInfo to metadata table
        if (meta != null) {
          final String measHISTInfo = "MeasHISTInfo.";
          meta.put(measHISTInfo + "fidaTime", new Float(fidaTime));
          meta.put(measHISTInfo + "fildaTime", new Float(fildaTime));
          meta.put(measHISTInfo + "fidaPoints", new Integer(fidaPoints));
          meta.put(measHISTInfo + "fildaPoints", new Integer(fildaPoints));
          meta.put(measHISTInfo + "mcsTime", new Float(mcsTime));
          meta.put(measHISTInfo + "mcsPoints", new Integer(mcsPoints));
        }
      }
    }

    in.seek(dataBlockOffs);

    allBlockOffsets = new long[noOfDataBlocks];
    allBlockLengths = new long[noOfDataBlocks];

    for (int i = 0; i < noOfDataBlocks; i++) {
      // read BHFileBlockHeader
      blockNo = in.readShort();
      dataOffs = in.readInt();
      nextBlockOffs = in.readInt();
      blockType = in.readUnsignedShort();
      measDescBlockNo = in.readShort();
      lblockNo = (0xffffffffL & in.readInt()); // unsigned
      int len = in.readInt();
      blockLength = (0xffffffffL & len); // unsigned

      allBlockOffsets[i] = in.getFilePointer();
      allBlockLengths[i] = blockLength;

      // save BHFileBlockHeader to metadata table
      if (meta != null) {
        final String bhFileBlockHeader = "BHFileBlockHeader.";
        meta.put(bhFileBlockHeader + "blockNo", new Short(blockNo));
        meta.put(bhFileBlockHeader + "dataOffs", new Integer(dataOffs));
        meta.put(bhFileBlockHeader + "nextBlockOffs", new Integer(nextBlockOffs));
        meta.put(bhFileBlockHeader + "blockType", new Integer(blockType));
        meta.put(bhFileBlockHeader + "measDescBlockNo", new Short(measDescBlockNo));
        meta.put(bhFileBlockHeader + "lblockNo", new Long(lblockNo));
        meta.put(bhFileBlockHeader + "blockLength", new Long(blockLength));
      }

      in.skipBytes(len);
    }
  }
  /**
   * The CodecOptions parameter should have the following fields set: {@link CodecOptions#width
   * width} {@link CodecOptions#height height}
   *
   * @see Codec#decompress(RandomAccessInputStream, CodecOptions)
   */
  public byte[] decompress(RandomAccessInputStream in, CodecOptions options)
      throws FormatException, IOException {
    if (in == null) throw new IllegalArgumentException("No data to decompress.");
    if (options == null) options = CodecOptions.getDefaultOptions();

    in.skipBytes(8);

    int plane = options.width * options.height;

    stride = options.width;
    int rowInc = stride - 4;
    short opcode;
    int nBlocks;
    int colorA = 0, colorB;
    int[] color4 = new int[4];
    int index, idx;
    int ta, tb;
    int blockPtr = 0;
    rowPtr = pixelPtr = 0;
    int pixelX, pixelY;

    int[] pixels = new int[plane];
    byte[] rtn = new byte[plane * 3];

    while (in.read() != (byte) 0xe1) ;
    in.skipBytes(3);

    totalBlocks = ((options.width + 3) / 4) * ((options.height + 3) / 4);

    while (in.getFilePointer() + 2 < in.length()) {
      opcode = in.readByte();
      nBlocks = (opcode & 0x1f) + 1;

      if ((opcode & 0x80) == 0) {
        if (in.getFilePointer() >= in.length()) break;
        colorA = (opcode << 8) | in.read();
        opcode = 0;
        if (in.getFilePointer() >= in.length()) break;
        if ((in.read() & 0x80) != 0) {
          opcode = 0x20;
          nBlocks = 1;
        }
        in.seek(in.getFilePointer() - 1);
      }

      switch (opcode & 0xe0) {
        case 0x80:
          while (nBlocks-- > 0) {
            updateBlock(options.width);
          }
          break;
        case 0xa0:
          if (in.getFilePointer() + 2 >= in.length()) break;
          colorA = in.readShort();
          while (nBlocks-- > 0) {
            blockPtr = rowPtr + pixelPtr;
            for (pixelY = 0; pixelY < 4; pixelY++) {
              for (pixelX = 0; pixelX < 4; pixelX++) {
                if (blockPtr >= pixels.length) break;
                pixels[blockPtr] = colorA;

                short s = (short) (pixels[blockPtr] & 0x7fff);
                unpack(s, rtn, blockPtr, pixels.length);
                blockPtr++;
              }
              blockPtr += rowInc;
            }
            updateBlock(options.width);
          }
          break;
        case 0xc0:
        case 0x20:
          if (in.getFilePointer() + 2 >= in.length()) break;
          if ((opcode & 0xe0) == 0xc0) {
            colorA = in.readShort();
          }

          colorB = in.readShort();

          color4[0] = colorB;
          color4[1] = 0;
          color4[2] = 0;
          color4[3] = colorA;

          ta = (colorA >> 10) & 0x1f;
          tb = (colorB >> 10) & 0x1f;
          color4[1] |= ((11 * ta + 21 * tb) >> 5) << 10;
          color4[2] |= ((21 * ta + 11 * tb) >> 5) << 10;

          ta = (colorA >> 5) & 0x1f;
          tb = (colorB >> 5) & 0x1f;
          color4[1] |= ((11 * ta + 21 * tb) >> 5) << 5;
          color4[2] |= ((21 * ta + 11 * tb) >> 5) << 5;

          ta = colorA & 0x1f;
          tb = colorB & 0x1f;
          color4[1] |= (11 * ta + 21 * tb) >> 5;
          color4[2] |= (21 * ta + 11 * tb) >> 5;

          while (nBlocks-- > 0) {
            blockPtr = rowPtr + pixelPtr;
            for (pixelY = 0; pixelY < 4; pixelY++) {
              if (in.getFilePointer() >= in.length()) break;
              index = in.read();
              for (pixelX = 0; pixelX < 4; pixelX++) {
                idx = (index >> (2 * (3 - pixelX))) & 3;
                if (blockPtr >= pixels.length) break;
                pixels[blockPtr] = color4[idx];

                short s = (short) (pixels[blockPtr] & 0x7fff);
                unpack(s, rtn, blockPtr, pixels.length);
                blockPtr++;
              }
              blockPtr += rowInc;
            }
            updateBlock(options.width);
          }
          break;
        case 0x00:
          blockPtr = rowPtr + pixelPtr;
          for (pixelY = 0; pixelY < 4; pixelY++) {
            for (pixelX = 0; pixelX < 4; pixelX++) {
              if ((pixelY != 0) || (pixelX != 0)) {
                if (in.getFilePointer() + 2 >= in.length()) break;
                colorA = in.readShort();
              }
              if (blockPtr >= pixels.length) break;
              pixels[blockPtr] = colorA;

              short s = (short) (pixels[blockPtr] & 0x7fff);
              unpack(s, rtn, blockPtr, pixels.length);
              blockPtr++;
            }
            blockPtr += rowInc;
          }
          updateBlock(options.width);
          break;
      }
    }
    return rtn;
  }