// // ScreenReader.java // /* OME Bio-Formats package for reading and converting biological file formats. Copyright (C) 2005-@year@ UW-Madison LOCI and Glencoe Software, Inc. 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, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package loci.formats.in; import java.io.File; import java.io.IOException; import java.util.Arrays; import java.util.Comparator; import java.util.Vector; import loci.common.Location; import loci.common.RandomAccessInputStream; import loci.common.services.DependencyException; import loci.common.services.ServiceException; import loci.common.services.ServiceFactory; import loci.formats.ClassList; import loci.formats.CoreMetadata; import loci.formats.FormatException; import loci.formats.FormatReader; import loci.formats.FormatTools; import loci.formats.IFormatReader; import loci.formats.ImageReader; import loci.formats.MetadataTools; import loci.formats.MissingLibraryException; import loci.formats.meta.MetadataStore; import loci.formats.ome.OMEXMLMetadata; import loci.formats.ome.OMEXMLMetadataImpl; import loci.formats.services.OMEXMLService; import loci.formats.services.OMEXMLServiceImpl; import ome.xml.model.Image; import ome.xml.model.OME; import ome.xml.model.enums.NamingConvention; import ome.xml.model.primitives.NonNegativeInteger; import ome.xml.model.primitives.PositiveInteger; /** * ScreenReader is a generic reader for files in non-HCS formats that have been * organized into a screen. * * <dl><dt><b>Source code:</b></dt> * <dd><a href="http://trac.openmicroscopy.org.uk/ome/browser/bioformats.git/components/bio-formats/src/loci/formats/in/ScreenReader.java">Trac</a>, * <a href="http://git.openmicroscopy.org/?p=bioformats.git;a=blob;f=components/bio-formats/src/loci/formats/in/ScreenReader.java;hb=HEAD">Gitweb</a></dd></dl> * * @author Melissa Linkert melissa at glencoesoftware.com */ public class ScreenReader extends FormatReader { // -- Fields -- private String[] plateMetadataFiles; private ImageReader[] readers; private boolean[][] plateMaps; private ClassList<IFormatReader> validReaders; // -- Constructor -- /** Constructs a new screen reader. */ public ScreenReader() { super("Screen", ""); suffixSufficient = false; domains = new String[] {FormatTools.HCS_DOMAIN}; ClassList<IFormatReader> classes = ImageReader.getDefaultReaderClasses(); Class<? extends IFormatReader>[] classArray = classes.getClasses(); validReaders = new ClassList<IFormatReader>(IFormatReader.class); for (Class<? extends IFormatReader> c : classArray) { if (!c.equals(ScreenReader.class)) { validReaders.addClass(c); } } hasCompanionFiles = true; } // -- IFormatReader API methods -- /* @see loci.formats.IFormatReader#isSingleFile(String) */ public boolean isSingleFile(String id) throws FormatException, IOException { return false; } /* @see loci.formats.IFormatReader#isThisType(String, boolean) */ public boolean isThisType(String filename, boolean open) { if (!open) return super.isThisType(filename, open); // no file system access Location file = new Location(filename).getAbsoluteFile(); String parent = file.getParent(); boolean validNames = isValidWellName(file.getAbsolutePath()) && isValidPlateName(parent); ImageReader r = new ImageReader(validReaders); boolean validFormat = r.isThisType(filename); boolean singleFiles = false; try { singleFiles = r.isSingleFile(filename); } catch (FormatException e) { } catch (IOException e) { } return validNames && validFormat && singleFiles; } /* @see loci.formats.IFormatReader#isThisType(RandomAccessInputStream) */ public boolean isThisType(RandomAccessInputStream stream) throws IOException { return false; } /* @see loci.formats.IFormatReader#fileGroupOption(String) */ public int fileGroupOption(String id) throws FormatException, IOException { return FormatTools.MUST_GROUP; } /* @see loci.formats.IFormatReader#get8BitLookupTable() */ public byte[][] get8BitLookupTable() throws FormatException, IOException { FormatTools.assertId(currentId, true, 1); if (readers == null || readers[0].getCurrentFile() == null) { return null; } return readers[0].get8BitLookupTable(); } /* @see loci.formats.IFormatReader#get16BitLookupTable() */ public short[][] get16BitLookupTable() throws FormatException, IOException { FormatTools.assertId(currentId, true, 1); if (readers == null || readers[0].getCurrentFile() == null) { return null; } return readers[0].get16BitLookupTable(); } /** * @see loci.formats.IFormatReader#openBytes(int, byte[], int, int, int, int) */ 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[] spwIndexes = getSPWIndexes(getSeries()); int well = spwIndexes[0]; int field = spwIndexes[1]; readers[well].setSeries(field); return readers[well].openBytes(no, buf, x, y, w, h); } /* @see loci.formats.IFormatReader#getSeriesUsedFiles(boolean) */ public String[] getSeriesUsedFiles(boolean noPixels) { FormatTools.assertId(currentId, true, 1); int[] spwIndexes = getSPWIndexes(getSeries()); int well = spwIndexes[0]; Vector<String> files = new Vector<String>(); if (plateMetadataFiles != null) { for (String f : plateMetadataFiles) { files.add(f); } } String[] readerFiles = readers[well].getSeriesUsedFiles(noPixels); if (readerFiles != null) { for (String f : readerFiles) { files.add(f); } } return files.toArray(new String[files.size()]); } /* @see loci.formats.IFormatReader#close(boolean) */ public void close(boolean fileOnly) throws IOException { super.close(fileOnly); if (readers != null) { for (ImageReader well : readers) { well.close(fileOnly); } } if (!fileOnly) { readers = null; plateMaps = null; plateMetadataFiles = null; } } // -- Internal FormatReader API methods -- /* @see loci.formats.FormatReader#initFile(String) */ protected void initFile(String id) throws FormatException, IOException { super.initFile(id); Location file = new Location(id).getAbsoluteFile(); Location plate = null; Location screen = null; if (isValidWellName(file.getAbsolutePath())) { plate = file.getParentFile(); screen = plate.getParentFile(); } else throw new FormatException(id + " is not a valid well name."); // build the list of plate directories Vector<String> metadataFiles = new Vector<String>(); Comparator<String> c = new Comparator<String>() { public int compare(String s1, String s2) { int row1 = (int) (getRow(s1).charAt(0) - 'A'); int row2 = (int) (getRow(s2).charAt(0) - 'A'); if (row1 != row2) { return row1 - row2; } int col1 = Integer.parseInt(getColumn(s1)) - 1; int col2 = Integer.parseInt(getColumn(s2)) - 1; return col1 - col2; } }; // build the list of well files for each plate Vector<String> tmpWells = new Vector<String>(); String[] plateList = plate.list(true); int maxRow = 0, maxCol = 0; for (String well : plateList) { Location wellFile = new Location(plate, well); if (isValidWellName(wellFile.getAbsolutePath())) { String row = getRow(well); String col = getColumn(well); boolean canAdd = true; for (String tmpWell : tmpWells) { String tmpRow = getRow(tmpWell); String tmpCol = getColumn(tmpWell); if (tmpRow.equals(row) && tmpCol.equals(col)) { canAdd = false; break; } } if (!canAdd) continue; tmpWells.add(wellFile.getAbsolutePath()); if ((row.charAt(0) - 'A') > maxRow) { maxRow = row.charAt(0) - 'A'; } if (Integer.parseInt(col) - 1 > maxCol) { maxCol = Integer.parseInt(col) - 1; } } else if (!wellFile.isDirectory()) { metadataFiles.add(wellFile.getAbsolutePath()); } } String[] files = tmpWells.toArray(new String[tmpWells.size()]); Arrays.sort(files, c); readers = new ImageReader[files.length]; int coreLength = files.length; plateMaps = new boolean[maxRow + 1][maxCol + 1]; plateMetadataFiles = metadataFiles.toArray(new String[metadataFiles.size()]); tmpWells.clear(); metadataFiles.clear(); // initialize each of the well files OMEXMLMetadata omexmlMeta; OMEXMLService service; try { ServiceFactory factory = new ServiceFactory(); service = factory.getInstance(OMEXMLService.class); omexmlMeta = service.createOMEXMLMetadata(); } catch (DependencyException de) { throw new MissingLibraryException(OMEXMLServiceImpl.NO_OME_XML_MSG, de); } catch (ServiceException se) { throw new FormatException(se); } Vector<CoreMetadata> coreMetadata = new Vector<CoreMetadata>(); int lastCount = 0; int[] seriesCounts = new int[files.length]; for (int well=0; well<files.length; well++) { readers[well] = new ImageReader(validReaders); readers[well].setMetadataStore(omexmlMeta); readers[well].setId(files[well]); CoreMetadata[] core = readers[well].getCoreMetadata(); for (int field=0; field<core.length; field++) { coreMetadata.add(core[field]); } seriesCounts[well] = readers[well].getSeriesCount(); lastCount = seriesCounts[well]; String row = getRow(files[well]); String col = getColumn(files[well]); int rowIndex = (int) (row.charAt(0) - 'A'); int colIndex = Integer.parseInt(col) - 1; plateMaps[rowIndex][colIndex] = true; } core = coreMetadata.toArray(new CoreMetadata[coreMetadata.size()]); OME root = (OME) omexmlMeta.getRoot(); Image img = root.getImage(0); for (int i=lastCount; i<core.length; i++) { root.addImage(img); } ((OMEXMLMetadataImpl) omexmlMeta).resolveReferences(); omexmlMeta.setRoot(root); // populate HCS metadata MetadataStore store = makeFilterMetadata(); service.convertMetadata(omexmlMeta, store); MetadataTools.populatePixels(store, this); String screenID = MetadataTools.createLSID("Screen", 0); store.setScreenID(screenID, 0); store.setScreenName(screen.getName(), 0); String plateID = MetadataTools.createLSID("Plate", 0); store.setPlateID(plateID, 0); store.setScreenPlateRef(plateID, 0, 0); store.setPlateName(plate.getName(), 0); store.setPlateRows(new PositiveInteger(plateMaps.length), 0); store.setPlateColumns(new PositiveInteger(plateMaps[0].length), 0); store.setPlateRowNamingConvention(NamingConvention.LETTER, 0); store.setPlateColumnNamingConvention(NamingConvention.NUMBER, 0); int realWell = 0; for (int row=0; row<plateMaps.length; row++) { for (int col=0; col<plateMaps[row].length; col++) { int well = row * plateMaps[row].length + col; String wellID = MetadataTools.createLSID("Well", 0, well); store.setWellID(wellID, 0, well); store.setWellColumn(new NonNegativeInteger(col), 0, well); store.setWellRow(new NonNegativeInteger(row), 0, well); if (plateMaps[row][col]) { for (int field=0; field<seriesCounts[realWell]; field++) { int seriesIndex = getSeriesIndex(realWell, field); String imageID = MetadataTools.createLSID("Image", seriesIndex); store.setImageID(imageID, seriesIndex); store.setImageName("Well " + ((char) (row + 'A')) + (col + 1) + ", Field " + (field + 1), seriesIndex); String wellSampleID = MetadataTools.createLSID("WellSample", 0, well, field); store.setWellSampleID(wellSampleID, 0, well, field); store.setWellSampleImageRef(imageID, 0, well, field); store.setWellSampleIndex( new NonNegativeInteger(seriesIndex), 0, well, field); } realWell++; } } } } // -- Helper methods -- private boolean isValidWellName(String filename) { if (new Location(filename).getAbsoluteFile().isDirectory()) return false; String row = getRow(filename); String col = getColumn(filename); try { Integer.parseInt(col); return col.length() <= 2 && Character.isLetter(row.charAt(0)); } catch (NumberFormatException e) { } return false; } private boolean isValidPlateName(String filename) { return new Location(filename).isDirectory(); } private int getSeriesIndex(int well, int field) { int fieldCount = readers[well].getSeriesCount(); int seriesIndex = 0; int validWells = -1; for (int row=0; row<plateMaps.length; row++) { for (int col=0; col<plateMaps[row].length; col++) { if (plateMaps[row][col]) { validWells++; if (validWells == well) { return seriesIndex + field; } seriesIndex += readers[validWells].getSeriesCount(); } } } return -1; } private int[] getSPWIndexes(int seriesIndex) { int s = 0; int wellIndex = 0; for (int row=0; row<plateMaps.length; row++) { for (int col=0; col<plateMaps[row].length; col++) { if (plateMaps[row][col]) { for (int i=0; i<readers[wellIndex].getSeriesCount(); i++) { s++; if (s == seriesIndex + 1) { return new int[] {wellIndex, i}; } } wellIndex++; } } } return new int[] {-1, -1}; } private int[] getRowAndColumn(int well) { int validWells = 0; for (int row=0; row<plateMaps.length; row++) { for (int col=0; col<plateMaps[row].length; col++) { if (plateMaps[row][col]) { validWells++; if (validWells == well + 1) { return new int[] {row, col}; } } } } return new int[] {-1, -1}; } private boolean hasValidWells(Location plate) { if (!plate.isDirectory()) return false; String[] wells = plate.list(true); for (String well : wells) { if (isValidWellName(new Location(plate, well).getAbsolutePath())) { return true; } } return false; } private String getRow(String well) { String wellName = well.substring(well.lastIndexOf(File.separator) + 1); char firstChar = Character.toUpperCase(wellName.charAt(0)); while (wellName.indexOf("_") > 0 && (firstChar < 'A' || firstChar > 'P')) { wellName = wellName.substring(wellName.indexOf("_") + 1); firstChar = Character.toUpperCase(wellName.charAt(0)); } return wellName.substring(0, 1).toUpperCase(); } private String getColumn(String well) { String wellName = well.substring(well.lastIndexOf(File.separator) + 1); char firstChar = Character.toUpperCase(wellName.charAt(0)); while (wellName.indexOf("_") > 0 && (firstChar < 'A' || firstChar > 'P')) { wellName = wellName.substring(wellName.indexOf("_") + 1); firstChar = Character.toUpperCase(wellName.charAt(0)); } int end = wellName.lastIndexOf("_"); if (end < 0) end = wellName.lastIndexOf("."); if (end < 0) end = wellName.length(); if (end <= 1) return null; return wellName.substring(1, end); } }