/**
** -----------------------------------------------------------------------------**
** JP46_Reader.java
**
** Reads Elphel Camera JP46 files into ImageJ, un-applying gamma and gains
**
** Copyright (C) 2010 Elphel, Inc.
**
** -----------------------------------------------------------------------------**
**
** JP46_Reader.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 .
** -----------------------------------------------------------------------------**
**
*/
import ij.*;
import ij.io.*;
import ij.plugin.*;
import ij.plugin.filter.*;
import ij.plugin.filter.PlugInFilter;
import ij.process.*;
import ij.gui.*;
import java.awt.*;
import java.util.*;
import java.awt.image.*;
import java.awt.event.*;
import java.io.*;
import ij.plugin.frame.*;
import ij.plugin.PlugIn;
import java.util.Random;
import java.util.Arrays;
import java.lang.Math.*;
import java.util.Vector;
import ij.text.*;
import javax.swing.*;
import javax.swing.filechooser.*;
/** This plugin opens images in Elphel JP46 format (opens as JPEG, reads MakerNote and converts). */
public class JP46_Reader extends PlugInFrame implements ActionListener {
Panel panel1;
Frame instance;
String arg;
static File dir;
public JP46_Reader() {
super("JP46 Reader");
if (IJ.versionLessThan("1.39t")) return;
if (instance!=null) {
instance.toFront();
return;
}
instance = this;
addKeyListener(IJ.getInstance());
setLayout(new GridBagLayout());
setBounds(50, 50, 250, 80);
panel1 = new Panel();
addButton("Open JP46...",panel1);
addButton("Open JP46 (no scale)...",panel1);
add(panel1);
//GUI.center(this);
setLocation(0,0);
setVisible(true);
}
void addButton(String label, Panel panel) {
Button b = new Button(label);
b.addActionListener(this);
b.addKeyListener(IJ.getInstance());
panel.add(b);
}
public void actionPerformed(ActionEvent e) {
String label = e.getActionCommand();
/** nothing */
if (label==null) return;
/** 'Open JP46' button */
if (label.equals("Open JP46...")) {
read_jp46(arg,true);
}else if (label.equals("Open JP46 (no scale)...")) {
read_jp46(arg,false);
}
IJ.showStatus("DONE");
}
public void read_jp46(String arg, Boolean scale) {
JFileChooser fc=null;
//try {fc = new JFileChooser();}
fc = new JFileChooser();
//catch (Throwable e) {IJ.error("This plugin requires Java 2 or Swing."); return;}
fc.setMultiSelectionEnabled(true);
if (dir==null) {
String sdir = OpenDialog.getDefaultDirectory();
if (sdir!=null)
dir = new File(sdir);
}
if (dir!=null)
fc.setCurrentDirectory(dir);
int returnVal = fc.showOpenDialog(IJ.getInstance());
if (returnVal!=JFileChooser.APPROVE_OPTION)
return;
File[] files = fc.getSelectedFiles();
if (files.length==0) { // getSelectedFiles does not work on some JVMs
files = new File[1];
files[0] = fc.getSelectedFile();
}
String path = fc.getCurrentDirectory().getPath()+Prefs.getFileSeparator();
dir = fc.getCurrentDirectory();
Opener opener = new Opener();
for (int i=0; i>24)/256.0;
gammas[i]=((MakerNote[i+4]>>16)&0xff)/100.0;
gamma_scales[i]=MakerNote[i+4] & 0xffff;
}
IJ.showStatus("R=" +(0.001*((int)(1000*gains[0])))+
" G=" +(0.001*((int)(1000*gains[1])))+
" Gb="+(0.001*((int)(1000*gains[2])))+
" B=" +(0.001*((int)(1000*gains[3])))+
" Gamma[0]="+(0.001*((int)(1000*gammas[0])))+
" Black[0]="+((int) (256*blacks[0])));
String info=new String();
info+="Gain\t"+ IJ.d2s(gains[0],3) + "\t"+ IJ.d2s(gains[1],3) + "\t"+ IJ.d2s(gains[2],3) + "\t"+ IJ.d2s(gains[3],3) + "\n"+
"Gamma\t"+IJ.d2s(gammas[0],3) + "\t"+IJ.d2s(gammas[1],3) + "\t"+IJ.d2s(gammas[2],3) + "\t"+IJ.d2s(gammas[3],3) + "\n"+
"Black\t"+IJ.d2s(blacks[0],3) + "\t"+IJ.d2s(blacks[1],3) + "\t"+IJ.d2s(blacks[2],3) + "\t"+IJ.d2s(blacks[3],3) + "\n";
if (MakerNote.length>=12) {
WOI_LEFT= MakerNote[8]&0xffff;
WOI_WIDTH= MakerNote[8]>>16;
WOI_TOP= MakerNote[9]&0xffff;
WOI_HEIGHT= MakerNote[9]>>16;
FLIPH= MakerNote[10] & 1;
FLIPV= (MakerNote[10]>> 1) & 1;
BAYER_MODE=(MakerNote[10]>> 2) & 3;
COLOR_MODE=(MakerNote[10]>> 4) & 0x0f;
DCM_HOR= (MakerNote[10]>> 8) & 0x0f;
DCM_VERT= (MakerNote[10]>>12) & 0x0f;
BIN_HOR= (MakerNote[10]>>16) & 0x0f;
BIN_VERT= (MakerNote[10]>>20) & 0x0f;
info+="WOI_LEFT\t" + WOI_LEFT+"\t\t\t\n"+
"WOI_WIDTH\t"+ WOI_WIDTH+"\t\t\t\n"+
"WOI_TOP\t" + WOI_TOP+"\t\t\t\n"+
"WOI_HEIGHT\t"+ WOI_HEIGHT+"\t\t\t\n"+
"FLIP_HOR\t"+ (FLIPH!=0)+"\t\t\t\n"+
"FLIP_VERT\t"+ (FLIPV!=0)+"\t\t\t\n"+
"BAYER_MODE\t"+ BAYER_MODE+"\t\t\t\n"+
"COLOR_MODE\t"+ COLOR_MODE+"\t\t\t\n"+
"DECIM_HOR\t"+ DCM_HOR+"\t\t\t\n"+
"DECIM_VERT\t"+ DCM_VERT+"\t\t\t\n"+
"BIN_HOR\t"+ BIN_HOR+"\t\t\t\n"+
"BIN_VERT\t"+ BIN_VERT+"\t\t\t\n";
/*
putlong_meta_raw_irq(get_imageParamsThis(P_WOI_LEFT) | (get_imageParamsThis(P_WOI_WIDTH)<<16), maker_offset+32);
putlong_meta_raw_irq(get_imageParamsThis(P_WOI_TOP) | (get_imageParamsThis(P_WOI_HEIGHT)<<16), maker_offset+36);
putlong_meta_raw_irq((get_imageParamsThis(P_FLIPH) & 1) |
((get_imageParamsThis(P_FLIPV)<<1) & 2) |
((get_imageParamsThis(P_BAYER)<<2) & 0xc) |
((get_imageParamsThis(P_COLOR)<<4) & 0xF0) |
((get_imageParamsThis(P_DCM_HOR)<<8) & 0xF00) |
((get_imageParamsThis(P_DCM_VERT)<<12) & 0xF000) |
((get_imageParamsThis(P_BIN_HOR)<<16) & 0xF0000) |
((get_imageParamsThis(P_BIN_VERT)<<20) & 0xF00000), maker_offset+40);
*/
}
TextWindow tw = new TextWindow(imp.getTitle()+" info", "Parameter\tRed\tGreen(R)\tGreen(B)\tBlue",info, 400, (MakerNote.length>=12)?400:160);
//tw.setLocation(0,0);
for (i=0;i<4;i++) rgammas[i]=elphel_gamma_calc (gammas[i], blacks[i], gamma_scales[i]);
}
/**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]);
}
for (i=0;i<4;i++) gains[i]/=min_gain;
for (i=0;i<4;i++) blacks256[i]=256.0*blacks[i];
/**
String tstring=new String();
tstring+="\nrgammas[0]=";
for (i=0;i<256;i++) {
tstring+=" " + (((int) (100*rgammas[0][i]))/100.0);
if ((i & 0x0f)==0x0f) tstring+="\n";
}
IJ.showMessage("jp46Reorder Debug ",tstring);
*/
ImageProcessor ip = imp.getProcessor();
if (FLIPH!=0) ip.flipHorizontal(); /** To correct Bayer */
if (FLIPV!=0) ip.flipVertical(); /** To correct Bayer */
int width = ip.getWidth();
int height = ip.getHeight();
int yb,y,xb,x,offset;
float [] pixels = (float[])ip.getPixels();
float [][] macroblock=new float[16][16];
for (yb=0;yb<(height>>4); yb++) for (xb=0;xb<(width>>4); xb++) { /** iterating macroblocks */
for (y=0;y<16;y++) {
offset=((yb<<4)+y)*width+(xb<<4);
for (x=0;x<16;x++) {
macroblock[((y<<1)&0xe) | ((y>>3) & 0x1)][((x<<1)&0xe) | ((x>>3) & 0x1)]=pixels[offset+x];
}
}
/** apply gammas here */
if (MakerNote !=null) {
if (scale) {
for (y=0;y<16;y+=2) for (x=0;x<16;x+=2) {
i=(int) macroblock[y ][x ]; if (i<0) i=0 ; else if (i>255) i=255;
macroblock[y ][x ]= (float) (((rgammas[1][i])-blacks256[1])/gains[1]);
i=(int) macroblock[y ][x+1]; if (i<0) i=0 ; else if (i>255) i=255;
macroblock[y ][x+1]= (float) (((rgammas[0][i])-blacks256[0])/gains[0]);
i=(int) macroblock[y+1][x ]; if (i<0) i=0 ; else if (i>255) i=255;
macroblock[y+1][x ]= (float) (((rgammas[3][i])-blacks256[3])/gains[3]);
i=(int) macroblock[y+1][x+1]; if (i<0) i=0 ; else if (i>255) i=255;
macroblock[y+1][x+1]= (float) (((rgammas[2][i])-blacks256[2])/gains[2]);
}
}else{
for (y=0;y<16;y+=2) for (x=0;x<16;x+=2) {
i=(int) macroblock[y ][x ]; if (i<0) i=0 ; else if (i>255) i=255;
macroblock[y ][x ]= (float) ((rgammas[1][i])-blacks256[1]);
i=(int) macroblock[y ][x+1]; if (i<0) i=0 ; else if (i>255) i=255;
macroblock[y ][x+1]= (float) ((rgammas[0][i])-blacks256[0]);
i=(int) macroblock[y+1][x ]; if (i<0) i=0 ; else if (i>255) i=255;
macroblock[y+1][x ]= (float) ((rgammas[3][i])-blacks256[3]);
i=(int) macroblock[y+1][x+1]; if (i<0) i=0 ; else if (i>255) i=255;
macroblock[y+1][x+1]= (float) ((rgammas[2][i])-blacks256[2]);
}
}
}
for (y=0;y<16;y++) {
offset=(yb<<4)+y;
for (x=0;x<16;x++) {
ip.setf((xb<<4)+ x, offset, macroblock[y][x]);
}
}
}
imp.draw();
imp.show();
return;
}
/** reverses gamma calculations in the camera
returns double[] table , in the range 0.0..255.996
*/
double [] elphel_gamma_calc (double gamma, double black, long gamma_scale) {
int i;
double x, black256 ,k;
int[] gtable = new int[257];
double[] rgtable =new double[256];
int ig;
black256=black*256.0;
k=1.0/(256.0-black256);
if (gamma < 0.13) gamma=0.13;
if (gamma >10.0) gamma=10.0;
for (i=0; i<257; i++) {
x=k*(i-black256);
if (x < 0.0 ) x=0.0;
ig= (int) (0.5+65535.0*Math.pow(x,gamma));
ig=(ig* (int) gamma_scale)/0x400;
if (ig > 0xffff) ig=0xffff;
gtable[i]=ig;
}
/** now gtable[] is the same as was used in the camera */
/** FPGA was using linear interpolation between elements of the gamma table, so now we'll reverse that process */
// double[] rgtable =new double[256];
int indx=0;
double outValue;
for (i=0; i<256; i++ ) {
outValue=128+(i<<8);
while ((gtable[indx+1]=256) rgtable[i]=65535.0/256;
else if (gtable[indx+1]==gtable[indx]) rgtable[i]=i;
else rgtable[i]=indx+(1.0*(outValue-gtable[indx]))/(gtable[indx+1] - gtable[indx]);
}
return rgtable;
}
long[] readElphelMakerNote(String directory, String fileName, int len) throws IOException {
int ExifOffset=0x0c;
int i,offs;
int [] sig= {0x92 ,0x7c, /** MakerNote*/
0x00 ,0x04, /** type (long)*/
0x00 ,0x00 ,0x00 ,0x08 }; /** number*/
/** should always read all MakerNote - verify that format did not change (edit here when it does). */
sig[7]=len & 0xff;
sig[6]=(len>>8) & 0xff;
sig[5]=(len>>16) & 0xff;
sig[4]=(len>>24) & 0xff;
RandomAccessFile in = new RandomAccessFile(directory + fileName, "r");
byte[] head = new byte[4096]; /** just read the beginning of the file */
in.readFully(head);
if ((head[ExifOffset]!=0x4d) || (head[ExifOffset+1]!=0x4d)) {
IJ.showMessage("JP46 Reader", "Exif Header not found in "+directory + fileName);
return null;
}
/** search for MakerNote */
i= ExifOffset+2;
for (i= ExifOffset+2; i< (head.length-sig.length); i++ ) {
if ((((head[i ]^sig[0]) & 0xff)==0) && (((head[i+1]^sig[1]) & 0xff)==0) && (((head[i+2]^sig[2]) & 0xff)==0) && (((head[i+3]^sig[3]) & 0xff)==0) &&
(((head[i+4]^sig[4]) & 0xff)==0) && (((head[i+5]^sig[5]) & 0xff)==0) && (((head[i+6]^sig[6]) & 0xff)==0) && (((head[i+7]^sig[7]) & 0xff)==0)) break;
if (i==0xa0) {
}
}
i+=sig.length;
if (i>= (head.length-4)) {
/** IJ.showMessage("JP46 Reader", "MakerNote tag not found in "+directory + fileName+ ", finished at offset="+i); // re-enable with DEBUG_LEVEL*/
return null;
}
offs=ExifOffset+((head[i]<<24) |(head[i+1]<<16)| (head[i+2]<<8) | head[i+3]);
// IJ.showMessage("JP46 Reader Debug", "MakerNote starts at offset "+offs);
if (offs > (head.length-len) ) {
IJ.showMessage("JP46 Reader", "Error: MakerNote starts too far - at offset "+offs+", while we read only "+ head.length+ "bytes");
return null;
}
long[] note=new long[len];
for (i=0; i>16;
g = (c&0xff00)>>8;
b = c&0xff;
if (!((r==g)&&(g==b))) {
IJ.error("Warning: color image");
return;
}
}
}
IJ.showStatus("Converting to 32-bits");
new ImageConverter(imp).convertToGray32();
}
}