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 @@
<artifactId>loci_tools</artifactId>
<version>6.0.1</version>
</dependency>
<dependency>
<groupId>com.drewnoakes</groupId>
<artifactId>metadata-extractor</artifactId>
<version>2.11.0</version>
<type>java-source</type>
</dependency>
</dependencies>
<build>
......
......@@ -70,6 +70,8 @@ import java.util.concurrent.atomic.AtomicInteger;
import javax.swing.JFileChooser;
import javax.swing.filechooser.FileFilter;
import com.elphel.imagej.lwir.LwirReader;
import ij.CompositeImage;
import ij.IJ;
import ij.ImageJ;
......@@ -118,7 +120,8 @@ private Panel panel1,
panelClt3,
panelClt4,
panelClt5,
panelClt_GPU
panelClt_GPU,
panelLWIR
;
JP46_Reader_camera JP4_INSTANCE=null;
......@@ -384,6 +387,7 @@ private Panel panel1,
public static boolean DCT_MODE=false; //true; // show all buttons
public static boolean MODE_3D=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 ImagePlus DBG_IMP = null;
public ImagePlus CORRELATE_IMP = null;
......@@ -420,7 +424,7 @@ private Panel panel1,
instance = this;
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));
panel6 = new Panel();
......@@ -645,7 +649,13 @@ private Panel panel1,
addButton("ShowGPU", panelClt_GPU, color_conf_process);
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();
GUI.center(this);
......@@ -740,6 +750,15 @@ private Panel panel1,
IJ.showMessage("Error",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) {
......@@ -4783,6 +4802,29 @@ private Panel panel1,
}
TENSORFLOW_INFER_MODEL.test_tensorflow(keep_empty);
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
// End of buttons code
}
......
......@@ -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
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;
}
......@@ -1421,16 +1394,11 @@ public class JP46_Reader_camera extends PlugInFrame implements ActionListener {
timestamp=imp.getProperty("timestamp");
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];
r[0]=new Rectangle(0, 0, width,HEIGHTS[0]);
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]);
// assuming that (HEIGHTS[1]==0) && (HEIGHTS[2]!=0) == false
if (FLIPGV!=0){
if (HEIGHTS[1]>0) {
......@@ -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 (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),
// 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++) {
// 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;
r[i].height &=~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 {
if (((r[i].width & 1)==0 ) & (((r[i].x+FLIPH[i])&1)!=0)) r[i].width-=2;
r[i].width &=~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;
// 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
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;
}
}
......@@ -38,6 +38,7 @@ public class ElphelMeta {
long[] maker_note,
double exposure,
String date_time,
int bytesPerPixel,
boolean scale) throws FormatException, IOException {
if (property_table == null) {
property_table = new Hashtable<String, String> ();
......@@ -45,7 +46,7 @@ public class ElphelMeta {
if (!Double.isNaN(exposure)) {
property_table.put("EXPOSURE", String.format("%f",exposure));
}
if (date_time == null) {
if (date_time != null) {
property_table.put("DATE_TIME", date_time);
}
if (maker_note != null) {
......@@ -138,7 +139,7 @@ public class ElphelMeta {
property_table.put("FLIPH", String.format("%d",FLIPH));
property_table.put("FLIPV", String.format("%d",FLIPV));
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_VERT", String.format("%d",DCM_VERT));
property_table.put("BIN_HOR", String.format("%d",BIN_HOR));
......@@ -203,7 +204,12 @@ public class ElphelMeta {
/**adjusting gains to have the result picture in the range 0..256 */
min_gain=2.0*gains[0];
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
......
......@@ -39,7 +39,9 @@ import org.slf4j.LoggerFactory;
import loci.common.ByteArrayHandle;
import loci.common.Location;
import loci.common.RandomAccessInputStream;
import loci.formats.FormatException;
import loci.formats.FormatTools;
import loci.formats.in.MetadataLevel;
import loci.formats.in.TiffReader;
import loci.formats.meta.MetadataStore;
......@@ -85,6 +87,10 @@ public class ElphelTiffReader extends TiffReader{ // BaseTiffReader {
public static final String ELPHEL_PROPERTY_PREFIX = "ELPHEL_";
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. */
// protected transient boolean mergeSubIFDs = true; // false;
......@@ -104,6 +110,7 @@ public class ElphelTiffReader extends TiffReader{ // BaseTiffReader {
// private String inId = null; // to close Location.mapFile
private URL url = null; // save here actual URL when reading file to memory
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 String companionFile;
// private String description;
......@@ -117,20 +124,60 @@ public class ElphelTiffReader extends TiffReader{ // BaseTiffReader {
/** Constructs a new Tiff reader. */
public ElphelTiffReader() {
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;
LOGGER.info("ElphelTiffReader(), after supper(), mergeSubIFDs = true;");
}
// -- 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
public void setId(String id) throws FormatException, IOException {
LOGGER.debug("setId("+id+"). before super" );
file_initialized = false;
mapped_externally = false;
if (Location.getIdMap().containsKey(id)) {
LOGGER.debug("id '"+id+"' is already mapped" );
content_fileName = null; // id; // maybe set to null to handle externally?
LOGGER.info("Starting initFile() method, read file directly");
content_fileName = id; // id; // maybe set to null to handle externally?
mapped_externally = true;
LOGGER.info("Starting setId() method, read file directly");
super.setId(id);
} else {
// If URL, then read to memory, if normal file - use direct access
......@@ -192,6 +239,8 @@ public class ElphelTiffReader extends TiffReader{ // BaseTiffReader {
throws FormatException,
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");
super.initFile(id);
LOGGER.info("Ending initFile() method");
......@@ -216,7 +265,8 @@ public class ElphelTiffReader extends TiffReader{ // BaseTiffReader {
LOGGER.info("close("+fileOnly+") before super");
super.close(fileOnly); // curerent_id == null only during actual close?
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);
file_initialized = false;
}
......@@ -246,6 +296,14 @@ public class ElphelTiffReader extends TiffReader{ // BaseTiffReader {
double exposure = Double.NaN;
String date_time = null;
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) {
IFD exifIFD = exifIFDs.get(0);
tiffParser.fillInIFD(exifIFD);
......@@ -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(
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());
......@@ -281,6 +340,16 @@ public class ElphelTiffReader extends TiffReader{ // BaseTiffReader {
LOGGER.info("initStandardMetadata() - got "+tags.length+" tags");
}
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
boolean ij = checkCommentImageJ(comment);
// if (ij) parseCommentImageJ(comment);
......
......@@ -64,7 +64,8 @@ public class ImagejJp4Tiff {
// -- Constants --
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 --
......@@ -81,17 +82,29 @@ public class ImagejJp4Tiff {
// classList.addClass(com.elphel.imagej.readers.ElphelTiffJp4Reader.class);
classList.addClass(com.elphel.imagej.readers.ElphelJp4Reader.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) {
ServiceFactory factory = null;
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) {
// TODO Auto-generated catch block
e.printStackTrace();
}
OMEXMLService service = null;
try {
service = factory.getInstance(OMEXMLService.class);
......@@ -117,10 +130,13 @@ public class ImagejJp4Tiff {
// -- API methods --
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
// If URL, then read to memory, if normal file - use direct access
url = null;
......@@ -131,11 +147,12 @@ public class ImagejJp4Tiff {
} catch (MalformedURLException e) {
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) {
LOGGER.info("Read "+ path_url +" to memory first");
LOGGER.error("Read "+ path_url +" to memory first");
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"
if(content_disposition != null && content_disposition.indexOf("=") != -1) {
content_fileName = content_disposition.split("=")[1]; //getting value after '='
......@@ -147,7 +164,8 @@ public class ImagejJp4Tiff {
String suffix = slash < 0 ? "" : mime.substring(slash+1);
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);
if (is != null) is.close();
LOGGER.info("Bytes read: "+ inBytes.length);
......@@ -182,11 +200,17 @@ public class ImagejJp4Tiff {
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());
ip.setPixels(pixels);
ip.resetMinAndMax();
Hashtable<String, Object> meta_hash = reader.getGlobalMetadata();
String prefix = ElphelTiffReader.ELPHEL_PROPERTY_PREFIX;
String imageName = content_fileName; // path;
String imageNameKey = prefix+ElphelTiffReader.CONTENT_FILENAME;
......@@ -208,6 +232,7 @@ public class ImagejJp4Tiff {
return imp;
}
// -- Helper methods --
public static ImagePlus encodeProperiesToInfo(ImagePlus imp){
......@@ -231,5 +256,100 @@ public class ImagejJp4Tiff {
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