From 0f36dd6f49d50befc49547ee3c49c3c322c1c696 Mon Sep 17 00:00:00 2001 From: Walker Knapp Date: Thu, 6 Jun 2024 18:43:46 -0400 Subject: [PATCH 1/3] Andor SIF: Prevent integer overflow when parsing files --- components/formats-gpl/src/loci/formats/in/SIFReader.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/components/formats-gpl/src/loci/formats/in/SIFReader.java b/components/formats-gpl/src/loci/formats/in/SIFReader.java index 25d506e561e..993c93f28af 100644 --- a/components/formats-gpl/src/loci/formats/in/SIFReader.java +++ b/components/formats-gpl/src/loci/formats/in/SIFReader.java @@ -79,7 +79,7 @@ public byte[] openBytes(int no, byte[] buf, int x, int y, int w, int h) { FormatTools.checkPlaneParameters(this, no, buf.length, x, y, w, h); - in.seek(pixelOffset + no * FormatTools.getPlaneSize(this)); + in.seek(pixelOffset + (long)no * (long)FormatTools.getPlaneSize(this)); readPlane(in, x, y, w, h, buf); return buf; } @@ -147,10 +147,10 @@ else if (lineNumber < endLine) { int y2 = Integer.parseInt(tokens[4]); int x3 = Integer.parseInt(tokens[5]); int y3 = Integer.parseInt(tokens[6]); - m.sizeX = (int) (Math.abs(x1 - x2) + x3); - m.sizeY = (int) (Math.abs(y1 - y2) + y3); + m.sizeX = Math.abs(x1 - x2) + x3; + m.sizeY = Math.abs(y1 - y2) + y3; pixelOffset = in.length() - FOOTER_SIZE - - (getImageCount() * getSizeX() * getSizeY() * 4); + ((long)getImageCount() * (long)getSizeX() * (long)getSizeY() * 4L); } } From e918721f14c5eb4f2321d22c18ecff9839d123c2 Mon Sep 17 00:00:00 2001 From: Walker Knapp Date: Fri, 7 Jun 2024 11:29:02 -0400 Subject: [PATCH 2/3] Andor SIF: Support files with non-standard footers --- .../src/loci/formats/in/SIFReader.java | 117 ++++++++++-------- 1 file changed, 67 insertions(+), 50 deletions(-) diff --git a/components/formats-gpl/src/loci/formats/in/SIFReader.java b/components/formats-gpl/src/loci/formats/in/SIFReader.java index 993c93f28af..b3d306ea99d 100644 --- a/components/formats-gpl/src/loci/formats/in/SIFReader.java +++ b/components/formats-gpl/src/loci/formats/in/SIFReader.java @@ -46,7 +46,6 @@ public class SIFReader extends FormatReader { // -- Constants -- private static final String MAGIC_STRING = "Andor Technology"; - private static final int FOOTER_SIZE = 8; // -- Fields -- @@ -102,56 +101,74 @@ protected void initFile(String id) throws FormatException, IOException { in = new RandomAccessInputStream(id); CoreMetadata m = core.get(0); - double[] timestamp = null; - - int lineNumber = 1; - String line = in.readLine(); - int endLine = -1; - while (endLine < 0 || lineNumber < endLine) { - lineNumber++; - - if (line.startsWith("Pixel number")) { - String[] tokens = line.split(" "); - if (tokens.length > 2) { - m.sizeC = Integer.parseInt(tokens[2]); - m.sizeX = Integer.parseInt(tokens[3]); - m.sizeY = Integer.parseInt(tokens[4]); - m.sizeZ = Integer.parseInt(tokens[5]); - m.sizeT = Integer.parseInt(tokens[6]); - m.imageCount = getSizeZ() * getSizeT() * getSizeC(); - timestamp = new double[getImageCount()]; - endLine = lineNumber; - } - } - else if (lineNumber < endLine) { - int index = lineNumber - (endLine - getImageCount()) - 1; - if (index >= 0) { - try { - timestamp[index] = Double.parseDouble(line.trim()); - } - catch (NumberFormatException e) { - LOGGER.debug("Could not parse timestamp #" + index, e); - } - } - } - else { - addGlobalMetaList("Line", line.trim()); - } + String line; + String[] tokens; + + // Magic line + addGlobalMetaList("Line", in.readLine()); + addGlobalMetaList("Line", in.readLine()); + + // Parse SIF version + addGlobalMetaList("Line", line = in.readLine()); + tokens = line.split(" "); + int sifVersion = Integer.parseInt(tokens[0]); + + // Read header contents until "Pixel number" line with enough tokens to contain size information + while (!(line = in.readLine()).startsWith("Pixel number") + || (tokens = line.split(" ")).length < 3) { + addGlobalMetaList("Line", line.trim()); + } + + // Read channels, Z sections, and image count from this line + // X and Y from this line are not accurate for cropped images + m.sizeC = Integer.parseInt(tokens[2]); + m.sizeZ = Integer.parseInt(tokens[5]); + m.sizeT = Integer.parseInt(tokens[6]); + m.imageCount = getSizeZ() * getSizeT() * getSizeC(); + + // Subsequent lines contain updated information for each subimage + for (int i = 0; i < getSizeZ(); i++) { line = in.readLine(); + tokens = line.split(" "); + + int x1 = Integer.parseInt(tokens[1]); + int y1 = Integer.parseInt(tokens[2]); + int x2 = Integer.parseInt(tokens[3]); + int y2 = Integer.parseInt(tokens[4]); + int x3 = Integer.parseInt(tokens[5]); + int y3 = Integer.parseInt(tokens[6]); + m.sizeX = Math.abs(x1 - x2) + x3; + m.sizeY = Math.abs(y1 - y2) + y3; + } + + // Parse timestamps for each frame + double[] timestamp = new double[getImageCount()]; + for (int i = 0; i < getImageCount(); i++) { + line = in.readLine(); + + timestamp[i] = Double.parseDouble(line.trim()); + } + + pixelOffset = in.getFilePointer(); + + // Some SIF versions contain an additional flag and data block following timestamps. + // If present, the pixelOffset must be adjusted to after this data. + // TODO: Is there any way to parse this segment while guaranteeing that we aren't reading image data? + // I.e., is it possible that the first two bytes of image data are 0x300A ("0\n")? + byte flag = in.readByte(); + byte flagTerminator = in.readByte(); + + if (flag == '1' && flagTerminator == '\n') { + if (sifVersion == 65567) { + // SIF Version 65567 contains an additional table for all frames + for (int i = 0; i < getImageCount(); i++) { + line = in.readLine(); + } - if (endLine > 0 && endLine == lineNumber) { - String[] tokens = line.split(" "); - int x1 = Integer.parseInt(tokens[1]); - int y1 = Integer.parseInt(tokens[2]); - int x2 = Integer.parseInt(tokens[3]); - int y2 = Integer.parseInt(tokens[4]); - int x3 = Integer.parseInt(tokens[5]); - int y3 = Integer.parseInt(tokens[6]); - m.sizeX = Math.abs(x1 - x2) + x3; - m.sizeY = Math.abs(y1 - y2) + y3; - pixelOffset = in.length() - FOOTER_SIZE - - ((long)getImageCount() * (long)getSizeX() * (long)getSizeY() * 4L); + pixelOffset = in.getFilePointer(); } + } else if (flag == '0' && flagTerminator == '\n') { + pixelOffset = in.getFilePointer(); } m.pixelType = FormatTools.FLOAT; @@ -160,11 +177,11 @@ else if (lineNumber < endLine) { MetadataStore store = makeFilterMetadata(); MetadataTools.populatePixels(store, this, - getMetadataOptions().getMetadataLevel() != MetadataLevel.MINIMUM); + getMetadataOptions().getMetadataLevel() != MetadataLevel.MINIMUM); if (getMetadataOptions().getMetadataLevel() != MetadataLevel.MINIMUM) { for (int i=0; i Date: Fri, 7 Jun 2024 14:56:51 -0400 Subject: [PATCH 3/3] Andor SIF: Improve performance of reading fixed-width ASCII tables --- .../src/loci/formats/in/SIFReader.java | 38 +++++++++++++++---- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/components/formats-gpl/src/loci/formats/in/SIFReader.java b/components/formats-gpl/src/loci/formats/in/SIFReader.java index b3d306ea99d..92531bcbb61 100644 --- a/components/formats-gpl/src/loci/formats/in/SIFReader.java +++ b/components/formats-gpl/src/loci/formats/in/SIFReader.java @@ -26,6 +26,9 @@ package loci.formats.in; import java.io.IOException; +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; import loci.common.RandomAccessInputStream; import loci.formats.CoreMetadata; @@ -143,10 +146,9 @@ protected void initFile(String id) throws FormatException, IOException { // Parse timestamps for each frame double[] timestamp = new double[getImageCount()]; - for (int i = 0; i < getImageCount(); i++) { - line = in.readLine(); - - timestamp[i] = Double.parseDouble(line.trim()); + String[] lines = readFixedWidthTable(getImageCount()); + for (int i = 0; i < lines.length; i++) { + timestamp[i] = Double.parseDouble(lines[i].trim()); } pixelOffset = in.getFilePointer(); @@ -161,9 +163,7 @@ protected void initFile(String id) throws FormatException, IOException { if (flag == '1' && flagTerminator == '\n') { if (sifVersion == 65567) { // SIF Version 65567 contains an additional table for all frames - for (int i = 0; i < getImageCount(); i++) { - line = in.readLine(); - } + readFixedWidthTable(getImageCount()); pixelOffset = in.getFilePointer(); } @@ -177,7 +177,7 @@ protected void initFile(String id) throws FormatException, IOException { MetadataStore store = makeFilterMetadata(); MetadataTools.populatePixels(store, this, - getMetadataOptions().getMetadataLevel() != MetadataLevel.MINIMUM); + getMetadataOptions().getMetadataLevel() != MetadataLevel.MINIMUM); if (getMetadataOptions().getMetadataLevel() != MetadataLevel.MINIMUM) { for (int i=0; i