Mercurial > repos > lldelisle > incucyte_stack_and_upload_omero
comparison stack_buildXml.groovy @ 0:e1cba36becb2 draft
planemo upload for repository https://github.com/lldelisle/tools-lldelisle/tree/master/tools/incucyte_stack_and_upload_omero commit 4ac9b1d66ba6857357867c8eccb6c9d1ad603364
| author | lldelisle |
|---|---|
| date | Tue, 19 Dec 2023 15:02:41 +0000 |
| parents | |
| children | 3c942429f610 |
comparison
equal
deleted
inserted
replaced
| -1:000000000000 | 0:e1cba36becb2 |
|---|---|
| 1 /* | |
| 2 **************************************************** | |
| 3 * Relative to the generation of the .companion.ome * | |
| 4 **************************************************** | |
| 5 * #%L | |
| 6 * BSD implementations of Bio-Formats readers and writers | |
| 7 * %% | |
| 8 * The functions buildXML, makeImage, makePlate, postProcess and asString has been modified and adapted from | |
| 9 * https://github.com/ome/bioformats/blob/master/components/formats-bsd/test/loci/formats/utests/SPWModelMock.java | |
| 10 * | |
| 11 * Copyright (C) 2005 - 2015 Open Microscopy Environment: | |
| 12 * - Board of Regents of the University of Wisconsin-Madison | |
| 13 * - Glencoe Software, Inc. | |
| 14 * - University of Dundee | |
| 15 * | |
| 16 * @author Chris Allan <callan at blackcat dot ca> | |
| 17 * %% | |
| 18 * | |
| 19 **************************************************** | |
| 20 * Relative to the rest of the script * | |
| 21 **************************************************** | |
| 22 * | |
| 23 * * = AUTHOR INFORMATION = | |
| 24 * Code written by Rémy Dornier, EPFL - SV - PTECH - BIOP | |
| 25 * and Romain Guiet, EPFL - SV - PTECH - BIOP | |
| 26 * and Lucille Delisle, EPFL - SV - UPDUB | |
| 27 * and Pierre Osteil, EPFL - SV - UPDUB | |
| 28 * | |
| 29 * Last modification: 2023-12-19 | |
| 30 * | |
| 31 * = COPYRIGHT = | |
| 32 * © All rights reserved. ECOLE POLYTECHNIQUE FEDERALE DE LAUSANNE, Switzerland, BioImaging And Optics Platform (BIOP), 2023 | |
| 33 * | |
| 34 * Licensed under the BSD-3-Clause License: | |
| 35 * Redistribution and use in source and binary forms, with or without modification, are permitted provided | |
| 36 * that the following conditions are met: | |
| 37 * 1. Redistributions of source code must retain the above copyright notice, | |
| 38 * this list of conditions and the following disclaimer. | |
| 39 * 2. Redistributions in binary form must reproduce the above copyright notice, | |
| 40 * this list of conditions and the following disclaimer | |
| 41 * in the documentation and/or other materials provided with the distribution. | |
| 42 * 3. Neither the name of the copyright holder nor the names of its contributors | |
| 43 * may be used to endorse or promote products | |
| 44 * derived from this software without specific prior written permission. | |
| 45 * | |
| 46 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
| 47 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
| 48 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
| 49 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE | |
| 50 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
| 51 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
| 52 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
| 53 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
| 54 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |
| 55 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
| 56 * POSSIBILITY OF SUCH DAMAGE. | |
| 57 * | |
| 58 */ | |
| 59 | |
| 60 /** | |
| 61 * | |
| 62 * The purpose of this script is to combine a series of time-lapse images into | |
| 63 * one file per well/field with possibly multiple channels and multiple time points | |
| 64 * and in addition create a .companion.ome file to create an OMERO plate object, | |
| 65 * with a single image per well/field. This .companion.ome file can be directly uploaded on OMERO via | |
| 66 * OMERO.insight software or CLI, with screen import option. | |
| 67 * | |
| 68 * To make the script run | |
| 69 * 1. Create a parent folder (base_dir) and a output folder (output_dir) | |
| 70 * 2. Create a dir *Phase, *Green, *Red with the corresponding channels | |
| 71 * 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' | |
| 72 * 4. The images can be either regular tif or the raw tif from Incucyte which contains multiple series. | |
| 73 * 5. You must provide the path of the Incucyte XML file to populate key values | |
| 74 * | |
| 75 * The expected outputs are: | |
| 76 * 1. In the output_dir one tiff per well/field (multi-T and potentially multi-C) | |
| 77 * 2. In the output_dir a .companion.ome | |
| 78 */ | |
| 79 | |
| 80 #@ File(style="directory", label="Directory with up to 3 subdirectories ending by Green, Phase and/or Red") base_dir | |
| 81 #@ File(label="Incucyte XML File (plateMap)") incucyteXMLFile | |
| 82 #@ File(style="directory", label="Output directory (must exist)") output_dir | |
| 83 #@ String(label="Final XML file name", value="Test") xmlName | |
| 84 #@ String(label="Number of well in plate", choices={"96", "384"}, value="96") nWells | |
| 85 #@ Integer(label="Maximum number of images per well", value=1, min=1) n_images_per_well | |
| 86 #@ String(label="Objective", choices={"4x","10x","20x"}) objectiveChoice | |
| 87 #@ String(label="Plate name", value="Experiment:0") plateName | |
| 88 #@ String(label="common Key Values formatted as key1=value1;key2=value2", value="") commonKeyValues | |
| 89 #@ Boolean(label="Ignore Compound concentration from plateMap", value=true) ignoreConcentration | |
| 90 #@ Boolean(label="Ignore Cell passage number from plateMap", value=true) ignorePassage | |
| 91 #@ Boolean(label="Ignore Cell seeding concentration from plateMap", value=true) ignoreSeeding | |
| 92 | |
| 93 | |
| 94 /** | |
| 95 * ***************************************************************************************************************** | |
| 96 * ********************************************* Final Variables ************************************************** | |
| 97 * ********************************************* DO NOT MODIFY **************************************************** | |
| 98 * **************************************************************************************************************** | |
| 99 */ | |
| 100 | |
| 101 /** objectives and respective pixel sizes */ | |
| 102 objective = 0 | |
| 103 objectives = new String[]{"4x", "10x", "20x"} | |
| 104 pixelSizes = new double[]{2.82, 1.24, 0.62} | |
| 105 | |
| 106 /** pattern for date */ | |
| 107 REGEX_FOR_DATE = ".*_([0-9]{4})y([0-9]{2})m([0-9]{2})d_([0-9]{2})h([0-9]{2})m.tif" | |
| 108 | |
| 109 ALTERNATIVE_REGEX_FOR_DATE = ".*_([0-9]{2})d([0-9]{2})h([0-9]{2})m.tif" | |
| 110 | |
| 111 /** Image properties keys */ | |
| 112 DIMENSION_ORDER = "dimension_order" | |
| 113 FILE_NAME = "file_name" | |
| 114 IMG_POS_IN_WELL = "img_pos_in_well" | |
| 115 FIRST_ACQUISITION_DATE = "acquisition_date" | |
| 116 FIRST_ACQUISITION_TIME = "acquisition_time" | |
| 117 RELATIVE_ACQUISITION_HOUR = "relative_acquisition_hour" | |
| 118 | |
| 119 /** global variable for index to letter conversion */ | |
| 120 LETTERS = new String("ABCDEFGHIJKLMNOP") | |
| 121 | |
| 122 // Version number = date of last modif | |
| 123 VERSION = "20231219" | |
| 124 | |
| 125 /** Key-Value pairs namespace */ | |
| 126 GENERAL_ANNOTATION_NAMESPACE = "openmicroscopy.org/omero/client/mapAnnotation" | |
| 127 annotations = new StructuredAnnotations() | |
| 128 | |
| 129 /** Plate details and conventions */ | |
| 130 PLATE_ID = "Plate:0" | |
| 131 PLATE_NAME = plateName | |
| 132 | |
| 133 if (nWells == "96") { | |
| 134 nRows = 8 | |
| 135 nCols = 12 | |
| 136 } else if (nWells == "384") { | |
| 137 nRows = 16 | |
| 138 nCols = 24 | |
| 139 } | |
| 140 | |
| 141 WELL_ROWS = new PositiveInteger(nRows) | |
| 142 WELL_COLS = new PositiveInteger(nCols) | |
| 143 WELL_ROW = NamingConvention.LETTER | |
| 144 WELL_COL = NamingConvention.NUMBER | |
| 145 | |
| 146 /** XML namespace. */ | |
| 147 XML_NS = "http://www.openmicroscopy.org/Schemas/OME/2010-06" | |
| 148 | |
| 149 /** XSI namespace. */ | |
| 150 XSI_NS = "http://www.w3.org/2001/XMLSchema-instance" | |
| 151 | |
| 152 /** XML schema location. */ | |
| 153 SCHEMA_LOCATION = "http://www.openmicroscopy.org/Schemas/OME/2010-06/ome.xsd" | |
| 154 | |
| 155 | |
| 156 /** | |
| 157 * ***************************************************************************************************************** | |
| 158 * **************************************** Beginning of the script *********************************************** | |
| 159 * **************************************************************************************************************** | |
| 160 */ | |
| 161 | |
| 162 try { | |
| 163 | |
| 164 println "Beginning of the script" | |
| 165 | |
| 166 /** | |
| 167 * Prepare list of wells name | |
| 168 */ | |
| 169 String[] well = [] | |
| 170 | |
| 171 well = [(0..(nRows - 1)),(0..(nCols - 1))].combinations().collect{ r,c -> LETTERS.substring(r, r + 1) +""+ (c+ 1).toString() } | |
| 172 | |
| 173 IJ.run("Close All", "") | |
| 174 | |
| 175 // loop for all the wells | |
| 176 | |
| 177 // Store all merged ImagePlus into a HashMap where | |
| 178 // keys are well name (A1, A10) | |
| 179 // values are a list of ImagePlus corresponding to different field of view | |
| 180 Map<String, List<ImagePlus>> wellSamplesMap = new HashMap<>() | |
| 181 | |
| 182 well.each{ input -> | |
| 183 IJ.run("Close All", "") | |
| 184 | |
| 185 List<ImagePlus> final_imp_list = process_well(base_dir, input, n_images_per_well) //, perform_bc, mediaChangeTime ) | |
| 186 if (!final_imp_list.isEmpty()) { | |
| 187 wellSamplesMap.put(input, final_imp_list) | |
| 188 for(ImagePlus final_imp : final_imp_list){ | |
| 189 final_imp.setTitle(input+"_"+final_imp.getProperty(IMG_POS_IN_WELL)) | |
| 190 //final_imp.show() | |
| 191 | |
| 192 def fs = new FileSaver(final_imp) | |
| 193 File output_path = new File (output_dir ,final_imp.getTitle()+"_merge.tif" ) | |
| 194 fs.saveAsTiff(output_path.toString() ) | |
| 195 final_imp.setProperty(FILE_NAME, output_path.getName()) | |
| 196 | |
| 197 IJ.run("Close All", "") | |
| 198 } | |
| 199 } else { | |
| 200 println "No match for " + input | |
| 201 } | |
| 202 } | |
| 203 | |
| 204 | |
| 205 // get folder and xml file path | |
| 206 output_dir_abs = output_dir.getAbsolutePath() | |
| 207 incucyteXMLFilePath = incucyteXMLFile.getAbsolutePath() | |
| 208 | |
| 209 if (! new File(incucyteXMLFilePath).exists()) { | |
| 210 println "The incucyte file does not exists" | |
| 211 return | |
| 212 } | |
| 213 | |
| 214 // select the right objective | |
| 215 switch (objectiveChoice){ | |
| 216 case "4x": | |
| 217 objective = 0 | |
| 218 break | |
| 219 case "10x": | |
| 220 objective = 1 | |
| 221 break | |
| 222 case "20x": | |
| 223 objective = 2 | |
| 224 break | |
| 225 } | |
| 226 | |
| 227 // get plate scheme as key-values | |
| 228 Map<String, List<MapPair>> keyValuesPerWell = parseIncucyteXML(incucyteXMLFilePath, ignoreConcentration, ignorePassage, ignoreSeeding) | |
| 229 | |
| 230 // get global key-values | |
| 231 List<MapPair> globalKeyValues = getGlobalKeyValues(objective, commonKeyValues) | |
| 232 double pixelSize = pixelSizes[objective] | |
| 233 | |
| 234 // generate OME-XML metadata file | |
| 235 OME ome = buildXMLFile(wellSamplesMap, keyValuesPerWell, globalKeyValues, pixelSize) | |
| 236 | |
| 237 // create XML document | |
| 238 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance() | |
| 239 DocumentBuilder parser = factory.newDocumentBuilder() | |
| 240 Document document = parser.newDocument() | |
| 241 | |
| 242 // Produce a valid OME DOM element hierarchy | |
| 243 Element root = ome.asXMLElement(document) | |
| 244 postProcess(root, document) | |
| 245 | |
| 246 // Produce string XML | |
| 247 try(OutputStream outputStream = new FileOutputStream(output_dir_abs + File.separator + xmlName + ".companion.ome")){ | |
| 248 outputStream.write(asString(document).getBytes()) | |
| 249 } catch(Exception e){ | |
| 250 e.printStackTrace() | |
| 251 } | |
| 252 println "End of the script" | |
| 253 | |
| 254 } catch (Throwable e) { | |
| 255 println("Something went wrong: " + e) | |
| 256 e.printStackTrace() | |
| 257 throw e | |
| 258 | |
| 259 if (GraphicsEnvironment.isHeadless()){ | |
| 260 // Force to give exit signal of error | |
| 261 System.exit(1) | |
| 262 } | |
| 263 | |
| 264 } | |
| 265 | |
| 266 return | |
| 267 | |
| 268 /** | |
| 269 * **************************************************************************************************************** | |
| 270 * ******************************************* End of the script ************************************************** | |
| 271 * | |
| 272 * **************************************************************************************************************** | |
| 273 * | |
| 274 * *********************************** Helpers and processing methods ********************************************* | |
| 275 * *************************************************************************************************************** | |
| 276 */ | |
| 277 | |
| 278 def process_well(baseDir, input_wellId, n_image_per_well){ //, perform_bc, mediaChangeTime){ | |
| 279 File bf_dir = baseDir.listFiles().find{ it =~ /.*Phase.*/} | |
| 280 File green_dir = baseDir.listFiles().find{ it =~ /.*Green.*/} | |
| 281 File red_dir = baseDir.listFiles().find{ it =~ /.*Red.*/} | |
| 282 //if (verbose) println bf_dir | |
| 283 //if (verbose) println green_dir | |
| 284 | |
| 285 // The images are stored in a TreeMap where | |
| 286 // keys are wellSampleId = field identifier | |
| 287 // values are a TreeMap that we call channelMap where: | |
| 288 // keys are colors (Green, Grays, Red) | |
| 289 // values are an ImagePlus (T-stack) | |
| 290 Map<Integer, Map<String, ImagePlus>> sampleToChannelMap = new TreeMap<>() | |
| 291 | |
| 292 List<File> folder_list = [bf_dir, green_dir, red_dir] | |
| 293 List<String> channels_list = ["Grays", "Green", "Red"] | |
| 294 | |
| 295 // loop over the field and open images | |
| 296 for(int wellSampleId = 1; wellSampleId <= n_image_per_well; wellSampleId++) { | |
| 297 // nT is the number of time-points for the well input_wellId | |
| 298 int nT = 0 | |
| 299 String first_channel = "" | |
| 300 String first_acq_date = "" | |
| 301 String first_acq_time = "" | |
| 302 String rel_acq_hour = "" | |
| 303 | |
| 304 // Initiate a channel map for the wellSampleId | |
| 305 Map<String, ImagePlus> channelMap = new TreeMap<>() | |
| 306 | |
| 307 // Checking if there are images in the corresponding dir | |
| 308 // which corresponds to the input_wellId | |
| 309 // and to the wellSampleId | |
| 310 // The image name should be: | |
| 311 // Prefix + "_" + input_wellId + "_" + wellSampleId + "_" + year (4 digits) + "y" + month (2 digits) + "m" + day + "d_" + hour + "h" + minute + "m.tif" | |
| 312 FileFilter fileFilter = new WildcardFileFilter("*_" + input_wellId + "_" + wellSampleId + "_*") | |
| 313 for(int i = 0; i < folder_list.size(); i++){ | |
| 314 if (folder_list.get(i) != null) { | |
| 315 File[] files_matching = folder_list.get(i).listFiles(fileFilter as FileFilter).sort() | |
| 316 if (files_matching.size() != 0) { | |
| 317 // In order to deal with raw images from Incucyte which are | |
| 318 // Multi series images | |
| 319 // We open the first image | |
| 320 ImagePlus first_imp = Opener.openUsingBioFormats(files_matching[0].getAbsolutePath()) | |
| 321 // We check if we can read the infos | |
| 322 first_image_infos = Opener.getTiffFileInfo(files_matching[0].getAbsolutePath()) | |
| 323 // We define the imageplus object | |
| 324 ImagePlus single_channel_imp | |
| 325 if (first_image_infos == null) { | |
| 326 // They are raw from incucyte | |
| 327 // We need to open images one by one and add them to the stack | |
| 328 ImageStack stack = new ImageStack(first_imp.width, first_imp.height); | |
| 329 files_matching.each{ | |
| 330 ImagePlus single_imp = (new Opener()).openUsingBioFormats(it.getAbsolutePath()) | |
| 331 String new_title = single_imp.getTitle().split(" - ")[0] | |
| 332 stack.addSlice(new_title, single_imp.getProcessor()) | |
| 333 } | |
| 334 single_channel_imp = new ImagePlus(FilenameUtils.getBaseName(folder_list.get(i).getAbsolutePath()), stack); | |
| 335 } else { | |
| 336 // They are regular tif file | |
| 337 // We can use FolderOpener to create the stack at once | |
| 338 single_channel_imp = FolderOpener.open(folder_list.get(i).getAbsolutePath(), " filter=_"+ input_wellId + "_"+wellSampleId+"_") | |
| 339 } | |
| 340 // Phase are 8-bit and need to be changed to 16-bit | |
| 341 // Other are already 16-bit but it does not hurt | |
| 342 IJ.run(single_channel_imp, "16-bit", "") | |
| 343 | |
| 344 // check frame size | |
| 345 if (nT == 0) { | |
| 346 // This is the first channel with images | |
| 347 nT = single_channel_imp.getNSlices() | |
| 348 first_channel = channels_list.get(i) | |
| 349 // Process all dates: | |
| 350 Pattern date_pattern = Pattern.compile(REGEX_FOR_DATE) | |
| 351 ImageStack stack = single_channel_imp.getStack() | |
| 352 // Go to the first time (which is slice) | |
| 353 single_channel_imp.setSlice(1) | |
| 354 int currentSlice = single_channel_imp.getCurrentSlice() | |
| 355 String label = stack.getSliceLabel(currentSlice) | |
| 356 LocalDateTime dateTime_ref = getDate(label, date_pattern) | |
| 357 if (dateTime_ref == null) { | |
| 358 date_pattern = Pattern.compile(ALTERNATIVE_REGEX_FOR_DATE) | |
| 359 dateTime_ref = getDate(label, date_pattern) | |
| 360 } | |
| 361 if (dateTime_ref != null) { | |
| 362 first_acq_date = dateTime_ref.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")) | |
| 363 first_acq_time = dateTime_ref.format(DateTimeFormatter.ofPattern("HH:mm:ss")) | |
| 364 for (int ti = 2; ti<= nT; ti++) { | |
| 365 // Process each frame starting at 2 | |
| 366 single_channel_imp.setSlice(ti) | |
| 367 int hours = getHoursFromImp(single_channel_imp, stack, dateTime_ref, date_pattern) | |
| 368 if (rel_acq_hour == "") { | |
| 369 rel_acq_hour = "" + hours | |
| 370 } else { | |
| 371 rel_acq_hour += "," + hours | |
| 372 } | |
| 373 } | |
| 374 } else { | |
| 375 first_acq_date = "NA" | |
| 376 first_acq_time = "NA" | |
| 377 } | |
| 378 } else { | |
| 379 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 + "." | |
| 380 } | |
| 381 // Set acquisition properties | |
| 382 single_channel_imp.setProperty(FIRST_ACQUISITION_DATE, first_acq_date) | |
| 383 single_channel_imp.setProperty(FIRST_ACQUISITION_TIME, first_acq_time) | |
| 384 single_channel_imp.setProperty(RELATIVE_ACQUISITION_HOUR, rel_acq_hour) | |
| 385 println single_channel_imp.getProperty(FIRST_ACQUISITION_DATE) | |
| 386 println single_channel_imp.getProperty(FIRST_ACQUISITION_TIME) | |
| 387 // add the image stack to the channel map for the corresponding color | |
| 388 channelMap.put(channels_list.get(i), single_channel_imp) | |
| 389 } | |
| 390 } | |
| 391 } | |
| 392 if (nT != 0) { | |
| 393 // add the channelmap to the sampleToChannelMap using the wellSampleId as key | |
| 394 sampleToChannelMap.put(wellSampleId, channelMap) | |
| 395 } | |
| 396 } | |
| 397 | |
| 398 ArrayList<ImagePlus> final_imp_list = [] | |
| 399 | |
| 400 // Now loop over the wellSampleId which have images | |
| 401 for(Integer wellSampleId : sampleToChannelMap.keySet()){ | |
| 402 // get the channel map | |
| 403 Map<String, ImagePlus> channelsMap = sampleToChannelMap.get(wellSampleId) | |
| 404 ArrayList<String> channels = [] | |
| 405 ArrayList<ImagePlus> current_images = [] | |
| 406 | |
| 407 for(String channel : channelsMap.keySet()){ | |
| 408 channels.add(channel) | |
| 409 current_images.add(channelsMap.get(channel)) | |
| 410 } | |
| 411 // Get number of time: | |
| 412 int nT = current_images[0].nSlices | |
| 413 | |
| 414 // Merge all | |
| 415 ImagePlus merged_imps = Concatenator.run(current_images as ImagePlus[]) | |
| 416 // Re-order to make a multi-channel, time-lapse image | |
| 417 ImagePlus final_imp | |
| 418 if (channels.size() == 1 && nT == 1) { | |
| 419 final_imp = merged_imps | |
| 420 } else { | |
| 421 final_imp = HyperStackConverter.toHyperStack(merged_imps, channels.size() , 1, nT, "xytcz", "Color") | |
| 422 } | |
| 423 // add properties to the image | |
| 424 final_imp.setProperty(DIMENSION_ORDER, DimensionOrder.XYCZT) | |
| 425 final_imp.setProperty(IMG_POS_IN_WELL, wellSampleId) | |
| 426 final_imp.setProperty(FIRST_ACQUISITION_DATE, current_images[0].getProperty(FIRST_ACQUISITION_DATE)) | |
| 427 final_imp.setProperty(FIRST_ACQUISITION_TIME, current_images[0].getProperty(FIRST_ACQUISITION_TIME)) | |
| 428 final_imp.setProperty(RELATIVE_ACQUISITION_HOUR, current_images[0].getProperty(RELATIVE_ACQUISITION_HOUR)) | |
| 429 | |
| 430 // set LUTs | |
| 431 (0..channels.size()-1).each{ | |
| 432 final_imp.setC(it + 1) | |
| 433 IJ.run(final_imp, channels[it], "") | |
| 434 final_imp.resetDisplayRange() | |
| 435 } | |
| 436 | |
| 437 final_imp_list.add(final_imp) | |
| 438 } | |
| 439 | |
| 440 return final_imp_list | |
| 441 } | |
| 442 | |
| 443 | |
| 444 /** | |
| 445 * create the full XML metadata (plate, images, channels, annotations....) | |
| 446 * | |
| 447 * @param imagesName | |
| 448 * @param keyValuesPerWell | |
| 449 * @param globalKeyValues | |
| 450 * @param pixelSize | |
| 451 * @return OME-XML metadata instance | |
| 452 */ | |
| 453 def buildXMLFile(Map<String,List<ImagePlus>> wellToImagesMap, Map<String, List<MapPair>> keyValuesPerWell, List<MapPair> globalKeyValues, double pixelSize) { | |
| 454 // create a new OME-XML metadata instance | |
| 455 OME ome = new OME() | |
| 456 | |
| 457 Map<String, Integer> imgInWellPosToListMap = new HashMap<>() | |
| 458 int imgCmp = 0 | |
| 459 for (String wellId: wellToImagesMap.keySet()) { | |
| 460 // get well position from image name | |
| 461 List<ImagePlus> imagesWithinWell = wellToImagesMap.get(wellId) | |
| 462 | |
| 463 for (ImagePlus image : imagesWithinWell) { | |
| 464 // get KVP corresponding to the current well | |
| 465 // Initiate a list of keyValues for the wellId | |
| 466 // (or use the existing one) | |
| 467 List<MapPair> keyValues = [] | |
| 468 if(keyValuesPerWell.containsKey(wellId)) | |
| 469 keyValues = keyValuesPerWell.get(wellId) | |
| 470 keyValues.addAll(globalKeyValues) | |
| 471 | |
| 472 // create an Image node in the ome-xml | |
| 473 imgInWellPosToListMap.put(wellId+ "_" +image.getProperty(IMG_POS_IN_WELL),imgCmp) | |
| 474 ome.addImage(makeImage(imgCmp++, image, keyValues, pixelSize)) | |
| 475 } | |
| 476 } | |
| 477 | |
| 478 // create Plate node | |
| 479 ome.addPlate(makePlate(wellToImagesMap, imgInWellPosToListMap, pixelSize, ome)) | |
| 480 | |
| 481 // add annotation nodes | |
| 482 ome.setStructuredAnnotations(annotations) | |
| 483 | |
| 484 return ome | |
| 485 } | |
| 486 | |
| 487 /** | |
| 488 * create an image xml-element, populated with annotations, channel, pixels and path elements | |
| 489 * | |
| 490 * @param index image ID | |
| 491 * @param imageName | |
| 492 * @param keyValues | |
| 493 * @param pixelSize | |
| 494 * @return an image xml-element | |
| 495 */ | |
| 496 def makeImage(int index, ImagePlus imagePlus, List<MapPair> keyValues, double pixelSize) { | |
| 497 // Create <Image/> | |
| 498 Image image = new Image() | |
| 499 image.setID("Image:" + index) | |
| 500 // The image name is the name of the file without extension | |
| 501 image.setName(((String)imagePlus.getProperty(FILE_NAME)).split("\\.")[0]) | |
| 502 // Set the acquisitionDate: | |
| 503 if (imagePlus.getProperty(FIRST_ACQUISITION_DATE) != "NA") { | |
| 504 image.setAcquisitionDate(new Timestamp(imagePlus.getProperty(FIRST_ACQUISITION_DATE) + "T" + imagePlus.getProperty(FIRST_ACQUISITION_TIME))) | |
| 505 // Also add it to the key values: | |
| 506 keyValues.add(new MapPair("acquisition.day", (String)imagePlus.getProperty(FIRST_ACQUISITION_DATE))) | |
| 507 keyValues.add(new MapPair("acquisition.time", (String)imagePlus.getProperty(FIRST_ACQUISITION_TIME))) | |
| 508 keyValues.add(new MapPair("relative.acquisition.hours", (String)imagePlus.getProperty(RELATIVE_ACQUISITION_HOUR))) | |
| 509 } | |
| 510 // Create <MapAnnotations/> | |
| 511 MapAnnotation mapAnnotation = new MapAnnotation() | |
| 512 mapAnnotation.setID("ImageKeyValueAnnotation:" + index) | |
| 513 mapAnnotation.setNamespace(GENERAL_ANNOTATION_NAMESPACE) | |
| 514 mapAnnotation.setValue(keyValues) | |
| 515 annotations.addMapAnnotation(mapAnnotation); // add the KeyValues to the general structured annotation element | |
| 516 image.linkAnnotation(mapAnnotation) | |
| 517 | |
| 518 // Create <Pixels/> | |
| 519 Pixels pixels = new Pixels() | |
| 520 pixels.setID("Pixels:" + index) | |
| 521 pixels.setSizeX(new PositiveInteger(imagePlus.getWidth())) | |
| 522 pixels.setSizeY(new PositiveInteger(imagePlus.getHeight())) | |
| 523 pixels.setSizeZ(new PositiveInteger(imagePlus.getNSlices())) | |
| 524 pixels.setSizeC(new PositiveInteger(imagePlus.getNChannels())) | |
| 525 pixels.setSizeT(new PositiveInteger(imagePlus.getNFrames())) | |
| 526 pixels.setDimensionOrder((DimensionOrder) imagePlus.getProperty(DIMENSION_ORDER)) | |
| 527 pixels.setType(getPixelType(imagePlus)) | |
| 528 pixels.setPhysicalSizeX(new Length(pixelSize, UNITS.MICROMETER)) | |
| 529 pixels.setPhysicalSizeY(new Length(pixelSize, UNITS.MICROMETER)) | |
| 530 | |
| 531 // Create <TiffData/> under <Pixels/> | |
| 532 TiffData tiffData = new TiffData() | |
| 533 tiffData.setFirstC(new NonNegativeInteger(0)) | |
| 534 tiffData.setFirstT(new NonNegativeInteger(0)) | |
| 535 tiffData.setFirstZ(new NonNegativeInteger(0)) | |
| 536 tiffData.setPlaneCount(new NonNegativeInteger(imagePlus.getNSlices()*imagePlus.getNChannels()*imagePlus.getNFrames())) | |
| 537 | |
| 538 // Create <UUID/> under <TiffData/> | |
| 539 UUID uuid = new UUID() | |
| 540 uuid.setFileName((String)imagePlus.getProperty(FILE_NAME)) | |
| 541 uuid.setValue(java.util.UUID.randomUUID().toString()) | |
| 542 tiffData.setUUID(uuid) | |
| 543 | |
| 544 // Put <TiffData/> under <Pixels/> | |
| 545 pixels.addTiffData(tiffData) | |
| 546 | |
| 547 // Create <Channel/> under <Pixels/> | |
| 548 LUT[] luts = imagePlus.getLuts() | |
| 549 for (int i = 0; i < luts.length; i++) { | |
| 550 Channel channel = new Channel() | |
| 551 channel.setID("Channel:" + i) | |
| 552 channel.setColor(new Color(luts[i].getRed(255),luts[i].getGreen(255), luts[i].getBlue(255),255)) | |
| 553 pixels.addChannel(channel) | |
| 554 } | |
| 555 | |
| 556 // Put <Pixels/> under <Image/> | |
| 557 image.setPixels(pixels) | |
| 558 | |
| 559 return image | |
| 560 } | |
| 561 | |
| 562 | |
| 563 /** | |
| 564 * get pixel type based on the imagePlus type | |
| 565 * @param imp | |
| 566 * @return pixel type | |
| 567 */ | |
| 568 def getPixelType(ImagePlus imp){ | |
| 569 switch (imp.getType()) { | |
| 570 case ImagePlus.GRAY8: | |
| 571 return PixelType.UINT8 | |
| 572 case ImagePlus.GRAY16: | |
| 573 return PixelType.UINT16 | |
| 574 case ImagePlus.GRAY32: | |
| 575 return PixelType.FLOAT | |
| 576 default: | |
| 577 return PixelType.FLOAT | |
| 578 } | |
| 579 } | |
| 580 | |
| 581 /** | |
| 582 * create a Plate xml-element, populated with wells and their attributes | |
| 583 * @param imagesName | |
| 584 * @return Plate xml-element | |
| 585 */ | |
| 586 def makePlate(Map<String, List<ImagePlus>> wellToImagesMap, Map<String, Integer> imgPosInListMap, double pixelSize, OME ome) { | |
| 587 // Create <Plate/> | |
| 588 Plate plate = new Plate() | |
| 589 plate.setName(PLATE_NAME) | |
| 590 plate.setID(PLATE_ID) | |
| 591 plate.setRows(WELL_ROWS) | |
| 592 plate.setColumns(WELL_COLS) | |
| 593 plate.setRowNamingConvention(WELL_ROW) | |
| 594 plate.setColumnNamingConvention(WELL_COL) | |
| 595 | |
| 596 // for each image (one image per well) | |
| 597 for (String wellId: wellToImagesMap.keySet()) { | |
| 598 // get well position from image name | |
| 599 List<ImagePlus> imagesWithinWell = wellToImagesMap.get(wellId) | |
| 600 | |
| 601 // get well position from image name | |
| 602 int row = convertLetterToNumber(wellId.substring(0, 1)) | |
| 603 int col = Integer.parseInt(wellId.substring(1)) - 1 | |
| 604 | |
| 605 // row and col should correspond to a real well | |
| 606 if(row >= 0 && col >= 0 && col < 12) { | |
| 607 // Create <Well/> under <Plate/> | |
| 608 Well well = new Well() | |
| 609 well.setID(String.format("Well:%d_%d", row, col)) | |
| 610 well.setRow(new NonNegativeInteger(row)) | |
| 611 well.setColumn(new NonNegativeInteger(col)) | |
| 612 | |
| 613 for (ImagePlus imagePlus : imagesWithinWell) { | |
| 614 int wellSampleIndex = imgPosInListMap.get(wellId + "_" + imagePlus.getProperty(IMG_POS_IN_WELL)) | |
| 615 | |
| 616 // Create <WellSample/> under <Well/> | |
| 617 WellSample sample = new WellSample() | |
| 618 sample.setID(String.format("WellSample:%d", wellSampleIndex)) | |
| 619 sample.setIndex(new NonNegativeInteger(wellSampleIndex)) | |
| 620 if (imagePlus.getCalibration() != null) { | |
| 621 sample.setPositionX(new Length(imagePlus.getCalibration().xOrigin * pixelSize, UNITS.MICROMETER)) | |
| 622 sample.setPositionY(new Length(imagePlus.getCalibration().yOrigin * pixelSize, UNITS.MICROMETER)) | |
| 623 } | |
| 624 sample.linkImage(ome.getImage(wellSampleIndex)) | |
| 625 | |
| 626 // Put <WellSample/> under <Well/> | |
| 627 well.addWellSample(sample) | |
| 628 } | |
| 629 | |
| 630 // Put <Well/> under <Plate/> | |
| 631 plate.addWell(well) | |
| 632 } | |
| 633 } | |
| 634 return plate | |
| 635 } | |
| 636 | |
| 637 /** | |
| 638 * convert the XML metadata document into string | |
| 639 * | |
| 640 * @param document | |
| 641 * @return | |
| 642 * @throws TransformerException | |
| 643 * @throws UnsupportedEncodingException | |
| 644 */ | |
| 645 def asString(Document document) throws TransformerException, UnsupportedEncodingException { | |
| 646 TransformerFactory transformerFactory = TransformerFactory.newInstance() | |
| 647 Transformer transformer = transformerFactory.newTransformer() | |
| 648 | |
| 649 //Setup indenting to "pretty print" | |
| 650 transformer.setOutputProperty(OutputKeys.INDENT, "yes") | |
| 651 transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4") | |
| 652 | |
| 653 Source source = new DOMSource(document) | |
| 654 ByteArrayOutputStream os = new ByteArrayOutputStream() | |
| 655 Result result = new StreamResult(new OutputStreamWriter(os, "utf-8")) | |
| 656 transformer.transform(source, result) | |
| 657 | |
| 658 return os.toString() | |
| 659 } | |
| 660 | |
| 661 /** | |
| 662 * add document header | |
| 663 * | |
| 664 * @param root | |
| 665 * @param document | |
| 666 */ | |
| 667 def postProcess(Element root, Document document) { | |
| 668 root.setAttribute("xmlns", XML_NS) | |
| 669 root.setAttribute("xmlns:xsi", XSI_NS) | |
| 670 root.setAttribute("xsi:schemaLocation", XML_NS + " " + SCHEMA_LOCATION) | |
| 671 root.setAttribute("UUID", java.util.UUID.randomUUID().toString()) | |
| 672 document.appendChild(root) | |
| 673 } | |
| 674 | |
| 675 | |
| 676 /** | |
| 677 * read Incucyte plate-scheme XML file, extract attributes per well and convert attributes to OMERO-compatible | |
| 678 * keys-value pairs XML elements. | |
| 679 * | |
| 680 * @param path Incucyte plate-scheme XML file path | |
| 681 * @return Map of OME-XML compatible key-values per well | |
| 682 */ | |
| 683 def parseIncucyteXML(String path, Boolean ignoreConcentration, Boolean ignorePassage, Boolean ignoreSeeding) { | |
| 684 Map<String, List<MapPair>> keyValuesPerWell = new HashMap<>() | |
| 685 | |
| 686 final String rowAttribute = "row" | |
| 687 final String columnAttribute = "col" | |
| 688 | |
| 689 final String wellNode = "well" | |
| 690 final String itemsNode = "items" | |
| 691 final String wellItemNode = "wellItem" | |
| 692 final String referenceItemNode = "referenceItem" | |
| 693 | |
| 694 // There are 3 types of referenceItem: Compound, CellType, GrowthCondition | |
| 695 // | |
| 696 // For the Compound, each well can have a concentration and a concentrationUnits | |
| 697 // the referenceItem has a displayName | |
| 698 // The key should be: displayName (concentrationUnits) | |
| 699 // The value should be: concentration | |
| 700 // However, if ignoreConcentration is set to true: | |
| 701 // The key should be: displayName (NA) | |
| 702 // The value should be: 1 | |
| 703 // | |
| 704 // For the CellType, each well can have a passage, a seedingDensity and a seedingDensityUnits | |
| 705 // the referenceItem has a displayName | |
| 706 // The passage key should be: displayName_passage | |
| 707 // The value should be: passage | |
| 708 // However, if ignorePassage is set to true no key value should be stored for this | |
| 709 // Then: | |
| 710 // The seeding key should be: displayName_seedingDensity (seedingDensityUnits) | |
| 711 // The value should be: seedingDensity | |
| 712 // However, if ignoreSeeding is set to true: | |
| 713 // The key should be: displayName | |
| 714 // The value should be: "yes" | |
| 715 // | |
| 716 // For the GrowthCondition, the referenceItem has a displayName | |
| 717 // The key should be: displayName | |
| 718 // The value should be: "yes" | |
| 719 | |
| 720 try { | |
| 721 // create an document | |
| 722 DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance() | |
| 723 DocumentBuilder dBuilder = dbFactory.newDocumentBuilder() | |
| 724 | |
| 725 // read the xml file | |
| 726 Document doc = dBuilder.parse(new File(path)) | |
| 727 doc.getDocumentElement().normalize() | |
| 728 | |
| 729 // get all "well" nodes | |
| 730 NodeList wellList = doc.getElementsByTagName(wellNode) | |
| 731 | |
| 732 for(int i = 0; i < wellList.getLength(); i++) { | |
| 733 Node well = wellList.item(i) | |
| 734 | |
| 735 // extract well attributes | |
| 736 String row = well.getAttributes().getNamedItem(rowAttribute).getTextContent() | |
| 737 int r = row as int | |
| 738 String col = well.getAttributes().getNamedItem(columnAttribute).getTextContent() | |
| 739 String wellNumber = LETTERS.substring(r, r + 1) + (Integer.parseInt(col)+ 1) | |
| 740 | |
| 741 // extract items node, under well node | |
| 742 Node items = ((Element)well).getElementsByTagName(itemsNode).item(0) | |
| 743 if (items != null) { | |
| 744 // get all "wellItem" nodes, under items node | |
| 745 NodeList wellItemList = ((Element)items).getElementsByTagName(wellItemNode) | |
| 746 | |
| 747 // read referenceItem node's attributes and convert them into key-values | |
| 748 List<MapPair> keyValues = new ArrayList<>() | |
| 749 for (int j = 0; j < wellItemList.getLength(); j++) { | |
| 750 Node wellItem = wellItemList.item(j) | |
| 751 // extract referenceItem node, under wellItem node | |
| 752 Node referenceItem = ((Element)wellItem).getElementsByTagName(referenceItemNode).item(0) | |
| 753 String wellType = wellItem.getAttributes().getNamedItem("type").getTextContent() | |
| 754 | |
| 755 // select the right key-values | |
| 756 switch (wellType){ | |
| 757 case "Compound": | |
| 758 String compound_name = referenceItem.getAttributes().getNamedItem("displayName").getTextContent() | |
| 759 if (ignoreConcentration) { | |
| 760 keyValues.add(new MapPair(compound_name + " (NA)", "1")) | |
| 761 } else { | |
| 762 String unit = wellItem.getAttributes().getNamedItem("concentrationUnits").getTextContent() | |
| 763 unit = unit.replace("\u00B5", "u") | |
| 764 String value = wellItem.getAttributes().getNamedItem("concentration").getTextContent() | |
| 765 keyValues.add(new MapPair(compound_name + " (" + unit + ")", value)) | |
| 766 } | |
| 767 break | |
| 768 case "CellType": | |
| 769 String cell_name = referenceItem.getAttributes().getNamedItem("displayName").getTextContent() | |
| 770 if (!ignorePassage) { | |
| 771 String passage = wellItem.getAttributes().getNamedItem("passage").getTextContent() | |
| 772 keyValues.add(new MapPair(cell_name + "_passage", passage)) | |
| 773 } | |
| 774 if (ignoreSeeding) { | |
| 775 keyValues.add(new MapPair(cell_name, "yes")) | |
| 776 } else { | |
| 777 String unit = wellItem.getAttributes().getNamedItem("seedingDensityUnits").getTextContent() | |
| 778 unit = unit.replace("\u00B5", "u") | |
| 779 String value = wellItem.getAttributes().getNamedItem("seedingDensity").getTextContent() | |
| 780 keyValues.add(new MapPair(cell_name + "_seedingDensity (" + unit + ")", value)) | |
| 781 } | |
| 782 break | |
| 783 case "GrowthCondition": | |
| 784 String growth_condition = referenceItem.getAttributes().getNamedItem("displayName").getTextContent() | |
| 785 keyValues.add(new MapPair(growth_condition, "yes")) | |
| 786 break | |
| 787 } | |
| 788 } | |
| 789 keyValuesPerWell.put(wellNumber,keyValues) | |
| 790 | |
| 791 } | |
| 792 } | |
| 793 return keyValuesPerWell | |
| 794 } catch (Exception e) { | |
| 795 println "XML platemap could not be parsed" | |
| 796 e.printStackTrace() | |
| 797 return new HashMap<>() | |
| 798 } | |
| 799 } | |
| 800 | |
| 801 /** | |
| 802 * make a list of all key-values that are common to all images | |
| 803 * | |
| 804 * @param objective | |
| 805 * @param commonKeyValues (a String with the following format: key1=value1;key2=value2) | |
| 806 * @return a list of OME-XML key-values | |
| 807 */ | |
| 808 def getGlobalKeyValues(int objective, String commonKeyValues){ | |
| 809 List<MapPair> keyValues = new ArrayList<>() | |
| 810 keyValues.add(new MapPair("groovy_version", VERSION)) | |
| 811 keyValues.add(new MapPair("objective", objectives[objective])) | |
| 812 if (commonKeyValues != "") { | |
| 813 String[] keyValList = commonKeyValues.split(';') | |
| 814 for (int i = 0; i < keyValList.size(); i ++) { | |
| 815 String keyval = keyValList[i] | |
| 816 String[] keyvalsplit = keyval.split('=') | |
| 817 int nPieces = keyvalsplit.size() | |
| 818 String value = keyvalsplit[nPieces - 1] | |
| 819 String key = keyvalsplit[0] | |
| 820 // In case there are '=' in key | |
| 821 for (int j = 1; j < nPieces - 1; j++) { | |
| 822 key += '=' + keyvalsplit[j] | |
| 823 } | |
| 824 keyValues.add(new MapPair(key, value)) | |
| 825 } | |
| 826 } | |
| 827 return keyValues | |
| 828 } | |
| 829 | |
| 830 /** | |
| 831 * convert alphanumeric well position to numeric position | |
| 832 * | |
| 833 * @param letter | |
| 834 * @return | |
| 835 */ | |
| 836 def convertLetterToNumber(String letter){ | |
| 837 for (int i = 0; i < LETTERS.size(); i++) { | |
| 838 if (LETTERS.substring(i, i + 1) == letter) { | |
| 839 return i | |
| 840 } | |
| 841 } | |
| 842 return -1 | |
| 843 } | |
| 844 | |
| 845 // Returns a date from a label and a date_pattern | |
| 846 def getDate(String label, Pattern date_pattern){ | |
| 847 // println "Trying to get date from " + label | |
| 848 Matcher date_m = date_pattern.matcher(label) | |
| 849 LocalDateTime dateTime | |
| 850 if (date_m.matches()) { | |
| 851 if (date_m.groupCount() == 5) { | |
| 852 dateTime = LocalDateTime.parse(date_m.group(1) + "-" + date_m.group(2) + "-" + date_m.group(3) + "T" + date_m.group(4) + ":" + date_m.group(5)) | |
| 853 } else { | |
| 854 dateTime = LocalDateTime.parse("1970-01-" + 1 + (date_m.group(1) as int) + "T" + date_m.group(2) + ":" + date_m.group(3)) | |
| 855 } | |
| 856 } | |
| 857 // println "Found " + dateTime | |
| 858 return dateTime | |
| 859 } | |
| 860 | |
| 861 // Returns the number of hours | |
| 862 def getHoursFromImp(ImagePlus imp, ImageStack stack, LocalDateTime dateTime_ref, Pattern date_pattern){ | |
| 863 int currentSlice = imp.getCurrentSlice() | |
| 864 String label = stack.getSliceLabel(currentSlice) | |
| 865 LocalDateTime dateTime = getDate(label, date_pattern) | |
| 866 if (dateTime != null) { | |
| 867 return ChronoUnit.HOURS.between(dateTime_ref, dateTime) as int | |
| 868 } else { | |
| 869 return -1 | |
| 870 } | |
| 871 } | |
| 872 | |
| 873 | |
| 874 /** | |
| 875 * ***************************************************************************************************************** | |
| 876 * ************************************************* Imports **************************************************** | |
| 877 * **************************************************************************************************************** | |
| 878 */ | |
| 879 | |
| 880 | |
| 881 import ij.IJ | |
| 882 import ij.ImagePlus | |
| 883 import ij.ImageStack | |
| 884 import ij.io.FileSaver | |
| 885 import ij.io.Opener | |
| 886 import ij.plugin.Concatenator | |
| 887 import ij.plugin.FolderOpener | |
| 888 import ij.plugin.HyperStackConverter | |
| 889 import ij.process.LUT | |
| 890 | |
| 891 import java.awt.GraphicsEnvironment | |
| 892 import java.io.File | |
| 893 import java.time.format.DateTimeFormatter | |
| 894 import java.time.LocalDateTime | |
| 895 import java.time.temporal.ChronoUnit | |
| 896 import java.util.stream.Collectors | |
| 897 import java.util.stream.IntStream | |
| 898 import java.util.regex.* | |
| 899 | |
| 900 import javax.xml.parsers.DocumentBuilder | |
| 901 import javax.xml.parsers.DocumentBuilderFactory | |
| 902 import javax.xml.transform.OutputKeys | |
| 903 import javax.xml.transform.Result | |
| 904 import javax.xml.transform.Source | |
| 905 import javax.xml.transform.Transformer | |
| 906 import javax.xml.transform.TransformerException | |
| 907 import javax.xml.transform.TransformerFactory | |
| 908 import javax.xml.transform.dom.DOMSource | |
| 909 import javax.xml.transform.stream.StreamResult | |
| 910 | |
| 911 import ome.units.UNITS | |
| 912 import ome.units.quantity.Length | |
| 913 | |
| 914 import ome.xml.model.Channel | |
| 915 import ome.xml.model.Image | |
| 916 import ome.xml.model.MapAnnotation | |
| 917 import ome.xml.model.MapPair | |
| 918 import ome.xml.model.OME | |
| 919 import ome.xml.model.Pixels | |
| 920 import ome.xml.model.Plate | |
| 921 import ome.xml.model.StructuredAnnotations | |
| 922 import ome.xml.model.TiffData | |
| 923 import ome.xml.model.UUID | |
| 924 import ome.xml.model.Well | |
| 925 import ome.xml.model.WellSample | |
| 926 import ome.xml.model.enums.DimensionOrder | |
| 927 import ome.xml.model.enums.NamingConvention | |
| 928 import ome.xml.model.enums.PixelType | |
| 929 import ome.xml.model.primitives.Color | |
| 930 import ome.xml.model.primitives.NonNegativeInteger | |
| 931 import ome.xml.model.primitives.PositiveInteger | |
| 932 import ome.xml.model.primitives.Timestamp | |
| 933 | |
| 934 import org.apache.commons.io.filefilter.WildcardFileFilter | |
| 935 import org.apache.commons.io.FilenameUtils | |
| 936 | |
| 937 import org.w3c.dom.Document | |
| 938 import org.w3c.dom.Element | |
| 939 import org.w3c.dom.Node | |
| 940 import org.w3c.dom.NodeList |
