Commit 42ed650b authored by Andrey Filippov's avatar Andrey Filippov

Merge branch 'lwir' of git.elphel.com:Elphel/imagej-elphel into lwir

parents ed9cb86c c7b6db5e
...@@ -90,6 +90,12 @@ ...@@ -90,6 +90,12 @@
<artifactId>loci_tools</artifactId> <artifactId>loci_tools</artifactId>
<version>6.0.1</version> <version>6.0.1</version>
</dependency> </dependency>
<dependency>
<groupId>com.drewnoakes</groupId>
<artifactId>metadata-extractor</artifactId>
<version>2.11.0</version>
<type>java-source</type>
</dependency>
</dependencies> </dependencies>
<build> <build>
......
...@@ -70,6 +70,8 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -70,6 +70,8 @@ import java.util.concurrent.atomic.AtomicInteger;
import javax.swing.JFileChooser; import javax.swing.JFileChooser;
import javax.swing.filechooser.FileFilter; import javax.swing.filechooser.FileFilter;
import com.elphel.imagej.lwir.LwirReader;
import ij.CompositeImage; import ij.CompositeImage;
import ij.IJ; import ij.IJ;
import ij.ImageJ; import ij.ImageJ;
...@@ -118,7 +120,8 @@ private Panel panel1, ...@@ -118,7 +120,8 @@ private Panel panel1,
panelClt3, panelClt3,
panelClt4, panelClt4,
panelClt5, panelClt5,
panelClt_GPU panelClt_GPU,
panelLWIR
; ;
JP46_Reader_camera JP4_INSTANCE=null; JP46_Reader_camera JP4_INSTANCE=null;
...@@ -384,6 +387,7 @@ private Panel panel1, ...@@ -384,6 +387,7 @@ private Panel panel1,
public static boolean DCT_MODE=false; //true; // show all buttons public static boolean DCT_MODE=false; //true; // show all buttons
public static boolean MODE_3D=false; // 3D-related commands public static boolean MODE_3D=false; // 3D-related commands
public static boolean GPU_MODE=false; // 3D-related commands public static boolean GPU_MODE=false; // 3D-related commands
public static boolean LWIR_MODE = false; // LWIR-related commands
public PixelMapping.InterSensor.DisparityTiles DISPARITY_TILES=null; public PixelMapping.InterSensor.DisparityTiles DISPARITY_TILES=null;
public ImagePlus DBG_IMP = null; public ImagePlus DBG_IMP = null;
public ImagePlus CORRELATE_IMP = null; public ImagePlus CORRELATE_IMP = null;
...@@ -420,7 +424,7 @@ private Panel panel1, ...@@ -420,7 +424,7 @@ private Panel panel1,
instance = this; instance = this;
addKeyListener(IJ.getInstance()); addKeyListener(IJ.getInstance());
int menuRows=4 + (ADVANCED_MODE?4:0) + (MODE_3D?3:0) + (DCT_MODE?6:0) + (GPU_MODE?1:0); int menuRows=4 + (ADVANCED_MODE?4:0) + (MODE_3D?3:0) + (DCT_MODE?6:0) + (GPU_MODE?1:0) +(LWIR_MODE?1:0);
setLayout(new GridLayout(menuRows, 1)); setLayout(new GridLayout(menuRows, 1));
panel6 = new Panel(); panel6 = new Panel();
...@@ -645,7 +649,13 @@ private Panel panel1, ...@@ -645,7 +649,13 @@ private Panel panel1,
addButton("ShowGPU", panelClt_GPU, color_conf_process); addButton("ShowGPU", panelClt_GPU, color_conf_process);
add(panelClt_GPU); add(panelClt_GPU);
} }
if (LWIR_MODE) {
panelLWIR = new Panel();
panelLWIR.setLayout(new GridLayout(1, 0, 5, 5)); // rows, columns, vgap, hgap
addButton("LWIR_ACQUIRE", panelLWIR, color_conf_process);
add(panelLWIR);
}
//
pack(); pack();
GUI.center(this); GUI.center(this);
...@@ -740,6 +750,15 @@ private Panel panel1, ...@@ -740,6 +750,15 @@ private Panel panel1,
IJ.showMessage("Error",msg); IJ.showMessage("Error",msg);
throw new IOException (msg); throw new IOException (msg);
} }
sValue= this.prefsProperties.getProperty("LWIR_MODE");
if (sValue!=null) {
LWIR_MODE=Boolean.parseBoolean(sValue);
System.out.println("Read LWIR_MODE="+LWIR_MODE);
} else {
String msg="LWIR_MODE is undefined in "+this.prefsPath;
IJ.showMessage("Error",msg);
throw new IOException (msg);
}
} }
public static String stack2string(Exception e) { public static String stack2string(Exception e) {
...@@ -4783,6 +4802,29 @@ private Panel panel1, ...@@ -4783,6 +4802,29 @@ private Panel panel1,
} }
TENSORFLOW_INFER_MODEL.test_tensorflow(keep_empty); TENSORFLOW_INFER_MODEL.test_tensorflow(keep_empty);
return; return;
/* ======================================================================== */
} else if (label.equals("LWIR_ACQUIRE")) {
DEBUG_LEVEL=MASTER_DEBUG_LEVEL;
loci.common.DebugTools.enableLogging("ERROR"); // INFO"); // ERROR");
LwirReader lwirReader = new LwirReader();
ImagePlus [][] imps = lwirReader.readAllMultiple(
10, // final int num_frames,
true, // final boolean show,
false); // true); // final boolean scale)
for (ImagePlus imp: imps[0]) {
imp.show();
}
System.out.println("LWIR_ACQUIRE: got "+imps.length+" image sets");
ImagePlus [][] imps_sync = lwirReader.matchSets(imps, 0.001, 3); // double max_mismatch)
if (imps_sync != null) {
ImagePlus [] imps_avg = lwirReader.averageMultiFrames(imps_sync);
for (ImagePlus imp: imps_avg) {
imp.show();
}
}
//JTabbedTest //JTabbedTest
// End of buttons code // End of buttons code
} }
......
...@@ -1133,34 +1133,7 @@ public class JP46_Reader_camera extends PlugInFrame implements ActionListener { ...@@ -1133,34 +1133,7 @@ public class JP46_Reader_camera extends PlugInFrame implements ActionListener {
fi.fileFormat = isTiff?FileInfo.TIFF: FileInfo.GIF_OR_JPG; // even if set originally, it is lost after convertToGray32 fi.fileFormat = isTiff?FileInfo.TIFF: FileInfo.GIF_OR_JPG; // even if set originally, it is lost after convertToGray32
imp.setFileInfo(fi); imp.setFileInfo(fi);
FileInfo ofi = imp.getOriginalFileInfo();
fi = imp.getFileInfo();
// testing
/*
if ((ofi!=null) && (ofi.directory!=null) && (ofi.fileFormat == FileInfo.TIFF)) {
String path = ofi.directory + ofi.fileName;
EyesisTiff ET = new EyesisTiff();
ImagePlus imptiff = ET.readTiff(path);
if (imptiff!=null) {
imptiff.show();
}
// IJ.error("TIFF Dumper", "File path not available or not TIFF file");
IJ.log("\\Clear");
IJ.log("PATH = "+path);
try {
dumpIFDs(path);
} catch(IOException e) {
IJ.error("Tiff Dumper", ""+e);
}
Frame log = WindowManager.getFrame("Log");
if (log!=null) log.toFront();
}
*/
} }
return imp; return imp;
} }
...@@ -1421,16 +1394,11 @@ public class JP46_Reader_camera extends PlugInFrame implements ActionListener { ...@@ -1421,16 +1394,11 @@ public class JP46_Reader_camera extends PlugInFrame implements ActionListener {
timestamp=imp.getProperty("timestamp"); timestamp=imp.getProperty("timestamp");
if (timestamp!=null); if (timestamp!=null);
/*
System.out.println("FLIPV="+FLIPGV+" FLIPH="+FLIPGH);
for (i=0;i<3;i++) System.out.println("FLIPV["+i+"]= "+FLIPV[i]+" FLIPH["+i+"]= "+FLIPH[i]);
for (i=0;i<3;i++) System.out.println("HEIGHTS["+i+"]="+HEIGHTS[i]);
for (i=0;i<2;i++) System.out.println("BLANKS["+i+"]= "+BLANKS[i]);
*/
Rectangle [] r = new Rectangle[3]; Rectangle [] r = new Rectangle[3];
r[0]=new Rectangle(0, 0, width,HEIGHTS[0]); r[0]=new Rectangle(0, 0, width,HEIGHTS[0]);
r[1]=new Rectangle(0, HEIGHTS[0]+BLANKS[0], width,HEIGHTS[1]); r[1]=new Rectangle(0, HEIGHTS[0]+BLANKS[0], width,HEIGHTS[1]);
r[2]=new Rectangle(0, HEIGHTS[0]+BLANKS[0]+HEIGHTS[1]+BLANKS[1],width,HEIGHTS[2]); r[2]=new Rectangle(0, HEIGHTS[0]+BLANKS[0]+HEIGHTS[1]+BLANKS[1],width,HEIGHTS[2]);
// assuming that (HEIGHTS[1]==0) && (HEIGHTS[2]!=0) == false // assuming that (HEIGHTS[1]==0) && (HEIGHTS[2]!=0) == false
if (FLIPGV!=0){ if (FLIPGV!=0){
if (HEIGHTS[1]>0) { if (HEIGHTS[1]>0) {
...@@ -1448,13 +1416,10 @@ public class JP46_Reader_camera extends PlugInFrame implements ActionListener { ...@@ -1448,13 +1416,10 @@ public class JP46_Reader_camera extends PlugInFrame implements ActionListener {
if (FLIPGV>0) for (i=0;i<3;i++) FLIPV[i]=1-FLIPV[i]; if (FLIPGV>0) for (i=0;i<3;i++) FLIPV[i]=1-FLIPV[i];
if (FLIPGH>0) for (i=0;i<3;i++) FLIPH[i]=1-FLIPH[i]; if (FLIPGH>0) for (i=0;i<3;i++) FLIPH[i]=1-FLIPH[i];
// for (i=0;i<3;i++) System.out.println("Final: FLIPV["+i+"]= "+FLIPV[i]+" FLIPH["+i+"]= "+FLIPH[i]);
// if needed, we'll cut one pixel line. later can modify to add one extra, but then we need to duplicate the pre-last one (same Bayer), // if needed, we'll cut one pixel line. later can modify to add one extra, but then we need to duplicate the pre-last one (same Bayer),
// not just add zeros - later before sliding FHT the two border lines are repeated for 16 times to reduce border effects. // not just add zeros - later before sliding FHT the two border lines are repeated for 16 times to reduce border effects.
for (i=0;i<3;i++) { for (i=0;i<3;i++) {
// System.out.println("before r["+i+"].x= "+r[i].x+" r["+i+"].width= "+r[i].width);
// System.out.println("before r["+i+"].y= "+r[i].y+" r["+i+"].height= "+r[i].height);
if (((r[i].height & 1)==0 ) & (((r[i].y+FLIPV[i])&1)!=0)) r[i].height-=2; if (((r[i].height & 1)==0 ) & (((r[i].y+FLIPV[i])&1)!=0)) r[i].height-=2;
r[i].height &=~1; r[i].height &=~1;
if (((r[i].y+FLIPV[i])&1)!=0) r[i].y+=1; if (((r[i].y+FLIPV[i])&1)!=0) r[i].y+=1;
...@@ -1462,16 +1427,8 @@ public class JP46_Reader_camera extends PlugInFrame implements ActionListener { ...@@ -1462,16 +1427,8 @@ public class JP46_Reader_camera extends PlugInFrame implements ActionListener {
if (((r[i].width & 1)==0 ) & (((r[i].x+FLIPH[i])&1)!=0)) r[i].width-=2; if (((r[i].width & 1)==0 ) & (((r[i].x+FLIPH[i])&1)!=0)) r[i].width-=2;
r[i].width &=~1; r[i].width &=~1;
if (((r[i].x+FLIPH[i])&1)!=0) r[i].x+=1; if (((r[i].x+FLIPH[i])&1)!=0) r[i].x+=1;
// System.out.println("after r["+i+"].x= "+r[i].x+" r["+i+"].width= "+r[i].width);
// System.out.println("after r["+i+"].y= "+r[i].y+" r["+i+"].height= "+r[i].height);
} }
if (r[numImg].height<=0) return null; if (r[numImg].height<=0) return null;
// ImageProcessor ip=imp.getProcessor();
// ip.setRoi(r[numImg]);
// ImageProcessor ip_individual=ip.crop().duplicate(); //java.lang.NegativeArraySizeException
/* /*
* When using in multithreaded with (probably) the same composite image * When using in multithreaded with (probably) the same composite image
Exception in thread "Thread-3564" java.lang.ArrayIndexOutOfBoundsException: 8970912 Exception in thread "Thread-3564" java.lang.ArrayIndexOutOfBoundsException: 8970912
......
/**
** -----------------------------------------------------------------------------**
** LwirReader.java
**
** Multithreaded reading cameras - visible and LWIR
**
**
** Copyright (C) 2019 Elphel, Inc.
**
** -----------------------------------------------------------------------------**
**
** LwirReader.java is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see <http://www.gnu.org/licenses/>.
** -----------------------------------------------------------------------------**
**
*/
package com.elphel.imagej.lwir;
import java.io.IOException;
import java.util.Properties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.elphel.imagej.readers.ElphelTiffReader;
import com.elphel.imagej.readers.ImagejJp4TiffMulti;
import ij.ImagePlus;
import ij.process.FloatProcessor;
import ij.process.ImageProcessor;
import loci.formats.FormatException;
public class LwirReader {
public static String [] BASE_URLS= {
"http://192.168.0.36:2323/bchn0",
"http://192.168.0.36:2324/bchn0",
"http://192.168.0.36:2325/bchn0",
"http://192.168.0.36:2326/bchn0",
"http://192.168.0.38:2323/bchn4",
"http://192.168.0.38:2324/bchn4",
"http://192.168.0.38:2325/bchn4",
"http://192.168.0.38:2326/bchn4",
};
// public static String IMAGE_URL_FIRST="/towp/wait/img/next/save";
// public static String IMAGE_URL_NEXT= "/torp/wait/img/next/save";
// public static String IMAGE_URL_FIRST="/towp/wait/img/save"; // next image name, same image number/time
// public static String IMAGE_URL_NEXT= "/torp/wait/img/save"; // same image name, same image number/time
public static String IMAGE_URL_FIRST="/towp/wait/img/save"; // next image name, same image number/time
public static String IMAGE_URL_NEXT= "/torp/next/wait/img/save"; // same image name, same image number/time
// public static String IMAGE_URL_FIRST="/towp/wait/bimg/save"; // next image name, same image number/time
// public static String IMAGE_URL_NEXT= "/torp/next/wait/bimg/save"; // same image name, same image number/time
/** Logger for this class. */
private static final Logger LOGGER =
LoggerFactory.getLogger(ElphelTiffReader.class);
private ImagejJp4TiffMulti imagejJp4TiffMulti;
public LwirReader() {
imagejJp4TiffMulti = null;
}
public ImagePlus[][] readAllMultiple(
final int num_frames,
final boolean show,
final boolean scale) {
return readAllMultiple(num_frames, show, scale, "STD_");
}
public ImagePlus[][] readAllMultiple(
final int num_frames,
final boolean show,
final boolean scale,
final String std)
{
String [] urls0 = new String[BASE_URLS.length];
String [] urls1 = new String[BASE_URLS.length];
ImagePlus [][] imps = new ImagePlus[num_frames][BASE_URLS.length];
for (int i = 0; i < BASE_URLS.length; i++) {
urls0[i] = BASE_URLS[i] + IMAGE_URL_FIRST;
urls1[i] = BASE_URLS[i] + IMAGE_URL_NEXT;
}
if (imagejJp4TiffMulti == null) {
imagejJp4TiffMulti = new ImagejJp4TiffMulti();
for (int i = 0; i < 2; i++) {
try {
imagejJp4TiffMulti.getMultiImages(urls0, imps[0], scale, std);
} catch (IOException e) {
LOGGER.error("readAllMultiple0: IOException, priming" );
} catch (FormatException e) {
LOGGER.error("readAllMultiple0:FormatException, priming");
}
LOGGER.error("priming..."+(i+1));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
for (int n = 0; n < num_frames; n++) {
LOGGER.error("---- Acquiring frame set "+n);
try {
imagejJp4TiffMulti.getMultiImages( (n==0)? urls0:urls1, imps[n], scale, std);
} catch (IOException e) {
LOGGER.error("readAllMultiple0: IOException, n = " + n);
} catch (FormatException e) {
LOGGER.error("readAllMultiple0:FormatException, n = " + n);
}
}
double [][] img_seconds = new double [num_frames][BASE_URLS.length];
int [][] img_numbers = new int [num_frames][BASE_URLS.length];
String [][] img_names = new String [num_frames][BASE_URLS.length];
for (int n = 0; n < num_frames; n++) {
for (int i = 0; i < imps[n].length; i++) {
String dt = null;
try {
dt = (String) imps[n][i].getProperty("DATE_TIME");
} catch (Exception e) {
LOGGER.error("Something is wrong!");
}
img_seconds[n][i] = Double.parseDouble(dt.substring(dt.lastIndexOf(":")+1));
try {
img_numbers[n][i] = Integer.parseInt((String) imps[n][i].getProperty("STD_Image_Number"));
} catch (Exception e) {
img_numbers[n][i] = -1;
}
img_names[n][i] = (String) imps[n][i].getProperty("CONTENT_FILENAME");
LOGGER.error("Seconds for" + n+":"+i+" - "+img_seconds[n][i]+", number"+img_numbers[n][i]+", name "+img_names[n][i]);
}
}
for (ImagePlus [] imp_sets :imps) {
for (ImagePlus imp: imp_sets) {
// imp.show();
}
}
return imps;
}
public ImagePlus [] averageMultiFrames(ImagePlus [][] sets) {
int num_frames = sets.length;
int num_channels = sets[0].length;
ImagePlus [] imps_avg = new ImagePlus [num_channels];
for (int chn = 0; chn < num_channels; chn++) {
int width = sets[0][chn].getWidth();
int height = sets[0][chn].getHeight();
String title = sets[0][chn].getTitle()+"_average"+num_frames;
float [] pixels_avg = (float []) sets[0][chn].getProcessor().getPixels(); //null pointer
for (int n = 1; n < num_frames; n++) {
float [] pixels = (float []) sets[n][chn].getProcessor().getPixels();
for (int i = 0; i < pixels_avg.length; i++) {
pixels_avg[i] += pixels[i];
}
}
double scale = 1.0/num_frames;
for (int i = 0; i < pixels_avg.length; i++) {
pixels_avg[i] *= scale;
}
ImageProcessor ip=new FloatProcessor(width,height);
ip.setPixels(pixels_avg);
ip.resetMinAndMax();
imps_avg[chn]= new ImagePlus(title, ip);
Properties properties0 = sets[0][chn].getProperties();
for (String key:properties0.stringPropertyNames()) {
imps_avg[chn].setProperty(key, properties0.getProperty(key));
}
// TODO: Overwrite some properties?
}
return imps_avg;
}
public ImagePlus [][] matchSets(ImagePlus [][] sets, double max_mismatch, int max_frame_diff){
int num_frames = sets.length;
int num_channels = sets[0].length;
double [][] img_seconds = new double [num_frames][num_channels];
int [] frame_offsets = new int[num_channels];
double [] time_offsets = new double [num_channels];
boolean some_notsynced = false;
int fr_min = 0, fr_max = 0;
for (int n = 0; n < num_frames; n++) {
for (int i = 0; i < num_channels; i++) {
String dt = (String) sets[n][i].getProperty("DATE_TIME");
img_seconds[n][i] = Double.parseDouble(dt.substring(dt.lastIndexOf(":")+1));
}
}
for (int i = 1; i < num_channels; i++) { // compare all to the first channel
frame_offsets[i] = 0;
time_offsets[i] = secOffs(img_seconds[0][0], img_seconds[0][i]);
if (num_frames > max_frame_diff) {
if (time_offsets[i] > max_mismatch) {
for (int fr_diff = 1; fr_diff < max_frame_diff; fr_diff++) {
double aoff = secOffs(img_seconds[fr_diff][0], img_seconds[0][i]);
if (aoff < time_offsets[i]) {
time_offsets[i] = aoff;
frame_offsets[i] = fr_diff; // second channel starts later, than [0]
}
if (time_offsets[i] <=max_mismatch) break;
aoff = secOffs(img_seconds[0][0], img_seconds[fr_diff][i]);
if (aoff < time_offsets[i]) {
time_offsets[i] = aoff;
frame_offsets[i] = -fr_diff; // second channel starts earlier, than [0]
}
if (time_offsets[i] <=max_mismatch) break;
}
}
}
if (time_offsets[i] > max_mismatch) {
some_notsynced = true;
}
if (frame_offsets[i] > 0) {
fr_max = frame_offsets[i];
}
if (frame_offsets[i] < 0) {
fr_min = frame_offsets[i];
}
}
if (some_notsynced) {
LOGGER.error("*** Some channels are not synchronized, reboot or sensors re-start is needed ***");
for (int i = 0; i < num_channels; i++) {
LOGGER.error("Channel "+ i+" frame offset="+frame_offsets[i]+ ", time offset = "+time_offsets[i]+" sec");
}
return null;
}
if (((fr_max - fr_min) > max_frame_diff) || ((fr_max - fr_min) > (num_frames - 1))) {
LOGGER.error("*** Earliest/latest channels differ by more than 1 frame, that should not happen! ***");
for (int i = 0; i < num_channels; i++) {
LOGGER.error("Channel "+ i+" frame offset="+frame_offsets[i]+ ", time offset = "+time_offsets[i]+" sec");
}
return null;
}
for (int i = 0; i < num_channels; i++) {
// change to info later:
LOGGER.error("Channel "+ i+" frame offset="+frame_offsets[i]+ ", time offset = "+time_offsets[i]+" sec");
}
ImagePlus [][] imps_synced = new ImagePlus [num_frames - fr_max + fr_min][num_channels];
for (int n = 0; n < imps_synced.length; n++) {
imps_synced[n][0]= sets[n+fr_max][0];
for (int i = 1; i < num_channels; i++) {
imps_synced[n][i]= sets[n - fr_min][i];
}
}
return imps_synced;
}
private double secOffs(double sec1, double sec2) {
double aoff = Math.abs(sec2 - sec1);
if (aoff > 30) {
aoff = Math.abs(aoff - 30);
}
return aoff;
}
}
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
** **
** -----------------------------------------------------------------------------** ** -----------------------------------------------------------------------------**
** **
** ElphelTiffReader.java is free software: you can redistribute it and/or modify ** ElphelJp4Reader.java is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by ** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or ** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version. ** (at your option) any later version.
...@@ -40,17 +40,20 @@ import org.joda.time.DateTime; ...@@ -40,17 +40,20 @@ import org.joda.time.DateTime;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import com.drew.imaging.ImageMetadataReader;
import com.drew.metadata.Metadata;
import com.drew.metadata.Tag;
import com.drew.metadata.exif.ExifIFD0Directory;
import com.drew.metadata.exif.ExifSubIFDDirectory;
import loci.common.ByteArrayHandle; import loci.common.ByteArrayHandle;
import loci.common.Location; import loci.common.Location;
import loci.common.RandomAccessInputStream; import loci.common.RandomAccessInputStream;
import loci.common.services.DependencyException;
import loci.common.services.ServiceException; import loci.common.services.ServiceException;
import loci.common.services.ServiceFactory;
import loci.formats.FormatException; import loci.formats.FormatException;
import loci.formats.FormatTools; import loci.formats.FormatTools;
import loci.formats.in.ImageIOReader; import loci.formats.in.ImageIOReader;
import loci.formats.in.MetadataLevel; import loci.formats.in.MetadataLevel;
import loci.formats.services.EXIFService;
//import loci.formats.services.EXIFService; //import loci.formats.services.EXIFService;
import ome.xml.meta.MetadataStore; import ome.xml.meta.MetadataStore;
import ome.xml.model.primitives.Timestamp; import ome.xml.model.primitives.Timestamp;
...@@ -59,12 +62,23 @@ import ome.xml.model.primitives.Timestamp; ...@@ -59,12 +62,23 @@ import ome.xml.model.primitives.Timestamp;
public class ElphelJp4Reader extends ImageIOReader{ public class ElphelJp4Reader extends ImageIOReader{
// -- Constants -- // -- Constants --
public static final String MAKER_NOTE = "Makernote"; public static final String MAKER_NOTE = "Makernote";
public static final String SUB_SEC_TIME_ORIGINAL = "Sub-Sec Time Original"; public static final String SUB_SEC_TIME_ORIGINAL = "SUBSEC_TIME_ORIGINAL"; // "Sub-Sec Time Original";
public static final String EXPOSURE_TIME = "Exposure Time"; public static final String EXPOSURE_TIME = "Exposure Time";
public static final String DATE_TIME_ORIGINAL = "Date/Time Original"; public static final String DATE_TIME_ORIGINAL = "DATE_TIME_ORIGINAL"; // "Date/Time Original";
public static final String ELPHEL_PROPERTY_PREFIX = "ELPHEL_"; public static final String ELPHEL_PROPERTY_PREFIX = "ELPHEL_";
public static final String CONTENT_FILENAME = "CONTENT_FILENAME"; public static final String CONTENT_FILENAME = "CONTENT_FILENAME";
public static final boolean REORDER = true; // false;
public static final String[][] REPLACEMENT_TAGS = // to/from!
{{"SUBSEC_TIME_ORIGINAL", "Sub-Sec Time Original"},
{"DATE_TIME_ORIGINAL", "Date/Time Original"},
{"Instrument_Make", "Make"},
{"Serial_Number", "Unknown tag (0xc62f)"},
{"Instrument_Model", "Model"}};
/** Logger for this class. */ /** Logger for this class. */
private static final Logger LOGGER = private static final Logger LOGGER =
...@@ -73,9 +87,12 @@ public class ElphelJp4Reader extends ImageIOReader{ ...@@ -73,9 +87,12 @@ public class ElphelJp4Reader extends ImageIOReader{
// -- Fields -- // -- Fields --
private URL url = null; // save here actual URL when reading file to memory private URL url = null; // save here actual URL when reading file to memory
private String content_fileName = null; // from Content-disposition private String content_fileName = null; // from Content-disposition
private boolean mapped_externally = false; // file is read/mapped externally, do not close it here
private boolean file_initialized = false; private boolean file_initialized = false;
private ElphelTiffReader elphelTiffReader = null; private byte [] image_bytes = null;
// private ImageReader helperReader; private ExifSubIFDDirectory directory;
private ExifIFD0Directory directory_ifd0;
private HashMap<String,String> REPLACEMENT_TAG_MAP = null; // per instance
// -- Constructor -- // -- Constructor --
...@@ -84,11 +101,17 @@ public class ElphelJp4Reader extends ImageIOReader{ ...@@ -84,11 +101,17 @@ public class ElphelJp4Reader extends ImageIOReader{
public ElphelJp4Reader() { public ElphelJp4Reader() {
super("JP4", new String[] {"jp4"}); super("JP4", new String[] {"jp4"});
// mergeSubIFDs = true; // false; // mergeSubIFDs = true; // false;
// TODO: See if the selection is just between 2 readers (jp4 and tiff - just Elphel cameras),
// or these readers are combined with all other readers in readers.txt
suffixNecessary = true; // false suffixNecessary = true; // false
suffixSufficient = true; // false; suffixSufficient = true; // false;
LOGGER.info("ElphelTiffReader(), after super()"); LOGGER.info("ElphelTiffReader(), after super()");
elphelTiffReader = new ElphelTiffReader(); if (REPLACEMENT_TAG_MAP == null) {
REPLACEMENT_TAG_MAP = new HashMap<String,String>();
for (String [] line: REPLACEMENT_TAGS) {
REPLACEMENT_TAG_MAP.put(line[1], line[0]);
}
}
} }
// -- IFormatReader API methods -- // -- IFormatReader API methods --
...@@ -102,6 +125,13 @@ public class ElphelJp4Reader extends ImageIOReader{ ...@@ -102,6 +125,13 @@ public class ElphelJp4Reader extends ImageIOReader{
} }
/* @see loci.formats.IFormatReader#isThisType(RandomAccessInputStream) */ /* @see loci.formats.IFormatReader#isThisType(RandomAccessInputStream) */
/* https://docs.openmicroscopy.org/bio-formats/5.7.2/developers/reader-guide.html
* Check the first few bytes of a file to determine if the file can be read
* by this reader. You can assume that index 0 in the stream corresponds to
* the index 0 in the file. Return true if the file can be read; false if not
* (or if there is no way of checking).
*/
@Override @Override
public boolean isThisType(RandomAccessInputStream stream) throws IOException public boolean isThisType(RandomAccessInputStream stream) throws IOException
{ {
...@@ -116,18 +146,22 @@ public class ElphelJp4Reader extends ImageIOReader{ ...@@ -116,18 +146,22 @@ public class ElphelJp4Reader extends ImageIOReader{
{ {
return false; return false;
} }
return true; return true;
} }
@Override @Override
public void setId(String id) throws FormatException, IOException { // same as for tiff? public void setId(String id) throws FormatException, IOException { // same as for tiff?
image_bytes = null;
// buffered_data = null;
LOGGER.debug("setId("+id+"). before super" ); LOGGER.debug("setId("+id+"). before super" );
file_initialized = false; file_initialized = false;
mapped_externally = false;
if (Location.getIdMap().containsKey(id)) { if (Location.getIdMap().containsKey(id)) {
LOGGER.debug("id '"+id+"' is already mapped" ); LOGGER.debug("id '"+id+"' is already mapped" );
content_fileName = null; // id; // maybe set to null to handle externally? content_fileName = id; // id; // maybe set to null to handle externally?
mapped_externally = true;
LOGGER.info("Starting initFile() method, read file directly"); LOGGER.info("Starting initFile() method, read file directly");
super.setId(id); super.setId(id);
} else { } else {
...@@ -167,7 +201,7 @@ public class ElphelJp4Reader extends ImageIOReader{ ...@@ -167,7 +201,7 @@ public class ElphelJp4Reader extends ImageIOReader{
if (is != null) is.close(); if (is != null) is.close();
LOGGER.info("Bytes read: "+ inBytes.length); LOGGER.info("Bytes read: "+ inBytes.length);
Location.mapFile(content_fileName, new ByteArrayHandle(inBytes)); Location.mapFile(content_fileName, new ByteArrayHandle(inBytes));
HashMap<String,Object> dbg_loc = Location.getIdMap(); // HashMap<String,Object> dbg_loc = Location.getIdMap();
super.setId(content_fileName); super.setId(content_fileName);
} else { // read file normally } else { // read file normally
content_fileName = id; content_fileName = id;
...@@ -193,40 +227,63 @@ public class ElphelJp4Reader extends ImageIOReader{ ...@@ -193,40 +227,63 @@ public class ElphelJp4Reader extends ImageIOReader{
throw new FormatException(e); throw new FormatException(e);
} }
LOGGER.debug("initFile("+id+"), currentId="+currentId+", after super" ); LOGGER.debug("initFile("+id+"), currentId="+currentId+", after super" );
// elphelTiffReader.setId(id); //error here - invalid tiff file
// Below needs to be modified - EXIFService does not work with mapFile // Below needs to be modified - EXIFService does not work with mapFile
MetadataStore store = makeFilterMetadata(); MetadataStore store = makeFilterMetadata();
LOGGER.info("Parsing JPEG EXIF data"); LOGGER.info("Parsing JPEG EXIF data");
HashMap<String, String> tags = null; HashMap<String, String> tags = null;
try { try {
EXIFService exif = new ServiceFactory().getInstance(EXIFService.class); // Reimplementing ExifServiceImpl as original does not have ExifIFD0Directory
if (exif == null) { try (RandomAccessInputStream jpegFile = new RandomAccessInputStream(id)) {
return; try {
} Metadata metadata = ImageMetadataReader.readMetadata(jpegFile);
exif.initialize(id); directory = metadata.getFirstDirectoryOfType(ExifSubIFDDirectory.class);
directory_ifd0 = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class);
// Set the acquisition date }
Date date = exif.getCreationDate(); catch (Throwable e) {
if (date != null) { throw new ServiceException("Could not read EXIF data", e);
}
}
Date date = directory.getDate(ExifSubIFDDirectory.TAG_DATETIME_ORIGINAL);
if (date != null) {
Timestamp timestamp = new Timestamp(new DateTime(date)); Timestamp timestamp = new Timestamp(new DateTime(date));
store.setImageAcquisitionDate(timestamp, 0); store.setImageAcquisitionDate(timestamp, 0);
} }
tags = exif.getTags(); tags = new HashMap<String, String>();
if (directory != null) {
for (Tag tag : directory.getTags()) {
String tag_name = tag.getTagName();
if (REPLACEMENT_TAG_MAP.containsKey(tag_name)) {
tags.put(REPLACEMENT_TAG_MAP.get(tag_name), tag.getDescription());
} else {
tags.put(tag.getTagName(), tag.getDescription());
}
}
}
if (directory_ifd0 != null) {
for (Tag tag : directory_ifd0.getTags()) {
String tag_name = tag.getTagName();
if (REPLACEMENT_TAG_MAP.containsKey(tag_name)) {
tags.put(REPLACEMENT_TAG_MAP.get(tag_name), tag.getDescription());
} else {
tags.put(tag.getTagName(), tag.getDescription());
}
}
}
// remove "sec" from exposure
if (tags.containsKey(EXPOSURE_TIME)){
tags.put(EXPOSURE_TIME, tags.get(EXPOSURE_TIME).split(" ")[0]);
}
for (String tagName : tags.keySet()) { for (String tagName : tags.keySet()) {
addGlobalMeta(tagName, tags.get(tagName)); addGlobalMeta(tagName, tags.get(tagName));
} //{Makernote=105455 131072 127570 300581 171508736 171508736 171508736 171508736 169869312 124780556 1118544 0 0 327779 648 1296, Sub-Sec Time Original=560439, Exposure Time=11167/500000 sec, Date/Time Original=2019:05:13 04:30:26} } //{Makernote=105455 131072 127570 300581 171508736 171508736 171508736 171508736 169869312 124780556 1118544 0 0 327779 648 1296, Sub-Sec Time Original=560439, Exposure Time=11167/500000 sec, Date/Time Original=2019:05:13 04:30:26}
} }
catch (ServiceException e) { catch (ServiceException e) {
LOGGER.debug("Could not parse EXIF data", e); LOGGER.debug("Could not parse EXIF data", e);
} }
catch (DependencyException e) {
LOGGER.debug("Could not parse EXIF data", e);
}
long [] maker_note = null; long [] maker_note = null;
double exposure = Double.NaN; double exposure = Double.NaN;
String date_time = null; String date_time = null;
...@@ -244,20 +301,21 @@ public class ElphelJp4Reader extends ImageIOReader{ ...@@ -244,20 +301,21 @@ public class ElphelJp4Reader extends ImageIOReader{
if (tags.containsKey(DATE_TIME_ORIGINAL)){ if (tags.containsKey(DATE_TIME_ORIGINAL)){
date_time = tags.get(DATE_TIME_ORIGINAL); date_time = tags.get(DATE_TIME_ORIGINAL);
if (tags.containsKey(SUB_SEC_TIME_ORIGINAL)){ if (tags.containsKey(SUB_SEC_TIME_ORIGINAL)){
date_time += tags.get(SUB_SEC_TIME_ORIGINAL); date_time += "."+tags.get(SUB_SEC_TIME_ORIGINAL);
} }
} }
int bytes_per_pixel = 1;
Hashtable<String, String> property_table = ElphelMeta.getMeta( Hashtable<String, String> property_table = ElphelMeta.getMeta(
null, maker_note, exposure, date_time, true ); null, maker_note, exposure, date_time, bytes_per_pixel, true );
LOGGER.info("Created elphelMeta table, size="+property_table.size()); LOGGER.info("Created elphelMeta table, size="+property_table.size());
for (String key:property_table.keySet()) { for (String key:property_table.keySet()) {
addGlobalMeta(ELPHEL_PROPERTY_PREFIX+key,property_table.get(key)); addGlobalMeta(ELPHEL_PROPERTY_PREFIX+key,property_table.get(key));
} }
MetadataLevel level = getMetadataOptions().getMetadataLevel(); MetadataLevel level = getMetadataOptions().getMetadataLevel();
if (level != MetadataLevel.MINIMUM) { if (level != MetadataLevel.MINIMUM) {
// Integer[] tags = ifds.get(0).keySet().toArray(new Integer[0]); // Integer[] tags = ifds.get(0).keySet().toArray(new Integer[0]);
// LOGGER.info("initStandardMetadata() - got "+tags.length+" tags"); // LOGGER.info("initStandardMetadata() - got "+tags.length+" tags");
} }
addGlobalMeta(ELPHEL_PROPERTY_PREFIX+CONTENT_FILENAME,content_fileName); addGlobalMeta(ELPHEL_PROPERTY_PREFIX+CONTENT_FILENAME,content_fileName);
...@@ -267,18 +325,19 @@ public class ElphelJp4Reader extends ImageIOReader{ ...@@ -267,18 +325,19 @@ public class ElphelJp4Reader extends ImageIOReader{
/* @see loci.formats.IFormatReader#close(boolean) */ /* @see loci.formats.IFormatReader#close(boolean) */
@Override @Override
public void close(boolean fileOnly) throws IOException { public void close(boolean fileOnly) throws IOException {
HashMap<String,Object> dbg_loc = Location.getIdMap(); // HashMap<String,Object> dbg_loc = Location.getIdMap();
String saveCurrentId = currentId; String saveCurrentId = currentId;
currentId = null; currentId = null;
LOGGER.info("close("+fileOnly+") before super"); LOGGER.info("close("+fileOnly+") before super");
super.close(fileOnly); // curerent_id == null only during actual close? super.close(fileOnly); // curerent_id == null only during actual close?
LOGGER.info("close("+fileOnly+") after super"); LOGGER.info("close("+fileOnly+") after super");
currentId = saveCurrentId; currentId = saveCurrentId;
if ((content_fileName != null) && file_initialized){ // if ((content_fileName != null) && file_initialized){
if (!mapped_externally && file_initialized){ // will try to unmap non-mapped file, OK
Location.mapFile(content_fileName, null); Location.mapFile(content_fileName, null);
file_initialized = false; file_initialized = false;
} }
dbg_loc = Location.getIdMap(); // dbg_loc = Location.getIdMap();
if (!fileOnly) { if (!fileOnly) {
// companionFile = null; // companionFile = null;
...@@ -291,9 +350,78 @@ public class ElphelJp4Reader extends ImageIOReader{ ...@@ -291,9 +350,78 @@ public class ElphelJp4Reader extends ImageIOReader{
} }
} }
/**
* @see loci.formats.IFormatReader#openBytes(int, byte[], int, int, int, int)
*
* openBytes(int, byte[], int, int, int, int) Returns a byte array containing
* the pixel data for a specified subimage from the given file. The dimensions
* of the subimage (upper left X coordinate, upper left Y coordinate, width,
* and height) are specified in the final four int parameters.
* This should throw a FormatException if the image number is invalid (less than
* 0 or >= the number of images). The ordering of the array returned by openBytes
* should correspond to the values returned by isLittleEndian and isInterleaved.
* Also, the length of the byte array should be
* [image_width * image height * bytes_per_pixel]. Extra bytes will generally
* be truncated. It is recommended that the first line of this method be
* FormatTools.checkPlaneParameters(this, no, buf.length, x, y, w, h) - this ensures
* that all of the parameters are valid.
*/
@Override
public byte[] openBytes(int no, byte[] buf, int x, int y, int w, int h)
throws FormatException, IOException
{
LOGGER.info("openBytes() - before super()");
FormatTools.checkPlaneParameters(this, no, buf.length, x, y, w, h);
if (image_bytes == null) {
jp4Decode(no);
}
int width = getSizeX();
int y1 = y + h;
int dest=0;
for (int line = y; line < y1; line++) {
System.arraycopy(image_bytes,
line*width+x,
buf,
dest,
w);
dest += w;
}
LOGGER.info("openBytes() - after super()");
return buf;
}
public void jp4Decode(int no) throws FormatException, IOException {
int width = getSizeX();
int height = getSizeY();
image_bytes = new byte[width*height];
byte [] ib = new byte [width*height];
byte [][] macroblock=new byte[16][16];
super.openBytes(no, ib, 0, 0, width, height);
if (REORDER) {
int yb,y,xb,x,offset,nb,xbyr,ybyr; //,i;
for (yb=0;yb<(height>>4); yb++) for (xb=0;xb<(width>>4); xb++) { /* iterating macroblocks */
for (nb=0; nb<4;nb++) {
xbyr=nb & 1;
ybyr=(nb>>1) & 1;
for (y=0;y<8;y++) {
offset=((yb<<4)+y)*width+ (nb<<3) +((xb>=(width>>5))?(((xb<<5)-width)+(width<<3)):(xb<<5));
for (x=0;x<8;x++) {
macroblock[(y<<1) | ybyr][(x<<1) | xbyr]=ib[offset+x];
}
}
}
for (y=0;y<16;y++) {
System.arraycopy(macroblock[y],
0,
image_bytes,
((yb<<4)+y)*width + (xb<<4),
16);
}
}
} else {
image_bytes = ib; // temporary
}
LOGGER.info("jp4Decode()");
}
} }
...@@ -38,6 +38,7 @@ public class ElphelMeta { ...@@ -38,6 +38,7 @@ public class ElphelMeta {
long[] maker_note, long[] maker_note,
double exposure, double exposure,
String date_time, String date_time,
int bytesPerPixel,
boolean scale) throws FormatException, IOException { boolean scale) throws FormatException, IOException {
if (property_table == null) { if (property_table == null) {
property_table = new Hashtable<String, String> (); property_table = new Hashtable<String, String> ();
...@@ -45,7 +46,7 @@ public class ElphelMeta { ...@@ -45,7 +46,7 @@ public class ElphelMeta {
if (!Double.isNaN(exposure)) { if (!Double.isNaN(exposure)) {
property_table.put("EXPOSURE", String.format("%f",exposure)); property_table.put("EXPOSURE", String.format("%f",exposure));
} }
if (date_time == null) { if (date_time != null) {
property_table.put("DATE_TIME", date_time); property_table.put("DATE_TIME", date_time);
} }
if (maker_note != null) { if (maker_note != null) {
...@@ -138,7 +139,7 @@ public class ElphelMeta { ...@@ -138,7 +139,7 @@ public class ElphelMeta {
property_table.put("FLIPH", String.format("%d",FLIPH)); property_table.put("FLIPH", String.format("%d",FLIPH));
property_table.put("FLIPV", String.format("%d",FLIPV)); property_table.put("FLIPV", String.format("%d",FLIPV));
property_table.put("BAYER_MODE",String.format("%d",BAYER_MODE)); property_table.put("BAYER_MODE",String.format("%d",BAYER_MODE));
property_table.put("COLOR_MODE",((COLOR_MODE==2)?"JP46":((COLOR_MODE==5)?"JP4":((COLOR_MODE==0)?"MONO":"OTHER")))); property_table.put("COLOR_MODE",((COLOR_MODE==2)?"JP46":((COLOR_MODE==5)?"JP4":((COLOR_MODE==0)?"MONO":((COLOR_MODE==15)?"RAW":"OTHER")))));
property_table.put("DCM_HOR", String.format("%d",DCM_HOR)); property_table.put("DCM_HOR", String.format("%d",DCM_HOR));
property_table.put("DCM_VERT", String.format("%d",DCM_VERT)); property_table.put("DCM_VERT", String.format("%d",DCM_VERT));
property_table.put("BIN_HOR", String.format("%d",BIN_HOR)); property_table.put("BIN_HOR", String.format("%d",BIN_HOR));
...@@ -203,7 +204,12 @@ public class ElphelMeta { ...@@ -203,7 +204,12 @@ public class ElphelMeta {
/**adjusting gains to have the result picture in the range 0..256 */ /**adjusting gains to have the result picture in the range 0..256 */
min_gain=2.0*gains[0]; min_gain=2.0*gains[0];
for (i=0;i<4;i++) { for (i=0;i<4;i++) {
if (min_gain > gains[i]*(1.0-blacks[i])) min_gain = gains[i]*(1.0-blacks[i]); if (bytesPerPixel == 1) {
if (min_gain > gains[i]*(1.0-blacks[i])) min_gain = gains[i]*(1.0-blacks[i]);
} else {
if (min_gain > gains[i]) min_gain = gains[i];
}
} }
property_table.put("GAIN",String.format("%f",min_gain)); // common gain property_table.put("GAIN",String.format("%f",min_gain)); // common gain
......
...@@ -39,7 +39,9 @@ import org.slf4j.LoggerFactory; ...@@ -39,7 +39,9 @@ import org.slf4j.LoggerFactory;
import loci.common.ByteArrayHandle; import loci.common.ByteArrayHandle;
import loci.common.Location; import loci.common.Location;
import loci.common.RandomAccessInputStream;
import loci.formats.FormatException; import loci.formats.FormatException;
import loci.formats.FormatTools;
import loci.formats.in.MetadataLevel; import loci.formats.in.MetadataLevel;
import loci.formats.in.TiffReader; import loci.formats.in.TiffReader;
import loci.formats.meta.MetadataStore; import loci.formats.meta.MetadataStore;
...@@ -85,6 +87,10 @@ public class ElphelTiffReader extends TiffReader{ // BaseTiffReader { ...@@ -85,6 +87,10 @@ public class ElphelTiffReader extends TiffReader{ // BaseTiffReader {
public static final String ELPHEL_PROPERTY_PREFIX = "ELPHEL_"; public static final String ELPHEL_PROPERTY_PREFIX = "ELPHEL_";
public static final String CONTENT_FILENAME = "CONTENT_FILENAME"; public static final String CONTENT_FILENAME = "CONTENT_FILENAME";
public static final int TAG_IMAGE_NUMBER = 0x9211; //com.drew.metadata.exif.ExifDirectoryBase
public static final int TAG_SERIAL_NUMBER = 0xc62f;
public static final int TAG_IMAGE_DESCRIPTION = 0x010e;
/** Merge SubIFDs into the main IFD list. */ /** Merge SubIFDs into the main IFD list. */
// protected transient boolean mergeSubIFDs = true; // false; // protected transient boolean mergeSubIFDs = true; // false;
...@@ -104,6 +110,7 @@ public class ElphelTiffReader extends TiffReader{ // BaseTiffReader { ...@@ -104,6 +110,7 @@ public class ElphelTiffReader extends TiffReader{ // BaseTiffReader {
// private String inId = null; // to close Location.mapFile // private String inId = null; // to close Location.mapFile
private URL url = null; // save here actual URL when reading file to memory private URL url = null; // save here actual URL when reading file to memory
private String content_fileName = null; // from Content-disposition private String content_fileName = null; // from Content-disposition
private boolean mapped_externally = false; // file is read/mapped externally, do not close it here
private boolean file_initialized = false; private boolean file_initialized = false;
// private String companionFile; // private String companionFile;
// private String description; // private String description;
...@@ -117,20 +124,60 @@ public class ElphelTiffReader extends TiffReader{ // BaseTiffReader { ...@@ -117,20 +124,60 @@ public class ElphelTiffReader extends TiffReader{ // BaseTiffReader {
/** Constructs a new Tiff reader. */ /** Constructs a new Tiff reader. */
public ElphelTiffReader() { public ElphelTiffReader() {
super(); // "Tagged Image File Format", ELPHEL_TIFF_SUFFIXES); // See if we can use TiffReader without its parent super(); // "Tagged Image File Format", ELPHEL_TIFF_SUFFIXES); // See if we can use TiffReader without its parent
// mergeSubIFDs = true; // false;
// TODO: See if the selection is just between 2 readers (jp4 and tiff - just Elphel cameras),
// or these readers are combined with all other readers in readers.txt
suffixNecessary = true; // false
suffixSufficient = true; // false;
/// mergeSubIFDs = true; // false; /// mergeSubIFDs = true; // false;
LOGGER.info("ElphelTiffReader(), after supper(), mergeSubIFDs = true;"); LOGGER.info("ElphelTiffReader(), after supper(), mergeSubIFDs = true;");
} }
// -- IFormatReader API methods -- // -- IFormatReader API methods --
/* @see loci.formats.IFormatReader#isThisType(String, boolean) */
@Override
public boolean isThisType(String name, boolean open) {
if (open) {
return super.isThisType(name, open);
}
return checkSuffix(name, getSuffixes());
}
/* @see loci.formats.IFormatReader#isThisType(RandomAccessInputStream) */
/*
* Check the first few bytes of a file to determine if the file can be read
* by this reader. You can assume that index 0 in the stream corresponds to
* the index 0 in the file. Return true if the file can be read; false if not
* (or if there is no way of checking).
*/
@Override
public boolean isThisType(RandomAccessInputStream stream) throws IOException
{
final int blockLen = 4;
if (!FormatTools.validStream(stream, blockLen, false)) return false;
byte[] signature = new byte[blockLen];
stream.read(signature);
if (signature[0] != (byte) 0xff || signature[1] != (byte) 0xd8 ||
signature[2] != (byte) 0xff || (signature[3] & 0xf0) == 0)
{
return false;
}
return true;
}
@Override @Override
public void setId(String id) throws FormatException, IOException { public void setId(String id) throws FormatException, IOException {
LOGGER.debug("setId("+id+"). before super" ); LOGGER.debug("setId("+id+"). before super" );
file_initialized = false; file_initialized = false;
mapped_externally = false;
if (Location.getIdMap().containsKey(id)) { if (Location.getIdMap().containsKey(id)) {
LOGGER.debug("id '"+id+"' is already mapped" ); LOGGER.debug("id '"+id+"' is already mapped" );
content_fileName = null; // id; // maybe set to null to handle externally? content_fileName = id; // id; // maybe set to null to handle externally?
LOGGER.info("Starting initFile() method, read file directly"); mapped_externally = true;
LOGGER.info("Starting setId() method, read file directly");
super.setId(id); super.setId(id);
} else { } else {
// If URL, then read to memory, if normal file - use direct access // If URL, then read to memory, if normal file - use direct access
...@@ -192,6 +239,8 @@ public class ElphelTiffReader extends TiffReader{ // BaseTiffReader { ...@@ -192,6 +239,8 @@ public class ElphelTiffReader extends TiffReader{ // BaseTiffReader {
throws FormatException, throws FormatException,
java.io.IOException java.io.IOException
{ {
// Trying ServiceFactory before it is going to be initialized, so static defaultFactory will be initialized
// with small set of services - only needed for Elphel
LOGGER.info("Starting initFile() method"); LOGGER.info("Starting initFile() method");
super.initFile(id); super.initFile(id);
LOGGER.info("Ending initFile() method"); LOGGER.info("Ending initFile() method");
...@@ -216,7 +265,8 @@ public class ElphelTiffReader extends TiffReader{ // BaseTiffReader { ...@@ -216,7 +265,8 @@ public class ElphelTiffReader extends TiffReader{ // BaseTiffReader {
LOGGER.info("close("+fileOnly+") before super"); LOGGER.info("close("+fileOnly+") before super");
super.close(fileOnly); // curerent_id == null only during actual close? super.close(fileOnly); // curerent_id == null only during actual close?
LOGGER.info("close("+fileOnly+") after super"); LOGGER.info("close("+fileOnly+") after super");
if ((content_fileName != null) && file_initialized){ // if ((content_fileName != null) && file_initialized){
if (!mapped_externally && file_initialized){ // will try to unmap non-mapped file, OK
Location.mapFile(content_fileName, null); Location.mapFile(content_fileName, null);
file_initialized = false; file_initialized = false;
} }
...@@ -246,6 +296,14 @@ public class ElphelTiffReader extends TiffReader{ // BaseTiffReader { ...@@ -246,6 +296,14 @@ public class ElphelTiffReader extends TiffReader{ // BaseTiffReader {
double exposure = Double.NaN; double exposure = Double.NaN;
String date_time = null; String date_time = null;
IFDList exifIFDs = tiffParser.getExifIFDs(); IFDList exifIFDs = tiffParser.getExifIFDs();
//ifds.get(0) has 37393=297894 (ImageNumbder), but not exifIFD
IFD ifd0 = ifds.get(0);
if (ifd0.containsKey(TAG_IMAGE_NUMBER)) addGlobalMeta("Image_Number", ifd0.get(TAG_IMAGE_NUMBER));
if (ifd0.containsKey(TAG_SERIAL_NUMBER)) addGlobalMeta("Serial_Number", ifd0.get(TAG_SERIAL_NUMBER));
if (ifd0.containsKey(TAG_IMAGE_DESCRIPTION)) addGlobalMeta("Image_Description", ifd0.get(TAG_IMAGE_DESCRIPTION));
//TAG_SERIAL_NUMBER
if (exifIFDs.size() > 0) { if (exifIFDs.size() > 0) {
IFD exifIFD = exifIFDs.get(0); IFD exifIFD = exifIFDs.get(0);
tiffParser.fillInIFD(exifIFD); tiffParser.fillInIFD(exifIFD);
...@@ -266,9 +324,10 @@ public class ElphelTiffReader extends TiffReader{ // BaseTiffReader { ...@@ -266,9 +324,10 @@ public class ElphelTiffReader extends TiffReader{ // BaseTiffReader {
} }
} }
} }
int bpp = getBitsPerPixel();
int bytes_per_pixel = (bpp + 7) / 9;
Hashtable<String, String> property_table = ElphelMeta.getMeta( Hashtable<String, String> property_table = ElphelMeta.getMeta(
null, maker_note, exposure, date_time, true ); null, maker_note, exposure, date_time, bytes_per_pixel, true );
LOGGER.info("Created elphelMeta table, size="+property_table.size()); LOGGER.info("Created elphelMeta table, size="+property_table.size());
...@@ -281,6 +340,16 @@ public class ElphelTiffReader extends TiffReader{ // BaseTiffReader { ...@@ -281,6 +340,16 @@ public class ElphelTiffReader extends TiffReader{ // BaseTiffReader {
LOGGER.info("initStandardMetadata() - got "+tags.length+" tags"); LOGGER.info("initStandardMetadata() - got "+tags.length+" tags");
} }
addGlobalMeta(ELPHEL_PROPERTY_PREFIX+CONTENT_FILENAME,content_fileName); addGlobalMeta(ELPHEL_PROPERTY_PREFIX+CONTENT_FILENAME,content_fileName);
// convert MAKER_NOTE to the same text format as in com.drew.metadata
if (maker_note != null) {
StringBuffer sb_maker_note = new StringBuffer();
for (int i = 0; i < maker_note.length; i++) {
if (i > 0) sb_maker_note.append(" ");
sb_maker_note.append(maker_note[i]);
}
addGlobalMeta("MAKER_NOTE", sb_maker_note.toString());
}
// check for ImageJ-style TIFF comment // check for ImageJ-style TIFF comment
boolean ij = checkCommentImageJ(comment); boolean ij = checkCommentImageJ(comment);
// if (ij) parseCommentImageJ(comment); // if (ij) parseCommentImageJ(comment);
......
...@@ -64,7 +64,8 @@ public class ImagejJp4Tiff { ...@@ -64,7 +64,8 @@ public class ImagejJp4Tiff {
// -- Constants -- // -- Constants --
private static final Logger LOGGER = LoggerFactory.getLogger(ClassList.class); private static final Logger LOGGER = LoggerFactory.getLogger(ClassList.class);
private static final boolean BYPASS_SERVICES = true; private static final boolean BYPASS_SERVICES = false; // true;
private static final String SERVICES_PATH = "services.properties.forelphel";
// -- Fields -- // -- Fields --
...@@ -81,17 +82,29 @@ public class ImagejJp4Tiff { ...@@ -81,17 +82,29 @@ public class ImagejJp4Tiff {
// classList.addClass(com.elphel.imagej.readers.ElphelTiffJp4Reader.class); // classList.addClass(com.elphel.imagej.readers.ElphelTiffJp4Reader.class);
classList.addClass(com.elphel.imagej.readers.ElphelJp4Reader.class); classList.addClass(com.elphel.imagej.readers.ElphelJp4Reader.class);
classList.addClass(com.elphel.imagej.readers.ElphelTiffReader.class); classList.addClass(com.elphel.imagej.readers.ElphelTiffReader.class);
// Trying ServiceFactory before it is going to be initialized, so static defaultFactory will be initialized
// with small set of services - only needed for Elphel
//URL u = this.getClass().getResource(SERVICES_PATH);
// URL u = this.getClass().getResource("/"+SERVICES_PATH);
// loci.common.DebugTools.enableLogging("ERROR");
if (!BYPASS_SERVICES) { if (!BYPASS_SERVICES) {
ServiceFactory factory = null; ServiceFactory factory = null;
try { try {
factory = new ServiceFactory(); // factory = new ServiceFactory();
// Still does not work - during initFile->populatePixels it loads all
// services, including buggy ones
factory = new ServiceFactory(); // "/"+SERVICES_PATH);
} catch (DependencyException e) { } catch (DependencyException e) {
// TODO Auto-generated catch block // TODO Auto-generated catch block
e.printStackTrace(); e.printStackTrace();
} }
OMEXMLService service = null; OMEXMLService service = null;
try { try {
service = factory.getInstance(OMEXMLService.class); service = factory.getInstance(OMEXMLService.class);
...@@ -117,10 +130,13 @@ public class ImagejJp4Tiff { ...@@ -117,10 +130,13 @@ public class ImagejJp4Tiff {
// -- API methods -- // -- API methods --
public ImagePlus readTiffJp4(String path) throws IOException, FormatException { public ImagePlus readTiffJp4(String path) throws IOException, FormatException {
return readTiffJp4(path, "STD_"); // null); return readTiffJp4(path, true); // null);
}
public ImagePlus readTiffJp4(String path, boolean scale) throws IOException, FormatException {
return readTiffJp4(path, scale, "STD_"); // null);
} }
public ImagePlus readTiffJp4(String path_url, String std ) throws IOException, FormatException { // std - include non-elphel properties with prefix std public ImagePlus readTiffJp4(String path_url, boolean scale, String std ) throws IOException, FormatException { // std - include non-elphel properties with prefix std
// determine if it is a file or URL and read url to memory // determine if it is a file or URL and read url to memory
// If URL, then read to memory, if normal file - use direct access // If URL, then read to memory, if normal file - use direct access
url = null; url = null;
...@@ -131,11 +147,12 @@ public class ImagejJp4Tiff { ...@@ -131,11 +147,12 @@ public class ImagejJp4Tiff {
} catch (MalformedURLException e) { } catch (MalformedURLException e) {
LOGGER.warn("Bad URL: " + path_url); LOGGER.warn("Bad URL: " + path_url);
} }
//https://stackoverflow.com/questions/39086500/read-http-response-header-and-body-from-one-http-request-in-java
if (url != null) { if (url != null) {
LOGGER.info("Read "+ path_url +" to memory first"); LOGGER.error("Read "+ path_url +" to memory first");
URLConnection connection = url.openConnection(); URLConnection connection = url.openConnection();
String content_disposition = connection.getHeaderField("Content-Disposition"); String content_disposition = connection.getHeaderField("Content-Disposition"); // reads file
// raw = "attachment; filename=abc.jpg" // raw = "attachment; filename=abc.jpg"
if(content_disposition != null && content_disposition.indexOf("=") != -1) { if(content_disposition != null && content_disposition.indexOf("=") != -1) {
content_fileName = content_disposition.split("=")[1]; //getting value after '=' content_fileName = content_disposition.split("=")[1]; //getting value after '='
...@@ -147,7 +164,8 @@ public class ImagejJp4Tiff { ...@@ -147,7 +164,8 @@ public class ImagejJp4Tiff {
String suffix = slash < 0 ? "" : mime.substring(slash+1); String suffix = slash < 0 ? "" : mime.substring(slash+1);
content_fileName = "unknown." + suffix; content_fileName = "unknown." + suffix;
} }
InputStream is = url.openStream (); // // InputStream is = url.openStream (); // reads file for the second time
InputStream is = connection.getInputStream ();
byte[] inBytes = IOUtils.toByteArray(is); byte[] inBytes = IOUtils.toByteArray(is);
if (is != null) is.close(); if (is != null) is.close();
LOGGER.info("Bytes read: "+ inBytes.length); LOGGER.info("Bytes read: "+ inBytes.length);
...@@ -182,11 +200,17 @@ public class ImagejJp4Tiff { ...@@ -182,11 +200,17 @@ public class ImagejJp4Tiff {
pixels[i] = ((bb.getShort())) & 0xffff; pixels[i] = ((bb.getShort())) & 0xffff;
} }
} }
Hashtable<String, Object> meta_hash = reader.getGlobalMetadata();
boolean degamma = bytes_per_pixel < 2; // both JP4 and 8-bit tiff
if (deGammaScale(pixels, reader.getSizeX(), meta_hash, degamma, scale ) == null) {
LOGGER.error("Problem degamma/scaling of "+content_fileName);
}
ImageProcessor ip=new FloatProcessor(reader.getSizeX(), reader.getSizeY()); ImageProcessor ip=new FloatProcessor(reader.getSizeX(), reader.getSizeY());
ip.setPixels(pixels); ip.setPixels(pixels);
ip.resetMinAndMax(); ip.resetMinAndMax();
Hashtable<String, Object> meta_hash = reader.getGlobalMetadata();
String prefix = ElphelTiffReader.ELPHEL_PROPERTY_PREFIX; String prefix = ElphelTiffReader.ELPHEL_PROPERTY_PREFIX;
String imageName = content_fileName; // path; String imageName = content_fileName; // path;
String imageNameKey = prefix+ElphelTiffReader.CONTENT_FILENAME; String imageNameKey = prefix+ElphelTiffReader.CONTENT_FILENAME;
...@@ -208,6 +232,7 @@ public class ImagejJp4Tiff { ...@@ -208,6 +232,7 @@ public class ImagejJp4Tiff {
return imp; return imp;
} }
// -- Helper methods -- // -- Helper methods --
public static ImagePlus encodeProperiesToInfo(ImagePlus imp){ public static ImagePlus encodeProperiesToInfo(ImagePlus imp){
...@@ -231,5 +256,100 @@ public class ImagejJp4Tiff { ...@@ -231,5 +256,100 @@ public class ImagejJp4Tiff {
return imp; return imp;
} }
public float [] deGammaScale(float [] pixels, int width, Hashtable<String, Object> meta_hash, boolean degamma, boolean scale) {
int height = pixels.length/width;
String prefix = ElphelTiffReader.ELPHEL_PROPERTY_PREFIX;
double [] rgains = {1.0, 1.0, 1.0, 1.0};
double [] blacks = new double[4];
double[] blacks256= new double[4];
double [] gammas = new double[4];
long [] gamma_scales = new long[4];
double [][] rgammas = new double[4][];
double min_gain = 1.0;
boolean flipv, fliph;
if (meta_hash.get(prefix+"FLIPV")!=null) flipv= Integer.valueOf((String) meta_hash.get(prefix+"FLIPV")).intValue() > 0; else return null;
if (meta_hash.get(prefix+"FLIPH")!=null) fliph= Integer.valueOf((String) meta_hash.get(prefix+"FLIPV")).intValue() > 0; else return null;
if (meta_hash.get(prefix+"GAIN")!=null) min_gain= Double.valueOf((String) meta_hash.get(prefix+"GAIN")).doubleValue(); else return null;
for (int i=0;i<4;i++) { /* r,g,gb,b */
if (scale) {
if (meta_hash.get(prefix+"gains_"+i)!=null) rgains[i]= min_gain/Double.valueOf((String) meta_hash.get(prefix+"gains_"+i)).doubleValue(); else return null;
}
if (degamma) {
if (meta_hash.get(prefix+"blacks_"+i)!=null) blacks[i]= Double.valueOf((String) meta_hash.get(prefix+"blacks_"+i)).doubleValue(); else return null;
if (meta_hash.get(prefix+"gammas_"+i)!=null) gammas[i]= Double.valueOf((String) meta_hash.get(prefix+"gammas_"+i)).doubleValue(); else return null;
if (meta_hash.get(prefix+"gamma_scales_"+i)!=null) gamma_scales[i]= Integer.valueOf((String) meta_hash.get(prefix+"gamma_scales_"+i)).intValue(); else return null;
}
}
if (flipv) {
if (scale) {
ElphelMeta.swapArrayElements (rgains, 1, 3);
ElphelMeta.swapArrayElements (rgains, 0, 2);
}
if (degamma) {
ElphelMeta.swapArrayElements (blacks, 1, 3);
ElphelMeta.swapArrayElements (blacks, 0, 2);
ElphelMeta.swapArrayElements (gammas, 1, 3);
ElphelMeta.swapArrayElements (gammas, 0, 2);
ElphelMeta.swapArrayElements (gamma_scales,1, 3);
ElphelMeta.swapArrayElements (gamma_scales,0, 2);
}
}
if (fliph) {
if (scale) {
ElphelMeta.swapArrayElements (rgains, 1, 0);
ElphelMeta.swapArrayElements (rgains, 3, 2);
}
if (degamma) {
ElphelMeta.swapArrayElements (blacks, 1, 0);
ElphelMeta.swapArrayElements (blacks, 3, 2);
ElphelMeta.swapArrayElements (gammas, 1, 0);
ElphelMeta.swapArrayElements (gammas, 3, 2);
ElphelMeta.swapArrayElements (gamma_scales,1, 0);
ElphelMeta.swapArrayElements (gamma_scales,3, 2);
}
}
if (degamma) {
for (int i=0;i<4;i++) {
rgammas[i]=ElphelMeta.elphel_gamma_calc (gammas[i], blacks[i], gamma_scales[i]);
blacks256[i]=256.0*blacks[i];
for (int j = 0; j < rgammas[i].length; j++) {
rgammas[i][j] -= blacks256[i];
if (scale) {
rgammas[i][j] *= rgains[i];
}
}
}
}
if (degamma) {
for (int y = 0; y < height; y+=2) {
for (int dy = 0; dy < 2; dy++) {
int base = (y + dy)*width;
for (int x = 0; x < width; x+=2) {
for (int dx = 0; dx<2; dx++) {
int indx = base + x + dx;
int iv = (int) pixels[indx];
if (iv < 0) iv = 0;
else if (iv > 255) iv = 255;
pixels[indx] = (float) rgammas[(dy << 1) + 1 - dx][iv];
}
}
}
}
} else if (scale) {
for (int y = 0; y < height; y+=2) {
for (int dy = 0; dy < 2; dy++) {
int base = (y + dy)*width;
for (int x = 0; x < width; x+=2) {
for (int dx = 0; dx<2; dx++) {
int indx = base + x + dx;
pixels[indx] *= rgains[(dy << 1) + 1 - dx];
}
}
}
}
}
//TODO: Add flips here!
return pixels;
}
} }
/**
** -----------------------------------------------------------------------------**
** ImagejJp4TiffMulti.java
**
** Uses loci.format compatible readers for Elphel 8/16 bpp monochrome Tiff and
** JP4 files to read/parse camera files in a single url read operation by
** buffering camera data with Location.mapFile()
**
**
** Copyright (C) 2019 Elphel, Inc.
**
** -----------------------------------------------------------------------------**
**
** ImagejJp4TiffMulti.java is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see <http://www.gnu.org/licenses/>.
** -----------------------------------------------------------------------------**
**
*/
package com.elphel.imagej.readers;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicInteger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ij.ImagePlus;
import loci.formats.ClassList;
import loci.formats.FormatException;
public class ImagejJp4TiffMulti {
private static final int MAX_THREADS = 100;
private static final Logger LOGGER = LoggerFactory.getLogger(ClassList.class);
private final ImagejJp4Tiff [] imagejJp4Tiff = new ImagejJp4Tiff [MAX_THREADS];
public ImagePlus [] getMultiImages(
final String [] urls,
final ImagePlus [] imps,
final boolean scale,
final String std) throws IOException, FormatException // std - include non-elphel properties with prefix std
{
// final ImagePlus [] imps = new ImagePlus [urls.length];
final Thread[] threads = newThreadArray(MAX_THREADS);
final AtomicInteger indxAtomic = new AtomicInteger(0);
final AtomicInteger threadAtomic = new AtomicInteger(0);
for (int ithread = 0; ithread < threads.length; ithread++) {
threads[ithread] = new Thread() {
@Override
public void run() {
int threadIndx = threadAtomic.getAndIncrement();
if (imagejJp4Tiff[threadIndx] == null ) {
imagejJp4Tiff[threadIndx] = new ImagejJp4Tiff();
}
for (int indx = indxAtomic.getAndIncrement(); indx < urls.length; indx = indxAtomic.getAndIncrement())
{
try {
imps[indx] = imagejJp4Tiff[threadIndx].readTiffJp4(urls[indx], scale, std);
} catch (IOException e) {
LOGGER.error("getMultiImages IOException " + urls[indx]);
} catch (FormatException e) {
LOGGER.error("getMultiImages FormatException " + urls[indx]);
}
}
}
};
}
startAndJoin(threads);
return imps;
}
/* Create a Thread[] array as large as the number of processors available.
* From Stephan Preibisch's Multithreading.java class. See:
* http://repo.or.cz/w/trakem2.git?a=blob;f=mpi/fruitfly/general/MultiThreading.java;hb=HEAD
*/
private Thread[] newThreadArray(int maxCPUs) {
int n_cpus = Runtime.getRuntime().availableProcessors();
if (n_cpus>maxCPUs)n_cpus=maxCPUs;
return new Thread[n_cpus];
}
/* Start all given threads and wait on each of them until all are done.
* From Stephan Preibisch's Multithreading.java class. See:
* http://repo.or.cz/w/trakem2.git?a=blob;f=mpi/fruitfly/general/MultiThreading.java;hb=HEAD
*/
private static void startAndJoin(Thread[] threads)
{
for (int ithread = 0; ithread < threads.length; ++ithread)
{
threads[ithread].setPriority(Thread.NORM_PRIORITY);
threads[ithread].start();
}
try
{
for (int ithread = 0; ithread < threads.length; ++ithread)
threads[ithread].join();
} catch (InterruptedException ie)
{
throw new RuntimeException(ie);
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment