/* * #%L * OME Bio-Formats package for reading and converting biological file formats. * %% * Copyright (C) 2005 - 2015 Open Microscopy Environment: * - Board of Regents of the University of Wisconsin-Madison * - Glencoe Software, Inc. * - University of Dundee * %% * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program. If not, see * <http://www.gnu.org/licenses/gpl-2.0.html>. * #L% */ package loci.formats.in; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import loci.common.Location; import loci.common.RandomAccessInputStream; import loci.formats.CoreMetadata; import loci.formats.FormatException; import loci.formats.FormatReader; import loci.formats.FormatTools; import loci.formats.MetadataTools; import loci.formats.UnsupportedCompressionException; import loci.formats.codec.ZlibCodec; import loci.formats.meta.MetadataStore; import ome.xml.model.enums.NamingConvention; import ome.xml.model.primitives.NonNegativeInteger; import ome.xml.model.primitives.PositiveFloat; import ome.units.quantity.Length; /** * Reader for Cellomics C01 files. */ public class CellomicsReader extends FormatReader { // -- Constants -- public static final int C01_MAGIC_BYTES = 16; // -- Fields -- // A typical Cellomics file name is // WHICA-VTI1_090915160001_A01f00o1.DIB // The plate name is: // WHICA-VTI1_090915160001 // The well name is A01 // The site / field is 00 // the channel is 1 // // The channel prefix can be "o" or "d" // Both site and channel are optional. // // The pattern greedily captures: // The plate name in group 1 // The well name in group 2 // The field, optionally, in group 3 // The channel, optionally, in group 4 private static final Pattern PATTERN_O = Pattern.compile("(.*)_(\\p{Alpha}\\d{2})(f\\d{2,3})?(o\\d+)?[^_]+$"); private static final Pattern PATTERN_D = Pattern.compile("(.*)_(\\p{Alpha}\\d{2})(f\\d{2,3})?(d\\d+)?[^_]+$"); private Pattern cellomicsPattern; private String[] files; // -- Constructor -- /** Constructs a new Cellomics reader. */ public CellomicsReader() { super("Cellomics C01", new String[] {"c01", "dib"}); domains = new String[] {FormatTools.LM_DOMAIN, FormatTools.HCS_DOMAIN}; datasetDescription = "One or more .c01 files"; } // -- IFormatReader API methods -- /* @see loci.formats.IFormatReader#isThisType(RandomAccessInputStream) */ @Override public boolean isThisType(RandomAccessInputStream stream) throws IOException { final int blockLen = 4; if (!FormatTools.validStream(stream, blockLen, false)) return false; return stream.readInt() == C01_MAGIC_BYTES; } /* @see loci.formats.IFormatReader#getDomains() */ @Override public String[] getDomains() { FormatTools.assertId(currentId, true, 1); return new String[] {FormatTools.HCS_DOMAIN}; } /** * @see loci.formats.IFormatReader#openBytes(int, byte[], int, int, int, int) */ @Override public byte[] openBytes(int no, byte[] buf, int x, int y, int w, int h) throws FormatException, IOException { FormatTools.checkPlaneParameters(this, no, buf.length, x, y, w, h); int[] zct = getZCTCoords(no); String file = files[getSeries() * getSizeC() + zct[1]]; RandomAccessInputStream s = getDecompressedStream(file); int planeSize = FormatTools.getPlaneSize(this); s.seek(52 + zct[0] * planeSize); readPlane(s, x, y, w, h, buf); s.close(); return buf; } /* @see loci.formats.IFormatReader#close(boolean) */ @Override public void close(boolean fileOnly) throws IOException { super.close(fileOnly); if (!fileOnly) { files = null; cellomicsPattern = null; } } /* @see loci.formats.IFormatReader#getSeriesUsedFiles(boolean) */ @Override public String[] getSeriesUsedFiles(boolean noPixels) { FormatTools.assertId(currentId, true, 1); int nFiles = files.length / getSeriesCount(); String[] seriesFiles = new String[nFiles]; System.arraycopy(files, getSeries() * nFiles, seriesFiles, 0, nFiles); return seriesFiles; } /* @see loci.formats.IFormatReader#fileGroupOption(String) */ @Override public int fileGroupOption(String id) throws FormatException, IOException { return FormatTools.MUST_GROUP; } // -- Internal FormatReader API methods -- /* @see loci.formats.FormatReader#initFile(String) */ @Override protected void initFile(String id) throws FormatException, IOException { super.initFile(id); // look for files with similar names Location baseFile = new Location(id).getAbsoluteFile(); Location parent = baseFile.getParentFile(); ArrayList<String> pixelFiles = new ArrayList<String>(); String plateName = getPlateName(baseFile.getName()); if (plateName != null && isGroupFiles()) { String[] list = parent.list(); for (String f : list) { if (plateName.equals(getPlateName(f)) && (checkSuffix(f, "c01") || checkSuffix(f, "dib"))) { Location loc = new Location(parent, f); if ((!f.startsWith(".") || !loc.isHidden()) && getChannel(f) >= 0) { pixelFiles.add(loc.getAbsolutePath()); } } } } else pixelFiles.add(id); files = pixelFiles.toArray(new String[pixelFiles.size()]); int wellRows = 0; int wellColumns = 0; int fields = 0; ArrayList<Integer> uniqueRows = new ArrayList<Integer>(); ArrayList<Integer> uniqueCols = new ArrayList<Integer>(); ArrayList<Integer> uniqueFields = new ArrayList<Integer>(); ArrayList<Integer> uniqueChannels = new ArrayList<Integer>(); for (String f : files) { int wellRow = getWellRow(f); int wellCol = getWellColumn(f); int field = getField(f); int channel = getChannel(f); if (!uniqueRows.contains(wellRow)) uniqueRows.add(wellRow); if (!uniqueCols.contains(wellCol)) uniqueCols.add(wellCol); if (!uniqueFields.contains(field)) uniqueFields.add(field); if (!uniqueChannels.contains(channel)) uniqueChannels.add(channel); } fields = uniqueFields.size(); wellRows = uniqueRows.size(); wellColumns = uniqueCols.size(); if (fields * wellRows * wellColumns > files.length) { files = new String[] {id}; } Arrays.sort(files, new Comparator<String>() { @Override public int compare(String f1, String f2) { int wellRow1 = getWellRow(f1); int wellCol1 = getWellColumn(f1); int field1 = getField(f1); int channel1 = getChannel(f1); int wellRow2 = getWellRow(f2); int wellCol2 = getWellColumn(f2); int field2 = getField(f2); int channel2 = getChannel(f2); if (wellRow1 < wellRow2){ return -1; }else if (wellRow1 > wellRow2){ return 1; } if (wellCol1 < wellCol2){ return -1; }else if (wellCol1 > wellCol2){ return 1; } if (field1 < field2){ return -1; }else if (field1 > field2){ return 1; } return channel1-channel2; } }); core.clear(); int seriesCount = files.length; if (uniqueChannels.size() > 0) { seriesCount /= uniqueChannels.size(); } for (int i=0; i<seriesCount; i++) { core.add(new CoreMetadata()); } in = getDecompressedStream(id); LOGGER.info("Reading header data"); in.order(true); in.skipBytes(4); int x = in.readInt(); int y = in.readInt(); int nPlanes = in.readShort(); int nBits = in.readShort(); int compression = in.readInt(); if (x * y * nPlanes * (nBits / 8) + 52 > in.length()) { throw new UnsupportedCompressionException( "Compressed pixel data is not yet supported."); } in.skipBytes(4); int pixelWidth = 0, pixelHeight = 0; if (getMetadataOptions().getMetadataLevel() != MetadataLevel.MINIMUM) { pixelWidth = in.readInt(); pixelHeight = in.readInt(); int colorUsed = in.readInt(); int colorImportant = in.readInt(); LOGGER.info("Populating metadata hashtable"); addGlobalMeta("Image width", x); addGlobalMeta("Image height", y); addGlobalMeta("Number of planes", nPlanes); addGlobalMeta("Bits per pixel", nBits); addGlobalMeta("Compression", compression); addGlobalMeta("Pixels per meter (X)", pixelWidth); addGlobalMeta("Pixels per meter (Y)", pixelHeight); addGlobalMeta("Color used", colorUsed); addGlobalMeta("Color important", colorImportant); } LOGGER.info("Populating core metadata"); for (int i=0; i<getSeriesCount(); i++) { CoreMetadata ms = core.get(i); ms.sizeX = x; ms.sizeY = y; ms.sizeZ = nPlanes; ms.sizeT = 1; ms.sizeC = uniqueChannels.size(); ms.imageCount = getSizeZ() * getSizeT() * getSizeC(); ms.littleEndian = true; ms.dimensionOrder = "XYCZT"; ms.pixelType = FormatTools.pixelTypeFromBytes(nBits / 8, false, false); } LOGGER.info("Populating metadata store"); MetadataStore store = makeFilterMetadata(); MetadataTools.populatePixels(store, this); store.setPlateID(MetadataTools.createLSID("Plate", 0), 0); store.setPlateName(plateName, 0); store.setPlateRowNamingConvention(NamingConvention.LETTER, 0); store.setPlateColumnNamingConvention(NamingConvention.NUMBER, 0); int realRows = wellRows; int realCols = wellColumns; if (files.length == 1) { realRows = 1; realCols = 1; } else if (realRows <= 8 && realCols <= 12) { realRows = 8; realCols = 12; } else { realRows = 16; realCols = 24; } int fieldCntr = 0; int wellCntr = 0; int wellIndexPrev = 0; int wellIndex = 0; for (int i=0; i<getSeriesCount(); i++) { String file = files[i * getSizeC()]; int fieldIndex = getField(file); int row = getWellRow(file); int col = getWellColumn(file); store.setImageName( String.format("Well %s%02d, Field #%02d", new String(Character.toChars(row+'A')), col, fieldIndex), i); if (files.length == 1) { row = 0; col = 0; } if (i>0 && files.length != 1){ String prevFile = files[(i-1) * getSizeC()]; int prevRow = getWellRow(prevFile); int prevCol = getWellColumn(prevFile); if (prevRow < realRows && prevCol < realCols){ wellIndexPrev = prevRow * realCols + prevCol; } } String imageID = MetadataTools.createLSID("Image", i); store.setImageID(imageID, i); if (row < realRows && col < realCols) { wellIndex = row * realCols + col; if ((wellIndexPrev != wellIndex) || i==0){ if(i>0){ wellCntr++; fieldCntr = 0; }else{ wellIndexPrev = wellIndex; } store.setWellID(MetadataTools.createLSID("Well", 0, wellIndex), 0, wellCntr); store.setWellRow(new NonNegativeInteger(row), 0, wellCntr); store.setWellColumn(new NonNegativeInteger(col), 0, wellCntr); } if (files.length == 1) { fieldIndex = 0; } if (fieldIndex == 0){ fieldCntr=0; } String wellSampleID = MetadataTools.createLSID("WellSample", 0, wellIndex, fieldIndex); store.setWellSampleID(wellSampleID, 0, wellCntr, fieldCntr); store.setWellSampleIndex( new NonNegativeInteger(i), 0, wellCntr, fieldCntr); store.setWellSampleImageRef(imageID, 0, wellCntr, fieldCntr); fieldCntr++; } } if (getMetadataOptions().getMetadataLevel() != MetadataLevel.MINIMUM) { // physical dimensions are stored as pixels per meter - we want them // in microns per pixel double width = pixelWidth == 0 ? 0.0 : 1000000.0 / pixelWidth; double height = pixelHeight == 0 ? 0.0 : 1000000.0 / pixelHeight; Length sizeX = FormatTools.getPhysicalSizeX(width); Length sizeY = FormatTools.getPhysicalSizeY(height); for (int i=0; i<getSeriesCount(); i++) { if (sizeX != null) { store.setPixelsPhysicalSizeX(sizeX, 0); } if (sizeY != null) { store.setPixelsPhysicalSizeY(sizeY, 0); } } } } // -- Helper methods -- private Matcher matchFilename(final String filename) { final String name = new Location(filename).getName(); if (cellomicsPattern == null) { Matcher m = PATTERN_O.matcher(name); if (m.matches() && m.group(4) != null) { cellomicsPattern = PATTERN_O; return m; } else { cellomicsPattern = PATTERN_D; } } return cellomicsPattern.matcher(name); } private String getPlateName(final String filename) { Matcher m = matchFilename(filename); if (m.matches()) { return m.group(1); } return null; } private String getWellName(String filename) { Matcher m = matchFilename(filename); if (m.matches()) { return m.group(2); } return null; } private int getWellRow(String filename) { String wellName = getWellName(filename); if ((wellName == null) || (wellName.length() < 1) ) return 0; int ord = wellName.toUpperCase().charAt(0) - 'A'; if ((ord < 0) || (ord >= 26)) return 0; return ord; } private int getWellColumn(String filename) { String wellName = getWellName(filename); if ((wellName == null) || (wellName.length() <= 2)) return 0; if (! Character.isDigit(wellName.charAt(1))) return 0; if (! Character.isDigit(wellName.charAt(2))) return 0; return Integer.parseInt(wellName.substring(1, 3)); } private int getField(String filename) { Matcher m = matchFilename(filename); if (m.matches() && (m.group(3) != null)) { return Integer.parseInt(m.group(3).substring(1)); } return 0; } private int getChannel(String filename) { Matcher m = matchFilename(filename); if (m.matches() && (m.group(4) != null)) { return Integer.parseInt(m.group(4).substring(1)); } return -1; } private RandomAccessInputStream getDecompressedStream(String filename) throws FormatException, IOException { RandomAccessInputStream s = new RandomAccessInputStream(filename); if (checkSuffix(filename, "c01")) { LOGGER.info("Decompressing file"); s.seek(4); ZlibCodec codec = new ZlibCodec(); byte[] file = codec.decompress(s, null); s.close(); return new RandomAccessInputStream(file); } return s; } }