From 5c39e9d57f5064eeb226ee4e772f54a552e00cae Mon Sep 17 00:00:00 2001 From: Yoshihiro OKUMURA Date: Tue, 12 Jul 2022 09:40:35 +0900 Subject: [PATCH] first commit. --- .gitignore | 74 +++++ README.md | 41 +++ pom.xml | 114 ++++++++ .../tools/BioFormatsImageException.java | 12 + .../neurodata/tools/BioFormatsImageInfo.java | 273 ++++++++++++++++++ 5 files changed, 514 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 pom.xml create mode 100644 src/main/java/jp/riken/neurodata/tools/BioFormatsImageException.java create mode 100644 src/main/java/jp/riken/neurodata/tools/BioFormatsImageInfo.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d342d80 --- /dev/null +++ b/.gitignore @@ -0,0 +1,74 @@ +# Created by https://www.toptal.com/developers/gitignore/api/java,maven,visualstudiocode +# Edit at https://www.toptal.com/developers/gitignore?templates=java,maven,visualstudiocode + +### Java ### +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* +replay_pid* + +### Maven ### +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties +# https://github.com/takari/maven-wrapper#usage-without-binary-jar +.mvn/wrapper/maven-wrapper.jar + +# Eclipse m2e generated files +# Eclipse Core +.project +# JDT-specific (Eclipse Java Development Tools) +.classpath + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history +.ionide + +# Support for Project snippet scope +.vscode/*.code-snippets + +# Ignore code-workspaces +*.code-workspace + +# End of https://www.toptal.com/developers/gitignore/api/java,maven,visualstudiocode diff --git a/README.md b/README.md new file mode 100644 index 0000000..9bed06f --- /dev/null +++ b/README.md @@ -0,0 +1,41 @@ +# BioFormatsImageInfo +Metadata extraction tool based on Bio-Formats + +### Compile +```shell +mvn compile +``` + +### run +```shell +mvn exec:java -Dexec.mainClass=jp.riken.neurodata.tools.BioFormatsImageInfo -Dexec.args="'[path to image file]'" +``` + +### make jar package +```shell +mvn package +``` + +### run by jar +```shell +java -jar target\bioformats-imageinfo-1.0.0.jar [path to image file] +``` + +### run by class +```java +import java.util.LinkedHashMap; +import java.util.Map; +import jp.riken.neurodata.tools.BioFormatsImageInfo; +import jp.riken.neurodata.tools.BioFormatsException; + +String path = "[path to image file]"; +String format = ""; +Map metadata = new LinkedHashMap(); + +try { + format = BioFormatsImageInfo.readMetadata(path, metadata)); +} catch (BioFormatsImageException e) { + // error occured + e.printStackTrace(); +} +``` diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..9ebe57a --- /dev/null +++ b/pom.xml @@ -0,0 +1,114 @@ + + + + 4.0.0 + jp.riken.neurodata.tools.BioFormatsImageInfo + bioformats-imageinfo + 1.0.0 + + bioformats-imageinfo + https://neurodata.riken.jp + + + UTF-8 + 1.8 + 1.8 + + + + + ome + bioformats_package + 6.10.0 + pom + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + 3.3.0 + + + copy-dependencies + prepare-package + + copy-dependencies + + + ${project.build.directory}/libs + + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.2.2 + + + + true + libs/ + jp.riken.neurodata.tools.BioFormatsImageInfo + + + + + + org.apache.maven.plugins + maven-assembly-plugin + 3.4.1 + + + jar-with-dependencies + + + + jp.riken.neurodata.tools.BioFormatsImageInfo + + + + + + make-assembly + package + + single + + + + + + + + + + central + Central Repository + https://repo.maven.apache.org/maven2 + default + + + + + central + Central Repository + https://repo.maven.apache.org/maven2 + + + unidata.releases + https://artifacts.unidata.ucar.edu/content/repositories/unidata-releases + false + + + ome + OME Artifactory + https://artifacts.openmicroscopy.org/artifactory/maven/ + + + + diff --git a/src/main/java/jp/riken/neurodata/tools/BioFormatsImageException.java b/src/main/java/jp/riken/neurodata/tools/BioFormatsImageException.java new file mode 100644 index 0000000..1aee081 --- /dev/null +++ b/src/main/java/jp/riken/neurodata/tools/BioFormatsImageException.java @@ -0,0 +1,12 @@ +package jp.riken.neurodata.tools; + +public class BioFormatsImageException extends Exception { + + public BioFormatsImageException(String message) { + super(message); + } + + public BioFormatsImageException(Throwable cause) { + super(cause.getMessage()); + } +} diff --git a/src/main/java/jp/riken/neurodata/tools/BioFormatsImageInfo.java b/src/main/java/jp/riken/neurodata/tools/BioFormatsImageInfo.java new file mode 100644 index 0000000..f0ee980 --- /dev/null +++ b/src/main/java/jp/riken/neurodata/tools/BioFormatsImageInfo.java @@ -0,0 +1,273 @@ +package jp.riken.neurodata.tools; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Hashtable; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.io.FilenameUtils; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import loci.common.DebugTools; +import loci.formats.FormatException; +import loci.formats.FormatTools; +import loci.formats.IFormatReader; +import loci.formats.ImageReader; +import loci.formats.MetadataTools; +import loci.formats.Modulo; +import loci.formats.meta.MetadataRetrieve; +import loci.formats.meta.MetadataStore; +import loci.formats.tools.ImageInfo; + +public class BioFormatsImageInfo { + + protected static Map readImages(IFormatReader reader) throws FormatException, IOException { + Map metadata = new LinkedHashMap(); + + // read basic metadata + int seriesCount = reader.getSeriesCount(); + metadata.put("count", seriesCount); + MetadataStore ms = reader.getMetadataStore(); + MetadataRetrieve mr = ms instanceof MetadataRetrieve ? (MetadataRetrieve) ms : null; + for (int j = 0; j < seriesCount; j++) { + reader.setSeries(j); + Map seriesMetadata = new LinkedHashMap(); + + // read basic metadata for series #j + int imageCount = reader.getImageCount(); + int resolutionCount = reader.getResolutionCount(); + + // output basic metadata for series #i + String seriesName = mr == null ? null : mr.getImageName(j); + if (seriesName != null) { + seriesMetadata.put("name", seriesName); + } + + // resolutions + if (resolutionCount > 1) { + Map resolutionsMetadata = new LinkedHashMap(); + resolutionsMetadata.put("count", resolutionCount); + for (int i = 0; i < resolutionCount; i++) { + reader.setResolution(i); + resolutionsMetadata.put(String.format("resolution[%d]", i), + makeRectangle(reader.getSizeX(), reader.getSizeY())); + } + seriesMetadata.put("resolutions", resolutionsMetadata); + reader.setResolution(0); + } + + seriesMetadata.put("imageCount", imageCount); + seriesMetadata.put("RGB", new Object[] { reader.isRGB(), reader.getRGBChannelCount() }); + seriesMetadata.put("interleaved", reader.isInterleaved()); + + // indexed + { + List indexed = new ArrayList(); + indexed.add(reader.isIndexed()); + indexed.add(String.format("%b color", !reader.isFalseColor())); + byte[][] table8 = reader.get8BitLookupTable(); + if (table8 != null) { + indexed.add( + String.format("8-bit LUT: %d x %s", table8.length, table8[0] == null ? "null" : "" + table8[0].length)); + } + short[][] table16 = reader.get16BitLookupTable(); + if (table16 != null) { + indexed.add(String.format("16-bit LUT: %d x %s", table16.length, + table16[0] == null ? "null" : "" + table16[0].length)); + } + seriesMetadata.put("indexed", indexed); + } + + seriesMetadata.put("sizeX", reader.getSizeX()); + seriesMetadata.put("sizeY", reader.getSizeY()); + int sizeZ = reader.getSizeZ(); + seriesMetadata.put("sizeZ", makeDimension(sizeZ, sizeZ, reader.getModuloZ())); + seriesMetadata.put("sizeC", makeDimension(reader.getSizeC(), reader.getEffectiveSizeC(), reader.getModuloC())); + int sizeT = reader.getSizeT(); + seriesMetadata.put("sizeT", makeDimension(sizeT, sizeT, reader.getModuloT())); + seriesMetadata.put("tileSize", makeRectangle(reader.getOptimalTileWidth(), reader.getOptimalTileHeight())); + seriesMetadata.put("thumbnailSize", makeRectangle(reader.getThumbSizeX(), reader.getThumbSizeY())); + seriesMetadata.put("endianness", reader.isLittleEndian() ? "little" : "big"); + seriesMetadata.put("dimensionOrder", + new String[] { reader.getDimensionOrder(), reader.isOrderCertain() ? "certain" : "uncertain" }); + seriesMetadata.put("pixelType", FormatTools.getPixelTypeString(reader.getPixelType())); + seriesMetadata.put("validBitsPerPixel", reader.getBitsPerPixel()); + seriesMetadata.put("metadataComplete", reader.isMetadataComplete()); + seriesMetadata.put("thumbnailSeries", reader.isThumbnailSeries()); + + // plane + { + int[] indices; + if (imageCount > 6) { + int q = imageCount / 2; + indices = new int[] { 0, q - 2, q - 1, q, q + 1, q + 2, imageCount - 1 }; + } else if (imageCount > 2) { + indices = new int[] { 0, imageCount / 2, imageCount - 1 }; + } else if (imageCount > 1) { + indices = new int[] { 0, 1 }; + } else { + indices = new int[] { 0 }; + } + int[][] zct = new int[indices.length][]; + int[] indices2 = new int[indices.length]; + for (int i = 0; i < indices.length; i++) { + zct[i] = reader.getZCTCoords(indices[i]); + indices2[i] = reader.getIndex(zct[i][0], zct[i][1], zct[i][2]); + Map planeMetadata = new LinkedHashMap(); + planeMetadata.put("Z", zct[i][0]); + planeMetadata.put("C", zct[i][1]); + planeMetadata.put("T", zct[i][2]); + if (indices[i] != indices2[i]) { + planeMetadata.put("[mismatch]", indices2[i]); + } + seriesMetadata.put(String.format("plane[%d]", indices[i]), planeMetadata); + } + } + metadata.put(String.format("series[%d]", j), seriesMetadata); + } + + return metadata; + } + + protected static Map readAnnotations(IFormatReader reader) { + Map metadata = new LinkedHashMap(); + // global metadata + Hashtable globalMetadata = reader.getGlobalMetadata(); + for (String key : MetadataTools.keys(globalMetadata)) { + metadata.put(key, globalMetadata.get(key)); + } + // original metadata + int seriesCount = reader.getSeriesCount(); + for (int j = 0; j < seriesCount; j++) { + reader.setSeries(j); + Hashtable seriesMagedata = reader.getSeriesMetadata(); + if (!seriesMagedata.isEmpty()) { + Map seriesMetadata = new LinkedHashMap(); + String[] keys = MetadataTools.keys(seriesMagedata); + for (int i = 0; i < keys.length; i++) { + seriesMetadata.put(keys[i], seriesMagedata.get(keys[i])); + } + metadata.put(String.format("series[%d]", j), seriesMetadata); + } + } + + return metadata; + } + + protected static Object makeDimension(int size, int effSize, Modulo modulo) { + int mLength = modulo.length(); + if (size == effSize && mLength == 1) { + return size; + } + List ret = new ArrayList(); + ret.add(size); + if (size != effSize) { + ret.add(String.format("(effectively %d)", effSize)); + } + if (mLength != 1) { + ret.add(String.format("(%d %s x %d %s)", size / mLength, modulo.parentType, mLength, modulo.type)); + } + + return ret; + } + + protected static LinkedHashMap makeRectangle(int width, int height) { + LinkedHashMap ret = new LinkedHashMap(); + ret.put("width", width); + ret.put("height", height); + return ret; + } + + protected static String getJsonString(Map map) { + ObjectMapper mapper = new ObjectMapper(); + String json = null; + try { + json = mapper.writeValueAsString(map); + // json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(map); + } catch (Exception e) { + // return "null" if conversion error occured + json = "null"; + } + + return json; + } + + public static String readMetadata(String path, Map metadata) throws BioFormatsImageException { + if (FilenameUtils.getExtension(path).toLowerCase().equals("zip")) { + // ignore if file is zip archive + throw new BioFormatsImageException("Zip is not supported"); + } + String format = ""; + ImageInfo info = new ImageInfo(); + IFormatReader reader = new ImageReader(); + try { + // info.parseArgs(new String[] { path, "-nopix", "-noflat", "-omexml" }); + info.parseArgs(new String[] { path, "-nopix", "-noflat" }); + info.setReader(reader); + info.mapLocation(); + info.configureReaderPreInit(); + + // initialize reader + reader.setId(path); + format = reader.getFormat(); + info.configureReaderPostInit(); + info.checkWarnings(); + // info.printOMEXML(); + // info.readCoreMetadata(); + metadata.put("images", readImages(reader)); + info.initPreMinMaxValues(); + reader.setSeries(0); + reader.setResolution(0); + // info.printGlobalMetadata(); + // info.printOriginalMetadata(); + metadata.put("annotations", readAnnotations(reader)); + } catch (Exception e) { + throw new BioFormatsImageException(e); + } finally { + if (reader != null) { + try { + reader.close(); + } catch (IOException e) { + throw new BioFormatsImageException(e); + } + } + } + + return format; + } + + // -- Main method -- + public static void main(String[] args) { + boolean status = true; + String message = ""; + String format = ""; + Map metadata = new LinkedHashMap(); + if (args.length != 1) { + status = false; + message = "filename argument required"; + } else { + try { + DebugTools.enableLogging("ERROR"); + format = readMetadata(args[0], metadata); + } catch (BioFormatsImageException e) { + status = false; + metadata.clear(); + message = e.getMessage(); + } + } + + Map results = new LinkedHashMap(); + results.put("status", status); + results.put("message", message); + results.put("format", format); + results.put("metadata", metadata.isEmpty() ? null : metadata); + System.out.println(getJsonString(results)); + + if (!status) { + System.exit(1); + } + } +}