diff --git a/README.md b/README.md index 749d68f..613870e 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ -# Hatch 3.1.2 - +# Hatch 3.2.0 This tool converts the largest image in a VSI, SVS, or TIF image into a new TIFF image with a freshly created image pyramid with each scaling 1/2 dimensions each scale. diff --git a/dependency-reduced-pom.xml b/dependency-reduced-pom.xml index 8b96ffb..db9ca07 100644 --- a/dependency-reduced-pom.xml +++ b/dependency-reduced-pom.xml @@ -3,7 +3,7 @@ 4.0.0 edu.stonybrook.bmi hatch - 3.1.2 + 3.2.0 @@ -45,7 +45,7 @@ maven-shade-plugin - 3.4.1 + 3.5.1 package @@ -87,7 +87,7 @@ org.graalvm.buildtools native-maven-plugin - 0.9.27 + 0.10.0 true @@ -130,6 +130,7 @@ 21 + 3.10.1 21 UTF-8 7.0.0 diff --git a/pom.xml b/pom.xml index 9f35775..dba3479 100644 --- a/pom.xml +++ b/pom.xml @@ -3,13 +3,14 @@ 4.0.0 edu.stonybrook.bmi hatch - 3.1.2 + 3.2.0 jar UTF-8 21 21 7.0.0 + 3.10.1 @@ -29,6 +30,11 @@ jcommander 1.82 + + com.twelvemonkeys.imageio + imageio-tiff + ${twelvemonkeys.version} + @@ -87,7 +93,7 @@ org.apache.maven.plugins maven-shade-plugin - 3.4.1 + 3.5.1 false @@ -129,7 +135,7 @@ org.graalvm.buildtools native-maven-plugin - 0.9.27 + 0.10.0 true diff --git a/src/main/java/edu/stonybrook/bmi/hatch/Hatch.java b/src/main/java/edu/stonybrook/bmi/hatch/Hatch.java index 91cfd09..c9419a7 100644 --- a/src/main/java/edu/stonybrook/bmi/hatch/Hatch.java +++ b/src/main/java/edu/stonybrook/bmi/hatch/Hatch.java @@ -12,6 +12,7 @@ import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.logging.Level; +import java.util.logging.LogManager; import java.util.logging.Logger; import java.util.stream.Stream; @@ -20,14 +21,29 @@ * @author erich */ public class Hatch { - public static String software = "hatch 3.1.2 by Wing-n-Beak"; + public static String software = "hatch 3.2.0 by Wing-n-Beak"; private static final String[] ext = new String[] {".vsi", ".svs", ".tif"}; + private static final String errorlog = "error.log"; + private static Logger LOGGER; public static final String HELP = Hatch.software+"\n"+ """ usage: hatch -v : verbose """; + public Hatch() { + LOGGER = Logger.getLogger(Hatch.class.getName()); + } + + static { + try { + LogManager.getLogManager().readConfiguration(Hatch.class.getResourceAsStream("/logging.properties")); + } catch (IOException | SecurityException | ExceptionInInitializerError ex) { + Logger.getLogger(Hatch.class.getName()).log(Level.SEVERE, "Failed to read logging.properties file", ex); + } + LOGGER = Logger.getLogger(Hatch.class.getName()); + } + private static void Traverse(HatchParameters params) { Path s = params.src.toPath(); Path d = params.dest.toPath(); @@ -48,28 +64,23 @@ private static void Traverse(HatchParameters params) { String frag = s.relativize(f).toString(); frag = frag.substring(0,frag.length()-4)+".tif"; Path t = Path.of(d.toString(), frag); - if (!t.toFile().exists()) { - System.out.println("PROCESSING FILE --> "+f); - engine.submit(new FileProcessor(params, f, t)); - } else if (t.toFile().length()==0) { - System.out.println("ZERO LENGTH FILE --> "+f); + if (t.toFile().exists()&¶ms.overwrite) { t.toFile().delete(); - System.out.println("RE-PROCESSING FILE --> "+f); - engine.submit(new FileProcessor(params, f, t)); } + engine.submit(new FileProcessor(params, f, t)); }); - } catch (IOException ex) { - Logger.getLogger(Hatch.class.getName()).log(Level.SEVERE, null, ex); + } catch (IOException ex) { + LOGGER.severe("FILE PROCESSOR ERROR --> "+params.src.toString()+" "+params.dest.toString()+" "+ex.toString()); } int cc = engine.getActiveCount()+engine.getQueue().size(); while ((engine.getActiveCount()+engine.getQueue().size())>0) { int curr = engine.getActiveCount()+engine.getQueue().size(); if (cc!=curr) { cc=curr; - if (params.verbose) System.out.println("All jobs submitted...waiting for "+curr); + if (params.verbose) LOGGER.log(Level.INFO,"All jobs submitted...waiting for "+curr); } } - System.out.println("Engine shutdown"); + LOGGER.info("Engine shutdown"); engine.shutdown(); } @@ -79,13 +90,17 @@ private static String getFileNameBase(File file) { } public static void main(String[] args) { + LOGGER.setLevel(Level.SEVERE); loci.common.DebugTools.setRootLevel("WARN"); HatchParameters params = new HatchParameters(); JCommander jc = JCommander.newBuilder().addObject(params).build(); jc.setProgramName(Hatch.software+"\nhatch"); try { jc.parse(args); - System.out.println(params); + LOGGER.log(Level.INFO,params.toString()); + if (params.verbose) { + LOGGER.setLevel(Level.INFO); + } if (params.isHelp()) { jc.usage(); System.exit(0); @@ -118,7 +133,7 @@ public static void main(String[] args) { try (X2TIF v2t = new X2TIF(params, params.src.toString(), params.dest.toString(), series)){ v2t.Execute(); } catch (Exception ex) { - Logger.getLogger(Hatch.class.getName()).log(Level.SEVERE, null, ex); + LOGGER.severe("FILE PROCESSOR ERROR --> "+params.src.toString()+" "+params.dest.toString()+" "+ex.toString()); } } } else { @@ -128,7 +143,7 @@ public static void main(String[] args) { try (X2TIF v2t = new X2TIF(params, params.src.toString(), dest.toString(), null);) { v2t.Execute(); } catch (Exception ex) { - Logger.getLogger(Hatch.class.getName()).log(Level.SEVERE, null, ex); + LOGGER.severe("FILE PROCESSOR ERROR --> "+params.src.toString()+" "+params.dest.toString()+" "+ex.toString()); } } else { params.series.forEach(s->{ @@ -136,7 +151,7 @@ public static void main(String[] args) { try (X2TIF v2t = new X2TIF(params, params.src.toString(), dest.toString(), Integer.valueOf(s));) { v2t.Execute(); } catch (Exception ex) { - Logger.getLogger(Hatch.class.getName()).log(Level.SEVERE, null, ex); + LOGGER.severe("FILE PROCESSOR ERROR --> "+params.src.toString()+" "+params.dest.toString()+" "+ex.toString()); } }); } @@ -147,15 +162,15 @@ public static void main(String[] args) { try (X2TIF v2t = new X2TIF(params, params.src.toString(), params.dest.toString(), null);) { v2t.Execute(); } catch (Exception ex) { - Logger.getLogger(Hatch.class.getName()).log(Level.SEVERE, null, ex); + LOGGER.severe("FILE PROCESSOR ERROR --> "+params.src.toString()+" "+params.dest.toString()+" "+ex.toString()); } } } } else { - System.out.println(params.src.toString()+" does not exist!"); + LOGGER.severe(params.src.toString()+" does not exist!"); } } catch (ParameterException ex) { - System.out.println(ex.getMessage()); + LOGGER.severe("FILE PROCESSOR ERROR --> "+params.src.toString()+" "+params.dest.toString()+" "+ex.toString()); } } } @@ -164,25 +179,40 @@ class FileProcessor implements Callable { private final HatchParameters params; private final File src; private final File dest; + private static Logger LOGGER; public FileProcessor(HatchParameters params, Path src, Path dest) { this.params = params; this.src = src.toFile(); this.dest = dest.toFile(); + LOGGER = Logger.getLogger(Hatch.class.getName()); } @Override public String call() { if (dest.exists()) { - dest.delete(); - } else { - dest.getParentFile().mkdirs(); + if (params.overwrite) { + dest.delete(); + } else { + if (params.retry) { + if (!Validate2.file(dest.toPath())) { + dest.delete(); + } + } else { + if (params.validate) { + Validate2.file(dest.toPath()); + } + return null; + } + } } + dest.getParentFile().mkdirs(); try (X2TIF v2t = new X2TIF(params, src.toString(), dest.toString(), null)) { v2t.Execute(); } catch (Exception ex) { - System.out.println("FILE PROCESSOR ERROR --> "+src+" "+dest+" "+ex.toString()); - } + LOGGER.severe("FILE PROCESSOR ERROR --> "+src+" "+dest+" "+ex.toString()); + } + Validate2.file(dest.toPath()); return null; } } diff --git a/src/main/java/edu/stonybrook/bmi/hatch/HatchParameters.java b/src/main/java/edu/stonybrook/bmi/hatch/HatchParameters.java index e54d9cf..2b21ce1 100644 --- a/src/main/java/edu/stonybrook/bmi/hatch/HatchParameters.java +++ b/src/main/java/edu/stonybrook/bmi/hatch/HatchParameters.java @@ -29,6 +29,15 @@ public boolean isHelp() { @Parameter(names = {"-v","-verbose"}) public boolean verbose = false; + @Parameter(names = {"-o","-overwrite"}) + public boolean overwrite = false; + + @Parameter(names = {"-r","-retry"}) + public boolean retry = false; + + @Parameter(names = {"-validate"}) + public boolean validate = false; + @Parameter(names = "-jp2", hidden = true) public boolean jp2 = false; diff --git a/src/main/java/edu/stonybrook/bmi/hatch/SingleLineFormatter.java b/src/main/java/edu/stonybrook/bmi/hatch/SingleLineFormatter.java new file mode 100644 index 0000000..77ddb82 --- /dev/null +++ b/src/main/java/edu/stonybrook/bmi/hatch/SingleLineFormatter.java @@ -0,0 +1,17 @@ +package edu.stonybrook.bmi.hatch; + +import java.util.logging.Formatter; +import java.util.logging.LogRecord; + +public class SingleLineFormatter extends Formatter { + + @Override + public String format(LogRecord record) { + return String.format("%1$tF %1$tT %2$s %3$s: %4$s %n", + record.getMillis(), + record.getLevel().getLocalizedName(), + record.getSourceClassName() + "." + record.getSourceMethodName(), + formatMessage(record)); + } +} + diff --git a/src/main/java/edu/stonybrook/bmi/hatch/Validate2.java b/src/main/java/edu/stonybrook/bmi/hatch/Validate2.java new file mode 100644 index 0000000..aaba115 --- /dev/null +++ b/src/main/java/edu/stonybrook/bmi/hatch/Validate2.java @@ -0,0 +1,74 @@ +package edu.stonybrook.bmi.hatch; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Iterator; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.imageio.ImageIO; +import javax.imageio.stream.ImageInputStream; + +/** + * + * @author erich + */ +public class Validate2 { + + public static boolean file(Path path) { + Logger LOGGER = Logger.getLogger(Validate2.class.getName()); + javax.imageio.ImageReader reader = null; + ImageInputStream input; + try { + input = ImageIO.createImageInputStream(path.toFile()); + Iterator readers = ImageIO.getImageReadersByFormatName("tif"); + javax.imageio.ImageReader ir = null; + while (readers.hasNext()) { + ir = readers.next(); + //System.out.println("IMAGE CLASS --> "+ir.getClass().getCanonicalName()); + if ("com.twelvemonkeys.imageio.plugins.tiff.TIFFImageReader".equals(ir.getClass().getCanonicalName())) { + //System.out.println("YES! : "+ir.getClass().getCanonicalName()); + reader = ir; + } else { + //System.out.println("NOPE : "+ir.getClass().getCanonicalName()); + } + } + if (ir==null) { + throw new IllegalArgumentException("No reader for: " + path); + } + //System.out.println("READER IS ---> "+reader.getClass().getCanonicalName()); + reader.setInput(input); + try { + if (reader.getWidth(0)==0) { + LOGGER.severe(path.toString()+" X dimension is ZERO"); + return false; + } + } catch (IOException ex) { + Logger.getLogger(Validate2.class.getName()).log(Level.SEVERE, null, ex); + return false; + } + try { + if (reader.getHeight(0)==0) { + LOGGER.severe(path.toString()+" Y dimension is ZERO"); + return false; + } + } catch (IOException ex) { + LOGGER.severe(ex.getMessage()); + return false; + } + try { + int last = reader.getNumImages(true)-1; + if ((reader.getWidth(last)>1024)||(reader.getHeight(last)>1024)) { + LOGGER.severe(path.toString()+" smallest scaled image ("+reader.getNumImages(true)+" - "+reader.getWidth(last)+"x"+reader.getHeight(last)+") must be less than 1024x1024"); + return false; + } + } catch (IOException ex) { + LOGGER.severe(ex.getMessage()); + return false; + } + } catch (IOException ex) { + LOGGER.severe(ex.getMessage()); + return false; + } + return true; + } +} diff --git a/src/main/java/edu/stonybrook/bmi/hatch/X2TIF.java b/src/main/java/edu/stonybrook/bmi/hatch/X2TIF.java index 35c4725..85ffb26 100644 --- a/src/main/java/edu/stonybrook/bmi/hatch/X2TIF.java +++ b/src/main/java/edu/stonybrook/bmi/hatch/X2TIF.java @@ -1,7 +1,6 @@ package edu.stonybrook.bmi.hatch; import java.awt.image.BufferedImage; -import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileNotFoundException; @@ -27,8 +26,6 @@ import loci.formats.tiff.IFD; import loci.formats.tiff.PhotoInterp; import loci.formats.tiff.TiffRational; -import ome.codecs.CodecException; -import ome.codecs.JPEG2000Codec; import ome.units.UNITS; import ome.units.quantity.Length; import ome.xml.model.enums.DimensionOrder; @@ -60,14 +57,16 @@ public class X2TIF implements AutoCloseable { private HatchWriter writer; private IMetadata meta; private byte compression; + private static Logger LOGGER; public X2TIF(HatchParameters params, String src, String dest, Integer series) { + LOGGER = Logger.getLogger(X2TIF.class.getName()); time = new StopWatch(); inputFile = src; outputFile = dest; this.params = params; if (params.verbose) { - System.out.println("initializing..."); + LOGGER.log(Level.INFO,"initializing..."); } try { ServiceFactory factory = new ServiceFactory(); @@ -95,13 +94,13 @@ public X2TIF(HatchParameters params, String src, String dest, Integer series) { maximage = series; } if ((series!=null)&&((series<0)||(series>reader.getSeriesCount()))) { - System.out.println("Series doesn't exist : "+src+" --> "+series); + LOGGER.log(Level.INFO,"Series doesn't exist : "+src+" --> "+series); System.exit(0); } try { writer = new HatchWriter(dest); } catch (IOException ex) { - Logger.getLogger(X2TIF.class.getName()).log(Level.SEVERE, null, ex); + LOGGER.log(Level.SEVERE, "FILE PROCESSOR ERROR --> "+params.src+" "+params.dest+" "+ex.toString()); } reader.setSeries(maximage); tileSizeX = reader.getOptimalTileWidth(); @@ -113,16 +112,16 @@ public X2TIF(HatchParameters params, String src, String dest, Integer series) { ppy = retrieve.getPixelsPhysicalSizeY(maximage); SetPPS(); if (params.verbose) { - System.out.println("Image Size : "+width+"x"+height); - System.out.println("Tile size : "+tileSizeX+"x"+tileSizeY); - System.out.println("Compression : "+reader.metadata.get("Compression")); + LOGGER.log(Level.INFO,"Image Size : "+width+"x"+height); + LOGGER.log(Level.INFO,"Tile size : "+tileSizeX+"x"+tileSizeY); + LOGGER.log(Level.INFO,"Compression : "+reader.metadata.get("Compression")); } //String xml = service.getOMEXML(omexml); - //System.out.println(xml); + //Systsm.out.println(xml); try { if (reader.metadata.get("Compression")==null) { if (params.verbose) { - System.out.println("NULL compression specified...trying JPEG...no promises..."); + LOGGER.log(Level.INFO,"NULL compression specified...trying JPEG...no promises..."); } } else if ((reader.metadata.get("Compression")=="JPEG-2000")&¶ms.jp2) { @@ -130,7 +129,7 @@ public X2TIF(HatchParameters params, String src, String dest, Integer series) { throw new Error("Hatch can only convert images that have JPEG compression."); } } catch (Error e){ - System.out.println(X2TIF.class.getName()+" : "+e.getLocalizedMessage()); + LOGGER.log(Level.SEVERE, e.getLocalizedMessage()+" : "+src+" "+dest); System.exit(0); } int size = Math.max(width, height); @@ -139,7 +138,7 @@ public X2TIF(HatchParameters params, String src, String dest, Integer series) { depth = ss-tiless+2; TileSize = reader.getOptimalTileHeight() * reader.getOptimalTileWidth() * 24; if (params.verbose) { - System.out.println("# of scales to be generated : "+depth); + LOGGER.log(Level.INFO,"# of scales to be generated : "+depth); } meta = service.createOMEXMLMetadata(); meta.setImageID("Image:0", 0); @@ -157,8 +156,14 @@ public X2TIF(HatchParameters params, String src, String dest, Integer series) { meta.setPixelsSizeZ(new PositiveInteger(1), 0); meta.setPixelsSizeC(new PositiveInteger(3), 0); meta.setPixelsSizeT(new PositiveInteger(1), 0); - } catch (DependencyException | ServiceException | FormatException | IOException ex) { - Logger.getLogger(X2TIF.class.getName()).log(Level.SEVERE, null, ex); + } catch (DependencyException ex) { + LOGGER.log(Level.SEVERE, "DependencyException : "+src+" "+dest); + } catch (ServiceException ex) { + LOGGER.log(Level.SEVERE, "ServiceException : "+src+" "+dest); + } catch (FormatException ex) { + LOGGER.log(Level.SEVERE, "FormatException : "+src+" "+dest); + } catch (IOException ex) { + LOGGER.log(Level.SEVERE, "IOException : "+src+" "+dest); } } @@ -210,9 +215,9 @@ public void Dump2File3(byte[] buffer, int a, int b) { fos.flush(); } } catch (FileNotFoundException ex) { - Logger.getLogger(Pyramid.class.getName()).log(Level.SEVERE, null, ex); + LOGGER.log(Level.SEVERE, "FILE PROCESSOR ERROR --> "+params.src+" "+params.dest+" "+ex.toString()); } catch (IOException ex) { - Logger.getLogger(Pyramid.class.getName()).log(Level.SEVERE, null, ex); + LOGGER.log(Level.SEVERE, "FILE PROCESSOR ERROR --> "+params.src+" "+params.dest+" "+ex.toString()); } } @@ -234,14 +239,14 @@ public void DumpBI2File3(BufferedImage bi, int a, int b) { try { jpgWriter.write(null, outputImage, param); } catch (IOException ex) { - Logger.getLogger(NeoJPEGCodec.class.getName()).log(Level.SEVERE, null, ex); + LOGGER.log(Level.SEVERE, "FILE PROCESSOR ERROR --> "+params.src+" "+params.dest+" "+ex.toString()); } jpgWriter.dispose(); Files.write(f.toPath(), baos.toByteArray()); } catch (FileNotFoundException ex) { - Logger.getLogger(Pyramid.class.getName()).log(Level.SEVERE, null, ex); + LOGGER.log(Level.SEVERE, "FILE PROCESSOR ERROR --> "+params.src+" "+params.dest+" "+ex.toString()); } catch (IOException ex) { - Logger.getLogger(Pyramid.class.getName()).log(Level.SEVERE, null, ex); + LOGGER.log(Level.SEVERE, "FILE PROCESSOR ERROR --> "+params.src+" "+params.dest+" "+ex.toString()); } } @@ -255,7 +260,7 @@ public static void Display(byte[] buffer, int from, int to) { public void readWriteTiles() throws FormatException, IOException { if (params.verbose) { - System.out.println("transferring image data..."); + LOGGER.log(Level.INFO,"transferring image data..."); } reader.setSeries(maximage); int nXTiles = width / tileSizeX; @@ -319,7 +324,7 @@ public void readWriteTiles() throws FormatException, IOException { for (int y=0; y "+params.src+" "+params.dest+" "+ex.toString()); } return bb; } - public void Execute() { - try { - readWriteTiles(); - time.Cumulative(); - } catch(IOException | FormatException e) { - System.err.println(e.toString()); - } + public void Execute() throws FormatException, IOException { + readWriteTiles(); + time.Cumulative(); } @Override diff --git a/src/main/resources/logging.properties b/src/main/resources/logging.properties new file mode 100644 index 0000000..b8c2054 --- /dev/null +++ b/src/main/resources/logging.properties @@ -0,0 +1,15 @@ +# Default global logging level +.level=INFO + +# ConsoleHandler +handlers=java.util.logging.ConsoleHandler, java.util.logging.FileHandler +java.util.logging.ConsoleHandler.level=FINE +java.util.logging.ConsoleHandler.formatter = edu.stonybrook.bmi.hatch.SingleLineFormatter + +edu.stonybrook.bmi.hatch.Hatch.level=FINE + +java.util.logging.FileHandler.pattern=./error.log +java.util.logging.FileHandler.limit=0 +java.util.logging.FileHandler.count=1 +java.util.logging.FileHandler.formatter=edu.stonybrook.bmi.hatch.SingleLineFormatter +java.util.logging.FileHandler.level=SEVERE