Mercurial > repos > mvdbeek > incucyte_stack_and_upload_omero_debug
view stack_buildXml.groovy @ 0:a599551e800d draft
planemo upload for repository https://github.com/lldelisle/tools-lldelisle/tree/master/tools/incucyte_stack_and_upload_omero commit 4ac9b1d66ba6857357867c8eccb6c9d1ad603364-dirty
author | mvdbeek |
---|---|
date | Tue, 06 Aug 2024 15:28:26 +0000 |
parents | |
children |
line wrap: on
line source
/* **************************************************** * Relative to the generation of the .companion.ome * **************************************************** * #%L * BSD implementations of Bio-Formats readers and writers * %% * The functions buildXML, makeImage, makePlate, postProcess and asString has been modified and adapted from * https://github.com/ome/bioformats/blob/master/components/formats-bsd/test/loci/formats/utests/SPWModelMock.java * * Copyright (C) 2005 - 2015 Open Microscopy Environment: * - Board of Regents of the University of Wisconsin-Madison * - Glencoe Software, Inc. * - University of Dundee * * @author Chris Allan <callan at blackcat dot ca> * %% * **************************************************** * Relative to the rest of the script * **************************************************** * * * = AUTHOR INFORMATION = * Code written by Rémy Dornier, EPFL - SV - PTECH - BIOP * and Romain Guiet, EPFL - SV - PTECH - BIOP * and Lucille Delisle, EPFL - SV - UPDUB * and Pierre Osteil, EPFL - SV - UPDUB * * Last modification: 2023-12-19 * * = COPYRIGHT = * © All rights reserved. ECOLE POLYTECHNIQUE FEDERALE DE LAUSANNE, Switzerland, BioImaging And Optics Platform (BIOP), 2023 * * Licensed under the BSD-3-Clause License: * Redistribution and use in source and binary forms, with or without modification, are permitted provided * that the following conditions are met: * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the distribution. * 3. Neither the name of the copyright holder nor the names of its contributors * may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * */ /** * * The purpose of this script is to combine a series of time-lapse images into * one file per well/field with possibly multiple channels and multiple time points * and in addition create a .companion.ome file to create an OMERO plate object, * with a single image per well/field. This .companion.ome file can be directly uploaded on OMERO via * OMERO.insight software or CLI, with screen import option. * * To make the script run * 1. Create a parent folder (base_dir) and a output folder (output_dir) * 2. Create a dir *Phase, *Green, *Red with the corresponding channels * 3. The image names must contains a prefix followed by '_', the name of the well (no 0-pad) followed by '_', followed by the field id followed by '_', the date of the acquisition in YYYYyMMmDDdHHhMMm and the extension '.tif' * 4. The images can be either regular tif or the raw tif from Incucyte which contains multiple series. * 5. You must provide the path of the Incucyte XML file to populate key values * * The expected outputs are: * 1. In the output_dir one tiff per well/field (multi-T and potentially multi-C) * 2. In the output_dir a .companion.ome */ #@ File(style="directory", label="Directory with up to 3 subdirectories ending by Green, Phase and/or Red") base_dir #@ File(label="Incucyte XML File (plateMap)") incucyteXMLFile #@ File(style="directory", label="Output directory (must exist)") output_dir #@ String(label="Final XML file name", value="Test") xmlName #@ String(label="Number of well in plate", choices={"96", "384"}, value="96") nWells #@ Integer(label="Maximum number of images per well", value=1, min=1) n_images_per_well #@ String(label="Objective", choices={"4x","10x","20x"}) objectiveChoice #@ String(label="Plate name", value="Experiment:0") plateName #@ String(label="common Key Values formatted as key1=value1;key2=value2", value="") commonKeyValues #@ Boolean(label="Ignore Compound concentration from plateMap", value=true) ignoreConcentration #@ Boolean(label="Ignore Cell passage number from plateMap", value=true) ignorePassage #@ Boolean(label="Ignore Cell seeding concentration from plateMap", value=true) ignoreSeeding /** * ***************************************************************************************************************** * ********************************************* Final Variables ************************************************** * ********************************************* DO NOT MODIFY **************************************************** * **************************************************************************************************************** */ /** objectives and respective pixel sizes */ objective = 0 objectives = new String[]{"4x", "10x", "20x"} pixelSizes = new double[]{2.82, 1.24, 0.62} /** pattern for date */ REGEX_FOR_DATE = ".*_([0-9]{4})y([0-9]{2})m([0-9]{2})d_([0-9]{2})h([0-9]{2})m.tif" ALTERNATIVE_REGEX_FOR_DATE = ".*_([0-9]{2})d([0-9]{2})h([0-9]{2})m.tif" /** Image properties keys */ DIMENSION_ORDER = "dimension_order" FILE_NAME = "file_name" IMG_POS_IN_WELL = "img_pos_in_well" FIRST_ACQUISITION_DATE = "acquisition_date" FIRST_ACQUISITION_TIME = "acquisition_time" RELATIVE_ACQUISITION_HOUR = "relative_acquisition_hour" /** global variable for index to letter conversion */ LETTERS = new String("ABCDEFGHIJKLMNOP") // Version number = date of last modif VERSION = "20231219" /** Key-Value pairs namespace */ GENERAL_ANNOTATION_NAMESPACE = "openmicroscopy.org/omero/client/mapAnnotation" annotations = new StructuredAnnotations() /** Plate details and conventions */ PLATE_ID = "Plate:0" PLATE_NAME = plateName if (nWells == "96") { nRows = 8 nCols = 12 } else if (nWells == "384") { nRows = 16 nCols = 24 } WELL_ROWS = new PositiveInteger(nRows) WELL_COLS = new PositiveInteger(nCols) WELL_ROW = NamingConvention.LETTER WELL_COL = NamingConvention.NUMBER /** XML namespace. */ XML_NS = "http://www.openmicroscopy.org/Schemas/OME/2010-06" /** XSI namespace. */ XSI_NS = "http://www.w3.org/2001/XMLSchema-instance" /** XML schema location. */ SCHEMA_LOCATION = "http://www.openmicroscopy.org/Schemas/OME/2010-06/ome.xsd" /** * ***************************************************************************************************************** * **************************************** Beginning of the script *********************************************** * **************************************************************************************************************** */ try { println "Beginning of the script" /** * Prepare list of wells name */ String[] well = [] well = [(0..(nRows - 1)),(0..(nCols - 1))].combinations().collect{ r,c -> LETTERS.substring(r, r + 1) +""+ (c+ 1).toString() } IJ.run("Close All", "") // loop for all the wells // Store all merged ImagePlus into a HashMap where // keys are well name (A1, A10) // values are a list of ImagePlus corresponding to different field of view Map<String, List<ImagePlus>> wellSamplesMap = new HashMap<>() well.each{ input -> IJ.run("Close All", "") List<ImagePlus> final_imp_list = process_well(base_dir, input, n_images_per_well) //, perform_bc, mediaChangeTime ) if (!final_imp_list.isEmpty()) { wellSamplesMap.put(input, final_imp_list) for(ImagePlus final_imp : final_imp_list){ final_imp.setTitle(input+"_"+final_imp.getProperty(IMG_POS_IN_WELL)) //final_imp.show() def fs = new FileSaver(final_imp) File output_path = new File (output_dir ,final_imp.getTitle()+"_merge.tif" ) fs.saveAsTiff(output_path.toString() ) final_imp.setProperty(FILE_NAME, output_path.getName()) IJ.run("Close All", "") } } else { println "No match for " + input } } // get folder and xml file path output_dir_abs = output_dir.getAbsolutePath() incucyteXMLFilePath = incucyteXMLFile.getAbsolutePath() if (! new File(incucyteXMLFilePath).exists()) { println "The incucyte file does not exists" return } // select the right objective switch (objectiveChoice){ case "4x": objective = 0 break case "10x": objective = 1 break case "20x": objective = 2 break } // get plate scheme as key-values Map<String, List<MapPair>> keyValuesPerWell = parseIncucyteXML(incucyteXMLFilePath, ignoreConcentration, ignorePassage, ignoreSeeding) // get global key-values List<MapPair> globalKeyValues = getGlobalKeyValues(objective, commonKeyValues) double pixelSize = pixelSizes[objective] // generate OME-XML metadata file OME ome = buildXMLFile(wellSamplesMap, keyValuesPerWell, globalKeyValues, pixelSize) // create XML document DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance() DocumentBuilder parser = factory.newDocumentBuilder() Document document = parser.newDocument() // Produce a valid OME DOM element hierarchy Element root = ome.asXMLElement(document) postProcess(root, document) // Produce string XML try(OutputStream outputStream = new FileOutputStream(output_dir_abs + File.separator + xmlName + ".companion.ome")){ outputStream.write(asString(document).getBytes()) } catch(Exception e){ e.printStackTrace() } println "End of the script" } catch (Throwable e) { println("Something went wrong: " + e) e.printStackTrace() throw e if (GraphicsEnvironment.isHeadless()){ // Force to give exit signal of error System.exit(1) } } return /** * **************************************************************************************************************** * ******************************************* End of the script ************************************************** * * **************************************************************************************************************** * * *********************************** Helpers and processing methods ********************************************* * *************************************************************************************************************** */ def process_well(baseDir, input_wellId, n_image_per_well){ //, perform_bc, mediaChangeTime){ File bf_dir = baseDir.listFiles().find{ it =~ /.*Phase.*/} File green_dir = baseDir.listFiles().find{ it =~ /.*Green.*/} File red_dir = baseDir.listFiles().find{ it =~ /.*Red.*/} //if (verbose) println bf_dir //if (verbose) println green_dir // The images are stored in a TreeMap where // keys are wellSampleId = field identifier // values are a TreeMap that we call channelMap where: // keys are colors (Green, Grays, Red) // values are an ImagePlus (T-stack) Map<Integer, Map<String, ImagePlus>> sampleToChannelMap = new TreeMap<>() List<File> folder_list = [bf_dir, green_dir, red_dir] List<String> channels_list = ["Grays", "Green", "Red"] // loop over the field and open images for(int wellSampleId = 1; wellSampleId <= n_image_per_well; wellSampleId++) { // nT is the number of time-points for the well input_wellId int nT = 0 String first_channel = "" String first_acq_date = "" String first_acq_time = "" String rel_acq_hour = "" // Initiate a channel map for the wellSampleId Map<String, ImagePlus> channelMap = new TreeMap<>() // Checking if there are images in the corresponding dir // which corresponds to the input_wellId // and to the wellSampleId // The image name should be: // Prefix + "_" + input_wellId + "_" + wellSampleId + "_" + year (4 digits) + "y" + month (2 digits) + "m" + day + "d_" + hour + "h" + minute + "m.tif" FileFilter fileFilter = new WildcardFileFilter("*_" + input_wellId + "_" + wellSampleId + "_*") for(int i = 0; i < folder_list.size(); i++){ if (folder_list.get(i) != null) { File[] files_matching = folder_list.get(i).listFiles(fileFilter as FileFilter).sort() if (files_matching.size() != 0) { // In order to deal with raw images from Incucyte which are // Multi series images // We open the first image ImagePlus first_imp = Opener.openUsingBioFormats(files_matching[0].getAbsolutePath()) // We check if we can read the infos first_image_infos = Opener.getTiffFileInfo(files_matching[0].getAbsolutePath()) // We define the imageplus object ImagePlus single_channel_imp if (first_image_infos == null) { // They are raw from incucyte // We need to open images one by one and add them to the stack ImageStack stack = new ImageStack(first_imp.width, first_imp.height); files_matching.each{ ImagePlus single_imp = (new Opener()).openUsingBioFormats(it.getAbsolutePath()) String new_title = single_imp.getTitle().split(" - ")[0] stack.addSlice(new_title, single_imp.getProcessor()) } single_channel_imp = new ImagePlus(FilenameUtils.getBaseName(folder_list.get(i).getAbsolutePath()), stack); } else { // They are regular tif file // We can use FolderOpener to create the stack at once single_channel_imp = FolderOpener.open(folder_list.get(i).getAbsolutePath(), " filter=_"+ input_wellId + "_"+wellSampleId+"_") } // Phase are 8-bit and need to be changed to 16-bit // Other are already 16-bit but it does not hurt IJ.run(single_channel_imp, "16-bit", "") // check frame size if (nT == 0) { // This is the first channel with images nT = single_channel_imp.getNSlices() first_channel = channels_list.get(i) // Process all dates: Pattern date_pattern = Pattern.compile(REGEX_FOR_DATE) ImageStack stack = single_channel_imp.getStack() // Go to the first time (which is slice) single_channel_imp.setSlice(1) int currentSlice = single_channel_imp.getCurrentSlice() String label = stack.getSliceLabel(currentSlice) LocalDateTime dateTime_ref = getDate(label, date_pattern) if (dateTime_ref == null) { date_pattern = Pattern.compile(ALTERNATIVE_REGEX_FOR_DATE) dateTime_ref = getDate(label, date_pattern) } if (dateTime_ref != null) { first_acq_date = dateTime_ref.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")) first_acq_time = dateTime_ref.format(DateTimeFormatter.ofPattern("HH:mm:ss")) for (int ti = 2; ti<= nT; ti++) { // Process each frame starting at 2 single_channel_imp.setSlice(ti) int hours = getHoursFromImp(single_channel_imp, stack, dateTime_ref, date_pattern) if (rel_acq_hour == "") { rel_acq_hour = "" + hours } else { rel_acq_hour += "," + hours } } } else { first_acq_date = "NA" first_acq_time = "NA" } } else { assert single_channel_imp.getNSlices() == nT : "The number of "+channels_list.get(i)+" images for well "+input_wellId+" and field " + wellSampleId + " does not match the number of images in " + first_channel + "." } // Set acquisition properties single_channel_imp.setProperty(FIRST_ACQUISITION_DATE, first_acq_date) single_channel_imp.setProperty(FIRST_ACQUISITION_TIME, first_acq_time) single_channel_imp.setProperty(RELATIVE_ACQUISITION_HOUR, rel_acq_hour) println single_channel_imp.getProperty(FIRST_ACQUISITION_DATE) println single_channel_imp.getProperty(FIRST_ACQUISITION_TIME) // add the image stack to the channel map for the corresponding color channelMap.put(channels_list.get(i), single_channel_imp) } } } if (nT != 0) { // add the channelmap to the sampleToChannelMap using the wellSampleId as key sampleToChannelMap.put(wellSampleId, channelMap) } } ArrayList<ImagePlus> final_imp_list = [] // Now loop over the wellSampleId which have images for(Integer wellSampleId : sampleToChannelMap.keySet()){ // get the channel map Map<String, ImagePlus> channelsMap = sampleToChannelMap.get(wellSampleId) ArrayList<String> channels = [] ArrayList<ImagePlus> current_images = [] for(String channel : channelsMap.keySet()){ channels.add(channel) current_images.add(channelsMap.get(channel)) } // Get number of time: int nT = current_images[0].nSlices // Merge all ImagePlus merged_imps = Concatenator.run(current_images as ImagePlus[]) // Re-order to make a multi-channel, time-lapse image ImagePlus final_imp if (channels.size() == 1 && nT == 1) { final_imp = merged_imps } else { final_imp = HyperStackConverter.toHyperStack(merged_imps, channels.size() , 1, nT, "xytcz", "Color") } // add properties to the image final_imp.setProperty(DIMENSION_ORDER, DimensionOrder.XYCZT) final_imp.setProperty(IMG_POS_IN_WELL, wellSampleId) final_imp.setProperty(FIRST_ACQUISITION_DATE, current_images[0].getProperty(FIRST_ACQUISITION_DATE)) final_imp.setProperty(FIRST_ACQUISITION_TIME, current_images[0].getProperty(FIRST_ACQUISITION_TIME)) final_imp.setProperty(RELATIVE_ACQUISITION_HOUR, current_images[0].getProperty(RELATIVE_ACQUISITION_HOUR)) // set LUTs (0..channels.size()-1).each{ final_imp.setC(it + 1) IJ.run(final_imp, channels[it], "") final_imp.resetDisplayRange() } final_imp_list.add(final_imp) } return final_imp_list } /** * create the full XML metadata (plate, images, channels, annotations....) * * @param imagesName * @param keyValuesPerWell * @param globalKeyValues * @param pixelSize * @return OME-XML metadata instance */ def buildXMLFile(Map<String,List<ImagePlus>> wellToImagesMap, Map<String, List<MapPair>> keyValuesPerWell, List<MapPair> globalKeyValues, double pixelSize) { // create a new OME-XML metadata instance OME ome = new OME() Map<String, Integer> imgInWellPosToListMap = new HashMap<>() int imgCmp = 0 for (String wellId: wellToImagesMap.keySet()) { // get well position from image name List<ImagePlus> imagesWithinWell = wellToImagesMap.get(wellId) for (ImagePlus image : imagesWithinWell) { // get KVP corresponding to the current well // Initiate a list of keyValues for the wellId // (or use the existing one) List<MapPair> keyValues = [] if(keyValuesPerWell.containsKey(wellId)) keyValues = keyValuesPerWell.get(wellId) keyValues.addAll(globalKeyValues) // create an Image node in the ome-xml imgInWellPosToListMap.put(wellId+ "_" +image.getProperty(IMG_POS_IN_WELL),imgCmp) ome.addImage(makeImage(imgCmp++, image, keyValues, pixelSize)) } } // create Plate node ome.addPlate(makePlate(wellToImagesMap, imgInWellPosToListMap, pixelSize, ome)) // add annotation nodes ome.setStructuredAnnotations(annotations) return ome } /** * create an image xml-element, populated with annotations, channel, pixels and path elements * * @param index image ID * @param imageName * @param keyValues * @param pixelSize * @return an image xml-element */ def makeImage(int index, ImagePlus imagePlus, List<MapPair> keyValues, double pixelSize) { // Create <Image/> Image image = new Image() image.setID("Image:" + index) // The image name is the name of the file without extension image.setName(((String)imagePlus.getProperty(FILE_NAME)).split("\\.")[0]) // Set the acquisitionDate: if (imagePlus.getProperty(FIRST_ACQUISITION_DATE) != "NA") { image.setAcquisitionDate(new Timestamp(imagePlus.getProperty(FIRST_ACQUISITION_DATE) + "T" + imagePlus.getProperty(FIRST_ACQUISITION_TIME))) // Also add it to the key values: keyValues.add(new MapPair("acquisition.day", (String)imagePlus.getProperty(FIRST_ACQUISITION_DATE))) keyValues.add(new MapPair("acquisition.time", (String)imagePlus.getProperty(FIRST_ACQUISITION_TIME))) keyValues.add(new MapPair("relative.acquisition.hours", (String)imagePlus.getProperty(RELATIVE_ACQUISITION_HOUR))) } // Create <MapAnnotations/> MapAnnotation mapAnnotation = new MapAnnotation() mapAnnotation.setID("ImageKeyValueAnnotation:" + index) mapAnnotation.setNamespace(GENERAL_ANNOTATION_NAMESPACE) mapAnnotation.setValue(keyValues) annotations.addMapAnnotation(mapAnnotation); // add the KeyValues to the general structured annotation element image.linkAnnotation(mapAnnotation) // Create <Pixels/> Pixels pixels = new Pixels() pixels.setID("Pixels:" + index) pixels.setSizeX(new PositiveInteger(imagePlus.getWidth())) pixels.setSizeY(new PositiveInteger(imagePlus.getHeight())) pixels.setSizeZ(new PositiveInteger(imagePlus.getNSlices())) pixels.setSizeC(new PositiveInteger(imagePlus.getNChannels())) pixels.setSizeT(new PositiveInteger(imagePlus.getNFrames())) pixels.setDimensionOrder((DimensionOrder) imagePlus.getProperty(DIMENSION_ORDER)) pixels.setType(getPixelType(imagePlus)) pixels.setPhysicalSizeX(new Length(pixelSize, UNITS.MICROMETER)) pixels.setPhysicalSizeY(new Length(pixelSize, UNITS.MICROMETER)) // Create <TiffData/> under <Pixels/> TiffData tiffData = new TiffData() tiffData.setFirstC(new NonNegativeInteger(0)) tiffData.setFirstT(new NonNegativeInteger(0)) tiffData.setFirstZ(new NonNegativeInteger(0)) tiffData.setPlaneCount(new NonNegativeInteger(imagePlus.getNSlices()*imagePlus.getNChannels()*imagePlus.getNFrames())) // Create <UUID/> under <TiffData/> UUID uuid = new UUID() uuid.setFileName((String)imagePlus.getProperty(FILE_NAME)) uuid.setValue(java.util.UUID.randomUUID().toString()) tiffData.setUUID(uuid) // Put <TiffData/> under <Pixels/> pixels.addTiffData(tiffData) // Create <Channel/> under <Pixels/> LUT[] luts = imagePlus.getLuts() for (int i = 0; i < luts.length; i++) { Channel channel = new Channel() channel.setID("Channel:" + i) channel.setColor(new Color(luts[i].getRed(255),luts[i].getGreen(255), luts[i].getBlue(255),255)) pixels.addChannel(channel) } // Put <Pixels/> under <Image/> image.setPixels(pixels) return image } /** * get pixel type based on the imagePlus type * @param imp * @return pixel type */ def getPixelType(ImagePlus imp){ switch (imp.getType()) { case ImagePlus.GRAY8: return PixelType.UINT8 case ImagePlus.GRAY16: return PixelType.UINT16 case ImagePlus.GRAY32: return PixelType.FLOAT default: return PixelType.FLOAT } } /** * create a Plate xml-element, populated with wells and their attributes * @param imagesName * @return Plate xml-element */ def makePlate(Map<String, List<ImagePlus>> wellToImagesMap, Map<String, Integer> imgPosInListMap, double pixelSize, OME ome) { // Create <Plate/> Plate plate = new Plate() plate.setName(PLATE_NAME) plate.setID(PLATE_ID) plate.setRows(WELL_ROWS) plate.setColumns(WELL_COLS) plate.setRowNamingConvention(WELL_ROW) plate.setColumnNamingConvention(WELL_COL) // for each image (one image per well) for (String wellId: wellToImagesMap.keySet()) { // get well position from image name List<ImagePlus> imagesWithinWell = wellToImagesMap.get(wellId) // get well position from image name int row = convertLetterToNumber(wellId.substring(0, 1)) int col = Integer.parseInt(wellId.substring(1)) - 1 // row and col should correspond to a real well if(row >= 0 && col >= 0 && col < 12) { // Create <Well/> under <Plate/> Well well = new Well() well.setID(String.format("Well:%d_%d", row, col)) well.setRow(new NonNegativeInteger(row)) well.setColumn(new NonNegativeInteger(col)) for (ImagePlus imagePlus : imagesWithinWell) { int wellSampleIndex = imgPosInListMap.get(wellId + "_" + imagePlus.getProperty(IMG_POS_IN_WELL)) // Create <WellSample/> under <Well/> WellSample sample = new WellSample() sample.setID(String.format("WellSample:%d", wellSampleIndex)) sample.setIndex(new NonNegativeInteger(wellSampleIndex)) if (imagePlus.getCalibration() != null) { sample.setPositionX(new Length(imagePlus.getCalibration().xOrigin * pixelSize, UNITS.MICROMETER)) sample.setPositionY(new Length(imagePlus.getCalibration().yOrigin * pixelSize, UNITS.MICROMETER)) } sample.linkImage(ome.getImage(wellSampleIndex)) // Put <WellSample/> under <Well/> well.addWellSample(sample) } // Put <Well/> under <Plate/> plate.addWell(well) } } return plate } /** * convert the XML metadata document into string * * @param document * @return * @throws TransformerException * @throws UnsupportedEncodingException */ def asString(Document document) throws TransformerException, UnsupportedEncodingException { TransformerFactory transformerFactory = TransformerFactory.newInstance() Transformer transformer = transformerFactory.newTransformer() //Setup indenting to "pretty print" transformer.setOutputProperty(OutputKeys.INDENT, "yes") transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4") Source source = new DOMSource(document) ByteArrayOutputStream os = new ByteArrayOutputStream() Result result = new StreamResult(new OutputStreamWriter(os, "utf-8")) transformer.transform(source, result) return os.toString() } /** * add document header * * @param root * @param document */ def postProcess(Element root, Document document) { root.setAttribute("xmlns", XML_NS) root.setAttribute("xmlns:xsi", XSI_NS) root.setAttribute("xsi:schemaLocation", XML_NS + " " + SCHEMA_LOCATION) root.setAttribute("UUID", java.util.UUID.randomUUID().toString()) document.appendChild(root) } /** * read Incucyte plate-scheme XML file, extract attributes per well and convert attributes to OMERO-compatible * keys-value pairs XML elements. * * @param path Incucyte plate-scheme XML file path * @return Map of OME-XML compatible key-values per well */ def parseIncucyteXML(String path, Boolean ignoreConcentration, Boolean ignorePassage, Boolean ignoreSeeding) { Map<String, List<MapPair>> keyValuesPerWell = new HashMap<>() final String rowAttribute = "row" final String columnAttribute = "col" final String wellNode = "well" final String itemsNode = "items" final String wellItemNode = "wellItem" final String referenceItemNode = "referenceItem" // There are 3 types of referenceItem: Compound, CellType, GrowthCondition // // For the Compound, each well can have a concentration and a concentrationUnits // the referenceItem has a displayName // The key should be: displayName (concentrationUnits) // The value should be: concentration // However, if ignoreConcentration is set to true: // The key should be: displayName (NA) // The value should be: 1 // // For the CellType, each well can have a passage, a seedingDensity and a seedingDensityUnits // the referenceItem has a displayName // The passage key should be: displayName_passage // The value should be: passage // However, if ignorePassage is set to true no key value should be stored for this // Then: // The seeding key should be: displayName_seedingDensity (seedingDensityUnits) // The value should be: seedingDensity // However, if ignoreSeeding is set to true: // The key should be: displayName // The value should be: "yes" // // For the GrowthCondition, the referenceItem has a displayName // The key should be: displayName // The value should be: "yes" try { // create an document DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance() DocumentBuilder dBuilder = dbFactory.newDocumentBuilder() // read the xml file Document doc = dBuilder.parse(new File(path)) doc.getDocumentElement().normalize() // get all "well" nodes NodeList wellList = doc.getElementsByTagName(wellNode) for(int i = 0; i < wellList.getLength(); i++) { Node well = wellList.item(i) // extract well attributes String row = well.getAttributes().getNamedItem(rowAttribute).getTextContent() int r = row as int String col = well.getAttributes().getNamedItem(columnAttribute).getTextContent() String wellNumber = LETTERS.substring(r, r + 1) + (Integer.parseInt(col)+ 1) // extract items node, under well node Node items = ((Element)well).getElementsByTagName(itemsNode).item(0) if (items != null) { // get all "wellItem" nodes, under items node NodeList wellItemList = ((Element)items).getElementsByTagName(wellItemNode) // read referenceItem node's attributes and convert them into key-values List<MapPair> keyValues = new ArrayList<>() for (int j = 0; j < wellItemList.getLength(); j++) { Node wellItem = wellItemList.item(j) // extract referenceItem node, under wellItem node Node referenceItem = ((Element)wellItem).getElementsByTagName(referenceItemNode).item(0) String wellType = wellItem.getAttributes().getNamedItem("type").getTextContent() // select the right key-values switch (wellType){ case "Compound": String compound_name = referenceItem.getAttributes().getNamedItem("displayName").getTextContent() if (ignoreConcentration) { keyValues.add(new MapPair(compound_name + " (NA)", "1")) } else { String unit = wellItem.getAttributes().getNamedItem("concentrationUnits").getTextContent() unit = unit.replace("\u00B5", "u") String value = wellItem.getAttributes().getNamedItem("concentration").getTextContent() keyValues.add(new MapPair(compound_name + " (" + unit + ")", value)) } break case "CellType": String cell_name = referenceItem.getAttributes().getNamedItem("displayName").getTextContent() if (!ignorePassage) { String passage = wellItem.getAttributes().getNamedItem("passage").getTextContent() keyValues.add(new MapPair(cell_name + "_passage", passage)) } if (ignoreSeeding) { keyValues.add(new MapPair(cell_name, "yes")) } else { String unit = wellItem.getAttributes().getNamedItem("seedingDensityUnits").getTextContent() unit = unit.replace("\u00B5", "u") String value = wellItem.getAttributes().getNamedItem("seedingDensity").getTextContent() keyValues.add(new MapPair(cell_name + "_seedingDensity (" + unit + ")", value)) } break case "GrowthCondition": String growth_condition = referenceItem.getAttributes().getNamedItem("displayName").getTextContent() keyValues.add(new MapPair(growth_condition, "yes")) break } } keyValuesPerWell.put(wellNumber,keyValues) } } return keyValuesPerWell } catch (Exception e) { println "XML platemap could not be parsed" e.printStackTrace() return new HashMap<>() } } /** * make a list of all key-values that are common to all images * * @param objective * @param commonKeyValues (a String with the following format: key1=value1;key2=value2) * @return a list of OME-XML key-values */ def getGlobalKeyValues(int objective, String commonKeyValues){ List<MapPair> keyValues = new ArrayList<>() keyValues.add(new MapPair("groovy_version", VERSION)) keyValues.add(new MapPair("objective", objectives[objective])) if (commonKeyValues != "") { String[] keyValList = commonKeyValues.split(';') for (int i = 0; i < keyValList.size(); i ++) { String keyval = keyValList[i] String[] keyvalsplit = keyval.split('=') int nPieces = keyvalsplit.size() String value = keyvalsplit[nPieces - 1] String key = keyvalsplit[0] // In case there are '=' in key for (int j = 1; j < nPieces - 1; j++) { key += '=' + keyvalsplit[j] } keyValues.add(new MapPair(key, value)) } } return keyValues } /** * convert alphanumeric well position to numeric position * * @param letter * @return */ def convertLetterToNumber(String letter){ for (int i = 0; i < LETTERS.size(); i++) { if (LETTERS.substring(i, i + 1) == letter) { return i } } return -1 } // Returns a date from a label and a date_pattern def getDate(String label, Pattern date_pattern){ // println "Trying to get date from " + label Matcher date_m = date_pattern.matcher(label) LocalDateTime dateTime if (date_m.matches()) { if (date_m.groupCount() == 5) { dateTime = LocalDateTime.parse(date_m.group(1) + "-" + date_m.group(2) + "-" + date_m.group(3) + "T" + date_m.group(4) + ":" + date_m.group(5)) } else { dateTime = LocalDateTime.parse("1970-01-" + 1 + (date_m.group(1) as int) + "T" + date_m.group(2) + ":" + date_m.group(3)) } } // println "Found " + dateTime return dateTime } // Returns the number of hours def getHoursFromImp(ImagePlus imp, ImageStack stack, LocalDateTime dateTime_ref, Pattern date_pattern){ int currentSlice = imp.getCurrentSlice() String label = stack.getSliceLabel(currentSlice) LocalDateTime dateTime = getDate(label, date_pattern) if (dateTime != null) { return ChronoUnit.HOURS.between(dateTime_ref, dateTime) as int } else { return -1 } } /** * ***************************************************************************************************************** * ************************************************* Imports **************************************************** * **************************************************************************************************************** */ import ij.IJ import ij.ImagePlus import ij.ImageStack import ij.io.FileSaver import ij.io.Opener import ij.plugin.Concatenator import ij.plugin.FolderOpener import ij.plugin.HyperStackConverter import ij.process.LUT import java.awt.GraphicsEnvironment import java.io.File import java.time.format.DateTimeFormatter import java.time.LocalDateTime import java.time.temporal.ChronoUnit import java.util.stream.Collectors import java.util.stream.IntStream import java.util.regex.* import javax.xml.parsers.DocumentBuilder import javax.xml.parsers.DocumentBuilderFactory import javax.xml.transform.OutputKeys import javax.xml.transform.Result import javax.xml.transform.Source import javax.xml.transform.Transformer import javax.xml.transform.TransformerException import javax.xml.transform.TransformerFactory import javax.xml.transform.dom.DOMSource import javax.xml.transform.stream.StreamResult import ome.units.UNITS import ome.units.quantity.Length import ome.xml.model.Channel import ome.xml.model.Image import ome.xml.model.MapAnnotation import ome.xml.model.MapPair import ome.xml.model.OME import ome.xml.model.Pixels import ome.xml.model.Plate import ome.xml.model.StructuredAnnotations import ome.xml.model.TiffData import ome.xml.model.UUID import ome.xml.model.Well import ome.xml.model.WellSample import ome.xml.model.enums.DimensionOrder import ome.xml.model.enums.NamingConvention import ome.xml.model.enums.PixelType import ome.xml.model.primitives.Color import ome.xml.model.primitives.NonNegativeInteger import ome.xml.model.primitives.PositiveInteger import ome.xml.model.primitives.Timestamp import org.apache.commons.io.filefilter.WildcardFileFilter import org.apache.commons.io.FilenameUtils import org.w3c.dom.Document import org.w3c.dom.Element import org.w3c.dom.Node import org.w3c.dom.NodeList