Commit 63782ff5 authored by Andrey Filippov's avatar Andrey Filippov

initial release

parent f233f5d1
......@@ -12,12 +12,12 @@
<relativePath />
</parent>
<groupId>sc.fiji</groupId>
<artifactId>Process_Pixels</artifactId>
<groupId>com.elphel</groupId>
<artifactId>imagej-elphel</artifactId>
<version>1.0.0</version>
<name>plugins/Process_Pixels.jar</name>
<description>A Maven project implementing an ImageJ 1.x plugin</description>
<name>plugins/ImageJ_Elphel.jar</name>
<description>A Maven project implementing imagej-elphel plugin</description>
<dependencies>
<dependency>
......@@ -25,6 +25,18 @@
<artifactId>ij</artifactId>
<version>${imagej1.version}</version>
</dependency>
<dependency>
<groupId>org.kie.modules</groupId>
<artifactId>
org-apache-commons-configuration-main
</artifactId>
<version>6.1.0.Beta2</version>
</dependency>
<dependency>
<groupId>org.kie.modules</groupId>
<artifactId>org-apache-commons-lang-main</artifactId>
<version>6.1.0.Beta2</version>
</dependency>
</dependencies>
<build>
......@@ -46,8 +58,8 @@
</testResources>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<groupId>com.elphel</groupId>
<artifactId>imagej-elphel</artifactId>
<executions>
<execution>
<goals>
......@@ -64,12 +76,12 @@
<developers>
<developer>
<id>dscho</id>
<name>Johannes Schindelin</name>
<email>johannes.schindelin@gmx.de</email>
<url>http://loci.wisc.edu/people/johannes-schindelin</url>
<organization>UW-Madison LOCI</organization>
<organizationUrl>http://loci.wisc.edu/</organizationUrl>
<id>AndreyFilippov</id>
<name>Andrey Filippov</name>
<email>andrey@elphel.com</email>
<url>http://blog.elphel.com/andrey</url>
<organization>Elphel, Inc.</organization>
<organizationUrl>http://www3.elphel.com</organizationUrl>
<roles>
<role>architect</role>
<role>developer</role>
......@@ -87,10 +99,10 @@
</repositories>
<scm>
<connection>scm:git:git://github.com/imagej/minimal-ij1-plugin</connection>
<developerConnection>scm:git:git@github.com:imagej/minimal-ij1-plugin</developerConnection>
<connection>scm:git:git://github.com/Elphel/imagej-elphel</connection>
<developerConnection>sscm:git:https://github.com/Elphel/imagej-elphel</developerConnection>
<tag>HEAD</tag>
<url>https://github.com/imagej/minimal-ij1-plugin</url>
<url>https://github.com/Elphel/imagej-elpheln</url>
</scm>
</project>
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
** -----------------------------------------------------------------------------**
** BlueLeak.java
**
** Mitigating lens poor performance for blue light, especially noticeable in
** the dark areas near overexposed ones.
**
** Copyright (C) 2014 Elphel, Inc.
**
** -----------------------------------------------------------------------------**
**
** EyesisCorrectionParameters.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/>.
** -----------------------------------------------------------------------------**
**
*/
public class BlueLeak {
private double [][] rgb_in;
private double [] yrg;
private double [] blue;
private int width;
private int height;
private int length;
EyesisCorrectionParameters.ColorProcParameters colorProcParameters;
private showDoubleFloatArrays SDFA_INSTANCE;
private int [] dirs20;
private double [] weights20;
private int [] dirs8;
private int [] dirs4;
private int debugLevel;
private String dbgImgTitle;
private int margin=50; // make configurable?
private int xMax,yMax;
public BlueLeak(
EyesisCorrectionParameters.ColorProcParameters colorProcParameters,
double [][] rgb,
int width,
showDoubleFloatArrays SDFA_INSTANCE,
String dbgImgTitle,
int debugLevel){
length=rgb[0].length;
rgb_in=rgb;
this.width=width;
height=length/width;
this.SDFA_INSTANCE=SDFA_INSTANCE;
this.colorProcParameters=colorProcParameters;
if (debugLevel>2) System.out.println("width="+width+" height="+height+" length="+length);
int [] dirs8 = {-width,-width+1, 1, width+1, width,width-1,-1,-width-1};
this.dirs8=dirs8;
int [] dirs4 = {-width, 1, width,-1};
this.dirs4=dirs4;
int [] dirs20 = {
-width,-width+1, 1, width+1, width,width-1,-1,-width-1,
-2*width-1,-2*width,-2*width+1,
-width+2, 2, width+2,
2*width-1,2*width,2*width+1,
-width-2, -2, width-2
};
this.dirs20=dirs20;
double [] weights20={
1.0, 0.75, 1.0, 0.75, 1.0, 0.75, 1.0, 0.75,
0.5, 0.5, 0.5, 0.5,
0.5, 0.5, 0.5, 0.5,
0.5, 0.5, 0.5, 0.5,
0.5, 0.5, 0.5, 0.5};
this.weights20=weights20;
this.debugLevel=debugLevel; //+1; /*** CHANGE later ***/
this.dbgImgTitle=dbgImgTitle;
this.xMax=width-margin;
this.yMax=height-margin;
this.yrg=getYrg();
}
public enum BLUE_STATE{
UNDEF,
OVEREXP,
EXPANDED,
PROBLEM,
SOLUTION,
SOLVED,
TMP
}
private BLUE_STATE[] markBlueOverexp(double [] blueOverExp){
BLUE_STATE[] result= new BLUE_STATE[blueOverExp.length];
for (int i=0;i<result.length;i++){
result[i]=Double.isNaN(blueOverExp[i])?BLUE_STATE.UNDEF:BLUE_STATE.OVEREXP;
}
return result;
}
private List<Integer> createInitialList(
BLUE_STATE[] state,
BLUE_STATE thisState,
BLUE_STATE neighborState,
boolean use8
){
List <Integer> pixelList=new ArrayList<Integer>();
int [] dirs=use8?dirs8:dirs4;
for (int i=0;i<length;i++){
if (state[i]!=thisState) continue;
for (int dir=0; dir<dirs.length; dir++){
int index=i+dirs[dir];
int y=index/width;
if ((y<margin) || (y>=yMax)) continue;
int x=index%width;
if ((x<margin) || (x>=xMax)) continue;
if (state[index]==neighborState){
pixelList.add(new Integer(i));
break;
}
}
}
return pixelList;
}
private double[] findBlueSolutions(
double [] blueOverExp,
boolean blueLeakNoHint,
boolean blueLeakNoBrighten,
boolean use8
){
int [] dirs=use8?dirs8:dirs4;
BLUE_STATE[] state= markBlueOverexp(blueOverExp);
List<Integer> pixelList= createInitialList(
state,
BLUE_STATE.UNDEF,
BLUE_STATE.OVEREXP,
use8);
// Shrink overexposed areas by blueOverShrink steps, mark them as UNDEF
for (int nShrink=0;nShrink<colorProcParameters.blueOverShrink; nShrink++){
int len=pixelList.size();
for (int i=0;i<len;i++){
int oldIndex=pixelList.remove(0);
for (int dir=0; dir<dirs.length; dir++){
int index=oldIndex+dirs[dir];
int y=index/width;
if ((y<margin) || (y>=yMax)) continue;
int x=index%width;
if ((x<margin) || (x>=xMax)) continue;
if (state[index]==BLUE_STATE.OVEREXP){
pixelList.add(new Integer(index));
state[index]=BLUE_STATE.UNDEF;
}
}
}
if (debugLevel>2) System.out.println("Shrinking blue overexposed area, step "+nShrink+", number of pixels in the list: "+pixelList.size());
}
double [] yRef=new double [length];
double [] leak=new double [length];
// double [] yrg=getYrg();
for (int i=0;i<length;i++){
yRef[i] = ((state[i]==BLUE_STATE.OVEREXP) || (state[i]==BLUE_STATE.EXPANDED)) ?yrg[i]:Double.NaN;
leak[i] = yRef[i];
}
List<Integer> solutionList=new ArrayList<Integer>();
int problemPixels=0;
double rate1=1.0/colorProcParameters.blueBandWidth;
double rate0=1.0/colorProcParameters.blueBandWidthDark;
if (debugLevel>1){
String [] channel_blue_titles={"orig. blue","yRef","state"};
double [][] dbgImg= new double [3][];
dbgImg[0]=rgb_in[2];
dbgImg[1]=yRef;
dbgImg[2]=dbgStateToDouble(state);
SDFA_INSTANCE.showArrays(dbgImg, width, height, true, "blue_overexp_expanded", channel_blue_titles);
}
int numStep=0;
while (!pixelList.isEmpty()){
boolean lookForSolution=numStep>=colorProcParameters.blueOverGrow; // first passes - just expand, do not look for solution
//pixelList has pixels still UNDEFINED near OVEREXP (or problem/solution)
if (debugLevel>2) System.out.println("Starting step "+numStep+", number of pixels in the list: "+pixelList.size()+", in solutions: "+solutionList.size());
int [] solutionFront=new int[pixelList.size()]; // 0-problem, 1 solution, -1 - none (overexposed area shrank completely)
for (int i=0;i<solutionFront.length;i++){
int oldIndex=pixelList.get(i);
if (debugLevel>2) if (i==0) System.out.println ("For i==0 oldIndex="+oldIndex+", state["+oldIndex+"]="+state[oldIndex]);
if (state[oldIndex]!=BLUE_STATE.UNDEF){
System.out.println("BUG: State is not UNDEF for i="+i+", oldIndex="+oldIndex+" x="+(oldIndex%width)+", y="+oldIndex/width);
return null; //rgb_in[2];
}
double w=0.0;
double avgRef=0.0;
double avgLeak=0.0;
for (int dir=0; dir<dirs20.length; dir++){
int index=oldIndex+dirs20[dir];
int y=index/width;
if ((y<margin) || (y>=yMax)) continue;
int x=index%width;
if ((x<margin) || (x>=xMax)) continue;
if (state[index]!=BLUE_STATE.UNDEF){ // can be problem/solution or overexp?
avgRef+= weights20[dir]*yRef[index];
avgLeak+=weights20[dir]*leak[index];
w+=weights20[dir];
}
}
if (w>0.0){
avgRef/=w;
avgLeak/=w;
yRef[oldIndex]=avgRef;
if (lookForSolution){
// double rate=rate0+ yrg[oldIndex]/avgRef*rate1;
double rate=avgRef*rate0+ yrg[oldIndex]*rate1;
leak[oldIndex]=avgLeak-rate;
solutionFront[i]=(leak[oldIndex]<=yrg[oldIndex])?1:0;
} else {
leak[oldIndex]= avgRef;
solutionFront[i]=0;
}
} else { // now after shrinking it is OK
// System.out.println("BUG: Could not find any neighbors.");
solutionFront[i]=-1;
}
}
// Second pass - apply new states
for (int i=0;i<solutionFront.length;i++){
int index=pixelList.remove(0);
if (solutionFront[i]>0){
state[index]=BLUE_STATE.SOLUTION;
solutionList.add(new Integer(index));
} if (solutionFront[i]==0){
state[index]=BLUE_STATE.PROBLEM;
pixelList.add(new Integer(index));
problemPixels++;
} // Do nothing if <0 - keep undefined, do not add to list
}
// Find fresh cells near pixelList members (destructive)
int len=pixelList.size();
for (int i=0;i<len;i++) {
int oldIndex=pixelList.remove(0);
for (int dir=0; dir<dirs.length; dir++){
int index=oldIndex+dirs[dir];
int y=index/width;
if ((y<margin) || (y>=yMax)) continue;
int x=index%width;
if ((x<margin) || (x>=xMax)) continue;
if (state[index]==BLUE_STATE.UNDEF){
pixelList.add(new Integer(index));
state[index]=BLUE_STATE.TMP; // will be removed before return
}
}
}
// restore state to undefined
for (Iterator<Integer> iter= pixelList.iterator(); iter.hasNext();){
int i=iter.next();
assert (state[i]==BLUE_STATE.TMP):"BLUE_STATE.TMP expected, got "+state[i]+" for i="+i+", x="+(i%width)+" y="+(i/width);
state[i]=BLUE_STATE.UNDEF;
}
numStep++;
}
if (debugLevel>2) System.out.println("Found "+problemPixels+" blue problem pixels, "+solutionList.size()+" pixels on solutions front");
// just verify they are all OK
for (Iterator<Integer> iter= solutionList.iterator(); iter.hasNext();){
int index=iter.next();
assert(state[index]==BLUE_STATE.SOLUTION):"Expected SOLUTION, got "+state[index];
}
if ((debugLevel>1)){
String [] channel_blue_titles={"orig. blue","Yrg", "yRef","leak","state"};
double [][] dbgImg= new double [5][];
dbgImg[0]=rgb_in[2];
dbgImg[1]=yrg;
dbgImg[2]=yRef;
dbgImg[3]=leak;
dbgImg[4]=dbgStateToDouble(state);
SDFA_INSTANCE.showArrays(dbgImg, width, height, true, "blue_overexp_expanded__"+numStep, channel_blue_titles);
}
blue=new double[length];
for (int i=0;i<length;i++) blue[i]=rgb_in[2][i]; // just copy
// if ((debugLevel>1)) return blue;
// re-use yRef to hold blue/yrg ratio, deducted from solution. Actual blue/Yrg ration will gradually shift to
// blue_neutral_ratio farther from solution to prevent colored spikes far from solution origin
for (Iterator<Integer> iter= solutionList.iterator(); iter.hasNext();){
int index=iter.next();
yRef[index]=blue[index]/yrg[index];
}
// repeat filling problem areas
int numSolStep=0;
int numSolved=0;
double fadeRate=(colorProcParameters.blueSolutionRadius>0)?(1.0/colorProcParameters.blueSolutionRadius):0.0;
while (!solutionList.isEmpty()){
int numSolvedPass=0;
// Find problem pixels around, add to list, mark as TMP, add to list (while removing original) //EXPANDED
int len=solutionList.size();
for (int i=0;i<len;i++){
int oldIndex=solutionList.remove(0);
for (int dir=0; dir<dirs.length; dir++){
int index=oldIndex+dirs[dir];
int y=index/width;
if ((y<margin) || (y>=yMax)) continue;
int x=index%width;
if ((x<margin) || (x>=xMax)) continue;
if (state[index]==BLUE_STATE.PROBLEM){
state[index]=BLUE_STATE.TMP;
solutionList.add(new Integer(index));
}
}
}
if (debugLevel>2) System.out.println(numSolStep+": new front length="+solutionList.size()+", old length="+len);
// first non-destructive pass through the list - calculate yRef[] (applying fadeRate), blue[]
for (Iterator<Integer> iter= solutionList.iterator(); iter.hasNext();){
int oldIndex=iter.next();
assert(state[oldIndex]==BLUE_STATE.TMP):"expected TMP, got"+state[iter.next()];
double w=0.0;
double avg=0.0;
for (int dir=0; dir<dirs20.length; dir++){
int index=oldIndex+dirs20[dir];
int y=index/width;
if ((y<margin) || (y>=yMax)) continue;
int x=index%width;
if ((x<margin) || (x>=xMax)) continue;
if ((state[index]==BLUE_STATE.SOLUTION) || (state[index]==BLUE_STATE.SOLVED)){
avg+= weights20[dir]*yRef[index];
w+=weights20[dir];
}
}
if (w>0){
avg/=w;
avg=(1-fadeRate)*avg+fadeRate*colorProcParameters.blueNeutral;
yRef[oldIndex]=avg;
// blue[oldIndex]=yrg[oldIndex]*avg;
double newBlue=yrg[oldIndex]*avg;
if (!blueLeakNoBrighten || (blue[oldIndex] > newBlue)) blue[oldIndex]=newBlue;
numSolvedPass++;
numSolved++;
} else {
System.out.println("BUG: Could not find any neighbors when applying solution, state[oldIndex]="+state[oldIndex]); // Got here many times
}
}
// second non-destructive pass through the list - change state to "SOLVED"
for (Iterator<Integer> iter= solutionList.iterator(); iter.hasNext();){
state[iter.next()]=BLUE_STATE.SOLVED;
}
if (debugLevel>2) System.out.println("Applying solution, step #"+(numSolStep++)+" solved on this pass "+numSolvedPass+" pixels.");
}
if (debugLevel>1) System.out.println("Appled solution for blue color leak, "+(numSolStep)+" steps, solved "+numSolved+" pixels.");
// fix small "windows" where there are no blue color reference pixels in the closed area
//blueLeakNoBrighten
if (blueLeakNoHint) {
int numSmallFixed=0;
for (int i=0;i<length;i++) if (state[i]== BLUE_STATE.PROBLEM){
double newBlue=yrg[i]*colorProcParameters.blueNeutral;
if (!blueLeakNoBrighten || (blue[i] > newBlue)) blue[i]=newBlue;
numSmallFixed++;
}
if (debugLevel>1) System.out.println("Corrected"+numSmallFixed+" blue pixels near overexposed blue");
}
if (dbgImgTitle!=null){
String [] channel_blue_titles={"orig. blue","corrected blue","state"};
double [][] dbgImg= new double [3][];
dbgImg[0]=rgb_in[2];
dbgImg[1]=blue;
dbgImg[2]=dbgStateToDouble(state);
SDFA_INSTANCE.showArrays(dbgImg, width, height, true, dbgImgTitle, channel_blue_titles);
}
return blue; // corrected blue channel
}
private double [] dbgStateToDouble(BLUE_STATE[] state){
double [] result = new double[length];
for (int i=0;i<length;i++){
if (state[i] == BLUE_STATE.OVEREXP) result[i]=0.2;
else if (state[i] == BLUE_STATE.EXPANDED) result[i]=0.6;
else if (state[i] == BLUE_STATE.PROBLEM) result[i]=0.4;
else if (state[i] == BLUE_STATE.SOLUTION) result[i]=0.8;
else if (state[i] == BLUE_STATE.SOLVED) result[i]=1.0;
else if (state[i] == BLUE_STATE.TMP) result[i]=1.2;
}
return result;
}
private double [] getYrg (){
double kr=colorProcParameters.kr;
double kb=colorProcParameters.kb;
double kg=1.0-kr-kb;
double krg=kr+kg;
kr/=krg;
kg/=krg;
double [] yrg=new double [rgb_in[0].length];
for (int i=0;i<yrg.length;i++) {
yrg[i]=kr*rgb_in[0][i]+kg*rgb_in[1][i];
}
return yrg;
}
private double [] overexposed(
EyesisCorrectionParameters.ColorProcParameters colorProcParameters,
double [] dpixels,
int width
){
int size=colorProcParameters.satDetSquareSize;
double [] mask=overexposed(
colorProcParameters,
dpixels,
width,
0,
0);
for (int shftY=0; shftY<size; shftY+=size/2)
for (int shftX=0; shftX<size; shftX+=size/2)
if ((shftY!=0) || (shftX!=0)){
double [] mask1=overexposed(
colorProcParameters,
dpixels,
width,
shftY,
shftY);
for (int i=0;i<mask.length;i++){
if (Double.isNaN(mask[i])){
mask[i]=mask1[i];
} else if (!Double.isNaN(mask1[i])){
if (Math.abs(mask[i]-dpixels[i])>Math.abs(mask1[i]-dpixels[i])){
mask[i]=mask1[i];
}
}
}
}
return mask;
}
private double [] overexposed(
EyesisCorrectionParameters.ColorProcParameters colorProcParameters,
double [] dpixels,
int width,
int shftX,
int shftY
){
int height=dpixels.length/width;
int size=colorProcParameters.satDetSquareSize;
double [] mask=new double[dpixels.length];
for (int i=0;i<dpixels.length;i++) mask[i]=Double.NaN;
double avrg=0.0;
for (int i=0;i<dpixels.length;i++) avrg+=dpixels[i];
avrg/=dpixels.length;
double [] tile=new double [size*size];
// int dbgCount=0;
for (int y0=shftY; y0<(height-size/2); y0+=size){
int ymax=size;
if ((y0+ymax) > height) ymax=height-y0;
for (int x0=shftX;x0<(width-size/2);x0+=size){
int len=0;
int indx;
int xmax=size;
if ((x0+xmax) > width) xmax=width-x0;
for (int y=0;y<ymax;y++){
indx=(y+y0)*width+x0;
for (int x=0;x<xmax;x++){
tile[len++]=dpixels[indx++];
}
}
double tileAvg=0.0;
for (int i=0;i<len;i++) tileAvg+=tile[i];
tileAvg/=len;
if (tileAvg<(avrg*colorProcParameters.satDetMinFrac)) continue; // too small value to be an overexposed tile
int numFit=0;
double wnd_min=tileAvg - avrg*colorProcParameters.satDetRelDiff;
double wnd_max=tileAvg + avrg*+colorProcParameters.satDetRelDiff;
for (int i=0;i<len;i++) if ((tile[i]>=wnd_min) && (tile[i]<=wnd_max)) numFit++;
if (numFit< (len*colorProcParameters.satDetPartInside)) continue; // too small number of pixels fit close to average;
len=0;
double wnd_min_fin=tileAvg-avrg*colorProcParameters.satDetFinRelDiff;
double wnd_max_fin=tileAvg+avrg*colorProcParameters.satDetFinRelDiff;
for (int y=0;y<ymax;y++){
indx=(y+y0)*width+x0;
for (int x=0;x<xmax;x++){
double d=tile[len++];
double dpx=dpixels[indx];
mask[indx++] = ((d>=wnd_min_fin) && (d<=wnd_max_fin))?dpx:Double.NaN;
}
}
}
}
return mask;
}
private void overexposedGrow(
double [] dpixels,
double [] ovrexp,
BLUE_STATE[] state,
List<Integer> pixelList,
double relDiff,
boolean addBrighter,
double newWeight,
int numSteps,
boolean use8) {
int [] dirs=use8?dirs8:dirs4;
List<Integer> laterCheckList=new ArrayList<Integer>(); // put pixels that do not match current filter, but may be used later
// TODO: move markBlueOverexp and createInitialList outside as this method can be called several times
if (debugLevel>1) System.out.println("overexposedGrow, relDiff="+relDiff+", addBrighter="+addBrighter);
for (int step=0;(step<numSteps) && !pixelList.isEmpty();step++){
// first pass - consume pixelList, re-add satisfying ones, mark them as TMP
int len=pixelList.size();
for (int i=0;i<len;i++){
int oldIndex=pixelList.remove(0);
// first find if this pixel falls withing overexposed classification, then use weighted average of known overexposed pixels
// and pixel value (if it is inside range)
for (int dir=0; dir<dirs.length; dir++){
int index=oldIndex+dirs[dir];
int y=index/width;
if ((y<margin) || (y>=yMax)) continue;
int x=index%width;
if ((x<margin) || (x>=xMax)) continue;
if (state[index]!=BLUE_STATE.OVEREXP) continue;
double d=ovrexp[index]-dpixels[oldIndex];
double abs_d=Math.abs(d);
if ((abs_d>(relDiff*ovrexp[index])) && (!addBrighter || (d>0.0))) continue; // not eligible
state[oldIndex]=BLUE_STATE.TMP;
break;
}
// added, now calculate the overexposed value
if (state[oldIndex]==BLUE_STATE.TMP){
pixelList.add(new Integer(oldIndex)); // re-add to the list
double w=0.0;
double avgRef=0.0;
for (int dir=0; dir<dirs20.length; dir++){
int index=oldIndex+dirs20[dir];
int y=index/width;
if ((y<margin) || (y>=yMax)) continue;
int x=index%width;
if ((x<margin) || (x>=xMax)) continue;
if (state[index]!=BLUE_STATE.UNDEF){ // can be problem/solution or overexp?
avgRef+= weights20[dir]*ovrexp[index];
w+=weights20[dir];
}
}
assert (w>0):"marked with TMP are supposed to have at least 1 eligible neighbor";
double avgOther=avgRef/w;
if (Math.abs(dpixels[oldIndex]-avgOther)<=(relDiff*avgOther)){ // overshoots should not contribute to the average saturation level
avgRef+=newWeight*dpixels[oldIndex];
w+=newWeight;
}
ovrexp[oldIndex]= avgRef/=w;
} else {
laterCheckList.add(new Integer(oldIndex)); // check if it will be valid later when filter will be loosened
}
}
// mark list (non-destructively) with OVEREXP (was TMP)
for (Iterator<Integer> iter= pixelList.iterator(); iter.hasNext();){
state[iter.next()]=BLUE_STATE.OVEREXP;
}
if (debugLevel>2) System.out.print("Step "+step+" pixelList.size()="+pixelList.size());
// Consume list, mark UNDEFINED neighbors as TMP, add to list
len=pixelList.size();
for (int i=0;i<len;i++){
int oldIndex=pixelList.remove(0);
for (int dir=0; dir<dirs.length; dir++){
int index=oldIndex+dirs[dir];
int y=index/width;
if ((y<margin) || (y>=yMax)) continue;
int x=index%width;
if ((x<margin) || (x>=xMax)) continue;
if (state[index]!=BLUE_STATE.UNDEF) continue;
pixelList.add(new Integer(index));
state[index]=BLUE_STATE.TMP;
}
}
// change TMP back to UNDEF (nondestructively)
for (Iterator<Integer> iter= pixelList.iterator(); iter.hasNext();){
state[iter.next()]=BLUE_STATE.UNDEF;
}
if (debugLevel>2) System.out.println(" --> "+pixelList.size());
}
// add abandoned pixels - they may become valid again with different filter
// first mark current pixels
if (debugLevel>1) System.out.print("pixelList.size()="+pixelList.size()+", laterCheckList.size()="+laterCheckList.size());
for (Iterator<Integer> iter= pixelList.iterator(); iter.hasNext();){
state[iter.next()]=BLUE_STATE.TMP;
}
for (Iterator<Integer> iter= laterCheckList.iterator(); iter.hasNext();){
Integer index=iter.next();
if (state[index]==BLUE_STATE.UNDEF){
state[index]=BLUE_STATE.TMP;
pixelList.add(index);
}
}
// change TMP back to UNDEF (nondestructively)
for (Iterator<Integer> iter= pixelList.iterator(); iter.hasNext();){
state[iter.next()]=BLUE_STATE.UNDEF;
}
if (debugLevel>1) System.out.println(" ==> combined pixelList.size()="+pixelList.size());
}
public double [] detailsMask(
double [] pixels,
double sigma,
double relThreshold){ // relative value,
double [] fpix=new double [length];
for (int i=0;i<length;i++) fpix[i]=pixels[i];
DoubleGaussianBlur gb = new DoubleGaussianBlur();
gb.blurDouble(fpix,width,height,sigma,sigma, 0.01);
for (int i=0;i<length;i++) fpix[i]-=(pixels[i]+relThreshold*fpix[i]);
return fpix;
}
public double [][] process(){
boolean debugThis=debugLevel>1;
boolean calcRGOver=false; // currently red and green overexposure is not used, only for debug images - sabve on calculations
double [][] colorDiff=null;
double [][] dbg_proc=null;
// double [][] overExpPix = new double [3][length]; // may be needed all (not yet)
double [][] overExpPix = new double [3][]; // may be needed all (not yet)
overExpPix[2]=new double[length];
if (calcRGOver || debugThis){
overExpPix[0]=new double[length];
overExpPix[1]=new double[length];
}
if (debugThis) {
colorDiff=new double[2][length];
for (int px=0;px<length;px++){
colorDiff[0][px]=rgb_in[0][px]/rgb_in[1][px];
colorDiff[1][px]=rgb_in[2][px]/rgb_in[1][px];
}
dbg_proc =new double[11][]; // Only some are currently needed, rest is just for debugging
dbg_proc[0]=rgb_in[0];
dbg_proc[1]=rgb_in[1];
dbg_proc[2]=rgb_in[2];
dbg_proc[3]=colorDiff[0];
dbg_proc[4]=colorDiff[1];
}
for (int chn=(calcRGOver || debugThis)?0:2;chn<3;chn++) {
Runtime runtime = Runtime.getRuntime();
runtime.gc();
overExpPix[chn] = overexposed(
colorProcParameters,
rgb_in[chn],
width);
if (debugThis) {
dbg_proc[5+chn]=new double[length];
for (int i=0;i<length;i++) dbg_proc[5+chn][i]= overExpPix[chn][i];
}
BLUE_STATE[] state= markBlueOverexp(overExpPix[chn]);
List<Integer> pixelList= createInitialList(
state,
BLUE_STATE.UNDEF,
BLUE_STATE.OVEREXP,
colorProcParameters.use8);
double avrg=0.0;
for (int i=0;i<length;i++) avrg+=rgb_in[chn][i];
avrg/=length;
if (debugLevel>1) System.out.println("*** chn="+chn+", avrg="+avrg);
overexposedGrow(
rgb_in[chn], //double [] dpixels,
overExpPix[chn], // double [] ovrexp,
state, // BLUE_STATE[] state,
pixelList,
colorProcParameters.satDetGrowRelDiff, // double maxDiff,
false, // boolean addBrighter,
colorProcParameters.satDetNewWeight,
colorProcParameters.satDetExpSym, // int numSteps,
colorProcParameters.use8); // boolean use8) {
overexposedGrow(
rgb_in[chn], //double [] dpixels,
overExpPix[chn], // double [] ovrexp,
state, // BLUE_STATE[] state,
pixelList,
colorProcParameters.satDetGrowRelDiff, // double maxDiff,
true, // boolean addBrighter,
colorProcParameters.satDetNewWeight,
colorProcParameters.satDetExpOver, // int numSteps,
colorProcParameters.use8); // boolean use8) {
overexposedGrow(
rgb_in[chn], //double [] dpixels,
overExpPix[chn], // double [] ovrexp,
state, // BLUE_STATE[] state,
pixelList,
colorProcParameters.satDetGrowRelDiffCleanUp, // double maxDiff,
true, // boolean addBrighter,
colorProcParameters.satDetNewWeight,
colorProcParameters.satDetExpCleanUp, // int numSteps,
colorProcParameters.use8); // boolean use8) {
}
if (colorProcParameters.blueLeakFixWires){
double [] details= detailsMask(
yrg,
colorProcParameters.blueLeakWiresSize, // double sigma,
colorProcParameters.blueLeakWiresThreshold); //double relThreshold, // relative value,
int numDet=0;
for (int i=0;i<length;i++)
if (!(overExpPix[2][i]==Double.NaN) && (details[i]>0.0)){
overExpPix[2][i]=Double.NaN; // mark as if not over-exposed
numDet++;
}
if (debugLevel>1) System.out.println("Marked "+numDet+" blue pixels as if not overexposed (trees, wires)");
if (debugLevel>1) {
SDFA_INSTANCE.showArrays(details, width, height,"Details");
}
}
if (debugThis){
String [] channel_titles={"red","green","blue", "r/g", "b/g","oe_R","oeG","oe_B","filt_oe_R","filt_oeG","filt_oe_B"};
dbg_proc[8]=overExpPix[0];
dbg_proc[9]=overExpPix[1];
dbg_proc[10]=overExpPix[2];
SDFA_INSTANCE.showArrays(dbg_proc, width, height, true, "test_chn",channel_titles);
}
/// public void showArraysSparse(double[][] pixels, int width, int height, boolean asStack, String title, String [] titles) {
double [] blue = findBlueSolutions(
overExpPix[2],
colorProcParameters.blueLeakNoHint,
colorProcParameters.blueLeakNoBrighten,
colorProcParameters.use8); // use8);
double [][] rgb_out={rgb_in[0],rgb_in[1],blue};
return rgb_out;
//TODO: add processing of R/G/b overexposed areas using the least sensitive channel.
}
}
import ij.IJ;
import ij.io.OpenDialog;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import javax.swing.JFileChooser;
import javax.swing.filechooser.FileFilter;
public class CalibrationFileManagement {
public static String DEFAULT_DIRECTORY=null;
/*
public String getLatestName(FileFilter filter, String dirPath){
File dFile=new File(dirPath);
if (!dFile.isDirectory()) {
String msg="Could not find directory with saved SFE configuration: "+dirPath;
// IJ.showMessage(msg);
System.out.println("Error: "+msg);
return null;
}
File [] files=dFile.listFiles(filter);
}
*/
/** ======================================================================== */
public static String selectDirectory(boolean save, String title, String button, FileFilter filter, String defaultPath) {
return selectDirectoryOrFile(false, save,true, title, button, filter,defaultPath); // always open dialog
}
/**
*
* @param smart if true, and defaultPath matches criteria, return it and do not open dialog
* @param save file/directory for writing, new OK
* @param title Dialog title
* @param button Name on the Button
* @param filter Selection filter
* @param defaultPath default path
* @return selected directory name
*/
public static String selectDirectory(boolean smart, boolean save, String title, String button, FileFilter filter, String defaultPath) {
return selectDirectoryOrFile(smart, save,true, title, button, filter,defaultPath);
}
public static String [] selectDirectories(boolean save, String title, String button, FileFilter filter, String [] defaultPaths) {
return selectDirectoriesOrFiles(save,true, title, button, filter, defaultPaths);
}
public static String selectFile(boolean save, String title, String button, FileFilter filter, String defaultPath) {
return selectDirectoryOrFile(false, save,false, title, button, filter, defaultPath ); // always open dialog
}
public static String selectFile(boolean smart, boolean save, String title, String button, FileFilter filter, String defaultPath) {
return selectDirectoryOrFile(smart, save,false, title, button, filter, defaultPath );
}
public static String [] selectFiles(boolean save, String title, String button, FileFilter filter, String [] defaultPaths) {
return selectDirectoriesOrFiles(save,false, title, button, filter, defaultPaths );
}
/** ======================================================================== */
public static String [] selectDirectoriesOrFiles(boolean save,
boolean directory,
String title,
String button,
FileFilter filter,
String [] defaultPaths) {
File dir=null;
String defaultPath=null;
File [] files=null;
int fileNum;
if ((defaultPaths!=null) && (defaultPaths.length>0)) {
File [] tfiles=new File [defaultPaths.length];
int nf=defaultPaths.length;
for (fileNum=0;fileNum<defaultPaths.length; fileNum++) {
tfiles[fileNum]=new File(defaultPaths[fileNum]);
if ((!tfiles[fileNum].exists()) ||(!tfiles[fileNum].isFile())) {
tfiles[fileNum]=null;
nf--;
}
}
files=new File[nf];
nf=0;
for (fileNum=0;fileNum<defaultPaths.length; fileNum++) if (tfiles[fileNum]!=null){
files[nf++]=tfiles[fileNum];
}
}
if ((defaultPaths!=null) && (defaultPaths.length>0) && (!defaultPaths[0].equals(""))) {
defaultPath=defaultPaths[0];
dir = new File(defaultPath);
}
if ((dir==null) || (!dir.exists())) {
if (DEFAULT_DIRECTORY!=null) {
defaultPath = DEFAULT_DIRECTORY;
dir = new File(defaultPath);
}
}
if ((dir==null) || (!dir.exists())) {
defaultPath = OpenDialog.getDefaultDirectory();
if (defaultPath!=null) dir = new File(defaultPath);
}
if ((dir!=null) && (!dir.exists())) dir=null;
if ((dir!=null) && (!dir.isDirectory())){
dir=dir.getParentFile();
}
//getSelectedFiles
JFileChooser fc= new JFileChooser();
fc.setFileSelectionMode(directory?JFileChooser.DIRECTORIES_ONLY:JFileChooser.FILES_ONLY);
fc.setMultiSelectionEnabled(true);
if ((title!=null) && (title.length()>0)) fc.setDialogTitle(title);
if ((button!=null) && (button.length()>0)) fc.setApproveButtonText(button);
if (filter!=null) fc.setFileFilter(filter) ;
if (dir!=null) fc.setCurrentDirectory(dir);
fc.setSelectedFiles(files);
int returnVal = save?(fc.showSaveDialog(IJ.getInstance())):(fc.showOpenDialog(IJ.getInstance()));
if (returnVal!=JFileChooser.APPROVE_OPTION) return null;
DEFAULT_DIRECTORY=fc.getCurrentDirectory().getPath();
files=fc.getSelectedFiles();
if (files.length<1) return null;
String [] filenames=new String[files.length];
// for (int nFile=0;nFile<files.length;nFile++) filenames[nFile]= files[nFile].getName();
for (int nFile=0;nFile<files.length;nFile++) filenames[nFile]= files[nFile].getPath();
return filenames;
}
public static String selectDirectoryOrFile(
boolean smart, // do not open dialog if defaultPath matches filter
boolean save,
boolean directory,
String title,
String button,
FileFilter filter,
String defaultPath) {
File dir=null;
if ((defaultPath!=null) && (defaultPath.length()>0)) {
dir = new File(defaultPath);
}
// If directory is specified, smart=true, save is enabled, but it does not exist - try to create it
if (smart &&
directory &&
(dir!=null) &&
(defaultPath.length()>1) && // skip "/"
save &&
!dir.exists()) dir.mkdirs();
// see if defaultPath matches
if (smart &&
(dir!=null) &&
(defaultPath.length()>1) && // skip "/"
(dir.exists()) &&
(dir.isDirectory() ^ (!directory)) && // directory if requested directory, file if requested file
(dir.isDirectory() || filter.accept(dir))){ // don't care for directory, match filter if file
return defaultPath;
}
if ((dir==null) || (!dir.exists())) {
if (DEFAULT_DIRECTORY!=null) {
defaultPath = DEFAULT_DIRECTORY;
dir = new File(defaultPath);
}
}
if ((dir==null) || (!dir.exists())) {
defaultPath = OpenDialog.getDefaultDirectory();
if (defaultPath!=null) dir = new File(defaultPath);
}
if ((dir!=null) && (!dir.exists())) dir=null;
if ((dir!=null) && (!dir.isDirectory())){
dir=dir.getParentFile();
}
JFileChooser fc= new JFileChooser();
fc.setFileSelectionMode(directory?JFileChooser.DIRECTORIES_ONLY:JFileChooser.FILES_ONLY);
fc.setMultiSelectionEnabled(false);
if ((title!=null) && (title.length()>0)) fc.setDialogTitle(title);
if ((button!=null) && (button.length()>0)) fc.setApproveButtonText(button);
if (filter!=null) fc.setFileFilter(filter) ;
if (dir!=null) fc.setCurrentDirectory(dir);
int returnVal = save?(fc.showSaveDialog(IJ.getInstance())):(fc.showOpenDialog(IJ.getInstance()));
if (returnVal!=JFileChooser.APPROVE_OPTION) return null;
DEFAULT_DIRECTORY=fc.getCurrentDirectory().getPath();
return fc.getSelectedFile().getPath();
}
public static void saveStringToFile (String path,String data){
BufferedWriter writer = null;
try {
writer = new BufferedWriter( new FileWriter( path));
writer.write( data);
} catch ( IOException e) {
String msg = e.getMessage();
if (msg==null || msg.equals("")) msg = ""+e;
IJ.showMessage("Error",msg);
throw new IllegalArgumentException (msg);
}
finally {
try {
if ( writer != null)
writer.close( );
} catch ( IOException e) {
}
}
}
/** ======================================================================== */
static class MultipleExtensionsFileFilter extends FileFilter implements FilenameFilter {
protected String [] patterns; // case insensitive
protected String description="JP4 files";
protected String prefix=""; // case sensitive
public MultipleExtensionsFileFilter (String prefix, String [] patterns,String description) {
this.prefix= prefix;
this.description=description;
this.patterns= patterns.clone();
}
public MultipleExtensionsFileFilter (String [] patterns,String description) {
this.description=description;
this.patterns=patterns.clone();
}
public MultipleExtensionsFileFilter (String [] patterns) {
this.patterns=patterns.clone();
}
public boolean accept (File file) {
int i;
String name=file.getName();
if (file.isDirectory()) return true;
if (!name.startsWith(this.prefix)) return false; // empty prefix OK
for (i=0;i<patterns.length;i++) {
if (name.toLowerCase().endsWith(patterns[i].toLowerCase())) return true;
}
return false;
}
public boolean accept (File dir, String name) { // directory - don't care here, only name
if (!name.startsWith(this.prefix)) return false; // empty prefix OK
for (int i=0;i<patterns.length;i++) {
if (name.toLowerCase().endsWith(patterns[i].toLowerCase())) return true;
}
return false;
}
public String getDescription() {
return description;
}
}
}
This source diff could not be displayed because it is too large. You can view the blob instead.
/**
** -----------------------------------------------------------------------------**
** CorrectionColorProc.java
**
** Color conversion methods used in aberration correction for Eyesis4pi
**
**
** Copyright (C) 2012 Elphel, Inc.
**
** -----------------------------------------------------------------------------**
**
** CorrectionColorProc.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/>.
** -----------------------------------------------------------------------------**
**
*/
import ij.ImageStack;
import ij.gui.GenericDialog;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicInteger;
public class CorrectionColorProc {
showDoubleFloatArrays SDFA_INSTANCE= new showDoubleFloatArrays();
public AtomicInteger stopRequested=null; // 1 - stop now, 2 - when convenient
double [] denoiseMaskChroma;
int denoiseMaskChromaWidth;
int debugLevel=1;
public CorrectionColorProc(AtomicInteger stopRequested){
this.stopRequested=stopRequested;
}
double [] getDenoiseMaskChroma() {return this.denoiseMaskChroma;}
int getDenoiseMaskChromaWidth() {return this.denoiseMaskChromaWidth;}
void setDebug(int debugLevel){this.debugLevel=debugLevel;}
public void processColorsWeights(ImageStack stack,
double scale, // initial maximal pixel value (16))
EyesisCorrectionParameters.ColorProcParameters colorProcParameters,
CorrectionColorProc.ColorGainsParameters channelGainParameters,
int channel,
double [] denoiseMask,
boolean blueProc,
int debugLevel
) {
double thisGain= colorProcParameters.gain;
double thisBalanceRed= colorProcParameters.balanceRed;
double thisBalanceBlue=colorProcParameters.balanceBlue;
if (channelGainParameters!=null) {
thisGain*= channelGainParameters.gain[channel];
thisBalanceRed*= channelGainParameters.balanceRed[channel];
thisBalanceBlue*=channelGainParameters.balanceBlue[channel];
}
float [] fpixels_r= (float[]) stack.getPixels(1);
float [] fpixels_g= (float[]) stack.getPixels(2);
float [] fpixels_b= (float[]) stack.getPixels(3);
boolean useWeights=(stack.getSize()>=5);
if (!useWeights) {
stack.addSlice("dummy1", fpixels_r);
stack.addSlice("dummy2", fpixels_g);
}
float [] fpixels_wr=(float[]) stack.getPixels(4);
float [] fpixels_wb=(float[]) stack.getPixels(5);
int length=fpixels_r.length;
int width= stack.getWidth();
int height=stack.getHeight();
/** Scale colors, gamma-convert */
int i;
double gain_red= thisBalanceRed* thisGain/scale;
double gain_blue=thisBalanceBlue*thisGain/scale;
double gain_green=thisGain/scale;
double gamma_a=Math.pow(colorProcParameters.minLin,colorProcParameters.gamma)*(1.0-colorProcParameters.gamma);
gamma_a=gamma_a/(1.0-gamma_a);
double gamma_linK=(1.0+gamma_a)*colorProcParameters.gamma*Math.pow(colorProcParameters.minLin,colorProcParameters.gamma)/colorProcParameters.minLin;
double Kg=1.0-colorProcParameters.kr-colorProcParameters.kb;
// Correct color saturation for gamma
double Ar=colorProcParameters.kr*gain_red;
double Ag=Kg*gain_green;
double Ab=colorProcParameters.kb*gain_blue;
if (debugLevel>1) {
System.out.println ( " processColorsWeights() Ar="+Ar+" Ag="+Ag+" Ab="+Ab);
System.out.println ( " processColorsWeights() colorProcParameters.saturationBlue="+colorProcParameters.saturationBlue+
" colorProcParameters.saturationRed="+colorProcParameters.saturationRed);
}
//public void showArrays(double[][] pixels, int width, int height, boolean asStack, String title) {
for (i=0;i<length;i++) {
double Y=Ar*fpixels_r[i]+Ag*fpixels_g[i]+Ab*fpixels_b[i];
Y=linGamma(colorProcParameters.gamma, gamma_a, gamma_linK, colorProcParameters.minLin, Y)/Y;
fpixels_r[i]*=Y*gain_red;
fpixels_g[i]*=Y*gain_green;
fpixels_b[i]*=Y*gain_blue;
}
if (colorProcParameters.corrBlueLeak && blueProc) {
double [][] blueLeakRgb =new double[3][length];
for (int px=0;px<length;px++){
blueLeakRgb[0][px]=fpixels_r[px];
blueLeakRgb[1][px]=fpixels_g[px];
blueLeakRgb[2][px]=fpixels_b[px];
}
BlueLeak blueLeak = new BlueLeak(
colorProcParameters,
blueLeakRgb,
width,
SDFA_INSTANCE,
null, // "blue_corr", //TODO: add normal generation/saving of the intermediate images
debugLevel); // debug level
double [][] blueRemovedRGB= blueLeak.process(); // will later return corrected RGB to use
for (int px=0;px<length;px++){
fpixels_r[px]=(float) blueRemovedRGB[0][px];
fpixels_g[px]=(float) blueRemovedRGB[1][px];
fpixels_b[px]=(float) blueRemovedRGB[2][px];
}
}
/** Convert to YPbPr */
double Y,Pb,Pr;
// double Kg=1.0-colorProcParameters.kr-colorProcParameters.kb;
double Sb=0.5/(1.0-colorProcParameters.kb)*colorProcParameters.saturationBlue;
double Sr=0.5/(1.0-colorProcParameters.kr)*colorProcParameters.saturationRed;
double Yr,Yg,Yb,Wr,Wg,Wb,S;
/** coefficients to find Y from Pb, Pr and a color (R,G or B)
Yr = R- Pr*KPrR
Yb = B- Pb*KPbB
Yg = G+ Pr*KPrG + Pb*KPbG
*/
double KPrR= -(2.0*(1-colorProcParameters.kr))/colorProcParameters.saturationRed;
double KPbB= -(2.0*(1-colorProcParameters.kb))/colorProcParameters.saturationBlue;
double KPrG= 2.0*colorProcParameters.kr*(1-colorProcParameters.kr)/Kg/colorProcParameters.saturationRed;
double KPbG= 2.0*colorProcParameters.kb*(1-colorProcParameters.kb)/Kg/colorProcParameters.saturationBlue;
if (debugLevel>1) {
System.out.println ( " processColorsWeights() gain_red="+gain_red+" gain_green="+gain_green+" gain_blue="+gain_blue);
System.out.println ( " processColorsWeights() gamma="+colorProcParameters.gamma+ " minLin="+colorProcParameters.minLin+" gamma_a="+gamma_a+" gamma_linK="+gamma_linK);
System.out.println ( " processColorsWeights() Kr="+colorProcParameters.kr+" Kg="+Kg+" Kb="+colorProcParameters.kb+" Sr="+Sr+" Sb="+Sb);
System.out.println ( " processColorsWeights() KPrR="+KPrR+" KPbB="+KPbB+" KPrG="+KPrG+" KPbG="+KPbG);
}
float [] fpixels_pb= new float [length];
float [] fpixels_pr= new float [length];
float [] fpixels_y0= new float [length];
float [] fpixels_y= fpixels_y0;
float [] fpixels_yR=null;
float [] fpixels_yG=null;
float [] fpixels_yB=null;
if (debugLevel>2) {
fpixels_yR= new float [length];
fpixels_yG= new float [length];
fpixels_yB= new float [length];
}
for (i=0;i<length;i++) {
Y=colorProcParameters.kr*fpixels_r[i]+Kg*fpixels_g[i]+colorProcParameters.kb*fpixels_b[i];
fpixels_pb[i] = (float) (Sb*(fpixels_b[i]-Y));
fpixels_pr[i] = (float) (Sr*(fpixels_r[i]-Y));
fpixels_y0[i]=(float) Y;
}
/** calculate Y from weighted colors, weights derived from how good each color component predicts signal in each subpixel of Bayer pattern */
if (useWeights) {
fpixels_y= new float [length];
for (i=0;i<length;i++) {
Pb=fpixels_pb[i];
Pr=fpixels_pr[i];
Yr = fpixels_r[i]- Pr*KPrR;
Yb = fpixels_b[i]- Pb*KPbB;
Yg = fpixels_g[i]+ Pr*KPrG + Pb*KPbG;
Wr=fpixels_wr[i];
Wb=fpixels_wb[i];
Wg=1.0-Wr-Wb;
S=1.0/(Wr*(colorProcParameters.weightScaleR-1.0)+Wb*(colorProcParameters.weightScaleB-1.0)+1.0);
Wr*=S*colorProcParameters.weightScaleR;
Wb*=S*colorProcParameters.weightScaleB;
Wg*=S;
Y=Yr*Wr+Yg*Wg+Yb*Wb;
fpixels_y[i]=(float) Y;
if (debugLevel>2) {
fpixels_yR[i]= (float) Yr;
fpixels_yG[i]= (float) Yg;
fpixels_yB[i]= (float) Yb;
}
}
}
/** Low-pass filter Pb and Pr */
DoubleGaussianBlur gb=new DoubleGaussianBlur();
double [] dpixels_pr=new double[fpixels_pr.length];
double [] dpixels_pb=new double[fpixels_pb.length];
for (i=0;i<dpixels_pr.length;i++) {
dpixels_pr[i]=fpixels_pr[i];
dpixels_pb[i]=fpixels_pb[i];
}
if (colorProcParameters.maskMax>0.0) {
double [] dmask=new double[fpixels_y0.length];
for (i=0;i<dpixels_pr.length;i++) dmask[i]=fpixels_y0[i];
double [] dpixels_pr_dark=dpixels_pr.clone();
double [] dpixels_pb_dark=dpixels_pb.clone();
gb.blurDouble(dmask, width, height, colorProcParameters.maskSigma, colorProcParameters.maskSigma, 0.01);
gb.blurDouble(dpixels_pr, width, height, colorProcParameters.chromaBrightSigma, colorProcParameters.chromaBrightSigma, 0.01);
gb.blurDouble(dpixels_pb, width, height, colorProcParameters.chromaBrightSigma, colorProcParameters.chromaBrightSigma, 0.01);
gb.blurDouble(dpixels_pr_dark, width, height, colorProcParameters.chromaDarkSigma, colorProcParameters.chromaDarkSigma, 0.01);
gb.blurDouble(dpixels_pb_dark, width, height, colorProcParameters.chromaDarkSigma, colorProcParameters.chromaDarkSigma, 0.01);
if (debugLevel>2) {
SDFA_INSTANCE.showArrays(dmask, width, height,"dmask");
SDFA_INSTANCE.showArrays(dpixels_pr, width, height,"dpixels_pr");
SDFA_INSTANCE.showArrays(dpixels_pb, width, height,"dpixels_pb");
SDFA_INSTANCE.showArrays(dpixels_pr_dark, width, height,"dpixels_pr_dark");
SDFA_INSTANCE.showArrays(dpixels_pb_dark, width, height,"dpixels_pb_dark");
}
double mp;
double k =1.0/(colorProcParameters.maskMax-colorProcParameters.maskMin);
for (i=0;i<dmask.length;i++) {
mp=dmask[i];
if (mp < colorProcParameters.maskMin) {
dmask[i]=0.0;
} else if (mp< colorProcParameters.maskMax) {
dmask[i]= k*(mp-colorProcParameters.maskMin);
} else dmask[i]=1.0;
}
//TODO: null DENOISE_MASK if it is not calculated
if (colorProcParameters.combineWithSharpnessMask) {
if (denoiseMask==null) {
System.out.println ( "Can not combine masks as denoiseMask is null (i.e. no denoise was performed)");
} else if (denoiseMask.length!=dmask.length) {
System.out.println ( "Can not combine masks as denoiseMask length is different from that of dmask");
} else {
for (i=0;i<dmask.length;i++) {
dmask[i]+=denoiseMask[i];
if (dmask[i]>1.0) dmask[i]=1.0;
}
}
}
for (i=0;i<dmask.length;i++) {
mp=dmask[i];
dpixels_pb[i]= (1.0-mp)*dpixels_pb_dark[i]+ mp* dpixels_pb[i];
dpixels_pr[i]= (1.0-mp)*dpixels_pr_dark[i]+ mp* dpixels_pr[i];
}
this.denoiseMaskChroma=dmask; // (global, used to return denoise mask to save/show
this.denoiseMaskChromaWidth=width; // width of the this.denoiseMaskChroma image
} else {
gb.blurDouble(dpixels_pr, width, height, colorProcParameters.chromaBrightSigma, colorProcParameters.chromaBrightSigma, 0.01);
gb.blurDouble(dpixels_pb, width, height, colorProcParameters.chromaBrightSigma, colorProcParameters.chromaBrightSigma, 0.01);
this.denoiseMaskChroma=null; // (global, used to return denoise mask to save/show
}
for (i=0;i<dpixels_pr.length;i++) {
fpixels_pr[i]=(float) dpixels_pr[i];
fpixels_pb[i]=(float) dpixels_pb[i];
}
stack.addSlice("Pr", fpixels_pr);
stack.addSlice("Pb", fpixels_pb);
stack.addSlice("Y", fpixels_y);
stack.addSlice("Y0", fpixels_y0); // not filtered by low-pass, preliminary (for comaprison only)
if (debugLevel>2) {
stack.addSlice("Yr",fpixels_yR);
stack.addSlice("Yg",fpixels_yG);
stack.addSlice("Yb",fpixels_yB);
}
}
// old versiion
public void processColorsWeightsOld(ImageStack stack,
double scale, // initial maximal pixel value (16))
EyesisCorrectionParameters.ColorProcParameters colorProcParameters,
CorrectionColorProc.ColorGainsParameters channelGainParameters,
int channel,
double [] denoiseMask,
int debugLevel
) {
double thisGain= colorProcParameters.gain;
double thisBalanceRed= colorProcParameters.balanceRed;
double thisBalanceBlue=colorProcParameters.balanceBlue;
if (channelGainParameters!=null) {
thisGain*= channelGainParameters.gain[channel];
thisBalanceRed*= channelGainParameters.balanceRed[channel];
thisBalanceBlue*=channelGainParameters.balanceBlue[channel];
}
float [] fpixels_r= (float[]) stack.getPixels(1);
float [] fpixels_g= (float[]) stack.getPixels(2);
float [] fpixels_b= (float[]) stack.getPixels(3);
boolean useWeights=(stack.getSize()>=5);
if (!useWeights) {
stack.addSlice("dummy1", fpixels_r);
stack.addSlice("dummy2", fpixels_g);
}
float [] fpixels_wr=(float[]) stack.getPixels(4);
float [] fpixels_wb=(float[]) stack.getPixels(5);
int length=fpixels_r.length;
int width= stack.getWidth();
int height=stack.getHeight();
/** Scale colors, gamma-convert */
int i;
double gain_red= thisBalanceRed* thisGain/scale;
double gain_blue=thisBalanceBlue*thisGain/scale;
double gain_green=thisGain/scale;
double gamma_a=Math.pow(colorProcParameters.minLin,colorProcParameters.gamma)*(1.0-colorProcParameters.gamma);
gamma_a=gamma_a/(1.0-gamma_a);
double gamma_linK=(1.0+gamma_a)*colorProcParameters.gamma*Math.pow(colorProcParameters.minLin,colorProcParameters.gamma)/colorProcParameters.minLin;
for (i=0;i<length;i++) {
fpixels_r[i]=(float) linGamma(colorProcParameters.gamma, gamma_a, gamma_linK, colorProcParameters.minLin, fpixels_r[i]*gain_red);
fpixels_g[i]=(float) linGamma(colorProcParameters.gamma, gamma_a, gamma_linK, colorProcParameters.minLin, fpixels_g[i]*gain_green);
fpixels_b[i]=(float) linGamma(colorProcParameters.gamma, gamma_a, gamma_linK, colorProcParameters.minLin, fpixels_b[i]*gain_blue);
}
/** Convert to YPbPr */
double Y,Pb,Pr;
double Kg=1.0-colorProcParameters.kr-colorProcParameters.kb;
double Sb=0.5/(1.0-colorProcParameters.kb)*colorProcParameters.saturationBlue;
double Sr=0.5/(1.0-colorProcParameters.kr)*colorProcParameters.saturationRed;
double Yr,Yg,Yb,Wr,Wg,Wb,S;
/** coefficients to find Y from Pb, Pr and a color (R,G or B)
Yr = R- Pr*KPrR
Yb = B- Pb*KPbB
Yg = G+ Pr*KPrG + Pb*KPbG
*/
double KPrR= -(2.0*(1-colorProcParameters.kr))/colorProcParameters.saturationRed;
double KPbB= -(2.0*(1-colorProcParameters.kb))/colorProcParameters.saturationBlue;
double KPrG= 2.0*colorProcParameters.kr*(1-colorProcParameters.kr)/Kg/colorProcParameters.saturationRed;
double KPbG= 2.0*colorProcParameters.kb*(1-colorProcParameters.kb)/Kg/colorProcParameters.saturationBlue;
if (debugLevel>1) {
System.out.println ( " processColorsWeights() gain_red="+gain_red+" gain_green="+gain_green+" gain_blue="+gain_blue);
System.out.println ( " processColorsWeights() gamma="+colorProcParameters.gamma+ " minLin="+colorProcParameters.minLin+" gamma_a="+gamma_a+" gamma_linK="+gamma_linK);
System.out.println ( " processColorsWeights() Kr="+colorProcParameters.kr+" Kg="+Kg+" Kb="+colorProcParameters.kb+" Sr="+Sr+" Sb="+Sb);
System.out.println ( " processColorsWeights() KPrR="+KPrR+" KPbB="+KPbB+" KPrG="+KPrG+" KPbG="+KPbG);
}
float [] fpixels_pb= new float [length];
float [] fpixels_pr= new float [length];
float [] fpixels_y0= new float [length];
float [] fpixels_y= fpixels_y0;
float [] fpixels_yR=null;
float [] fpixels_yG=null;
float [] fpixels_yB=null;
if (debugLevel>2) {
fpixels_yR= new float [length];
fpixels_yG= new float [length];
fpixels_yB= new float [length];
}
for (i=0;i<length;i++) {
Y=colorProcParameters.kr*fpixels_r[i]+Kg*fpixels_g[i]+colorProcParameters.kb*fpixels_b[i];
fpixels_pb[i] = (float) (Sb*(fpixels_b[i]-Y));
fpixels_pr[i] = (float) (Sr*(fpixels_r[i]-Y));
fpixels_y0[i]=(float) Y;
}
/** calculate Y from weighted colors, weights derived from how good each color component predicts signal in each subpixel of Bayer pattern */
if (useWeights) {
fpixels_y= new float [length];
for (i=0;i<length;i++) {
Pb=fpixels_pb[i];
Pr=fpixels_pr[i];
Yr = fpixels_r[i]- Pr*KPrR;
Yb = fpixels_b[i]- Pb*KPbB;
Yg = fpixels_g[i]+ Pr*KPrG + Pb*KPbG;
Wr=fpixels_wr[i];
Wb=fpixels_wb[i];
Wg=1.0-Wr-Wb;
S=1.0/(Wr*(colorProcParameters.weightScaleR-1.0)+Wb*(colorProcParameters.weightScaleB-1.0)+1.0);
Wr*=S*colorProcParameters.weightScaleR;
Wb*=S*colorProcParameters.weightScaleB;
Wg*=S;
Y=Yr*Wr+Yg*Wg+Yb*Wb;
fpixels_y[i]=(float) Y;
if (debugLevel>2) {
fpixels_yR[i]= (float) Yr;
fpixels_yG[i]= (float) Yg;
fpixels_yB[i]= (float) Yb;
}
}
}
/** Low-pass filter Pb and Pr */
DoubleGaussianBlur gb=new DoubleGaussianBlur();
double [] dpixels_pr=new double[fpixels_pr.length];
double [] dpixels_pb=new double[fpixels_pb.length];
for (i=0;i<dpixels_pr.length;i++) {
dpixels_pr[i]=fpixels_pr[i];
dpixels_pb[i]=fpixels_pb[i];
}
if (colorProcParameters.maskMax>0.0) {
double [] dmask=new double[fpixels_y0.length];
for (i=0;i<dpixels_pr.length;i++) dmask[i]=fpixels_y0[i];
double [] dpixels_pr_dark=dpixels_pr.clone();
double [] dpixels_pb_dark=dpixels_pb.clone();
gb.blurDouble(dmask, width, height, colorProcParameters.maskSigma, colorProcParameters.maskSigma, 0.01);
gb.blurDouble(dpixels_pr, width, height, colorProcParameters.chromaBrightSigma, colorProcParameters.chromaBrightSigma, 0.01);
gb.blurDouble(dpixels_pb, width, height, colorProcParameters.chromaBrightSigma, colorProcParameters.chromaBrightSigma, 0.01);
gb.blurDouble(dpixels_pr_dark, width, height, colorProcParameters.chromaDarkSigma, colorProcParameters.chromaDarkSigma, 0.01);
gb.blurDouble(dpixels_pb_dark, width, height, colorProcParameters.chromaDarkSigma, colorProcParameters.chromaDarkSigma, 0.01);
if (debugLevel>2) {
SDFA_INSTANCE.showArrays(dmask, width, height,"dmask");
SDFA_INSTANCE.showArrays(dpixels_pr, width, height,"dpixels_pr");
SDFA_INSTANCE.showArrays(dpixels_pb, width, height,"dpixels_pb");
SDFA_INSTANCE.showArrays(dpixels_pr_dark, width, height,"dpixels_pr_dark");
SDFA_INSTANCE.showArrays(dpixels_pb_dark, width, height,"dpixels_pb_dark");
}
double mp;
double k =1.0/(colorProcParameters.maskMax-colorProcParameters.maskMin);
for (i=0;i<dmask.length;i++) {
mp=dmask[i];
if (mp < colorProcParameters.maskMin) {
dmask[i]=0.0;
} else if (mp< colorProcParameters.maskMax) {
dmask[i]= k*(mp-colorProcParameters.maskMin);
} else dmask[i]=1.0;
}
//TODO: null DENOISE_MASK if it is not calculated
if (colorProcParameters.combineWithSharpnessMask) {
if (denoiseMask==null) {
System.out.println ( "Can not combine masks as denoiseMask is null (i.e. no denoise was performed)");
} else if (denoiseMask.length!=dmask.length) {
System.out.println ( "Can not combine masks as denoiseMask length is different from that of dmask");
} else {
for (i=0;i<dmask.length;i++) {
dmask[i]+=denoiseMask[i];
if (dmask[i]>1.0) dmask[i]=1.0;
}
}
}
for (i=0;i<dmask.length;i++) {
mp=dmask[i];
dpixels_pb[i]= (1.0-mp)*dpixels_pb_dark[i]+ mp* dpixels_pb[i];
dpixels_pr[i]= (1.0-mp)*dpixels_pr_dark[i]+ mp* dpixels_pr[i];
}
this.denoiseMaskChroma=dmask; // (global, used to return denoise mask to save/show
this.denoiseMaskChromaWidth=width; // width of the this.denoiseMaskChroma image
} else {
gb.blurDouble(dpixels_pr, width, height, colorProcParameters.chromaBrightSigma, colorProcParameters.chromaBrightSigma, 0.01);
gb.blurDouble(dpixels_pb, width, height, colorProcParameters.chromaBrightSigma, colorProcParameters.chromaBrightSigma, 0.01);
this.denoiseMaskChroma=null; // (global, used to return denoise mask to save/show
}
for (i=0;i<dpixels_pr.length;i++) {
fpixels_pr[i]=(float) dpixels_pr[i];
fpixels_pb[i]=(float) dpixels_pb[i];
}
stack.addSlice("Pr", fpixels_pr);
stack.addSlice("Pb", fpixels_pb);
stack.addSlice("Y", fpixels_y);
stack.addSlice("Y0", fpixels_y0); // not filtered by low-pass, preliminary (for comaprison only)
if (debugLevel>2) {
stack.addSlice("Yr",fpixels_yR);
stack.addSlice("Yg",fpixels_yG);
stack.addSlice("Yb",fpixels_yB);
}
}
/** ======================================================================== */
public double linGamma(double gamma, double a, double k, double x0, double x) {
if (x<0) return 0.0;
if (x<=x0) return k*x;
return (1.0+a)*Math.pow(x,gamma)-a;
// return x;
// individual per-channel color balance and gain
}
public void YPrPbToRGB(ImageStack stack,
double Kr, // 0.299;
double Kb, // 0.114;
int sliceY,
int slicePr,
int slicePb
) {
float [] fpixels_r= (float[]) stack.getPixels(1);
float [] fpixels_g= (float[]) stack.getPixels(2);
float [] fpixels_b= (float[]) stack.getPixels(3);
float [] fpixels_Y= (float[]) stack.getPixels(sliceY);
float [] fpixels_Pr=(float[]) stack.getPixels(slicePr);
float [] fpixels_Pb=(float[]) stack.getPixels(slicePb);
int length=fpixels_r.length;
double Kg=1.0-Kr-Kb;
int i;
/**
R= Y+ Pr*2.0*(1-Kr)
B= Y+ Pb*2.0*(1-Kb)
G= Y +Pr*(- 2*Kr*(1-Kr))/Kg + Pb*(-2*Kb*(1-Kb))/Kg
*/
double KPrR= 2.0*(1-Kr);
double KPbB= 2.0*(1-Kb);
double KPrG= -2.0*Kr*(1-Kr)/Kg;
double KPbG= -2.0*Kb*(1-Kb)/Kg;
double Y,Pr,Pb;
for (i=0;i<length;i++) {
Pb=fpixels_Pb[i];
Pr=fpixels_Pr[i];
Y =fpixels_Y [i];
fpixels_r[i]=(float) (Y+ Pr*KPrR);
fpixels_b[i]=(float) (Y+ Pb*KPbB);
fpixels_g[i]=(float) (Y+ Pr*KPrG + Pb*KPbG);
}
}
public static class ColorGainsParameters {
public double[] gain={
1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,
1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,
1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,
1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0};
public double[] balanceRed={
1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,
1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,
1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,
1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0};
public double[] balanceBlue={
1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,
1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,
1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,
1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0};
public ColorGainsParameters(){
}
public void setProperties(String prefix,Properties properties){
properties.setProperty(prefix+"channels",this.gain.length+"");
for (int i=0; i<this.gain.length;i++) {
properties.setProperty(prefix+"gain_"+i,this.gain[i]+"");
properties.setProperty(prefix+"balanceRed_"+i,this.balanceRed[i]+"");
properties.setProperty(prefix+"balanceBlue_"+i,this.balanceBlue[i]+"");
}
}
public void getProperties(String prefix,Properties properties){
if (properties.getProperty(prefix+"channels")!=null) {
int numChannels=Integer.parseInt(properties.getProperty(prefix+"channels"));
this.gain= new double[numChannels];
this.balanceRed= new double[numChannels];
this.balanceBlue=new double[numChannels];
for (int i=0;i<numChannels;i++) {
this.gain[i]= Double.parseDouble(properties.getProperty(prefix+"gain_"+i));
this.balanceRed[i]= Double.parseDouble(properties.getProperty(prefix+"balanceRed_"+i));
this.balanceBlue[i]=Double.parseDouble(properties.getProperty(prefix+"balanceBlue_"+i));
}
}
}
public void modifyNumChannels(int numChannels){
if ((numChannels>0) && (numChannels!=this.gain.length)) {
double [] gain1=this.gain;
double [] balanceRed1=this.balanceRed;
double [] balanceBlue1=this.balanceBlue;
this.gain= new double[numChannels];
this.balanceRed= new double[numChannels];
this.balanceBlue=new double[numChannels];
for (int i=0;i<numChannels;i++) {
int j=i;
if (j>=gain1.length) j=gain1.length-1;
this.gain[i]=gain1[j];
this.balanceRed[i]=balanceRed1[j];
this.balanceBlue[i]=balanceBlue1[j];
}
}
}
public boolean showDialog() {
GenericDialog gd = new GenericDialog("Individual channels colors/gains");
for (int i =0; i<this.gain.length;i++){
gd.addMessage(String.format("=== CHANNEL %02d ===",i));
gd.addNumericField(String.format("%02d: Gain (brightness)",i), this.gain[i], 3);
gd.addNumericField(String.format("%02d: Balance Red/Green",i), this.balanceRed[i], 3);
gd.addNumericField(String.format("%02d: Balance Blue/Green",i), this.balanceBlue[i], 3);
}
WindowTools.addScrollBars(gd);
gd.showDialog();
if (gd.wasCanceled()) return false;
for (int i =0; i<this.gain.length;i++){
this.gain[i]= gd.getNextNumber();
this.balanceRed[i]= gd.getNextNumber();
this.balanceBlue[i]=gd.getNextNumber();
}
return true;
}
}
}
/**
** -----------------------------------------------------------------------------**
** CorrectionDenoise.java
**
** De-noise methods for aberration correction for Eyesis4pi
**
**
** Copyright (C) 2012 Elphel, Inc.
**
** -----------------------------------------------------------------------------**
**
** CorrectionDenoise.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/>.
** -----------------------------------------------------------------------------**
**
*/
import ij.IJ;
import ij.ImageStack;
import java.util.concurrent.atomic.AtomicInteger;
public class CorrectionDenoise {
showDoubleFloatArrays SDFA_INSTANCE= new showDoubleFloatArrays();
public AtomicInteger stopRequested=null; // 1 - stop now, 2 - when convenient
double [] denoiseMask;
int denoiseMaskWidth;
int debugLevel=1;
public CorrectionDenoise(AtomicInteger stopRequested){
this.stopRequested=stopRequested;
}
double [] getDenoiseMask() {return this.denoiseMask;}
int getDenoiseMaskWidth() {return this.denoiseMaskWidth;}
void setDebug(int debugLevel){this.debugLevel=debugLevel;}
// uses global OUT_PIXELS to accumulate results
/** ======================================================================== */
/** Combine two 3-slice image stacks generated from the same source image - one high-res/high noise, other low-res/low noise
* @param nonlinParameters TODO*/
public ImageStack combineLoHiStacks(ImageStack stack_convolved, // ImageStack with the image, convolved with the reversed PSF (sharp but with high noise)
ImageStack stack_gaussian, // ImageStack with the image, convolved with the Gaussian (just lateral compensated) (blurred, but low noise)
int channel, // number of channel to apply to the min/max. If <0 - do not apply
EyesisCorrectionParameters. NonlinParameters nonlinParameters, // show mask generated and used
final double [][] noiseMask, // 2-d array of kernelsNoiseGain (divide mask by it)
final int noiseStep, // linear pixels per noiseMask pixels (32)
final int threadsMax, // maximal step in pixels on the maxRadius for 1 angular step (i.e. 0.5)
final boolean updateStatus, // update status info
final int debugLevel){
int i,j,k;
int imgWidth= stack_convolved.getWidth();
int imgHeight=stack_convolved.getHeight();
double [] diffGreens= new double [imgWidth*imgHeight];
double [] diffGreens1;
double filtMin=nonlinParameters.filtMin;
double filtMax=nonlinParameters.filtMax;
if (channel>=0){
filtMin*=nonlinParameters.thresholdCorr[channel];
filtMax*=nonlinParameters.thresholdCorr[channel];
}
/** find number of the green channel - should be called "green", if none - use last */
int greenChn=2;
for (i=0;i<3;i++) if (stack_convolved.getSliceLabel(i+1).equals("green")){
greenChn=i;
break;
}
double d;
double max=0.0f;
// double average=0.0f;
DoubleGaussianBlur gb=new DoubleGaussianBlur();
float [] hipassPixels=(float[]) stack_convolved.getPixels(greenChn+1);
float [] lopassPixels=(float[]) stack_gaussian.getPixels(greenChn+1);
/*
for (i=0;i<lopassPixels.length;i++) {
d=hipassPixels[i]-lopassPixels[i];
diffGreens[i]=d*d;
if (max<lopassPixels[i]) max=lopassPixels[i];
}
*/
for (i=0;i<lopassPixels.length;i++) {
// d=hipassPixels[i]-lopassPixels[i];
// diffGreens[i]=d*d;
diffGreens[i]=hipassPixels[i]-lopassPixels[i];
}
if ((debugLevel>3) && nonlinParameters.showMask) SDFA_INSTANCE.showArrays(diffGreens.clone(), imgWidth, imgHeight,"diffGreens-nofilter");
if (nonlinParameters.blurSigma>0) {
if (debugLevel>1) System.out.println ( "Applying gaussian blur to difference hi/lo pass, blurSigma="+nonlinParameters.blurSigma);
gb.blurDouble(diffGreens, imgWidth, imgHeight, nonlinParameters.blurSigma, nonlinParameters.blurSigma, 0.01);
}
if ((debugLevel>3) && nonlinParameters.showMask) SDFA_INSTANCE.showArrays(diffGreens.clone(), imgWidth, imgHeight,"diffGreens-blurred");
for (i=0;i<lopassPixels.length;i++) {
diffGreens[i]=diffGreens[i]*diffGreens[i];
}
if ((debugLevel>2) && nonlinParameters.showMask) SDFA_INSTANCE.showArrays(lopassPixels.clone(), imgWidth, imgHeight,"lopassPixels");
for (i=0;i<lopassPixels.length;i++) {
if (max<lopassPixels[i]) max=lopassPixels[i];
}
if (debugLevel>1) System.out.println ( "max(lopassPixels)="+max);
// max*=((float) NONLIN_PARAMETERS.threshold);
// Make threshold absolute - when (blured) intensity is below thershold, the divisor is not decreasing
max=((float) nonlinParameters.threshold);
if ((debugLevel>3) && nonlinParameters.showMask) SDFA_INSTANCE.showArrays(diffGreens.clone(), imgWidth, imgHeight,"diffGreens-squared");
for (i=0;i<lopassPixels.length;i++) {
diffGreens[i]/=(float) Math.max(max,lopassPixels[i]);
}
// if ((debugLevel>3) && nonlinParameters.showMask) SDFA_INSTANCE.showArrays(diffGreens.clone(), imgWidth, imgHeight,"diffG-norm-limited");
if ((debugLevel>1) && nonlinParameters.showMask) SDFA_INSTANCE.showArrays(diffGreens.clone(), imgWidth, imgHeight,"diffG-norm-limited");
if (nonlinParameters.useRejectBlocksFilter) { // use frequency domain filtering
double lowpassSigmaFreq=1.0*nonlinParameters.maskFFTSize/(2*Math.PI*nonlinParameters.lowPassSigma); // low pass sigma in frequency domain
double [] filterFHT = createFilterForBlockArtifacts(
nonlinParameters.maskFFTSize, // size of square FHT
nonlinParameters.blockPeriod, // period (pixels) of the block artifacts to reject (32)
nonlinParameters.rejectFreqSigma, // sigma of the rejection spots ( 0.0 - just zero a single point)
lowpassSigmaFreq); // sigma of the low pass filter (frequency units, 0.0 - do not filter)
if ((debugLevel>3) && nonlinParameters.showMask) SDFA_INSTANCE.showArrays(filterFHT,"filterFHT");
// Extend at least by half sliding window in each direction to reduce border effect
diffGreens1=extendDoubleArrayForSlidingWindow(
diffGreens, // input pixel array
imgWidth, // width of the image
nonlinParameters.maskFFTSize/2); // size of sliding step (half of the sliding window size)
int extendedWidth= extendDimension(imgWidth, (nonlinParameters.maskFFTSize/2));
int extendedHeight= extendDimension(imgHeight,(nonlinParameters.maskFFTSize/2));
if ((debugLevel>3) && nonlinParameters.showMask) SDFA_INSTANCE.showArrays(diffGreens1.clone(),extendedWidth, extendedHeight,"diffGreens-extended");
// run block rejection filter
diffGreens1=filterMaskFromBlockArtifacts(
diffGreens1, // input pixel array
extendedWidth, // width of the image
extendedHeight, // width of the image
nonlinParameters.maskFFTSize, // size of sliding FHT
filterFHT, // filter to multiply FHT (created once for the whole filter mask)
threadsMax, // maximal step in pixels on the maxRadius for 1 angular step (i.e. 0.5)
updateStatus, // update status info
debugLevel);
if ((debugLevel>3) && nonlinParameters.showMask) SDFA_INSTANCE.showArrays(diffGreens1.clone(),extendedWidth,
extendedHeight,"diffGreens-filtered-extended");/**/
// cut extra margins, crop to original size
diffGreens1=reducedDoubleArrayAfterSlidingWindow(
diffGreens1, // input pixel array
imgWidth, // width of the image
imgHeight,
nonlinParameters.maskFFTSize/2); // size of sliding step (half of the sliding window size)
if ((debugLevel>2) && nonlinParameters.showMask) SDFA_INSTANCE.showArrays(diffGreens1.clone(),imgWidth,
imgHeight,"diffGreens-filtered");
if (nonlinParameters.combineBothModes) {
// DoubleGaussianBlur gb=new DoubleGaussianBlur();
gb.blurDouble(diffGreens, imgWidth, imgHeight, nonlinParameters.lowPassSigma, nonlinParameters.lowPassSigma, 0.01);
for (i=0;i<diffGreens.length;i++){
d=diffGreens[i]*diffGreens1[i];
diffGreens[i]=(d>0)?Math.sqrt(diffGreens[i]*diffGreens1[i]):0.0;
}
} else {
diffGreens=diffGreens1;
}
} else { // just apply low-pass filter to the mask
// DoubleGaussianBlur gb=new DoubleGaussianBlur();
gb.blurDouble(diffGreens, imgWidth, imgHeight, nonlinParameters.lowPassSigma, nonlinParameters.lowPassSigma, 0.01);
}
if ((debugLevel>1) && nonlinParameters.showMask) SDFA_INSTANCE.showArrays(diffGreens.clone(), imgWidth, imgHeight,"diffGreens-filtered");
// final double [][] noiseMask, // 2-d array of kernelsNoiseGain (divide mask by it)
// final int noiseStep, // linear pixels per noiseMask pixels (32)
/** divide mask by noiseMask, if defined */
if (noiseMask!=null) {
if (debugLevel>1) System.out.println ( "diffGreens.length="+diffGreens.length+" imgWidth="+imgWidth+" noiseMask.length="+noiseMask.length+" noiseMask[0].length="+noiseMask[0].length);
for (i=0;i<diffGreens.length;i++) {
j=(i/imgWidth)/noiseStep;
k=(i%imgWidth)/noiseStep;
if (j>=noiseMask.length) j=noiseMask.length-1;
if (k>=noiseMask[j].length) k=noiseMask[j].length-1;
diffGreens[i]/=noiseMask[j][k];
}
}
if ((debugLevel>1) && nonlinParameters.showMask) SDFA_INSTANCE.showArrays(diffGreens.clone(), imgWidth, imgHeight,"diffGreens-noise");
if (nonlinParameters.useRingFilter) {
diffGreens=ringFilter(diffGreens, // mask to be filtered
imgWidth, // mask width
nonlinParameters.minMaxValue*nonlinParameters.filtMax, // min value for the local maximum to be processed (absolute, not relative)
nonlinParameters.overRingThreshold, // Ratio of the local maximum to maximal value in a ring to trigger filter
nonlinParameters.overRingLimit, // limit for the pixels in the center ring relative to the maximum in a ring
nonlinParameters.ringIR, // ring inner radius
nonlinParameters.ringOR); // ring outer radius
if ((debugLevel>1) && nonlinParameters.showMask) SDFA_INSTANCE.showArrays(diffGreens.clone(), imgWidth, imgHeight,"diffGreens-ring");
}
if (debugLevel>1) System.out.println ( "filtMax="+filtMax+" filtMin="+filtMin);
d= (float) ( 1.0/(filtMax-filtMin));
if (filtMax>filtMin) {
for (i=0;i<diffGreens.length;i++) {
if (diffGreens[i]<filtMin) diffGreens[i]=0.0f;
else if (diffGreens[i]>filtMax) diffGreens[i]=1.0f;
else diffGreens[i]=d*(diffGreens[i]- (float) filtMin);
}
}
// if (nonlinParameters.showMask) {
// SDFA_INSTANCE.showArrays(diffGreens, imgWidth, imgHeight,"mask");
// }
this.denoiseMask=diffGreens;
this.denoiseMaskWidth=imgWidth;
/** Combine 2 stacks and a mask */
return combineStacksWithMask (stack_gaussian,
stack_convolved,
diffGreens);
}
public double [] reducedDoubleArrayAfterSlidingWindow(
double [] ipixels, // input pixel array
int imgWidth, // width of the image
int imgHeight,
int step){ // size of sliding step (half of the sliding window size)
int width=extendDimension(imgWidth,step);
double [] pixels=new double[imgWidth*imgHeight];
int i,j,base;
int index=0;
for (i=0;i< imgHeight;i++) {
base=width*(i+step)+step;
for (j=0;j<imgWidth;j++) pixels[index++]=ipixels[base++];
}
return pixels;
}
public double [] filterMaskFromBlockArtifacts(
// public float [] filterMaskFromBlockArtifacts(
final double [] pixels, // input pixel array
// final float [] pixels, // input pixel array
final int imgWidth, // width of the image
final int imgHeight, // width of the image
final int size, // size of sliding FHT
final double [] filter, // filter to multiply FHT (created once for the whole filter mask)
final int threadsMax, // maximal step in pixels on the maxRadius for 1 angular step (i.e. 0.5)
final boolean updateStatus, // update status info
final int debugLevel)
{
if (debugLevel>1) System.out.println("filterMaskFromBlockArtifacts, imgWidth="+imgWidth);
if (debugLevel>1) System.out.println("filterMaskFromBlockArtifacts, imgHeight="+imgHeight);
if (pixels==null) return null;
final int length=imgWidth*imgHeight;
final int step=size/2;
final int tilesX=imgWidth/step-1; // horizontal number of overlapping tiles in the source image (should be expanded from the registered one by "step" in each direction)
final int tilesY=imgHeight/step-1; // vertical number of overlapping tiles in the source image (should be expanded from the registered one by "step" in each direction)
if (debugLevel>1) System.out.println("filterMaskFromBlockArtifacts, tilesX="+tilesX);
if (debugLevel>1) System.out.println("filterMaskFromBlockArtifacts, tilesY="+tilesY);
int i; //tileX,tileY;
// for (i=0;i<length;i++) MASK_LOHIRES[i]=0.0;
// MASK_LOHIRES=new float[length];
final double [] maskLoHiRes=new double[length];
for (i=0;i<length;i++) maskLoHiRes[i]=0.0;
final double [] slidingWindow= getSlidingMask(size); // 256x256?
final Thread[] threads = newThreadArray(threadsMax);
final AtomicInteger ai = new AtomicInteger(0);
final int numberOfKernels=tilesY*tilesX;
final long startTime = System.nanoTime();
for (int ithread = 0; ithread < threads.length; ithread++) {
threads[ithread] = new Thread() {
public void run() {
double [] tile= new double[size * size ];
int tileY,tileX;
DoubleFHT fht_instance = new DoubleFHT(); // provide DoubleFHT instance to save on initializations (or null)
// showDoubleFloatArrays SDFA_instance=null; // just for debugging?
for (int nTile = ai.getAndIncrement(); nTile < numberOfKernels; nTile = ai.getAndIncrement()) {
tileY = nTile /tilesX;
tileX = nTile % tilesX;
if (tileX==0) {
if (updateStatus) IJ.showStatus("Filtering noise rejection mask, row "+(tileY+1)+" of "+tilesY);
if (debugLevel>1) System.out.println("Filtering noise rejection mask, row "+(tileY+1)+" of "+tilesY+" : "+IJ.d2s(0.000000001*(System.nanoTime()-startTime),3));
}
extractSquareTile( pixels, // source pixel array,
tile, // will be filled, should have correct size before call
slidingWindow, // window (same size as the kernel)
imgWidth, // width of pixels array
tileX*step, // left corner X
tileY*step); // top corner Y
fht_instance.swapQuadrants(tile);
fht_instance.transform(tile);
tile=fht_instance.multiply(tile,filter,false);
fht_instance.inverseTransform(tile);
fht_instance.swapQuadrants(tile);
/** accumulate result */
/*This is synchronized method. It is possible to make threads to write to non-overlapping regions of the OUT_PIXELS, but as the accumulation
* takes just small fraction of several FHTs, it should be OK - reasonable number of threads will spread and not "stay in line"
*/
accumulateSquareTile(maskLoHiRes, // float pixels array to accumulate tile
tile, // data to accumulate to the pixels array
imgWidth, // width of pixels array
tileX*step, // left corner X
tileY*step); // top corner Y
}
}
};
}
startAndJoin(threads);
return maskLoHiRes;
}
/** ======================================================================== */
public double [] ringFilter(double [] dmask, // mask to be filtered
int width, // mask width
double minMaxValue, // min value for the local maximum to be processed (absolute, not relative)
double overRingThreshold, // Ratio of the local maximum to maximal value in a ring to trigger filter
double overRingLimit, // limit for the pixels in the center ring relative to the maximum in a ring
double ringIR, // ring inner radius
double ringOR) { // ring outer radius
if (dmask==null) return null;
int margins=(int)Math.ceil(ringIR);
int height=dmask.length/width;
double [] result= dmask.clone();
int i,j,nr,nc,y,x,n;
double ringIR2=ringIR*ringIR;
double ringOR2=ringOR*ringOR;
double r2;
nc=0;
nr=0;
for (i=-margins+1;i<margins;i++) for (j=-margins+1;j<margins;j++) {
r2=i*i+j*j;
if (r2<ringIR2) nc++;
else if (r2<=ringOR2) nr++;
}
if ((nc==0) || (nr==0)) return result; // do not filter;
int [] indxc=new int[nc];
int [] indxr=new int[nr];
nc=0;
nr=0;
for (i=-margins+1;i<margins;i++) for (j=-margins+1;j<margins;j++) {
r2=i*i+j*j;
if (r2<ringIR2) {
indxc[nc++]=j+width*i;
}
else if (r2<=ringOR2) {
indxr[nr++]=j+width*i;
}
}
int [] neighb={-width,-width+1,1,width+1,width,width-1,-1,-width-1};
int index;
boolean isMax;
double d,maxInRing;
for (y=margins; y<height-margins;y++) for (x=margins; x<width-margins;x++) {
index=y*width+x;
d=dmask[index];
if (d<minMaxValue) continue; // too small value - don't bother to filter
isMax=true;
for (n=0;n<neighb.length;n++) if (dmask[index+neighb[n]]>d) {
isMax=false;
break;
}
if (!isMax) continue; // only process local maximums
maxInRing=dmask[index+indxr[0]];
for (n=1;n<nr;n++) if (dmask[index+indxr[n]]>maxInRing) maxInRing=dmask[index+indxr[n]];
if (d< (maxInRing*overRingThreshold)) continue; // under threshold, nop
// limit values in the circle
maxInRing*=overRingLimit;
for (n=0; n<nc;n++) if (dmask[index+indxc[n]]>maxInRing) result[index+indxc[n]]=maxInRing;
}
return result;
}
/** ======================================================================== */
/** ======================================================================== */
/** Combine 2 stacks and a mask */
public ImageStack combineStacksWithMask (ImageStack stack_bg,
ImageStack stack_fg,
// float [] mask ) {
double [] mask ) {
ImageStack stack=new ImageStack(stack_bg.getWidth(),stack_bg.getHeight());
int slice,i;
float [] fpixels;
float [] fpixels_bg;
float [] fpixels_fg;
for (slice=1; slice <=stack_bg.getSize(); slice++) {
fpixels_bg= (float[])stack_bg.getPixels(slice);
fpixels_fg= (float[])stack_fg.getPixels(slice);
fpixels=new float [fpixels_bg.length];
for (i=0;i<fpixels_bg.length;i++) fpixels[i]= (float) (mask[i]*fpixels_fg[i]+(1.0f-mask[i])*fpixels_bg[i]);
stack.addSlice(stack_fg.getSliceLabel(slice), fpixels);
}
return stack;
}
/** ======================================================================== */
public double [] getSlidingMask(int size) {
double [] mask = new double [size*size];
double [] maskLine=new double [size];
double k=2*Math.PI/size;
int i,j,index;
for (i=0;i<size;i++) maskLine[i]= 0.5*(1.0-Math.cos(i*k));
index=0;
for (i=0;i<size;i++) for (j=0;j<size;j++) mask[index++]=maskLine[i]*maskLine[j];
return mask;
}
/** ======================================================================== */
/* Filters mask that selects between hi-res/high-noise deconvolved image and lo-res/lo-noise image convolved with Gaussian
* by rejecting frequencies that correspond to multiples of JPEG blocks (here with the current settings it is 32 pixels - twice 16x16 macroblock
*/
public double [] createFilterForBlockArtifacts(
final int size, // size of square FHT
final int rejectPeriod, // period (pixels) of the block artifacts to reject (32)
final double rejectSigma, // sigma of the rejection spots ( 0.0 - just zero a single point)
final double lopassSigma) // sigma of the low pass filter (frequency units, 0.0 - do not filter)
{
double [] maskFHT=new double[size*size];
int freqPeriod=size/rejectPeriod;
int i,j;
for (i=0;i<size*size;i++) maskFHT[i]=1.0; // Initialize mask
double []rejGauss;
double k;
int pointX,pointY,x,y;
if (rejectSigma>=0.0) {
int rejSize= (int) (rejectSigma*4)+1;
if (rejSize>(freqPeriod/2)) rejSize=(freqPeriod/2);
rejGauss=new double[rejSize];
rejGauss[0]=1.0;
k=1.0/(2*rejectSigma*rejectSigma);
for (i=1;i<rejSize;i++) {
rejGauss[i]=Math.exp(-(k*i*i));
}
for (pointY=0;pointY<size;pointY+=freqPeriod) for (pointX=0;pointX<size;pointX+=freqPeriod) if ((pointY>0) || (pointX>0)) {
for (y=-rejSize+1;y<rejSize;y++) for (x=-rejSize+1;x<rejSize;x++) {
i=(pointY+y+size)%size;
j=(pointX+x+size)%size;
maskFHT[i*size+j]=1.0-rejGauss[Math.abs(y)]*rejGauss[Math.abs(x)];
}
}
}
if (lopassSigma>0) {
double [] maskTmp=maskFHT;
maskFHT=new double[size*size];
for (i=0;i<size*size;i++) maskFHT[i]=0.0; // Initialize mask
int lopassSize= (int) (lopassSigma*4)+1;
if (lopassSize>(size/2)) lopassSize=(size/2);
rejGauss=new double[lopassSize];
rejGauss[0]=1.0;
k=1.0/(2*lopassSigma*lopassSigma);
for (i=1;i<lopassSize;i++) {
rejGauss[i]=Math.exp(-(k*i*i));
}
for (y=-lopassSize+1;y<lopassSize;y++) for (x=-lopassSize+1;x<lopassSize;x++) {
i=(y+size)%size;
j=(x+size)%size;
maskFHT[i*size+j]=maskTmp[i*size+j]*rejGauss[Math.abs(y)]*rejGauss[Math.abs(x)];
}
}
return maskFHT;
}
private int extendDimension(
int dimension,
int step) {
return step*((int) Math.ceil(dimension/step) +2);
}
public double [] extendDoubleArrayForSlidingWindow(
double [] ipixels, // input pixel array
int imgWidth, // width of the image
int step){ // size of sliding step (half of the sliding window size)
int imgHeight= ipixels.length/imgWidth; // width of the image
int width=extendDimension(imgWidth,step);
int height=extendDimension(imgHeight,step);
double [] pixels=new double[width*height];
int i,j,k;
int index=0;
for (i=0;i<height;i++) {
if ((i<step) || (i>=(imgHeight+step))) {
k=(i+1)*width;
for (j=i*width;j<k;j++) pixels[j]=0.0;
} else {
k=i*width+step;
for (j=i*width;j<k;j++) pixels[j]=0.0;
k=(i+1)*width;
for (j=i*width+step+imgWidth;j<k;j++) pixels[j]=0.0;
k=i*width+step+imgWidth;
for (j=i*width+step;j<k;j++) pixels[j]=ipixels[index++];
}
}
return pixels;
}
/** ======================================================================== */
/**extract and multiply by window function (same size as kernel itself) */
void extractSquareTile(float [] pixels, // source pixel array,
double [] tile, // will be filled, should have correct size before call
double [] window, // window (same size as the kernel)
int width, // width of pixels array
int x0, // left corner X
int y0) { // top corner Y
int length=tile.length;
int size=(int) Math.sqrt(length);
int i,j,x,y;
int height=pixels.length/width;
int index=0;
for (i=0;i<size;i++) {
y=y0+i;
if ((y>=0) && (y<height)) {
index=i*size;
for (j=0;j<size;j++) {
x=x0+j;
if ((x>=0) && (x<width)) tile [index]=pixels[y*width+x]*window[index];
index++;
}
}
}
}
/** ======================================================================== */
void extractSquareTile(double [] pixels, // source pixel array,
double [] tile, // will be filled, should have correct size before call
double [] window, // window (same size as the kernel)
int width, // width of pixels array
int x0, // left corner X
int y0) { // top corner Y
int length=tile.length;
int size=(int) Math.sqrt(length);
int i,j,x,y;
int height=pixels.length/width;
int index=0;
for (i=0;i<size;i++) {
y=y0+i;
if ((y>=0) && (y<height)) {
index=i*size;
for (j=0;j<size;j++) {
x=x0+j;
if ((x>=0) && (x<width)) tile [index]=pixels[y*width+x]*window[index];
index++;
}
}
}
}
/** accumulate square tile to the pixel array (tile may extend beyond the array, will be cropped) */
synchronized void accumulateSquareTile(
float [] pixels, // float pixels array to accumulate tile
double [] tile, // data to accumulate to the pixels array
int width, // width of pixels array
int x0, // left corner X
int y0) { // top corner Y
int length=tile.length;
int size=(int) Math.sqrt(length);
int i,j,x,y;
int height=pixels.length/width;
int index=0;
for (i=0;i<size;i++) {
y=y0+i;
if ((y>=0) && (y<height)) {
index=i*size;
for (j=0;j<size;j++) {
x=x0+j;
if ((x>=0) && (x<width)) pixels[y*width+x]+=tile [index];
index++;
}
}
}
}
synchronized void accumulateSquareTile(
double [] pixels, // float pixels array to accumulate tile
double [] tile, // data to accumulate to the pixels array
int width, // width of pixels array
int x0, // left corner X
int y0) { // top corner Y
int length=tile.length;
int size=(int) Math.sqrt(length);
int i,j,x,y;
int height=pixels.length/width;
int index=0;
for (i=0;i<size;i++) {
y=y0+i;
if ((y>=0) && (y<height)) {
index=i*size;
for (j=0;j<size;j++) {
x=x0+j;
if ((x>=0) && (x<width)) pixels[y*width+x]+=tile [index];
index++;
}
}
}
}
/** ======================================================================== */
/** 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
*/
public 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);
}
}
}
/**
** -----------------------------------------------------------------------------**
** Crosstalk_Deconv.java
**
** Calculates 4 kernels for convolution with 4 color components to
** compensate for inter-pixel crosstalk
** Applies the calculated (or direct) kernels to selected image
**
** Copyright (C) 2010 Elphel, Inc.
**
** -----------------------------------------------------------------------------**
**
** Crosstalk_Deconv.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/>.
** -----------------------------------------------------------------------------**
**
*/
import ij.*;
import ij.process.*;
import ij.gui.*;
import java.awt.*;
import java.awt.event.*;
import ij.plugin.frame.*;
import java.util.Random;
import java.util.Arrays;
import ij.text.*;
public class Crosstalk_Deconv extends PlugInFrame implements ActionListener {
/**
*
*/
private static final long serialVersionUID = 650738637136446153L;
Panel panel;
int previousID;
static Frame instance;
String crossTalkName="Crosstalk";
String simulName="Simulation";
public static int DEBUG_LEVEL = 1;
public static int MASTER_DEBUG_LEVEL = 1;
public static boolean in_place = true; /** replace image with the processed */
public static int BayerMask=1; /** Which components to keep during "Mask Bayer" command (+1 - g1, +2 - r, +4 - b, +8 - g2*/
public static boolean kernels_valid=false;
public static boolean monoRed=false; /** use crosstalk for red in all channels */
public static boolean monoBlue=false; /** use crosstalk for blue in all channels */
public static double [][] crossCoeff= { /**[red.green,blue][N,NE,E,...NW] */
{0.061, 0.0, 0.091, 0.0, 0.061, 0.0, 0.061, 0.0},
{0.023, 0.0, 0.038, 0.0, 0.023, 0.0, 0.038, 0.0},
{0.0086, 0.0, 0.019, 0.0, 0.0086, 0.0, 0.019, 0.0}};
public static int FFTSize=16;
public static int imageFFTSize=128;
public static int [][][][] invertSeq ; /** once calculated sequence of elements for 4x4 matrix inversion i,j,i,j,i,j,sign */
public static int [][] determinantSeq ; /** once calculated sequence of elements for 4x4 matrix determinant: j,j,j,j,sign */
public static int invertDimension=0; /** determins if 4x4 matrix inversion arrays are initialized */
public static double crosstalkThreshold=0.02; /** ignore crosstalk coefficients when abs(ip_RminusB)< is less than crosstalkThreshold */
public static int simulWidth=1200;
public static int simulHeight=800;
public static double simulSlant=0.033; /** pixel shift between rows */
public static double simulPeriod=64.1; /** stripes period */
public static double simulContrast=0.9; /** blackLevel=simulContrast*whiteLevel */
public static double simulLSFWidth=1.1;
public static double [] simulBayer={30.0,120.0,1.0,30.0}; /** Gr,R,B,Gb - as with 600x10nm filter - need correction. that was with crosstalk */
public static double simulElectrons=66.7; /** 120 - 8000e- */
String patternName="Pattern";
// public boolean patternHorizontal;
public static int patternType=2; /** 0 - vertical, 1 - horizontal, 2 - H+V, 3 - H*V */
public static int patternWidth=1920;
public static int patternHeight=1200;
public static int patternColor=1; /** 1 - red, 2 - green, 4 - blue and their combinartions */
public static double patternContrast=1.0; /** stripes contrast */
public static double patternPeriod=12.96; /** stripes period */
public static double patternNonLinear=0.0; /** non-linearity of the pattern - 0.0 - linear, 1.0 square*/
public static double patternSlant=0.033; /** pixel shift between rows */
public static double patternScale=0.303; /** sensor pixels per LCD screen pixels */
public static double patternV2H=1.0; /** contrast ratio (>1 - V more than H), to simulate astigmatism */
public static double pattern3rdHA=0.0; /** simulation: amount of third harmonic, 1.0 - 100% (horizontal)*/
public static double pattern3rdHP=0.0; /** simulation: 3-rd harmonic phase, */
public static double pattern3rdVA=0.0; /** simulation: amount of third harmonic, 1.0 - 100% (horizontal)*/
public static double pattern3rdVP=0.0; /** simulation: 3-rd harmonic phase, */
public static int patternSimSize=300; /** size of simulated pattern (square, poweer of 2) */
public static double [] patternSimBayer={30.0,120.0,1.0,30.0}; /** Gr,R,B,Gb as acquiered by simulated sensor */
public static double patternSimElectrons=66.7; /** 120 - 8000e- */
public static double DC_RminusB,DC_RBminusGG;
public static double [] DC_bayer;
private ImageProcessor ip_kr,ip_kg,ip_kb,ip_simul,ip_pattern,ip_RminusB, ip_GbminusGr, ip_patternSim;
private ImagePlus imp_kr,imp_kg,imp_kb,imp_simul,imp_pattern,imp_RminusB, imp_GbminusGr, imp_patternSim, imp_src;
private FHT fht_kr, fht_kg,fht_kb;
private FHT fht_RminusB, fht_GbminusGr,fht_crosstalk;
private static double [] accum_crosstalk; /** accumulating fht pixles here */
private static double [] accum_crosstalk_weight; /** accumulating fht pixles weights (abs of denominators) here */
private static double [][][] FKr,FKrX,FKrY,FKrXY, FKg,FKgX,FKgY,FKgXY, FKb,FKbX,FKbY,FKbXY;
private static double [][][] FRslt_g1, FRslt_r, FRslt_b, FRslt_g2;
private static float[] direct_kr,direct_kg,direct_kb;
private static float[] reverse_kg1,reverse_kr,reverse_kb,reverse_kg2;
private static float [][][] kernels; // Four kernels for convolution
// private double [][] Rslt_g1, Rslt_r, Rslt_b, Rslt_g2;
// private ImagePlus Rslt_g1, Rslt_r, Rslt_b, Rslt_g2;
private FHT Rslt_g1, Rslt_r, Rslt_b, Rslt_g2;
/*
private double[][] MS= {{1 , 1, 1, 1},
{1 , -1, 1, -1},
{1 , 1, -1, -1},
{1 , -1, -1, 1}};
*/
private double[][] MS= {{0.5 , 0.5, 0.5, 0.5},
{0.5 , -0.5, 0.5, -0.5},
{0.5 , 0.5, -0.5, -0.5},
{0.5 , -0.5, -0.5, 0.5}};
// FHT fht_kr, fht_kg, fht_kb;
// public void run(String arg) {
public Crosstalk_Deconv() {
super("Crosstalk Compensation");
if (IJ.versionLessThan("1.39t")) return;
if (instance!=null) {
instance.toFront();
return;
}
instance = this;
addKeyListener(IJ.getInstance());
setLayout(new FlowLayout());
panel = new Panel();
addButton("Configure");
addButton("Remove Crosstalk");
addButton("Add Crosstalk");
addButton("Create kernels");
addButton("Mask Bayer");
addButton("Create Simulation");
addButton("Create Color Pattern");
addButton("Simulate Color Pattern");
addButton("Measure Crosstalk");
add(panel);
pack();
GUI.center(this);
setVisible(true);
}
void addButton(String label) {
Button b = new Button(label);
b.addActionListener(this);
b.addKeyListener(IJ.getInstance());
panel.add(b);
}
public void actionPerformed(ActionEvent e) {
String label = e.getActionCommand();
if (label==null) return;
if (label.equals("Configure")) {
showDialog();
return;
} else if (label.equals("Create kernels")) {
DEBUG_LEVEL=MASTER_DEBUG_LEVEL;
createReverseKernels();
return;
} else if (label.equals("Create Simulation")) {
createSimulation();
return;
} else if (label.equals("Create Color Pattern")) {
createColorPattern();
return;
} else if (label.equals("Simulate Color Pattern")) {
simulatePatternCapture();
return;
}
/** other commands need current image (float) */
imp_src = WindowManager.getCurrentImage();
if (imp_src==null) {
IJ.showStatus("No image");
IJ.showMessage("Crosstalk_Deconv Error","Image required");
return;
}
if (imp_src.getType()!=ImagePlus.GRAY32) {
IJ.showStatus("Converting source image to gray 32 bits (float)");
new ImageConverter(imp_src).convertToGray32();
}
ImageProcessor ip=imp_src.getProcessor();
ImagePlus imp=imp_src;
String newTitle= imp_src.getTitle();
if (label.equals("Measure Crosstalk")) {
DEBUG_LEVEL=MASTER_DEBUG_LEVEL;
/*
Rectangle r=prepareCrosstalkRegion(imp_src);
if (r.width==0) return;
imp_src.setRoi(r);
preprocessCrosstalk(ip,newTitle, false);
measureCrosstalk(ip_RminusB, ip_GbminusGr,newTitle);
*/
accumulateCrosstalk (imp_src, imageFFTSize, newTitle, crosstalkThreshold);
return;
}
/*
public Rectangle prepareCrosstalkRegion(ImageProcessor ip, String title) {
// imp_src.setRoi(r);
public Rectangle prepareCrosstalkRegion(ImagePlus imp) {
Rectangle r=roi_src.getBounds();
public boolean preprocessCrosstalk(ImageProcessor ip, String title) {
*/
/** calculate kernels if they are not current */
if (!kernels_valid) {
DEBUG_LEVEL=0;
createReverseKernels();
}
if (label.equals("Remove Crosstalk")) {
// IJ.showMessage("Crosstalk_Deconv","Remove Crosstalk");
set2removeCrosstalk();
newTitle+="_removed_"+crossTalkName;
IJ.showStatus("Convolving source image with the inverse crosstalk kernel");
} else if (label.equals("Add Crosstalk")) {
// IJ.showMessage("Crosstalk_Deconv","Add Crosstalk");
set2addCrosstalk();
// String crossTalkName;
newTitle+="_added_"+crossTalkName;
IJ.showStatus("Convolving source image with the crosstalk kernel");
} else if (label.equals("Mask Bayer")) {
// IJ.showMessage("Crosstalk_Deconv","Add Crosstalk");
newTitle+="_"+(((BayerMask&1)!=0)?"G1":"")+(((BayerMask&2)!=0)?"R":"")+(((BayerMask&2)!=0)?"B":"")+(((BayerMask&8)!=0)?"G2":"");
IJ.showStatus("Masking out Bayer components");
} else return; /** add more options later, if needed */
if (!in_place) {
ip=ip.duplicate(); /** create a copy of the image before convolving */
imp= new ImagePlus(newTitle, ip);
imp.show();
} else imp.setTitle(newTitle);
if (label.equals("Mask Bayer")) {
maskBayer(ip);
} else {
convolveBayerKernel(ip);
}
imp.updateAndDraw(); /** Redisplays final image*/
IJ.showStatus("Crosstalk_Deconv DONE");
}
public void processWindowEvent(WindowEvent e) {
super.processWindowEvent(e);
if (e.getID()==WindowEvent.WINDOW_CLOSING) {
instance = null;
}
}
public boolean showColorPatternDialog() {
double a;
String [] PatternTypeName={"Vertical","Horizontal","Sum V+H" /*, "Product V*H"*/};
GenericDialog gd = new GenericDialog("Simulation parameters");
gd.addStringField ("Pattern name: ", patternName, 32);
gd.addChoice ("Pattern type", PatternTypeName, PatternTypeName[patternType]);
gd.addNumericField("Nonlenearity (0 - sine, 1 - square, <0 - odd harmonics):", patternNonLinear, 3);
gd.addNumericField("Image width:", patternWidth, 0);
gd.addNumericField("Image height:", patternHeight, 0);
gd.addNumericField("Pattern Color (1-red, 2-green, 4 - blue):", patternColor, 0);
gd.addNumericField("Contrast (%):", patternContrast*100, 1);
gd.addNumericField("Stripes period (pixels):", patternPeriod, 3);
// gd.addCheckbox ("Use square wave instead of sine wave? ", patternSquareWave);
gd.addNumericField("Pixel shift per row:", patternSlant, 3);
gd.addNumericField("Simulated pattern size (square):", patternSimSize,0);
gd.addNumericField("Simulated scale (sensor pixels per LCD pixel):",patternScale,3);
gd.addNumericField("Relative contrast in vertical direction (to horizontal):",patternV2H,3);
gd.addNumericField("Horizontal 3-rd harmonic amount:", pattern3rdHA,3);
gd.addNumericField("Horizontal 3-rd harmonic phase (degrees):", pattern3rdHP,3);
gd.addNumericField("Vertical 3-rd harmonic amount:", pattern3rdVA,3);
gd.addNumericField("Vertical 3-rd harmonic phase (degrees):", pattern3rdVP,3);
gd.addNumericField("Bayer R scale :", patternSimBayer[1], 3);
gd.addNumericField("Bayer Gr scale :", patternSimBayer[0], 3);
gd.addNumericField("Bayer Gb scale :", patternSimBayer[3], 3);
gd.addNumericField("Bayer B scale :", patternSimBayer[2], 3);
gd.addNumericField("Electrons per 1.0 in the output (0 - no noise) :", patternSimElectrons, 3);
gd.showDialog();
if (gd.wasCanceled()) return false;
patternName = gd.getNextString();
// patternHorizontal= gd.getNextBoolean();
patternType=gd.getNextChoiceIndex();
patternNonLinear = gd.getNextNumber();
if (patternNonLinear<-.99) patternNonLinear=-.99;
else if (patternNonLinear>1.0) patternNonLinear=1.0;
patternWidth = (int) gd.getNextNumber();
patternHeight = (int) gd.getNextNumber();
patternColor = (int) gd.getNextNumber();
a=gd.getNextNumber(); if ((a>=0.0) && (a<=100.0)) patternContrast=0.01*a;
patternPeriod = gd.getNextNumber();
// patternSquareWave= gd.getNextBoolean();
patternSlant = gd.getNextNumber();
patternSimSize = (int) gd.getNextNumber();
patternScale = gd.getNextNumber();
patternV2H = gd.getNextNumber();
pattern3rdHA = gd.getNextNumber();
pattern3rdHP = gd.getNextNumber();
pattern3rdVA = gd.getNextNumber();
pattern3rdVP = gd.getNextNumber();
patternSimBayer[1] = gd.getNextNumber();
patternSimBayer[0] = gd.getNextNumber();
patternSimBayer[3] = gd.getNextNumber();
patternSimBayer[2] = gd.getNextNumber();
patternSimElectrons = gd.getNextNumber();
return true;
}
public void createColorPattern () {
int i,j,iy,ipix;
double x,p,wh,wv;
if (!showColorPatternDialog()) return;
ip_pattern=new ColorProcessor(patternWidth, patternHeight);
int[] pixels = (int[])ip_pattern.getPixels();
ipix=0;
p=0.0;
for (i = 0; i < patternHeight; i++) {
for (j = 0; j < patternWidth; j++) {
x=j+patternSlant*i;
x/=patternPeriod;
x-=Math.floor(x);
wv=Math.sin(2*Math.PI*x);
x=(patternHeight-i-1)+patternSlant*j;
x/=patternPeriod;
x-=Math.floor(x);
wh=Math.sin(2*Math.PI*x);
switch (patternType) {
case 0: p=wv; break;
case 1: p=wh; break;
case 2: p=(wv+wh)/2.0; break;
case 3: p=wv*wh; break;
}
if (patternNonLinear > 0.0) {
if (patternNonLinear >= 1.0) p= (p>0.0)?1.0:-1.0;
else if (p>0.0) p=Math.pow(p,1.0-patternNonLinear);
else if (p<0.0) p=-Math.pow(-p,1.0-patternNonLinear);
} else if (patternNonLinear < 0.0) { /** odd harmonics also */
p= 2.0*Math.pow(0.5*(p+1.0),1.0+patternNonLinear)-1.0;
}
iy= (int) (127.5*(1.0+patternContrast* p));
iy&= 255; // just in case
pixels[ipix++] = (255 << 24) | (((patternColor & 1)!=0)? (iy<<16):0) | (((patternColor & 2)!=0)? (iy<< 8):0) | (((patternColor & 4)!=0)? iy:0) ;
// if ((i==10) && (j==10)) IJ.showMessage("Debug","ipix="+ipix+"\npixels[ipix-1]="+(pixels[ipix-1]&0xffffff)+"\npixels.length="+pixels.length);
}
}
ip_pattern.setPixels(pixels);
imp_pattern= new ImagePlus(patternName+"-"+patternType+"-"+patternPeriod+"-"+patternNonLinear, ip_pattern);
imp_pattern.show();
IJ.showStatus("Pattren image done.");
}
public void simulatePatternCapture() {
int i,j,index;
double x,p,wv,wh;
double noise=(patternSimElectrons>0)?(1.0/patternSimElectrons):0.0;
if (!showColorPatternDialog()) return;
Random generator = new Random( 123456 );
ip_patternSim= new FloatProcessor(patternSimSize,patternSimSize);
float[] pixels = (float[])ip_patternSim.getPixels();
double simPeriod=patternPeriod*patternScale;
index=0;
p=0.0;
double v2h=1.0/(patternV2H + 1.0);
for (i = 0; i < patternSimSize; i++) {
for (j = 0; j < patternSimSize; j++) {
x=j+patternSlant*i;
x/=simPeriod;
x-=Math.floor(x);
wv=Math.sin(2*Math.PI*x);
if (pattern3rdHA>0.0) wv=wv*(1-pattern3rdHA)+pattern3rdHA*Math.sin(2*Math.PI*(3*x+pattern3rdHP/360.0)); /** may be less than 1.0 amplitude */
x=(patternHeight-i-1)+patternSlant*j;
x/=simPeriod;
x-=Math.floor(x);
wh=Math.sin(2*Math.PI*x);
if (pattern3rdVA>0.0) wh=wh*(1-pattern3rdVA)+pattern3rdVA*Math.sin(2*Math.PI*(3*x+pattern3rdVP/360.0)); /** may be less than 1.0 amplitude */
switch (patternType) {
case 0: p=wv; break;
case 1: p=wh; break;
case 2:
// p=0.5*(wv+wh);
p=v2h*(wh*patternV2H+wv);
break;
case 3: p=wv*wh; break;
}
if (patternNonLinear > 0.0) { /** only even harmonics */
if (patternNonLinear >= 1.0) p= (p>0.0)?1.0:-1.0;
else if (p>0.0) p=Math.pow(p,1.0-patternNonLinear);
else if (p<0.0) p=-Math.pow(-p,1.0-patternNonLinear);
} else if (patternNonLinear < 0.0) { /** odd harmonics also */
p= 2.0*Math.pow(0.5*(p+1.0),1.0+patternNonLinear)-1.0;
}
p= 0.5*(1.0+patternContrast*p);
p*=patternSimBayer[((i&1)<<1) +(j&1)];
if (noise>0) {
p+=Math.sqrt(noise*p)*generator.nextGaussian();
}
pixels[index++]=(float) p;
}
}
ip_patternSim.setPixels(pixels);
ip_patternSim.resetMinAndMax();
imp_patternSim= new ImagePlus("simulated_"+patternName+"-"+patternType+"-"+patternPeriod+"-"+patternNonLinear, ip_patternSim);
imp_patternSim.show();
IJ.showStatus("Simulated pattern image done.");
}
public boolean accumulateCrosstalk (ImagePlus imp, int size, String title, double threshold) {
ImageProcessor ip=imp.getProcessor();
Roi roi_src= imp.getRoi();
if (roi_src==null){
imp.setRoi(0, 0, imp.getWidth(), imp.getHeight());
roi_src= imp.getRoi();
}
Rectangle r=roi_src.getBounds();
if ((r.width<size) || (r.height<size)) {
IJ.showMessage("Error","Selection is too small");
return false;
}
/** align to Bayer */
if ((r.x & 1) !=0) {
r.width+=1;
r.x--;
}
if ((r.y & 1) !=0) {
r.height+=1;
r.y--;
}
int dw,dh,nw,nh,iw,ih,i;
int row, base, l, c;
dw=r.width-size;
dh=r.height-size;
nw=(dw + (size >> 2)) / (size >>1);
nh=(dh + (size >> 2)) / (size >>1);
/** initialize arrays here */
accum_crosstalk = new double[size*size];
accum_crosstalk_weight= new double[size*size];
for (i=0;i<accum_crosstalk.length;i++) accum_crosstalk[i]=0.0 ; /** is there arrayFill() ? */
for (i=0;i<accum_crosstalk_weight.length;i++) accum_crosstalk_weight[i]=0.0 ; /** is there arrayFill() ? */
/** silent if (nw>0) || (nh>0) or just iw>0 or ih>0 */
Rectangle rs= new Rectangle();
float [] pixels_RminusB;
// float [] pixels_GbminusGr;
float [] pixels_crosstalk;
double p, dn;
dn=1.0/(nh+1)/(nw+1);
rs.width=size;
rs.height=size;
pixels_crosstalk=null;
pixels_RminusB=null;
for (ih=0;ih<=nh;ih++) for (iw=0;iw<=nw;iw++) {
rs.x=(r.x+ ((nw>0)?((dw*iw)/nw):0)) & (~1);
rs.y=(r.y+ ((nh>0)?((dh*ih)/nh):0)) & (~1);
imp_src.setRoi(rs);
preprocessCrosstalk(ip, title+"-"+ih+":"+iw, ((ih!=0) || (iw!=0)));
// measureCrosstalk(ip_RminusB, ip_GbminusGr,newTitle);
// private FHT fht_RminusB, fht_GbminusGr,fht_crosstalk;
fht_RminusB=new FHT(ip_RminusB);
fht_GbminusGr=new FHT(ip_GbminusGr);
fht_RminusB.transform();
fht_GbminusGr.transform();
fht_crosstalk=fht_GbminusGr.divide(fht_RminusB); /** values are twice the crosstalk */
pixels_RminusB=(float[])fht_RminusB.getPixels();
// pixels_GbminusGr=(float[])fht_GbminusGr.getPixels();
pixels_crosstalk=(float[]) fht_crosstalk.getPixels();
for (row=0; row<size; row++) {
base=row*size;
for (c=0; c<size; c++) {
l = ((size-row)%size) * size + (size-c)%size;
p=Math.sqrt(pixels_RminusB[base+c]*pixels_RminusB[base+c] + pixels_RminusB[l]*pixels_RminusB[l]);
accum_crosstalk_weight[base+c]+=p;
accum_crosstalk[base+c]+=p*pixels_crosstalk[base+c];
}
}
}
/** restore, mask*/
double max =0.0;
for (row=0; row<size; row++) {
base=row*size;
for (c=0; c<size; c++) {
p=accum_crosstalk_weight[base+c];
if (p>max) max=p;
}
}
/** divide arrays here */
max*=threshold;
for (row=0; row<size; row++) {
base=row*size;
for (c=0; c<size; c++) {
l = ((size-row)%size) * size + (size-c)%size;
p=accum_crosstalk_weight[base+c];
if (p>=max) {
pixels_crosstalk[base+c]=(float)(accum_crosstalk[base+c]/accum_crosstalk_weight[base+c]);
pixels_crosstalk[l]=(float)(accum_crosstalk[l]/accum_crosstalk_weight[l]);
} else {
pixels_crosstalk[base+c]=0f;
pixels_crosstalk[l]=0f;
}
pixels_RminusB[base+c]=(float) (accum_crosstalk_weight[base+c]*dn);
}
}
fht_crosstalk.setPixels(pixels_crosstalk);
fht_RminusB.setPixels(pixels_RminusB);
/** find results */
finalizeCrosstalk(fht_RminusB, fht_crosstalk, size, title);
imp_src.setRoi(r); /** restore origional ROI */
return true;
}
public void finalizeCrosstalk(FHT fht_RminusB, FHT fht_crosstalk, int size, String title) {
int precision=3;
int region=16;
double [][] K=new double[4][];
int [][] maxXY=new int[4][2];
double [] Kswap;
double [] c;
double e1,e2;
int [] maxXYswap;
float [] pixels_RminusB=(float[]) fht_RminusB.getPixels();
float [] pixels_crosstalk=(float[]) fht_crosstalk.getPixels();
if (DEBUG_LEVEL>3) printComplexSubArray(pixels_RminusB, -(size>>2) - (region>>1) , - (region>>1), region, region, size, "RminusB");
if (DEBUG_LEVEL>2) printComplexSubArray(pixels_crosstalk, -(size>>2) - (region>>1) , - (region>>1), region, region, size, "crosstalk");
/** find two maximums in R-B near vertical lines pattern. One is real, other - alias (gets high energy in Gb-Gr) */
maxXY[0]=findTwoMax(pixels_RminusB, -(size>>2) , 0, region, region, size);
maxXY[1][0]=-(size>>1)-maxXY[0][0];
maxXY[1][1]=-maxXY[0][1];
K[0]=getFHTComplexPixel(pixels_crosstalk, maxXY[0][0], maxXY[0][1], size);
K[1]=getFHTComplexPixel(pixels_crosstalk, maxXY[1][0], maxXY[1][1], size);
/** find two maximums in R-B near horizontal lines pattern. One is real, other - alias (gets high energy in Gb-Gr) */
/** Do it independently, so will work with just vertical or just horizontal lines pattern */
maxXY[2]=findTwoMax(pixels_RminusB, 0, -(size>>2) , region, region, size);
maxXY[3][0]=-maxXY[2][0];
maxXY[3][1]=-(size>>1)-maxXY[2][1];
K[2]=getFHTComplexPixel(pixels_crosstalk, maxXY[2][0], maxXY[2][1], size);
K[3]=getFHTComplexPixel(pixels_crosstalk, maxXY[3][0], maxXY[3][1], size);
/** Find which is real, which is fake */
if ((K[0][0]*K[0][0]+K[0][1]*K[0][1])>(K[1][0]*K[1][0]+K[1][1]*K[1][1])) { /** swap */
Kswap=K[0];
K[0]=K[1];
K[1]=Kswap;
maxXYswap=maxXY[0];
maxXY[0]=maxXY[1];
maxXY[1]=maxXYswap;
}
/** Find which is real, which is fake (make it independently) */
if ((K[2][0]*K[2][0]+K[2][1]*K[2][1])>(K[3][0]*K[3][0]+K[3][1]*K[3][1])) { /** swap */
Kswap=K[2];
K[2]=K[3];
K[3]=Kswap;
maxXYswap=maxXY[2];
maxXY[2]=maxXY[3];
maxXY[3]=maxXYswap;
}
c=getFHTComplexPixel(pixels_RminusB, maxXY[0][0], maxXY[0][1], size);
e1=(Math.sqrt(c[0]*c[0]+c[1]*c[1])/Math.abs(DC_RminusB))/size/size*8.0; /** 8.0 - 8 maximums*/
c=getFHTComplexPixel(pixels_RminusB, maxXY[2][0], maxXY[2][1], size);
e2=(Math.sqrt(c[0]*c[0]+c[1]*c[1])/Math.abs(DC_RminusB))/size/size*8.0;
new TextWindow(title+"_crosstalk_raw_data", "parameter\tvalue",
"File\t"+title+"\n"+
"Color\t"+ ((DC_RBminusGG<0)?"Green/white":((DC_RminusB>0)?"Red":"Blue"))+"\n"+
((DEBUG_LEVEL>4)?
("DC_RBminusGG\t"+DC_RBminusGG+"\n"+
"DC_RminusB\t"+DC_RminusB+"\n"+
"DC_bayer[0]\t"+DC_bayer[0]+"\n"+
"DC_bayer[1]\t"+DC_bayer[1]+"\n"+
"DC_bayer[2]\t"+DC_bayer[2]+"\n"+
"DC_bayer[3]\t"+DC_bayer[3]+"\n"):"")+
((DEBUG_LEVEL>2)?
("K[0]("+maxXY[0][0]+","+maxXY[0][1]+")\t"+IJ.d2s(K[0][0],precision)+ ((K[0][1]>=0)?"+":"")+IJ.d2s(K[0][1],precision)+"i\n"+
"(K[1]("+maxXY[1][0]+","+maxXY[1][1]+")\t"+IJ.d2s(K[1][0],precision)+ ((K[1][1]>=0)?"+":"")+IJ.d2s(K[1][1],precision)+"i)\n"+
"K[2]("+maxXY[2][0]+","+maxXY[2][1]+")\t"+IJ.d2s(K[2][0],precision)+ ((K[2][1]>=0)?"+":"")+IJ.d2s(K[2][1],precision)+"i\n"+
"(K[3]("+maxXY[3][0]+","+maxXY[3][1]+")\t"+IJ.d2s(K[3][0],precision)+ ((K[3][1]>=0)?"+":"")+IJ.d2s(K[3][1],precision)+"i)\n"):"")+
((DEBUG_LEVEL>1)?
/** K[0][0] is inverted as y in the image is upside down */
("S+N\t"+IJ.d2s(-K[0][0],precision)+"\n"+
"E-W\t"+IJ.d2s(K[0][1],precision)+"\n"+
"E+W\t"+IJ.d2s(K[2][0],precision)+"\n"+
"N-S\t"+IJ.d2s(K[2][1],precision)+"\n"):"")+
"N\t"+IJ.d2s(0.5*(-K[0][0]+K[2][1]),precision)+"\n"+
"E\t"+IJ.d2s(0.5*(K[0][1]+K[2][0]),precision)+"\n"+
"S\t"+IJ.d2s(0.5*(-K[0][0]-K[2][1]),precision)+"\n"+
"W\t"+IJ.d2s(0.5*(-K[0][1]+K[2][0]),precision)+"\n"+
"Eh\t"+IJ.d2s(e1,precision)+"\n"+
"Ev\t"+IJ.d2s(e2,precision)+"\n"+
"",
500,300);
if (crosstalkThreshold>0.0) maskFHT (fht_crosstalk, fht_RminusB, crosstalkThreshold);
// ImagePlus imp= new ImagePlus("crosstalk_fht", fht_crosstalk);
// imp.show();
float [] pixels_crosstalk_masked=(float[]) fht_crosstalk.getPixels();
if (DEBUG_LEVEL>2) printComplexSubArray(pixels_crosstalk_masked, -(size>>2) - (region>>1) , - (region>>1), region, region, size, "crosstalk");
if (DEBUG_LEVEL>1) {
ImageStack stack_crosstalk = fht_crosstalk.getComplexTransform();
ImagePlus imp2 = new ImagePlus(title+"_crosstalk_fht_stack", stack_crosstalk);
imp2.getProcessor().resetMinAndMax();
imp2.show();
}
//fht_RminusB
if (DEBUG_LEVEL>2) {
ImagePlus imp3 = new ImagePlus(title+"_accumulated_abs(R-B)", fht_RminusB);
imp3.getProcessor().resetMinAndMax();
imp3.show();
}
}
public Rectangle prepareCrosstalkRegion(ImagePlus imp) {
Roi roi_src= imp.getRoi();
if (roi_src==null){
imp.setRoi(0, 0, imp.getWidth(), imp.getHeight());
roi_src= imp.getRoi();
}
Rectangle r=roi_src.getBounds();
if ((r.width<8) || (r.height<8)) {
IJ.showMessage("Error","Selection is too small");
r.x=0;r.y=0;r.width=0;r.height=0;
return r;
}
/** find largest square power of 2 roi inside the current one */
if ((r.x & 1) !=0) {
r.width+=1;
r.x--;
}
if ((r.y & 1) !=0) {
r.height+=1;
r.y--;
}
int size;
for (size=1; (size<=r.height) && (size<=r.width); size<<=1);
size >>=1;
r.x+=((r.width-size)>>2) <<1;
r.y+=((r.height-size)>>2) <<1;
r.width=size;
r.height=size;
// imp.setRoi(r);
return r;
}
public void preprocessCrosstalk(ImageProcessor ip, String title, boolean silent) {
int n,row,col,index, brow,bcol;
Rectangle r=ip.getRoi();
int size=r.width;
float [] pixels=(float[])ip.crop().getPixels();
/** remove DC, apply Hamming */
DC_bayer=new double[4];
index=0;
for (row=0;row<size;row++) {
brow=row & 1;
for (col=0;col<size;col++) {
bcol=col & 1;
DC_bayer[(brow<<1)+bcol]+=pixels[index++];
}
}
for (n=0;n<4;n++) DC_bayer[n]/=(size*size/4);
DC_RminusB=DC_bayer[1]-DC_bayer[2];
DC_RBminusGG=DC_bayer[1]+DC_bayer[2]-DC_bayer[0]-DC_bayer[3];
double [] hamming=new double[size];
double a=2.0*Math.PI/size;
for (col=0;col< size; col++ ) hamming [col]= 0.54-0.46*Math.cos(a*col);
float [] pixels_RminusB= new float[pixels.length];
float [] pixels_GbminusGr= new float[pixels.length];
ip_RminusB= new FloatProcessor(size,size);
ip_GbminusGr= new FloatProcessor(size,size);
index=0;
for (row=0;row<size;row++) {
brow=row & 1;
for (col=0;col<size;col++) {
bcol=col & 1;
switch ((brow<<1)+bcol) {
case 0: /** Gr */
pixels_GbminusGr[index] = - (float) ((pixels[index]-DC_bayer[0])*hamming[row]*hamming[col]);
pixels_RminusB[index] = 0.0f;
break;
case 1: /** R */
pixels_GbminusGr[index] = 0.0f;
pixels_RminusB[index] = (float) ((pixels[index]-DC_bayer[1])*hamming[row]*hamming[col]);
break;
case 2: /** B */
pixels_GbminusGr[index] = 0.0f;
pixels_RminusB[index] = - (float) ((pixels[index]-DC_bayer[2])*hamming[row]*hamming[col]);
break;
case 3: /** Gb */
pixels_GbminusGr[index] = (float) ((pixels[index]-DC_bayer[3])*hamming[row]*hamming[col]);
pixels_RminusB[index] = 0.0f;
break;
}
index++;
}
}
ip_RminusB.setPixels(pixels_RminusB);
ip_GbminusGr.setPixels(pixels_GbminusGr);
ip_RminusB.resetMinAndMax();
ip_GbminusGr.resetMinAndMax();
if (!silent && (DEBUG_LEVEL>1)) {
imp_RminusB= new ImagePlus(title+"_RminusB", ip_RminusB);
imp_GbminusGr= new ImagePlus(title+"_GbminusGr", ip_GbminusGr);
imp_RminusB.show();
imp_GbminusGr.show();
}
}
public void measureCrosstalk(ImageProcessor ip_RminusB,ImageProcessor ip_GbminusGr, String title) {
int precision=3;
fht_RminusB=new FHT(ip_RminusB);
fht_GbminusGr=new FHT(ip_GbminusGr);
fht_RminusB.transform();
fht_GbminusGr.transform();
int size=ip_RminusB.getWidth();
int region=16;
double [][] K=new double[4][];
int [][] maxXY=new int[4][2];
double [] Kswap;
double [] c;
double e1,e2;
int [] maxXYswap;
float [] pixels_RminusB=(float[])fht_RminusB.getPixels();
float [] pixels_GbminusGr=(float[])fht_GbminusGr.getPixels();
if (DEBUG_LEVEL>3) printComplexSubArray(pixels_RminusB, -(size>>2) - (region>>1) , - (region>>1), region, region, size, "RminusB");
if (DEBUG_LEVEL>3) printComplexSubArray(pixels_GbminusGr, -(size>>2) - (region>>1) , - (region>>1), region, region, size, "GrminusGb");
//public FHT divide(FHT fht) {
fht_crosstalk=fht_GbminusGr.divide(fht_RminusB); /** values are twice the crosstalk */
float [] pixels_crosstalk=(float[]) fht_crosstalk.getPixels();
if (DEBUG_LEVEL>2) printComplexSubArray(pixels_crosstalk, -(size>>2) - (region>>1) , - (region>>1), region, region, size, "crosstalk");
/** find two maximums in R-B near vertical lines pattern. One is real, other - alias (gets high energy in Gb-Gr) */
// maxXY[0]=findTwoMax(pixels_RminusB, -(size>>2) , - (region>>1), region, region, size); // worked?
maxXY[0]=findTwoMax(pixels_RminusB, -(size>>2) , 0, region, region, size);
maxXY[1][0]=-(size>>1)-maxXY[0][0];
maxXY[1][1]=-maxXY[0][1];
K[0]=getFHTComplexPixel(pixels_crosstalk, maxXY[0][0], maxXY[0][1], size);
K[1]=getFHTComplexPixel(pixels_crosstalk, maxXY[1][0], maxXY[1][1], size);
/** find two maximums in R-B near horizontal lines pattern. One is real, other - alias (gets high energy in Gb-Gr) */
/** Do it independently, so will work with just vertical or just horizontal lines pattern */
maxXY[2]=findTwoMax(pixels_RminusB, 0, -(size>>2) , region, region, size);
maxXY[3][0]=-maxXY[2][0];
maxXY[3][1]=-(size>>1)-maxXY[2][1];
K[2]=getFHTComplexPixel(pixels_crosstalk, maxXY[2][0], maxXY[2][1], size);
K[3]=getFHTComplexPixel(pixels_crosstalk, maxXY[3][0], maxXY[3][1], size);
/** Find which is real, which is fake */
if ((K[0][0]*K[0][0]+K[0][1]*K[0][1])>(K[1][0]*K[1][0]+K[1][1]*K[1][1])) { /** swap */
Kswap=K[0];
K[0]=K[1];
K[1]=Kswap;
maxXYswap=maxXY[0];
maxXY[0]=maxXY[1];
maxXY[1]=maxXYswap;
}
/** Find which is real, which is fake (make it independently) */
if ((K[2][0]*K[2][0]+K[2][1]*K[2][1])>(K[3][0]*K[3][0]+K[3][1]*K[3][1])) { /** swap */
Kswap=K[2];
K[2]=K[3];
K[3]=Kswap;
maxXYswap=maxXY[2];
maxXY[2]=maxXY[3];
maxXY[3]=maxXYswap;
}
c=getFHTComplexPixel(pixels_RminusB, maxXY[0][0], maxXY[0][1], size);
e1=(Math.sqrt(c[0]*c[0]+c[1]*c[1])/Math.abs(DC_RminusB))/size/size*8.0; /** 8.0 - 8 maximums*/
c=getFHTComplexPixel(pixels_RminusB, maxXY[2][0], maxXY[2][1], size);
e2=(Math.sqrt(c[0]*c[0]+c[1]*c[1])/Math.abs(DC_RminusB))/size/size*8.0;
//DC_RminusB,DC_RBminusGG
new TextWindow(title+"_crosstalk_raw_data", "parameter\tvalue",
"File\t"+title+"\n"+
"Color\t"+ ((DC_RBminusGG<0)?"Green/white":((DC_RminusB>0)?"Red":"Blue"))+"\n"+
((DEBUG_LEVEL>4)?
("DC_RBminusGG\t"+DC_RBminusGG+"\n"+
"DC_RminusB\t"+DC_RminusB+"\n"+
"DC_bayer[0]\t"+DC_bayer[0]+"\n"+
"DC_bayer[1]\t"+DC_bayer[1]+"\n"+
"DC_bayer[2]\t"+DC_bayer[2]+"\n"+
"DC_bayer[3]\t"+DC_bayer[3]+"\n"):"")+
((DEBUG_LEVEL>2)?
("K[0]("+maxXY[0][0]+","+maxXY[0][1]+")\t"+IJ.d2s(K[0][0],precision)+ ((K[0][1]>=0)?"+":"")+IJ.d2s(K[0][1],precision)+"i\n"+
"(K[1]("+maxXY[1][0]+","+maxXY[1][1]+")\t"+IJ.d2s(K[1][0],precision)+ ((K[1][1]>=0)?"+":"")+IJ.d2s(K[1][1],precision)+"i)\n"+
"K[2]("+maxXY[2][0]+","+maxXY[2][1]+")\t"+IJ.d2s(K[2][0],precision)+ ((K[2][1]>=0)?"+":"")+IJ.d2s(K[2][1],precision)+"i\n"+
"(K[3]("+maxXY[3][0]+","+maxXY[3][1]+")\t"+IJ.d2s(K[3][0],precision)+ ((K[3][1]>=0)?"+":"")+IJ.d2s(K[3][1],precision)+"i)\n"):"")+
((DEBUG_LEVEL>1)?
("S+N\t"+IJ.d2s(-K[0][0],precision)+"\n"+
"E-W\t"+IJ.d2s(K[0][1],precision)+"\n"+
"E+W\t"+IJ.d2s(K[2][0],precision)+"\n"+
"N-S\t"+IJ.d2s(K[2][1],precision)+"\n"):"")+
"N\t"+IJ.d2s(0.5*(-K[0][0]+K[2][1]),precision)+"\n"+
"E\t"+IJ.d2s(0.5*(K[0][1]+K[2][0]),precision)+"\n"+
"S\t"+IJ.d2s(0.5*(-K[0][0]-K[2][1]),precision)+"\n"+
"W\t"+IJ.d2s(0.5*(-K[0][1]+K[2][0]),precision)+"\n"+
"Eh\t"+IJ.d2s(e1,precision)+"\n"+
"Ev\t"+IJ.d2s(e2,precision)+"\n"+
"",
500,300);
// sb.append("\t"+IJ.d2s(c[0],precision)+ ((c[1]>0)?"+":"")+ IJ.d2s(c[1],precision)+"i");
if (crosstalkThreshold>0.0) maskFHT (fht_crosstalk, fht_RminusB, crosstalkThreshold);
// ImagePlus imp= new ImagePlus("crosstalk_fht", fht_crosstalk);
// imp.show();
float [] pixels_crosstalk_masked=(float[]) fht_crosstalk.getPixels();
if (DEBUG_LEVEL>2) printComplexSubArray(pixels_crosstalk_masked, -(size>>2) - (region>>1) , - (region>>1), region, region, size, "crosstalk");
if (DEBUG_LEVEL>1) {
ImageStack stack_crosstalk = fht_crosstalk.getComplexTransform();
ImagePlus imp2 = new ImagePlus(title+"_crosstalk_fht_stack", stack_crosstalk);
imp2.getProcessor().resetMinAndMax();
imp2.show();
}
}
/** return one pixel as complex, -size/2<=x<=+size/2,-size/2<=y<=+size/2, y negative at row=0 */
public double[] getFHTComplexPixel(float [] fht_pixels, int x, int y, int size) {
double [] rslt = {0.0,0.0};
int row= y+(size>>1);
int col= x+(size>>1);
int row1= (size-row) %size;
int col1= (size-col) %size;
// IJ.showMessage("DEBUG520","x="+x+"\ny="+y+"\nsize="+size+"\nrow="+row+"\ncol="+col+"\nrow1="+row1+"\ncol1="+col1);
rslt[0]= 0.5*(fht_pixels[row1*size+col1] + fht_pixels[row*size+col]);
rslt[1]= 0.5*(fht_pixels[row1*size+col1] - fht_pixels[row*size+col]);
return rslt;
}
public void printComplexSubArray(float [] fht_pixels, int x, int y, int w, int h, int size, String title) {
int ix,iy;
int precision=3;
// int size=Math.sqrt(fht_pixels.length);
String header=new String();
StringBuffer sb = new StringBuffer();
double [] c;
header+="y\\x";
for (ix=x; ix< (x+w); ix++ ) header +="\t"+ix;
header +="\n";
for (iy=y; iy< (y+h); iy++ ) {
sb.append(IJ.d2s(iy,0));
for (ix=x; ix< (x+w); ix++ ) {
c=getFHTComplexPixel(fht_pixels, ix, iy, size);
sb.append("\t"+IJ.d2s(c[0],precision)+ ((c[1]>0)?"+":"")+ IJ.d2s(c[1],precision)+"i");
}
sb.append("\n");
}
new TextWindow(title+"_(csv)", header, sb.toString(), 800,600);
}
public int[] findTwoMax(float [] fht_pixels, int x0, int y0, int w, int h, int size) {
int [] rslt= new int[2];
int ix,iy;
int ix0= x0-(w>>1);
int ix1= x0+(w>>1);
int iy1= y0+(w>>1);
double max=0.0;
double [] c1,c2;
double a;
for (iy=y0; iy<= iy1; iy++ ) {
for (ix=ix0; ix<= ix1; ix++ ) {
c1=getFHTComplexPixel(fht_pixels, ix, iy, size);
c2=getFHTComplexPixel(fht_pixels, (x0<<1)-ix, (y0<<1)-iy, size);
a=c1[0]*c1[0]+c1[1]*c1[1]+c2[0]*c2[0]+c2[1]*c2[1];
if (a>max) {
max=a;
rslt[0]=ix;
rslt[1]=iy;
}
}
}
return rslt;
}
/*
StringBuffer sb = new StringBuffer();
for (i=0;i<xValues.length;i++) {
sb.append(IJ.d2s(xValues[i],2));
for (n=0;n<yValues.length;n+=2) sb.append("\t"+IJ.d2s(yValues[n][i],4)+((yValues[n+1][i]>=0)?"+":"")+IJ.d2s(yValues[n+1][i],4)+"i");
sb.append("\n");
}
TextWindow tw = new TextWindow(title+"_(csv)", hs, sb.toString(), (Width>0)?Width:(84*(yValues.length+1)), (Height>0)?Height:600);
*/
public void maskFHT (FHT fht, FHT fht_mask, double threshold) {
int row, base, l, c;
int size=fht.getWidth();
float max = 0f;
float p;
float[] pixels= (float[])fht.getPixels();
float[] mask= (float[])fht_mask.getPixels();
for (row=0; row<size; row++) {
base=row*size;
for (c=0; c<size; c++) {
l = ((size-row)%size) * size + (size-c)%size;
p=(mask[base+c]*mask[base+c] + mask[l]*mask[l])/2f;
if (p>max) max=p;
}
}
max*=threshold*threshold;
for (row=0; row<size; row++) {
base=row*size;
for (c=0; c<size; c++) {
l = ((size-row)%size) * size + (size-c)%size;
p=(mask[base+c]*mask[base+c] + mask[l]*mask[l])/2f;
if (p<max) {
pixels[base+c]=0f;
pixels[l]=0f;
}
}
}
fht.setPixels(pixels);
}
public boolean showSimulationDialog() {
double a;
GenericDialog gd = new GenericDialog("Simulation parameters");
gd.addStringField("Simulation name: ", simulName, 32);
gd.addNumericField("Image width:", simulWidth, 0);
gd.addNumericField("Image height:", simulHeight, 0);
gd.addNumericField("SE pixel shift per row:", simulSlant, 3);
gd.addNumericField("Stripes period (pixels):",simulPeriod, 3);
gd.addNumericField("Contrast (%):", simulContrast*100, 1);
gd.addNumericField("LSF width (pixels):", simulLSFWidth, 3);
gd.addNumericField("Bayer R scale :", simulBayer[1], 3);
gd.addNumericField("Bayer Gr scale :", simulBayer[0], 3);
gd.addNumericField("Bayer Gb scale :", simulBayer[3], 3);
gd.addNumericField("Bayer B scale :", simulBayer[2], 3);
gd.addNumericField("Electrons per 1.0 in the output (0 - no noise) :", simulElectrons, 3);
gd.showDialog();
if (gd.wasCanceled()) return false;
simulName = gd.getNextString();
simulWidth = (int) gd.getNextNumber();
simulHeight = (int) gd.getNextNumber();
simulSlant = gd.getNextNumber();
simulPeriod = gd.getNextNumber();
a=gd.getNextNumber(); if ((a>=0.0) && (a<=100.0)) simulContrast=0.01*a;
simulLSFWidth = gd.getNextNumber();
simulBayer[1] = gd.getNextNumber();
simulBayer[0] = gd.getNextNumber();
simulBayer[3] = gd.getNextNumber();
simulBayer[2] = gd.getNextNumber();
simulElectrons = gd.getNextNumber();
return true;
}
public void createSimulation() {
int i,j,index,ix;
double x,a,b;
int pattDiv=16;
double min,max;
b=2*Math.sqrt(Math.log(2))/simulLSFWidth/pattDiv;
if (!showSimulationDialog()) return;
// IJ.showMessage("Debug","Creating simulation image");
int pattLength= (int)(pattDiv*simulPeriod)+2;
int pattLength4=pattLength/4;
double [] pattern=new double [pattLength];
for (i=0; i<pattLength; i++) pattern[i]=0;
for (i=pattLength4; i< pattLength- pattLength4; i++ ) {
for (j=0;j<pattLength4;j++) {
a=b*j;
a=Math.exp(-(a*a));
pattern[i-j]+=a;
if (j>0) pattern[i+j]+=a;
}
}
min=pattern[0];
max=pattern[1];
for (i=1; i<pattLength; i++) {
if (pattern[i]>max) max=pattern[i];
if (pattern[i]<max) min=pattern[i];
}
a=1.0/(max-min);
for (i=0; i<pattLength; i++) {
pattern[i]=(pattern[i]-min)*a;
}
ip_simul = new FloatProcessor(simulWidth,simulHeight);
float [] pixels= new float [simulWidth *simulHeight];
/**create left side "horizontal" stripes */
IJ.showStatus("Creating horizontal stripes...");
// IJ.showMessage("Debug","simulPeriod*pattDiv="+simulPeriod*pattDiv+"\npattern.length="+pattern.length);
for (i=0;i<(simulWidth>>1); i++ ) {
index=simulWidth*(simulHeight-1)+i;
for (j=0;j<simulHeight;j++) {
x=j+simulSlant*i;
x-=simulPeriod*Math.floor(x/simulPeriod);
x*=pattDiv;
ix= (int) x;
x-=ix;
//if (ix>=(pattern.length-1)) IJ.showMessage("Debug","ix="+ix+"\nx="+x);
pixels[index]=(float)(pattern[ix]*(1.0- x) + x*pattern[ix+1]);
// pixels[index]=(float)(ix+x);
index-=simulWidth;
}
}
/**create right side "vertical" stripes */
IJ.showStatus("Creating verical stripes...");
for (i=0; i<simulHeight; i++ ) {
index=simulWidth*i+ (simulWidth>>1);
for (j=0;j<(simulWidth>>1);j++) {
x=j+simulSlant*i;
x-=simulPeriod*Math.floor(x/simulPeriod);
x*=pattDiv;
ix= (int) x;
x-=ix;
pixels[index+j]=(float)(pattern[ix]*(1.0- x) + x*pattern[ix+1]);
}
}
IJ.showStatus("Applying Bayer colors...");
for (i=0;i<simulHeight; i++ ) {
index=simulWidth*i;
for (j=0;j<simulWidth;j++) {
ix=((i&1)<<1) +(j&1);
a=pixels[index+j];
pixels[index+j]=(float) ((1.0-simulContrast)+simulContrast * a*simulBayer[ix]);
}
}
Random generator = new Random( 123456 );
if (simulElectrons>0.0) {
IJ.showStatus("Aplying noise...");
a=1.0/simulElectrons;
for (i=0;i<simulHeight; i++ ) {
index=simulWidth*i;
for (j=0;j<simulWidth;j++) {
ix=((i&1)<<1) +(j&1);
pixels[index+j]+= Math.sqrt(a*pixels[index+j])*generator.nextGaussian();
}
}
}
ip_simul.setPixels(pixels);
ip_simul.resetMinAndMax();
imp_simul= new ImagePlus(simulName, ip_simul);
imp_simul.show();
IJ.showStatus("Simulation image done.");
}
public boolean showDialog() {
int i,c;
String []threeColors= {"Red","Green","Blue"};
String []dirs={"N__","NE","__E","SE","S__","SW","__W","NW"};
boolean rf,bf;
GenericDialog gd = new GenericDialog("Pixel Crosstalk parameters");
gd.addStringField("Image suffix: ", crossTalkName, 32);
gd.addCheckbox("Convert in-place?", in_place);
// public static boolean monoRed=false; /** use crosstalk for red in all channels */
// public static boolean monoBlue=false; /** use crosstalk for blue in all channels */
gd.addCheckbox("Use red filter?", monoRed && !monoBlue);
gd.addCheckbox("Use blue filter?", !monoRed && monoBlue);
for (c=0;c<threeColors.length;c++) for (i=0;i<dirs.length;i++) {
gd.addNumericField(threeColors[c]+"_"+dirs[i], crossCoeff[c][i], 4);
}
gd.addNumericField("Crosstalk_kernel_FFT_Size:", FFTSize, 0);
gd.addCheckbox("Bayer_mask:_keep_G1?", (BayerMask & 1)!=0);
gd.addCheckbox("Bayer_mask:_keep_R?", (BayerMask & 2)!=0);
gd.addCheckbox("Bayer_mask:_keep_B?", (BayerMask & 4)!=0);
gd.addCheckbox("Bayer_mask:_keep_G2?", (BayerMask & 8)!=0);
gd.addNumericField("Crosstalk_measurement FFT_Size:", imageFFTSize, 0);
gd.addNumericField("Crosstalk processing threshold:", crosstalkThreshold, 3);
gd.addNumericField("Debug Level:", MASTER_DEBUG_LEVEL, 0);
gd.showDialog();
if (gd.wasCanceled()) return false;
crossTalkName = gd.getNextString();
in_place=gd.getNextBoolean();
rf=gd.getNextBoolean();
bf=gd.getNextBoolean();
monoRed=rf;
monoBlue=bf && !rf;
for (c=0;c<threeColors.length;c++) for (i=0;i<dirs.length;i++) {
crossCoeff[c][i]=gd.getNextNumber();
}
FFTSize=1;
for (i=(int) gd.getNextNumber(); i >1; i>>=1) FFTSize <<=1; /** make FFTSize to be power of 2*/
BayerMask = (gd.getNextBoolean())?1:0;
BayerMask |= (gd.getNextBoolean())?2:0;
BayerMask |= (gd.getNextBoolean())?4:0;
BayerMask |= (gd.getNextBoolean())?8:0;
imageFFTSize=1;
for (i=(int) gd.getNextNumber(); i >1; i>>=1) imageFFTSize <<=1; /** make FFTSize to be power of 2*/
crosstalkThreshold= gd.getNextNumber();
MASTER_DEBUG_LEVEL= (int) gd.getNextNumber();
kernels_valid=false;
return true;
}
public void maskBayer(ImageProcessor ip) {
//BayerMask
float [] pixels=(float[])ip.getPixels();
int row,col,c;
int width=ip.getWidth();
int height=ip.getHeight();
for (row=0;row<height;row++) {
for (col=0;col<width; col++) {
c= (((row & 1) <<1) | (col &1));
if (((1<<c) & BayerMask) ==0) pixels[row*width+col]=0.0f;
}
}
ip.setPixels(pixels);
}
public void convolveBayerKernel(ImageProcessor ip) {
float [] src_pixels=(float[])ip.getPixels();
float [] dst_pixels=new float[src_pixels.length];
//arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
// System.arraycopy(src_pixels, 0, dst_pixels, 0, src_pixels.length); /** for the borders closer to 1/2 kernel size*/
// java.util.Arrays.fill(mollyarray,0);
Arrays.fill(dst_pixels,0.0f);
int row,col,i,j,c,c0;
int size=(FFTSize>>1)-1;
int fullSize=FFTSize-1;
float d;
int width=ip.getWidth();
int height=ip.getHeight();
c0=(size & 1) | ((size & 1)<<1); //usually 3
for (row=0;row<(height-fullSize);row++) {
for (col=0;col<(width-fullSize); col++) {
c= (((row & 1) <<1) | (col &1)) ^ c0;
d=src_pixels[(row+size)*width+col+size];
for (i=0;i<fullSize;i++) for (j=0;j<fullSize;j++) {
dst_pixels[(row+i)*width+col+j]+=d*kernels[c][i][j];
}
}
}
/**TODO: Add margins */
ip.setPixels(dst_pixels);
}
public void set2addCrosstalk() {
int i,j,l;
kernels=new float [4][FFTSize-1][FFTSize-1];
for (i=1;i<FFTSize;i++) for (j=1;j<FFTSize;j++) {
l=i*FFTSize+j;
kernels[0][i-1][j-1]=direct_kg[l];
kernels[1][i-1][j-1]=direct_kr[l];
kernels[2][i-1][j-1]=direct_kb[l];
kernels[3][i-1][j-1]=direct_kg[l];
}
}
public void set2removeCrosstalk() {
int i,j,l;
kernels=new float [4][FFTSize-1][FFTSize-1];
for (i=1;i<FFTSize;i++) for (j=1;j<FFTSize;j++) {
l=i*FFTSize+j;
kernels[0][i-1][j-1]=reverse_kg1[l];
kernels[1][i-1][j-1]=reverse_kr[l];
kernels[2][i-1][j-1]=reverse_kb[l];
kernels[3][i-1][j-1]=reverse_kg2[l];
}
}
public void createReverseKernels() {
// showDialog();
ip_kr = new FloatProcessor(FFTSize,FFTSize);
ip_kg = new FloatProcessor(FFTSize,FFTSize);
ip_kb = new FloatProcessor(FFTSize,FFTSize);
IJ.showStatus("Creating crosstalk kernels...");
initCrosstalkKernels();
if (DEBUG_LEVEL>1) showCrosstalkKernels();
IJ.showStatus("Calculating FFHT of crosstalk kernels...");
FHTCrosstalkKernels();
// showCrosstalkKernelsFHT(); ///Not needed - spectra are displayed during transfrorm (depending on FFT options)
setupFK();
if (DEBUG_LEVEL>3) testShowAllFHT();
IJ.showStatus("Reversing crosstalk kernels...");
crossTalkResolve();
if (DEBUG_LEVEL>1) showFHT (FFTHalf2FHT(FRslt_g1), "FRslt_g1");
if (DEBUG_LEVEL>1) showFHT (FFTHalf2FHT(FRslt_r), "FRslt_r");
if (DEBUG_LEVEL>1) showFHT (FFTHalf2FHT(FRslt_b), "FRslt_b");
if (DEBUG_LEVEL>1) showFHT (FFTHalf2FHT(FRslt_g2), "FRslt_g2");
Rslt_g1=iFHT(FFTHalf2FHT(FRslt_g1), "Rslt_g1");
Rslt_r= iFHT(FFTHalf2FHT(FRslt_r), "Rslt_r");
Rslt_b= iFHT(FFTHalf2FHT(FRslt_b), "Rslt_b");
Rslt_g2=iFHT(FFTHalf2FHT(FRslt_g2), "Rslt_g2");
kernels_valid=true;
// private static float[] reverse_kg,reverse_kr,reverse_kb,reverse_kg1;
reverse_kg1=(float[])Rslt_g1.getPixels();
reverse_kr= (float[])Rslt_r.getPixels();
reverse_kb= (float[])Rslt_b.getPixels();
reverse_kg2=(float[])Rslt_g2.getPixels();
IJ.showStatus("Calculating reverse kernels DONE.");
}
public void crossTalkResolve () {
int row, col;
double[][][] MK=new double [4][4][2]; /** elements are initialized from FK** */
double[][][] RK;
FRslt_g1=new double[(FFTSize>>1)+1][FFTSize][2];
FRslt_r= new double[(FFTSize>>1)+1][FFTSize][2];
FRslt_b= new double[(FFTSize>>1)+1][FFTSize][2];
FRslt_g2=new double[(FFTSize>>1)+1][FFTSize][2];
for (row=0;row<=(FFTSize>>1); row++ ) {
for (col = 0; col<FFTSize; col++) {
MK[0][0][0]= FKg[row][col][0]; MK[0][1][0]= FKr[row][col][0]; MK[0][2][0]= FKb[row][col][0]; MK[0][3][0]= FKg[row][col][0];
MK[0][0][1]= FKg[row][col][1]; MK[0][1][1]= FKr[row][col][1]; MK[0][2][1]= FKb[row][col][1]; MK[0][3][1]= FKg[row][col][1];
MK[1][0][0]= FKgX[row][col][0]; MK[1][1][0]= -FKrX[row][col][0]; MK[1][2][0]= FKbX[row][col][0]; MK[1][3][0]= -FKgX[row][col][0];
MK[1][0][1]= FKgX[row][col][1]; MK[1][1][1]= -FKrX[row][col][1]; MK[1][2][1]= FKbX[row][col][1]; MK[1][3][1]= -FKgX[row][col][1];
MK[2][0][0]= FKgY[row][col][0]; MK[2][1][0]= FKrY[row][col][0]; MK[2][2][0]= -FKbY[row][col][0]; MK[2][3][0]= -FKgY[row][col][0];
MK[2][0][1]= FKgY[row][col][1]; MK[2][1][1]= FKrY[row][col][1]; MK[2][2][1]= -FKbY[row][col][1]; MK[2][3][1]= -FKgY[row][col][1];
MK[3][0][0]= FKgXY[row][col][0]; MK[3][1][0]= -FKrXY[row][col][0]; MK[3][2][0]= -FKbXY[row][col][0]; MK[3][3][0]= FKgXY[row][col][0];
MK[3][0][1]= FKgXY[row][col][1]; MK[3][1][1]= -FKrXY[row][col][1]; MK[3][2][1]= -FKbXY[row][col][1]; MK[3][3][1]= FKgXY[row][col][1];
/** RK:= ~(MS*MK*MS) */
RK=complexMatrixInvert4(realComplexMatrixMultiply(MS,complexRealMatrixMultiply(MK,MS)));
/** Save to resuls */
/** lost 1/4 somewhere */
FRslt_g1[row][col][0]=4.0*RK[0][0][0]; FRslt_r[row][col][0]=4.0*RK[0][1][0]; FRslt_b[row][col][0]=4.0*RK[0][2][0]; FRslt_g2[row][col][0]=4.0*RK[0][3][0];
FRslt_g1[row][col][1]=4.0*RK[0][0][1]; FRslt_r[row][col][1]=4.0*RK[0][1][1]; FRslt_b[row][col][1]=4.0*RK[0][2][1]; FRslt_g2[row][col][1]=4.0*RK[0][3][1];
}
}
}
/*
| Kg Kr Kb Kg |
| KgX -KrX KbX -KgX | = MK
| KgY KrY -KbY -KgY |
| KgXY -KrXY -KbXY KgXY |
private double [][][] FKr,FKrX,FKrY,FKrXY, FKg,FKgX,FKgY,FKgXY, FKb,FKbX,FKbY,FKbXY;
private double [][][] FRslt_g1, FRslt_r, FRslt_b, FRslt_g2;
private double [][] Rslt_g1, Rslt_r, Rslt_b, Rslt_g2;
MS[4][4]
private double[][][] complexRealMatrixMultiply (double[][][] a, double[][] b ) {
private double[][][] realComplexMatrixMultiply (double[][] a, double[][][] b ) {
private double[][][] complexMatrixInvert4 (double[][][] a ) {
private FHT fht_rslt_g1, fht_rslt_r, fht_rslt_b, fht_rslt_g2;
public FHT getCopy() {
ImageProcessor ip = super.duplicate();
FHT fht = new FHT(ip);
fht.isFrequencyDomain = isFrequencyDomain;
fht.quadrantSwapNeeded = quadrantSwapNeeded;
fht.rgb = rgb;
fht.originalWidth = originalWidth;
fht.originalHeight = originalHeight;
fht.originalBitDepth = originalBitDepth;
fht.originalColorModel = originalColorModel;
return fht;
}
fht_kr
inverseTransform()
*/
/** make FHT from array of pixels, perform inverse FHT */
//public ImagePlus iFHT(float[] fht_pixels, String title) {
public FHT iFHT(float[] fht_pixels, String title) {
FHT fht=fht_kr.getCopy(); /** could not find a way to create a frequency-domain FHT, had to copy one */
// ImageProcessor ip_fht = new FloatProcessor(FFTSize,FFTSize);
/*
ip_fht.setPixels(fht_pixels);
ip_fht.resetMinAndMax();
ImagePlus imp= new ImagePlus(title, ip_fht);
*/
fht.setPixels(fht_pixels);
fht.inverseTransform();
fht.swapQuadrants(); /** put 0,0 in the center */
float[] pixels=(float[])fht.getPixels();
int i;
double S=0.0;
for (i=0;i<pixels.length;i++) S+=pixels[i];
fht.resetMinAndMax();
// ImagePlus imp= new ImagePlus(title, ip_fht);
if (DEBUG_LEVEL>0) {
ImagePlus imp= new ImagePlus(title+" "+S, fht);
imp.show();
}
// return imp;
return fht;
}
public void FHTCrosstalkKernels() {
fht_kr = new FHT(ip_kr);
fht_kg = new FHT(ip_kg);
fht_kb = new FHT(ip_kb);
/** Swapping quadrants, so the center will be 0,0 */
fht_kr.swapQuadrants();
fht_kg.swapQuadrants();
fht_kb.swapQuadrants();
fht_kr.transform();
fht_kg.transform();
fht_kb.transform();
}
private void showFHT (float [] fht_pixels, String title) {
ImageProcessor ip_fht = new FloatProcessor(FFTSize,FFTSize);
ip_fht.setPixels(fht_pixels);
ip_fht.resetMinAndMax();
ImagePlus imp= new ImagePlus(title, ip_fht);
imp.show();
}
private void testShowAllFHT () {
showFHT (FFTHalf2FHT(FKr), "FKr");
showFHT (FFTHalf2FHT(FKrX), "FKrX");
showFHT (FFTHalf2FHT(FKrY), "FKrY");
showFHT (FFTHalf2FHT(FKrXY),"FKrXY");
showFHT (FFTHalf2FHT(FKg), "FKg");
showFHT (FFTHalf2FHT(FKgX), "FKgX");
showFHT (FFTHalf2FHT(FKgY), "FKgY");
showFHT (FFTHalf2FHT(FKgXY),"FKgXY");
showFHT (FFTHalf2FHT(FKb), "FKb");
showFHT (FFTHalf2FHT(FKbX), "FKbX");
showFHT (FFTHalf2FHT(FKbY), "FKbY");
showFHT (FFTHalf2FHT(FKbXY),"FKbXY");
}
// private double [][][] FKr,FKrX,FKrY,FKrXY, FKg,FKgX,FKgY,FKgXY, FKb,FKbX,FKbY,FKbXY;
private void setupFK () {
FKr =FHT2FFTHalf (fht_kr);
FKrX =shiftX(FKr);
FKrY =shiftY(FKr);
FKrXY=shiftY(FKrX);
FKg =FHT2FFTHalf (fht_kg);
FKgX =shiftX(FKg);
FKgY =shiftY(FKg);
FKgXY=shiftY(FKgX);
FKb =FHT2FFTHalf (fht_kb);
FKbX =shiftX(FKb);
FKbY =shiftY(FKb);
FKbXY=shiftY(FKbX);
}
/** shifts sides horizontally by 1/2 of the range */
private double[][][] shiftX (double[][][] k) {
double[][][] result = new double[(FFTSize>>1)+1][FFTSize][2];
int row, col;
for (row=0;row<=(FFTSize>>1);row++) for (col=0;col<FFTSize;col++) {
result[row][(col + (FFTSize >> 1)) % FFTSize][0]=k[row][col][0];
result[row][(col + (FFTSize >> 1)) % FFTSize][1]=k[row][col][1];
}
return result;
}
/** shifts sides vertically by 1/2 of the range */
private double[][][] shiftY (double[][][] k) {
double[][][] result = new double[(FFTSize>>1)+1][FFTSize][2];
int row1, row2, col;
for (row1=0;row1<=(FFTSize>>1);row1++) {
row2=(FFTSize>>1)-row1;
for (col=0;col< FFTSize;col++) {
result[row1][col][0]= k[row2][col][0];
result[row1][col][1]=-k[row2][col][1];
}
}
return result;
}
/** converts FHT results (frequency space) to complex numbers of [FFTSize/2+1][FFTSize] */
private double[][][] FHT2FFTHalf (FHT fht) {
float[] fht_pixels=(float[])fht.getPixels();
double[][][] fftHalf=new double[(FFTSize>>1)+1][FFTSize][2];
int row1,row2,col1,col2;
// double dbg1, dbg2,dbg3, dbg4;
for (row1=0;row1<=(FFTSize>>1);row1++) {
row2=(FFTSize-row1) %FFTSize;
for (col1=0;col1<FFTSize;col1++) {
col2=(FFTSize-col1) %FFTSize;
fftHalf[row1][col1]= complex( 0.5*(fht_pixels[row1*FFTSize+col1] + fht_pixels[row2*FFTSize+col2]),
0.5*(fht_pixels[row2*FFTSize+col2] - fht_pixels[row1*FFTSize+col1]));
}
}
return fftHalf;
}
/** converts FFT arrays of complex numbers of [FFTSize/2+1][FFTSize] to FHT arrays */
private float[] FFTHalf2FHT (double [][][] fft) {
float[] fht_pixels=new float [FFTSize*FFTSize];
int row1,row2,col1,col2;
for (row1=0;row1<=(FFTSize>>1);row1++) {
row2=(FFTSize-row1) %FFTSize;
for (col1=0;col1 < FFTSize;col1++) {
col2=(FFTSize-col1) %FFTSize;
/** out of bounds */
fht_pixels[row1*FFTSize+col1]=(float)(fft[row1][col1][0]+fft[row1][col1][1]);
fht_pixels[row2*FFTSize+col2]=(float)(fft[row1][col1][0]-fft[row1][col1][1]);
}
}
return fht_pixels;
}
//public static boolean monoRed=false; /** use crosstalk for red in all channels */
// public static boolean monoBlue=false; /** use crosstalk for blue in all channels */
public void initCrosstalkKernels() {
double [] cScales = new double[3];
int center=FFTSize>>1;
int [][]dirsVectors={{ 0,-1}, /** N*/
{ 1,-1}, /** NE*/
{ 1, 0}, /** E*/
{ 1, 1}, /** SE*/
{ 0, 1}, /** S*/
{-1, 1}, /** SW*/
{-1, 0}, /** W*/
{-1,-1}};/** NW*/
int c,i;
for (c=0;c<3;c++) {
cScales[c]=1.0;
for (i=0;i<8;i++) cScales[c]+=crossCoeff[monoRed?0:(monoBlue?2:c)][i];
cScales[c]=1.0/cScales[c];
}
ip_kr.putPixelValue(center, center ,cScales[monoRed?0:(monoBlue?2:0)]);
ip_kg.putPixelValue(center, center ,cScales[monoRed?0:(monoBlue?2:1)]);
ip_kb.putPixelValue(center, center ,cScales[monoRed?0:2]);
for (i=0;i<8;i++) {
ip_kr.putPixelValue(center+dirsVectors[i][0], center+dirsVectors[i][1] ,cScales[0]*crossCoeff[monoRed?0:(monoBlue?2:0)][i]);
ip_kg.putPixelValue(center+dirsVectors[i][0], center+dirsVectors[i][1] ,cScales[1]*crossCoeff[monoRed?0:(monoBlue?2:1)][i]);
ip_kb.putPixelValue(center+dirsVectors[i][0], center+dirsVectors[i][1] ,cScales[2]*crossCoeff[monoRed?0:2][i]);
}
// private static double [][] direct_kr,direct_kg,direct_kb,;
direct_kr=(float[])ip_kr.getPixels();
direct_kg=(float[])ip_kg.getPixels();
direct_kb=(float[])ip_kb.getPixels();
ip_kr.resetMinAndMax();
ip_kg.resetMinAndMax();
ip_kb.resetMinAndMax();
}
public void printComplexMatrix(double[][][] a, String title) {
int i,j;
String s=new String();
for (i=0;i<a.length;i++) {
for (j=0;j<a[0].length;j++) {
s+= String.format(" %7.3f(%7.3f)",a[i][j][0],a[i][j][1]);
}
s+="\n";
}
IJ.showMessage(title+" ("+ a.length+"x"+a[0].length+")",s);
}
public double [][][] complexRandom(int h, int w) {
Random generator = new Random( 123456 );
int i,j;
double[][][] result =new double[h][w][2];
for (i=0;i<h;i++) for (j=0;j<w;j++) {
result[i][j][0]=2.0*(generator.nextDouble()-0.5);
result[i][j][1]=2.0*(generator.nextDouble()-0.5);
}
return result;
}
public double[][][] complexMatrixAdd (double[][][] a, double[][][] b ) {
int h=a.length;
int w=a[0].length;
int i,j;
if ((b[0].length!=w) || (b.length!=h)) return null;
double[][][] result=new double[h][w][2];
for (i=0;i<h;i++) for (j=0;j<w;j++) {
result[i][j]=complexAdd(a[i][j], b[i][j]);
}
return result;
}
public double[][][] complexMatrixScale (double[][][] a, double k ) {
int h=a.length;
int w=a[0].length;
int i,j;
double[][][] result=new double[h][w][2];
for (i=0;i<h;i++) for (j=0;j<w;j++) {
result[i][j]=complexScale(a[i][j], k);
}
return result;
}
public double[][][] complexMatrixConjugate (double[][][] a ) {
int h=a.length;
int w=a[0].length;
int i,j;
double[][][] result=new double[h][w][2];
for (i=0;i<h;i++) for (j=0;j<w;j++) {
result[i][j]=complexConjugate(a[i][j]);
}
return result;
}
public double[][][] complexMatrixTranspose (double[][][] a ) {
int h=a.length;
int w=a[0].length;
int i,j;
double[][][] result=new double[w][h][2];
for (i=0;i<h;i++) for (j=0;j<w;j++) {
result[j][i]=a[i][j];
}
return result;
}
public double[][][] complexMatrixMultiply (double[][][] a, double[][][] b ) {
int h=a.length;
int n=a[0].length;
int w=b[0].length;
int i,j,k;
if (b.length!=n) return null;
double[][][] result=new double[h][w][2];
for (i=0;i<h;i++) for (j=0;j<w;j++) {
result[i][j][0]=0.0;
result[i][j][1]=0.0;
for (k=0;k<n;k++) result[i][j]=complexAdd(result[i][j],complexMultiply(a[i][k],b[k][j])); /** out of boud */
}
return result;
}
public double[][][] complexRealMatrixMultiply (double[][][] a, double[][] b ) {
int h=a.length;
int n=a[0].length;
int w=b[0].length;
int i,j,k;
if (b.length!=n) return null;
double[][][] result=new double[h][w][2];
for (i=0;i<h;i++) for (j=0;j<w;j++) {
result[i][j][0]=0.0;
result[i][j][1]=0.0;
for (k=0;k<n;k++) result[i][j]=complexAdd(result[i][j],complexScale(a[i][k],b[k][j]));
}
return result;
}
public double[][][] realComplexMatrixMultiply (double[][] a, double[][][] b ) {
int h=a.length;
int n=a[0].length;
int w=b[0].length;
int i,j,k;
if (b.length!=n) return null;
double[][][] result=new double[h][w][2];
for (i=0;i<h;i++) for (j=0;j<w;j++) {
result[i][j][0]=0.0;
result[i][j][1]=0.0;
for (k=0;k<n;k++) result[i][j]=complexAdd(result[i][j],complexScale(b[k][j],a[i][k]));
}
return result;
}
public void matrix4InvertInit() { /** May be extended to different dimensions */
int row,k,l,l1,s;
int[] i ={0,0,0,0,0};
boolean[] t= {true,true,true,true};
int[] seq= {0,0,0,0};
if ( invertDimension!=4) {
invertSeq= new int [4][4][6][7] ; /** once calculated sequence of elements for 4x4 matrix inversion i,j,i,j,i,j,sign */
determinantSeq=new int [24][5] ; /** once calculated sequence of elements for 4x4 matrix determinant: j,j,j,j,sign */
k=0;
for (i[1]=0;i[1]<4;i[1]++) {
t[i[1]]=false;
for (i[2]=0;i[2]<4;i[2]++) if (t[i[2]]) {
t[i[2]]=false;
for (i[3]=0;i[3]<4;i[3]++) if (t[i[3]]) {
t[i[3]]=false;
for (i[4]=0;i[4]<4;i[4]++) if (t[i[4]]) { /** now all i1,i2,i3,i4 are different */
determinantSeq[k][0]=i[1];
determinantSeq[k][1]=i[2];
determinantSeq[k][2]=i[3];
determinantSeq[k][3]=i[4];
seq[0]=i[1];seq[1]=i[2];seq[2]=i[3];seq[3]=i[4];
determinantSeq[k][4]=1;
while ((seq[0]>seq[1]) || (seq[1]>seq[2]) || (seq[2]>seq[3])) {
if (seq[0]>seq[1]) {s=seq[0];seq[0]=seq[1];seq[1]=s;}
else if (seq[1]>seq[2]) {s=seq[1];seq[1]=seq[2];seq[2]=s;}
else if (seq[2]>seq[3]) {s=seq[2];seq[2]=seq[3];seq[3]=s;}
determinantSeq[k][4]*=-1;
}
k++;
}
t[i[3]]=true;
}
t[i[2]]=true;
}
t[i[1]]=true;
}
for (row=0; row<4; row++) for (i[1]=0; i[1]<4; i[1]++) {
t[i[1]]=false;
k=0;
for (i[2]=0;i[2]<4;i[2]++) if (t[i[2]]) {
t[i[2]]=false;
for (i[3]=0;i[3]<4;i[3]++) if (t[i[3]]) {
t[i[3]]=false;
for (i[4]=0;i[4]<4;i[4]++) if (t[i[4]]) { /** now all i1,i2,i3,i4 are different */
seq[row]=i[1];
l1=0;
for (l=0;l<4;l++) if (l!=row) {
seq[l]=(l>row)?i[l+1]:i[l+2];
invertSeq[row][i[1]][k][l1++]=l;
invertSeq[row][i[1]][k][l1++]=seq[l];
}
/** calculate sign of the term */
invertSeq[row][i[1]][k][l1]=1;
while ((seq[0]>seq[1]) || (seq[1]>seq[2]) || (seq[2]>seq[3])) {
if (seq[0]>seq[1]) {s=seq[0];seq[0]=seq[1];seq[1]=s;}
else if (seq[1]>seq[2]) {s=seq[1];seq[1]=seq[2];seq[2]=s;}
else if (seq[2]>seq[3]) {s=seq[2];seq[2]=seq[3];seq[3]=s;}
invertSeq[row][i[1]][k][l1]*=-1;
}
k++;/** next term */
}
t[i[3]]=true;
}
t[i[2]]=true;
}
t[i[1]]=true;
}
invertDimension=4;
}
}
/** to troubleshoot - try real matrices */
private double[][][] complexMatrixInvert4 (double[][][] a ) {
if ( invertDimension!=4) matrix4InvertInit();
if ((a.length!=4) ||(a[0].length!=4) || (a[0][0].length!=2)) {
IJ.showMessage("ERROR in complexMatrixInvert4()","complexMatrixInvert4() requires complex matrices [4,4]" );
return null;
}
int i,j,k;
double[][][] result = new double[4][4][2];
double[] det=new double[2];
double[] ddet=new double[2];
double[] d=new double[2];
double[] d1=new double[2];
//String debug_string= new String();
det=complex (0.0,0.0);
for (k=0;k<determinantSeq.length;k++) {
d=complexMultiply(a[0][determinantSeq[k][0]],a[1][determinantSeq[k][1]]);
d=complexMultiply(d,a[2][determinantSeq[k][2]]);
d=complexMultiply(d,a[3][determinantSeq[k][3]]);
d=complexScale (d, determinantSeq[k][4]); //sign
det=complexAdd(det,d);
// debug_string+=d[0]+"("+d[1]+")\n";
}
// IJ.showMessage("Debug:complexMatrixInvert4()","Determinant="+det[0]+"("+det[1]+")\n"+debug_string);
if ((det[0]==0.0) && (det[1]==0.0)) det[0]=0.0000001; // to make sure determinant is non-zero
ddet=complexDivide(complex(1.0,0.0),det);
// IJ.showMessage("Debug:complexMatrixInvert4()","1/Determinant="+ddet[0]+"("+ddet[1]+")" );
/* d=complexMultiply(det,ddet);
IJ.showMessage("Debug:complexMatrixInvert4()","1/Determinant*Determinant="+d[0]+"("+d[1]+")" );*/
for (i=0;i<4;i++) for (j=0;j<4;j++) {
d1=complex(0.0,0.0);
for (k=0;k<invertSeq[i][j].length;k++) {
d=complexMultiply(a[invertSeq[i][j][k][0]][invertSeq[i][j][k][1]] , a[invertSeq[i][j][k][2]][invertSeq[i][j][k][3]] );
d=complexMultiply(d , a[invertSeq[i][j][k][4]][invertSeq[i][j][k][5]]);
if (invertSeq[i][j][k][6] <0) {d[0]=-d[0];d[1]=-d[1];}
d1[0]+=d[0];
d1[1]+=d[1];
}
result[j][i]=complexMultiply(d1,ddet);
}
return result;
}
public double[] complex(double re, double im) {
double[] result = {re,im};
return result;
}
public double[] complexAdd(double[] a, double [] b) {
double[] result = {a[0]+b[0],a[1]+b[1]};
return result;
}
public double[] complexSubtract(double[] a, double [] b) {
double[] result = {a[0]-b[0],a[1]-b[1]};
return result;
}
public double[] complexScale(double[] a, double k) {
double[] result = {a[0]*k, a[1]*k};
return result;
}
public double[] complexConjugate(double[] a) {
double[] result = {a[0],-a[1]};
return result;
}
public double[] complexMultiply(double[] a, double [] b) {
double[] result = {a[0]*b[0]-a[1]*b[1],a[1]*b[0]+a[0]*b[1]};
return result;
}
public double[] complexDivide(double[] a, double [] b) {
double l2=b[0]*b[0] + b[1]*b[1];
double[] result = {(a[0]*b[0]+a[1]*b[1])/l2,(a[1]*b[0]-a[0]*b[1])/l2};
return result;
}
/** Still do not understand - how to open scaled image that is all painted */
public void showCrosstalkKernels() {
int drawScale=16;
imp_kr= new ImagePlus("Red Crosstalk Kernel", ip_kr);
imp_kr.show();
ImageWindow win_kr = imp_kr.getWindow();
win_kr.getCanvas().setMagnification(drawScale);
ImageCanvas ic_kr = imp_kr.getCanvas();
ic_kr.setSourceRect(new Rectangle(0, 0, drawScale*FFTSize, drawScale*FFTSize));
ic_kr.setDrawingSize(drawScale*FFTSize, drawScale*FFTSize);
win_kr.pack();
win_kr.repaint();
// imp_kr.updateAndDraw();
imp_kg= new ImagePlus("Green Crosstalk Kernel", ip_kg);
imp_kg.show();
ImageWindow win_kg = imp_kg.getWindow();
win_kg.getCanvas().setMagnification(drawScale);
ImageCanvas ic_kg = imp_kg.getCanvas();
ic_kg.setSourceRect(new Rectangle(0, 0, drawScale*FFTSize, drawScale*FFTSize));
ic_kg.setDrawingSize(drawScale*FFTSize, drawScale*FFTSize);
//imp_kg.updateAndDraw();
win_kg.pack();
win_kg.repaint();
imp_kb= new ImagePlus("Blue Crosstalk Kernel", ip_kb);
imp_kb.show();
ImageWindow win_kb = imp_kb.getWindow();
win_kb.getCanvas().setMagnification(drawScale);
ImageCanvas ic_kb = imp_kb.getCanvas();
ic_kb.setSourceRect(new Rectangle(0, 0, drawScale*FFTSize, drawScale*FFTSize));
ic_kb.setDrawingSize(drawScale*FFTSize, drawScale*FFTSize);
win_kb.pack();
// win_kb.repaint();
// win_kb.updateImage(imp_kb); /**makes it magnification==1 *
imp_kb.updateAndRepaintWindow();
imp_kr.updateAndRepaintWindow();
}
public void showCrosstalkKernelsFHT() {
fht_kr.getPowerSpectrum ();
fht_kg.getPowerSpectrum ();
fht_kb.getPowerSpectrum ();
}
}
/**
.
Interpixel crosstalk causes some "leak" between pixels, leak that happens after the color filter array.
And photons of different wavelengths get to the different depth, so red pixels cause more crosstalk than
the green or blue ones. This program calculates the convolution kernels to correct that effect.
To use those kernels you need to split acquired pixel (Bayer) array:
G1 R G1 R G1 R..
B G2 B G2 B G2..
G1 R G1 R G1 R..
B G2 B G2 B G2..
...
into 4 sub-arrays:
G1 0 G1 0 G1 0..
0 0 0 0 0 0
G1 0 G1 0 G1 0..
0 0 0 0 0 0
0 R 0 R 0 R.
0 0 0 0 0 0
0 R 0 R 0 R.
0 0 0 0 0 0
0 0 0 0 0 0
B 0 B 0 0 G2..
0 0 0 0 0 0
B 0 B 0 0 G2..
and
0 0 0 0 0 0
0 G2 0 G2 0 G2..
0 0 0 0 0 0
0 G2 0 G2 0 G2..
Then convolve each of them with the corresponding reverse kernel calculated by this program and add together.
The result should be image with eth crosstalk compensated.
Below is the explanation to the reverse kernels calculation.
Bayer arrays (repeat through all the image)
Bg1=1 0
0 0
Br= 0 1
0 0
Bb= 0 0
1 0
Bg2=0 0
0 1
Inp (x,y) - input light (before crosstalk)
Meas(x,y) - measured pixels (suffered by crosstalk)
Kr, Kg, Kb - forward crosstalk kernels
@ - convolution
Meas=(Inp*Br) @ Kr + (Inp*(Bg1+Bg2)) @ Kg + (Inp*Bb) @ Kb
In the frequency domain (after applying forward 2d Fourier transform F[])
F[Meas]= (F[Inp]@F[Br]) * F[Kr] +
(F[Inp]@F[Bg1]) * F[Kg] +
(F[Inp]@F[Bg2]) * F[Kg] +
(F[Inp]@F[Bb]) * F[Kb]
So multiplication (masking) became convolution, convolution - multiplication. Luckily convolution with F[Br] (and other Bayer masks)
is rather simple. Convolutions result that the source is replicated 4 times, each replica is shifted by half of the full range
vertically and/or horizontally (i.e. for 16x16 FFT the shift is by 8 pixels), the replicas are combined with coeffients of +/- 1
FKr=F[Kr], FKg=F[Kg], FKb=F[Kb],...
FX = F[Inp] shifted by 1/2 X range (horizontal quadrant swap: 12/34 -> 21/43)
FY = F[Inp] shifted by 1/2 Y range (vertical quadrant swap: 12/34 -> 34/12)
FXY = F[Inp] shifted by 1/2 X and 1/2 Y range (diagonal quadrant swap: 12/34 -> 43/21)
Here are the signs of the replicas:
F[Inp]@F[Br] =F-FX+FY-FXY
F[Inp]@F[Bg1]=F+FX+FY+FXY
F[Inp]@F[Bg2]=F-FX-FY+FXY
F[Inp]@F[Bb] =F+FX-FY-FXY
This can be reprfesented with matrices:
| F[Inp]@F[Bg1] | | 1 1 1 1 | | F |
| F[Inp]@F[Br] | = | 1 -1 1 -1 | * | FX |
| F[Inp]@F[Bb] | | 1 1 -1 -1 | | FY |
| F[Inp]@F[Bg2] | | 1 -1 -1 1 | | FXY|
| F[Inp]@F[Bg1] |
| F[Inp]@F[Br] | = VFIC
| F[Inp]@F[Bb] |
| F[Inp]@F[Bg2] |
| 1 1 1 1 |
| 1 -1 1 -1 | = MS
| 1 1 -1 -1 |
| 1 -1 -1 1 |
| F |
| FX | = VF
| FY |
| FXY|
VFIC = MS *VF
F[Meas]= (F+FX+FY+FXY) * FKg +
(F-FX+FY-FXY) * FKr +
(F+FX-FY-FXY) * FKb +
(F-FX-FY+FXY) * FKg
| Kg |
| Kr | = VK
| Kb |
| Kg |
FM =|Kg Kr Kb Kg| * VFIC
where FM =F[Meas]
FM= transpose(VK) * VFIC = transpose(VK) * MS * VF
Applying same quadrant swap to FM (measured data) we can get 3 more equations (to be used to represent the
result as a convolution with 4 masked sub-arrays of the image)
FM =F[Meas], FMX=..., FMY=..., FMXY=... (quadrant swap)
| FMBg1 | | F[Meas*Bg1] | | F[Meas]@F[Bg1] | | FM+FMX+FMY+FMXY | | 1 1 1 1 | | FM |
| FMBr | = | F[Meas*Br] | = | F[Meas]@F[Br] | = | FM-FMX+FMY-FMXY | = | 1 -1 1 -1 | * | FMX |
| FMBb | | F[Meas*Bb] | | F[Meas]@F[Br] | | FM+FMX-FMY-FMXY | | 1 1 -1 -1 | | FMY |
| FMBg2 | | F[Meas*Bg2] | | F[Meas]@F[Bg2] | | FM-FMX-FMY+FMXY | | 1 -1 -1 1 | | FMXY |
| FMBg1 |
| FMBr | = VFMB // vector of F[] of the pixels multiplied (masked) by Bayer (i.e. FMBg1 - Fourier of all but G1 pixels set to zero)
| FMBb |
| FMBg2 |
| FM |
| FMX | = VFM // Vector of Fourier of measured pixels and with quadrants swapped
| FMY |
| FMXY |
VFMB= MS* VFM
================
FM= transpose(VK) * VFIC = transpose(VK) * MS * VF
VFX=MX*VF
| 0 1 0 0 |
| 1 0 0 0 | = MX
| 0 0 0 1 |
| 0 0 1 0 |
| 0 0 1 0 |
| 0 0 0 1 | = MY
| 1 0 0 0 |
| 0 1 0 0 |
| 0 0 0 1 |
| 0 0 1 0 | = MXY
| 0 1 0 0 |
| 1 0 0 0 |
FM= transp(VK) * MS * VF
FMX= transp(VKX) * MS * MX * VF
FMY= transp(VKY) * MS * MY * VF
FMXY= transp(VKXY) * MS * MXY * VF
FMX= transp(VKX) * (MS * MX * ~MS) * MS * VF
FMY= transp(VKY) * (MS * MY * ~MS) * MS * VF
FMXY= transp(VKXY) * (MS * MXY * ~MS) * MS * VF
MSX= (MS * MX * ~MS)
MSY= (MS * MY * ~MS)
MSXY= (MS * MXY * ~MS)
| 1 0 0 0 |
| 0 -1 0 0 | = MSX= (MS * MX * ~MS)
| 0 0 1 0 |
| 0 0 0 -1 |
| 1 0 0 0 |
| 0 1 0 0 | = MSY= (MS * MY * ~MS)
| 0 0 -1 0 |
| 0 0 0 -1 |
| 1 0 0 0 |
| 0 -1 0 0 | = MSXY= (MS * MXY * ~MS)
| 0 0 -1 0 |
| 0 0 0 1 |
FM= transp(VK) * MS * VF
FMX= transp(VKX) * MSX * MS * VF
FMY= transp(VKY) * MSY * MS * VF
FMXY= transp(VKXY) * MSXY * MS * VF
| Kg |
| Kr | = VK
| Kb |
| Kg |
| KgX |
| KrX | = VKX
| KbX |
| KgX |
| KgY |
| KrY | = VKY
| KbY |
| KgY |
| KgXY |
| KrXY | = VKXY
| KbXY |
| KgXY |
| Kg Kr Kb Kg |
| KgX -KrX KbX -KgX | = MK
| KgY KrY -KbY -KgY |
| KgXY -KrXY -KbXY KgXY |
| FM |
| FMX | = VFM // Vector of Fourier of measured pixels and with quadrants swapped
| FMY |
| FMXY |
| FMBg1 |
| FMBr | = VFMB // vector of F[] of the pixels multiplied by bayer (i.e. FMBg1 - Fourier of all but G1 pixels set to zero)
| FMBb |
| FMBg2 |
VFM=MK*MS*VF //! Fourie of the measured picture (differently swapped quadrants) from "ideal" picture and kernels
VFMB= MS* VFM //! Fourie of the measured picture masked by Bayer patterns from Fourie of the (not masked) measured picture, shifted by quadrants
VFMB= MS*MK*MS* VF
VF =~(MS*MK*MS) * VFMB
MI=~(MS*MK*MS) //!, then
VF =MI * VFMB //! We need just the first line of MI, make inverse FFT and it will produce the 4 kernel for convolution
//! with measured picture, masked with Bayer patterns (result is the sum of them)
*/
/**
** -----------------------------------------------------------------------------**
** DebayerScissors.java
**
** Frequency-domain debosaic methods for aberration correction for Eyesis4pi
**
**
** Copyright (C) 2012 Elphel, Inc.
**
** -----------------------------------------------------------------------------**
**
** DebayerScissors.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/>.
** -----------------------------------------------------------------------------**
**
*/
import ij.IJ;
import ij.ImageStack;
import java.util.concurrent.atomic.AtomicInteger;
public class DebayerScissors {
// showDoubleFloatArrays SDFA_INSTANCE= new showDoubleFloatArrays();
public AtomicInteger stopRequested=null; // 1 - stop now, 2 - when convenient
double [] debayerEnergy;
int debayerEnergyWidth;
int debugLevel=1;
public DebayerScissors(AtomicInteger stopRequested){
this.stopRequested=stopRequested;
}
double [] getDebayerEnergy() {return this.debayerEnergy;}
int getDebayerEnergyWidth() {return this.debayerEnergyWidth;}
void setDebug(int debugLevel){this.debugLevel=debugLevel;}
// uses global OUT_PIXELS to accumulate results
public ImageStack aliasScissorsStack (
final ImageStack imageStack, // stack with 3 colors/slices with the image
final EyesisCorrectionParameters.DebayerParameters debayerParameters, // 64 - fft size
final boolean generateDebayerEnergy,
final int threadsMax, // maximal step in pixels on the maxRadius for 1 angular step (i.e. 0.5)
final boolean updateStatus, // update status info
final int globalDebugLevel)
{
// final int wasDebugLevel=this.debugLevel;
// this debug of a specific tile will work in a single-threading only, because it uses global DEBUG_LEVEL
final int xTileDebug, yTileDebug;
if (!debayerParameters.debug || (debayerParameters.xDebug<0) || (debayerParameters.xDebug<0)) {
xTileDebug=-1;
yTileDebug=-1;
} else {
xTileDebug=(debayerParameters.xDebug>=(debayerParameters.size/4))?((debayerParameters.xDebug-debayerParameters.size/4)/(debayerParameters.size/2)):0;
yTileDebug=(debayerParameters.yDebug>=(debayerParameters.size/4))?((debayerParameters.yDebug-debayerParameters.size/4)/(debayerParameters.size/2)):0;
}
if (imageStack==null) return null;
final int imgWidth=imageStack.getWidth();
final int imgHeight=imageStack.getHeight();
final int length=imgWidth*imgHeight;
final int step=debayerParameters.size/2;
final int tilesX=imgWidth/step-1; // horizontal number of overlapping tiles in the source image (should be expanded from the registerd one by "step" in each direction)
final int tilesY=imgHeight/step-1; // vertical number of overlapping tiles in the source image (should be expanded from the registerd one by "step" in each direction)
final int nChn=imageStack.getSize();
int i,chn; //tileX,tileY;
/** find number of the green channel - should be called "green", if none - use last */
i=nChn-1;
for (chn=0;chn<nChn;chn++) if (imageStack.getSliceLabel(chn+1).equals("green")){
i=chn;
break;
}
final int greenChn=i;
final float [][] outPixles=new float[nChn][length]; // same as input
debayerEnergy=null;
if (generateDebayerEnergy) {
debayerEnergy=new double[tilesY*tilesX];
}
for (chn=0;chn<nChn;chn++) for (i=0;i<length;i++) outPixles[chn][i]=0.0f;
final double [] slidingWindow= getSlidingMask(debayerParameters.size); // 64x64
// outPixles=new float[nChn][length]; // GLOBAL same as input
final Thread[] threads = newThreadArray(threadsMax);
final AtomicInteger ai = new AtomicInteger(0);
final int numberOfKernels=tilesY*tilesX;
final long startTime = System.nanoTime();
for (int ithread = 0; ithread < threads.length; ithread++) {
threads[ithread] = new Thread() {
public void run() {
double [][] tile= new double[nChn][debayerParameters.size * debayerParameters.size ];
double [][] both_masks;
float [][] pixels= new float[nChn][];
int chn,tileY,tileX,i;
for (chn=0;chn<nChn;chn++) pixels[chn]= (float[]) imageStack.getPixels(chn+1);
DoubleFHT fht_instance = new DoubleFHT(); // provide DoubleFHT instance to save on initializations (or null)
showDoubleFloatArrays SDFA_instance=null; // just for debugging?
deBayerScissors debayer_instance=new deBayerScissors( debayerParameters.size, // size of the square array, centar is at size/2, size/2, only top half+line will be used
debayerParameters.polarStep, // maximal step in pixels on the maxRadius for 1 angular step (i.e. 0.5)
debayerParameters.debayerRelativeWidthGreen, // result green mask mpy by scaled default (diamond)
debayerParameters.debayerRelativeWidthRedblue, // result red/blue mask mpy by scaled default (square)
debayerParameters.debayerRelativeWidthRedblueMain, // green mask when applied to red/blue, main (center)
debayerParameters.debayerRelativeWidthRedblueClones);// green mask when applied to red/blue, clones
for (int nTile = ai.getAndIncrement(); nTile < numberOfKernels; nTile = ai.getAndIncrement()) {
tileY = nTile /tilesX;
tileX = nTile % tilesX;
if (tileX==0) {
if (updateStatus) IJ.showStatus("Reducing sampling aliases, row "+(tileY+1)+" of "+tilesY);
if (globalDebugLevel>2) System.out.println("Reducing sampling aliases, row "+(tileY+1)+" of "+tilesY+" : "+IJ.d2s(0.000000001*(System.nanoTime()-startTime),3));
}
// if ((tileY==yTileDebug) && (tileX==xTileDebug)) this.debugLevel=4;
// else this.debugLevel=wasDebugLevel;
for (chn=0;chn<nChn;chn++){
extractSquareTile( pixels[chn], // source pixel array,
tile[chn], // will be filled, should have correct size before call
slidingWindow, // window (same size as the kernel)
imgWidth, // width of pixels array
tileX*step, // left corner X
tileY*step); // top corner Y
}
/** Scale green channel x0.5 as there are twice more pixels there as in red or blue. Or move it somewhere else and multiply to original range ? */
for (i=0;i<tile[greenChn].length;i++) tile[greenChn][i]*=0.5;
if ((tileY==yTileDebug) && (tileX==xTileDebug)) {
if (SDFA_instance==null) SDFA_instance= new showDoubleFloatArrays();
SDFA_instance.showArrays (tile.clone(),debayerParameters.size,debayerParameters.size, "x"+(tileX*step)+"_y"+(tileY*step));
}
for (chn=0;chn<nChn;chn++){
fht_instance.swapQuadrants(tile[chn]);
fht_instance.transform(tile[chn]);
}
if ((tileY==yTileDebug) && (tileX==xTileDebug) && (SDFA_instance!=null)) SDFA_instance.showArrays (tile.clone(),debayerParameters.size,debayerParameters.size, "tile-fht");
both_masks= debayer_instance.aliasScissors(tile[greenChn], // fht array for green, will be masked in-place
debayerParameters.debayerThreshold, // no high frequencies - use default uniform filter
debayerParameters.debayerGamma, // power function applied to the amplitudes before generating spectral masks
debayerParameters.debayerBonus, // scale far pixels as (1.0+bonus*r/rmax)
debayerParameters.mainToAlias,// relative main/alias amplitudes to enable lixels (i.e. 0.5 means that if alias is >0.5*main, the pixel will be masked out)
debayerParameters.debayerMaskBlur, // for both masks sigma for gaussian blur of the binary masks (<0 -do not use "scissors")
debayerParameters.debayerUseScissors, // use "scissors", if false - just apply "diamond" ands "square" with DEBAYER_PARAMETERS.debayerRelativeWidthGreen and DEBAYER_PARAMETERS.debayerRelativeWidthRedblue
((tileY==yTileDebug) && (tileX==xTileDebug))?4:1);
// 1); // internal debug level ((this.debugLevel>2) && (yTile==yTile0) && (xTile==xTile0))?3:1;
if ((tileY==yTileDebug) && (tileX==xTileDebug) && (SDFA_instance!=null)) {
SDFA_instance.showArrays (tile.clone(),debayerParameters.size,debayerParameters.size, "A00");
SDFA_instance.showArrays (both_masks.clone(),debayerParameters.size,debayerParameters.size, "masks");
}
if (debayerEnergy!=null) {
debayerEnergy[tileY*tilesX+tileX]=debayer_instance.getMidEnergy();
}
for (chn=0;chn<nChn;chn++) {
tile[chn]=fht_instance.multiply(tile[chn],both_masks[(chn==greenChn)?0:1],false);
fht_instance.inverseTransform(tile[chn]);
fht_instance.swapQuadrants(tile[chn]);
/** accumulate result */
/*This is synchronized method. It is possible to make threads to write to non-overlapping regions of the outPixles, but as the accumulation
* takes just small fraction of severtal FHTs, it should be OK - reasonable number of threads will spread and not "stay in line"
*/
accumulateSquareTile(outPixles[chn], // float pixels array to accumulate tile
tile[chn], // data to accumulate to the pixels array
imgWidth, // width of pixels array
tileX*step, // left corner X
tileY*step); // top corner Y
}
if ((tileY==yTileDebug) && (tileX==xTileDebug) && (SDFA_instance!=null)) SDFA_instance.showArrays (tile.clone(),debayerParameters.size,debayerParameters.size, "B00");
}
}
};
}
startAndJoin(threads);
// this.debugLevel=wasDebugLevel;
/** prepare result stack to return */
ImageStack outStack=new ImageStack(imgWidth,imgHeight);
for (chn=0;chn<nChn;chn++) {
outStack.addSlice(imageStack.getSliceLabel(chn+1), outPixles[chn]);
}
debayerEnergyWidth= (debayerEnergy!=null)?tilesX:0; // for the image to be displayed externally
// if (debayerParameters.showEnergy) {
// SDFA_INSTANCE.showArrays (debayerEnergy,tilesX,tilesY, "Debayer-Energy");
// }
return outStack;
}
/** ======================================================================== */
/**extract and multiply by window function (same size as kernel itself) */
void extractSquareTile(float [] pixels, // source pixel array,
double [] tile, // will be filled, should have correct size before call
double [] window, // window (same size as the kernel)
int width, // width of pixels array
int x0, // left corner X
int y0) { // top corner Y
int length=tile.length;
int size=(int) Math.sqrt(length);
int i,j,x,y;
int height=pixels.length/width;
int index=0;
for (i=0;i<size;i++) {
y=y0+i;
if ((y>=0) && (y<height)) {
index=i*size;
for (j=0;j<size;j++) {
x=x0+j;
if ((x>=0) && (x<width)) tile [index]=pixels[y*width+x]*window[index];
index++;
}
}
}
}
/** ======================================================================== */
void extractSquareTile(double [] pixels, // source pixel array,
double [] tile, // will be filled, should have correct size before call
double [] window, // window (same size as the kernel)
int width, // width of pixels array
int x0, // left corner X
int y0) { // top corner Y
int length=tile.length;
int size=(int) Math.sqrt(length);
int i,j,x,y;
int height=pixels.length/width;
int index=0;
for (i=0;i<size;i++) {
y=y0+i;
if ((y>=0) && (y<height)) {
index=i*size;
for (j=0;j<size;j++) {
x=x0+j;
if ((x>=0) && (x<width)) tile [index]=pixels[y*width+x]*window[index];
index++;
}
}
}
}
/** ======================================================================== */
/** accumulate square tile to the pixel array (tile may extend beyond the array, will be cropped) */
synchronized void accumulateSquareTile(
float [] pixels, // float pixels array to accumulate tile
double [] tile, // data to accumulate to the pixels array
int width, // width of pixels array
int x0, // left corner X
int y0) { // top corner Y
int length=tile.length;
int size=(int) Math.sqrt(length);
int i,j,x,y;
int height=pixels.length/width;
int index=0;
for (i=0;i<size;i++) {
y=y0+i;
if ((y>=0) && (y<height)) {
index=i*size;
for (j=0;j<size;j++) {
x=x0+j;
if ((x>=0) && (x<width)) pixels[y*width+x]+=tile [index];
index++;
}
}
}
}
synchronized void accumulateSquareTile(
double [] pixels, // float pixels array to accumulate tile
double [] tile, // data to accumulate to the pixels array
int width, // width of pixels array
int x0, // left corner X
int y0) { // top corner Y
int length=tile.length;
int size=(int) Math.sqrt(length);
int i,j,x,y;
int height=pixels.length/width;
int index=0;
for (i=0;i<size;i++) {
y=y0+i;
if ((y>=0) && (y<height)) {
index=i*size;
for (j=0;j<size;j++) {
x=x0+j;
if ((x>=0) && (x<width)) pixels[y*width+x]+=tile [index];
index++;
}
}
}
}
/** ======================================================================== */
public double [] getSlidingMask(int size) {
double [] mask = new double [size*size];
double [] maskLine=new double [size];
double k=2*Math.PI/size;
int i,j,index;
for (i=0;i<size;i++) maskLine[i]= 0.5*(1.0-Math.cos(i*k));
index=0;
for (i=0;i<size;i++) for (j=0;j<size;j++) mask[index++]=maskLine[i]*maskLine[j];
return mask;
}
/** ======================================================================== */
/** ======================================================================== */
/** 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
*/
public 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);
}
}
}
import ij.IJ;
import ij.ImagePlus;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import javax.swing.SwingUtilities;
public class DenseCorrespondence {
public double [][] disparityScales= null; // for each image - a pair of {scaleX, scaleY} or null if undefined (interSesnor has the same)
public ImagePlus impDisparity=null;
public int corrFFTSize; // to properties - 32 *4
public int overlapStep; // to properties - 16
public int paddedSize; // same as in zTile - 20 *4
public int subpixel; // same as in cyclopeanTile - subdivide mask visibility pixels
private double disparityPerEntry; //disparity increment (in pix) per array element
private int tilesX;
private int tilesY;
public String title;
private float [][] pixels=null;
private float [] centerPixels=null; // disparity arrays combined for the center virtual image
private float [][] syntheticPixels=null; // disparity arrays for individual images restored from the centerPixels
private BitSet innerMask=null; // will be provided to zMap instances to quickly find that there are no inner (sans padding) pixels
private int [] borderMask=null; // will be provided to zMap +1:top+2:bottom+8:left+16:right (to prevent roll over when iterating around
private double centerPixelsFatZero=0.0;
// private int [][] imagePairs=null;
private int [][] imagePairIndices=null;
private double [] doubleTileWindow=null;
private CyclopeanTile [][] cyclopeanMap=null;
private Rectangle zMapWOI=null; // full: 0,0,tilesX,tilesY
public Photometric photometric=null;
public int getPadding() {return (this.paddedSize-this.overlapStep)/2;}
private void disparitySweepTile (
int tileX,
int tileY,
CyclopeanTile [][] allCyclopeanMap,
// int nImg,
// int [] sImgSet, // list of second images
int [][]imgPairs,
double [][][] imageData, // [img]{Alpha, Y,Cb,Cr, Ext}. Alpha may be null - will handle later?
int imageFullWidth,
double blurVarianceSigma,
// int refineTilePeriod,
double subTilePhaseCoeff,
double subTileHighPassSigma,
double subTileLowPassSigma,
// double refineCorrMaxDistance,
// double refineCorrThreshold,
int refineSubPixel,
double zMapMinForeground,
int zMapVarMask,
double [] zMapVarThresholds,
double [] zMapVarWeights,
int auxVarMode,
int normalize,
int zMapCorrMask,
double [] zMapCorrThresholds,
double [] zMapCorrThresholdsRel,
double [] zMapCorrWeights,
double [] window,
DoubleFHT doubleFHT,
double [] subTileWindow,
DoubleFHT subTileFHT,
// new arguments
int combineMode, // different image pairs - 0
double disparityMax,
double disparityMin,
double minAbsolute, // or NaN - will use enabled/disabled state of the tile
double minRelative,
boolean filterByForeground, // apply known certain masks
double filterByForegroundMargin,
boolean filterByDisabled,
double disparityTolearnce,
double maskBlurSigma,
double corrHighPassSigma, // subtract blurred version to minimize correlation caused by masks
//tone-matching statistical parameters
double varianceBlurScale,
double kLocal,
boolean refineTileDisparity,
int matchStatMode,
int threadsMax,
boolean showProgress,
int debugLevel){
// TODO: also calculate "unlikely" - high autocorrelation, not occluded, low inter-correlation
double [] normVarWeights= normalizeWeights(zMapVarMask,zMapVarWeights);
double [] normCorrWeights=normalizeWeights(zMapCorrMask,zMapCorrWeights);
// int auxChannelNumber=3;
CyclopeanTile cyclopeanTile=allCyclopeanMap[tileY][tileX];
int tileOverlap=cyclopeanTile.getOverlap();
int paddedSize=cyclopeanTile.getPaddedSize();
int paddedLength=cyclopeanTile.getPaddedLength();
int size=this.overlapStep*2;
int length=size*size;
double[] zeros=new double [length];
for (int i=0;i<length;i++) zeros[i]=0.0;
int margin=size/4-tileOverlap;
// double [][][][] slices=new double [sImgSet.length][][][];
double [][][][] slices=new double [imgPairs.length][][][];
int [] dirs1={1,size+1,size,size-1,-1,-size-1,-size,-size+1,0};
// double [][][][] variance=new double [sImgSet.length][][][]; // [sIndex][pair 0/1][chn][pixel
// int length=4*this.overlapStep*this.overlapStep; // initialize it to a correct value right here?
int [] borderMask=cyclopeanTile.getBorderMask();
// Iterate through pairsd to this image - oter pairs will be processed separately and the result (likelyhood of belonguing to
// the particular plane can be evaluated (they use the same "radar" data
cyclopeanTile.setMinCorrelations(minAbsolute, minRelative, true); // Set here or before? include FG
if (debugLevel>3) {
System.out.println ("zTile.setMinCorrelations("+minAbsolute+","+ minRelative+")");
boolean [] enabled=cyclopeanTile.enabledPlane;
for (int plane=0;plane<cyclopeanTile.getNumberOfPlanes();plane++) {
System.out.println(plane+": disparity="+cyclopeanTile.getPlaneDisparity(plane)+" strength="+cyclopeanTile.getPlaneStrength(plane)+" enabled="+
enabled[plane]);
}
System.out.println("cyclopeanTile.minAbsolute="+cyclopeanTile.minAbsolute+" cyclopeanTile.minRelative="+cyclopeanTile.minRelative);
}
if (Double.isNaN(window[0])) {
int index=0;
int quarterSize=size/4; //8
int halfSize=size/2; // 16
int size34=3*size/4; // 24
double[] window1d=doubleFHT.getHamming1d(halfSize); // Hamming
for (int iy=0;iy<size;iy++) {
double wy=(iy<quarterSize)?window1d[iy]:((iy>size34)?window1d[iy-halfSize]:1.0);
for (int ix=0;ix<size;ix++) {
double wx=(ix<quarterSize)?window1d[ix]:((ix>size34)?window1d[ix-halfSize]:1.0);
window[index++]=wx*wy;
}
}
if (debugLevel>2){ // one per thread
(new showDoubleFloatArrays()).showArrays(
window,
size,
size,
"Window_X"+tileX+"-Y"+tileY
);
}
}
// create image list;
int maxImage=0;
for (int i=0;i<imgPairs.length;i++) for (int n=0;n<2;n++) if (imgPairs[i][n]>maxImage) maxImage=imgPairs[i][n];
boolean [] imgList= new boolean [maxImage+1]; // which images are used
for (int i=0;i<imgList.length;i++) imgList[i]=false;
for (int i=0;i<imgPairs.length;i++) for (int n=0;n<2;n++) imgList[imgPairs[i][n]]=true;
int plane=cyclopeanTile.getForegroundIndex(); //foregroundIndex should be updated to point next to the last processed foreground (may be after the last)
// for (;(plane<cyclopeanTile.getNumberOfPlanes()) && (cyclopeanTile.getPlaneDisparity(plane)<disparityMin);plane++);{
for (;(plane<cyclopeanTile.getNumberOfPlanes()) && (cyclopeanTile.getPlaneDisparity(plane)>=disparityMin);plane++);{
// if ((plane>=cyclopeanTile.getNumberOfPlanes()) || (cyclopeanTile.getPlaneDisparity(plane)>disparityMax)){
// return;// nothing left in this tile
// }
double disparity=cyclopeanTile.getPlaneDisparity(plane);
double [][][] planeStrength=new double [cyclopeanTile.getNumberOfPlanes()][imgPairs.length][];
//disparityMin..disparityMax should contain not more than 1 plane (verify? enforce?)
for (int planeOther=plane; planeOther<cyclopeanTile.getNumberOfPlanes() ;planeOther++) planeStrength[plane]=null;
double [][] visibility=new double[imgList.length][];
for (int nImg=0;nImg<imgList.length;nImg++){
if (imgList[nImg]){
// double dX=disparity*this.disparityScales[nImg][0];
// double dY=disparity*this.disparityScales[nImg][1];
// double dXYlen=Math.sqrt(dX*dX+dY*dY);
// double [] udXY={dX/dXYlen,dY/dXYlen};
// gets just cumulative data from already processed (closer) layers. Will update visibility later
visibility[nImg]= getImageTransparency( // later make option with full color calculation?
nImg,//ZTile [][] thisZMap,
allCyclopeanMap,
(tileX+0.5)*this.overlapStep, //double pX, // result tile center in image pixels
(tileY+0.5)*this.overlapStep, //double pY, // result tile center in image pixels
// tileX,
// tileY,
size, // resultSize
disparity, //double disparity,
/// filterByForegroundMargin,
/// filterByDisabled?disparityTolearnce:Double.NaN,
debugLevel);
if (debugLevel>3){ // +2 for selected tile
System.out.println("Iterating tileX="+tileX+", tileY="+tileY+" disparity="+disparity+" nImg="+nImg);
} else visibility[nImg]=null;
}
}
if (debugLevel>5){
(new showDoubleFloatArrays()).showArrays(
visibility, // TODO: add slice names
paddedSize,
paddedSize,
true,
"PEM_X"+tileX+"-Y"+tileY
);
}
if (refineTileDisparity) {
// read image tiles (upscaled as visibility)s,
// double newDisparity=
// see if it did change - recalculate visibility
}
// read
// update foregroundIndex, calculate and update cumulative transparency for each image
// cumnulative transparency - mark, but update from the result tiles, to mamke it thread safe
}
} // end of private void disparitySweepTile
/**
* Calculate disparity correction by processing selected pair of images (normally - all pairs)
* @param visibility - square visibility array for each image twice the tile period (2*16=32), up-scaled by this.subpixel (normally 4)
* @param disparity - original disparity value
* @param xc - selection center (in pixels) on virtual cyclopean image
* @param yc
* @param refineSubPixel subdivide disparity arrays (and final correlation array) from image pixels (normally 8)
* @param maskBlurSigma blur visibility mask for correlation (~=corrHighPassSigma), default 2.0 pixels (original image)
* @param corrHighPassSigma high pass image data before masking for correlation, default 2.0 pixels (original image)
* @param zMapCorrMask bitmap of used channels (+1 - Y, +2 - Cb, +4 - Cr, +8 - Aux)
* @param zMapCorrWeights array of correlation weights - {Y,Cb,Cr,Aux} - will be normalized
* @param refinePhaseCoeff mix phase/normal correlation (0.0 - normal, 1.0 - phase)
* @param refineHighPassSigma correlation high pass filter - counts in frequency domain, will not be scaled with this.subpixel
* @param refineLowPassSigma correlation low pass filter - fraction of full frequency range will not be down-scaled with this.subpixel
* @param refineCorrMaxDistance maximum disparity correction distance (in image pixels) - 1.5 pix
* @param refineCorrThreshold relative correlation strength to enable disparity correction 0.01
* @param refineCorrMinPixels minimal number of visible (in both images) pixels to enable disparity correction
* @param window double array of twice tile period squared (normally 32*32=1024), Double.isNaN(window[0]) triggers initialization
* @param scaledWindow double array of twice scaled tile period squared (normally 128*128*32=16384), Double.isNaN(window[0]) triggers initialization
* @param doubleFHT DoubleFHT instance to be reused for the thread
* @param imgPairs pairs of images to use - normally {{0,1},{0,2},{1,2}}
* @param imageData original image data - for each image, each channel (0 - alpha, ...4 - Aux) pixels in scanline order: [img]{Alpha, Y,Cb,Cr, Ext}. Alpha may be null - will handle later?
* @param imageFullWidth original image width
* @param debugLevel debug level
* @return corrected disparity value (or same as input disparity if correction is impossible
*/
public double refineDisparity(
double [][] visibility,
double disparity,
double xc, // "cyclopean" center of the tile
double yc,
int refineSubPixel, // 8 (normally > than this.subPixel)
double maskBlurSigma,
double corrHighPassSigma,
int zMapCorrMask,
// double [] zMapCorrThresholds,
// double [] zMapCorrThresholdsRel,
double [] zMapCorrWeights,
double refinePhaseCoeff,
double refineHighPassSigma,
double refineLowPassSigma,
double refineCorrMaxDistance,
double refineCorrThreshold,
double refineCorrMinPixels,// minimal non-occluded overlapping area to use for disparity correction
double [] window, // flat top, now 32x32
double [] scaledWindow, //
DoubleFHT doubleFHT,
int [][]imgPairs,
double [][][] imageData, // [img]{Alpha, Y,Cb,Cr, Ext}. Alpha may be null - will handle later?
int imageFullWidth,
int debugLevel){
double [] normCorrWeights=normalizeWeights(zMapCorrMask,zMapCorrWeights);
boolean [] imgList= getImageList(imgPairs); // which images are used
int [] channelIndex=getChannelList(zMapCorrMask<<1); // skip alpha, start with Y==1
int [] channelIndexWeights=getChannelList(zMapCorrMask); // Y==0
double [][][] filteredImageData = new double [imgList.length][][]; // does not include unused channels
double [][][] imageTiles=new double [imgList.length][][];
int size=2*this.overlapStep;
int scaledSize=this.subpixel*size;
double scaledMinPixels=refineCorrMinPixels*this.subpixel*this.subpixel;
double scaledMaskBlurSigma= maskBlurSigma*this.subpixel;
double scaledCorrHighPassSigma= corrHighPassSigma*this.subpixel;
double scaledMaxDistance=refineCorrMaxDistance*this.subpixel;
double scaledMaxDist2=scaledMaxDistance*scaledMaxDistance;
if (refineSubPixel<this.subpixel) refineSubPixel=this.subpixel;
int refineUpsampleExtra=refineSubPixel/this.subpixel;
int halfDispRange=(int) Math.ceil(2*refineCorrMaxDistance*refineSubPixel);
for (int nImg=0;nImg<imgList.length;nImg++){
if (imgList[nImg]){
filteredImageData[nImg]=new double [channelIndex.length][];
for (int iChn=0;iChn<channelIndex.length;iChn++){
filteredImageData[nImg][iChn]=imageData[nImg][channelIndex[iChn]];
}
} else{
filteredImageData[nImg]=null;
}
}
for (int nImg=0;nImg<imgList.length;nImg++){
if (imgList[nImg]){
double xcImg= xc + disparity*this.disparityScales[nImg][0]*this.subpixel;
double ycImg= yc + disparity*this.disparityScales[nImg][1]*this.subpixel;
imageTiles[nImg]=getImageTile(
xcImg,
ycImg,
size, // power of 2 - full size (was half)
this.subpixel, // power of 2
filteredImageData[nImg], // only needed channels
imageFullWidth,
window, // should be size*size long;
scaledWindow, // should be size*size*subPixel*subPixel long;
doubleFHT, // to reuse tables
debugLevel);
} else imageTiles[nImg]=null;
}
if (debugLevel>3){
String [] channelNames={"Alpha","Y","Cb","Cr","Aux"};
String [] debugTitles=new String [imgList.length*channelIndex.length];
double [][] debugData=new double [imgList.length*channelIndex.length][];
for (int iChn=0;iChn<channelIndex.length;iChn++) for (int nImg=0;nImg<imgList.length;nImg++) {
debugTitles[iChn*imgList.length+nImg]=channelNames[channelIndex[iChn]]+"-"+nImg;
debugData[iChn*imgList.length+nImg]=imageTiles[nImg][iChn];
}
(new showDoubleFloatArrays()).showArrays(
debugData,
scaledSize,
scaledSize,
true,
"SIMG-"+IJ.d2s(disparity,1)+"_x"+IJ.d2s(xc,1)+"_y"+IJ.d2s(yc,1),
debugTitles);
}
double [][] visibilityMask=new double [visibility.length][];
for (int nImg=0;nImg<imgList.length;nImg++) if (imgList[nImg]){
// Create a blurred version of visibility mask for each image
visibilityMask[nImg]=visibility[nImg];
if (maskBlurSigma>0.0){
visibilityMask[nImg]=visibility[nImg].clone();
(new DoubleGaussianBlur()).blurDouble(
visibilityMask[nImg],
scaledSize,
scaledSize,
scaledMaskBlurSigma,
scaledMaskBlurSigma,
0.01);
}
// Optionally (normally needed) high-pass each channel of each image, multiply but flat-top window
for (int iChn=0;iChn<imageTiles[nImg].length;iChn++){
if (scaledCorrHighPassSigma>0.0){
double [] loPass=imageTiles[nImg][iChn].clone();
(new DoubleGaussianBlur()).blurDouble(
loPass,
scaledSize,
scaledSize,
scaledCorrHighPassSigma,
scaledCorrHighPassSigma,
0.01);
// for (int i=0;i<loPass.length;i++) imageTiles[nImg][iChn][i]=(imageTiles[nImg][iChn][i]-loPass[i])*visibilityMask[nImg][i];
for (int i=0;i<loPass.length;i++) imageTiles[nImg][iChn][i]=(imageTiles[nImg][iChn][i]-loPass[i])*scaledWindow[i];
} else {
// normalizeAndWindow (imageTiles[nImg][iChn], null, true); // only remove DC
normalizeAndWindow (imageTiles[nImg][iChn], scaledWindow, true); // remove DC, multiply by scaled flat-top window
}
}
}
if (debugLevel>3){ // debug show visibility masks and high-passed channels for each image
String [] channelNames={"Alpha","Y","Cb","Cr","Aux"};
String [] debugTitles=new String [imgList.length*(channelIndex.length+1)];
double [][] debugData=new double [imgList.length*(channelIndex.length+1)][];
for (int iChn=0;iChn<channelIndex.length;iChn++) for (int nImg=0;nImg<imgList.length;nImg++) {
debugTitles[iChn*imgList.length+nImg]=channelNames[channelIndex[iChn]]+"-"+nImg;
debugData[iChn*imgList.length+nImg]=imageTiles[nImg][iChn];
}
for (int nImg=0;nImg<imgList.length;nImg++) {
debugTitles[channelIndex.length*imgList.length+nImg]="v-mask-"+nImg;
debugData[channelIndex.length*imgList.length+nImg]=visibilityMask[nImg];
}
(new showDoubleFloatArrays()).showArrays(
debugData,
scaledSize,
scaledSize,
true,
"HPM-"+IJ.d2s(disparity,1)+"_x"+IJ.d2s(xc,1)+"_y"+IJ.d2s(yc,1),
debugTitles);
}
double [][]centerCorrs=new double [imgPairs.length][3];
double [] maskTotal=new double [imgPairs.length];
double [] disparityArray=null;
double [] minCorr=new double [imgPairs.length];
// calculate center correlations for each enabled channel, use blurred visibility masks
for (int nPair=0;nPair<imgPairs.length;nPair++){
int nImg=imgPairs[nPair][0];
int sImg=imgPairs[nPair][1];
centerCorrs[nPair][0]=0.0;
centerCorrs[nPair][1]=0.0;
centerCorrs[nPair][2]=0.0;
maskTotal[nPair]=0.0;
double [] pairWeight=new double [scaledWindow.length];
for (int i=0;i<pairWeight.length;i++) {
// double w= scaledWindow[i]*scaledWindow[i]*visibilityMask[nImg][i]*visibilityMask[sImg][i];
double w= visibilityMask[nImg][i]*visibilityMask[sImg][i];
pairWeight[i]=w;
maskTotal[nPair]+=scaledWindow[i]*scaledWindow[i]*w;
}
for (int iChn=0;iChn<imageTiles[nImg].length;iChn++){
double [] centerCorrsChn ={0.0,0.0,0.0};
for (int i=0;i<pairWeight.length;i++) {
centerCorrsChn[0]+=pairWeight[i]*imageTiles[nImg][iChn][i]*imageTiles[nImg][iChn][i];
centerCorrsChn[1]+=pairWeight[i]*imageTiles[sImg][iChn][i]*imageTiles[sImg][iChn][i];
centerCorrsChn[2]+=pairWeight[i]*imageTiles[nImg][iChn][i]*imageTiles[sImg][iChn][i];
}
for (int n=0;n<3;n++) {
centerCorrs[nPair][n]+=normCorrWeights[channelIndexWeights[iChn]]*centerCorrsChn[n];
}
}
for (int n=0;n<3;n++) {
centerCorrs[nPair][n]=Math.sqrt(centerCorrs[nPair][n]/maskTotal[nPair]);
}
// for each image pair - see if correlation is sufficient, then - correlate and find disparity correction
// disparityArray[nImg][sImg]=null;
if (maskTotal[nPair]>=scaledMinPixels){
minCorr[nPair]=1.0;
for (int n=0;n<centerCorrs[nPair].length;n++) {
centerCorrs[nPair][n]=Math.sqrt(centerCorrs[nPair][n]/maskTotal[nPair]);
minCorr[nPair]*=centerCorrs[nPair][n];
}
minCorr[nPair]=Math.pow(minCorr[nPair],1.0/centerCorrs[nPair].length); // Use other metrics?
} else {
minCorr[nPair]=0.0;
}
if (debugLevel>3){ // +2 for selected tile
System.out.println(
" maskTotal["+nPair+"]="+maskTotal[nPair]+
" centerCorrs["+nPair+"][0]="+centerCorrs[nPair][0]+
" centerCorrs["+nPair+"][1]="+centerCorrs[nPair][1]+
" centerCorrs["+nPair+"][2]="+centerCorrs[nPair][2]+
" minCorr["+nPair+"]="+minCorr[nPair]+
" refineCorrThreshold="+refineCorrThreshold);
}
// TODO: now look if the correlation is strong enough to perform correction (minimal of 3?)
// scaledMinPixels
// decide if correction is possible?
if (minCorr[nPair]>=refineCorrThreshold){
double [] combinedChnCorr=new double [scaledWindow.length];
for (int i=0;i<combinedChnCorr.length;i++) combinedChnCorr[i]=0.0;
double[][] corr=new double [imageTiles[nImg].length][];
for (int iChn=0;iChn<imageTiles[nImg].length;iChn++){
corr[iChn]=doubleFHT.correlate (
imageTiles[sImg][iChn],
imageTiles[nImg][iChn],
refineHighPassSigma, // not scaled - they are in frequency domain
refineLowPassSigma/this.subpixel, // scaled to keep the same
refinePhaseCoeff); // second will be modified
for (int i=0;i<combinedChnCorr.length;i++) combinedChnCorr[i]+=normCorrWeights[channelIndexWeights[iChn]]*corr[iChn][i];
}
if (debugLevel>3){ // for each channel - show per-channel and combine correlation shows imageStack for each image pair
String [] channelNames={"Alpha","Y","Cb","Cr","Aux"};
String [] debugTitles=new String [channelIndex.length+1];
double [][] debugData=new double [channelIndex.length+1][];
debugTitles[0]="combo";
debugData[0]=combinedChnCorr;
for (int iChn=0;iChn<channelIndex.length;iChn++){
debugTitles[iChn+1]=channelNames[channelIndex[iChn]];
debugData[iChn+1]=corr[iChn];
}
(new showDoubleFloatArrays()).showArrays(
debugData,
scaledSize,
scaledSize,
true,
"CORR_"+nImg+"-"+sImg+"_d"+IJ.d2s(disparity,1)+"_x"+IJ.d2s(xc,1)+"_y"+IJ.d2s(yc,1),
debugTitles);
}
// See if maximum is close enough, then subpixel and calculate partial disparityArray - to be combined into a single one later
double max=0;
int iMax=0;
for (int i=0;i<combinedChnCorr.length;i++) if (combinedChnCorr[i]>max){
max=combinedChnCorr[i];
iMax=i;
}
int ixc=iMax%scaledSize-scaledSize/2;
int iyc=iMax/scaledSize-scaledSize/2;
if (debugLevel>3) System.out.println("refineDisparity(): max="+max+" iMax="+iMax+" refineCorrMaxDistance="+refineCorrMaxDistance+" scaledMaxDist2="+scaledMaxDist2+
" r2="+(ixc*ixc+iyc*iyc)+" r="+Math.sqrt(ixc*ixc+iyc*iyc));
if ((ixc*ixc+iyc*iyc)<=scaledMaxDist2){ // maximum close enough
double dX=disparity*(this.disparityScales[sImg][0]-this.disparityScales[nImg][0]);
double dY=disparity*(this.disparityScales[sImg][1]-this.disparityScales[nImg][1]);
double dXYlen=Math.sqrt(dX*dX+dY*dY);
double [] udXY={dX/dXYlen,dY/dXYlen};
double [] upsampled=doubleFHT.upsample(combinedChnCorr,refineUpsampleExtra);
int interpolatedSize=scaledSize*refineUpsampleExtra;
int interpolatedCenter=(interpolatedSize+1)*interpolatedSize/2;
if (debugLevel>3) { // show upsampled combined for all channels) correlation for enabled pairs
(new showDoubleFloatArrays()).showArrays(
upsampled,
interpolatedSize,
interpolatedSize,
"US_N"+nImg+"-"+sImg+"_xc"+IJ.d2s(xc,2)+"_yc"+IJ.d2s(yc,2));
}
if (disparityArray==null) { // initilize disparity array (linear, to find maximal value) at the first use
disparityArray=new double [2*halfDispRange+1];
for (int i=0;i<disparityArray.length;i++) disparityArray[i]=0.0;
}
// build 1-d disparity section by bi-linear interpolation of the 2d correlation results (accumulate for all enabled
// image pairs
for (int i=0;i<2*halfDispRange+1;i++){
double deltaX=udXY[0]*(i-halfDispRange);
double deltaY=udXY[1]*(i-halfDispRange);
int iX=(int) Math.floor(deltaX); // zero in the center
int iY=(int) Math.floor(deltaY);
deltaX-=iX;
deltaY-=iY;
int index00= interpolatedCenter+iY*interpolatedSize+iX;
int index01=index00+interpolatedSize;
int index10=index00+1;
int index11=index01+1;
disparityArray[i]+= // ACCUMULATE bi-linear interpolated data
(upsampled[index00]*(1.0-deltaX)+upsampled[index10]*deltaX)*(1.0-deltaY)+
(upsampled[index01]*(1.0-deltaX)+upsampled[index11]*deltaX)* deltaY;
if (debugLevel>5){
System.out.println("disparityArray["+i+"]="+disparityArray[i]+
" deltaX="+deltaX+" deltaY="+deltaY+" iX="+iX+" iY="+iY+" index00="+index00+" index01="+index01+
" index10="+index10+" index11="+index11);
}
}
}
} // if (minCorr[nPair]>=refineCorrThreshold){
} // for (int nPair=0;nPair<imgPairs.length;nPair++)
// find best fitting disparity as a maximum on the disparity array (if it exists)
double disparCorr=0.0;
if (disparityArray!=null){
int iMax=0;
for (int i=1;i<disparityArray.length;i++){
if (disparityArray[i]>disparityArray[iMax]) iMax=i;
}
disparCorr=((double) (iMax-halfDispRange))/refineSubPixel;
if (debugLevel>5){
for (int i=0;i<disparityArray.length;i++){
System.out.println("combined_disparityArray["+i+"]="+disparityArray[i]);
}
System.out.println("\nDisparity correction="+disparCorr+" (iMax="+iMax+"), limit ="+refineCorrMaxDistance +" - VERIFY SIGN IS CORRECT!");
}
if (disparCorr<=refineCorrMaxDistance){
if (debugLevel>3) System.out.println("Old disparity="+disparity+" new disparity="+(disparity+disparCorr));
} else {
if (debugLevel>3) System.out.println("Old disparity="+disparity+" unchanged, attempted to correct by "+disparCorr);
disparCorr=0;
}
}
//just add correction to the original disparity value
return disparity+disparCorr;
}
boolean [] getImageList(int [][]imgPairs){
int maxImage=0;
for (int i=0;i<imgPairs.length;i++) for (int n=0;n<2;n++) if (imgPairs[i][n]>maxImage) maxImage=imgPairs[i][n];
boolean [] imgList= new boolean [maxImage+1]; // which images are used
for (int i=0;i<imgList.length;i++) imgList[i]=false;
for (int i=0;i<imgPairs.length;i++) for (int n=0;n<2;n++) imgList[imgPairs[i][n]]=true;
return imgList;
}
int [] getChannelList(int mask){
int numChannels=0;
for (int d=mask;d!=0;d>>=1) if ((d&1)!=0)numChannels++;
int [] channelIndex=new int [numChannels];
numChannels=0;
// int chn=0;
int d=mask;
for (int i=0;i<numChannels;i++) {
if ((d&1)!=0) channelIndex[numChannels++]=i;
d>>=1;
}
return channelIndex;
}
/**
* Create 1d disparity array for subcamera from Cyclopean data (will need low-pass filter to merge close maximums)
* @param nImg subcamera number
* @param cyclopeanMap 2-d array of cyclopean disparity data (uses only disparity/strengths and enabled - no visibility (supposed not to be
* available yet - easy to modify if needed)
* @param pX result tile center X in original pixels
* @param pY result tile center Y in original pixels
* @param window flat top window encoded in line-scan order. Normally 32 if subpixel==1, has to be the same resolution as visibilityMask (subpixel)
* @param pwr multiply strength by strength of the cyclopean tile to this power (strength depends on the features and the area )
* @param visibilityMask square (encoded in line-scan order) for the visibility of the tile of interest (masked by higher disparity leyaers)
* @param subpixel use resolution higher than pixels (not needed?), should match both window and cisibilityMask
* @param disparity disparity of the layer of interest
* @param disparityStep subdivide disparity pixels by this number when encoding result array
* @param debugLevel debug level (not yet used)
* @return 1d array of strengths of disparity values [0] - @infinity, [1] - @disparityStep, ... [disparity/disparityStep] - @disparity
* Will need low-pass filter to merge close disparities from neighbor cyclopean tiles
*/
public double [] getImageDisparityFromCyclopean(
int nImg,
CyclopeanTile [][] cyclopeanMap,
double pX, // center of the result X (in image pixels)
double pY, // center of the result Y (in image pixels)
double [] window, // flat-top tile mask to apply to cyclopean tiles
double pwr, // multiply strength of the cyclopean tile by strength to this power (strength depends on features and area)
double [] visibilityMask, // masked visibility at disparity (should have the same size as window)?
int subpixel, //>1 if window, wisibilityMask have higher resolution than original pixels
double disparity,
double disparityStep,
int debugLevel
){
if (visibilityMask==null) visibilityMask=window;
double dSizeVisibility=Math.sqrt(visibilityMask.length);
double dSizeWindow=Math.sqrt(window.length);
double dSizeSum=dSizeVisibility+dSizeWindow;
int sizeVisibility=(int) dSizeVisibility;
int sizeWindow=(int) dSizeWindow;
double [] disparityArray=new double [((int) Math.ceil(disparity/disparityStep))+1];
for (int i=0;i<disparityArray.length;i++) disparityArray[i]=0.0;
Point2D.Double disparity2DSubpixCenter=new Point2D.Double(
pX*subpixel,
pY*subpixel); // in subpixels, center
Point2D.Double infinity2DSubpixCenter= new Point2D.Double(
pX*subpixel+this.disparityScales[nImg][0]*disparity*subpixel,
pY*subpixel+this.disparityScales[nImg][1]*disparity*subpixel); // in subpixels!
//rectangle including tile TL corners at disparity that can influence result
Rectangle2D tilesDisparity2DSubpixCenter=new Rectangle2D.Double(
disparity2DSubpixCenter.getX(),
disparity2DSubpixCenter.getY(),
dSizeSum,
dSizeSum);
//rectangle including tile centers at infinity that can influence result
Rectangle2D tilesInfinity2DSubpixCenter=new Rectangle2D.Double(
infinity2DSubpixCenter.getX(),
infinity2DSubpixCenter.getY(),
dSizeSum,
dSizeSum);
Rectangle2D tiles2DSubpixCenter=new Rectangle2D.Double();
// This rectangle includes all tile centers that may have layers influence the result
Rectangle2D.union(
tilesDisparity2DSubpixCenter,
tilesInfinity2DSubpixCenter,
tiles2DSubpixCenter);
double periodSubpixel=this.overlapStep*subpixel;
double pXInfinity=pX+this.disparityScales[nImg][0]*disparity; //TODO: check sign!
double pYInfinity=pX+this.disparityScales[nImg][1]*disparity; //TODO: check sign!
//rectangle including tile centers at disparity that can influence result
Rectangle2D tilesCenterDisparity2D=new Rectangle2D.Double(pXInfinity-(0.5*dSizeSum/subpixel), pYInfinity-(0.5*dSizeSum/subpixel), dSizeSum, dSizeSum);
//rectangle including tile centers at infinity that can influence result
Rectangle2D tilesCenterInfinity2D=new Rectangle2D.Double(pX-(0.5*dSizeSum/subpixel), pY-(0.5*dSizeSum/subpixel), dSizeSum, dSizeSum);
Rectangle2D tilesCenter2D=new Rectangle2D.Double();
// This rectangle includes all tile centers that may have layers influence the result
Rectangle2D.union(tilesCenterDisparity2D,tilesCenterInfinity2D,tilesCenter2D);
// convert to tiles, intersect with overall bounds
Point tilesTL=new Point(
(int) Math.floor((tilesCenter2D.getMinX()-0.5*dSizeWindow)/periodSubpixel ),
(int) Math.floor((tilesCenter2D.getMinY()-0.5*dSizeWindow)/periodSubpixel));
Point tilesBR=new Point(
(int) Math.ceil((tilesCenter2D.getMaxX()+0.5*dSizeWindow)/periodSubpixel ),
(int) Math.ceil((tilesCenter2D.getMaxY()+0.5*dSizeWindow)/periodSubpixel));
Rectangle rectTiles=new Rectangle(
tilesTL.x,
tilesTL.y,
tilesBR.x-tilesTL.x,
tilesBR.y-tilesTL.y);
// limit by cyclopeanMap bounds
rectTiles=rectTiles.intersection(new Rectangle(0,0,cyclopeanMap[0].length,cyclopeanMap.length));
Rectangle rectResultTile=new Rectangle(0,0,sizeVisibility,sizeVisibility);
double sumWindow=Double.NaN;
for (int tY=0;tY<rectTiles.y+rectTiles.height;tY++){
for (int tX=0;tX<rectTiles.x+rectTiles.width;tX++){
for (int nPlane=0;
(nPlane<cyclopeanMap[tY][tX].getNumberOfPlanes()) && (cyclopeanMap[tY][tX].getPlaneDisparity(nPlane)<disparity);
nPlane++) if (cyclopeanMap[tY][tX].getPlaneEnabled(nPlane)){
double planeDisparity=cyclopeanMap[tY][tX].getPlaneDisparity(nPlane);
double planeStrength=Math.pow(cyclopeanMap[tY][tX].getPlaneStrength(nPlane),pwr);
Point2D.Double tileCenter=new Point2D.Double(
(tX+0.5)*periodSubpixel-this.disparityScales[nImg][0]*planeDisparity*subpixel, //TODO: check sign!
(tY+0.5)*periodSubpixel-this.disparityScales[nImg][1]*planeDisparity*subpixel); //TODO: check sign!
if (tilesCenter2D.contains(tileCenter)){
int disparityIndex=(int)Math.round(planeDisparity*disparityStep);
// calculate overlapping masks: - find shifty, shift x (destination to source), intersection rectangle, scan and accumulate strength
// calculate subpixel coordinate of the tile (tX,tY) top-left corner in result coordinates (relative to result top-left corner)
int dx=(sizeVisibility-sizeWindow)/2+(int) Math.round(subpixel*this.disparityScales[nImg][0]*(disparity-planeDisparity));
int dy=(sizeVisibility-sizeWindow)/2+(int) Math.round(subpixel*this.disparityScales[nImg][1]*(disparity-planeDisparity));
// wrong - round each separately?
Rectangle rectThisTile=new Rectangle(dx,dy,sizeWindow,sizeWindow);
Rectangle rectIntersect=rectResultTile.intersection(rectThisTile);
if (!rectIntersect.isEmpty()){
if (Double.isNaN(sumWindow)){
sumWindow=0.0;
for (int i=0;i<window.length;i++) sumWindow+=window[i];
}
double d=0;
for (int y=rectIntersect.y;y<(rectIntersect.y+rectIntersect.height);y++){
int rIndex=y*sizeVisibility+rectIntersect.x;
int sIndex=(y-dy)*sizeWindow+(rectIntersect.x-dx);
for (int i=0;i<rectIntersect.width;i++){
d+=visibilityMask[rIndex++]*window[sIndex++];
}
d*=planeStrength/sumWindow;
}
disparityArray[disparityIndex]+=d;
}
}
}
}
}
return disparityArray;
}
/**
* Calculate transparency of the closer layers (in the specified disparity range) for the square tile around specified point
* using the "cyclopean" tile array
* @param nImg number of image (sub-camera)
* @param cyclopeanMap cyclopean map tiles
* @param pX result tile center, in original image pixels, X
* @param pY result tile center, in original image pixels, Y
* @param resultSize side of the result square (in original image pixels, will be multiplied by this.subpixel (full size - 32, paddedSize - 20)
* @param disparity disparity for which to map transparency
// * @param minDisparity farthest layers to process
// * @param maxDisparity closest layers to process
// * @param mergeTolerance maximal distance between layers to consider them the same
* @param debugLevel debug level
* @return square visibility tile in scanline order
*/
public double [] getImageTransparency(
int nImg, // --> this.disparityScales
CyclopeanTile [][] cyclopeanMap,
// int tX, // current tile
// int tY,
double pX, // center of the result X (in image pixels)
double pY, // center of the result Y (in image pixels)
int resultSize, // will be multiplied by this.subpixel, full size - 32, paddedSize - 20
double disparity,
// double minDisparity, // not used?
// double maxDisparity, // not used?
// double mergeTolerance,
int debugLevel
){
// assuming to be exactly scaledTilePeriod*scaledTilePeriod public float [][]imageTransparency; // [image number][pixel number] - cumulative per-image transparency
int scaledResultSize=this.subpixel*resultSize;
// int scaledPaddedSize=this.subpixel*this.paddedSize;
int scaledTilePeriod=this.subpixel*this.overlapStep;
double [] spxTL={ // relative to imageTransparency[nImg] top left corner
(pX+this.disparityScales[nImg][0]*disparity)*this.subpixel-(scaledResultSize)/2,
(pY+this.disparityScales[nImg][1]*disparity)*this.subpixel-(scaledResultSize)/2};
/* Version using tile tX,tY numbers
double [] spxTL={ // relative to imageTransparency[nImg] top left corner
tX*scaledTilePeriod+this.disparityScales[nImg][0]*this.subpixel-(scaledResultSize-scaledTilePeriod)/2,
tY*scaledTilePeriod+this.disparityScales[nImg][1]*this.subpixel-(scaledResultSize-scaledTilePeriod)/2};
*/
int [] ispTL={(int) Math.floor(spxTL[0]),(int) Math.floor(spxTL[1])};
double [] dpxTL={spxTL[0]-ispTL[0],spxTL[1]-ispTL[1]};
double [] result = new double [resultSize*resultSize];
double [][]k={
{(1-dpxTL[0])*(1-dpxTL[1]), ( dpxTL[0])*(1-dpxTL[1])},
{(1-dpxTL[0])*( dpxTL[1]), ( dpxTL[0])*( dpxTL[1])}};
double [] ones=null;
int [][] whereY=new int [resultSize+1][2];
int tileY00=0,tileX00=0;
for (int y=0;y<resultSize+1;y++){
int tileY=(y+ispTL[1])/scaledTilePeriod-((y<0)?1:0);
if (y==0) tileY00=tileY;
whereY[y][0]=(y+ispTL[1])-tileY*scaledTilePeriod;
whereY[y][1]=tileY-tileY00;
}
int [][] whereX=new int [resultSize+1][2];
for (int x=0;x<resultSize+1;x++){
int tileX=(x+ispTL[0])/scaledTilePeriod-((x<0)?1:0);
if (x==0) tileX00=tileX;
whereX[x][0]=(x+ispTL[1])-tileX*scaledTilePeriod;
whereX[x][1]=tileX-tileX00;
}
double [][][] transparencyTiles=new double[whereY[whereY.length-1][2]][whereX[whereX.length-1][1]][scaledTilePeriod*scaledTilePeriod];
for (int y=0;y<transparencyTiles.length;y++) for (int x=0;x<transparencyTiles[y].length;x++){
int tileY=tileY00+y;
int tileX=tileX00+x;
transparencyTiles[y][x]=null;
if ((tileY>=0) && (tileY<cyclopeanMap.length) && (tileX>=0) && (tileX<cyclopeanMap[tileY].length) &&
(cyclopeanMap[tileY][tileX]!=null)){
transparencyTiles[y][x]=cyclopeanMap[tileY][tileX].getDoubleTransparency(nImg); // get cumulative transparency stored for each image
}
if (transparencyTiles[y][x]==null) {
if (ones==null){
ones = new double [scaledTilePeriod*scaledTilePeriod];
for (int i=0;i<ones.length;i++) ones[i]=1.0;
}
transparencyTiles[y][x]=ones;
}
}
for (int y=0;y<resultSize;y++){
for (int x=0;x<resultSize;y++){
int index=y*resultSize+x;
result[index]= k[0][0]*transparencyTiles[whereY[ y][1]][whereX[ x][1]][whereY[ y][0]*scaledTilePeriod+whereX[x ][0]];
if (k[0][1]>0) result[index]+=k[0][1]*transparencyTiles[whereY[ y][1]][whereX[x+1][1]][whereY[ y][0]*scaledTilePeriod+whereX[x+1][0]];
if (k[1][0]>0) result[index]+=k[1][0]*transparencyTiles[whereY[y+1][1]][whereX[ x][1]][whereY[y+1][0]*scaledTilePeriod+whereX[x ][0]];
if (k[1][1]>0) result[index]+=k[1][1]*transparencyTiles[whereY[y+1][1]][whereX[x+1][1]][whereY[y+1][0]*scaledTilePeriod+whereX[x+1][0]];
}
}
return result;
}
/**
* Create a stack of square tiles from the single image, sub-pixel shifted
* @param xc selection center X
* @param yc selection center Y
* @param size result tile size before upsampling (actual size will be size*subPixel
* @param subPixel subdivide from original pixels
* @param imageData stack of full size image per-channel data
* @param imageWidth source image width
* @param window double array of size*size, window[0] should be Double.NaN to be initialized
* @param iWindowUpSample inverse values of the scaled window - should be double[size*subPixel*size*subPixel]
* @param doubleFHT Double FHT class instance to be reused by the same thread multiple times
* @param debugLevel debug level
* @return stack of double[size*subPixel*size*subPixel], outer pixels are unreliable
*/
public double [][] getImageTile(
double xc,
double yc,
int size, // power of 2 - full size (was half)
int subPixel, // power of 2
double [][] imageData, // only needed channels
int imageWidth,
double [] window, // should be size*size long;
double [] windowUpSample, // should be size*size*subPixel*subPixel long;
DoubleFHT doubleFHT, // to reuse tables
int debugLevel
){
if (Double.isNaN(windowUpSample[0])) {
int upsapledSize=size*subPixel;
double[] iWindowUpSample1d=doubleFHT.getHamming1d(upsapledSize); // Hamming
int index=0;
int quarterSize=size/4;
int size34=3*size/4;
for (int iy=0;iy<size;iy++) {
double wy=(iy<quarterSize)?iWindowUpSample1d[iy]:((iy>size34)?iWindowUpSample1d[iy-size]:1.0);
for (int ix=0;ix<size;ix++) {
double wx=(ix<quarterSize)?iWindowUpSample1d[ix]:((ix>size34)?iWindowUpSample1d[ix-size]:1.0);
windowUpSample[index++]=wx*wy;
}
}
}
if (Double.isNaN(window[0])) {
double[] window1d=doubleFHT.getHamming1d(size); // Hamming
int index=0;
int quarterSize=size/4;
int size34=3*size/4;
for (int iy=0;iy<size;iy++) {
double wy=(iy<quarterSize)?window1d[iy]:((iy>size34)?window1d[iy-size]:1.0);
for (int ix=0;ix<size;ix++) {
double wx=(ix<quarterSize)?window1d[ix]:((ix>size34)?window1d[ix-size]:1.0);
window[index++]=wx*wy;
}
}
}
if (debugLevel>4){
System.out.print("getImageTile()");
if (debugLevel>3){
(new showDoubleFloatArrays()).showArrays(
window,
size,
size,
"window");
}
}
double [][] result=new double [imageData.length][];
double [] sliceTile=new double [size*size];
int ixc=(int) Math.round(xc);
int iyc=(int) Math.round(yc);
double dxc=xc-ixc;
double dyc=yc-iyc;
boolean shift=(dxc!=0.0) || (dyc!=0.0);
boolean scale=(subPixel!=1);
for (int chn =0; chn<imageData.length;chn++) {
sliceTile= getSelection(
imageData[chn], // source image/channel slice
sliceTile,
size, //int width,
size, //int height,
imageWidth,
ixc , //xc,
iyc); //yc);
if (debugLevel>4) {
(new showDoubleFloatArrays()).showArrays(
sliceTile,
size,
size,
"slice-getImageTile");
}
// normalize and save DC
double dc=0.0;
if (shift || scale){
dc=normalizeAndWindowGetDC (sliceTile, window); //windowInterpolation
if (debugLevel>4) {
(new showDoubleFloatArrays()).showArrays(
sliceTile,
size,
size,
"slice-normalized-dc"+dc);
}
result[chn]=doubleFHT.shift(sliceTile, subpixel, -dxc, -dyc);
if (debugLevel>4) {
(new showDoubleFloatArrays()).showArrays(
sliceTile,
size,
size,
"slice-shifted");
}
for (int i=0;i<result[chn].length;i++) result[chn][i]=result[chn][i]/windowUpSample[i]+dc;
} else {
if (debugLevel>3) System.out.println("No shift or scale is needed");
result[chn]=sliceTile.clone();
}
if (debugLevel>3) System.out.println("getImageTile() dc="+dc);
}
return result;
}
public double [] getSelection(
double [] imageSlice, // one image/channel slice
double [] selection, // null or array to reuse
int width,
int height,
int fullWidth,
int xc,
int yc){
int length=width*height;
int fullHeight=imageSlice.length/fullWidth;
if (selection ==null) selection = new double[length];
int y0=yc-height/2;
int x0=xc-width/2;
for (int iy=0;iy<height;iy++) {
int srcY=iy+y0;
boolean oob=(srcY<0) || (srcY>=fullHeight);
for (int ix=0;ix<width;ix++){
int oIndex=iy*width+ix;
int srcX=x0+ix;
if (oob ||(srcX<0) || (srcX>=fullWidth)) {
if (oIndex>=selection.length) System.out.println("\ngetSelection(imageSlice["+imageSlice.length+"],selection["+selection.length+"]"+
","+fullWidth+","+width+","+height+","+xc+","+yc+") ix="+ix+" iy="+iy+" oIndex="+oIndex);
selection[oIndex]=0.0;
} else {
selection[oIndex]=imageSlice[srcY*fullWidth+srcX];
}
}
}
return selection;
}
public double[] normalizeAndWindow (double [] pixels, double [] windowFunction, boolean removeDC) {
int j;
if (pixels==null) return null;
double s=0.0,s0=0.0;
if (removeDC) {
for (j=0;j<pixels.length;j++){
s+=pixels[j]*windowFunction[j];
s0+=windowFunction[j];
}
s/=s0;
}
if (windowFunction!=null) for (j=0;j<pixels.length;j++) pixels[j]=(pixels[j]-s)*windowFunction[j];
else for (j=0;j<pixels.length;j++) pixels[j]=(pixels[j]-s);
return pixels;
}
public double normalizeAndWindowGetDC (double [] pixels, double [] windowFunction) {
int j;
if (pixels==null) return 0;
double s=0.0,s0=0.0;
for (j=0;j<pixels.length;j++){
s+=pixels[j]*windowFunction[j];
s0+=windowFunction[j];
}
s/=s0;
for (j=0;j<pixels.length;j++) pixels[j]=(pixels[j]-s)*windowFunction[j];
return s;
}
public double [] normalizeWeights(int mask,double [] weights){
double [] maskedWeights=weights.clone();
for (int i=0;i<maskedWeights.length;i++) if ((mask & (1<<i))==0) maskedWeights[i]=0.0;
return normalizeWeights(maskedWeights);
}
public double [] normalizeWeights(double [] weights){
double [] normalizedWeights=new double [weights.length];
double sum=0.0;
for (int i=0;i<weights.length;i++) sum+=weights[i];
for (int i=0;i<weights.length;i++) normalizedWeights[i]=(sum==0.0)?0.0:(weights[i]/sum);
return normalizedWeights;
}
public int getDisparityPoints() {return this.impDisparity.getWidth()/this.tilesX;}
public double [] getTileDisparity(
int itx,
int ity,
float [] data){ //npair <0 - use center data
int width=this.impDisparity.getWidth();
int disparityPoints=getDisparityPoints();
double [] result=new double [disparityPoints];
int index=ity*width+itx*disparityPoints;
for (int i=0;i<disparityPoints;i++) result[i]=data[index++]; //oob -146583
return result;
}
public void setTileDisparity(
double []disparityArray,
int itx,
int ity,
float [] data){ //npair <0 - use center data
int width=this.impDisparity.getWidth();
int disparityPoints=getDisparityPoints();
int index=ity*width+itx*disparityPoints;
for (int i=0;i<disparityPoints;i++) {
if ((index>=data.length) || (i>=disparityArray.length)){
System.out.println("setTileDisparity(disparityArray,"+itx+","+ity+", data): index="+index+
" data.length="+data.length+" i="+i+" disparityArray.length="+disparityArray.length+" disparityPoints="+disparityPoints+" width="+width);
}
data[index++]=(float) disparityArray[i]; //oob 20375037
}
}
public void setupCyclopeanTile(
Rectangle woi, // in tiles - may be
// final int nImg,
final int maxNumber,
final double minFirst,
final double minAbsolute,
final double minRelative,
final double mergeMax,
final int overlap,
final double zMapMinForeground,
final int threadsMax,
final boolean showProgress,
final int debugLevel){
if (this.centerPixels==null) {
String msg="Centered disparity data is not defined";
IJ.showMessage("Error",msg);
throw new IllegalArgumentException (msg);
}
// if (this.syntheticPixels==null) moveDisparityFromCenter(threadsMax,showProgress,debugLevel);
final float [] thisCenterPixels= this.centerPixels;
if (woi==null) woi=new Rectangle (0, 0, this.tilesX,this.tilesY);
this.zMapWOI=new Rectangle();
this.zMapWOI.x=(woi.x>=0)?woi.x:0;
this.zMapWOI.y=(woi.y>=0)?woi.y:0;
this.zMapWOI.width= (((woi.x+woi.width) <=this.tilesX)?(woi.x+woi.width): this.tilesX)-this.zMapWOI.x;
this.zMapWOI.height=(((woi.y+woi.height)<=this.tilesY)?(woi.y+woi.height):this.tilesY)-this.zMapWOI.y;
final Rectangle zMapWOI=this.zMapWOI;
if (this.cyclopeanMap==null) initCyclopeanMap();
final CyclopeanTile [][] thisCyclopeanMap=this.cyclopeanMap;
final int tilesX=this.zMapWOI.width;
final int tiles=this.zMapWOI.width*this.zMapWOI.height;
if (debugLevel>2) System.out.println("setupZMap() woi.x="+woi.x+" woi.y="+woi.y+
" woi.width="+woi.width+" woi.height="+woi.height);
if (debugLevel>2) System.out.println("setupZMap() zMapWOI.x="+zMapWOI.x+" zMapWOI.y="+zMapWOI.y+
" zMapWOI.width="+zMapWOI.width+" zMapWOI.height="+zMapWOI.height);
this.paddedSize=this.overlapStep+2*overlap;
this.innerMask=(BitSet) new BitSet(this.paddedSize*this.paddedSize);
for (int i=0;i<this.overlapStep;i++) for (int j=0;j<this.overlapStep;j++){
this.innerMask.set((overlap+i)*this.paddedSize+overlap+j);
}
// private int [] borderMask=null; // will be provided to zMap +1:top+2:bottom+8:left+16:right (to prevent roll over when iterating around
int tileSize=2*this.overlapStep;
this.borderMask=new int [tileSize*tileSize];
for (int i=0;i<tileSize;i++) for (int j=0;j<tileSize;j++){
this.borderMask[i*tileSize+j]=((i==0)?5:0)+((i==(tileSize-1))?2:0)+((j==0)?0x50:0)+((j==(tileSize-1))?0x20:0);
}
final Thread[] threads = newThreadArray(threadsMax);
final AtomicInteger tileIndexAtomic = new AtomicInteger(0);
if (showProgress) IJ.showProgress(0.0);
if (showProgress) IJ.showStatus("Setting up cyclopeanMap ...");
for (int ithread = 0; ithread < threads.length; ithread++) {
threads[ithread] = new Thread() {
public void run() {
for (int tile=tileIndexAtomic.getAndIncrement(); tile<tiles;tile=tileIndexAtomic.getAndIncrement()){
setupCyclopeanTile(
zMapWOI.x+tile%tilesX, //int tileX,
zMapWOI.y+tile/tilesX, //int tileY,
centerPixels,
thisCyclopeanMap,
maxNumber,
minFirst,
minAbsolute,
minRelative,
mergeMax,
overlap,
zMapMinForeground,
debugLevel);
if (showProgress){
final int finalTile=tile;
SwingUtilities.invokeLater(new Runnable() {
public void run() {
IJ.showProgress(finalTile,tiles);
}
});
}
}
}
};
}
startAndJoin(threads);
IJ.showProgress(1.0);
}
private void setupCyclopeanTile(
int tileX,
int tileY,
float [] thisDisparityTiles,
CyclopeanTile [][] thisCyclopeanMap,
int maxNumber,
double minFirst,
double minAbsolute,
double minRelative,
double mergeMax,
int overlap,
double zMapMinForeground,
int debugLevel
){
if (debugLevel>2) System.out.println("setupCyclopeanTile("+tileX+","+tileY+",...)");
boolean debugThisTile=debugLevel>3;
thisCyclopeanMap[tileY][tileX]=new CyclopeanTile();
CyclopeanTile cyclopeanTile=thisCyclopeanMap[tileY][tileX];
double [] correlationSection = getTileDisparity(tileX, tileY, thisDisparityTiles);
// double threshold=minAbsolute;
boolean [] isMax =new boolean[correlationSection.length];
int iMax=-1;
int numMax=0;
for (int i=0;i<correlationSection.length;i++){
if ((correlationSection[i]>=minAbsolute) &&
((i==0)||(correlationSection[i]>=correlationSection[i-1])) &&
((i==(correlationSection.length-1)) || (correlationSection[i]>=correlationSection[i+1]))){
isMax[i]=true; // local max above absolute threshold
if ((numMax==0) || (correlationSection[i]>correlationSection[iMax])) iMax=i;
numMax=1;
} else {
isMax[i]=false;
}
}
// double mergeMax=1.5; //merge maximums for interpolation if closer than mergeMax;
int iMergeMax=(int) Math.round(mergeMax/this.disparityPerEntry);
if (debugThisTile){
System.out.println("mergeMax="+mergeMax+" iMergeMax="+iMergeMax);
}
if ((numMax>0) && (correlationSection[iMax]<minFirst)) numMax=0; // no maximums if the largest is below thershold
// for now - now maximums above threshold - use infinity (disparity 0)
if (numMax==0) {
isMax[0]=true; // infinity
numMax=1;
iMax=0;
} else {
numMax=0;
// double threshold=correlationSection[iMax]*minRelative; // no relative filtering here - will be done later
for (int i=0;i<correlationSection.length;i++) if (isMax[i]){
// if (correlationSection[i]>=threshold) numMax++;
numMax++;
//else isMax[i]=false;
}
}
if (numMax>maxNumber) numMax=maxNumber; // limit to specified number of correlation maximums to subpixel
int [] maxIndices=new int [numMax];
maxIndices[0]=iMax;
isMax[iMax]=false;
int maxNum;
for (maxNum=1;maxNum<maxIndices.length;maxNum++){
// merge previous one if possible
int nearDown=-1, nearUp=-1;
for (int i=0;i<=iMergeMax;i++){
if ((maxIndices[maxNum-1]-i)<0) break;
if (isMax[maxIndices[maxNum-1]-i]){
nearDown = maxIndices[maxNum-1]-i;
break;
}
}
for (int i=0;i<=iMergeMax;i++){
if ((maxIndices[maxNum-1]+i)>=isMax.length) break;
if (isMax[maxIndices[maxNum-1]+i]){
nearUp = maxIndices[maxNum-1]+i;
break;
}
}
int n=1+((nearDown>=0)?1:0)+((nearUp>=0)?1:0);
if (n>1){
double s=maxIndices[maxNum-1]+((nearDown>=0)?nearDown:0)+((nearUp>=0)?nearUp:0);
int iMerged=(int) Math.round(s/n);
if (debugThisTile){
System.out.println("Merging close maximums: "+maxIndices[maxNum-1]+" with "+
((nearDown>=0)?nearDown:"")+" "+((nearUp>=0)?nearUp:"")+" to "+iMerged);
}
maxIndices[maxNum-1]=iMerged;
isMax[iMerged]=false;
if (nearDown>=0) isMax[nearDown]=false;
if (nearUp>=0) isMax[nearUp]=false;
}
iMax=-1;
for (int i=0;i<correlationSection.length;i++) {
if (isMax[i]&&((iMax<0) || (correlationSection[i]>=correlationSection[iMax]))) iMax=i;
}
if (iMax<0) break; // no more maximums
isMax[iMax]=false;
maxIndices[maxNum]=iMax;
}
if (debugThisTile){
System.out.println("List maximums on correlation section");
for (int n=0;n<maxNum;n++) {
System.out.println(n+" "+maxIndices[n]+"("+(this.disparityPerEntry*maxIndices[n])+" pix) "+correlationSection[maxIndices[n]]);
}
}
//TODO: Always add infinity?
//TODO: use quadratic interpolation?
// reorder maximums by decreasing disparity (they are now ordered by strength
for (boolean ordered=false;!ordered;){
ordered=true;
for (int i=0;i<(maxIndices.length-1);i++) if (maxIndices[i]<maxIndices[i+1]){
ordered=false;
int tmp=maxIndices[i];
maxIndices[i]=maxIndices[i+1];
maxIndices[i+1]=tmp;
}
}
// is there infinity?
boolean noInfinity=(maxIndices.length==0) || (maxIndices[maxIndices.length-1]!=0);
cyclopeanTile.numPlanes=noInfinity?(maxIndices.length+1):maxIndices.length;
cyclopeanTile.maximums=new double [cyclopeanTile.numPlanes][2];
for (int i=0;i<maxIndices.length;i++){
cyclopeanTile.maximums[i][0]= (float) (maxIndices[i]*this.disparityPerEntry); // disparity in pixels
cyclopeanTile.maximums[i][1]= (float) correlationSection[maxIndices[i]]; // strength
}
cyclopeanTile.maximums[cyclopeanTile.numPlanes-1][0]= 0.0f; // disparity in pixels
cyclopeanTile.maximums[cyclopeanTile.numPlanes-1][1]= (float) correlationSection[0]; // strength
//overlap
cyclopeanTile.overlap=overlap;
cyclopeanTile.size=this.overlapStep;
cyclopeanTile.subpixel=this.subpixel;
cyclopeanTile.reset(zMapMinForeground, minAbsolute, minRelative);
cyclopeanTile.setInnerMask(this.innerMask);
cyclopeanTile.setBorderMask(this.borderMask);
}
private int imagesToNPair(int firstImage, int secondImageIndex){
for (int i=0;i<this.imagePairIndices.length;i++) if ((this.imagePairIndices[i][0]==firstImage)&& (this.imagePairIndices[i][1]==secondImageIndex)) return i;
return -1;
}
public void initCyclopeanMap(){
if (this.cyclopeanMap==null){
this.cyclopeanMap=new CyclopeanTile[this.tilesY][this.tilesX];
for (int tileY=0;tileY<this.tilesY;tileY++) for (int tileX=0;tileX<this.tilesX;tileX++){
this.cyclopeanMap[tileY][tileX]=null;
}
}
}
public Rectangle pixelsToTilesWOI(
Rectangle pixelWOI){
Rectangle woi=new Rectangle (
pixelWOI.x/this.overlapStep,
pixelWOI.y/this.overlapStep,
(pixelWOI.x+pixelWOI.width -1)/this.overlapStep - pixelWOI.x/this.overlapStep +1,
(pixelWOI.y+pixelWOI.height-1)/this.overlapStep - pixelWOI.y/this.overlapStep +1);
return woi;
}
public class CyclopeanTile{
public int numPlanes; //=0;
public int size; // 16
public int overlap; //=0;
public int subpixel;
public double foregroundThreshold; // current foreground threshold (may reconsider if occlusion happens)
public double minAbsolute=0;
public double minRelative=0;
public int foregroundIndex; // current foreground index;
public double [][] maximums; //={}; // {position, strength} in the order of decreasing disparity // now should always include infinity
public float [] zmap; //=null; // square, centered in the center of the tile - may have margins - final z-value for each pixel - will not work with transparency
public boolean [] enabledPlane;
// public BitSet [] enabledPixels; //=null;
// public BitSet [] certainPixels; //=null;
public float [][] likely; //=null;
public float [][] unlikely; //=null;
public float [][] auxData=null;
private BitSet innerMask=null;
private int []borderMask=null;
// TODO: replace - use "global" (cyclopean) layrered opaqueness and cumulative per-image ones (from the largest disparity to the current one)
// Maybe - later add cumulative pixel color also
// another idea for thin wire / tree branches. Guess color by increasing contrast and/or using the same object over different background, then shrink
// the width (sub-pixel) to have the same average color as on the images.
public float [][] globPlaneOpaque; // for each plane, each pixel inside window 1.0 for opaque, 0.0 - for transparent, no overlap, no - with overlap (used before merge
public double [][] globPlaneEdgeDisparity; // for each plane - top,left,right,bottom - NaN - use center, otherwise - linear interpolate from the center
// after the pass - see if any is NaN, while neighbor exists - then fill in both this and neighbor.
// Races will not harm, as the result will be the same
// what if two tiles are worked on simultaneously?
// first - correct disparity, then add globPlaneDisparity[plane]
public AtomicBoolean planeModified; // set during first pass - do not use it yet when calculating visibility (to prevent races), reset at second pass
public int lastModifiedPlane; // valid with planeModified set
// no need for atomic? just "-1" for lastModifiedPlane? Just set it before modifying disparity (it only matters if disparity correction made it farther,
// so it can be counted again? Or is it OK?
public AtomicBoolean [] planeSet; //set after the disparity is corrected (second pass, resets planeModified) and globPlaneOpaque is calculated (maybe does not need to be atomic?)
public AtomicBoolean [][] planeEdgeSet; // [plane][edge], where edge is 0/1 either right or bottom
// or one of 4 - E,SE,S,SW (wit diagonals?
// after the whole pass, try to merge free-hanging sides - with the larger disparity - within tolerance, with smaller disparities (possible after
// disparity correction - any?
// synchronize to prevent races when two neigb. tiles are corrected simultaneously?
/*
* No need to synchronize - on the
*/
public float [][]imageTransparency; // [image number][pixel number] - cumulative per-image transparency, mapped to zero disparity, no overlap
public double [] getDoubleTransparency(int nImg){
if ((this.imageTransparency==null) || (this.imageTransparency[nImg]==null)) return null;
double [] result = new double [this.imageTransparency.length];
for (int i=0;i<result.length;i++) result[i]=this.imageTransparency[nImg][i];
return result;
}
public int getNumberOfPlanes(){return this.maximums.length;}
public int getForegroundPlane(){return this.foregroundIndex;}
public double getPlaneDisparity (int plane){return this.maximums[plane][0];}
public void setPlaneDisparity (double disparity, int plane){this.maximums[plane][0]=disparity;}
public double getPlaneStrength (int plane){return this.maximums[plane][1];}
public boolean getPlaneEnabled (int plane){return this.enabledPlane[plane];}
public int getSize(){return this.size;}
public int getPaddedSize(){return this.size+2*this.overlap;}
public int getOverlap(){return this.overlap;}
public int getPaddedLength(){return (this.size+2*this.overlap)*(this.size+2*this.overlap);}
public void setInnerMask(BitSet mask){
this.innerMask=mask;
}
public void setBorderMask(int [] mask){
this.borderMask=mask;
}
public int [] getBorderMask(){return this.borderMask;}
public int getForegroundIndex() {return this.foregroundIndex;}
public void setForegroundPlane(int plane){
this.foregroundIndex=plane;
/*
if (this.maximums[plane][1]<this.foregroundThreshold) this.foregroundThreshold=this.maximums[plane][1];
this.enabledPlane[plane]=true;
*/
}
public double [] getDisparityListDescending(
double minDisparity,
double maxDisparity){
int numResult=0;
int plane;
for (plane=0;plane<this.numPlanes;plane++){
if (this.maximums[plane][0]>maxDisparity) continue;
if (this.maximums[plane][0]<minDisparity) break;
if (this.enabledPlane[plane]) numResult++;
}
double [] result=new double[numResult];
numResult=0;
for (plane=0;plane<this.numPlanes;plane++){
if (this.maximums[plane][0]>maxDisparity) continue;
if (this.maximums[plane][0]<minDisparity) break;
if (this.enabledPlane[plane]) result[numResult++]=this.maximums[plane][0];
}
return result;
}
/**
* Filter pixels by occlusion of the foreground and disabled in the current plane
* @param disparityFG - threshold to consider pixel being in front
* @param disparity - target disparity
* @param disparityTolerance - consider "this" as being withing disparityTolerance of disparity. NaN - do not filter by this
* @return
*/
/*
public double [] getVisibility(
double disparityFG,
double disparity,
double disparityTolerance){
return getEnabledNonOccluded(
disparityFG,
disparity,
disparityTolerance,
0);
}
public double [] getVisibility(
double disparityFG,
double disparity,
double disparityTolerance,
int debugLevel){
if (debugLevel>3) System.out.println(" ---- getEnabledNonOccluded("+disparityFG+","+
disparity+","+
disparityTolerance+","+
debugLevel+")");
int paddedSize=this.size+2*this.overlap;
int paddedLength=paddedSize*paddedSize;
BitSet nonOccludedBits=new BitSet(paddedLength);
nonOccludedBits.set(0,paddedLength);
for (int plane=0;(plane<getNumberOfPlanes()) && (getPlaneDisparity(plane)>disparityFG);plane++) if (this.enabledPlane[plane]) {
if (this.certainPixels[plane]!=null) nonOccludedBits.andNot(this.certainPixels[plane]);
if (debugLevel>3) System.out.println(" ------ plane="+plane+" cumul. nonOccludedBits.cardinality()="+nonOccludedBits.cardinality());
}
if (!Double.isNaN(disparityTolerance)){
BitSet enabledBits=new BitSet(paddedLength);
for (int plane=0;plane<getNumberOfPlanes();plane++)
if (this.enabledPlane[plane] && (Math.abs(getPlaneDisparity(plane)-disparity)<=disparityTolerance)) {
if (this.enabledPixels[plane]!=null) enabledBits.or(this.enabledPixels[plane]);
else enabledBits.set(0,paddedLength); // all enabled
if (debugLevel>3) System.out.println(" ------ plane="+plane+" cumul. enabledBits.cardinality()="+enabledBits.cardinality());
}
nonOccludedBits.and(enabledBits);
if (debugLevel>3) System.out.println(" ------ result nonOccludedBitscardinality()="+nonOccludedBits.cardinality());
}
boolean [] nonOccluded=new boolean[paddedLength];
for (int i=0;i<paddedLength;i++) nonOccluded[i]=nonOccludedBits.get(i);
return nonOccluded;
}
*/
public void reset(
double minForeground,
double minAbsolute,
double minRelative){
this.numPlanes=this.maximums.length;
this.globPlaneOpaque=new float[this.numPlanes][];
this.imageTransparency=null;
this.enabledPlane=new boolean[this.numPlanes];
for (int i=0;i<this.numPlanes;i++){
this.globPlaneOpaque[i]= null;
this.enabledPlane[i]=true;
}
this.zmap=null;
setMinCorrelations(minAbsolute, minRelative,false);
setForeGround(minForeground);
this.likely=new float[this.numPlanes][]; // maybe use later
for (int i=0;i<this.likely.length;i++)this.likely[i]=null;
this.unlikely=new float[this.numPlanes][]; // maybe use later
for (int i=0;i<this.unlikely.length;i++)this.unlikely[i]=null;
}
public void setMinCorrelations(
double minAbsolute,
double minRelative,
boolean keepFG){
if (!Double.isNaN(minAbsolute)) this.minAbsolute=minAbsolute;
if (!Double.isNaN(minRelative)) this.minRelative=minRelative;
double aMax=0;
for (int i=0;i<this.numPlanes;i++) if (this.maximums[i][1]>aMax) aMax=this.maximums[i][1];
aMax=Math.max(aMax*this.minRelative,this.minAbsolute);
for (int i=0;i<this.numPlanes;i++) this.enabledPlane[i]=this.maximums[i][1]>=aMax;
if (keepFG && (this.foregroundIndex<this.enabledPlane.length) && (this.foregroundIndex>=0)) {
this.enabledPlane[this.foregroundIndex]=true;
}
}
public void setForeGround(
double minForeground){
if (!Double.isNaN(minForeground)) this.foregroundThreshold=minForeground;
for (this.foregroundIndex=0;
(this.foregroundIndex<this.maximums.length) && (!this.enabledPlane[this.foregroundIndex] || (this.maximums[this.foregroundIndex][1]<this.foregroundThreshold))
;this.foregroundIndex++);
}
}
public class Photometric{
public String [] channelNames={"Y","Cb","Cr","Aux"};
public double [][] valueLimits=null; // lo/high limits for each channel
public int subdivAverage=256;
public int subdivHalfDifference=128; // 257
// public int subdivVariance = 256;
public double smoothVarianceSigma=10.0;
public double scaleVariance=3.0; // sigma along difference (vertical axis) will be scaleVariance*variance for this average value
public int numImages=0;
public double [][][] standardVariance=null;// for each image each channel - average variance (among 9 neighbors) for different values
public double [][] averageVariance=null;
public double [][][][] matchingQuality=null; // for each image, each second image index, each channel [subdivAverage*subdivDifference] <=1.0 values
// horizontal average, vertical - difference "2-1" (for "1-2" flip vertical)
public int histogramSize=1000;
public double getAverageVariance(int nImg,int chn){
return this.averageVariance[nImg][chn];
}
public double [] initStaging(){
double [] staging=new double[this.subdivAverage*(2*this.subdivHalfDifference+1)];
for (int i=0;i<staging.length;i++) staging[i]=0.0;
return staging;
}
public void addStagingSample(
double []staging,
int chn,
double weight,
double v1,
double v2){
double min=this.valueLimits[chn][0];
double step=(this.valueLimits[chn][1]-this.valueLimits[chn][0])/(this.subdivAverage-1);
double av=0.5*(v1+v2);
int countAvg=(int) Math.round((av-min)/step);
if (countAvg<0) countAvg=0;
if (countAvg>=this.subdivAverage) countAvg=this.subdivAverage-1;
int countDiff= (int)Math.round(0.5*(v2-v1))+subdivHalfDifference;
if (countDiff<0) countDiff=0;
if (countDiff>2*this.subdivHalfDifference) countDiff=2*this.subdivHalfDifference;
staging[countDiff*this.subdivAverage+countAvg]+=weight;
}
public void addStagingSample(
double []staging,
int chn,
double weight,
double v1,
double v2,
// reduce weight depending on difference, scale to measured variance
double scaleVariance,
double kLocal, // 0 - use global varaiance, 1.0 - use local
int nImg1, // first image number
int nImg2, // second image number
double var1, // first image variance
double var2, // second image variance
boolean debug){
double min=this.valueLimits[chn][0];
double step=(this.valueLimits[chn][1]-this.valueLimits[chn][0])/(this.subdivAverage-1);
double av=0.5*(v1+v2);
int countAvg=(int) Math.round((av-min)/step);
if (countAvg<0) countAvg=0;
if (countAvg>=this.subdivAverage) countAvg=this.subdivAverage-1;
double diff=0.5*(v2-v1);
int countDiff= (int)Math.round(diff/step)+subdivHalfDifference;
if (countDiff<0) countDiff=0;
if (countDiff>2*this.subdivHalfDifference) countDiff=2*this.subdivHalfDifference;
if (debug){
System.out.println("addStagingSample(...,"+chn+","+weight+","+v1+","+v2+","+scaleVariance+","+
kLocal+","+nImg1+","+nImg2+","+var1+","+var2);
System.out.println(" +++ min="+min+" step="+step+" av="+av+" diff="+diff+" countAvg="+countAvg+" countDiff="+countDiff);
}
if (scaleVariance>0.0){
if (kLocal<1.0){ // mix local variance with average over all image
int count1=(int) Math.round((v1-min)/step);
if (count1<0) count1=0;
if (count1>=this.subdivAverage) count1=this.subdivAverage-1;
int count2=(int) Math.round((v2-min)/step);
if (count2<0) count2=0;
if (count2>=this.subdivAverage) count2=this.subdivAverage-1;
if (kLocal<0) kLocal=0;
var1=kLocal*var1+(1.0-kLocal)*this.standardVariance[nImg1][chn][count1];
var2=kLocal*var2+(1.0-kLocal)*this.standardVariance[nImg2][chn][count2];
}
double sigma=scaleVariance*Math.sqrt(var1*var1);
weight*=Math.exp(-diff*diff/(2.0*sigma*sigma))/sigma/Math.sqrt(2*Math.PI);
if (debug) System.out.println(" +++ sigma="+sigma+" weight="+weight);
}
staging[countDiff*this.subdivAverage+countAvg]+=weight;
if (debug) System.out.println(" staging["+(countDiff*this.subdivAverage+countAvg)+"]="+staging[countDiff*this.subdivAverage+countAvg]);
}
public double getStrength(
double []staging,
int chn,
double v1,
double v2){
double min=this.valueLimits[chn][0];
double step=(this.valueLimits[chn][1]-this.valueLimits[chn][0])/(this.subdivAverage-1);
double av=0.5*(v1+v2);
int countAvg=(int) Math.floor((av-min)/step);
int countAvg1=(int) Math.ceil((av-min)/step);
double dx=(av-min)-step*countAvg;
if (countAvg<0) {
countAvg=0;
countAvg1=0;
dx=0.0;
}
if (countAvg1>=this.subdivAverage){
countAvg=this.subdivAverage-1;
countAvg1=this.subdivAverage-1;
dx=0.0;
}
double diff=0.5*(v2-v1);
int countDiff= (int)Math.floor(diff/step)+this.subdivHalfDifference;
int countDiff1= (int)Math.ceil(diff/step)+this.subdivHalfDifference;
double dy=diff-step*(countDiff-this.subdivHalfDifference);
if (countDiff<0) {
countDiff=0;
countDiff1=0;
dy=0.0;
}
if (countDiff1>2*this.subdivHalfDifference){
countDiff=2*this.subdivHalfDifference;
countDiff1=2*this.subdivHalfDifference;
dy=0.0;
}
if ((countDiff1*this.subdivAverage+countAvg1)>=staging.length){
System.out.println("BUG: getStrength() countAvg="+countAvg+" countAvg1="+countAvg1+
" countDiff="+countDiff+" countDiff1="+countDiff1+" staging.length="+staging.length);
}
return
staging[countDiff *this.subdivAverage+countAvg ]*(1-dy)*(1-dx)+
staging[countDiff *this.subdivAverage+countAvg1]*(1-dy)*( dx)+
staging[countDiff1*this.subdivAverage+countAvg ]*( dy)*(1-dx)+
staging[countDiff1*this.subdivAverage+countAvg1]*( dy)*( dx);
}
public void blurStaging(
double []staging,
int chn,
double sigma){
if (sigma<=0) return;
double min=this.valueLimits[chn][0];
double step=(this.valueLimits[chn][1]-this.valueLimits[chn][0])/(this.subdivAverage-1);
(new DoubleGaussianBlur()).blurDouble(
staging,
this.subdivAverage,
2*this.subdivHalfDifference+1,
sigma/step,
sigma/step,
0.01);
}
public Photometric(
double [][][] images,
int imageWidth,
int margins,
double ignoreFraction,
int subdivAverage,
int subdivHalfDifference,
// int subdivVariance,
double smoothVarianceSigma,
double scaleVariance,
int debugLevel
){
if (margins<1) margins=1;
this.subdivAverage=subdivAverage;
this.subdivHalfDifference=subdivHalfDifference;
// this.subdivVariance = subdivVariance;
this.smoothVarianceSigma=smoothVarianceSigma;
this.scaleVariance=scaleVariance; // sigma along difference (vertical axis) will be scaleVariance*variance for this average value
this.numImages=images.length;
this.valueLimits=new double[channelNames.length][];
int [] dirs1={1,imageWidth+1,imageWidth,imageWidth-1,-1,-imageWidth-1,-imageWidth,-imageWidth+1,0};
this.standardVariance=new double[this.numImages][this.channelNames.length][];
this.averageVariance=new double[this.numImages][this.channelNames.length];
for (int i=0;i<this.standardVariance.length;i++) for (int j=0;j<this.standardVariance[0].length;j++){
this.standardVariance[i][j]=null;
this.averageVariance[i][j]=0.0;
}
for (int chn=0;chn<this.valueLimits.length;chn++){
this.valueLimits[chn]=null;
double min=Double.NaN, max=Double.NaN;
for (int nImg=0;nImg<this.numImages;nImg++) if ((images[nImg]!=null) && (images[nImg][chn]!=null)){
double [] image=images[nImg][chn];
int imageHeight=image.length/imageWidth;
if (Double.isNaN(min)){
min=image[margins*imageWidth+margins];
max=min;
}
if (debugLevel>2){
System.out.println("nImg="+nImg+" chn="+chn+
" min0="+IJ.d2s(min,3)+" max0="+IJ.d2s(max,3));
}
for (int y=margins;y<(imageHeight-margins);y++) for (int x=margins;x<(imageWidth-margins);x++){
if (image[y*imageWidth+x]<min) {
min=image[y*imageWidth+x];
if (debugLevel>2) System.out.println("y="+y+" x="+x+" min="+IJ.d2s(min,3));
}
if (image[y*imageWidth+x]>max) {
max=image[y*imageWidth+x];
if (debugLevel>2) System.out.println("y="+y+" x="+x+" max="+IJ.d2s(max,3));
}
}
if (debugLevel>2){
System.out.println("nImg="+nImg+" chn="+chn+
" min="+IJ.d2s(min,3)+" max="+IJ.d2s(max,3));
}
}
int [] histogram=new int [this.histogramSize];
for (int i=0;i<histogram.length;i++) histogram[i]=0;
double step=(max-min)/(this.histogramSize-0.0001);
if (debugLevel>2){
System.out.println( " min="+IJ.d2s(min,3)+" max="+IJ.d2s(max,3)+" step="+IJ.d2s(step,6));
}
for (int nImg=0;nImg<this.numImages;nImg++) if ((images[nImg]!=null) && (images[nImg][chn]!=null)){
double [] image=images[nImg][chn];
int imageHeight=image.length/imageWidth;
for (int y=margins;y<(imageHeight-margins);y++) for (int x=margins;x<(imageWidth-margins);x++){
int index=(int) Math.floor((image[y*imageWidth+x]-min)/step);
if (index<0) index=0;
if (index>=this.histogramSize) index=this.histogramSize-1;
histogram[index]++; //java.lang.ArrayIndexOutOfBoundsException: 1005
}
}
int totalNum=0;
for (int i=0;i<histogram.length;i++)totalNum+= histogram[i];
int ignoreNumPix= (int)(Math.floor(totalNum*ignoreFraction));
if (debugLevel>2){
System.out.println( " totalNum="+totalNum+" ignoreNumPix="+ignoreNumPix);
}
this.valueLimits[chn]=new double[2];
int num=0;
for (int i=0;i<histogram.length;i++){
num+=histogram[i];
if (debugLevel>2) System.out.println("--- "+num+" min+"+i+"*step="+IJ.d2s(min+i*step,3));
if (num>=ignoreNumPix){
this.valueLimits[chn][0]=min+i*step;
if (debugLevel>2){
System.out.println("i="+i+" min+i*step="+IJ.d2s(min+i*step,3));
}
break;
}
}
num=0;
for (int i=histogram.length-1;i>=0;i--){
num+=histogram[i];
if (debugLevel>2) System.out.println("--- "+num+" min+"+i+"*step="+IJ.d2s(min+i*step,3));
if (num>=ignoreNumPix){
this.valueLimits[chn][1]=min+i*step;
if (debugLevel>2){
System.out.println("i="+i+" min+i*step="+IJ.d2s(min+i*step,3));
}
break;
}
}
if (debugLevel>1){
System.out.println("Channel '"+this.channelNames[chn]+
"' min="+IJ.d2s(this.valueLimits[chn][0],3)+" max="+IJ.d2s(this.valueLimits[chn][1],3));
}
// calculatye variance for each emage/channel
for (int nImg=0;nImg<this.numImages;nImg++) if ((images[nImg]!=null) && (images[nImg][chn]!=null)){
double [] image=images[nImg][chn];
int imageHeight=image.length/imageWidth;
this.standardVariance[nImg][chn]=new double[this.subdivAverage];
int [] samples=new int [this.subdivAverage];
int totalSamples=0;
for (int i=0;i<this.subdivAverage;i++){
this.standardVariance[nImg][chn][i]=0.0;
samples[i]=0;
}
double stepVar=(this.valueLimits[chn][1]-this.valueLimits[chn][0])/(this.subdivAverage-1);
double minVar=this.valueLimits[chn][0];
for (int y=margins;y<(imageHeight-margins);y++) for (int x=margins;x<(imageWidth-margins);x++){
int index=y*imageWidth+x;
double S1=0.0;
double S2=0.0;
for (int d=0;d<dirs1.length;d++){
int indexOther=index+dirs1[d];
S1+=image[indexOther];
S2+=image[indexOther]*image[indexOther];
}
double v2=(S2-(S1*S1)/dirs1.length)/dirs1.length; //dirs1.length;
double avg=S1/dirs1.length;
int count=(int) Math.round((avg-minVar)/stepVar);
if (count<0) count=0;
if (count>=this.subdivAverage) count=this.subdivAverage-1;
this.standardVariance[nImg][chn][count]+=v2; // squared
samples[count]++;
this.averageVariance[nImg][chn]+=v2;
totalSamples++;
}
for (int i=0;i<this.subdivAverage;i++){
if (samples[i]>0) this.standardVariance[nImg][chn][i]=Math.sqrt(this.standardVariance[nImg][chn][i]/samples[i]);
else this.standardVariance[nImg][chn][i]=0.0;
// if ((debugLevel>1) && (nImg==0) && (chn==0)){
if ((debugLevel>2) && (nImg==0)){
System.out.println("samples["+i+"]="+samples[i]+
" this.standardVariance["+nImg+"]["+chn+"]["+i+"]="+IJ.d2s(this.standardVariance[nImg][chn][i],3));
}
}
this.averageVariance[nImg][chn]=Math.sqrt(this.averageVariance[nImg][chn]/totalSamples);
for (int i=0;i<this.subdivAverage;i++) if (samples[i]==0){
int j=i+1;
for (;(j<this.subdivAverage) && (samples[j]==0);j++);
if (j==this.subdivAverage) j--;
int i0=i-1;
if (i0<0) i0=0;
if (samples[i0]==0) this.standardVariance[nImg][chn][i0]=this.standardVariance[nImg][chn][j];
if (samples[j]==0) this.standardVariance[nImg][chn][j]=this.standardVariance[nImg][chn][i0];
if (i0<j){
double a=(this.standardVariance[nImg][chn][j]-this.standardVariance[nImg][chn][i0])/(j-i0);
for (int k=0;k<(j-i0);k++){
this.standardVariance[nImg][chn][i0+k]=this.standardVariance[nImg][chn][i0]+a*k;
}
i=j+1;
}
}
// fill gaps (if any)
if (this.smoothVarianceSigma>0.0){
(new DoubleGaussianBlur()).blur1Direction(
this.standardVariance[nImg][chn], //double [] pixels,
this.subdivAverage, //int width,
1, //int height,
this.smoothVarianceSigma, //double sigma,
0.01, //double accuracy,
true //boolean xDirection: true - horizontal
);
}
}
}
// now cteate matchingQuality arrays for each image pair/channel
// this.matchingQuality=new double [this.numImages][this.numImages-1][this.valueLimits.length][this.subdivAverage*this.subdivDifference];
this.matchingQuality=new double [this.numImages][this.numImages-1][][]; //[this.subdivAverage*this.subdivDifference];
int subdivDifference=2*this.subdivHalfDifference+1;
int matchingQualityLength=this.subdivAverage*subdivDifference;
for (int nImg=0;nImg<this.numImages;nImg++) for (int sIndex=0;sIndex<(this.numImages-1);sIndex++){
int sImg=(sIndex>=nImg)?(sIndex+1):sIndex;
if ((images[nImg]!=null) && (images[sImg]!=null)){
this.matchingQuality[nImg][sIndex]=new double [this.valueLimits.length][];
for (int chn=0;chn<this.valueLimits.length;chn++){
if ((images[nImg][chn]!=null) && (images[sImg][chn]!=null)){
this.matchingQuality[nImg][sIndex][chn]=new double [matchingQualityLength];
double diffStep=(this.valueLimits[chn][1]-this.valueLimits[chn][0])/this.subdivHalfDifference;
double k=0.5*diffStep*diffStep/(this.scaleVariance*this.scaleVariance);
for (int averageIndex=0;averageIndex<this.subdivAverage;averageIndex++){
double variance2=this.standardVariance[nImg][chn][averageIndex]*this.standardVariance[sImg][chn][averageIndex];
// public double scaleVariance=3.0; // sigma along difference (vertical axis) will be scaleVariance*variance for this average value
double a=k/variance2;
for (int i=0;i<=this.subdivHalfDifference;i++){
double d=Math.exp(-a*i*i);
this.matchingQuality[nImg][sIndex][chn][(this.subdivHalfDifference-i)*this.subdivAverage+averageIndex]=d;
this.matchingQuality[nImg][sIndex][chn][(this.subdivHalfDifference+i)*this.subdivAverage+averageIndex]=d;
}
}
} else {
this.matchingQuality[nImg][sIndex][chn]=null;
}
}
} else {
this.matchingQuality[nImg][sIndex]=null;
}
}
}
public void showmatchingQuality(){
int subdivDifference=2*this.subdivHalfDifference+1;
int matchingQualityLength=this.subdivAverage*subdivDifference;
double [] zero = new double [matchingQualityLength];
int numPairs=this.numImages*(this.numImages-1);
double [][] debugData=new double [numPairs*this.channelNames.length][];
String [] titles=new String [numPairs*this.channelNames.length];
int index=0;
for (int nImg=0;nImg<this.numImages;nImg++) for (int sIndex=0;sIndex<(this.numImages-1);sIndex++){
int sImg=(sIndex>=nImg)?(sIndex+1):sIndex;
for (int chn=0;chn<this.channelNames.length;chn++){
titles[index]=this.channelNames[chn]+"-"+nImg+"-"+sImg;
debugData[index++]=(this.matchingQuality[nImg][sIndex][chn]!=null)?this.matchingQuality[nImg][sIndex][chn]:zero;
}
}
(new showDoubleFloatArrays()).showArrays(
debugData,
this.subdivAverage,
subdivDifference,
true,
"MQ-"+IJ.d2s(this.scaleVariance,2)+"_"+IJ.d2s(this.smoothVarianceSigma,2),
titles);
}
} // end of private class Photometric
/** 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);
}
}
}
This source diff could not be displayed because it is too large. You can view the blob instead.
/**
**
** DoubleFHT - Determine simulation pattern parameters to match
** the acquired image
**
** Copyright (C) 2010-2011 Elphel, Inc.
**
** -----------------------------------------------------------------------------**
**
** MatchSimulatedPattern.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/>.
** -----------------------------------------------------------------------------**
**
*/
import java.util.ArrayList;
import java.util.List;
import ij.*;
import ij.process.FHT;
//import ij.plugin.FFT;
//import ij.plugin.ContrastEnhancer;
//import java.awt.image.ColorModel;
//import ij.plugin.*;
public class DoubleFHT {
// private boolean isFrequencyDomain;
private int maxSize=20;
private double [] freqMask=null; // frequency mask cache, will be reused if all parameters are the same
// private int freqMaskN=0;
// private double freqHighPass=0;
// private double freqLowPass=0;
private double [] freqPass=null; //{freqHighPass,freqLowPass};
private int maxN=-1; // undefined
private int ln2=-1; // undefined
private double[] C; // TODO: make arrays for each reasonable FHT size (or use cache)
private double[] S;
private int[] bitrev;
private double[] tempArr;
public boolean showProgress=false;
private double [][][] CS_cache=new double[maxSize][][]; // CS cache for different sizes,
private double [][] freqMask_cache=new double[maxSize][]; // frequency mask cache, will be reused if all parameters are the same
private double [][] freqPass_cache=new double[maxSize][]; // cache for low/high frequencies
private int [][] bitrev_cache= new int [maxSize][];
private double [][] hamming1d=new double[maxSize][]; // will cache 1d Hamming window;
private double [] hamming1dMin=new double[maxSize]; // will cache 1d Hamming window;
private double [][] gaussian1d=new double[maxSize][]; // will cache 1d Gaussian window;
private double [] gaussian1dWidths=new double[maxSize]; // will cache 1d Gaussian window;
private double [] translateFHT=null; // cached array for sub-pixel translate
private double [] translateFHTdXY=null;
private int translateFHTN=0;
public boolean debug=false;
private showDoubleFloatArrays SDFA_INSTANCE= new showDoubleFloatArrays(); // just for debugging?
public DoubleFHT() {
this.C = null;
this.S = null;
for (int i=0;i<CS_cache.length;i++) {
this.CS_cache[i]=null;
this.freqMask_cache[i]=null;
this.freqPass_cache[i]=null;
this.bitrev_cache[i]=null;
this.hamming1d[i]=null;
this.gaussian1d[i]=null;
this.gaussian1dWidths[i]=-1.0;
}
}
public boolean powerOf2Size(double [] data) {
return powerOf2Size(data.length);
}
public boolean powerOf2Size(int len) {
int i=4;
while(i<len) i *= 4;
return i==len;
}
public void swapQuadrants(double [] data) {
if (data==null) return;
int size= (int) Math.sqrt(data.length);
int hsize=size/2;
int shift03=(size+1)*hsize;
int shift12=(size-1)*hsize;
int i,j,index;
double d;
for (i=0;i<hsize;i++) for (j=0;j<hsize;j++) {
index=i*size+j;
d=data[index];
data[index]=data[index+shift03];
data[index+shift03]=d;
index+=hsize;
d=data[index];
data[index]=data[index+shift12];
data[index+shift12]=d;
}
}
/** Performs a forward transform, converting this image into the frequency domain.
The image contained in data must be square and its width must be a power of 2. */
public boolean transform(double [] data) {
return transform(data, false);
}
/** Performs an inverse transform, converting this image into the space domain.
The image contained in data must be square and its width must be a power of 2. */
public boolean inverseTransform(double [] data) {
return transform(data, true);
}
/** Returns an inverse transform of this image, which is assumed to be in the frequency domain. */
public boolean transform(double [] data, boolean inverse) {
//IJ.log("transform: "+maxN+" "+inverse);
updateMaxN(data);
// maxN = (int) Math.sqrt(data.length);
// if ((S==null) || (S.length!=(maxN/4))) {
// makeSinCosTables(maxN);
// makeBitReverseTable(maxN);
// tempArr = new double[maxN];
// }
// float[] fht = (float[])getPixels();
rc2DFHT(data, inverse, this.maxN);
// isFrequencyDomain = !inverse;
return true;
}
public double []createFrequencyFilter(
double highPass,
double lowPass){
return createFrequencyFilter(null,highPass,lowPass);
}
/* public double []createFrequencyFilter(
double [] data,
double highPass,
double lowPass){
return createFrequencyFilter(data.length,highPass,lowPass);
}
*/
public double [] createFrequencyFilter(
double [] data, //int n,
double highPass,
double lowPass){
if (data !=null) updateMaxN(data);
// int n;
if ((this.freqMask!=null) && (this.freqPass[0]==highPass) && (this.freqPass[0]==lowPass)) return this.freqMask;
this.freqMask= new double [(this.maxN+1)*this.maxN/2+1];
double [] lo=new double[this.maxN];
double [] hi=new double[this.maxN];
int i,j;
double kHi= (highPass>0)?(0.5/highPass/highPass):0;
double kLo= (lowPass>0)?(1.0/lowPass/lowPass/this.maxN/this.maxN):0;
double mx2;
// System.out.println("createFrequencyFilter: highPass ="+highPass+ " lowPass= "+lowPass+ " kHi= "+kHi+ " kLo= "+kLo);
for (i=0;i<this.maxN;i++) {
mx2=-i*i;
hi[i]=(kHi>0)?Math.exp(mx2*kHi):0.0;
lo[i]=(kLo>0)?Math.exp(mx2*kLo):1.0;
}
// more precise version should add data from 4 corners, below is the simple version that may have discontinuities in the middle
for (int index=0;index<this.freqMask.length;index++) {
i=index/this.maxN;
j=index%this.maxN;
if (j>this.maxN/2) j=this.maxN-j;
this.freqMask[index]=(1.0-hi[i]*hi[j])*lo[i]*lo[j];
}
// this.freqMaskN=n;
this.freqPass=new double[2];
this.freqPass[0]=highPass;
this.freqPass[1]=lowPass;
// this.freqMask_cache[this.ln2]=this.freqMask;
// this.freqPass_cache[this.ln2]=this.freqPass;
return this.freqMask;
}
/**
* Shift the data by phase modification in the frequency domain
* @param data square 2**N array in line scan order. Data is modified in-place
* @param dx amount of the horizontal shift (left) in pixels
* @param dy amount of the vertical shift (down) in pixels
* @return modified (shifted) data
* TODO: add shift+up-sample combination
*/
public double [] shift(double [] data, double dx, double dy){
return shift(data, 1, dx, dy);
/*
updateMaxN(data);
double sX=2*Math.PI*dx/this.maxN;
double sY=2*Math.PI*dy/this.maxN;
int halfN=this.maxN/2;
double [] cosDX = new double[this.maxN];
double [] sinDX = new double[this.maxN];
double [] cosDY = new double[halfN+1];
double [] sinDY = new double[halfN+1];
for (int i=0;i<=halfN;i++){ // need less?
cosDX[i]=Math.cos(sX*i);
sinDX[i]=Math.sin(sX*i);
cosDY[i]=Math.cos(sY*i);
sinDY[i]=Math.sin(sY*i);
}
for (int i=1;i<halfN;i++){ // need less?
cosDX[this.maxN-i]= cosDX[i];
sinDX[this.maxN-i]=-sinDX[i];
}
swapQuadrants(data);
if (!transform(data,false)) return null; // direct FHT
for (int row =0; row<=halfN; row++) {
int rowMod = (this.maxN - row) % this.maxN;
int maxCol=(row<halfN)?(this.maxN-1):halfN;
for (int col=0; col<=maxCol; col++) {
int colMod = (this.maxN - col) % this.maxN;
int index= row * this.maxN + col;
int indexMod=rowMod * this.maxN + colMod;
double re=0.5*(data[index]+data[indexMod]);
double im=0.5*(data[index]-data[indexMod]);
if ((col==halfN) || (row==halfN)) im=0;
double cosDelta= cosDX[col]*cosDY[row] - sinDX[col]*sinDY[row]; // cos(deltaX)*cos(deltaY)-sin(deltaX)*sin(deltaY)
double sinDelta= sinDX[col]*cosDY[row] + cosDX[col]*sinDY[row]; // sin(deltaX)*cos(deltaY)+cos(deltaX)*sin(deltaY)
double reMod=re*cosDelta-im*sinDelta;
double imMod=re*sinDelta+im*cosDelta;
data[index]= reMod+imMod;
data[indexMod]=reMod-imMod;
}
}
if (!transform(data,true)) return null; // inverse FHT
swapQuadrants(data);
return data;
*/
}
/**
* Upsample input array by padding in the frequency domain
* @param first input square power of 2 array
* @param scale upsample scale (power of 2)
* @return upsampled array, scan
*/
public double [] upsample( double [] first, int scale){
return shift (first, scale, 0.0, 0.0);
/*
if (scale <=1) return first.clone();
updateMaxN(first);
swapQuadrants(first);
if (!transform(first,false)) return null; // direct FHT
int halfN=this.maxN/2;
int shift=this.maxN*(scale-1);
int scaledN=this.maxN*scale;
double [] result =new double [first.length*scale*scale];
for (int i=0;i<result.length;i++) result [i]=0.0;
double scale2=scale*scale;
for (int i=0;i<first.length;i++){
int iy=i/this.maxN;
int ix=i%this.maxN;
if (ix>halfN) ix+=shift;
if (iy>halfN) iy+=shift;
result[scaledN*iy+ix]=scale2*first[i];
}
updateMaxN(result);
if (!transform(result,true)) return null; // inverse FHT
swapQuadrants(result);
return result;
*/
}
/**
* Upsample data and shift it by phase modification in the frequency domain
* @param data square 2**N array in line scan order. Data is modified in-place
* @param scale upsample scale
* @param dx amount of the horizontal shift (left) in pixels (before scaling)
* @param dy amount of the vertical shift (down) in pixels (before scaling)
* @return modified (shifted) data
*/
public double [] shift(double [] data, int scale, double dx, double dy){
if ((scale==0) && (dx==0.0) && (dy==0.0)) return data;
updateMaxN(data);
swapQuadrants(data);
if (!transform(data,false)) return null; // direct FHT
double [] result;
// scale first
if (scale >1){
int halfN=this.maxN/2;
int shift=this.maxN*(scale-1);
int scaledN=this.maxN*scale;
result =new double [data.length*scale*scale];
for (int i=0;i<result.length;i++) result [i]=0.0;
double scale2=scale*scale;
for (int i=0;i<data.length;i++){
int iy=i/this.maxN;
int ix=i%this.maxN;
if (ix>halfN) ix+=shift;
if (iy>halfN) iy+=shift;
result[scaledN*iy+ix]=scale2*data[i];
}
updateMaxN(result);
} else {
result =data;
}
// now shift
if ((dx!=0.0) || (dy!=0.0)) {
dx*=scale;
dy*=scale;
double sX=2*Math.PI*dx/this.maxN;
double sY=2*Math.PI*dy/this.maxN;
int halfN=this.maxN/2;
double [] cosDX = new double[this.maxN];
double [] sinDX = new double[this.maxN];
double [] cosDY = new double[halfN+1];
double [] sinDY = new double[halfN+1];
for (int i=0;i<=halfN;i++){ // need less?
cosDX[i]=Math.cos(sX*i);
sinDX[i]=Math.sin(sX*i);
cosDY[i]=Math.cos(sY*i);
sinDY[i]=Math.sin(sY*i);
}
for (int i=1;i<halfN;i++){ // need less?
cosDX[this.maxN-i]= cosDX[i];
sinDX[this.maxN-i]=-sinDX[i];
}
for (int row =0; row<=halfN; row++) {
int rowMod = (this.maxN - row) % this.maxN;
int maxCol=(row<halfN)?(this.maxN-1):halfN;
for (int col=0; col<=maxCol; col++) {
int colMod = (this.maxN - col) % this.maxN;
int index= row * this.maxN + col;
int indexMod=rowMod * this.maxN + colMod;
double re=0.5*(data[index]+data[indexMod]);
double im=0.5*(data[index]-data[indexMod]);
if ((col==halfN) || (row==halfN)) im=0;
double cosDelta= cosDX[col]*cosDY[row] - sinDX[col]*sinDY[row]; // cos(deltaX)*cos(deltaY)-sin(deltaX)*sin(deltaY)
double sinDelta= sinDX[col]*cosDY[row] + cosDX[col]*sinDY[row]; // sin(deltaX)*cos(deltaY)+cos(deltaX)*sin(deltaY)
double reMod=re*cosDelta-im*sinDelta;
double imMod=re*sinDelta+im*cosDelta;
data[index]= reMod+imMod;
data[indexMod]=reMod-imMod;
}
}
}
if (!transform(result,true)) return null; // inverse FHT
swapQuadrants(result);
return result;
}
/* calculates correlation, destroys original arrays */
public double [] correlate (
double [] first,
double [] second,
double highPassSigma,
double lowPassSigma,
double phaseCoeff){
updateMaxN(first);
createFrequencyFilter(highPassSigma,lowPassSigma); // for repetitive calls will reuse mask
if (this.freqMask.length<first.length/2){
System.out.println("Error: first.length="+first.length+
" second.length="+second.length+
" this.maxN="+this.maxN+
" this.freqMask.length="+this.freqMask.length);
}
return correlate (first, second,phaseCoeff,this.freqMask);
}
public double [] correlate (
double [] first,
double [] second,
double phaseCoeff){
return correlate (first, second, phaseCoeff,null);
}
public double [] correlate (
double [] first,
double [] second,
double phaseCoeff,
double [] filter){
if (phaseCoeff<=0.0) return correlate (first, second, filter);
else return phaseCorrelate (first, second, phaseCoeff,filter);
}
//asymmetrical - will divide by squared second amplitude (pattern to match)
public double [] phaseCorrelate (
double [] first,
double [] second,
double phaseCoeff,
double [] filter){ // high/low pass filtering
if (first.length!=second.length) {
IJ.showMessage("Error","Correlation arrays should be the same size");
return null;
}
// System.out.println("phaseCorrelate, phaseCoeff="+phaseCoeff);
swapQuadrants(first);
swapQuadrants(second);
if (!transform(first,false)) return null; // direct FHT
if (!transform(second,false)) return null; // direct FHT
first= phaseMultiply(first, second, phaseCoeff); // correlation, not convolution
if (filter!=null) multiplyByReal(first, filter);
transform(first,true) ; // inverse transform
swapQuadrants(first);
return first;
}
public double [] applyFreqFilter(
double [] first,
double [] filter
){
if (filter==null) filter=this.freqMask;
if (this.freqMask.length<first.length/2){
System.out.println("Error in applyFreqFilter(): first.length="+first.length+
" this.maxN="+this.maxN+
" filter.length="+filter.length);
}
multiplyByReal(first, filter);
return first;
}
// modify - instead of a sigma - correlationHighPassSigma (0.0 - regular correlation, 1.0 - phase correlation)
public double [] correlate (
double [] first,
double [] second){
return correlate (first, second, null);
}
public double [] correlate (
double [] first,
double [] second,
double [] filter){ // high/low pass filtering
// System.out.println("correlate");
if (first.length!=second.length) {
IJ.showMessage("Error","Correlation arrays should be the same size");
return null;
}
swapQuadrants(first);
swapQuadrants(second);
if (!transform(first,false)) return null; // direct FHT
if (!transform(second,false)) return null; // direct FHT
first= multiply(first, second, true); // correlation, not convolution
if (filter!=null) multiplyByReal(first, filter);
transform(first,true) ; // inverse transform
swapQuadrants(first);
return first;
}
public double [] convolve (
double [] first,
double [] second) {
return convolve (first, second, null);
}
public double [] convolve (
double [] first,
double [] second,
double [] filter){ // high/low pass filtering
// System.out.println("correlate");
if (first.length!=second.length) {
IJ.showMessage("Error","Correlation arrays should be the same size");
return null;
}
swapQuadrants(first);
swapQuadrants(second);
if (!transform(first,false)) return null; // direct FHT
if (!transform(second,false)) return null; // direct FHT
first= multiply(first, second, false); // convolution, not correlation
if (filter!=null) multiplyByReal(first, filter);
transform(first,true) ; // inverse transform
swapQuadrants(first);
return first;
}
/**
* Translate real array (zero in the center) by a sub-pixel vector (preferably in +/- 0.5 range for each coordinate)
* by multiplying spectrum by the linear phase gradient. Caches array, so multiple arrays can be shifted by the same vector faster
* @param data square array to be translated by a sub-pixel value. in the end it holds FHT (before multiplication)!
* @param dx translation in x-direction
* @param dy translation in y direction
* @return modified data array, translated by specified vector (in-place)
*/
public double [] translateSubPixel (
double [] data,
double dx,
double dy){
if (debug) SDFA_INSTANCE.showArrays(data, "source-"+IJ.d2s(dx,3)+":"+IJ.d2s(dy,3));
swapQuadrants(data);
if (!transform(data,false)) return null; // direct FHT
if ((this.maxN!=translateFHTN) || (dx!=translateFHTdXY[0]) || (dy!=translateFHTdXY[1])){
calcTranslateFHT(dx,dy);
}
if (debug) SDFA_INSTANCE.showArrays(data, "fht-source-"+IJ.d2s(dx,3)+":"+IJ.d2s(dy,3));
data= multiply(data, this.translateFHT, false); // convolution, not correlation
if (debug) SDFA_INSTANCE.showArrays(data, "fht-multiplied-"+IJ.d2s(dx,3)+":"+IJ.d2s(dy,3));
transform(data,true) ; // inverse transform
swapQuadrants(data);
if (debug) SDFA_INSTANCE.showArrays(data, "result-"+IJ.d2s(dx,3)+":"+IJ.d2s(dy,3));
return data;
}
/**
* Calculating (and caching) FHT array for subpixel translation
* @param dx translation in x-direction
* @param dy translation in y direction
*/
private void calcTranslateFHT(double dx, double dy){
double k=2.0*Math.PI/this.maxN;
double kx=k*dx;
double ky=k*dy;
this.translateFHT=new double[this.maxN*this.maxN];
double sqrt2=Math.sqrt(2.0);
double pi4=Math.PI/4;
int hSize=this.maxN/2;
double a,b,p;
int rowMod, colMod;
for (int r=0;r<hSize;r++){
rowMod = (maxN - r) % maxN;
for (int c=0;c<hSize;c++){
colMod = (maxN - c) % maxN;
p=kx*c+ky*r;
a=sqrt2*Math.sin(pi4+p);
b=sqrt2*Math.sin(pi4-p);
this.translateFHT[r*this.maxN+c]=a;
this.translateFHT[rowMod*this.maxN+colMod]=b;
if (c==0) {
this.translateFHT[r*this.maxN+c+hSize]=a;
this.translateFHT[rowMod*this.maxN+colMod+hSize]=b;
} else {
p=-kx*c+ky*r;
a=sqrt2*Math.sin(pi4+p);
b=sqrt2*Math.sin(pi4-p);
this.translateFHT[r*this.maxN+ colMod]=a;
this.translateFHT[rowMod*this.maxN+c]=b;
}
}
}
// update cache
this.translateFHTdXY=new double[2];
this.translateFHTdXY[0]=dx;
this.translateFHTdXY[1]=dy;
this.translateFHTN=this.maxN;
if (debug) SDFA_INSTANCE.showArrays(this.translateFHT, "translateFHT-"+IJ.d2s(dx,3)+":"+IJ.d2s(dy,3));
}
private boolean updateMaxN(double [] data){
if (data==null) return false; // do nothing
if (!powerOf2Size(data)) {
String msg="Image is not power of 2 size";
IJ.showMessage("Error",msg);
throw new IllegalArgumentException (msg);
}
int n=(int) Math.sqrt(data.length);
boolean differentSize=(n!=this.maxN);
this.maxN =n;
if (differentSize){
//System.out.println("Changing FFT size: old ln2="+this.ln2+" new maxN="+this.maxN);
// save length old mask and mask parameters
if (this.ln2>=0){
this.freqMask_cache[this.ln2]=this.freqMask;
this.freqPass_cache[this.ln2]=this.freqPass;
}
// int ln2;
for (this.ln2=0;maxN>(1<<this.ln2); this.ln2++);
if ((this.ln2>=CS_cache.length) || (CS_cache[this.ln2]==null)) {
if (this.ln2>=CS_cache.length) {
String msg="Too large image, increase this.maxSize (it is now "+this.maxSize+", wanted "+this.ln2+")";
IJ.showMessage("Error",msg);
throw new IllegalArgumentException (msg);
}
this.freqMask_cache[this.ln2]=null;
this.freqPass_cache[this.ln2]=new double[2];
this.freqPass_cache[this.ln2][0]=0.0;
this.freqPass_cache[this.ln2][1]=0.0;
makeSinCosTables(this.maxN);
this.CS_cache[this.ln2]=new double[2][];
this.CS_cache[this.ln2][0]=this.C;
this.CS_cache[this.ln2][1]=this.S;
makeBitReverseTable(this.maxN);
this.bitrev_cache[this.ln2]=this.bitrev;
} else {
this.C= this.CS_cache[this.ln2][0];
this.S= this.CS_cache[this.ln2][1];
this.bitrev= this.bitrev_cache[this.ln2];
}
this.freqMask=this.freqMask_cache[this.ln2]; // for the new one there will be just null
this.freqPass=this.freqPass_cache[this.ln2];
this.tempArr = new double[maxN];
}
return differentSize;
}
public double [] getHamming1d(){
if (this.maxN>=0) return getHamming1d(this.maxN);
return null;
}
/**
*
* @param n power of 2 FHT size.
* @return one-dimensional array with Hamming window for low-pass frequency filtering (maximum in the center)
*/
public double [] getHamming1d(int n){
return getHamming1d(n,0.08); // normal Hamming window
}
public double [] getHamming1d(int n, double min){ // min==0.08 - normal Hamming, 0.0 - pure shifted cosine
int ln2;
for (ln2=0;n>(1<<ln2); ln2++);
if (n!=(1<<ln2)){
String msg="Not a power of 2 :"+ n;
IJ.showMessage("Error",msg);
throw new IllegalArgumentException (msg);
}
if (ln2>=this.hamming1d.length) {
String msg="Too large array length, increase this.maxSize (it is now "+this.maxSize+", wanted "+ln2+")";
IJ.showMessage("Error",msg);
throw new IllegalArgumentException (msg);
}
if ((this.hamming1d[ln2]==null) || (this.hamming1dMin[ln2]!=min)){
this.hamming1d[ln2]=new double[n];
this.hamming1dMin[ln2]=min;
double C054=0.5*(1+min);
double C046=0.5*(1-min);
for (int i=0; i<=n/2;i++) this.hamming1d[ln2][i]= (C054-C046*Math.cos((i*2.0*Math.PI)/n));
for (int i=1; i<=n/2;i++) this.hamming1d[ln2][n-i]= this.hamming1d[ln2][i];
}
return this.hamming1d[ln2];
}
/**
* Creates one-dimensional array for low-pass filtering in the frequency domain, by multiplying by Gaussian
* Zero is in the center, same as for Hamming above
* @param lowPass relative frequency. with lowPass==1.0, Gaussian sigma will be sqrt(2)*Nyquist frequency (==2/n)
* @param n - number of FFT points (2^2)
* @return one dimensional array, with 1.0 at 0, and minimum in the middle
*/
public double [] getGaussian1d(
double lowPass){
return getGaussian1d(lowPass,this.maxN);
}
public double [] getGaussian1d(
double lowPass,
int n){
int ln2;
for (ln2=0;n>(1<<ln2); ln2++);
if (n!=(1<<ln2)){
String msg="Not a power of 2 :"+ n;
IJ.showMessage("Error",msg);
throw new IllegalArgumentException (msg);
}
if (ln2>=this.gaussian1d.length) {
String msg="Too large array length, increase this.maxSize (it is now "+this.maxSize+", wanted "+ln2+")";
IJ.showMessage("Error",msg);
throw new IllegalArgumentException (msg);
}
if (this.gaussian1d[ln2]==null){
this.gaussian1d[ln2]=new double[n];
this.gaussian1dWidths[ln2]=-1.0;
}
if (this.gaussian1dWidths[ln2]!=lowPass){
this.gaussian1dWidths[ln2]=lowPass;
double kLo= (lowPass>0)?(1.0/lowPass/lowPass/n/n):0;
double mx2;
for (int i=0;i<=n/2;i++) {
mx2=-i*i;
this.gaussian1d[ln2][n/2-i]=(kLo>0.0)?Math.exp(mx2*kLo):1.0;
}
for (int i=1; i<=n/2;i++) this.gaussian1d[ln2][n-i]= this.gaussian1d[ln2][i];
}
return this.gaussian1d[ln2];
}
private void makeSinCosTables(int maxN) {
int ln2;
for (ln2=0;maxN>(1<<ln2); ln2++);
int n = maxN/4;
this.C = new double[n];
this.S = new double[n];
double theta = 0.0;
double dTheta = 2.0 * Math.PI/maxN;
for (int i=0; i<n; i++) {
this.C[i] = (double)Math.cos(theta);
this.S[i] = (double)Math.sin(theta);
theta += dTheta;
}
}
private void makeBitReverseTable(int maxN) {
bitrev = new int[maxN];
int nLog2 = log2(maxN);
for (int i=0; i<maxN; i++)
bitrev[i] = bitRevX(i, nLog2);
}
/** Performs a 2D FHT (Fast Hartley Transform). */
public void rc2DFHT(double[] x, boolean inverse, int maxN) {
//IJ.write("FFT: rc2DFHT (row-column Fast Hartley Transform)");
for (int row=0; row<maxN; row++)
dfht3(x, row*maxN, inverse, maxN);
progress(0.4);
transposeR(x, maxN);
progress(0.5);
for (int row=0; row<maxN; row++)
dfht3(x, row*maxN, inverse, maxN);
progress(0.7);
transposeR(x, maxN);
progress(0.8);
int mRow, mCol;
double A,B,C,D,E;
for (int row=0; row<=maxN/2; row++) { // Now calculate actual Hartley transform
for (int col=0; col<=maxN/2; col++) {
mRow = (maxN - row) % maxN;
mCol = (maxN - col) % maxN;
A = x[row * maxN + col]; // see Bracewell, 'Fast 2D Hartley Transf.' IEEE Procs. 9/86
B = x[mRow * maxN + col];
C = x[row * maxN + mCol];
D = x[mRow * maxN + mCol];
E = ((A + D) - (B + C)) / 2;
x[row * maxN + col] = A - E;
x[mRow * maxN + col] = B + E;
x[row * maxN + mCol] = C + E;
x[mRow * maxN + mCol] = D - E;
}
}
progress(0.95);
}
void progress(double percent) {
if (showProgress)
IJ.showProgress(percent);
}
/** Performs an optimized 1D FHT. */
public void dfht3 (double[] x, int base, boolean inverse, int maxN) {
int i, stage, gpNum, gpSize, numGps, Nlog2;
int bfNum, numBfs;
int Ad0, Ad1, Ad2, Ad3, Ad4, CSAd;
double rt1, rt2, rt3, rt4;
Nlog2 = log2(maxN);
BitRevRArr(x, base, Nlog2, maxN); //bitReverse the input array
gpSize = 2; //first & second stages - do radix 4 butterflies once thru
numGps = maxN / 4;
for (gpNum=0; gpNum<numGps; gpNum++) {
Ad1 = gpNum * 4;
Ad2 = Ad1 + 1;
Ad3 = Ad1 + gpSize;
Ad4 = Ad2 + gpSize;
rt1 = x[base+Ad1] + x[base+Ad2]; // a + b
rt2 = x[base+Ad1] - x[base+Ad2]; // a - b
rt3 = x[base+Ad3] + x[base+Ad4]; // c + d
rt4 = x[base+Ad3] - x[base+Ad4]; // c - d
x[base+Ad1] = rt1 + rt3; // a + b + (c + d)
x[base+Ad2] = rt2 + rt4; // a - b + (c - d)
x[base+Ad3] = rt1 - rt3; // a + b - (c + d)
x[base+Ad4] = rt2 - rt4; // a - b - (c - d)
}
if (Nlog2 > 2) {
// third + stages computed here
gpSize = 4;
numBfs = 2;
numGps = numGps / 2;
//IJ.write("FFT: dfht3 "+Nlog2+" "+numGps+" "+numBfs);
for (stage=2; stage<Nlog2; stage++) {
for (gpNum=0; gpNum<numGps; gpNum++) {
Ad0 = gpNum * gpSize * 2;
Ad1 = Ad0; // 1st butterfly is different from others - no mults needed
Ad2 = Ad1 + gpSize;
Ad3 = Ad1 + gpSize / 2;
Ad4 = Ad3 + gpSize;
rt1 = x[base+Ad1];
x[base+Ad1] = x[base+Ad1] + x[base+Ad2];
x[base+Ad2] = rt1 - x[base+Ad2];
rt1 = x[base+Ad3];
x[base+Ad3] = x[base+Ad3] + x[base+Ad4];
x[base+Ad4] = rt1 - x[base+Ad4];
for (bfNum=1; bfNum<numBfs; bfNum++) {
// subsequent BF's dealt with together
Ad1 = bfNum + Ad0;
Ad2 = Ad1 + gpSize;
Ad3 = gpSize - bfNum + Ad0;
Ad4 = Ad3 + gpSize;
CSAd = bfNum * numGps;
if (((base+Ad2)>=x.length) || ((base+Ad4)>=x.length) ||
(CSAd>=this.C.length) || (CSAd>=this.S.length)) {
System.out.println("dfht3 Error: ln2="+this.ln2+" maxN="+this.maxN+
" x.length="+x.length+" this.C.length="+this.C.length+" this.S.length="+this.S.length+
" base="+base+" Ad2="+Ad2+" Ad4="+Ad4+" CSAd="+CSAd);
}
rt1 = x[base+Ad2] * C[CSAd] + x[base+Ad4] * S[CSAd];
rt2 = x[base+Ad4] * C[CSAd] - x[base+Ad2] * S[CSAd];
x[base+Ad2] = x[base+Ad1] - rt1;
x[base+Ad1] = x[base+Ad1] + rt1;
x[base+Ad4] = x[base+Ad3] + rt2;
x[base+Ad3] = x[base+Ad3] - rt2;
} /* end bfNum loop */
} /* end gpNum loop */
gpSize *= 2;
numBfs *= 2;
numGps = numGps / 2;
} /* end for all stages */
} /* end if Nlog2 > 2 */
if (inverse) {
for (i=0; i<maxN; i++)
x[base+i] = x[base+i] / maxN;
}
}
void transposeR (double[] x, int maxN) {
int r, c;
double rTemp;
for (r=0; r<maxN; r++) {
for (c=r; c<maxN; c++) {
if (r != c) {
rTemp = x[r*maxN + c];
x[r*maxN + c] = x[c*maxN + r];
x[c*maxN + r] = rTemp;
}
}
}
}
int log2 (int x) {
int count = 15;
while (!btst(x, count))
count--;
return count;
}
private boolean btst (int x, int bit) {
//int mask = 1;
return ((x & (1<<bit)) != 0);
}
void BitRevRArr (double[] x, int base, int bitlen, int maxN) {
for (int i=0; i<maxN; i++)
tempArr[i] = x[base+bitrev[i]];
for (int i=0; i<maxN; i++)
x[base+i] = tempArr[i];
}
private int bitRevX (int x, int bitlen) {
int temp = 0;
for (int i=0; i<=bitlen; i++)
if ((x & (1<<i)) !=0)
temp |= (1<<(bitlen-i-1));
return temp & 0x0000ffff;
}
public int [] nonZeroAmpIndices(double [] ampMask){
int numNonZero=0;
for (int i=0;i<ampMask.length;i++) if (ampMask[i]>0.0) numNonZero++;
int [] result= new int [numNonZero];
numNonZero=0;
for (int i=0;i<ampMask.length;i++) if (ampMask[i]>0.0) result[numNonZero++]=i;
return result;
}
/**
* Calculate feature matching quality
* @param h FHT array
* @param directionAngle direction of the linear phase gradient
* @param distance Distance to the linear feature in the direction directionAngle (determines the phase gradient absolute value)
* @param distance Distance to the linear feature in the direction directionAngle (determines the phase gradient absolute value)
* @param phaseIntegrationWidth Width of the band corresponding to the linear feature
* @param amHPhase array with amplitudes and (modified) phases for each index or null. Used to mask out some of the points
* @param phaseStepCorr Zero-based linear phase values plus these values approximate real phases
* @param bandMask if not null, multiply amplitudes by this mask
* @return pair of correlation and self-RMS (to calculate relative feature strength)
*/
public double [] linearFeatureStrength(
double [] h,
double directionAngle,
double distance,
double phaseIntegrationWidth, // zero - no limit
int [] nonZeroIndices,
double [][] amHPhase,
double [] phaseStepCorr,
double [] bandMask){
double halfWidth=(phaseIntegrationWidth>0.0)?phaseIntegrationWidth/2:this.maxN;
double sin=Math.sin(directionAngle);
double cos=Math.cos(directionAngle);
int halfN=this.maxN/2;
double diffPhase=distance*2*Math.PI/this.maxN;
double corrSum=0.0;
double sumSquares=0.0;
int numSamples=0;
for (int numPoint=0; numPoint<nonZeroIndices.length;numPoint++) if ((amHPhase== null) || (amHPhase[numPoint][0]>0.0)) {
int index=nonZeroIndices[numPoint];
int x=(index+halfN)%this.maxN-halfN;
int y=index/this.maxN;
double t = cos*x+sin*y;
if (Math.abs(cos*y-sin*x)<=halfWidth) {
int indexMod=((this.maxN - (index/this.maxN)) % this.maxN) * this.maxN + ((this.maxN - (index%this.maxN)) % this.maxN);
double re=0.5*(h[index]+h[indexMod]);
double im=0.5*(h[index]-h[indexMod]);
double amp2=re*re+im*im;
if (bandMask!=null) amp2*=bandMask[numPoint];
// sumSquares+=((y!=0)?2:1)*amp2;
sumSquares+=amp2;
double phase=phaseStepCorr[numPoint]+diffPhase*t;
double cosPhase=Math.cos(phase);
double sinPhase=Math.sin(phase);
corrSum+=re*cosPhase+im*sinPhase;
// debugprint
numSamples++;
if (this.debug) System.out.println(" Used x="+x+" y="+y+" cos*y+sin*x="+(cos*y+sin*x)+ " cos="+cos+" sin="+sin+" t="+t);
} else {
if (this.debug) System.out.println("Dropped x="+x+" y="+y+" cos*y+sin*x="+(cos*y+sin*x)+ " cos="+cos+" sin="+sin+" t="+t);
}
}
double [] result={corrSum, Math.sqrt(numSamples*sumSquares)}; // second is n*RMS
return result;
}
/**
* Reconstruct FHT of the linear feature using the masked amplitudes of the original and linear phase approximation
* @param h FHT array
* @param directionAngle direction of the linear phase gradient
* @param distance Distance to the linear feature in the direction directionAngle (determines the phase gradient absolute value)
* @param phaseIntegrationWidth Width of the band corresponding to the linear feature
* @param nonZeroIndices array of non-zero indices in the FHT array
* @param amHPhase array with amplitudes and (modified) phases for each index or null. Used to mask out some of the points
* @param phaseStepCorr Zero-based linear phase values plus these values approximate real phases
* @param bandMask if not null, multiply amplitudes by this mask
* @return FHT Reconstructed array transformed to space domain with quadrants swapped;
*/
public double [] reconstructLinearFeature(
double [] h,
double directionAngle,
double distance,
double phaseIntegrationWidth, // zero - no limit
int [] nonZeroIndices,
double [][] amHPhase,
double [] phaseStepCorr,
double [] bandMask){
double halfWidth=(phaseIntegrationWidth>0.0)?phaseIntegrationWidth/2:this.maxN;
double sin=Math.sin(directionAngle);
double cos=Math.cos(directionAngle);
int halfN=this.maxN/2;
double diffPhase=distance*2*Math.PI/this.maxN;
double [] reconstructedFHT=new double [h.length];
for (int i=0;i<reconstructedFHT.length;i++) reconstructedFHT[i]=0.0;
for (int numPoint=0; numPoint<nonZeroIndices.length;numPoint++) if ((amHPhase== null) || (amHPhase[numPoint][0]>0.0)){
int index=nonZeroIndices[numPoint];
int x=(index+halfN)%this.maxN-halfN;
int y=index/this.maxN;
if (Math.abs(cos*y-sin*x)<=halfWidth) {
int indexMod=((this.maxN - (index/this.maxN)) % this.maxN) * this.maxN + ((this.maxN - (index%this.maxN)) % this.maxN);
double re=0.5*(h[index]+h[indexMod]);
double im=0.5*(h[index]-h[indexMod]);
double amp=Math.sqrt(re*re+im*im);
if (bandMask!=null) amp*=bandMask[numPoint];
double t = cos*x+sin*y;
double phase=phaseStepCorr[numPoint]+diffPhase*t;
double reconstructedRe=amp*Math.cos(phase);
double reconstructedIm=amp*Math.sin(phase);
reconstructedFHT[index]= reconstructedRe+reconstructedIm;
reconstructedFHT[indexMod]=reconstructedRe-reconstructedIm;
// if (this.debug) System.out.println("reconstructLinearFeature(): x="+x+" y="+y +" index="+index+ " indexMod="+indexMod+ " re="+re+" im="+im +" amp="+amp+
// " t="+t+ " phase="+ phase+" reconstructedRe="+reconstructedRe+" reconstructedIm="+reconstructedIm);
}
}
transform(reconstructedFHT,true);
swapQuadrants(reconstructedFHT);
return reconstructedFHT; // space domain
}
/**
* Adds phase shift to stitch symmetrical around 0 areas (there is zero at 0)
* @param directionAngle perpendicular to the linear feature (direction of high amplitude in frequency domain)
* @param zeroPhase extrapolated phase at 0 for positive direction at angle (half step)
* @param distance Estimated distance to line (only used with zeroBinHalfSize)
* @param phaseTolerance - if >0.0, will zero amplitude if the phase is too off
* @param amHPhase [numLixel]{amplitude, phase} - phase will be modified! May be null - will work faster
* @param zeroBinHalfSize - if abs(projection) is less than zeroBinHalfSize, use the best (of 2 options) phase
* @param nonZeroIndices
* @return applied phase correction, added to the modified phase will result actual phase
*/
public double [] compensatePhaseStep(
double directionAngle,
double zeroPhase,
double distance,
double phaseTolerance,
double [][] amHPhase,
int [] nonZeroIndices,
double zeroBinHalfSize){
double sin=Math.sin(directionAngle);
double cos=Math.cos(directionAngle);
int halfN=this.maxN/2;
double diffPhase=distance*2*Math.PI/this.maxN;
double [] phaseCorr=new double [nonZeroIndices.length];
for (int numPoint=0; numPoint<nonZeroIndices.length;numPoint++) {
int index=nonZeroIndices[numPoint];
int x=(index+halfN)%this.maxN-halfN;
int y=index/this.maxN;
double t = cos*x+sin*y;
double dPhase=(t>0)?zeroPhase:(-zeroPhase);
if (amHPhase!=null) {
if ( (y!=0) && (Math.abs(t)<zeroBinHalfSize)){
double flattenedPhase=amHPhase[numPoint][1]-diffPhase*t;
double diffPlus= flattenedPhase-zeroPhase;
double diffMinus=flattenedPhase+zeroPhase;
diffPlus-= (2*Math.PI*Math.round(diffPlus/(2*Math.PI)));
diffMinus-=(2*Math.PI*Math.round(diffMinus/(2*Math.PI)));
dPhase= (Math.abs(diffPlus)<Math.abs(diffMinus))?zeroPhase:(-zeroPhase);
/*
System.out.println("numPoint="+numPoint+" x="+x+" y="+y+
" phase(was)="+amHPhase[numPoint][1]+
" flattenedPhase="+flattenedPhase+
" diffPlus="+diffPlus+" diffMinus="+diffMinus+
" dPhase="+dPhase);
*/
}
amHPhase[numPoint][1]-=dPhase;
amHPhase[numPoint][1]-=(2*Math.PI*Math.round(amHPhase[numPoint][1]/(2*Math.PI)));
double phaseError=amHPhase[numPoint][1]-diffPhase*t;
phaseError-=(2*Math.PI*Math.round(phaseError/(2*Math.PI)));
if ((phaseTolerance>0.0) && (Math.abs(phaseError)>phaseTolerance)){
amHPhase[numPoint][0]=0.0; // bad phase - make zero;
}
}
phaseCorr[numPoint]=dPhase;
}
return phaseCorr;
}
int [] getBandIndices(
boolean keepDC,
double ribbonWidth,
double angle){
int halfN=this.maxN/2;
double halfWidth=ribbonWidth/2;
int length=halfN*this.maxN;
int [] tmpIndices=new int [length];
double sin=Math.sin(angle);
double cos=Math.cos(angle);
int pointNumber=0;
for (int index=0;index<length;index++){
int x=(index+halfN)%this.maxN-halfN;
int y=index/this.maxN;
double t= cos*x+sin*y;
double d= sin*x-cos*y;
int it=(int) Math.round(Math.abs(t));
if ((keepDC || (y>0) || (x>0)) &&(Math.abs(d) < halfWidth) && (it < halfN) ){
tmpIndices[pointNumber++]=index;
}
}
int [] indices=new int [pointNumber];
for (int i=0;i<pointNumber;i++) indices[i]=tmpIndices[i];
return indices;
}
double [] getRibbonMask(
boolean keepDC,
int [] bandIndices,
double ribbonWidth,
double resultHighPass0,
double angle){
double resultHighPass=keepDC?0.0:resultHighPass0;
int halfN=this.maxN/2;
double halfWidth=ribbonWidth/2;
double sin=Math.sin(angle);
double cos=Math.cos(angle);
if (sin<0){
sin=-sin;
cos=-cos;
}
double [] ribbonMask=new double [bandIndices.length];
for (int iIndex=0;iIndex<bandIndices.length;iIndex++){
int index=bandIndices[iIndex];
int x=(index+halfN)%this.maxN-halfN;
int y=index/this.maxN;
double t= Math.abs(cos*x+sin*y);
double d= sin*x-cos*y;
// int it=(int) Math.round(Math.abs(t));
int it=(int) Math.round(t);
if ((keepDC || (y>0) || (x>0)) &&(Math.abs(d) < halfWidth) && (it < halfN) ){
double w=(0.54+0.46*Math.cos(Math.PI*d/halfWidth)); // (masked) amplitude;
if (t<resultHighPass) w*=0.54+0.46*Math.cos(Math.PI*(resultHighPass-t)/resultHighPass);
else if (t>(halfN-halfWidth)) w*=0.54+0.46*Math.cos(Math.PI*(t-(halfN-halfWidth))/halfWidth);
ribbonMask[iIndex]=w;
// if (this.debug) System.out.println("getRibbonMask(): x="+x+" y="+y +" t="+t+" d="+d +" w="+w+ " w0="+ (0.54+0.46*Math.cos(Math.PI*d/halfWidth)));
} else {
ribbonMask[iIndex]=0.0;
System.out.println("getRibbonMask(): should not get here");
}
}
return ribbonMask;
}
/**
* Detect linear features in the frequency domain sample
* @param ribbonWidth Width of the band for processing phase (data inside will be multiplied by the Hamming filter perpendicular to the direction of the band
* @param fht frequency-domain FHT array of the sample
* @param minRMSFrac Process only frequencies with amplitude above this ratio of the RMS of all frequencies. (0 - do not use this threshold)
* @param minAbs Process only frequencies with (absolute) amplitude above this value (0 - do not use this threshold)
* @param maxPhaseMismatch Maximal mismatch between the diagonal pair of 2 pixel pairs to consider pixels valid (distance between crossing diagonals)
* @param dispertionCost - >0 - use phase dispersion when calculating quality of phase match, 0 - only weights.
* @param filter Bitmask enabling different phase half-steps (+1 - 0, +2 - pi/2, +4 - pi, +8 - 3pi/2. Value zero allows arbitrary step
* step 0 corresponds to thin white line, pi - thin black line, +pi/2 - edge black-to-white in the direction of directionAngle,
* 3pi/2 - white-to-black in the direction of directionAngle
* @return array of the following values:
* {bestAngle, angle of the perpendicular towards the linear feature: 0 in the direction of pixel x (right), pi/2 - in the direction of the pixel Y (down)
* distance, - distance from the sample center to the linear feature in the direction bnestAngle (in pixels)
* phaseAtZero, - extrapolated phase at zero frequency (DC) , see description of the parameter "filter" above
* maxStrength}; strength of the feature (combines multiple factors)
*/
double [] calcPhaseApproximation(
double ribbonWidth,
double [] fht,
double minRMSFrac,
double minAbs,
double maxPhaseMismatch,
double dispertionCost,
int filter
){
createPolarMasksIndices(ribbonWidth); // all but first time will return immediately.
int halfN=this.maxN/2;
double dispersionFatZero=(dispertionCost>0)?(Math.PI/halfN/dispertionCost):0.0;
double [][] dPHdXdY=new double [5][this.maxN*this.maxN/2]; // amp, phase, phaseWeight, dPhase/dX, dPhase/dY
for (int n=0;n<dPHdXdY.length;n++) for (int i=0;i<dPHdXdY[0].length;i++) dPHdXdY[n][i]=0.0;
double sum2=0.0;
for (int index=0;index<dPHdXdY[0].length;index++){
int indexMod=((this.maxN - (index/this.maxN)) % this.maxN) * this.maxN + ((this.maxN - (index%this.maxN)) % this.maxN);
double re=0.5*(fht[index]+fht[indexMod]);
double im=0.5*(fht[index]-fht[indexMod]);
// int x=(index+halfN)%this.maxN-halfN;
// int y=index/this.maxN;
double amp2=re*re+im*im;
sum2+=amp2;
dPHdXdY[0][index]=Math.sqrt(amp2);
dPHdXdY[1][index]=Math.atan2(im,re);
}
double rms=Math.sqrt(sum2/dPHdXdY[0].length);
double thresholdAmplitude=0.0;
if (this.debug) System.out.println("calc_dPHdXdY): rms="+rms);
if (minRMSFrac>0) thresholdAmplitude=minRMSFrac*rms;
if ((minAbs>0) && ((thresholdAmplitude==0) || (thresholdAmplitude>minAbs))) thresholdAmplitude=minAbs;
for (int index=0;index<dPHdXdY[0].length;index++) if (dPHdXdY[0][index]<thresholdAmplitude) dPHdXdY[0][index]=0.0;
double oneThird=1.0/3;
for (int y=0;y<(halfN-1);y++){
for (int x=-halfN+1; x<(halfN-1);x++){
// int index00=y*this.maxN+(x+halfN);
int index00=y*this.maxN+(x+this.maxN)%this.maxN;
int indexX0=y*this.maxN+(x+this.maxN+1)%this.maxN;
int index0Y=index00+this.maxN;
int indexXY=(y+1)*this.maxN+(x+this.maxN+1)%this.maxN;
if ((dPHdXdY[0][index00]>0) && (dPHdXdY[0][indexX0]>0) && (dPHdXdY[0][index0Y]>0) && (dPHdXdY[0][indexXY]>0)){
double dP00X0=dPHdXdY[1][indexX0]-dPHdXdY[1][index00];
double dP000Y=dPHdXdY[1][index0Y]-dPHdXdY[1][index00];
double dPX0XY=dPHdXdY[1][indexXY]-dPHdXdY[1][indexX0];
double dP0YXY=dPHdXdY[1][indexXY]-dPHdXdY[1][index0Y];
dP00X0-=(2*Math.PI)*Math.round(dP00X0/(2*Math.PI));
dP000Y-=(2*Math.PI)*Math.round(dP000Y/(2*Math.PI));
dPX0XY-=(2*Math.PI)*Math.round(dPX0XY/(2*Math.PI));
dP0YXY-=(2*Math.PI)*Math.round(dP0YXY/(2*Math.PI));
if (this.debug) {
System.out.println(" index00="+index00+" indexX0="+indexX0+" index0Y="+index0Y+" indexXY="+indexXY);
System.out.println(" dPHdXdY[1][index00]="+dPHdXdY[1][index00]+" dPHdXdY[1][indexX0]="+dPHdXdY[1][indexX0]+
" dPHdXdY[1][index0Y]="+dPHdXdY[1][index0Y]+" dPHdXdY[1][indexXY]="+dPHdXdY[1][indexXY]);
System.out.println(" dP00X0="+dP00X0+" dP000Y="+dP000Y+" dPX0XY="+dPX0XY+" dP0YXY="+dP0YXY);
}
double curv=Math.abs((dPX0XY-dP000Y)/2);
if ((maxPhaseMismatch==0) || (curv < maxPhaseMismatch)){
double w=Math.pow(dPHdXdY[0][index00]*dPHdXdY[0][indexX0]*dPHdXdY[0][index0Y], oneThird);
if (this.debug) System.out.print(">>> x="+x+" y="+y+ " curv="+curv);
dPHdXdY[2][index00]+=w;
dPHdXdY[3][index00]+=w*dP00X0;
dPHdXdY[4][index00]+=w*dP000Y;
if (this.debug) System.out.print(" index00="+index00+" w="+w);
w=Math.pow(dPHdXdY[0][index00]*dPHdXdY[0][indexX0]*dPHdXdY[0][indexXY], oneThird);
dPHdXdY[2][indexX0]+=w;
dPHdXdY[3][indexX0]+=w*dP00X0;
dPHdXdY[4][indexX0]+=w*dPX0XY;
if (this.debug) System.out.print(" indexX0="+indexX0+" w="+w);
w=Math.pow(dPHdXdY[0][index00]*dPHdXdY[0][index0Y]*dPHdXdY[0][indexXY], oneThird);
dPHdXdY[2][index0Y]+=w;
dPHdXdY[3][index0Y]+=w*dP0YXY; //dP000Y;
dPHdXdY[4][index0Y]+=w*dP000Y; //dP0YXY;
if (this.debug) System.out.print(" index0Y="+index0Y+" w="+w);
w=Math.pow(dPHdXdY[0][indexX0]*dPHdXdY[0][index0Y]*dPHdXdY[0][indexXY], oneThird);
dPHdXdY[2][indexXY]+=w;
dPHdXdY[3][indexXY]+=w*dP0YXY; //dPX0XY;
dPHdXdY[4][indexXY]+=w*dPX0XY; //dP0YXY;
if (this.debug) System.out.print(" indexXY="+indexXY+" w="+w);
} else {
if (this.debug) System.out.println(">-- x="+x+" y="+y+ " curv="+curv+" w="+Math.pow(dPHdXdY[0][index00]*dPHdXdY[0][indexX0]*dPHdXdY[0][index0Y], oneThird));
}
}
}
}
for (int i=1;i<halfN;i++){
int j=this.maxN-i;
if ((dPHdXdY[2][i] +dPHdXdY[2][j])>0.0) {
dPHdXdY[3][i]=(dPHdXdY[3][i]*dPHdXdY[2][i]+dPHdXdY[3][j]*dPHdXdY[2][j])/(dPHdXdY[2][i]+dPHdXdY[2][j]);
dPHdXdY[4][i]=(dPHdXdY[4][i]*dPHdXdY[2][i]+dPHdXdY[4][j]*dPHdXdY[2][j])/(dPHdXdY[2][i]+dPHdXdY[2][j]);
dPHdXdY[2][i]=dPHdXdY[2][i]+dPHdXdY[2][j];
dPHdXdY[3][j]=dPHdXdY[3][i];
dPHdXdY[4][j]=dPHdXdY[4][i];
dPHdXdY[2][j]=dPHdXdY[2][i];
}
}
for (int index=0;index<dPHdXdY[0].length;index++) if (dPHdXdY[2][index]>0){
dPHdXdY[3][index]/=dPHdXdY[2][index];
dPHdXdY[4][index]/=dPHdXdY[2][index];
}
// generate show image
if (this.debug){
double [][] debugdPHdXdY=new double [5][this.maxN*this.maxN];
for (int i=0;i<debugdPHdXdY[0].length;i++){
int x= (i%this.maxN)-halfN;
int y= (i/this.maxN)-halfN;
boolean other=(y<0);
if (y<0) {
y=-y;
x=-x;
}
if ((y<halfN) && (x>-halfN) && ( x<halfN)){
int j= (x+this.maxN)%this.maxN + this.maxN*y;
for (int n=0;n<debugdPHdXdY.length;n++) {
debugdPHdXdY[n][i]=(other && ((n==1)))? (-dPHdXdY[n][j]):dPHdXdY[n][j];
}
}
}
String [] titles={"amp","phase","weight","d/dx","d/dy"};
SDFA_INSTANCE.showArrays(
debugdPHdXdY,
this.maxN,
this.maxN,
true,
"dbgDiffPhases",
titles);
}
int numAngles=this.polarPhaseMasks.length;
double [] sinAngle=new double[numAngles];
double [] cosAngle=new double[numAngles];
double [] S0= new double [numAngles];
double [] SF= new double [numAngles];
double [] SF2=new double [numAngles];
double [] strength=new double [numAngles];
double [] mean=new double [numAngles];
for (int iAngle=0;iAngle<numAngles;iAngle++){
double angle=iAngle*Math.PI/numAngles;
sinAngle[iAngle]=Math.sin(angle);
cosAngle[iAngle]=Math.cos(angle);
S0[iAngle]=0.0;
SF[iAngle]=0.0;
SF2[iAngle]=0.0;
strength[iAngle]=0.0;
mean[iAngle]=0.0;
}
for (int index=0;index<this.polarPhaseIndices.length;index++) if (dPHdXdY[2][index]>0.0){
if (this.debug){
int x= ((index+halfN)%this.maxN)-halfN;
int y= (index/this.maxN);
System.out.println("\n+++ calc_dPHdXdY()x/y/ |"+x+"|"+y);
}
for (int iiAngle=0;iiAngle<this.polarPhaseIndices[index].length;iiAngle++){
int iAngle=this.polarPhaseIndices[index][iiAngle];
double w=this.polarPhaseMasks[iAngle][index]*dPHdXdY[2][index];
S0[iAngle]+=w;
if (this.debug){
System.out.println(" calc_dPHdXdY() iAngle/mask/weight/w: |"+"|"+iAngle+"|"+
this.polarPhaseMasks[iAngle][index]+"|"+dPHdXdY[2][index]+"|"+w+"|"+S0[iAngle]);
}
double F=dPHdXdY[3][index]*cosAngle[iAngle]+dPHdXdY[4][index]*sinAngle[iAngle];
SF[iAngle]+=w*F;
SF2[iAngle]+=w*F*F;
}
}
int iAngleMax=-1;
double maxStrength=0.0;
for (int iAngle=0;iAngle<numAngles;iAngle++) {
double disp=0;
if (S0[iAngle]>0.0){
mean[iAngle]=SF[iAngle]/S0[iAngle];
disp=Math.sqrt(S0[iAngle]*SF2[iAngle]-SF[iAngle]*SF[iAngle])/S0[iAngle];
// TODO: reduce dispersion influence if it is small
strength[iAngle]=(dispersionFatZero>0.0)?(dispersionFatZero* S0[iAngle]/(dispersionFatZero+disp)):S0[iAngle];
}
if (this.debug) System.out.println("calc_dPHdXdY(): |"+iAngle+"|"+
S0[iAngle]+"|"+SF[iAngle]+"|"+SF2[iAngle]+"|"+mean[iAngle]+"|"+disp+"|"+strength[iAngle]);
if (strength[iAngle]>maxStrength){
maxStrength=strength[iAngle];
iAngleMax=iAngle;
}
}
if (iAngleMax<0){
if (this.debug) System.out.println("calcPhaseApproximation(): Could not find maximal strength direction");
return null;
}
double b=0.5*(strength[(iAngleMax+1)%numAngles]-strength[(iAngleMax+numAngles-1)%numAngles]);
double a=0.5*(strength[(iAngleMax+1)%numAngles]+strength[(iAngleMax+numAngles-1)%numAngles])-strength[iAngleMax]; // negative
// double x=-0.5*b/a;
double dAngle=(-0.5*b/a);
double bestAngle=iAngleMax+dAngle;
maxStrength+=a*dAngle*dAngle+b*dAngle;
if (this.debug) System.out.println("calcPhaseApproximation(): |"+iAngleMax+"|"+dAngle+"|"+bestAngle+"|"+maxStrength);
int secondAngle= (iAngleMax+((dAngle<0)?-1:1)+numAngles)%numAngles;
double secondMean=mean[secondAngle];
if ((dAngle*(secondAngle-iAngleMax))<0) secondMean=-secondMean; // roll over
double bestMean=mean[iAngleMax]*(1.0-Math.abs(dAngle))+ secondMean*Math.abs(dAngle);
if (bestMean<0){
bestAngle+=numAngles;
bestMean=-bestMean;
}
bestAngle*=Math.PI/numAngles;
bestAngle-=(2*Math.PI)*Math.round(bestAngle/(2*Math.PI));
double distance=bestMean*this.maxN/(2*Math.PI);
if (this.debug) System.out.println("calcPhaseApproximation(): bestAngle="+bestAngle+" ("+IJ.d2s(180*bestAngle/Math.PI,2)+"), distance="+distance+" (bestMean="+bestMean+"), maxStrength="+maxStrength);
// bin weights, Re and Im for the points with non-zero amplitude and within the band in the direction angle along the direction angle
double [][] wReIm=new double [3][halfN]; // weight/re/im
double sin=Math.sin(bestAngle);
double cos=Math.cos(bestAngle);
double halfWidth=this.polarRibbonWidth/2;
boolean conj=(sin<0);
if (conj){
sin=-sin;
cos=-cos;
}
for (int index=0;index<dPHdXdY[0].length;index++){
int indexMod=((this.maxN - (index/this.maxN)) % this.maxN) * this.maxN + ((this.maxN - (index%this.maxN)) % this.maxN);
int x=(index+halfN)%this.maxN-halfN;
int y=index/this.maxN;
double t= cos*x+sin*y;
double d= sin*x-cos*y;
int it=(int) Math.round(Math.abs(t));
if (((y>0) || (x>0)) &&(Math.abs(d) < halfWidth) && (it < halfN) ){
double re=0.5*(fht[index]+fht[indexMod]);
double im=0.5*(fht[index]-fht[indexMod]);
if (conj) im=-im;
if (t<0){
im=-im;
t=-t;
}
double w=dPHdXdY[0][index]*(0.54+0.46*Math.cos(Math.PI*d/halfWidth)); // (masked) amplitude;
if (t<halfWidth) w*=0.54+0.46*Math.cos(Math.PI*(halfWidth-t)/halfWidth);
else if (t>(halfN-halfWidth)) w*=0.54+0.46*Math.cos(Math.PI*(t-(halfN-halfWidth))/halfWidth);
wReIm[0][it]+=w;
wReIm[1][it]+=w*re;
wReIm[2][it]+=w*im;
}
}
if (this.debug) {
System.out.println("calcPhaseApproximation(): binnig w/re/im along selected direction");
for (int it=0;it<wReIm[0].length;it++){
System.out.println("calcPhaseApproximation(): |"+it+"|"+wReIm[0][it]+"|"+wReIm[1][it]+"|"+wReIm[2][it]+"|"+Math.atan2(wReIm[2][it],wReIm[1][it]));
}
}
double [] approximatedZeroPhaseDist=approximateLinearPhase(
wReIm,
filter);
if (approximatedZeroPhaseDist==null) return null;
if (this.debug) {
System.out.println("calcPhaseApproximation(): initial distance="+distance+" updated distance="+approximatedZeroPhaseDist[1]+
" phase at zero="+approximatedZeroPhaseDist[0]+" ("+IJ.d2s(approximatedZeroPhaseDist[0]*180.0/Math.PI,1)+")");
for (int it=0;it<wReIm[0].length;it++){
System.out.println("calcPhaseApproximation(): |"+it+"|"+wReIm[0][it]+"|"+wReIm[1][it]+"|"+wReIm[2][it]+"|"+Math.atan2(wReIm[2][it],wReIm[1][it]));
}
}
distance=approximatedZeroPhaseDist[1];
double phaseAtZero=approximatedZeroPhaseDist[0];
double [] result={bestAngle,distance,phaseAtZero,maxStrength};
return result;
}
private double polarRibbonWidth=-1;
private double [][] polarPhaseMasks=null;
private int [][] polarPhaseIndices=null;
public void createPolarMasksIndices(
double ribbonWidth){ // <0 - invalidate
if (this.polarRibbonWidth==ribbonWidth) return; // already set;
this.polarRibbonWidth=ribbonWidth;
if (ribbonWidth<0.0){
this.polarPhaseMasks=null;
this.polarPhaseIndices=null;
return;
}
int halfN=this.maxN/2;
int length=halfN*this.maxN;
double halfWidth=ribbonWidth/2;
int numAngles=(int) Math.round(Math.PI*halfN);
this.polarPhaseMasks=new double [numAngles][length];
this.polarPhaseIndices=new int [length][];
for (int i=0;i<this.polarPhaseIndices.length;i++){
this.polarPhaseIndices[i]=null;
for (int iAngle=0;iAngle<numAngles;iAngle++) this.polarPhaseMasks[iAngle][i]=0.0;
}
for (int iAngle=0;iAngle<numAngles;iAngle++){
double angle=iAngle*Math.PI/numAngles;
double sin=Math.sin(angle);
double cos=Math.cos(angle);
double sumWeights=0.0;
for (int index=0;index<length;index++){
int x=(index+halfN)%this.maxN-halfN;
int y=index/this.maxN;
double t= cos*x+sin*y;
double d= sin*x-cos*y;
if (((y>0) || (x>0)) &&(Math.abs(d) < halfWidth) && (Math.abs(t) < halfN) ){
this.polarPhaseMasks[iAngle][index]=0.54+0.46*Math.cos(d/halfWidth*Math.PI); // normalize
if (Math.abs(t) > (halfN-halfWidth)) this.polarPhaseMasks[iAngle][index]*=0.54+0.46*Math.cos((Math.abs(t)-(halfN-halfWidth))/halfWidth*Math.PI);
sumWeights+=this.polarPhaseMasks[iAngle][index];
}
}
sumWeights=1.0/sumWeights;
for (int index=0;index<length;index++) this.polarPhaseMasks[iAngle][index]*=sumWeights;
}
for (int index=0;index<length;index++){
int numUsedAngles=0;
for (int iAngle=0;iAngle<numAngles;iAngle++) if (this.polarPhaseMasks[iAngle][index]>0.0) numUsedAngles++;
this.polarPhaseIndices[index]=new int[numUsedAngles]; // may be 0
int iiAngle=0;
for (int iAngle=0;iAngle<numAngles;iAngle++) if (this.polarPhaseMasks[iAngle][index]>0.0) this.polarPhaseIndices[index][iiAngle++]=iAngle;
}
if (this.debug){
double [][] debugPolarPhaseMasks=new double [numAngles+1][this.maxN*this.maxN];
for (int i=0;i<debugPolarPhaseMasks[0].length;i++){
int x= (i%this.maxN)-halfN;
int y= (i/this.maxN)-halfN;
if (y<0) {
y=-y;
x=-x;
}
if ((y<halfN) && (x>-halfN) && ( x<halfN)){
int j= (x+this.maxN)%this.maxN + this.maxN*y;
for (int n=0;n<numAngles;n++) {
debugPolarPhaseMasks[n][i]=this.polarPhaseMasks[n][j];
}
debugPolarPhaseMasks[numAngles][i]=this.polarPhaseIndices[j].length;
}
}
String [] titles=new String[numAngles+1];
for (int n=0;n<numAngles;n++) titles[n]=IJ.d2s(180.0*n/numAngles,1);
titles[numAngles]="usage number";
SDFA_INSTANCE.showArrays(
debugPolarPhaseMasks,
this.maxN,
this.maxN,
true,
"dbgDiffPhases",
titles);
}
}
/**
* Project and accumulate Re, Im on the direction perpendicular to the linear feature
* @param directionAngle direction of the perpendicular line
* @param phaseIntegrationWidth Disregard pixels outside of the band centered along directionAngle
* @param h FHT array
* @param nonZeroIndices List of the above-threshold indices in the FHT array
* @return arrays of {weight, re, Im} along the line with the step equal to 1 pixel
*/
public double [][] binReIm(
double directionAngle,
double phaseIntegrationWidth, // zero - no limit
double [] h,
int [] nonZeroIndices
){
int halfN=this.maxN/2;
double [][] wReIm=new double [3][halfN]; // weight/re/im
for (int n=0;n<wReIm.length;n++) for (int i=0;i<wReIm[0].length;i++) wReIm[n][i]=0.0;
double sin=Math.sin(directionAngle);
double cos=Math.cos(directionAngle);
double amp,re,im;
double halfWidth=(phaseIntegrationWidth>0.0)?phaseIntegrationWidth/2:this.maxN;
for (int numPoint=0; numPoint<nonZeroIndices.length;numPoint++) {
int index=nonZeroIndices[numPoint];
int indexMod=((this.maxN - (index/this.maxN)) % this.maxN) * this.maxN + ((this.maxN - (index%this.maxN)) % this.maxN);
re=0.5*(h[index]+h[indexMod]);
im=0.5*(h[index]-h[indexMod]);
int x=(index+halfN)%this.maxN-halfN;
int y=index/this.maxN;
amp=Math.sqrt(re*re+im*im);
if (y==0) amp*=0.5;
double t = cos*x+sin*y;
if (Math.abs(cos*y-sin*x)<=halfWidth) {
int itl= (int) Math.floor(t);
int ith=itl+1;
double wl=amp*(t-itl);
double wh=amp*(itl+1-t);
boolean conj=(itl<0);
if (conj){
itl=-itl;
ith=-ith;
}
if (itl<wReIm[0].length){
wReIm[0][itl]+=wl;
wReIm[1][itl]+=wl*re;
if (conj) wReIm[2][itl]-=wl*im;
else wReIm[2][itl]+=wl*im;
}
if (ith<wReIm[0].length){
wReIm[0][ith]+=wh;
wReIm[1][ith]+=wh*re;
if (conj) wReIm[2][ith]-=wh*im;
else wReIm[2][ith]+=wh*im;
}
} else {
System.out.println("binReIm(): dropped point x="+x+" y="+y+" as it is too far from the center line");
}
}
for (int i=0;i<wReIm[0].length;i++){
if (wReIm[0][i]>0.0){
wReIm[1][i]/=wReIm[0][i];
wReIm[2][i]/=wReIm[0][i];
}
}
return wReIm;
}
/**
* Linear approximate phase, optionally filtering types of features (black line, white line black-to-white edge, white-to-black edge
* @param wReIm - array of weights, Re and Im along the selected direction
* @param filter Bitmask enabling different phase half-steps (+1 - 0, +2 - pi/2, +4 - pi, +8 - 3pi/2. Value zero allows arbitrary step
* step 0 corresponds to thin white line, pi - thin black line, +pi/2 - edge black-to-white in the direction of directionAngle,
* 3pi/2 - white-to-black in the direction of directionAngle
* @return pair of phase at 0 and slope expressed in distance (in pixels) from the center to the linear feature along the selected
* direction (may be negative)
*/
public double [] approximateLinearPhase(
double [][]wReIm,
int filter){
double [] phase=new double [wReIm[0].length];
for (int i=0;i<phase.length;i++) if (wReIm[0][i]>0.0) phase[i]=Math.atan2(wReIm[2][i],wReIm[1][i]);
double diffPhase=0.0;
double sumWeights=0.0;
for (int i=0;i<(phase.length-1);i++){
double w=wReIm[0][i]*wReIm[0][i+1];
if (w>0.0){
double diff=phase[i+1]-phase[i];
if (diff>Math.PI) while (diff>Math.PI) diff-=2*Math.PI;
else if (diff<-Math.PI) while (diff<-Math.PI) diff+=2*Math.PI;
sumWeights+=w;
diffPhase+=diff*w;
}
}
if (sumWeights==0.0) return null;
diffPhase/=sumWeights;
double [] binPhases = {0.0,Math.PI/2,Math.PI,-Math.PI/2};
double [] zeroPhaseBins={0.0,0.0,0.0,0.0};
double zeroPhase=Double.NaN;
switch (filter){
case 0:
double re=0.0,im=0.0;
for (int n=0;n<phase.length;n++) if (wReIm[0][n]>0.0) {
double phZero=phase[n]-diffPhase*n;
re+=wReIm[0][n]*Math.cos(phZero);
im+=wReIm[0][n]*Math.sin(phZero);
}
zeroPhase=Math.atan2(im,re);
if (Double.isNaN(zeroPhase)) zeroPhase=0.0; //??
break;
case 1:zeroPhase=binPhases[0]; break;
case 2:zeroPhase=binPhases[1]; break;
case 4:zeroPhase=binPhases[2]; break;
case 8:zeroPhase=binPhases[3]; break;
default:
// double [] zeroPhaseBins={0.0,0.0,0.0,0.0};
boolean [] binEnabled={(filter & 1)!=0,(filter & 2)!=0,(filter & 4)!=0,(filter & 8)!=0};
for (int n=0;n<phase.length;n++) if (wReIm[0][n]>0.0) {
double phZero=phase[n]-diffPhase*n;
phZero-=2*Math.PI*Math.floor(phZero/(2*Math.PI)); // now in the range 0..2*PI
double bestBinDif=2*Math.PI;
int iBestBin=0;
for (int i=0;i<binEnabled.length;i++) if (binEnabled[i]){
double d=phZero-binPhases[i];
d-=2*Math.PI*Math.round(d/(2*Math.PI));
d=Math.abs(d);
if (d<bestBinDif){
bestBinDif=d;
iBestBin=i;
}
}
zeroPhaseBins[iBestBin]+=wReIm[0][n];
}
double maxBinValue=0;
int iMaxBin=-1;
for (int i=0;i<zeroPhaseBins.length;i++) if (zeroPhaseBins[i]>maxBinValue){
maxBinValue=zeroPhaseBins[i];
iMaxBin=i;
}
zeroPhase=iMaxBin*2*Math.PI/zeroPhaseBins.length;
if (zeroPhase>Math.PI) zeroPhase-=2*Math.PI;
}
double [] fullPhase=phase.clone();
for (int i=0;i<fullPhase.length;i++)if (wReIm[0][i]>0.0){
double diff=fullPhase[i]-(zeroPhase+diffPhase*i);
fullPhase[i]-=2*Math.PI*Math.round(diff/(2*Math.PI));
}
if (this.debug){
for (int i=0;i<zeroPhaseBins.length;i++) System.out.println("Phase "+i+" bin: "+zeroPhaseBins[i]);
System.out.println("Initial estimated distance to line "+(this.maxN*diffPhase/(2*Math.PI))+", zeroPhase="+zeroPhase);
for (int i=0;i<phase.length;i++) if (wReIm[0][i]>0.0) {
System.out.println(i+"|"+IJ.d2s(phase[i],3)+"|"+IJ.d2s(fullPhase[i],3)+"|"+IJ.d2s((zeroPhase+diffPhase*i),3));
}
}
// re-adjust slope (distance) after zero phase is selected
double SFX=0.0,SX2=0.0;
for (int i=0;i<fullPhase.length;i++)if (wReIm[0][i]>0.0){
SFX+=wReIm[0][i]*i*(fullPhase[i]-zeroPhase);
SX2+=wReIm[0][i]*i*i;
}
diffPhase=SFX/SX2;
double distance=this.maxN*diffPhase/(2*Math.PI);
double [] result={zeroPhase,distance};
if (this.debug){
System.out.println("Recalculated distance to line "+distance+" (slope="+diffPhase+" rad/pix)");
}
return result;
}
public double [][] calcIndAmReIm (double [] h, int [] nonZeroIndices){
double [][] ampReIm=new double[3][nonZeroIndices.length];
for (int numPoint=0; numPoint<nonZeroIndices.length;numPoint++) {
int index=nonZeroIndices[numPoint];
int indexMod=((this.maxN - (index/this.maxN)) % this.maxN) * this.maxN + ((this.maxN - (index%this.maxN)) % this.maxN);
ampReIm[1][numPoint]=0.5*(h[index]+h[indexMod]);
ampReIm[2][numPoint]=0.5*(h[index]-h[indexMod]);
ampReIm[0][numPoint]=Math.sqrt(ampReIm[1][index]*ampReIm[1][index]+ampReIm[2][index]*ampReIm[2][index]);
}
return ampReIm;
}
public double [][] calcIndAmHPhase (double [] h, int [] nonZeroIndices){
double re,im;
double [][] amHPhase=new double[nonZeroIndices.length][2];
for (int numPoint=0; numPoint<nonZeroIndices.length;numPoint++) {
int index=nonZeroIndices[numPoint];
int indexMod=((this.maxN - (index/this.maxN)) % this.maxN) * this.maxN + ((this.maxN - (index%this.maxN)) % this.maxN);
re=0.5*(h[index]+h[indexMod]);
im=0.5*(h[index]-h[indexMod]);
amHPhase[numPoint][0]=Math.sqrt(re*re+im*im);
if ((index/this.maxN)>0) amHPhase[numPoint][0]*=2; // double weight
amHPhase[numPoint][1]=Math.atan2(im,re);
/*
System.out.println("numPoint="+numPoint+" index="+nonZeroIndices[numPoint]+
" x="+(nonZeroIndices[numPoint]%this.maxN)+" y="+(nonZeroIndices[numPoint]/this.maxN)+
" re="+re+" im="+im+" phase="+amHPhase[numPoint][1]);
*/
}
return amHPhase;
}
public int updateFullCycles(
int [] fullCycles, // may be modified
double px,
double py,
int [] nonZeroIndices,
double [][] amHPhase
){
int numChanges=0;
double e=1E-8;
double [] linearPhase= tiltedPhase (px, py, nonZeroIndices);
for (int numPoint=0; numPoint<nonZeroIndices.length;numPoint++) {
// double delta=amHPhase[numPoint][1]+fullCycles[numPoint]*2*Math.PI -linearPhase[numPoint];
double delta=linearPhase[numPoint]-(amHPhase[numPoint][1]+fullCycles[numPoint]*2*Math.PI);
if (Math.abs(delta)>(Math.PI+e)){
System.out.print("numPoint="+numPoint+" index="+nonZeroIndices[numPoint]+
" x="+(nonZeroIndices[numPoint]%this.maxN)+" y="+(nonZeroIndices[numPoint]/this.maxN)+
" fullCycles["+numPoint+"]: "+fullCycles[numPoint]+" -> ");
fullCycles[numPoint]+=(int)Math.round(delta/(2*Math.PI));
System.out.println(fullCycles[numPoint]);
numChanges++;
}
}
return numChanges;
}
public double calcWeightedPhaseRMS(
int [] fullCycles, // may be modified
double px,
double py,
int [] nonZeroIndices,
double [][] ampPhase
){
double [] linearPhase= tiltedPhase (px, py, nonZeroIndices);
double S0=0.0,S2=0.0;
for (int numPoint=0; numPoint<ampPhase.length;numPoint++) {
double delta=ampPhase[numPoint][1]+fullCycles[numPoint]*2*Math.PI -linearPhase[numPoint];
S0+=ampPhase[numPoint][0];
S2+=ampPhase[numPoint][0]*delta*delta;
/*
System.out.println("nonZeroIndices["+numPoint+"]="+nonZeroIndices[numPoint]+
" x="+(nonZeroIndices[numPoint]%this.maxN)+" y="+(nonZeroIndices[numPoint]/this.maxN)+
" ampPhase["+numPoint+"][0]="+ampPhase[numPoint][0]+" ampPhase["+numPoint+"][1]="+ampPhase[numPoint][1]+
" delta="+delta+" S0="+S0+" S2="+S2);
*/
}
double weightedRMS=Math.sqrt(S2/S0);
return weightedRMS;
}
public double [] calcPhaseShift(
int [] fullCycles, // may be modified
int [] nonZeroIndices,
double [][] ampPhase
){
// double [] linearPhase= tiltedPhase (px, py, nonZeroIndices);
// minimize weighted RMS with a plane through (0,0)
int halfN=this.maxN/2;
double SFX=0.0,SFY=0.0,SX2=0.0,SY2=0.0,SXY=0.0;
for (int numPoint=0; numPoint<ampPhase.length;numPoint++) {
int index=nonZeroIndices[numPoint];
int x=(index+halfN)%this.maxN-halfN;
int y=index/this.maxN;
double fXY=ampPhase[numPoint][1]+Math.PI*2*fullCycles[numPoint];
double w=ampPhase[numPoint][0];
SFX+=w*x*fXY;
SFY+=w*y*fXY;
SX2+=w*x*x;
SY2+=w*y*y;
SXY+=w*x*y;
}
double denominator=SXY*SXY-SX2*SY2;
double [] phaseTilt={(SFY*SXY-SFX*SY2)/denominator,(SFX*SXY-SFY*SX2)/denominator};
return phaseTilt;
}
public double calcPhaseShift(
double px,
double py,
int [] fullCycles, // may be modified
int [] nonZeroIndices,
double [][] ampPhase
){
// double [] linearPhase= tiltedPhase (px, py, nonZeroIndices);
// minimize weighted RMS with a plane through (0,0)
int halfN=this.maxN/2;
double SFX=0.0,SFY=0.0,SX2=0.0,SY2=0.0,SXY=0.0;
for (int numPoint=0; numPoint<ampPhase.length;numPoint++) {
int index=nonZeroIndices[numPoint];
int x=(index+halfN)%this.maxN-halfN;
int y=index/this.maxN;
double fXY=ampPhase[numPoint][1]+Math.PI*2*fullCycles[numPoint];
double w=ampPhase[numPoint][0];
SFX+=w*x*fXY;
SFY+=w*y*fXY;
SX2+=w*x*x;
SY2+=w*y*y;
SXY+=w*x*y;
}
return (py*SFY+px*SFX-px*py*SXY)/(px*px*SX2+py*py*SY2);
}
public double [] tiltedPhase (
double px,
double py,
int [] nonZeroIndices){
int halfN=this.maxN/2;
double [] phase=new double [nonZeroIndices.length];
for (int numPoint=0; numPoint<nonZeroIndices.length;numPoint++) {
int index=nonZeroIndices[numPoint];
int x=(index+halfN)%this.maxN-halfN;
int y=index/this.maxN;
phase[numPoint]=px*x+py*y;
}
return phase;
}
/*
public double [][] calcAmReIm (double [] h, double [] ampMask){
int rowMod, colMod;
int halfN=this.maxN/2;
double [][] ampReIm=new double[3][this.maxN*halfN];
for (int r =0; r<this.maxN/2; r++) {
rowMod = (this.maxN - r) % this.maxN;
for (int c=0; c<this.maxN; c++){
int index= r * this.maxN + c;
if ((ampMask==null) || (ampMask[index]>0.0)){
colMod = (this.maxN - c) % this.maxN;
int indexMod=rowMod * this.maxN + colMod;
ampReIm[1][index]=0.5*(h[index]+h[indexMod]);
ampReIm[2][index]=0.5*(h[index]-h[indexMod]);
ampReIm[0][index]=Math.sqrt(ampReIm[1][index]*ampReIm[1][index]+ampReIm[2][index]*ampReIm[2][index]);
} else {
ampReIm[1][index]=0.0;
ampReIm[2][index]=0.0;
ampReIm[0][index]=0.0;
}
}
}
return ampReIm;
}
*/
public double [] filterAmplitude(
double [] h,
boolean removeIslands,
double threshold){ // relative to RMS value
int rowMod, colMod;
int halfN=this.maxN/2;
double [] amplitude=new double[this.maxN*halfN];
double max=0;
int iMax=-1;
double sum2=0.0;
double amp2;
for (int r =0; r<this.maxN/2; r++) {
rowMod = (this.maxN - r) % this.maxN;
for (int c=0; c<this.maxN; c++){
int index= r * this.maxN + c;
if (c==this.maxN/2) {
amplitude[index]=0.0;
} else {
colMod = (this.maxN - c) % this.maxN;
int indexMod=rowMod * this.maxN + colMod;
if (r>0) {
amp2=h[index]*h[index]+h[indexMod]*h[indexMod]; // count twice (for the second symmetrical half)
amplitude[index]=2.0*amp2; // so square root will be twice
} else {
amp2=0.5*(h[index]*h[index]+h[indexMod]*h[indexMod]);
amplitude[index]=amp2;
}
sum2+=amp2;
if (amp2>max){
max=amp2;
iMax=index;
}
}
}
}
double minAmp2=threshold*sum2/(this.maxN -1)/(this.maxN -1);
for (int i=0;i<amplitude.length;i++){
if (amplitude[i]<minAmp2) amplitude[i]=0.0;
else amplitude[i]=Math.sqrt(amplitude[i]);
}
int numDefined=0;
if (this.debug) {
for (int i=0;i<amplitude.length;i++) if (amplitude[i]>0.0) numDefined++;
System.out.println("quadraticAmplitude(): number of defined cells="+numDefined+" ( of "+amplitude.length+")");
}
if (removeIslands) {
boolean [] confirmed=new boolean [amplitude.length];
for (int i=0;i<confirmed.length;i++) confirmed[i]=false;
int [][] dirs={{1,0},{1,1},{0,1},{-1,1},{-1,0},{-1,-1},{0,-1},{1,-1}};
List <Integer> ampList=new ArrayList<Integer>(confirmed.length);
int x=(iMax+halfN)%this.maxN-halfN;
int y=iMax/this.maxN;
Integer Index=iMax;
ampList.add(Index);
confirmed[Index]=true;
while (ampList.size()>0){
int index=ampList.remove(0);
x=(index+halfN)%this.maxN-halfN;
y=index/this.maxN;
for (int iDir=0;iDir<dirs.length;iDir++){
int x1=x+dirs[iDir][0];
int y1=y+dirs[iDir][1];
if (y1<0){
y1=-y1;
x1=-x1;
}
if ((x1>-halfN) && (x1<halfN) && (y1>=0) && (y1<halfN)){
Index=(x1+this.maxN)%this.maxN + this.maxN*y1;
if (!confirmed[Index] && (amplitude[Index]>0.0)){
confirmed[Index]=true;
ampList.add(Index);
}
}
}
}
for (int i=0;i<amplitude.length;i++){
if (!confirmed[i]) amplitude[i]=0.0;
}
if (this.debug) {
numDefined=0;
for (int i=0;i<amplitude.length;i++) if (amplitude[i]>0.0) numDefined++;
System.out.println("quadraticAmplitude(): number of remaining cells="+numDefined+" ( of "+amplitude.length+")");
}
}
/*
if (this.debug){
SDFA_INSTANCE.showArrays(
amplitude,
this.maxN,
halfN,
"fltAmp");
double [] debugAmplitude=new double [this.maxN*this.maxN];
for (int i=0;i<debugAmplitude.length;i++){
debugAmplitude[i]=0.0;
int x= (i%this.maxN)-halfN;
int y= (i/this.maxN)-halfN;
if (y<0) {
y=-y;
x=-x;
}
if ((y<halfN) && (x>-halfN) && ( x<halfN)){
int j= (x+this.maxN)%this.maxN + this.maxN*y;
debugAmplitude[i]=amplitude[j];
if (y>0) debugAmplitude[i]*=0.5;
}
}
SDFA_INSTANCE.showArrays(
debugAmplitude,
this.maxN,
this.maxN,
"fltAmp");
}
*/
return amplitude;
}
public double [] quadraticAmplitude(
double [] amplitude,
boolean removeIslands,
double threshold){ // relative to RMS value
int halfN=this.maxN/2;
double[] result = new double[3]; // CX2, CY2, CXY
for (int i=0;i<result.length;i++) result[i]=0.0;
double s=0.0;
double amp;
for (int r =0; r<this.maxN/2; r++) {
for (int c=0; c<this.maxN; c++) if (c!=this.maxN/2){
int index= r * this.maxN + c;
amp=amplitude[index];
s+=amp;
int x=((c+halfN)%this.maxN)-halfN;
result[0]+=amp*x*x;
result[1]+=amp*r*r;
result[2]+=amp*x*r;
}
}
s*=halfN*halfN; // normalize so result will be related to pixels, not to the FFT size
for (int i=0;i<result.length;i++) result[i]/=s;
return result;
}
/*
public double [] quadraticAmplitude(
double [] h){
int rowMod, colMod;
double[] result = new double[3]; // CX2, CY2, CXY
for (int i=0;i<result.length;i++) result[i]=0.0;
double s=0.0;
double amp;
int halfN=this.maxN/2;
for (int r =0; r<this.maxN/2; r++) {
rowMod = (this.maxN - r) % this.maxN;
for (int c=0; c<this.maxN; c++) if (c!=this.maxN/2){
colMod = (this.maxN - c) % this.maxN;
int index= r * this.maxN + c;
int indexMod=rowMod * this.maxN + colMod;
amp=Math.sqrt(0.5*(h[index]*h[index]+h[indexMod]*h[indexMod]));
if (r>0) amp*=2; // count twice (for the second symmetrical half)
s+=amp;
int x=((c+halfN)%this.maxN)-halfN;
result[0]+=amp*x*x;
result[1]+=amp*r*r;
result[2]+=amp*x*r;
}
}
s*=halfN*halfN; // normalize so result will be related to pixels, not to the FFT size
for (int i=0;i<result.length;i++) result[i]/=s;
return result;
}
*/
public double [] ellipseAmplitude(
double [] quadratic){
double[] result = new double[3]; // angle (clockwise from X, large half-diameter, small/large ratio
double alpha=0.5*Math.atan2(2*quadratic[2], quadratic[0]-quadratic[1]); // 1/2*atan(2*Sxy/(sx2-sy2))
double cos=Math.cos(alpha), sin=Math.sin(alpha);
double I1=sin*sin*quadratic[0]+cos*cos*quadratic[1]-2*cos*sin*quadratic[2];
double I2=cos*cos*quadratic[0]+sin*sin*quadratic[1]+2*cos*sin*quadratic[2];
boolean rot=(I1>I2);
result[0]=alpha+(rot?Math.PI:0);
result[1]=Math.sqrt(rot?I1:I2);
result[2]=Math.sqrt(rot?(I2/I1):(I1/I2));
return result;
}
public double [] multiply(double [] h1, double [] h2, boolean conjugate) {
int rowMod, colMod;
double h2e, h2o;
double[] product = new double[maxN*maxN];
for (int r =0; r<maxN; r++) {
rowMod = (maxN - r) % maxN;
for (int c=0; c<maxN; c++) {
colMod = (maxN - c) % maxN;
h2e = (h2[r * maxN + c] + h2[rowMod * maxN + colMod]) / 2;
h2o = (h2[r * maxN + c] - h2[rowMod * maxN + colMod]) / 2;
if (conjugate)
product[r * maxN + c] = (double)(h1[r * maxN + c] * h2e - h1[rowMod * maxN + colMod] * h2o);
else
product[r * maxN + c] = (double)(h1[r * maxN + c] * h2e + h1[rowMod * maxN + colMod] * h2o);
}
}
return product;
}
public double [] phaseMultiply(double [] h1, double [] h2, double phaseCoeff) {
int rowMod, colMod;
double h2e, h2o,d;
double[] product = new double[maxN*maxN];
for (int r =0; r<maxN; r++) {
rowMod = (maxN - r) % maxN;
for (int c=0; c<maxN; c++) {
colMod = (maxN - c) % maxN;
h2e = (h2[r * maxN + c] + h2[rowMod * maxN + colMod]) / 2;
h2o = (h2[r * maxN + c] - h2[rowMod * maxN + colMod]) / 2;
d=phaseCoeff*(h2e*h2e+h2o*h2o)+(1.0-phaseCoeff);
product[r * maxN + c] = (h1[r * maxN + c] * h2e - h1[rowMod * maxN + colMod] * h2o)/d;
}
}
return product;
}
// Multiply by real array (i.e. filtering in frequency domain). Array m length should be at least maxN*maxN/2+1
public void multiplyByReal(double [] h, double [] m) {
int rowMod, colMod;
int index=0, indexM;
for (int r =0; r<maxN; r++) {
rowMod = (maxN - r) % maxN;
for (int c=0; c<maxN; c++) {
colMod = (maxN - c) % maxN;
indexM=rowMod * maxN + colMod;
if (indexM>index) indexM=index;
h[index]*=m[indexM];
index++;
}
}
}
/** nothing to prevent division by small values */
/** Returns the image resulting from the point by point Hartley division
of this image by the specified image. Both images are assumed to be in
the frequency domain. Division in the frequency domain is equivalent
to deconvolution in the space domain. */
public double [] divide(double [] h1, double [] h2) {
int rowMod, colMod;
double mag, h2e, h2o;
double[] result = new double[maxN*maxN];
for (int r=0; r<maxN; r++) {
rowMod = (maxN - r) % maxN;
for (int c=0; c<maxN; c++) {
colMod = (maxN - c) % maxN;
mag =h2[r*maxN+c] * h2[r*maxN+c] + h2[rowMod*maxN+colMod] * h2[rowMod*maxN+colMod];
if (mag<1e-20)
mag = 1e-20;
h2e = (h2[r*maxN+c] + h2[rowMod*maxN+colMod]);
h2o = (h2[r*maxN+c] - h2[rowMod*maxN+colMod]);
double tmp = (h1[r*maxN+c] * h2e - h1[rowMod*maxN+colMod] * h2o);
result[r*maxN+c] = tmp/mag;
}
}
return result;
}
public double [] calculateAmplitude(double [] fht) {
int size=(int) Math.sqrt(fht.length);
double[] amp = new double[size*size];
for (int row=0; row<size; row++) {
amplitude(row, size, fht, amp);
}
swapQuadrants(amp);
return amp;
}
public double [] calculateAmplitude2(double [] fht) {
int size=(int) Math.sqrt(fht.length);
double[] amp = new double[size*size];
for (int row=0; row<size; row++) {
amplitude2(row, size, fht, amp);
}
swapQuadrants(amp);
return amp;
}
/** Amplitude of one row from 2D Hartley Transform. */
void amplitude(int row, int size, double[] fht, double[] amplitude) {
int base = row*size;
int l;
for (int c=0; c<size; c++) {
l = ((size-row)%size) * size + (size-c)%size;
amplitude[base+c] = (double)Math.sqrt(fht[base+c]*fht[base+c] + fht[l]*fht[l]);
}
}
/** Squared amplitude of one row from 2D Hartley Transform. */
void amplitude2(int row, int size, double[] fht, double[] amplitude) {
int base = row*size;
int l;
for (int c=0; c<size; c++) {
l = ((size-row)%size) * size + (size-c)%size;
amplitude[base+c] = fht[base+c]*fht[base+c] + fht[l]*fht[l];
}
}
// Other FHT-related methods moved here
/** converts FHT results (frequency space) to complex numbers of [fftsize/2+1][fftsize] */
public double[][][] FHT2FFTHalf (FHT fht, int fftsize) {
float[] fht_pixels=(float[])fht.getPixels();
double[][][] fftHalf=new double[(fftsize>>1)+1][fftsize][2];
int row1,row2,col1,col2;
for (row1=0;row1<=(fftsize>>1);row1++) {
row2=(fftsize-row1) %fftsize;
for (col1=0;col1<fftsize;col1++) {
col2=(fftsize-col1) %fftsize;
// fftHalf[row1][col1]= complex( 0.5*(fht_pixels[row1*fftsize+col1] + fht_pixels[row2*fftsize+col2]),
// 0.5*(fht_pixels[row2*fftsize+col2] - fht_pixels[row1*fftsize+col1]));
fftHalf[row1][col1][0]= 0.5*(fht_pixels[row1*fftsize+col1] + fht_pixels[row2*fftsize+col2]);
fftHalf[row1][col1][1]= 0.5*(fht_pixels[row2*fftsize+col2] - fht_pixels[row1*fftsize+col1]);
}
}
return fftHalf;
}
public double[][][] FHT2FFTHalf (double [] fht_pixels, int fftsize) {
double[][][] fftHalf=new double[(fftsize>>1)+1][fftsize][2];
int row1,row2,col1,col2;
for (row1=0;row1<=(fftsize>>1);row1++) {
row2=(fftsize-row1) %fftsize;
for (col1=0;col1<fftsize;col1++) {
col2=(fftsize-col1) %fftsize;
fftHalf[row1][col1][0]= 0.5*(fht_pixels[row1*fftsize+col1] + fht_pixels[row2*fftsize+col2]);
fftHalf[row1][col1][1]= 0.5*(fht_pixels[row2*fftsize+col2] - fht_pixels[row1*fftsize+col1]);
}
}
return fftHalf;
}
/** converts FFT arrays of complex numbers of [fftsize/2+1][fftsize] to FHT arrays */
public float[] floatFFTHalf2FHT (double [][][] fft, int fftsize) {
float[] fht_pixels=new float [fftsize*fftsize];
int row1,row2,col1,col2;
for (row1=0;row1<=(fftsize>>1);row1++) {
row2=(fftsize-row1) %fftsize;
for (col1=0;col1 < fftsize;col1++) {
col2=(fftsize-col1) %fftsize;
fht_pixels[row1*fftsize+col1]=(float)(fft[row1][col1][0]-fft[row1][col1][1]);
fht_pixels[row2*fftsize+col2]=(float)(fft[row1][col1][0]+fft[row1][col1][1]);
}
}
return fht_pixels;
}
public double[] FFTHalf2FHT (double [][][] fft, int fftsize) {
double[] fht_pixels=new double [fftsize*fftsize];
int row1,row2,col1,col2;
for (row1=0;row1<=(fftsize>>1);row1++) {
row2=(fftsize-row1) %fftsize;
for (col1=0;col1 < fftsize;col1++) {
col2=(fftsize-col1) %fftsize;
fht_pixels[row1*fftsize+col1]=(double) (fft[row1][col1][0]-fft[row1][col1][1]);
fht_pixels[row2*fftsize+col2]=(double) (fft[row1][col1][0]+fft[row1][col1][1]);
}
}
return fht_pixels;
}
// Uses just first half and one line
public double[] FFTHalf2FHT (double [][]fft, int fftsize) {
double[] fht_pixels=new double [fftsize*fftsize];
int row1,row2,col1,col2;
for (row1=0;row1<=(fftsize>>1);row1++) {
row2=(fftsize-row1) %fftsize;
for (col1=0;col1 < fftsize;col1++) {
col2=(fftsize-col1) %fftsize;
fht_pixels[row1*fftsize+col1]=(double) (fft[0][row1*fftsize+col1]-fft[1][row1*fftsize+col1]);
fht_pixels[row2*fftsize+col2]=(double) (fft[0][row1*fftsize+col1]+fft[1][row1*fftsize+col1]);
}
}
return fht_pixels;
}
/** Amplitude/phase related methods */
public double [] interpolateFHT (double [] fht0, // first FHT array
double [] fht1, // second FHT array
double ratio){ // array of interpolation points - 0.0 - fht0, 1.0 - fht1
double [] points={ratio};
double [][] results= interpolateFHT (fht0, // first FHT array
fht1, // second FHT array
points, // array of interpolation points - 0.0 - fht0, 1.0 - fht1
true); // do not clone 0.0 and 1.0 (ends)
return results[0];
}
/** returns array of interpolated FHTs between fht0 and fht1, endpoints if present (0.0, 1.0) are referenced, not cloned */
public double [][] interpolateFHT (
double [] fht0, // first FHT array
double [] fht1, // second FHT array
double [] points){ // array of interpolation points - 0.0 - fht0, 1.0 - fht1
return interpolateFHT (fht0, // first FHT array
fht1, // second FHT array
points, // array of interpolation points - 0.0 - fht0, 1.0 - fht1
false); // do not clone 0.0 and 1.0 (ends)
}
public double [][] interpolateFHT (double [] fht0, // first FHT array
double [] fht1, // second FHT array
double [] points, // array of interpolation points - 0.0 - fht0, 1.0 - fht1
boolean cloneTrivial ){
double [] fht_div=divide(fht1,fht0);
int size=(int) Math.sqrt(fht0.length);
int hsize=size/2;
double [][][] aphase= new double[hsize+1][size][2];
double [][][] amp01= new double[hsize+1][size][2]; /** squared amplitudes of fht0 and fht1 */
double [][] phase= new double[hsize+1][size]; /** +/-pi phase of the first array */
double[][][]fft0= FHT2FFTHalf (fht0, size);
double[][][]fft1= FHT2FFTHalf (fht1, size);
double[][][]fft_div= FHT2FFTHalf (fht_div, size);
int i,j,k;
double a,c,p;
/** use mul for amplitudes, div - for phases */
for (i=0;i<=hsize;i++) for (j=0;j<size;j++) {
amp01[i][j][0]= fft0[i][j][0]*fft0[i][j][0]+fft0[i][j][1]*fft0[i][j][1];
amp01[i][j][1]= fft1[i][j][0]*fft1[i][j][0]+fft1[i][j][1]*fft1[i][j][1];
if (amp01[i][j][0]>0.0) phase[i][j]=Math.atan2(fft0[i][j][1],fft0[i][j][0]);
else phase[i][j]=0.0;
aphase[i][j][0]=amp01[i][j][0]*amp01[i][j][1]; // product of squared amplitudes is OK for phase restorations, we just need to know where amplitudes are higher
a= fft_div[i][j][0]*fft_div[i][j][0]+fft_div[i][j][1]*fft_div[i][j][1];
if (a>0.0) aphase[i][j][1]=Math.atan2(fft_div[i][j][1],fft_div[i][j][0]);
else aphase[i][j][1]=0.0;
}
aphase[0][0][1]=0.0;
/** calculate full phases */
fullPhase(aphase);
double [][]result=new double[points.length][];
for (k=0;k<result.length;k++) {
if (points[k]==0.0) result[k]=cloneTrivial?fht0.clone():fht0;
else if (points[k]==1.0) result[k]=cloneTrivial?fht1.clone():fht1;
else { /** interpolate */
c=points[k];
for (i=0;i<=hsize;i++) for (j=0;j<size;j++) {
if ((amp01[i][j][0]==0.0) || (amp01[i][j][1]==0.0)) a=0.0;
/** Extrapolation is defined here only in the direction of decreasing of the spectral amplitudes (outside, to the wider PSF), so additional limit to prevent division of small values */
/** Seems to work, possible improvements: 1-filter spectrum in high-freq areas. 2 - use farther inner points for farther approximation */
else if ((c<0.0) && (amp01[i][j][0]>amp01[i][j][1])) a=Math.sqrt(amp01[i][j][0]);
else if ((c>1.0) && (amp01[i][j][0]<amp01[i][j][1])) a=Math.sqrt(amp01[i][j][1]);
else a= Math.pow(amp01[i][j][0],0.5*(1.0-c))*Math.pow(amp01[i][j][1],0.5*c);
p= phase[i][j]+c*aphase[i][j][1];
fft0[i][j][0]=a*Math.cos(p);
fft0[i][j][1]=a*Math.sin(p);
}
result[k]=FFTHalf2FHT(fft0,size);
}
}
return result;
}
/**
*
* @param fht FHT data
* @param fullCentered - if false - returns minimal (size*(size/2-1) arrays, if true - fills the full square arrays and swaps quadrants
* @return 2-d array, first index: 0 - amplitude, 1 - phase. second index range depends on fullCentered
*/
public double [][] fht2AmpHase(double [] fht, boolean fullCentered){
int size=(int) Math.sqrt(fht.length);
int hsize=size/2;
double[][][]fft= FHT2FFTHalf (fht, size);
double [][]aphase=new double [2][size*(fullCentered?size:(hsize+1))];
int index=0;
double a;
for (int i=0;i<=hsize;i++) for (int j=0;j<size;j++){
a=Math.sqrt(fft[i][j][0]*fft[i][j][0]+fft[i][j][1]*fft[i][j][1]);
aphase[0][index]=a;
aphase[1][index++]=(a>0.0)?Math.atan2(fft[i][j][1],fft[i][j][0]):0.0;
}
if (fullCentered) {
for (int i=1;i<hsize;i++) for (int j=0;j<size;j++){
int j1=(size-j)%size;
aphase[0][(size-i)*size+j]= aphase[0][i*size+j1];
aphase[1][(size-i)*size+j]=-aphase[1][i*size+j1];
}
swapQuadrants(aphase[0]);
swapQuadrants(aphase[1]);
}
return aphase;
}
public double [][] fht2ReIm(double [] fht, boolean fullCentered){
int size=(int) Math.sqrt(fht.length);
int hsize=size/2;
double[][][]fft= FHT2FFTHalf (fht, size);
double [][]reIm=new double [2][size*(fullCentered?size:(hsize+1))];
int index=0;
for (int i=0;i<=hsize;i++) for (int j=0;j<size;j++){
reIm[0][index]=fft[i][j][0];
reIm[1][index++]=fft[i][j][1];
}
if (fullCentered) {
for (int i=1;i<hsize;i++) for (int j=0;j<size;j++){
int j1=(size-j)%size;
reIm[0][(size-i)*size+j]= reIm[0][i*size+j1];
reIm[1][(size-i)*size+j]=-reIm[1][i*size+j1];
}
swapQuadrants(reIm[0]);
swapQuadrants(reIm[1]);
}
return reIm;
}
/** replace +/-pi phase with the full phase, using amplitude to guide grouth of the covered area, so amplitude and phase does not need to be a pair from the same FFT array */
public void fullPhase(double [][][] aphase) {
int size = aphase[0].length;
int hsize=aphase.length-1;
boolean [][] map= new boolean[hsize+1][size];
int i,j;
aphase[0][0][1]=0.0;
int ix,iy,ix1,iy1,ix1n,iy1n;
List <Integer> pixelList=new ArrayList<Integer>(100);
Integer Index;
int [][] dirs={{-1,0},{-1,-1},{0,-1},{1,-1},{1,0},{1,1},{0,1},{-1,1}};
ix=0;
iy=0;
int clusterSize=0;
boolean noNew=true;
int maxX=0;
int maxY=0;
int oldX=0;
int oldY=0;
boolean oldConj=false;
int listIndex;
Index=iy*size + ix;
pixelList.clear();
pixelList.add (Index);
clusterSize++;
map[iy][ix]=true;
noNew=true;
double phase, oldPhase, fullCyclesPhase;
double maxValue=-1.0;
while (pixelList.size()>0) {
/** Find maximal new neighbor */
maxValue=-1.0;
listIndex=0;
while (listIndex<pixelList.size()) {
Index=pixelList.get(listIndex);
iy=Index/size;
ix=Index%size;
noNew=true;
for (j=0;j<8;j++) {
ix1=(ix+dirs[j][0]+size) % size;
iy1=(iy+dirs[j][1]+size) % size;
if ((iy1>hsize) || (((iy1==0) || (iy1==hsize)) && (ix1> hsize))) {
ix1n=(size-ix1)%size;
iy1n=(size-iy1)%size;
} else { /** But phase will be opposite sign */
ix1n=ix1;
iy1n=iy1;
}
if (!map[iy1n][ix1n]) {
noNew=false;
if (aphase[iy1n][ix1n][0]>maxValue) {
maxValue= aphase[iy1n][ix1n][0];
maxX=ix1n;
maxY=iy1n;
// if (DEBUG_LEVEL>4) System.out.println(" amplPhase(): iy="+iy+ " ix="+ix+" maxY="+maxY+" maxX="+maxX);
}
}
}
if (noNew) pixelList.remove(listIndex); // remove current list element
else listIndex++; // increase list index
}
if (pixelList.size()==0) break;
/** To calculate the phase - find already processed neighbor with the highest amplitude */
maxValue=-1.0;
for (j=0;j<8;j++) {
ix1=(maxX+dirs[j][0]+size) % size;
iy1=(maxY+dirs[j][1]+size) % size;
if ((iy1>hsize) || (((iy1==0) || (iy1==hsize)) && (ix1> hsize))) {
ix1n=(size-ix1)%size;
iy1n=(size-iy1)%size;
} else { /** But phase will be opposite sign */
ix1n=ix1;
iy1n=iy1;
}
if (map[iy1n][ix1n]) {
if (aphase[iy1n][ix1n][0]>maxValue) {
maxValue= aphase[iy1n][ix1n][0];
oldX=ix1n;
oldY=iy1n;
oldConj=(iy1!=iy1n) || (ix1!=ix1n); // point on the other half (conjugate)
}
}
}
/** Calculate the phase from the closest neighbor */
oldPhase=(oldConj?-1:1)*aphase[oldY][oldX][1];
fullCyclesPhase=2*Math.PI*Math.floor(oldPhase/(2*Math.PI)+0.5);
oldPhase-=fullCyclesPhase; // +/- pi
phase=aphase[maxY][maxX][1];
if ((phase - oldPhase) > Math.PI) fullCyclesPhase-=2*Math.PI;
else if ((oldPhase - phase) > Math.PI) fullCyclesPhase+=2*Math.PI;
aphase[maxY][maxX][1]+=fullCyclesPhase;
/* if (DEBUG_LEVEL>3) {
System.out.println(" amplPhase():Old:["+oldConj+"] ("+pixelList.size()+") "+oldX+":"+oldY+" "+IJ.d2s(aphase[oldY][oldX][0],2)+":"+ IJ.d2s(aphase[oldY][oldX][1],2)+
" New:"+maxX+":"+maxY+" "+IJ.d2s(aphase[maxY][maxX][0],2)+":"+ IJ.d2s(aphase[maxY][maxX][1],2)+
" Diff="+IJ.d2s((aphase[maxY][maxX][1]-(oldConj?-1:1)*aphase[oldY][oldX][1]),2));
}
*/
/** Add this new point to the list */
Index=maxY*size + maxX;
pixelList.add (Index);
clusterSize++;
map[maxY][maxX]=true;
} // end of while (pixelList.size()>0)
/** Fix remaining phases for y=0 and y=hsize */
for (i=1;i<hsize;i++) {
aphase[0][size-i][1]=-aphase[0][i][1];
aphase[hsize][size-i][1]=-aphase[hsize][i][1];
}
}
}
/**
* The code below is extracted form ImageJ plugin GaussianBlur.java, stripped of ImageProcessor and used (double) instead of (float) arrays.
* The following are notes from the original file:
*
*
* This plug-in filter uses convolution with a Gaussian function for smoothing.
* 'Radius' means the radius of decay to exp(-0.5) ~ 61%, i.e. the standard
* deviation sigma of the Gaussian (this is the same as in Photoshop, but
* different from the previous ImageJ function 'Gaussian Blur', where a value
* 2.5 times as much has to be entered.
* - Like all convolution operations in ImageJ, it assumes that out-of-image
* pixels have a value equal to the nearest edge pixel. This gives higher
* weight to edge pixels than pixels inside the image, and higher weight
* to corner pixels than non-corner pixels at the edge. Thus, when smoothing
* with very high blur radius, the output will be dominated by the edge
* pixels and especially the corner pixels (in the extreme case, with
* a blur radius of e.g. 1e20, the image will be raplaced by the average
* of the four corner pixels).
* - For increased speed, except for small blur radii, the lines (rows or
* columns of the image) are downscaled before convolution and upscaled
* to their original length thereafter.
*
* Version 03-Jun-2007 M. Schmid with preview, progressBar stack-aware,
* snapshot via snapshot flag; restricted range for resetOutOfRoi
*
*/
public class DoubleGaussianBlur {
/** the standard deviation of the Gaussian*/
// private static double sigma = 2.0;
/** whether sigma is given in units corresponding to the pixel scale (not pixels)*/
// private static boolean sigmaScaled = false;
/** The flags specifying the capabilities and needs */
// private int flags = DOES_ALL|SUPPORTS_MASKING|PARALLELIZE_STACKS|KEEP_PREVIEW;
// private ImagePlus imp; // The ImagePlus of the setup call, needed to get the spatial calibration
// private boolean hasScale = false; // whether the image has an x&y scale
private int nPasses = 1; // The number of passes (filter directions * color channels * stack slices)
// private int nChannels = 1; // The number of color channels
private int pass; // Current pass
/** Default constructor */
public DoubleGaussianBlur() {
}
public void blurDouble(double[] pixels,
int width,
int height,
double sigmaX,
double sigmaY,
double accuracy) {
if (sigmaX > 0)
blur1Direction(pixels, width,height, sigmaX, accuracy, true);
if (Thread.currentThread().isInterrupted()) return; // interruption for new parameters during preview?
if (sigmaY > 0)
blur1Direction(pixels, width,height, sigmaY, accuracy, false);
return;
}
/** Blur an image in one direction (x or y) by a Gaussian.
* @param ip The Image with the original data where also the result will be stored
* @param sigma Standard deviation of the Gaussian
* @param accuracy Accuracy of kernel, should not be > 0.02
* @param xDirection True for blurring in x direction, false for y direction
* @param extraLines Number of lines (parallel to the blurring direction)
* below and above the roi bounds that should be processed.
*/
public void blur1Direction(double [] pixels,
int width,
int height,
double sigma,
double accuracy,
boolean xDirection
// int extraLines
) {
final int UPSCALE_K_RADIUS = 2; //number of pixels to add for upscaling
final double MIN_DOWNSCALED_SIGMA = 4.; //minimum standard deviation in the downscaled image
// float[] pixels = (float[])ip.getPixels();
// int width = ip.getWidth();
// int height = ip.getHeight();
// Rectangle roi = ip.getRoi();
int length = xDirection ? width : height; //number of points per line (line can be a row or column)
int pointInc = xDirection ? 1 : width; //increment of the pixels array index to the next point in a line
int lineInc = xDirection ? width : 1; //increment of the pixels array index to the next line
// int lineFrom = (xDirection ? roi.y : roi.x) - extraLines; //the first line to process
// if (lineFrom < 0) lineFrom = 0;
int lineFrom = 0; //the first line to process
// int lineTo = (xDirection ? roi.y+roi.height : roi.x+roi.width) + extraLines; //the last line+1 to process
// if (lineTo > (xDirection ? height:width)) lineTo = (xDirection ? height:width);
int lineTo = (xDirection ? height:width);
// int writeFrom = xDirection? roi.x : roi.y; //first point of a line that needs to be written
// int writeTo = xDirection ? roi.x+roi.width : roi.y+roi.height;
int writeFrom = 0; //first point of a line that needs to be written
int writeTo = xDirection ? width : height;
// int inc = Math.max((lineTo-lineFrom)/(100/(nPasses>0?nPasses:1)+1),20);
pass++;
if (pass>nPasses) pass =1;
// Thread thread = Thread.currentThread(); // needed to check for interrupted state
if (sigma > 2*MIN_DOWNSCALED_SIGMA + 0.5) {
/* large radius (sigma): scale down, then convolve, then scale up */
int reduceBy = (int)Math.floor(sigma/MIN_DOWNSCALED_SIGMA); //downscale by this factor
if (reduceBy > length) reduceBy = length;
/* Downscale gives std deviation sigma = 1/sqrt(3); upscale gives sigma = 1/2. (in downscaled pixels) */
/* All sigma^2 values add to full sigma^2 */
double sigmaGauss = Math.sqrt(sigma*sigma/(reduceBy*reduceBy) - 1./3. - 1./4.);
int maxLength = (length+reduceBy-1)/reduceBy + 2*(UPSCALE_K_RADIUS + 1); //downscaled line can't be longer
double[][] gaussKernel = makeGaussianKernel(sigmaGauss, accuracy, maxLength);
int kRadius = gaussKernel[0].length*reduceBy; //Gaussian kernel radius after upscaling
int readFrom = (writeFrom-kRadius < 0) ? 0 : writeFrom-kRadius; //not including broadening by downscale&upscale
int readTo = (writeTo+kRadius > length) ? length : writeTo+kRadius;
int newLength = (readTo-readFrom+reduceBy-1)/reduceBy + 2*(UPSCALE_K_RADIUS + 1);
int unscaled0 = readFrom - (UPSCALE_K_RADIUS + 1)*reduceBy; //input point corresponding to cache index 0
//IJ.log("reduce="+reduceBy+", newLength="+newLength+", unscaled0="+unscaled0+", sigmaG="+(float)sigmaGauss+", kRadius="+gaussKernel[0].length);
double[] downscaleKernel = makeDownscaleKernel(reduceBy);
double[] upscaleKernel = makeUpscaleKernel(reduceBy);
double[] cache1 = new double[newLength]; //holds data after downscaling
double[] cache2 = new double[newLength]; //holds data after convolution
int pixel0 = lineFrom*lineInc;
for (int line=lineFrom; line<lineTo; line++, pixel0+=lineInc) {
downscaleLine(pixels, cache1, downscaleKernel, reduceBy, pixel0, unscaled0, length, pointInc, newLength);
convolveLine(cache1, cache2, gaussKernel, 0, newLength, 1, newLength-1, 0, 1);
upscaleLine(cache2, pixels, upscaleKernel, reduceBy, pixel0, unscaled0, writeFrom, writeTo, pointInc);
}
} else {
/* small radius: normal convolution */
double[][] gaussKernel = makeGaussianKernel(sigma, accuracy, length);
int kRadius = gaussKernel[0].length;
double[] cache = new double[length]; //input for convolution, hopefully in CPU cache
int readFrom = (writeFrom-kRadius < 0) ? 0 : writeFrom-kRadius;
int readTo = (writeTo+kRadius > length) ? length : writeTo+kRadius;
int pixel0 = lineFrom*lineInc;
for (int line=lineFrom; line<lineTo; line++, pixel0+=lineInc) {
int p = pixel0 + readFrom*pointInc;
for (int i=readFrom; i<readTo; i++ ,p+=pointInc)
cache[i] = pixels[p];
convolveLine(cache, pixels, gaussKernel, readFrom, readTo, writeFrom, writeTo, pixel0, pointInc);
}
}
return;
}
/** Create a 1-dimensional normalized Gaussian kernel with standard deviation sigma
* and the running sum over the kernel
* Note: this is one side of the kernel only, not the full kernel as used by the
* Convolver class of ImageJ.
* To avoid a step due to the cutoff at a finite value, the near-edge values are
* replaced by a 2nd-order polynomial with its minimum=0 at the first out-of-kernel
* pixel. Thus, the kernel function has a smooth 1st derivative in spite of finite
* length.
*
* @param sigma Standard deviation, i.e. radius of decay to 1/sqrt(e), in pixels.
* @param accuracy Relative accuracy; for best results below 0.01 when processing
* 8-bit images. For short or float images, values of 1e-3 to 1e-4
* are better (but increase the kernel size and thereby the
* processing time). Edge smoothing will fail with very poor
* accuracy (above approx. 0.02)
* @param maxRadius Maximum radius of the kernel: Limits kernel size in case of
* large sigma, should be set to image width or height. For small
* values of maxRadius, the kernel returned may have a larger
* radius, however.
* @return A 2*n array. Array[0][n] is the kernel, decaying towards zero,
* which would be reached at kernel.length (unless kernel size is
* limited by maxRadius). Array[1][n] holds the sum over all kernel
* values > n, including non-calculated values in case the kernel
* size is limited by <code>maxRadius</code>.
*/
public double[][] makeGaussianKernel(double sigma, double accuracy, int maxRadius) {
int kRadius = (int)Math.ceil(sigma*Math.sqrt(-2*Math.log(accuracy)))+1;
if (maxRadius < 50) maxRadius = 50; // too small maxRadius would result in inaccurate sum.
if (kRadius > maxRadius) kRadius = maxRadius;
double[][] kernel = new double[2][kRadius];
for (int i=0; i<kRadius; i++) // Gaussian function
kernel[0][i] = (double)(Math.exp(-0.5*i*i/sigma/sigma));
if (kRadius < maxRadius && kRadius > 3) { // edge correction
double sqrtSlope = Double.MAX_VALUE;
int r = kRadius;
while (r > kRadius/2) {
r--;
double a = Math.sqrt(kernel[0][r])/(kRadius-r);
if (a < sqrtSlope)
sqrtSlope = a;
else
break;
}
for (int r1 = r+2; r1 < kRadius; r1++)
kernel[0][r1] = (double)((kRadius-r1)*(kRadius-r1)*sqrtSlope*sqrtSlope);
}
double sum; // sum over all kernel elements for normalization
if (kRadius < maxRadius) {
sum = kernel[0][0];
for (int i=1; i<kRadius; i++)
sum += 2*kernel[0][i];
} else
sum = sigma * Math.sqrt(2*Math.PI);
double rsum = 0.5 + 0.5*kernel[0][0]/sum;
for (int i=0; i<kRadius; i++) {
double v = (kernel[0][i]/sum);
kernel[0][i] = (double)v;
rsum -= v;
kernel[1][i] = (double)rsum;
//IJ.log("k["+i+"]="+(float)v+" sum="+(float)rsum);
}
return kernel;
}
/** Scale a line (row or column of a FloatProcessor or part thereof)
* down by a factor <code>reduceBy</code> and write the result into
* <code>cache</code>.
* Input line pixel # <code>unscaled0</code> will correspond to output
* line pixel # 0. <code>unscaled0</code> may be negative. Out-of-line
* pixels of the input are replaced by the edge pixels.
*/
void downscaleLine(double[] pixels, double[] cache, double[] kernel,
int reduceBy, int pixel0, int unscaled0, int length, int pointInc, int newLength) {
double first = pixels[pixel0];
double last = pixels[pixel0 + pointInc*(length-1)];
int xin = unscaled0 - reduceBy/2;
int p = pixel0 + pointInc*xin;
for (int xout=0; xout<newLength; xout++) {
double v = 0;
for (int x=0; x<reduceBy; x++, xin++, p+=pointInc) {
v += kernel[x] * ((xin-reduceBy < 0) ? first : ((xin-reduceBy >= length) ? last : pixels[p-pointInc*reduceBy]));
v += kernel[x+reduceBy] * ((xin < 0) ? first : ((xin >= length) ? last : pixels[p]));
v += kernel[x+2*reduceBy] * ((xin+reduceBy < 0) ? first : ((xin+reduceBy >= length) ? last : pixels[p+pointInc*reduceBy]));
}
cache[xout] = v;
}
}
/* Create a kernel for downscaling. The kernel function preserves
* norm and 1st moment (i.e., position) and has fixed 2nd moment,
* (in contrast to linear interpolation).
* In scaled space, the length of the kernel runs from -1.5 to +1.5,
* and the standard deviation is 1/2.
* Array index corresponding to the kernel center is
* unitLength*3/2
*/
double[] makeDownscaleKernel (int unitLength) {
int mid = unitLength*3/2;
double[] kernel = new double[3*unitLength];
for (int i=0; i<=unitLength/2; i++) {
double x = i/(double)unitLength;
double v = (double)((0.75-x*x)/unitLength);
kernel[mid-i] = v;
kernel[mid+i] = v;
}
for (int i=unitLength/2+1; i<(unitLength*3+1)/2; i++) {
double x = i/(double)unitLength;
double v = (double)((0.125 + 0.5*(x-1)*(x-2))/unitLength);
kernel[mid-i] = v;
kernel[mid+i] = v;
}
return kernel;
}
/** Scale a line up by factor <code>reduceBy</code> and write as a row
* or column (or part thereof) to the pixels array of a FloatProcessor.
*/
void upscaleLine (double[] cache, double[] pixels, double[] kernel,
int reduceBy, int pixel0, int unscaled0, int writeFrom, int writeTo, int pointInc) {
int p = pixel0 + pointInc*writeFrom;
for (int xout = writeFrom; xout < writeTo; xout++, p+=pointInc) {
int xin = (xout-unscaled0+reduceBy-1)/reduceBy; //the corresponding point in the cache (if exact) or the one above
int x = reduceBy - 1 - (xout-unscaled0+reduceBy-1)%reduceBy;
pixels[p] = cache[xin-2]*kernel[x]
+ cache[xin-1]*kernel[x+reduceBy]
+ cache[xin]*kernel[x+2*reduceBy]
+ cache[xin+1]*kernel[x+3*reduceBy];
}
}
/** Create a kernel for upscaling. The kernel function is a convolution
* of four unit squares, i.e., four uniform kernels with value +1
* from -0.5 to +0.5 (in downscaled coordinates). The second derivative
* of this kernel is smooth, the third is not. Its standard deviation
* is 1/sqrt(3) in downscaled cordinates.
* The kernel runs from [-2 to +2[, corresponding to array index
* 0 ... 4*unitLength (whereby the last point is not in the array any more).
*/
double[] makeUpscaleKernel (int unitLength) {
double[] kernel = new double[4*unitLength];
int mid = 2*unitLength;
kernel[0] = 0;
for (int i=0; i<unitLength; i++) {
double x = i/(double)unitLength;
double v = (double)((2./3. -x*x*(1-0.5*x)));
kernel[mid+i] = v;
kernel[mid-i] = v;
}
for (int i=unitLength; i<2*unitLength; i++) {
double x = i/(double)unitLength;
double v = (double)((2.-x)*(2.-x)*(2.-x)/6.);
kernel[mid+i] = v;
kernel[mid-i] = v;
}
return kernel;
}
/** Convolve a line with a symmetric kernel and write to a separate array,
* possibly the pixels array of a FloatProcessor (as a row or column or part thereof)
*
* @param input Input array containing the line
* @param pixels Float array for output, can be the pixels of a FloatProcessor
* @param kernel "One-sided" kernel array, kernel[0][n] must contain the kernel
* itself, kernel[1][n] must contain the running sum over all
* kernel elements from kernel[0][n+1] to the periphery.
* The kernel must be normalized, i.e. sum(kernel[0][n]) = 1
* where n runs from the kernel periphery (last element) to 0 and
* back. Normalization should include all kernel points, also these
* not calculated because they are not needed.
* @param readFrom First array element of the line that must be read.
* <code>writeFrom-kernel.length</code> or 0.
* @param readTo Last array element+1 of the line that must be read.
* <code>writeTo+kernel.length</code> or <code>input.length</code>
* @param writeFrom Index of the first point in the line that should be written
* @param writeTo Index+1 of the last point in the line that should be written
* @param point0 Array index of first element of the 'line' in pixels (i.e., lineNumber * lineInc)
* @param pointInc Increment of the pixels array index to the next point (for an ImageProcessor,
* it should be <code>1</code> for a row, <code>width</code> for a column)
*/
public void convolveLine(double[] input, double[] pixels, double[][] kernel, int readFrom,
int readTo, int writeFrom, int writeTo, int point0, int pointInc) {
int length = input.length;
double first = input[0]; //out-of-edge pixels are replaced by nearest edge pixels
double last = input[length-1];
double[] kern = kernel[0]; //the kernel itself
double kern0 = kern[0];
double[] kernSum = kernel[1]; //the running sum over the kernel
int kRadius = kern.length;
int firstPart = kRadius < length ? kRadius : length;
int p = point0 + writeFrom*pointInc;
int i = writeFrom;
for (; i<firstPart; i++,p+=pointInc) { //while the sum would include pixels < 0
double result = input[i]*kern0;
result += kernSum[i]*first;
if (i+kRadius>length) result += kernSum[length-i-1]*last;
for (int k=1; k<kRadius; k++) {
double v = 0;
if (i-k >= 0) v += input[i-k];
if (i+k<length) v+= input[i+k];
result += kern[k] * v;
}
pixels[p] = result;
}
int iEndInside = length-kRadius<writeTo ? length-kRadius : writeTo;
for (;i<iEndInside;i++,p+=pointInc) { //while only pixels within the line are be addressed (the easy case)
double result = input[i]*kern0;
for (int k=1; k<kRadius; k++)
result += kern[k] * (input[i-k] + input[i+k]);
pixels[p] = result;
}
for (; i<writeTo; i++,p+=pointInc) { //while the sum would include pixels >= length
double result = input[i]*kern0;
if (i<kRadius) result += kernSum[i]*first;
if (i+kRadius>=length) result += kernSum[length-i-1]*last;
for (int k=1; k<kRadius; k++) {
double v = 0;
if (i-k >= 0) v += input[i-k];
if (i+k<length) v+= input[i+k];
result += kern[k] * v;
}
pixels[p] = result;
}
}
}
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
/**
** -----------------------------------------------------------------------------**
** EyesisCorrections.java
**
** Aberration correction for Eyesis4pi
**
**
** Copyright (C) 2012 Elphel, Inc.
**
** -----------------------------------------------------------------------------**
**
** EyesisCorrections.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/>.
** -----------------------------------------------------------------------------**
**
*/
import ij.CompositeImage;
import ij.IJ;
import ij.ImagePlus;
import ij.ImageStack;
import ij.Prefs;
import ij.gui.GenericDialog;
import ij.io.FileInfo;
import ij.io.FileSaver;
import ij.process.ColorProcessor;
import ij.process.FloatProcessor;
import ij.process.ImageProcessor;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicInteger;
import loci.common.services.DependencyException;
import loci.common.services.ServiceException;
import loci.formats.FormatException;
public class EyesisCorrections {
JP46_Reader_camera JP4_INSTANCE= new JP46_Reader_camera(false);
showDoubleFloatArrays SDFA_INSTANCE= new showDoubleFloatArrays();
DebayerScissors debayerScissors=null;
public AtomicInteger stopRequested=null; // 1 - stop now, 2 - when convenient
public PixelMapping pixelMapping=null;
public EyesisCorrectionParameters.CorrectionParameters correctionsParameters=null;
public boolean [] usedChannels;
public float [][] channelVignettingCorrection=null;
public int [][] channelWidthHeight=null;
public ImagePlus [] imageNoiseGains=null;
public String [] sharpKernelPaths=null;
public String [] smoothKernelPaths=null;
public int debugLevel;
public String [] stackColorNames= {"Red","Green","Blue"};
public int psfSubpixelShouldBe4=4; // sub-pixel decimation
public long startTime=0;
// public boolean BUG_subchannel=true; // top channel - 1, middle - 0, bottom - 2 (should be -0-1-2)
// public boolean BUG_subchannel=false; // top channel - 1, middle - 0, bottom - 2 (should be -0-1-2)
public EyesisCorrections (
AtomicInteger stopRequested,
EyesisCorrectionParameters.CorrectionParameters correctionsParameters
){
this.correctionsParameters=correctionsParameters;
this.stopRequested=stopRequested;
}
public void setDebug(int debugLevel){
this.debugLevel=debugLevel;
}
public int getNumChannels(){return (this.usedChannels!=null)?this.usedChannels.length:0;}
// TODO: preserve some data when re-running with new source files
public void initSensorFiles(int debugLevel){
this.sharpKernelPaths=null;
this.smoothKernelPaths=null;
String [] sensorPaths=correctionsParameters.selectSensorFiles(this.debugLevel);
this.pixelMapping=new PixelMapping(sensorPaths,debugLevel);
this.usedChannels= usedChannels(correctionsParameters.getSourcePaths());
// TODO: Combine with additional channel map to be able to select single image (of all 3)
if (correctionsParameters.removeUnusedSensorData){
for (int nChn=0;nChn< this.usedChannels.length; nChn++) if (!this.usedChannels[nChn]) this.pixelMapping.removeChannel(nChn);
}
int numUsedChannels=0;
for (int nChn=0;nChn< this.usedChannels.length; nChn++) if (this.usedChannels[nChn]) numUsedChannels++;
if (this.debugLevel>0) {
String sChannels="";
for (int nChn=0;nChn< this.usedChannels.length; nChn++) if (this.usedChannels[nChn]) sChannels+=" "+nChn;
System.out.println ("Number of used channels: "+numUsedChannels+" ("+sChannels+" )");
}
createChannelVignetting();
if ((this.debugLevel>1) && (correctionsParameters.sourcePaths!=null) && (correctionsParameters.sourcePaths.length>0)) {
testFF(correctionsParameters.sourcePaths[0]);
// this.channelVignettingCorrection[srcChannel]=this.pixelMapping.getBayerFlatFieldFloat(
/*
SDFA_INSTANCE.showArrays(
this.channelVignettingCorrection,
this.channelWidthHeight[srcChannel][0],
this.channelWidthHeight[srcChannel][1],
true,
"Flat-Field");
//LENS_DISTORTIONS.displayGridTitles());
*/
}
}
public double [] calcReferenceExposures(int debugLevel){
String [] paths=this.correctionsParameters.getSourcePaths();
double [] exposures=new double [paths.length];
if (this.correctionsParameters.exposureCorrectionMode<2){
for (int nFile=0;nFile<paths.length;nFile++) {
exposures[nFile]=(this.correctionsParameters.exposureCorrectionMode>0)?this.correctionsParameters.referenceExposure:Double.NaN;
}
} else {
ImagePlus imp; // using that composite image has same exposure
for (int nFile=0;nFile<paths.length;nFile++){
if (this.correctionsParameters.isJP4()){
imp=JP4_INSTANCE.open(
"", // path,
paths[nFile],
"", //arg - not used in JP46 reader
true, // un-apply camera color gains
null, // new window
false); // do not show
} else {
imp=new ImagePlus(paths[nFile]);
if (imp==null){
String msg="Failed to open source image file "+paths[nFile];
System.out.println("Error "+msg);
IJ.showMessage("Error",msg);
break;
}
// (new JP46_Reader_camera(false)).decodeProperiesFromInfo(imp_src); // decode existent properties from info
JP4_INSTANCE.decodeProperiesFromInfo(imp); // decode existent properties from info
}
if (imp.getProperty("EXPOSURE")!=null){
exposures[nFile]=Double.parseDouble((String)imp.getProperty("EXPOSURE"));
} else {
exposures[nFile]=Double.NaN;
}
if (debugLevel>1){
System.out.println((nFile+1)+": "+paths[nFile]+" - exposure="+exposures[nFile]);
}
}
String [] names=new String [paths.length];
for (int nFile=0;nFile<paths.length;nFile++) names[nFile]=this.correctionsParameters.getNameFromSourceTiff(paths[nFile]);
int [] firstImageIndex=new int [paths.length];
for (int nFile=0;nFile<paths.length;nFile++) {
firstImageIndex[nFile]=nFile;
for (int j=0;j<nFile;j++) if (names[j].equals(names[nFile])){
firstImageIndex[nFile]=j;
break;
}
}
double [][] minMaxExposure=new double[paths.length][2];
for (int nFile=0;nFile<paths.length;nFile++) {
minMaxExposure[nFile][0]=Double.NaN;
minMaxExposure[nFile][1]=Double.NaN;
}
for (int nFile=0;nFile<paths.length;nFile++) if (!Double.isNaN(exposures[nFile])){
int j=firstImageIndex[nFile];
if (Double.isNaN(minMaxExposure[j][0]) || (minMaxExposure[j][0]>exposures[nFile])) minMaxExposure[j][0]=exposures[nFile];
if (Double.isNaN(minMaxExposure[j][1]) || (minMaxExposure[j][1]<exposures[nFile])) minMaxExposure[j][1]=exposures[nFile];
}
for (int nFile=0;nFile<paths.length;nFile++) if (!Double.isNaN(exposures[nFile])){
int j=firstImageIndex[nFile];
exposures[nFile]=(1.0-this.correctionsParameters.relativeExposure)*minMaxExposure[j][0]+
this.correctionsParameters.relativeExposure*minMaxExposure[j][1];
}
}
// apply modes
return exposures;
}
public void rebuildEquirectangularMaps(
EyesisCorrectionParameters.EquirectangularParameters equirectangularParameters,
int threadsMax, // maximal number of threads to launch
boolean updateStatus,
int debugLevel){
this.sharpKernelPaths=null;
this.smoothKernelPaths=null;
String [] sensorPaths=correctionsParameters.selectSensorFiles(this.debugLevel);
String directory= correctionsParameters.selectEquirectangularDirectory(true,true);
if (directory==null) {
System.out.println ("No directory selected for equirectangular maps to save");
return;
}
// boolean processPlaneProjection= equirectangularParameters.generateCommonPlane &&
// equirectangularParameters.selectChannelsToProcess("Select channels for plane projection", this.pixelMapping.sensors.length);
this.pixelMapping=new PixelMapping(sensorPaths,debugLevel);
pixelMapping.generateAndSaveEquirectangularMaps(
correctionsParameters.equirectangularDirectory+
Prefs.getFileSeparator()+
correctionsParameters.equirectangularPrefix+
correctionsParameters.equirectangularSuffix,
equirectangularParameters.longitudeLeft,
equirectangularParameters.longitudeRight,
equirectangularParameters.latitudeTop,
equirectangularParameters.latitudeBottom,
equirectangularParameters.pixelsHorizontal,
equirectangularParameters.imageWidth, //int width,
equirectangularParameters.imageHeight, //int height,
equirectangularParameters.x0, //double x0,
equirectangularParameters.y0, //double y0,
1.0/equirectangularParameters.resolutionScale, //double pixelStep,
equirectangularParameters.longitudeWidth,
equirectangularParameters.clearFullMap,
equirectangularParameters.clearAllMaps,
threadsMax);
boolean processPlaneProjection= equirectangularParameters.generateCommonPlane &&
equirectangularParameters.selectChannelsToProcess("Select channels for plane projection", this.pixelMapping.sensors.length);
if (processPlaneProjection){
// equirectangularParameters.projectionPixelSize=pixelMapping.degreesPerPixel*Math.PI/180.0;
boolean [] channelMask= equirectangularParameters.getChannelsToProcess();
int numUsedChannels=0;
for (int i=0;i<channelMask.length;i++) if (channelMask[i]) numUsedChannels++;
int [] channelList=new int [numUsedChannels];
int iChannel=0;
for (int i=0;i<channelMask.length;i++) if (channelMask[i]) channelList[iChannel++]=i;
String sChannels="";
for (int i=0;i<channelList.length;i++) sChannels+=" "+channelList[i];
for (int i=0;i<channelList.length;i++) {
int channel=channelList[i];
if (!pixelMapping.isChannelAvailable(channel)){
String msg="No sensor data for channel "+channel;
System.out.println("Error "+msg);
IJ.showMessage("Error",msg);
return;
}
if (!pixelMapping.isEquirectangularMapAvailable(channel)){
String path=correctionsParameters.selectEquirectangularMapFile(
channel,
debugLevel);
if (path==null) {
String msg="No equirectangular map found for channel "+channel;
System.out.println("Error "+msg);
IJ.showMessage("Error",msg);
return;
}
if (debugLevel>1) System.out.println("rebuildEquirectangularMaps(): channel="+channel+" path="+path);
pixelMapping.loadChannelEquirectangularMap(
channel,
path);
if (!this.pixelMapping.isEquirectangularMapAvailable(channel)){
String msg="Failed to load equirectangular map for channel "+channel;
System.out.println("Error "+msg);
IJ.showMessage("Error",msg);
return;
}
}
}
String title="Projection_plane_map";
ImagePlus imp_pixelmap= pixelMapping.getPlaneToSensorsMap( // need to re-load equirectangular maps?
channelList, // int [] channels,
equirectangularParameters.projectionElevation, // Latitude (in degrees) of the normal to the projection plane
equirectangularParameters.projectionYaw, // Longitude (in degrees) of the normal to the projection plane
equirectangularParameters.projectionRoll, // Rotation of the projection plane around the perpendicular from the lens centers
equirectangularParameters.matchPixelSize?0.0: equirectangularParameters.projectionPixelSize, // Ratio of the plane pixel size to the distance from the lens center to the projection plane
equirectangularParameters.projectionWidth, // Width of the projection rectangle
equirectangularParameters.projectionHeight, // Height of the projection rectangle
equirectangularParameters.projectionCenterX, // X-coordinate (along the projection plane X - right) of the intersection of the projection plane with the perpendicular from the lens center
equirectangularParameters.projectionCenterY, // Y-coordinate (along the projection plane Y - down) of the intersection of the projection plane with the perpendicular from the lens center
equirectangularParameters.nominalHorizontalDisparity, // 60.0 - nominal distance between horizontal cameras, mm
title,
debugLevel
);
if (equirectangularParameters.matchPixelSize) {
equirectangularParameters.projectionPixelSize=Math.PI/180.0*pixelMapping.panoDegreesPerPixel;
if (debugLevel>0) System.out.println("rebuildEquirectangularMaps(): Setting equirectangularParameters.projectionPixelSize="+equirectangularParameters.projectionPixelSize);
}
if (imp_pixelmap!=null) {
if (debugLevel>2) {
imp_pixelmap.getProcessor().resetMinAndMax(); // imp_psf will be reused
imp_pixelmap.show();
}
FileSaver fs=new FileSaver(imp_pixelmap);
String resultPath=correctionsParameters.equirectangularDirectory+
Prefs.getFileSeparator()+correctionsParameters.planeMapPrefix+correctionsParameters.planeMapSuffix;
String msg="Saving pixel map to a common plane for sensors "+sChannels+": "+resultPath;
if (updateStatus) IJ.showStatus(msg);
if (debugLevel>0) System.out.println(msg);
fs.saveAsTiffStack(resultPath);
} else {
System.out.println("Failed to create pixel map for sensors "+sChannels);
}
}
}
public boolean updateImageNoiseGains(
EyesisCorrectionParameters.NonlinParameters nonlinParameters,
int fftSize, // 128 - fft size, kernel size should be size/2
int threadsMax, // maximal number of threads to launch
boolean updateStatus,
int globalDebugLevel){
boolean removeUnused=this.correctionsParameters.removeUnusedSensorData;
int numChannels=this.usedChannels.length;
if (this.imageNoiseGains==null){
this.imageNoiseGains= new ImagePlus[0];
}
if (this.imageNoiseGains.length!=numChannels){
ImagePlus [] tmp=this.imageNoiseGains.clone();
this.imageNoiseGains=new ImagePlus[numChannels];
for (int chn=0;chn<numChannels;chn++) this.imageNoiseGains[chn]=(chn<tmp.length)?tmp[chn]:null;
}
this.sharpKernelPaths=correctionsParameters.selectKernelChannelFiles(
0, // 0 - sharp, 1 - smooth
numChannels, // number of channels
this.debugLevel);
if (this.sharpKernelPaths==null) return false;
if (nonlinParameters.useDiffNoiseGains) {
this.smoothKernelPaths=correctionsParameters.selectKernelChannelFiles(
1, // 0 - sharp, 1 - smooth
numChannels, // number of channels
this.debugLevel);
if (this.smoothKernelPaths==null) return false;
}
for (int chn=0;chn<this.usedChannels.length;chn++){
if (this.usedChannels[chn] && (this.sharpKernelPaths[chn]!=null) && (!nonlinParameters.useDiffNoiseGains ||(this.smoothKernelPaths[chn]!=null))){
if (
(this.imageNoiseGains[chn]==null) ||
(!this.sharpKernelPaths[chn].equals((String) this.imageNoiseGains[chn].getProperty("sharpKernelPath"))) ||
(!this.smoothKernelPaths[chn].equals((String) this.imageNoiseGains[chn].getProperty("smoothKernelPath")))){
ImagePlus imp_kernel_sharp=new ImagePlus(this.sharpKernelPaths[chn]);
if (imp_kernel_sharp==null) {
System.out.println("Failed to open (sharp) kernel stack "+this.sharpKernelPaths[chn]);
this.sharpKernelPaths[chn]=null;
continue;
}
if (imp_kernel_sharp.getStackSize()<3) {
System.out.println("Need a 3-layer stack with kernels");
this.sharpKernelPaths[chn]=null;
continue;
}
ImageStack kernel_sharp_stack= imp_kernel_sharp.getStack();
ImageStack kernel_smooth_stack=null;
if (nonlinParameters.useDiffNoiseGains) {
ImagePlus imp_kernel_smooth=new ImagePlus(this.smoothKernelPaths[chn]);
if (imp_kernel_smooth==null) {
System.out.println("Failed to open (smooth) kernel stack "+this.smoothKernelPaths[chn]);
this.smoothKernelPaths[chn]=null;
continue;
}
if (imp_kernel_smooth.getStackSize()<3) {
System.out.println("Need a 3-layer stack with kernels");
this.smoothKernelPaths[chn]=null;
continue;
}
kernel_smooth_stack= imp_kernel_smooth.getStack();
}
ImageStack kernelsNoise=
calculateKernelsNoiseGains (
kernel_sharp_stack, //final ImageStack kernelStack1, // first stack with 3 colors/slices convolution kernels
kernel_smooth_stack, //final ImageStack kernelStack2, // second stack with 3 colors/slices convolution kernels (or null)
fftSize, //size, // 128 - fft size, kernel size should be size/2
nonlinParameters.blurSigma,
threadsMax, // maximal number of threads to launch
updateStatus,
globalDebugLevel);
kernel_sharp_stack= null; // TODO: - maybe keep one set to speed-up single-channel processing?
kernel_smooth_stack=null;
Runtime.getRuntime().gc();
String title="noiseGains_"+(nonlinParameters.useDiffNoiseGains?"diff_":"")+String.format("%02d",chn);
imageNoiseGains[chn]= new ImagePlus(title, kernelsNoise);
imageNoiseGains[chn].setProperty("sharpKernelPath", this.sharpKernelPaths[chn]);
imageNoiseGains[chn].setProperty("smoothKernelPath", nonlinParameters.useDiffNoiseGains?this.smoothKernelPaths[chn]:"");
if (this.correctionsParameters.saveNoiseGains || this.correctionsParameters.showNoiseGains) {
saveAndShow(this.imageNoiseGains[chn],
this.correctionsParameters,
this.correctionsParameters.saveNoiseGains,
this.correctionsParameters.showNoiseGains
);
}
}
} else {
if (removeUnused) this.imageNoiseGains[chn]=null;
}
if (this.stopRequested.get()>0) {
System.out.println("User requested stop");
return false;
}
}
return true;
}
public void createChannelVignetting(){
this.channelWidthHeight=new int [this.usedChannels.length][];
this.channelVignettingCorrection=new float [this.usedChannels.length][];
for (int nChn=0;nChn< this.usedChannels.length; nChn++){
this.channelWidthHeight[nChn]=null;
this.channelVignettingCorrection[nChn]=null;
}
int [][] bayer={{1,0},{2,1}}; // GR/BG
ImagePlus imp=null,imp_composite=null;
for (int nFile=0;nFile<correctionsParameters.getSourcePaths().length;nFile++){
int [] channels={correctionsParameters.getChannelFromSourceTiff(correctionsParameters.getSourcePaths()[nFile])};
if (correctionsParameters.isJP4()){
int subCamera= channels[0]- correctionsParameters.firstSubCamera; // to match those in the sensor files
channels=this.pixelMapping.channelsForSubCamera(subCamera);
}
if (channels!=null) {
imp=null;
imp_composite=null;
if (correctionsParameters.isJP4()){
imp_composite=JP4_INSTANCE.open(
"", // path,
correctionsParameters.getSourcePaths()[nFile],
"", //arg - not used in JP46 reader
true, // un-apply camera color gains
null, // new window
false); // do not show
} else imp=new ImagePlus(correctionsParameters.getSourcePaths()[nFile]);
if ((imp==null) && (imp_composite==null)) {
if (this.debugLevel>0) System.out.println("createChannelVignetting(): can not open "+correctionsParameters.getSourcePaths()[nFile]+
" as "+(correctionsParameters.isJP4()?"JP4":"TIFF")+" file");
continue;
}
for (int chn=0;chn<channels.length;chn++) {
int srcChannel=channels[chn];
if ((this.channelWidthHeight[srcChannel]==null) && this.pixelMapping.isChannelAvailable(srcChannel)){
int subChannel=this.pixelMapping.getSubChannel(srcChannel);
if (this.correctionsParameters.swapSubchannels01) {
switch (subChannel){
case 0: subChannel=1; break;
case 1: subChannel=0; break;
}
}
if (subChannel<0){
System.out.println("BUG in createChannelVignetting(): chn="+chn+
" subChannel="+subChannel+
" nFile="+nFile+" : "+correctionsParameters.getSourcePaths()[nFile]+
" channels.length="+channels.length);
for (int i=0;i<channels.length;i++) System.out.print(" "+channels[i]);
System.out.println();
for (int i=0;i<this.usedChannels.length;i++) if (this.usedChannels[i]) {
System.out.println(i+": subCamera="+this.pixelMapping.sensors[i].subcamera);
}
}
if (correctionsParameters.isJP4()) imp=JP4_INSTANCE.demuxImage(imp_composite, subChannel);
if (imp==null) imp=imp_composite; // not a composite image
int [] widthHeight={imp.getWidth(),imp.getHeight()};
this.channelWidthHeight[srcChannel]=widthHeight;
this.channelVignettingCorrection[srcChannel]=this.pixelMapping.getBayerFlatFieldFloat(
srcChannel,
this.channelWidthHeight[srcChannel][0],
this.channelWidthHeight[srcChannel][1],
bayer);
if (this.debugLevel>0){
System.out.println("Creating vignetting info for channel "+srcChannel+
" subchannel="+subChannel+" ("+
correctionsParameters.getSourcePaths()[nFile]+")");
System.out.println("imageWidth= "+this.channelWidthHeight[srcChannel][0]+" imageHeight="+this.channelWidthHeight[srcChannel][1]);
}
}
}
}
}
}
boolean [] usedChannels(String [] paths){
if (paths==null) paths=new String[0];
int numChannels=this.pixelMapping.getNumChannels();
boolean [] usedChannels=new boolean[numChannels];
for (int i=0;i<numChannels;i++) usedChannels[i]= false; // this.pixelMapping.isChannelAvailable(i);
for (int i=0;i<paths.length;i++){
int srcChannel=correctionsParameters.getChannelFromSourceTiff(paths[i]); // different for JP4
if (correctionsParameters.isJP4()){
int subCamera= srcChannel- correctionsParameters.firstSubCamera; // to match those in the sensor files
int [] channels=this.pixelMapping.channelsForSubCamera(subCamera);
if (channels!=null) for (int j=0;j<channels.length;j++) usedChannels[channels[j]]=true;
} else {
if (!this.pixelMapping.isChannelAvailable(srcChannel)){
if (debugLevel>0) System.out.println("No sensor data for channel "+srcChannel+", needed for source file "+paths[i]);
} else usedChannels[srcChannel] = true;
}
}
return usedChannels;
}
public void testFF(String path){
ImagePlus imp=new ImagePlus(path);
imp.getProcessor().resetMinAndMax(); // imp_psf will be reused
imp.show();
int srcChannel=correctionsParameters.getChannelFromSourceTiff(path);
int [] channels={srcChannel};
if (correctionsParameters.isJP4()){
int subCamera= srcChannel- correctionsParameters.firstSubCamera; // to match those in the sensor files
channels=this.pixelMapping.channelsForSubCamera(subCamera);
}
/* int [][] bayer={{1,0},{2,1}}; // GR/BG
// double [] corrFF=this.pixelMapping.getBayerFlatField(
float [] corrFF=this.pixelMapping.getBayerFlatFieldFloat(
srcChannel,
imp.getWidth(),
imp.getHeight(),
bayer);
*/
for (int chn=0;chn<channels.length;chn++){
srcChannel=channels[chn];
float [] corrFF=this.channelVignettingCorrection[srcChannel];
if (corrFF==null) return;
float [] pixels=(float[]) imp.getProcessor().getPixels();
double [] pixelsFlat=new double [corrFF.length];
for (int i=0;i<corrFF.length;i++) pixelsFlat[i]=pixels[i]*corrFF[i];
SDFA_INSTANCE.showArrays(corrFF, imp.getWidth(), imp.getHeight(), srcChannel+"-FF-correction");
SDFA_INSTANCE.showArrays(pixelsFlat, imp.getWidth(), imp.getHeight(), srcChannel+"-flat-"+imp.getTitle());
}
}
public boolean isChannelEnabled(int channel){
return ((channel>=0) && (channel<this.usedChannels.length) && this.usedChannels[channel]);
}
public void processChannelImages(
EyesisCorrectionParameters.SplitParameters splitParameters,
EyesisCorrectionParameters.DebayerParameters debayerParameters,
EyesisCorrectionParameters.NonlinParameters nonlinParameters,
EyesisCorrectionParameters.ColorProcParameters colorProcParameters,
CorrectionColorProc.ColorGainsParameters channelGainParameters,
EyesisCorrectionParameters.RGBParameters rgbParameters,
EyesisCorrectionParameters.EquirectangularParameters equirectangularParameters,
int convolveFFTSize, // 128 - fft size, kernel size should be size/2
final int threadsMax, // maximal number of threads to launch
final boolean updateStatus,
final int debugLevel){
this.startTime=System.nanoTime();
String [] sourceFiles=correctionsParameters.getSourcePaths();
boolean [] enabledFiles=new boolean[sourceFiles.length];
for (int i=0;i<enabledFiles.length;i++) enabledFiles[i]=false;
int numFilesToProcess=0;
int numImagesToProcess=0;
for (int nFile=0;nFile<enabledFiles.length;nFile++){
if ((sourceFiles[nFile]!=null) && (sourceFiles[nFile].length()>1)) {
int [] channels={correctionsParameters.getChannelFromSourceTiff(sourceFiles[nFile])};
if (correctionsParameters.isJP4()){
int subCamera= channels[0]- correctionsParameters.firstSubCamera; // to match those in the sensor files
channels=this.pixelMapping.channelsForSubCamera(subCamera);
}
if (channels!=null){
for (int i=0;i<channels.length;i++) if (isChannelEnabled(channels[i])){
if (!enabledFiles[nFile]) numFilesToProcess++;
enabledFiles[nFile]=true;
numImagesToProcess++;
}
}
}
}
if (numFilesToProcess==0){
System.out.println("No files to process (of "+sourceFiles.length+")");
return;
} else {
if (debugLevel>0) System.out.println(numFilesToProcess+ " files to process (of "+sourceFiles.length+"), "+numImagesToProcess+" images to process");
}
double [] referenceExposures=calcReferenceExposures(debugLevel); // multiply each image by this and divide by individual (if not NaN)
int [][] fileIndices=new int [numImagesToProcess][2]; // file index, channel number
int index=0;
for (int nFile=0;nFile<enabledFiles.length;nFile++){
if ((sourceFiles[nFile]!=null) && (sourceFiles[nFile].length()>1)) {
int [] channels={correctionsParameters.getChannelFromSourceTiff(sourceFiles[nFile])};
if (correctionsParameters.isJP4()){
int subCamera= channels[0]- correctionsParameters.firstSubCamera; // to match those in the sensor files
channels=this.pixelMapping.channelsForSubCamera(subCamera);
}
if (channels!=null){
for (int i=0;i<channels.length;i++) if (isChannelEnabled(channels[i])){
fileIndices[index ][0]=nFile;
fileIndices[index++][1]=channels[i];
}
}
}
}
for (int iImage=0;iImage<fileIndices.length;iImage++){
int nFile=fileIndices[iImage][0];
ImagePlus imp_src=null;
// int srcChannel=correctionsParameters.getChannelFromSourceTiff(sourceFiles[nFile]);
int srcChannel=fileIndices[iImage][1];
if (correctionsParameters.isJP4()){
int subchannel=this.pixelMapping.getSubChannel(srcChannel);
if (this.correctionsParameters.swapSubchannels01) {
switch (subchannel){
case 0: subchannel=1; break;
case 1: subchannel=0; break;
}
}
if (debugLevel>0) System.out.println("Processing channel "+fileIndices[iImage][1]+" - subchannel "+subchannel+" of "+sourceFiles[nFile]);
ImagePlus imp_composite=JP4_INSTANCE.open(
"", // path,
sourceFiles[nFile],
"", //arg - not used in JP46 reader
true, // un-apply camera color gains
null, // new window
false); // do not show
imp_src=JP4_INSTANCE.demuxImage(imp_composite, subchannel);
if (imp_src==null) imp_src=imp_composite; // not a composite image
// do we need to add any properties?
} else {
imp_src=new ImagePlus(sourceFiles[nFile]);
// (new JP46_Reader_camera(false)).decodeProperiesFromInfo(imp_src); // decode existent properties from info
JP4_INSTANCE.decodeProperiesFromInfo(imp_src); // decode existent properties from info
if (debugLevel>0) System.out.println("Processing "+sourceFiles[nFile]);
}
double scaleExposure=1.0;
if (!Double.isNaN(referenceExposures[nFile]) && (imp_src.getProperty("EXPOSURE")!=null)){
scaleExposure=referenceExposures[nFile]/Double.parseDouble((String) imp_src.getProperty("EXPOSURE"));
// imp_src.setProperty("scaleExposure", scaleExposure); // it may already have channel
if (debugLevel>0) System.out.println("Will scale intensity (to compensate for exposure) by "+scaleExposure);
}
imp_src.setProperty("name", correctionsParameters.getNameFromSourceTiff(sourceFiles[nFile]));
imp_src.setProperty("channel", srcChannel); // it may already have channel
imp_src.setProperty("path", sourceFiles[nFile]); // it may already have channel
// ImagePlus result=processChannelImage( // returns ImagePlus, but it already should be saved/shown
processChannelImage( // returns ImagePlus, but it already should be saved/shown
imp_src, // should have properties "name"(base for saving results), "channel","path"
splitParameters,
debayerParameters,
nonlinParameters,
colorProcParameters,
channelGainParameters,
rgbParameters,
convolveFFTSize, // 128 - fft size, kernel size should be size/2
scaleExposure,
threadsMax, // maximal number of threads to launch
updateStatus,
debugLevel);
// warp result (add support for different color modes)
if (this.correctionsParameters.equirectangular){
if (equirectangularParameters.clearFullMap) pixelMapping.deleteEquirectangularMapFull(srcChannel); // save memory? //removeUnusedSensorData - no, use equirectangular specific settings
if (equirectangularParameters.clearAllMaps) pixelMapping.deleteEquirectangularMapAll(srcChannel); // save memory? //removeUnusedSensorData - no, use equirectangular specific settings
}
//pixelMapping
Runtime.getRuntime().gc();
if (debugLevel>0) System.out.println("Processing image "+(iImage+1)+" (of "+fileIndices.length+") finished at "+
IJ.d2s(0.000000001*(System.nanoTime()-this.startTime),3)+" sec, --- Free memory="+Runtime.getRuntime().freeMemory()+" (of "+Runtime.getRuntime().totalMemory()+")");
if (this.stopRequested.get()>0) {
System.out.println("User requested stop");
return;
}
}
}
public void saveTiffWithAlpha(
ImagePlus imp,
EyesisCorrectionParameters.CorrectionParameters correctionsParameters)
throws IOException, FormatException, ServiceException, DependencyException{
int fullWidth,x0;
boolean cutTile=!this.correctionsParameters.usePlaneProjection && correctionsParameters.equirectangularCut;
if (cutTile){
fullWidth=Integer.parseInt((String) imp.getProperty("ImageFullWidth"));
x0= Integer.parseInt((String) imp.getProperty("XPosition"));
cutTile=(x0+imp.getWidth()>fullWidth) ;
}
if (cutTile ) {
if (this.debugLevel>0) System.out.println("Cutting result image in two parts to prevent roll-over");
saveTiffWithAlpha(
cropEquirectangular(imp, false),
correctionsParameters);
saveTiffWithAlpha(
cropEquirectangular(imp, true),
correctionsParameters);
return;
} else {
String path= correctionsParameters.selectResultsDirectory(
true, // smart,
true); //newAllowed, // save
if (path!=null){
path+=Prefs.getFileSeparator()+imp.getTitle()+".tiff";
if (this.debugLevel>0) System.out.println("Saving equirectangular result to "+path);
(new EyesisTiff()).saveTiff(
imp,
path,
correctionsParameters.equirectangularFormat,
((correctionsParameters.equirectangularFormat==3)?correctionsParameters.outputRangeFP:correctionsParameters.outputRangeInt),
correctionsParameters.imageJTags,
debugLevel);
}
}
}
public ImagePlus cropEquirectangular(
ImagePlus imp,
boolean right){ // right - rolled over to the left of the result, adds "-LEFT", "-RIGHT" to the title
int fullWidth=Integer.parseInt((String) imp.getProperty("ImageFullWidth"));
int x0= Integer.parseInt((String) imp.getProperty("XPosition"));
int height= imp.getHeight();
int width= imp.getWidth();
int dx=(fullWidth-x0);
int cropedWidth=right?(imp.getWidth()+-dx):dx;
ImagePlus imp_croped;
int addX=right?dx:0;
if (imp.getType()==ImagePlus.COLOR_RGB) {
int [] allPixels= (int []) imp.getProcessor().getPixels();
int [] cropedPixels=new int[cropedWidth*height];
for (int y=0;y<height;y++) for (int x=0;x< cropedWidth; x++){
cropedPixels[y*cropedWidth+x] = allPixels[y*width+(x+addX)];
}
ColorProcessor colorProcessor=new ColorProcessor(cropedWidth,height);
colorProcessor.setPixels(cropedPixels);
imp_croped= new ImagePlus(imp.getTitle()+(right?"-RIGHT":"-LEFT"),colorProcessor);
} else if (imp.getStackSize()==4) {
float [][] allPixels= new float [4][];
for (int c=0;c<allPixels.length;c++) allPixels[c]= (float []) imp.getStack().getPixels(c+1);
float [][] cropedPixels=new float [4][];
for (int c=0;c<cropedPixels.length;c++) cropedPixels[c]= new float [cropedWidth*height];
for (int c=0;c<cropedPixels.length;c++) for (int y=0;y<height;y++) for (int x=0;x< cropedWidth; x++){
cropedPixels[c][y*cropedWidth+x] = allPixels[c][y*width+(x+addX)];
}
ImageStack croppedStack=new ImageStack(cropedWidth,height);
for (int c=0;c<allPixels.length;c++){
croppedStack.addSlice(imp.getStack().getSliceLabel(c+1), cropedPixels[c]);
}
imp_croped= new ImagePlus(imp.getTitle()+(right?"-RIGHT":"-LEFT"),croppedStack);
} else{
String msg="cropEquirectangular(): Unsupported image format";
System.out.println("Error "+msg);
IJ.showMessage("Error",msg);
return null;
}
(new JP46_Reader_camera(false)).copyProperties (imp, imp_croped);
if (right) imp_croped.setProperty("XPosition", "0");
(new JP46_Reader_camera(false)).encodeProperiesToInfo(imp_croped);
return imp_croped;
}
public ImagePlus applyEquirectangular(
int channel,
ImagePlus imp,
int threadsMax,
int debugLevel){
if (!pixelMapping.isChannelAvailable(channel)){
String msg="No sensor data for channel "+channel;
System.out.println("Error "+msg);
IJ.showMessage("Error",msg);
return null;
}
if (!pixelMapping.isEquirectangularMapAvailable(channel)){
String path=correctionsParameters.selectEquirectangularMapFile(
channel,
debugLevel);
if (path==null) {
String msg="No equirectangular map found for channel "+channel;
System.out.println("Error "+msg);
IJ.showMessage("Error",msg);
return null;
}
if (debugLevel>1) System.out.println("applyEquirectangular(): channel="+channel+" path="+path);
pixelMapping.loadChannelEquirectangularMap(
channel,
path);
if (!this.pixelMapping.isEquirectangularMapAvailable(channel)){
String msg="Failed to load equirectangular map for channel "+channel;
System.out.println("Error "+msg);
IJ.showMessage("Error",msg);
return null;
}
}
// apply warping here
// double sourceImageScale=2.0*this.correctionsParameters.JPEG_scale;
int sourceImageScale=2; // *this.correctionsParameters.JPEG_scale;
ImagePlus imp_warped= pixelMapping.resampleToEquirectangular( // will Add "_EQR"
imp,
channel,
sourceImageScale,
threadsMax);
return imp_warped;
}
public ImagePlus applyCommonPlane(
int channel,
ImagePlus imp,
int threadsMax,
int debugLevel){
if (!pixelMapping.isChannelAvailable(channel)){
String msg="No sensor data for channel "+channel;
System.out.println("Error "+msg);
IJ.showMessage("Error",msg);
return null;
}
if (!pixelMapping.isPlaneMapMapAvailable(channel)){
String path=correctionsParameters.selectPlaneMapFile(
// channel,
debugLevel);
if (path==null) {
String msg="No common plane projection map found";
System.out.println("Error "+msg);
IJ.showMessage("Error",msg);
return null;
}
if (debugLevel>1) System.out.println("applyCommonPlane(): channel="+channel+" path="+path);
pixelMapping.loadPlaneMap(
// channel,
path,
debugLevel);
if (!this.pixelMapping.isPlaneMapMapAvailable(channel)){
String msg="Failed to load a common plane projection map for channel "+channel+", or that file does not have this sensor data";
System.out.println("Error "+msg);
IJ.showMessage("Error",msg);
return null;
}
}
// apply warping here
// double sourceImageScale=2.0*this.correctionsParameters.JPEG_scale;
int sourceImageScale=2; // *this.correctionsParameters.JPEG_scale;
ImagePlus imp_warped= pixelMapping.applyPlaneMap(
imp, //ImagePlus impSrc,
channel,
sourceImageScale, // 2.0
threadsMax,
debugLevel
);
return imp_warped;
}
public ImagePlus processChannelImage(
ImagePlus imp_src, // should have properties "name"(base for saving results), "channel","path"
EyesisCorrectionParameters.SplitParameters splitParameters,
EyesisCorrectionParameters.DebayerParameters debayerParameters,
EyesisCorrectionParameters.NonlinParameters nonlinParameters,
EyesisCorrectionParameters.ColorProcParameters colorProcParameters,
CorrectionColorProc.ColorGainsParameters channelGainParameters,
EyesisCorrectionParameters.RGBParameters rgbParameters,
int convolveFFTSize, // 128 - fft size, kernel size should be size/2
double scaleExposure,
final int threadsMax, // maximal number of threads to launch
final boolean updateStatus,
final int debugLevel){
boolean advanced=this.correctionsParameters.zcorrect || this.correctionsParameters.equirectangular;
boolean crop= advanced? true: this.correctionsParameters.crop;
boolean rotate= advanced? false: this.correctionsParameters.rotate;
double JPEG_scale= advanced? 1.0: this.correctionsParameters.JPEG_scale;
boolean toRGB= advanced? true: this.correctionsParameters.toRGB;
// may use this.StartTime to report intermediate steps execution times
String name=(String) imp_src.getProperty("name");
// int channel= Integer.parseInt((String) imp_src.getProperty("channel"));
int channel= (Integer) imp_src.getProperty("channel");
String path= (String) imp_src.getProperty("path");
if (this.correctionsParameters.vignetting){
if ((this.channelVignettingCorrection==null) || (channel<0) || (channel>=this.channelVignettingCorrection.length) || (this.channelVignettingCorrection[channel]==null)){
System.out.println("No vignetting data for channel "+channel);
return null;
}
float [] pixels=(float []) imp_src.getProcessor().getPixels();
if (pixels.length!=this.channelVignettingCorrection[channel].length){
System.out.println("Vignetting data for channel "+channel+" has "+this.channelVignettingCorrection[channel].length+" pixels, image "+path+" has "+pixels.length);
return null;
}
for (int i=0;i<pixels.length;i++){
pixels[i]*=this.channelVignettingCorrection[channel][i];
}
}
String title=name+"-"+String.format("%02d", channel);
ImagePlus result=imp_src;
if (debugLevel>1) System.out.println("processing: "+path);
result.setTitle(title+"RAW");
if (!this.correctionsParameters.split){
saveAndShow(result, this.correctionsParameters);
return result;
}
// Split into Bayer components, oversample, increase canvas
ImageStack stack= bayerToStack(
result, // source Bayer image, linearized, 32-bit (float))
splitParameters);
String titleFull=title+"-SPLIT";
if (!this.correctionsParameters.debayer) {
result= new ImagePlus(titleFull, stack);
saveAndShow(result, this.correctionsParameters);
return result;
}
// Demosaic image
if (debayerScissors==null) debayerScissors=new DebayerScissors(this.stopRequested);
debayerScissors.setDebug(debugLevel);
stack= debayerScissors.aliasScissorsStack(stack, // stack with 3 colors/slices with the image
debayerParameters,
(this.correctionsParameters.saveDebayerEnergy || this.correctionsParameters.showDebayerEnergy),
threadsMax, // number of image pixels/ sensor pixels (each direction) == 2
updateStatus,// update status info
debugLevel);
if (this.correctionsParameters.saveDebayerEnergy || this.correctionsParameters.showDebayerEnergy) {
if (debayerScissors.getDebayerEnergy()!=null) {
ImagePlus debayerMask=SDFA_INSTANCE.makeArrays (debayerScissors.getDebayerEnergy(),
debayerScissors.getDebayerEnergyWidth(),
debayerScissors.getDebayerEnergy().length/debayerScissors.getDebayerEnergyWidth(),
title+"-DEBAYER-ENERGY");
saveAndShow(debayerMask,
this.correctionsParameters,
this.correctionsParameters.saveDebayerEnergy,
this.correctionsParameters.showDebayerEnergy
);
}
}
titleFull=title+"-DEMOSAIC";
CorrectionDenoise correctionDenoise=new CorrectionDenoise(stopRequested);
CorrectionColorProc correctionColorProc=new CorrectionColorProc(stopRequested);
result= new ImagePlus(titleFull, stack);
if (this.correctionsParameters.deconvolve) {
//Ask for the kernel directory if it is undefined
if (this.sharpKernelPaths==null){ // make sure the paths list is reset after changing parameters
this.sharpKernelPaths=correctionsParameters.selectKernelChannelFiles(
0, // 0 - sharp, 1 - smooth
this.usedChannels.length, // number of channels
this.debugLevel);
}
if ((this.sharpKernelPaths==null) || (this.sharpKernelPaths[channel]==null)){
System.out.println("Sharp kernel path does not exist");
return null;
}
// Read deconvolution kernels
ImagePlus imp_sharp_kernels=new ImagePlus(this.sharpKernelPaths[channel]);
if (imp_sharp_kernels==null) {
System.out.println("Failed to open (sharp) kernel stack "+this.sharpKernelPaths[channel]);
return null;
} else if (imp_sharp_kernels.getStackSize()<3) {
System.out.println("Need a 3-layer stack with kernels - file "+this.sharpKernelPaths[channel]);
return null;
}
ImageStack convolutionSharpKernelStack=imp_sharp_kernels.getStack();
if (debugLevel>1) System.out.println("Using kernel stack "+this.sharpKernelPaths[channel]+" for convolution with "+result.getTitle());
ImageStack stackDeconvolvedSharp= convolveStackWithKernelStack( // stack_d
stack, // stack with 3 colors/slices with the image
convolutionSharpKernelStack, // stack with 3 colors/slices convolution kernels
convolveFFTSize, // 128 - fft size, kernel size should be size/2
threadsMax,
updateStatus, // update status info
debugLevel);
imp_sharp_kernels=null; // free memory
convolutionSharpKernelStack=null;
Runtime.getRuntime().gc();
titleFull=title+"-DECONV";
if (this.correctionsParameters.combine) {
// Read "smooth" kernels
if (this.smoothKernelPaths==null){ // make sure the paths list is reset after changing parameters
this.smoothKernelPaths=correctionsParameters.selectKernelChannelFiles(
1, // 0 - sharp, 1 - smooth
this.usedChannels.length, // number of channels
this.debugLevel);
}
if ((this.smoothKernelPaths==null) || (this.smoothKernelPaths[channel]==null)){
System.out.println("Smooth kernel path does not exist");
return null;
}
ImagePlus imp_smooth_kernels=new ImagePlus(this.smoothKernelPaths[channel]);
if (imp_smooth_kernels==null) {
System.out.println("Failed to open (smooth) kernel stack "+this.smoothKernelPaths[channel]);
return null;
} else if (imp_smooth_kernels.getStackSize()<3) {
System.out.println("Need a 3-layer stack with kernels - file "+this.smoothKernelPaths[channel]);
return null;
}
ImageStack convolutionSmoothKernelStack=imp_smooth_kernels.getStack();
if (debugLevel>1) System.out.println("Using smooth kernel stack "+this.smoothKernelPaths[channel]+" for convolution with "+result.getTitle());
ImageStack stackDeconvolvedSmooth = convolveStackWithKernelStack( //stack_g
stack, // stack with 3 colors/slices with the image
convolutionSmoothKernelStack, // stack with 3 colors/slices convolution kernels
convolveFFTSize, // 128 - fft size, kernel size should be size/2
threadsMax,
updateStatus, // update status info
debugLevel);
imp_smooth_kernels=null; // free memory
convolutionSmoothKernelStack=null;
Runtime.getRuntime().gc();
// Combine Smooth and Sharp images
double [][] noiseMask= extractNoiseMask(
this.imageNoiseGains[channel],// contains 3-slice stack (r,b,g)
nonlinParameters.noiseGainWeights[0], // coefficient for slice 0 (r)
nonlinParameters.noiseGainWeights[1], // coefficient for slice 1 (b)
nonlinParameters.noiseGainWeights[2], // coefficient for slice 2 (g)
1, // decimate result (not yet supported)
nonlinParameters.noiseGainPower
);
// show noise mask here?
nonlinParameters.showMask=this.correctionsParameters.showDenoiseMask;
// if (DEBUG_LEVEL>1) System.out.println ( " noiseMask.length="+((noiseMask==null)?"null":(noiseMask.length+" noiseMask[0].length="+noiseMask[0].length)));
// CorrectionDenoise correctionDenoise=new CorrectionDenoise(stopRequested);
correctionDenoise.setDebug(debugLevel); // not yet used
stack= correctionDenoise.combineLoHiStacks(
stackDeconvolvedSharp, // ImageStack with the image, convolved with the reversed PSF (sharp but with high noise)
stackDeconvolvedSmooth, // ImageStack with the image, convolved with the Gaussian (just lateral compensated) (blurred, but low noise)
channel,
nonlinParameters, // show mask generated and used
noiseMask, // 2-d array of kernelsNoiseGain (divide mask by it)
32, // linear pixels per noiseMask pixels (32)
threadsMax,
updateStatus, // update status info
debugLevel);
if (this.correctionsParameters.saveDenoiseMask || this.correctionsParameters.showDenoiseMask) {
ImagePlus denoiseMask=SDFA_INSTANCE.makeArrays (
correctionDenoise.getDenoiseMask(),
correctionDenoise.getDenoiseMaskWidth(),
correctionDenoise.getDenoiseMask().length/correctionDenoise.getDenoiseMaskWidth(),
title+"-MASK");
if (this.correctionsParameters.jpeg) {
//crop Mask to original image size
if (this.correctionsParameters.crop){
denoiseMask=cropImage32(denoiseMask,splitParameters);
}
//rotate the result
if (this.correctionsParameters.rotate){
denoiseMask=rotateImage32CW(denoiseMask);
}
//scale the result
if (this.correctionsParameters.JPEG_scale!=1.0){
ImageProcessor ip=denoiseMask.getProcessor();
ip.setInterpolationMethod(ImageProcessor.BICUBIC);
ip=ip.resize((int)(ip.getWidth()*this.correctionsParameters.JPEG_scale),(int) (ip.getHeight()*this.correctionsParameters.JPEG_scale));
denoiseMask= new ImagePlus(denoiseMask.getTitle(),ip);
denoiseMask.updateAndDraw();
}
if (this.correctionsParameters.showDenoiseMask) denoiseMask.show();
//public ImagePlus Image32toGreyRGB24(ImagePlus imp);
if (this.correctionsParameters.saveDenoiseMask) {
ImagePlus denoiseMaskRGB24=Image32toGreyRGB24(denoiseMask);
saveAndShow(denoiseMaskRGB24,
this.correctionsParameters,
this.correctionsParameters.saveDenoiseMask,
false, //processParameters.showDenoiseMask,
this.correctionsParameters.JPEG_quality);
denoiseMaskRGB24=null;
}
} else {
saveAndShow(denoiseMask,
this.correctionsParameters,
this.correctionsParameters.saveDenoiseMask,
this.correctionsParameters.showDenoiseMask
);
}
}
} else { // end of if (this.correctionsParameters.combine)
stack=stackDeconvolvedSharp;
} // end of else if (this.correctionsParameters.combine)
} else if (this.correctionsParameters.combine) { // "combine" w/o "deconvolve" - just use convolution with smooth kernels
// Read smooth kernels
// Read "smooth" kernels
if (this.smoothKernelPaths==null){ // make sure the paths list is reset after changing parameters
this.smoothKernelPaths=correctionsParameters.selectKernelChannelFiles(
1, // 0 - sharp, 1 - smooth
this.usedChannels.length, // number of channels
this.debugLevel);
}
if ((this.smoothKernelPaths==null) || (this.smoothKernelPaths[channel]==null)){
System.out.println("Smooth kernel path does not exist");
return null;
}
ImagePlus imp_smooth_kernels=new ImagePlus(this.smoothKernelPaths[channel]);
if (imp_smooth_kernels==null) {
System.out.println("Failed to open (smooth) kernel stack "+this.smoothKernelPaths[channel]);
return null;
} else if (imp_smooth_kernels.getStackSize()<3) {
System.out.println("Need a 3-layer stack with kernels - file "+this.smoothKernelPaths[channel]);
return null;
}
ImageStack convolutionSmoothKernelStack=imp_smooth_kernels.getStack();
if (debugLevel>1) System.out.println("Using smooth kernel stack "+this.smoothKernelPaths[channel]+" for convolution with "+result.getTitle());
ImageStack stackDeconvolvedSmooth = convolveStackWithKernelStack( // stack_g
stack, // stack with 3 colors/slices with the image
convolutionSmoothKernelStack, // stack with 3 colors/slices convolution kernels
convolveFFTSize, // 128 - fft size, kernel size should be size/2
threadsMax,
updateStatus, // update status info
debugLevel);
imp_smooth_kernels=null; // free memory
stack=stackDeconvolvedSmooth;
convolutionSmoothKernelStack=null;
Runtime.getRuntime().gc();
titleFull=title+"-LOWRES";
}// end of if (this.correctionsParameters.deconvolve)
//stack now has the result, titleFull - correct title for the image
if (!this.correctionsParameters.colorProc){
result= new ImagePlus(titleFull, stack);
saveAndShow(
result,
this.correctionsParameters);
return result;
}
//Processing colors - changing stack sequence to r-g-b (was r-b-g)
if (!fixSliceSequence(
stack,
debugLevel)){
if (debugLevel>0) System.out.println("fixSliceSequence() returned false");
return null;
}
// if (debugLevel>2){
if (debugLevel>1){
ImagePlus imp_dbg=new ImagePlus(imp_src.getTitle()+"-"+channel+"-preColors",stack);
saveAndShow(
imp_dbg,
this.correctionsParameters);
}
correctionColorProc.processColorsWeights(stack,
// 255.0/this.psfSubpixelShouldBe4/this.psfSubpixelShouldBe4, // double scale, // initial maximal pixel value (16))
255.0/this.psfSubpixelShouldBe4/this.psfSubpixelShouldBe4/scaleExposure, // double scale, // initial maximal pixel value (16))
colorProcParameters,
channelGainParameters,
channel,
correctionDenoise.getDenoiseMask(),
this.correctionsParameters.blueProc,
debugLevel);
if (debugLevel>1) System.out.println("Processed colors to YPbPr, total number of slices="+stack.getSize());
if (debugLevel>2){
ImagePlus imp_dbg=new ImagePlus("procColors",stack);
saveAndShow(
imp_dbg,
this.correctionsParameters);
}
// Show/save color denoise mask
if ((this.correctionsParameters.saveChromaDenoiseMask || this.correctionsParameters.showChromaDenoiseMask) && (correctionColorProc.getDenoiseMaskChroma()!=null)) {
ImagePlus chromaDenoiseMask=SDFA_INSTANCE.makeArrays (correctionColorProc.getDenoiseMaskChroma(),
correctionColorProc.getDenoiseMaskChromaWidth(),
correctionColorProc.getDenoiseMaskChroma().length/correctionColorProc.getDenoiseMaskChromaWidth(),
title+"-MASK_CHROMA");
if (this.correctionsParameters.jpeg) {
//crop Mask to original image size
if (this.correctionsParameters.crop){
chromaDenoiseMask=cropImage32(chromaDenoiseMask,splitParameters);
}
//rotate the result
if (this.correctionsParameters.rotate){
chromaDenoiseMask=rotateImage32CW(chromaDenoiseMask);
}
//scale the result
if (this.correctionsParameters.JPEG_scale!=1.0){
ImageProcessor ip=chromaDenoiseMask.getProcessor();
ip.setInterpolationMethod(ImageProcessor.BICUBIC);
ip=ip.resize((int)(ip.getWidth()*this.correctionsParameters.JPEG_scale),(int) (ip.getHeight()*this.correctionsParameters.JPEG_scale));
chromaDenoiseMask= new ImagePlus(chromaDenoiseMask.getTitle(),ip);
chromaDenoiseMask.updateAndDraw();
}
if (this.correctionsParameters.showChromaDenoiseMask) chromaDenoiseMask.show();
//public ImagePlus Image32toGreyRGB24(ImagePlus imp);
if (this.correctionsParameters.saveChromaDenoiseMask) {
ImagePlus chromaDenoiseMaskRGB24=Image32toGreyRGB24(chromaDenoiseMask);
saveAndShow(chromaDenoiseMaskRGB24,
this.correctionsParameters,
this.correctionsParameters.saveChromaDenoiseMask,
false, //processParameters.showChromaDenoiseMask,
this.correctionsParameters.JPEG_quality);
chromaDenoiseMaskRGB24=null;
}
} else {
saveAndShow(chromaDenoiseMask,
this.correctionsParameters,
this.correctionsParameters.saveChromaDenoiseMask,
this.correctionsParameters.showChromaDenoiseMask
);
}
}
if (toRGB) {
correctionColorProc.YPrPbToRGB(stack,
colorProcParameters.kr, // 0.299;
colorProcParameters.kb, // 0.114;
colorProcParameters.useFirstY?9:8, // int sliceY,
6, // int slicePr,
7// int slicePb
);
title=titleFull; // including "-DECONV" or "-COMBO"
titleFull=title+"-RGB-float";
//Trim stack to just first 3 slices
if (debugLevel>2){
ImagePlus imp_dbg=new ImagePlus("YPrPbToRGB",stack);
saveAndShow(
imp_dbg,
this.correctionsParameters);
}
while (stack.getSize()>3) stack.deleteLastSlice();
if (debugLevel>1) System.out.println("Trimming color stack");
} else {
title=titleFull; // including "-DECONV" or "-COMBO"
titleFull=title+"-YPrPb"; // including "-DECONV" or "-COMBO"
if (debugLevel>1) System.out.println("Using full stack, including YPbPr");
}
result= new ImagePlus(titleFull, stack);
// Crop image to match original one (scaled to oversampling)
if (crop){ // always crop if equirectangular
stack=cropStack32(stack,splitParameters);
if (debugLevel>2){
ImagePlus imp_dbg=new ImagePlus("cropped",stack);
saveAndShow(
imp_dbg,
this.correctionsParameters);
}
}
// rotate the result
if (rotate){ // never rotate for equirectangular
stack=rotateStack32CW(stack);
}
if (!toRGB && !this.correctionsParameters.jpeg){ // toRGB set for equirectangular
saveAndShow(result, this.correctionsParameters);
return result;
} else { // that's not the end result, save if required
saveAndShow(result, this.correctionsParameters, this.correctionsParameters.save32, false,this.correctionsParameters.JPEG_quality); // save, no show
}
// convert to RGB48 (16 bits per color component)
ImagePlus imp_RGB;
if (this.correctionsParameters.equirectangularFormat==0){
stack=convertRGB32toRGB16Stack(
stack,
rgbParameters);
titleFull=title+"-RGB48";
result= new ImagePlus(titleFull, stack);
// ImagePlus imp_RGB24;
CompositeImage compositeImage=convertToComposite(result);
if (!this.correctionsParameters.jpeg && !advanced){ // RGB48 was the end result
saveAndShow(compositeImage, this.correctionsParameters);
return result;
} else { // that's not the end result, save if required
saveAndShow(compositeImage, this.correctionsParameters, this.correctionsParameters.save16, false); // save, no show
}
imp_RGB=convertRGB48toRGB24(
stack,
title+"-RGB24",
0, 65536, // r range 0->0, 65536->256
0, 65536, // g range
0, 65536);// b range
if (JPEG_scale!=1.0){
ImageProcessor ip=imp_RGB.getProcessor();
ip.setInterpolationMethod(ImageProcessor.BICUBIC);
ip=ip.resize((int)(ip.getWidth()*JPEG_scale),(int) (ip.getHeight()*JPEG_scale));
imp_RGB= new ImagePlus(imp_RGB.getTitle(),ip);
imp_RGB.updateAndDraw();
}
} else {
switch (this.correctionsParameters.equirectangularFormat){
case 0:
titleFull=title+"-INT8";
break;
case 1:
titleFull=title+"-INT16";
break;
case 2:
titleFull=title+"-INT32";
break;
case 3:
titleFull=title+"-FLOAT32";
break;
case 4:
titleFull=title+"-IJSTACK";
break;
}
result= new ImagePlus(titleFull, stack);
imp_RGB=result;
}
if (advanced){
if (this.correctionsParameters.zcorrect){ // save double resolution image for distance measurements
saveAndShow(
imp_RGB,
this.correctionsParameters,
true, // save
false, // show
0); // force Tiff
}
if (this.correctionsParameters.equirectangular) {
ImagePlus impWarped=null;
if (this.correctionsParameters.usePlaneProjection){
impWarped= applyCommonPlane(
channel,
imp_RGB,
threadsMax,
debugLevel);
} else {
impWarped= applyEquirectangular(
channel,
imp_RGB,
threadsMax,
debugLevel);
}
if ((impWarped.getFileInfo().fileType== FileInfo.RGB) && // only for 8-bit RGB (this.correctionsParameters.equirectangularFormat==0)
this.correctionsParameters.planeAsJPEG &&
this.correctionsParameters.usePlaneProjection){ // equirectangular - always TIFF
saveAndShow(
impWarped,
this.correctionsParameters,
this.correctionsParameters.save,
this.correctionsParameters.show,
this.correctionsParameters.JPEG_quality);
} else {
if (this.correctionsParameters.equirectangularFormat<4){
try {
saveTiffWithAlpha(impWarped,this.correctionsParameters);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (FormatException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ServiceException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (DependencyException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} else { // temporarily
saveAndShow(impWarped, this.correctionsParameters);
}
}
}
} else {
saveAndShow(imp_RGB, this.correctionsParameters);
}
return result;
}
/** ======================================================================== */
private boolean fixSliceSequence (
ImageStack stack,
int debugLevel){
int i,j;
int [] rgbNumbers= {0,0,0};
for (j=0;j<3;j++) {
for (i=1;i<=3;i++) if (stack.getSliceLabel(i).toLowerCase().equals(this.stackColorNames[j].toLowerCase())){
// fix case (capitalized)
// System.out.println ( "stack.getSliceLabel("+i+")="+stack.getSliceLabel(i));
// System.out.println ( "stackColorNames["+j+"]="+stackColorNames[j]);
stack.setSliceLabel(this.stackColorNames[j],i);
rgbNumbers[j]=i;
// System.out.println ( "rgbNumbers["+j+"]="+rgbNumbers[j]);
break;
}
}
if (debugLevel>2) {
System.out.println ( "Input file color slice numbers:");
System.out.println ( " Red - slice "+((rgbNumbers[0]>0)?rgbNumbers[0]:"missing"));
System.out.println ( " Green - slice "+((rgbNumbers[1]>0)?rgbNumbers[1]:"missing"));
System.out.println ( " Blue - slice "+((rgbNumbers[2]>0)?rgbNumbers[2]:"missing"));
}
for (i=0;i<3;i++) if (rgbNumbers[i]<=0) {
System.out.println ( this.stackColorNames[i]+ " slice is missing in the input file. Please check slice names");
return false;
}
while ((rgbNumbers[0]!=1) || (rgbNumbers[1]!=2) ||(rgbNumbers[2]!=3)) {
if (rgbNumbers[0]==1) swapStackSlices(stack,2,3);
else if (rgbNumbers[2]==3) swapStackSlices(stack,1,2);
else swapStackSlices(stack,1,3);
for (j=0;j<3;j++) {
for (i=1;i<=3;i++) if (stack.getSliceLabel(i).equals(this.stackColorNames[j])){
rgbNumbers[j]=i;
break;
}
}
}
return true;
}
/** ======================================================================== */
public void swapStackSlices(ImageStack stack,
int slice1,
int slice2) {
String label=stack.getSliceLabel(slice1);
stack.setSliceLabel(stack.getSliceLabel(slice2), slice1);
stack.setSliceLabel(label, slice2);
Object pixels=stack.getPixels(slice1);
stack.setPixels (stack.getPixels(slice2), slice1);
stack.setPixels (pixels, slice2);
}
/** ======================================================================== */
public ImageStack cropStack32(
ImageStack stack,
EyesisCorrectionParameters.SplitParameters splitParameters) {
int size=stack.getSize();
int iWidth=stack.getWidth();
int height=stack.getHeight()-splitParameters.addTop-splitParameters.addBottom;
int width= stack.getWidth()-splitParameters.addLeft-splitParameters.addRight;
int length=width*height;
ImageStack stack_crop = new ImageStack(width,height);
int i,x,y,index,base;
float [] ipixels;
float [] opixels;
for (i=0;i<size;i++) {
ipixels= (float[])stack.getPixels(i+1);
opixels=new float [length];
index=0;
for (y=0;y< height;y++) {
base=iWidth*(y+splitParameters.addTop)+splitParameters.addLeft;
for (x=0;x<width;x++) opixels[index++]=ipixels[base++];
}
stack_crop.addSlice(stack.getSliceLabel(i+1), opixels);
}
return stack_crop;
}
/** ======================================================================== */
public ImageStack rotateStack32CW(
ImageStack stack) {
int size=stack.getSize();
int height=stack.getHeight();
int width= stack.getWidth();
int length=width*height;
ImageStack stack_rot = new ImageStack(height, width);
int i,x,y,index;
float [] ipixels;
float [] opixels;
for (i=0;i<size;i++) {
ipixels= (float[])stack.getPixels(i+1);
opixels=new float [length];
index=0;
for (x=0;x<width;x++)for(y=height-1;y>=0;y--) opixels[index++]=ipixels[y*width+x];
stack_rot.addSlice(stack.getSliceLabel(i+1), opixels);
}
return stack_rot;
}
/** ======================================================================== */
public ImagePlus cropImage32(
ImagePlus imp,
EyesisCorrectionParameters.SplitParameters splitParameters) {
int iWidth=imp.getWidth();
int height=imp.getHeight()-splitParameters.addTop-splitParameters.addBottom;
int width= imp.getWidth()-splitParameters.addLeft-splitParameters.addRight;
int length=width*height;
int x,y,index,base;
float [] ipixels=(float[])imp.getProcessor().getPixels();
float [] opixels=new float [length];
index=0;
for (y=0;y< height;y++) {
base=iWidth*(y+splitParameters.addTop)+splitParameters.addLeft;
for (x=0;x<width;x++) opixels[index++]=ipixels[base++];
}
ImageProcessor ip_crop=new FloatProcessor(width,height,opixels,null);
ImagePlus imp_crop = new ImagePlus(imp.getTitle(),ip_crop); // same title?
return imp_crop;
}
/** ======================================================================== */
public ImagePlus rotateImage32CW(
ImagePlus imp) {
int width=imp.getWidth();
int height=imp.getHeight();
int length=width*height;
int x,y,index;
float [] ipixels=(float[])imp.getProcessor().getPixels();
float [] opixels=new float [length];
index=0;
for (x=0;x<width;x++)for(y=height-1;y>=0;y--) opixels[index++]=ipixels[y*width+x];
ImageProcessor ip_rot=new FloatProcessor(height,width,opixels,null);
ImagePlus imp_rot = new ImagePlus(imp.getTitle(),ip_rot); // same title?
return imp_rot;
}
/** ======================================================================== */
public CompositeImage convertToComposite( ImagePlus imp) {
// if (imp.isComposite()) return imp;
if (imp.isComposite()) return null;
if (imp.getNChannels()>1) {
return null; // number of channels should be just 1
}
int c = imp.getStackSize();
imp.setDimensions(c, 1, 1);
CompositeImage ci = new CompositeImage(imp, CompositeImage.COMPOSITE);
// ci.show();
// imp.hide();
return ci;
}
/** ======================================================================== */
public ImageStack convertRGB32toRGB16Stack(
ImageStack stack32,
EyesisCorrectionParameters.RGBParameters rgbParameters) {
ImageStack stack16 = new ImageStack(stack32.getWidth(), stack32.getHeight());
int length=stack32.getWidth()*stack32.getHeight();
int i,j;
float [] fpixels;
short [] spixels;
double [] mins= {rgbParameters.r_min,rgbParameters.g_min,rgbParameters.b_min};
double [] maxs= {rgbParameters.r_max,rgbParameters.g_max,rgbParameters.b_max};
if (stack32.getSize()<3) return null;
double value;
double scale;
for (i=0;i<3;i++) {
fpixels= (float[])stack32.getPixels(i+1);
scale=65535.0/(maxs[i]-mins[i]);
spixels=new short [length];
for (j=0;j<length;j++) {
value=(fpixels[j]-mins[i])*scale;
if (value<0.0) value=0.0;
else if (value>65535.0) value=65535.0;
spixels[j]=(short)(value+0.5);
}
stack16.addSlice(stack32.getSliceLabel(i+1), spixels);
}
return stack16;
}
public ImagePlus convertRGB48toRGB24(
ImageStack stack16,
String title,
int r_min,
int r_max,
int g_min,
int g_max,
int b_min,
int b_max){
int [] mins= {r_min,g_min,b_min};
int [] maxs= {r_max,g_max,b_max};
int i;
int length=stack16.getWidth()*stack16.getHeight();
short [][] spixels=new short[3][];
int [] pixels=new int[length];
int c,d;
double [] scale=new double[3];
for (c=0;c<3;c++) {
scale[c]=256.0/(maxs[c]-mins[c]);
}
for (i=0;i<3;i++) spixels[i]= (short[])stack16.getPixels(i+1);
for (i=0;i<length;i++) {
pixels[i]=0;
for (c=0;c<3;c++) {
d=(int)(((spixels[c][i]& 0xffff)-mins[c])*scale[c]);
if (d>255) d=255;
else if (d<0) d=0;
pixels[i]= d | (pixels[i]<<8);
}
}
ColorProcessor cp=new ColorProcessor(stack16.getWidth(),stack16.getHeight());
cp.setPixels(pixels);
ImagePlus imp=new ImagePlus(title,cp);
return imp;
}
/** ======================================================================== */
public ImagePlus Image32toGreyRGB24(
ImagePlus imp){
int width=imp.getWidth();
int height=imp.getHeight();
int length=width*height;
int i;
float [] ipixels=(float[])imp.getProcessor().getPixels();
int [] pixels=new int[length];
float min=ipixels[0];
float max=ipixels[0];
for (i=0;i<length;i++) {
if (min>ipixels[i])min=ipixels[i];
if (max<ipixels[i])max=ipixels[i];
}
double d= 256.0/(max-min);
int c;
for (i=0;i<length;i++) {
c=(int)((ipixels[i]-min)*d);
if (c>255) c=255;
pixels[i]=c | (c <<8) | (c<<16);
}
ColorProcessor cp=new ColorProcessor(width,height,pixels);
ImagePlus imp_rgb=new ImagePlus(imp.getTitle(),cp);
return imp_rgb;
}
/** ======================================================================== */
/** Combine 2 stacks and a mask */
public ImageStack combineStacksWithMask (ImageStack stack_bg,
ImageStack stack_fg,
// float [] mask ) {
double [] mask ) {
ImageStack stack=new ImageStack(stack_bg.getWidth(),stack_bg.getHeight());
int slice,i;
float [] fpixels;
float [] fpixels_bg;
float [] fpixels_fg;
for (slice=1; slice <=stack_bg.getSize(); slice++) {
fpixels_bg= (float[])stack_bg.getPixels(slice);
fpixels_fg= (float[])stack_fg.getPixels(slice);
fpixels=new float [fpixels_bg.length];
for (i=0;i<fpixels_bg.length;i++) fpixels[i]= (float) (mask[i]*fpixels_fg[i]+(1.0f-mask[i])*fpixels_bg[i]);
stack.addSlice(stack_fg.getSliceLabel(slice), fpixels);
}
return stack;
}
/** ======================================================================== */
public double [] getSlidingMask(int size) { // duplicate with DebayerScissors
double [] mask = new double [size*size];
double [] maskLine=new double [size];
double k=2*Math.PI/size;
int i,j,index;
for (i=0;i<size;i++) maskLine[i]= 0.5*(1.0-Math.cos(i*k));
index=0;
for (i=0;i<size;i++) for (j=0;j<size;j++) mask[index++]=maskLine[i]*maskLine[j];
return mask;
}
/** ======================================================================== */
/** convolve image stack with the kernel stack using FHT. kernels should be (size/2)*(size/2) - currently 64x64, then image will be split into same
(size/2)*(size/2) overlapping by step=size/4 segments. Both are zero-padded to size x size, so after convolution the result will not roll over, and
processed 128x128 result arrays are accumulated in the output stack.
The input image should be properly extended by size/4 in each direction (and so the kernel arrays should match it) - that would minimize border effects.*/
/** ======================================================================== */
public ImageStack convolveStackWithKernelStack (
final ImageStack imageStack, // stack with 3 colors/slices with the image
final ImageStack kernelStack, // stack with 3 colors/slices convolution kernels
final int size, // 128 - fft size, kernel size should be size/2
final int threadsMax, // maximal number of threads to launch
final boolean updateStatus, // update status info
final int globalDebugLevel)
{
if ((imageStack==null) || (kernelStack==null)) return null;
final int imgWidth=imageStack.getWidth();
final int imgHeight=imageStack.getHeight();
final int length=imgWidth*imgHeight;
final int step=size/4;
final int kernelSize=size/2;
final int tilesX=imgWidth/step-1; // horizontal number of overlapping tiles in the source image (should be expanded from the registerd one by "step" in each direction)
final int tilesY=imgHeight/step-1; // vertical number of overlapping tiles in the source image (should be expanded from the registerd one by "step" in each direction)
final int kernelWidth=kernelStack.getWidth();
final int kernelNumHor=kernelWidth/(size/2);
final int nChn=imageStack.getSize();
final float [][] outPixels=new float[nChn][length]; // GLOBAL same as input
// float [][] outPixels=new float[nChn][length]; // same as input
int i,j;
for (i=0;i<nChn;i++) for (j=0;j<length;j++) outPixels[i][j]=0.0f;
final double [] slidingWindow=getSlidingMask(kernelSize); // 64x64
final Thread[] threads = newThreadArray(threadsMax);
final AtomicInteger ai = new AtomicInteger(0);
final int numberOfKernels= tilesY*tilesX*nChn;
final int numberOfKernelsInChn=tilesY*tilesX;
final long startTime = System.nanoTime();
for (int ithread = 0; ithread < threads.length; ithread++) {
threads[ithread] = new Thread() {
public void run() {
float [] pixels=null; // will be initialized at first use
float [] kernelPixels=null; // will be initialized at first use
double [] kernel= new double[kernelSize*kernelSize];
double [] inTile= new double[kernelSize*kernelSize];
double [] outTile= new double[size * size];
double [] doubleKernel= new double[size * size];
int chn,tileY,tileX;
int chn0=-1;
// double debug_sum;
// int i;
DoubleFHT fht_instance =new DoubleFHT(); // provide DoubleFHT instance to save on initializations (or null)
for (int nTile = ai.getAndIncrement(); nTile < numberOfKernels; nTile = ai.getAndIncrement()) {
chn=nTile/numberOfKernelsInChn;
tileY =(nTile % numberOfKernelsInChn)/tilesX;
tileX = nTile % tilesX;
if (tileX==0) {
if (updateStatus) IJ.showStatus("Convolving image with kernels, channel "+(chn+1)+" of "+nChn+", row "+(tileY+1)+" of "+tilesY);
if (globalDebugLevel>2) System.out.println("Processing kernels, channel "+(chn+1)+" of "+nChn+", row "+(tileY+1)+" of "+tilesY+" : "+IJ.d2s(0.000000001*(System.nanoTime()-startTime),3));
}
if (chn!=chn0) {
pixels= (float[]) imageStack.getPixels(chn+1);
kernelPixels=(float[]) kernelStack.getPixels(chn+1);
chn0=chn;
}
/** Read source image tile */
extractSquareTile( pixels, // source pixel array,
inTile, // will be filled, should have correct size before call
slidingWindow, // window (same size as the kernel)
imgWidth, // width of pixels array
tileX*step, // left corner X
tileY*step); // top corner Y
/** zero pad twice the original size*/
outTile=extendFFTInputTo (inTile, size);
/** FHT transform of the source image data*/
fht_instance.swapQuadrants(outTile);
fht_instance.transform( outTile);
/** read convolution kernel */
extractOneKernel(kernelPixels, // array of combined square kernels, each
kernel, // will be filled, should have correct size before call
kernelNumHor, // number of kernels in a row
//tileX*kernelSize, // horizontal number of kernel to extract
//tileY*kernelSize); // vertical number of kernel to extract
tileX, // horizontal number of kernel to extract
tileY); // vertical number of kernel to extract
/** zero pad twice the original size*/
doubleKernel=extendFFTInputTo (kernel, size);
// debug_sum=0;
// for (i=0;i<doubleKernel.length;i++) debug_sum+=doubleKernel[i];
// if (globalDebugLevel>1) System.out.println("kernel sum="+debug_sum);
//if ((tileY==tilesY/2) && (tileX==tilesX/2)) SDFA_INSTANCE.showArrays(doubleKernel,size,size, "doubleKernel-"+chn);
/** FHT transform of the kernel */
fht_instance.swapQuadrants(doubleKernel);
fht_instance.transform( doubleKernel);
/** multiply in frequency domain */
outTile= fht_instance.multiply(outTile, doubleKernel, false);
/** FHT inverse transform of the product - back to space domain */
fht_instance.inverseTransform(outTile);
fht_instance.swapQuadrants(outTile);
/** accumulate result */
//if ((tileY==tilesY/2) && (tileX==tilesX/2)) SDFA_INSTANCE.showArrays(outTile,size,size, "out-"+chn);
/*This is synchronized method. It is possible to make threads to write to non-overlapping regions of the outPixels, but as the accumulation
* takes just small fraction of severtal FHTs, it should be OK - reasonable number of threads will spread and not "stay in line"
*/
accumulateSquareTile(outPixels[chn], // float pixels array to accumulate tile
outTile, // data to accumulate to the pixels array
imgWidth, // width of pixels array
(tileX-1)*step, // left corner X
(tileY-1)*step); // top corner Y
}
}
};
}
startAndJoin(threads);
if (globalDebugLevel > 1) System.out.println("Threads done at "+IJ.d2s(0.000000001*(System.nanoTime()-startTime),3));
/** prepare result stack to return */
ImageStack outStack=new ImageStack(imgWidth,imgHeight);
for (i=0;i<nChn;i++) {
outStack.addSlice(imageStack.getSliceLabel(i+1), outPixels[i]);
}
return outStack;
}
/** Adds zero pixels around the image, "extending canvas" */
public double [][] extendFFTInputTo (double[][] input_pixels,
int newSize) {
double [][] pixels=new double[input_pixels.length][];
int i;
for (i=0;i<pixels.length;i++) pixels[i]= extendFFTInputTo (input_pixels[i], newSize);
return pixels;
}
public double [][] extendFFTInput (double[][] input_pixels,
int subDivFreq) {
double [][] pixels=new double[input_pixels.length][];
int i;
for (i=0;i<pixels.length;i++) pixels[i]= extendFFTInput (input_pixels[i], subDivFreq);
return pixels;
}
public double [] extendFFTInputTo (double[] input_pixels,
int newSize) {
int subDivFreq=newSize/((int)Math.sqrt (input_pixels.length));
return extendFFTInput (input_pixels,
subDivFreq);
}
public double [] extendFFTInput (double[] input_pixels,
int subDivFreq) {
if (input_pixels==null) return null;
int width=(int) Math.sqrt(input_pixels.length);
return extendFFTInput (input_pixels,
width, // width of the image
subDivFreq);
}
public double [] extendFFTInput (double[] input_pixels,
int width, // width of the image
int subDivFreq) {
if (input_pixels==null) return null;
double [] pixels=new double[input_pixels.length*subDivFreq*subDivFreq];
int j,base,x,y;
int height=input_pixels.length/width;
for (j=0;j<pixels.length;j++) pixels[j]=0.0;
j=0;
for (y=0;y<height;y++) {
base=width*(subDivFreq-1)*(width*subDivFreq +1)/2+y*width*subDivFreq;
for (x=0;x<width;x++) pixels[base+x]=input_pixels[j++];
}
return pixels;
}
// duplicates with DebayerScissors
/** ======================================================================== */
/**extract and multiply by window function (same size as kernel itself) */
void extractSquareTile(float [] pixels, // source pixel array,
double [] tile, // will be filled, should have correct size before call
double [] window, // window (same size as the kernel)
int width, // width of pixels array
int x0, // left corner X
int y0) { // top corner Y
int length=tile.length;
int size=(int) Math.sqrt(length);
int i,j,x,y;
int height=pixels.length/width;
int index=0;
for (i=0;i<size;i++) {
y=y0+i;
if ((y>=0) && (y<height)) {
index=i*size;
for (j=0;j<size;j++) {
x=x0+j;
if ((x>=0) && (x<width)) tile [index]=pixels[y*width+x]*window[index];
index++;
}
}
}
}
/** ======================================================================== */
void extractSquareTile(double [] pixels, // source pixel array,
double [] tile, // will be filled, should have correct size before call
double [] window, // window (same size as the kernel)
int width, // width of pixels array
int x0, // left corner X
int y0) { // top corner Y
int length=tile.length;
int size=(int) Math.sqrt(length);
int i,j,x,y;
int height=pixels.length/width;
int index=0;
for (i=0;i<size;i++) {
y=y0+i;
if ((y>=0) && (y<height)) {
index=i*size;
for (j=0;j<size;j++) {
x=x0+j;
if ((x>=0) && (x<width)) tile [index]=pixels[y*width+x]*window[index];
index++;
}
}
}
}
/** ======================================================================== */
/** accumulate square tile to the pixel array (tile may extend beyond the array, will be cropped) */
synchronized void accumulateSquareTile(
float [] pixels, // float pixels array to accumulate tile
double [] tile, // data to accumulate to the pixels array
int width, // width of pixels array
int x0, // left corner X
int y0) { // top corner Y
int length=tile.length;
int size=(int) Math.sqrt(length);
int i,j,x,y;
int height=pixels.length/width;
int index=0;
for (i=0;i<size;i++) {
y=y0+i;
if ((y>=0) && (y<height)) {
index=i*size;
for (j=0;j<size;j++) {
x=x0+j;
if ((x>=0) && (x<width)) pixels[y*width+x]+=tile [index];
index++;
}
}
}
}
synchronized void accumulateSquareTile(
double [] pixels, // float pixels array to accumulate tile
double [] tile, // data to accumulate to the pixels array
int width, // width of pixels array
int x0, // left corner X
int y0) { // top corner Y
int length=tile.length;
int size=(int) Math.sqrt(length);
int i,j,x,y;
int height=pixels.length/width;
int index=0;
for (i=0;i<size;i++) {
y=y0+i;
if ((y>=0) && (y<height)) {
index=i*size;
for (j=0;j<size;j++) {
x=x0+j;
if ((x>=0) && (x<width)) pixels[y*width+x]+=tile [index];
index++;
}
}
}
}
// end of duplicates with DebayerScissors
/** Convert source Bayer pattern (GR/BG) image to higher resolution, add margins by duplicating pattern around */
public ImageStack bayerToStack(ImagePlus imp, // source bayer image, linearized, 32-bit (float))
EyesisCorrectionParameters.SplitParameters splitParameters){
if (imp==null) return null;
// String [] chnNames={"red","blue","green"};
String [] chnNames={"Red","Blue","Green"}; //Different sequence than RGB!!
int nChn=chnNames.length;
ImageProcessor ip=imp.getProcessor();
int inWidth=imp.getWidth();
int inHeight=imp.getHeight();
int outHeight=inHeight*splitParameters.oversample+splitParameters.addTop+splitParameters.addBottom;
int outWidth=inWidth*splitParameters.oversample+splitParameters.addLeft+splitParameters.addRight;
int outLength=outWidth*outHeight;
float [][] outPixels=new float[nChn][outLength];
float [] pixels = (float[]) ip.getPixels();
int chn,y,x,i,index;
int bayerPeriod=2*splitParameters.oversample;
int ovrWidth= inWidth*splitParameters.oversample;
int ovrHeight=inHeight*splitParameters.oversample;
for (chn=0;chn<nChn;chn++) for (i=0;i<outPixels[chn].length;i++) outPixels[chn][i]=0.0f;
/** Can be optimized - now it calculate input address for all those 0-es */
for (index=0; index<outLength; index++) {
y=(index / outWidth)-splitParameters.addTop;
x=(index % outWidth)-splitParameters.addLeft;
if (y<0) y= (bayerPeriod-((-y) % bayerPeriod))%bayerPeriod;
else if (y>=ovrHeight) y= ovrHeight-bayerPeriod +((y-ovrHeight) % bayerPeriod);
if (x<0) x= (bayerPeriod-((-x) % bayerPeriod))%bayerPeriod;
else if (x>=ovrWidth) x= ovrWidth-bayerPeriod +((x-ovrWidth) % bayerPeriod);
if (((y% splitParameters.oversample)==0) && ((x% splitParameters.oversample)==0)) {
x/=splitParameters.oversample;
y/=splitParameters.oversample;
chn=((x&1)==(y&1))?2:(((x&1)!=0)?0:1);
outPixels[chn][index]=pixels[y*inWidth+x];
}
}
/** prepare result stack to return */
ImageStack outStack=new ImageStack(outWidth,outHeight);
for (chn=0;chn<nChn;chn++) {
outStack.addSlice(chnNames[chn], outPixels[chn]);
}
return outStack;
}
//double [] DENOISE_MASK=null;
// TODO: do similar for JP4, using "subcamera" to "use" all channels for it
/** ======================================================================== */
/** Calculate deconvolution kernel (or difference of the two) noise gain
* to be used when calculating mask that selects between deconvolved with
* different kernels
*/
public ImageStack calculateKernelsNoiseGains (
final ImageStack kernelStack1, // first stack with 3 colors/slices convolution kernels
final ImageStack kernelStack2, // second stack with 3 colors/slices convolution kernels (or null)
final int size, // 128 - fft size, kernel size should be size/2
final double blurSigma,
final int threadsMax, // maximal number of threads to launch
final boolean updateStatus,
final int globalDebugLevel) // update status info
{
if (kernelStack1==null) return null;
final boolean useDiff= (kernelStack2 != null);
final int kernelSize=size/2;
final int kernelWidth=kernelStack1.getWidth();
final int kernelNumHor=kernelWidth/(size/2);
final int kernelNumVert=kernelStack1.getHeight()/(size/2);
final int length=kernelNumHor*kernelNumVert;
final int nChn=kernelStack1.getSize();
final float [][] outPixles=new float[nChn][length]; // GLOBAL same as input
int i,j;
for (i=0;i<nChn;i++) for (j=0;j<length;j++) outPixles[i][j]=0.0f;
final Thread[] threads = newThreadArray(threadsMax);
final AtomicInteger ai = new AtomicInteger(0);
final int numberOfKernels= kernelNumHor*kernelNumVert*nChn;
final int numberOfKernelsInChn=kernelNumHor*kernelNumVert;
final long startTime = System.nanoTime();
for (int ithread = 0; ithread < threads.length; ithread++) {
threads[ithread] = new Thread() {
public void run() {
DoubleGaussianBlur gb=null;
if (blurSigma>0) gb=new DoubleGaussianBlur();
float [] kernelPixels1= null; // will be initialized at first use
float [] kernelPixels2= null; // will be initialized at first use
double [] kernel1= new double[kernelSize*kernelSize];
double [] kernel2= new double[kernelSize*kernelSize];
int chn,tileY,tileX;
int chn0=-1;
int i;
double sum;
for (int nTile = ai.getAndIncrement(); nTile < numberOfKernels; nTile = ai.getAndIncrement()) {
chn=nTile/numberOfKernelsInChn;
tileY =(nTile % numberOfKernelsInChn)/kernelNumHor;
tileX = nTile % kernelNumHor;
if (tileX==0) {
if (updateStatus) IJ.showStatus("Processing kernels, channel "+(chn+1)+" of "+nChn+", row "+(tileY+1)+" of "+kernelNumVert);
if (globalDebugLevel>2) System.out.println("Processing kernels, channel "+(chn+1)+" of "+nChn+", row "+(tileY+1)+" of "+kernelNumVert+" : "+IJ.d2s(0.000000001*(System.nanoTime()-startTime),3));
}
if (chn!=chn0) {
kernelPixels1=(float[]) kernelStack1.getPixels(chn+1);
if (useDiff) kernelPixels2=(float[]) kernelStack2.getPixels(chn+1);
chn0=chn;
}
/** read convolution kernel */
extractOneKernel(kernelPixels1, // array of combined square kernels, each
kernel1, // will be filled, should have correct size before call
kernelNumHor, // number of kernels in a row
tileX, // horizontal number of kernel to extract
tileY); // vertical number of kernel to extract
/** optionally read the second convolution kernel */
if (useDiff) {extractOneKernel(kernelPixels2, // array of combined square kernels, each
kernel2, // will be filled, should have correct size before call
kernelNumHor, // number of kernels in a row
tileX, // horizontal number of kernel to extract
tileY); // vertical number of kernel to extract
for (i=0; i<kernel1.length;i++) kernel1[i]-=kernel2[i];
}
if (blurSigma>0) gb.blurDouble(kernel1, kernelSize, kernelSize, blurSigma, blurSigma, 0.01);
/** Calculate sum of squared kernel1 elements */
sum=0.0;
for (i=0; i<kernel1.length;i++) sum+=kernel1[i]*kernel1[i];
outPixles[chn][tileY*kernelNumHor+tileX]= (float) (Math.sqrt(sum));
// System.out.println("Processing kernels, channel "+(chn+1)+" of "+nChn+", row "+(tileY+1)+" of "+kernelNumVert+" sum="+sum);
}
}
};
}
startAndJoin(threads);
if (globalDebugLevel > 1) System.out.println("Threads done at "+IJ.d2s(0.000000001*(System.nanoTime()-startTime),3));
/** prepare result stack to return */
ImageStack outStack=new ImageStack(kernelNumHor,kernelNumVert);
for (i=0;i<nChn;i++) {
outStack.addSlice(kernelStack1.getSliceLabel(i+1), outPixles[i]);
}
return outStack;
}
void extractOneKernel(float [] pixels, // array of combined square kernels, each
double [] kernel, // will be filled, should have correct size before call
int numHor, // number of kernels in a row
int xTile, // horizontal number of kernel to extract
int yTile) { // vertical number of kernel to extract
int length=kernel.length;
int size=(int) Math.sqrt(length);
int i,j;
int pixelsWidth=numHor*size;
int pixelsHeight=pixels.length/pixelsWidth;
int numVert=pixelsHeight/size;
/** limit tile numbers - effectively add margins around the known kernels */
if (xTile<0) xTile=0;
else if (xTile>=numHor) xTile=numHor-1;
if (yTile<0) yTile=0;
else if (yTile>=numVert) yTile=numVert-1;
int base=(yTile*pixelsWidth+xTile)*size;
for (i=0;i<size;i++) for (j=0;j<size;j++) kernel [i*size+j]=pixels[base+i*pixelsWidth+j];
}
/** Extract noise mask (proportional to noise gain of the kernels), the denoise mask should be divided by this
*
*/
public double [][] extractNoiseMask(
ImagePlus imp,// contains 3-slice stack (r,b,g)
double k0, // coefficient for slice 0 (r)
double k1, // coefficient for slice 1 (b)
double k2, // coefficient for slice 2 (g)
int decim, // decimate result (not yet supported)
double gainPower
){
if (imp==null) return null;
if (gainPower==0.0) return null; // do not use noise gain correction
ImageStack stack=imp.getStack();
int width=stack.getWidth();
int height=stack.getHeight();
double [][]mask=new double[height*decim][width*decim];
float [][] pixels= new float [3][];
pixels[0]=(float[]) stack.getPixels(1);
pixels[1]=(float[]) stack.getPixels(2);
pixels[2]=(float[]) stack.getPixels(3);
int i,j,x,y,indx;
for (y=0;y<height;y++) for (x=0;x<width;x++) {
indx=x+y*width;
for (i=0;i<decim;i++) for (j=0;j<decim;j++) {
mask[y*decim+i][x*decim+j]=Math.pow(k0*pixels[0][indx]+k1*pixels[1][indx]+k2*pixels[2][indx],gainPower);
}
}
return mask;
}
/** ======================================================================== */
private void saveAndShow(
ImagePlus imp,
EyesisCorrectionParameters.CorrectionParameters correctionsParameters){
saveAndShowEnable( imp, correctionsParameters , true, true);
}
private void saveAndShowEnable(
ImagePlus imp,
EyesisCorrectionParameters.CorrectionParameters correctionsParameters,
boolean enableSave,
boolean enableShow){
saveAndShow(
imp,
correctionsParameters,
correctionsParameters.save && enableSave,
correctionsParameters.show && enableShow,
correctionsParameters.JPEG_quality);
}
private void saveAndShow(
ImagePlus imp,
EyesisCorrectionParameters.CorrectionParameters correctionsParameters,
boolean save,
boolean show){
saveAndShow(imp, correctionsParameters, save, show, -1);
}
private void saveAndShow(
ImagePlus imp,
EyesisCorrectionParameters.CorrectionParameters correctionsParameters,
boolean save,
boolean show,
int jpegQuality){// <0 - keep current, 0 - force Tiff, >0 use for JPEG
String path=null;
if (save) path= correctionsParameters.selectResultsDirectory(
true, // smart,
true); //newAllowed, // save
if (path!=null) {
path+=Prefs.getFileSeparator()+imp.getTitle();
if (((imp.getStackSize()==1)) && (jpegQuality!=0) && ((imp.getFileInfo().fileType== FileInfo.RGB) || (jpegQuality>0))) {
if (this.debugLevel>0) System.out.println("Saving result to "+path+".jpeg");
FileSaver fs=new FileSaver(imp);
if (jpegQuality>0) FileSaver.setJpegQuality(jpegQuality);
fs.saveAsJpeg(path+".jpeg");
}
else {
if (this.debugLevel>0) System.out.println("Saving result to "+path+".tiff");
FileSaver fs=new FileSaver(imp);
if (imp.getStackSize()>1) fs.saveAsTiffStack(path+".tiff");
else fs.saveAsTiff(path+".tiff");
}
}
if (show) {
imp.getProcessor().resetMinAndMax(); // probably not needed
imp.show();
}
}
/*
private void saveAndShow(
CompositeImage compositeImage,
EyesisCorrectionParameters.ProcessParameters processParameters,
EyesisCorrectionParameters.CorrectionParameters correctionsParameters){
saveAndShow(compositeImage, processParameters, correctionsParameters , true, true);
}
private void saveAndShow(
CompositeImage compositeImage,
EyesisCorrectionParameters.ProcessParameters processParameters,
EyesisCorrectionParameters.CorrectionParameters correctionsParameters,
boolean enableSave,
boolean enableShow){
saveAndShow(
compositeImage,
correctionsParameters,
processParameters.save && enableSave,
processParameters.show && enableShow);
}
*/
private void saveAndShow(
CompositeImage compositeImage,
EyesisCorrectionParameters.CorrectionParameters correctionsParameters,
boolean save,
boolean show){
String path=null;
if (save) path= correctionsParameters.selectResultsDirectory(
true, // smart,
true); //newAllowed, // save
if (path!=null) {
path+=Prefs.getFileSeparator()+compositeImage.getTitle();
if (this.debugLevel>0) System.out.println("Saving result to "+path+".tiff");
FileSaver fs=new FileSaver(compositeImage);
if (compositeImage.getStackSize()>1) fs.saveAsTiffStack(path+".tiff");
else fs.saveAsTiff(path+".tiff");
}
if (show) {
compositeImage.show();
}
}
/** ======================================================================== */
/** 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
*/
public 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);
}
}
}
/**
** -----------------------------------------------------------------------------**
** EyesisTiff.java
**
** Writes Tiff files suitable for Emblend, preserve ImageJ Info data
** Uses bioformat library
**
**
** Copyright (C) 2012 Elphel, Inc.
**
** -----------------------------------------------------------------------------**
**
** EyesisTiff.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/>.
** -----------------------------------------------------------------------------**
**
*/
import java.awt.Frame;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Array;
import java.util.Arrays;
//import org.apache.log4j.Logger;
import ij.IJ;
import ij.ImagePlus;
import ij.WindowManager;
import ij.io.FileInfo;
import loci.common.RandomAccessInputStream;
import loci.common.services.DependencyException;
import loci.common.services.ServiceException;
import loci.formats.FormatException;
import loci.formats.tiff.IFD;
import loci.formats.tiff.IFDList;
import loci.formats.tiff.TiffParser;
import loci.formats.tiff.TiffRational;
import loci.formats.tiff.TiffSaver;
public class EyesisTiff {
// private static org.apache.log4j.Logger log= Logger.getLogger(EyesisTiff.class);
public EyesisTiff(){
// Please initialize the log4j system properly
}
public void saveTiff(
ImagePlus imp,
String path,
int mode, // 0 - 8-bit, 1 - 16-bit, 2 - 32-bit unsigned, 3 - 32FP (only if source image is a 4-stack of FP)
double scale, // full scale, absolute
boolean imageJTags,
int debugLevel
) throws IOException, FormatException, ServiceException, DependencyException{
if (imp.getType()==ImagePlus.COLOR_RGB) {
if (debugLevel>1) System.out.println("Saving 8-bit TIFF with alpha-channel: "+path);
saveTiffARGB32(imp, path, imageJTags, debugLevel);
return;
} else if (imp.getStackSize()==4) {
if (debugLevel>1) System.out.println("Saving 32-bit float TIFF with alpha-channel: "+path);
saveTiffARGB(imp, path, mode, scale, imageJTags,debugLevel);
return;
}
IJ.showMessage("Not yet implemented for this image type");
}
public void saveTiffARGB(
ImagePlus imp,
String path,
int mode, // 0 - 8-bit, 1 - 16-bit, 2 - 32-bit unsigned, 3 - 32FP (only if source image is a 4-stack of FP)
double scale, // full scale, absolute
boolean imageJTags,
int debugLevel
) throws IOException, FormatException, ServiceException, DependencyException{
int IFDImageFullWidth= 0x8214; // not defined in loci.formats.tiff.IFD
int IFDImageFullLength=0x8215; // not defined in loci.formats.tiff.IFD
int IFDImageJByteCounts= 0xc696; // was array {12( if no slices, roi, etc.), bytes in info}
int IFDImageJInfo= 0xc697; // ImageJ info, starting with magic IJIJinfo,
byte [] ImageJInfoMagic={73,74,73,74,105,110,102,111,0,0,0,1};
int pixelsDenominator=1000;
String description=(imp.getProperty("description")!=null)?((String) imp.getProperty("description")):"Elphel Eyesis4pi";
// int [] iPixels= (int []) imp.getProcessor().getPixels();
final float [][] imagePixels= new float [4][];
for (int c=0;c<imagePixels.length;c++) imagePixels[c]= (float []) imp.getStack().getPixels(c+1); // bytes are little endian
int bw;
byte [] bytes;
int pIndex=0;
// double s;
int pixelType=0; //int pixelType:INT8, UINT8, INT16, UINT16, INT32, UINT32, FLOAT, BIT, DOUBLE, COMPLEX, DOUBLECOMPLEX; - used to find bpp
switch (mode){
case 0:
int maxVal= 0xff;
double [] s0 ={scale*maxVal,scale*maxVal,scale*maxVal,maxVal};
bw= 1;
pixelType=0; //1; //0; //3; // 0; // should it be 3?
bytes=new byte[imagePixels[0].length*4*bw];
for (int i=0;i<imagePixels[0].length;i++) for (int c=0;c<imagePixels.length;c++){ // r,g,b,alpha
int d= (int) (imagePixels[c][i]*s0[c]);
if (d<0) d=0;
else if (d>maxVal) d= maxVal;
bytes[pIndex++]=(byte) ((d>> 0 )& 0xff);
}
break;
case 1:
int iMaxVal= 0xffff;
double [] s1 ={scale*iMaxVal,scale*iMaxVal,scale*iMaxVal,iMaxVal};
bw= 2;
pixelType=2; //3; //4;
bytes=new byte[imagePixels[0].length*4*bw];
for (int i=0;i<imagePixels[0].length;i++) for (int c=0;c<imagePixels.length;c++){ // r,g,b,alpha
int d= (int) (imagePixels[c][i]*s1[c]);
if (d<0) d=0;
else if (d>iMaxVal) d= iMaxVal;
bytes[pIndex++]=(byte) ((d>> 8 )& 0xff);
bytes[pIndex++]=(byte) ((d>> 0 )& 0xff);
}
break;
case 2:
long lMaxVal= 0xffffffffL;
double [] s2 ={scale*lMaxVal,scale*lMaxVal,scale*lMaxVal,lMaxVal};
bw= 3; //4;
pixelType=5; // 2; //5;
bytes=new byte[imagePixels[0].length*4*bw];
for (int i=0;i<imagePixels[0].length;i++) for (int c=0;c<imagePixels.length;c++){ // r,g,b,alpha
long d= (long) (imagePixels[c][i]*s2[c]);
if (d<0) d=0;
else if (d>lMaxVal) d= lMaxVal;
bytes[pIndex++]=(byte) ((d>>24 )& 0xff);
bytes[pIndex++]=(byte) ((d>>16 )& 0xff);
bytes[pIndex++]=(byte) ((d>> 8 )& 0xff);
bytes[pIndex++]=(byte) ((d>> 0 )& 0xff);
}
break;
case 3:
bw= 4;
pixelType=6;
bytes=new byte[imagePixels[0].length*4*bw];
// what scale should be for alpha 0..1 or 0.. scale?
for (int i=0;i<imagePixels[0].length;i++) for (int c=0;c<imagePixels.length;c++){ // r,g,b,alpha
int d;
if (scale==1.0) d=Float.floatToIntBits(imagePixels[c][i]);
else d=Float.floatToIntBits((float) (imagePixels[c][i]*scale));
bytes[pIndex++]=(byte) ((d>>24 )& 0xff);
bytes[pIndex++]=(byte) ((d>>16 )& 0xff);
bytes[pIndex++]=(byte) ((d>> 8 )& 0xff);
bytes[pIndex++]=(byte) ((d>> 0 )& 0xff);
}
break;
default:
IJ.error("saveTiffARGBFloat32", "Unsupported output format mode ="+mode);
return;
}
// System.out.println("saveTiffARGBFloat32(): mode="+mode+" pixelType="+pixelType+" bw="+bw);
IFD ifd=new IFD();
ifd.put(new Integer(IFD.LITTLE_ENDIAN), new Boolean(false));
// ifd.put(new Integer(IFD.LITTLE_ENDIAN), new Boolean(true));
ifd.put(new Integer(IFD.IMAGE_WIDTH), imp.getWidth());
ifd.put(new Integer(IFD.IMAGE_LENGTH), imp.getHeight());
ifd.put(new Integer(IFD.SAMPLES_PER_PIXEL), 4);
ifd.putIFDValue(IFD.SOFTWARE, "Elphel Eyesis");
ifd.putIFDValue(IFD.IMAGE_DESCRIPTION, description);
// copy some other data?
ifd.putIFDValue(IFD.COMPRESSION, 1); //TiffCompression.UNCOMPRESSED);
ifd.putIFDValue(IFD.PHOTOMETRIC_INTERPRETATION,2); // RGB
ifd.putIFDValue(IFD.EXTRA_SAMPLES,2); // 0 = Unspecified data 1 = Associated alpha data (with pre-multiplied color) 2 = Unassociated alpha data
// int [] bpsArray={8,8,8,8};
// ifd.putIFDValue(IFD.BITS_PER_SAMPLE, bpsArray); // will be done automatically
if (imp.getProperty("XPosition")!=null) {
ifd.putIFDValue(IFD.X_POSITION,
new TiffRational((int) Math.round(pixelsDenominator*Double.parseDouble((String) imp.getProperty("XPosition"))) , pixelsDenominator));
}
if (imp.getProperty("YPosition")!=null) {
ifd.putIFDValue(IFD.Y_POSITION,
new TiffRational((int) Math.round(pixelsDenominator*Double.parseDouble((String) imp.getProperty("YPosition"))) , pixelsDenominator));
}
if (imp.getProperty("ImageFullWidth")!=null){
ifd.putIFDValue(IFDImageFullWidth, (long) Integer.parseInt((String) imp.getProperty("ImageFullWidth")));
}
if (imp.getProperty("ImageFullLength")!=null){
ifd.putIFDValue(IFDImageFullLength, (long) Integer.parseInt((String) imp.getProperty("ImageFullLength")));
}
//TODO: Seems to match ImageJ Info, but it is not recognized :-(
if (imageJTags && (imp.getProperty("Info")!=null) && (imp.getProperty("Info") instanceof String)){
int skipFirstBytes=2;
String info=(String) imp.getProperty("Info");
byte [] bInfoBody=info.getBytes("UTF-16");
int [] bInfo = new int [ImageJInfoMagic.length+bInfoBody.length-skipFirstBytes];
int index=0;
for (int i=0;i<ImageJInfoMagic.length;i++) bInfo[index++]=ImageJInfoMagic[i];
for (int i=skipFirstBytes;i<bInfoBody.length; i++) bInfo[index++]=bInfoBody[i]; // first 2 bytes {-2, -1} ???
long [] imageJcounts={12, bInfoBody.length-skipFirstBytes};
ifd.putIFDValue(IFDImageJByteCounts, imageJcounts);
ifd.putIFDValue(IFDImageJInfo, bInfo);
}
(new File(path)).delete(); // Otherwise TiffSaver appends!
TiffSaver tiffSaver = new TiffSaver(path);
tiffSaver.setWritingSequentially(true);
tiffSaver.setLittleEndian(false);
tiffSaver.writeHeader();
// tiffSaver.writeIFD(ifd,0); //** SHould not write here, some fields are calculated during writeImage, that writes IFD too
// System.out.println("bytes.length="+bytes.length);
tiffSaver.writeImage(bytes,
ifd,
0, //int no,
pixelType, // 0, //int pixelType:INT8, INT16, INT32, UINT8, UINT16, UINT32, FLOAT, BIT, DOUBLE, COMPLEX, DOUBLECOMPLEX; - used to find bpp
true); // boolean last)
}
public void saveTiffARGB32(
ImagePlus imp,
String path,
boolean imageJTags,
int debugLevel
) throws IOException, FormatException, ServiceException, DependencyException{
int IFDImageFullWidth= 0x8214; // not defined in loci.formats.tiff.IFD
int IFDImageFullLength=0x8215; // not defined in loci.formats.tiff.IFD
// public static final int META_DATA_BYTE_COUNTS = 50838; // private tag registered with Adobe
// public static final int META_DATA = 50839; // private tag registered with Adobe
int IFDImageJByteCounts= 0xc696; // was array {12( if no slices, roi, etc.), bytes in info}
int IFDImageJInfo= 0xc697; // ImageJ info, starting with magic IJIJinfo,
byte [] ImageJInfoMagic={73,74,73,74,105,110,102,111,0,0,0,1};
int pixelsDenominator=1000;
String description=(imp.getProperty("description")!=null)?((String) imp.getProperty("description")):"Elphel Eyesis4pi";
int [] iPixels= (int []) imp.getProcessor().getPixels();
byte [] bytes=new byte[iPixels.length*4];
int pIndex=0;
for (int i=0;i<iPixels.length;i++){
bytes[pIndex++]=(byte) ((iPixels[i]>>16)& 0xff); // R
bytes[pIndex++]=(byte) ((iPixels[i]>> 8)& 0xff); // G
bytes[pIndex++]=(byte) ((iPixels[i]>> 0)& 0xff); // B
bytes[pIndex++]=(byte) ((iPixels[i]>>24)& 0xff); // alpha
}
IFD ifd=new IFD();
ifd.put(new Integer(IFD.LITTLE_ENDIAN), new Boolean(false));
// ifd.put(new Integer(IFD.LITTLE_ENDIAN), new Boolean(true));
ifd.put(new Integer(IFD.IMAGE_WIDTH), imp.getWidth());
ifd.put(new Integer(IFD.IMAGE_LENGTH), imp.getHeight());
ifd.put(new Integer(IFD.SAMPLES_PER_PIXEL), 4);
ifd.putIFDValue(IFD.SOFTWARE, "Elphel Eyesis");
ifd.putIFDValue(IFD.IMAGE_DESCRIPTION, description);
// copy some other data?
ifd.putIFDValue(IFD.COMPRESSION, 1); //TiffCompression.UNCOMPRESSED);
ifd.putIFDValue(IFD.PHOTOMETRIC_INTERPRETATION,2); // RGB
ifd.putIFDValue(IFD.EXTRA_SAMPLES,2); // extra bytes (over 3) meaning Unassociated alpha data
// int [] bpsArray={8,8,8,8};
// ifd.putIFDValue(IFD.BITS_PER_SAMPLE, bpsArray); // will be done automatically
if (imp.getProperty("XPosition")!=null) {
ifd.putIFDValue(IFD.X_POSITION,
new TiffRational((int) Math.round(pixelsDenominator*Double.parseDouble((String) imp.getProperty("XPosition"))) , pixelsDenominator));
}
if (imp.getProperty("YPosition")!=null) {
ifd.putIFDValue(IFD.Y_POSITION,
new TiffRational((int) Math.round(pixelsDenominator*Double.parseDouble((String) imp.getProperty("YPosition"))) , pixelsDenominator));
}
if (imp.getProperty("ImageFullWidth")!=null){
ifd.putIFDValue(IFDImageFullWidth, (long) Integer.parseInt((String) imp.getProperty("ImageFullWidth")));
}
if (imp.getProperty("ImageFullLength")!=null){
ifd.putIFDValue(IFDImageFullLength, (long) Integer.parseInt((String) imp.getProperty("ImageFullLength")));
}
//TODO: Seems to match ImageJ Info, but it is not recognized :-(
if (imageJTags && (imp.getProperty("Info")!=null) && (imp.getProperty("Info") instanceof String)){
int skipFirstBytes=2;
String info=(String) imp.getProperty("Info");
byte [] bInfoBody=info.getBytes("UTF-16");
int [] bInfo = new int [ImageJInfoMagic.length+bInfoBody.length-skipFirstBytes];
int index=0;
for (int i=0;i<ImageJInfoMagic.length;i++) bInfo[index++]=ImageJInfoMagic[i];
for (int i=skipFirstBytes;i<bInfoBody.length; i++) bInfo[index++]=bInfoBody[i]; // first 2 bytes {-2, -1} ???
/*
StringBuffer sb=new StringBuffer("bInfo: ");
for (int i=0;i<bInfo.length;i++) sb.append(bInfo[i]+" ");
System.out.println(sb.toString());
sb=new StringBuffer("ImageJInfoMagic: ");
for (int i=0;i<ImageJInfoMagic.length;i++) sb.append(ImageJInfoMagic[i]+" ");
System.out.println(sb.toString());
sb=new StringBuffer("bInfoBody: ");
for (int i=0;i<bInfoBody.length;i++) sb.append(bInfoBody[i]+" ");
System.out.println(sb.toString());
System.out.println("info[0]="+info.charAt(0));
System.out.println("info[1]="+info.charAt(1));
System.out.println("info[2]="+info.charAt(2));
*/
long [] imageJcounts={12, bInfoBody.length-skipFirstBytes};
ifd.putIFDValue(IFDImageJByteCounts, imageJcounts);
ifd.putIFDValue(IFDImageJInfo, bInfo);
}
(new File(path)).delete(); // Otherwise TiffSaver appends!
TiffSaver tiffSaver = new TiffSaver(path);
tiffSaver.setWritingSequentially(true);
tiffSaver.setLittleEndian(false);
tiffSaver.writeHeader();
// tiffSaver.writeIFD(ifd,0); //** SHould not write here, some fields are calculated during writeImage, that writes IFD too
System.out.println("bytes.length="+bytes.length);
tiffSaver.writeImage(bytes,
ifd,
0, //int no,
0, //int pixelType:INT8, INT16, INT32, UINT8, UINT16, UINT32, FLOAT, BIT, DOUBLE, COMPLEX, DOUBLECOMPLEX; - used to find bpp
true); // boolean last)
}
public void propertiesTiff(ImagePlus imp){
FileInfo fi = imp.getOriginalFileInfo();
if ((fi==null) ||(fi.directory==null) || (fi.fileFormat!=FileInfo.TIFF)) {
IJ.error("TIFF Dumper", "File path not available or not TIFF file");
return;
}
String path = fi.directory + fi.fileName;
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();
}
public static void dumpIFDs(String path) throws IOException {
IJ.showStatus("Parsing IFDs");
RandomAccessInputStream in = new RandomAccessInputStream(path);
//TiffParser parser = new TiffParser(in);
TiffParser parser = new TiffParser(in);
IFDList ifdList = parser.getIFDs();
IJ.showStatus("");
for (IFD ifd : ifdList) {
for (Integer key : ifd.keySet()) {
int k = key.intValue();
String name = IFD.getIFDTagName(k)+String.format("(%d [0x%x])", k,k);
String value = prettyValue(ifd.getIFDValue(k), 0);
IJ.log(name + " = " + value);
}
}
in.close();
}
private static String prettyValue(Object value, int indent) {
if (!value.getClass().isArray()) return value.toString()+" ("+value.getClass().toString()+")";
char[] spaceChars = new char[indent];
Arrays.fill(spaceChars, ' ');
String spaces = new String(spaceChars);
StringBuilder sb = new StringBuilder();
sb.append("{\n");
for (int i=0; i<Array.getLength(value); i++) {
sb.append(spaces);
sb.append(" ");
Object component = Array.get(value, i);
sb.append(prettyValue(component, indent + 2));
sb.append("\n");
}
sb.append(spaces);
sb.append("}");
byte [] bstring=new byte [Array.getLength(value)];
for (int i=0;i<bstring.length;i++) bstring[i]= (byte) Integer.parseInt(Array.get(value, i).toString());
// String astring=new String((byte []) value);
String astring="";
try {
astring = new String(bstring,"UTF-16");
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
sb.append("\n\""+astring+"\"");
return sb.toString();
}
}
This source diff could not be displayed because it is too large. You can view the blob instead.
/**
** -----------------------------------------------------------------------------**
** Goniometer.java
**
** Measurements in the "goniometer" machine
**
**
** Copyright (C) 2010 Elphel, Inc.
**
** -----------------------------------------------------------------------------**
**
** Goniometer.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/>.
** -----------------------------------------------------------------------------**
**
*/
import java.util.Properties;
import java.util.concurrent.atomic.AtomicInteger;
import ij.IJ;
import ij.ImagePlus;
import ij.gui.GenericDialog;
public class Goniometer {
/*
horizontal axis:
131 * 244 * 64 = 2045696
244 - worm gear
131 - motor
64 - pulses per revolution
5682.48889 per degree
*/
private showDoubleFloatArrays sdfaInstance = new showDoubleFloatArrays(); // just
// for
// debugging
public CalibrationHardwareInterface.CamerasInterface cameras = null;
// public CalibrationHardwareInterface.LaserPointers lasers = null;
// public static CalibrationHardwareInterface.FocusingMotors motorsS=null;
// public Distortions.DistortionProcessConfiguration
// distortionProcessConfiguration=null;
// public LensAdjustment.FocusMeasurementParameters focusMeasurementParameters = null;
// public Distortions.PatternParameters patternParameters=null;
// public Distortions.LensDistortionParameters
// lensDistortionParameters=null;
// public MatchSimulatedPattern.DistortionParameters distortion = null;
public MatchSimulatedPattern.DistortionParameters distortionParametersDefault=null;
public Distortions.EyesisCameraParameters eyesisCameraParameters = null;
public MatchSimulatedPattern[] matchSimulatedPatterns = null; // =new
// MatchSimulatedPattern();
public MatchSimulatedPattern.LaserPointer laserPointers = null;
MatchSimulatedPattern.PatternDetectParameters patternDetectParameters=null;
public SimulationPattern.SimulParameters simulParametersDefault=null;
public Goniometer.GoniometerParameters goniometerParameters = null;
public Distortions.DistortionProcessConfiguration distortionProcessConfiguration=null;
public int lastScanStep=-1;
public int debugLevel = 2;
public double bottomRollerTilt=60.0; // decrease scan step if tilt is above this
public double bottomRollersClearance=36.0; // angular clearance between the two bottom rollers
public Goniometer(CalibrationHardwareInterface.CamerasInterface cameras,
MatchSimulatedPattern.DistortionParameters distortionParametersDefault,
// MatchSimulatedPattern.DistortionParameters distortion,
MatchSimulatedPattern.PatternDetectParameters patternDetectParameters,
Distortions.EyesisCameraParameters eyesisCameraParameters,
MatchSimulatedPattern.LaserPointer laserPointers,
SimulationPattern.SimulParameters simulParametersDefault,
Goniometer.GoniometerParameters goniometerParameters,
Distortions.DistortionProcessConfiguration distortionProcessConfiguration
) {
this.cameras = cameras;
this.distortionParametersDefault = distortionParametersDefault;
// this.distortion = distortion;
this.patternDetectParameters=patternDetectParameters;
this.eyesisCameraParameters = eyesisCameraParameters;
this.laserPointers = laserPointers;
this.simulParametersDefault=simulParametersDefault;
this.goniometerParameters=goniometerParameters;
this.distortionProcessConfiguration=distortionProcessConfiguration;
}
//goniometerMotors
public boolean scanAndAcquire(
double targetAngleHorizontal,
double targetAngleVertical,
AtomicInteger stopRequested, // 1 - stop now, 2 - when convenient
boolean updateStatus
){
int tiltMotor=2; // 0-1-2
int axialMotor=1; // 0-1-2
int [] motors=this.goniometerParameters.goniometerMotors.updateMotorsPosition();
double thisTilt= motors[tiltMotor]/this.goniometerParameters.goniometerMotors.stepsPerDegreeTilt;
double thisAxial=motors[axialMotor]/this.goniometerParameters.goniometerMotors.stepsPerDegreeAxial;
double scanOverlapVertical=this.goniometerParameters.scanOverlapVertical;
double scanOverlapHorizontal=this.goniometerParameters.scanOverlapHorizontal;
boolean reverseAxial=(this.goniometerParameters.scanLimitAxialStart>this.goniometerParameters.scanLimitAxialEnd);
double scanLimitAxialLow= reverseAxial?this.goniometerParameters.scanLimitAxialEnd:this.goniometerParameters.scanLimitAxialStart;
double scanLimitAxialHigh= reverseAxial?this.goniometerParameters.scanLimitAxialStart:this.goniometerParameters.scanLimitAxialEnd;
boolean zenithToNadir=this.goniometerParameters.scanLatitudeHigh<this.goniometerParameters.scanLatitudeLow;
double scanLatitudeHigh=zenithToNadir? this.goniometerParameters.scanLatitudeLow: this.goniometerParameters.scanLatitudeHigh;
double scanLatitudeLow= zenithToNadir? this.goniometerParameters.scanLatitudeHigh:this.goniometerParameters.scanLatitudeLow;
double scanStepTilt= targetAngleVertical* (1.0-scanOverlapVertical);
double scanStepAxial=targetAngleHorizontal*(1.0-scanOverlapHorizontal); // valid at equator
if (this.debugLevel>1) System.out.println("scanStepTilt="+IJ.d2s(scanStepTilt,2)+", scanStepAxial="+IJ.d2s(scanStepAxial,2));
int numTiltSteps=(int) Math.ceil((scanLatitudeHigh-scanLatitudeLow)/scanStepTilt); // includes first and last
if (numTiltSteps>0){ // increase vertical overlap to make it same for all images
scanStepTilt=(scanLatitudeHigh-scanLatitudeLow)/numTiltSteps;
scanOverlapVertical=1.0-(scanStepTilt/targetAngleVertical);
}
if (this.debugLevel>1) System.out.println("Updated scanStepTilt="+IJ.d2s(scanStepTilt,2)+", scanOverlapVertical="+IJ.d2s(scanOverlapVertical,2));
double [] tilts=new double [numTiltSteps];
double [][] rots= new double [numTiltSteps][];
boolean dirAxial=reverseAxial;
int numStops=0;
for (int i=0;i<numTiltSteps;i++){
int tiltIndex= zenithToNadir?(numTiltSteps-i):i;
double tilt=-(scanLatitudeLow+tiltIndex*scanStepTilt+ 0.5*(1.0-scanOverlapVertical)*targetAngleVertical);
tilts [i]=tilt;
double tiltL=tilt-0.5*targetAngleVertical;
double tiltH=tilt+0.5*targetAngleVertical;
if (tiltL<-90.0) tiltL=-180.0-tiltL;
if (tiltH> 90.0) tiltH= 180.0-tiltH;
double minAbsTilt=Math.min(Math.abs(tiltL),Math.abs(tiltH));
if ((tiltL*tiltH)<0) minAbsTilt=0.0;
double cosMinAbsTilt=Math.cos(minAbsTilt*Math.PI/180.0);
if (this.debugLevel>2) System.out.println("tilt="+IJ.d2s(tilt,2)+"tiltL="+IJ.d2s(tiltL,2)+", tiltH="+IJ.d2s(tiltH,2)+
", minAbsTilt="+IJ.d2s(minAbsTilt,2)+", cosMinAbsTilt="+IJ.d2s(cosMinAbsTilt,2));
// double axialRange=
double scanStep=scanStepAxial;
double overlap=scanOverlapHorizontal;
int numAxialSteps=(int) Math.ceil((scanLimitAxialHigh-scanLimitAxialLow)*cosMinAbsTilt/scanStepAxial);
// Correction for bottom rollers that block view - if tilt is above 60degrees (positive - looking higher). The clear angle is ~36%
if (tilts[i]>this.bottomRollerTilt){
int numForRollers=(int) Math.ceil((scanLimitAxialHigh-scanLimitAxialLow)/(this.bottomRollersClearance*(1.0-scanOverlapHorizontal)));
if (numForRollers>numAxialSteps){
if (this.debugLevel>0){
System.out.println("Increasing number of steps to mitigate occlusion by the bottom rollers. Original number of steps: "+
numAxialSteps+", increased: "+numForRollers);
}
numAxialSteps=numForRollers;
}
}
// spread evenly
if (numAxialSteps>0){ // increase vertical overlap to make it same for all images
// scanStep=(scanLimitAxialHigh-scanLimitAxialLow)*cosMinAbsTilt/numAxialSteps;
// overlap=1.0-(scanStep/targetAngleHorizontal);
scanStep=(scanLimitAxialHigh-scanLimitAxialLow)/numAxialSteps;
overlap=1.0-(scanStep*cosMinAbsTilt/targetAngleHorizontal);
}
rots[i]=new double[numAxialSteps];
for (int j=0;j<numAxialSteps;j++){
int axialIndex= dirAxial?(numAxialSteps-j):j;
double axial=(scanLimitAxialLow+axialIndex*scanStep+ 0.5*(1.0-overlap)*targetAngleHorizontal/cosMinAbsTilt);
rots[i][j]=axial;
}
if (this.goniometerParameters.scanBidirectional) dirAxial=!dirAxial;
numStops+=numAxialSteps;
}
if (this.debugLevel>0){
System.out.println("First tilt: "+IJ.d2s(tilts[0],1)+" last tilt: "+IJ.d2s(tilts[numTiltSteps-1],1)+
", number of tilt steps: "+numTiltSteps+", total number of measurements - "+numStops);
}
if (this.debugLevel>1){
System.out.println("targetAngleHorizontal="+IJ.d2s(targetAngleHorizontal,2)+", targetAngleVertical="+IJ.d2s(targetAngleVertical,2));
for (int i=0;i<numTiltSteps;i++) {
System.out.println("Tilt # "+i+": "+tilts[i]+"\n axial ("+rots[i].length+" stops):");
for (int j=0;j<rots[i].length;j++) System.out.print(" "+IJ.d2s(rots[i][j],1));
System.out.println();
}
}
// return true;
// motorsSimultaneous
// show current tilt/axial
// double absTiltRange=Math.abs(this.goniometerParameters.scanLimitTiltStart-this.goniometerParameters.scanLimitTiltStart);
GenericDialog gd = new GenericDialog("Start scanning");
// this.serialNumber, // camera serial number string
gd.addMessage("About to start scanning and recording images, parameters are set in the \"Configure Goniometer\" dialog");
gd.addMessage("Please make sure goniometer motors are set that 0,0 corresponds to the camera in the initial position -");
gd.addMessage("vertical and cables are not entangled, camera exposure is set correctly.");
gd.addMessage("Camera will start from the tilt "+tilts[0]+" degrees (0 is vertical, positive - away from the target) and move to "+
tilts[tilts.length-1]+" degrees,");
gd.addMessage("making "+numStops+" stops for image acquisirtion from all channels.");
gd.addMessage("");
gd.addMessage("Axial rotations are set to "+(this.goniometerParameters.scanBidirectional?"ALTERNATE directions.":"be all in THE SAME direction."));
gd.addMessage("Simultaneous operation of the motors is "+(this.goniometerParameters.motorsSimultaneous?"ENABLED.":"DISABLED."));
int startStep=0;
if (this.lastScanStep>=0){
gd.addMessage("Last scan finished at stop "+this.lastScanStep);
if (this.lastScanStep<(numStops-1)) startStep= this.lastScanStep+1;
}
gd.addMessage("");
gd.addMessage("Current position is:");
gd.addMessage("tilt="+ IJ.d2s(thisTilt,2)+ " degrees ("+motors[tiltMotor]+ " motor steps)");
gd.addMessage("axial="+IJ.d2s(thisAxial,2)+" degrees ("+motors[axialMotor]+" motor steps)");
gd.addNumericField("Start from position (0.."+(numStops-1)+")", startStep, 0);
gd.addCheckbox("Debug timing", false);
gd.showDialog();
if (gd.wasCanceled()) return false;
startStep= (int) gd.getNextNumber();
boolean debugTiming=gd.getNextBoolean();
String src_dir=this.distortionProcessConfiguration.selectSourceDirectory(true, this.distortionProcessConfiguration.sourceDirectory, true);
if (src_dir==null) {
String msg="Failed to select directory "+this.distortionProcessConfiguration.sourceDirectory+" to save images ";
System.out.println("Error: "+msg);
IJ.showMessage("Error",msg);
return false;
}
// overwrite some distortionProcessConfiguration parameters (show, save, ...?) from GoniometerParameters
this.distortionProcessConfiguration.sourceDirectory=src_dir;
// just for now - setting motor debug 1 higher than this
this.goniometerParameters.goniometerMotors.debugLevel=this.debugLevel+1;
long startTime = System.nanoTime();
String status;
boolean OK;
int startTilt=0;
int startAxial=0;
this.lastScanStep=0;
while (startStep>=(this.lastScanStep+rots[startTilt].length)){
this.lastScanStep+=rots[startTilt].length;
startTilt++;
}
startAxial=startStep-this.lastScanStep;
this.lastScanStep=startStep-1;
for (int nTilt=startTilt;nTilt<numTiltSteps;nTilt++){
double tilt=tilts[nTilt];
tilt=0.1*Math.round(10*tilt); // is that needed?
int tiltMotorPosition= (int) Math.round(tilt*this.goniometerParameters.goniometerMotors.stepsPerDegreeTilt);
status=IJ.d2s(1E-9*(System.nanoTime()-startTime),3)+": Tilt run "+(nTilt+1)+" (of "+numTiltSteps+"), tilt angle "+
IJ.d2s(tilt,2)+" degrees, motor steps: "+tiltMotorPosition;
if (this.debugLevel>0) System.out.println(status);
if (updateStatus) IJ.showStatus(status);
this.goniometerParameters.motorsSimultaneous=false; // not yet implemented
// if (!this.goniometerParameters.motorsSimultaneous){
OK= this.goniometerParameters.goniometerMotors.moveMotorSetETA(tiltMotor, tiltMotorPosition);
if (!OK) {
String msg="Could not set motor "+(tiltMotor+1)+" to move to "+tiltMotorPosition+" - may be out of limit";
System.out.println("Error: "+msg);
IJ.showMessage("Error",msg);
return false;
}
OK=this.goniometerParameters.goniometerMotors.waitMotor(tiltMotor);
if (!OK) {
String msg="Motor "+(tiltMotor+1)+" failed to reach "+tiltMotorPosition+".";
System.out.println("Error: "+msg);
IJ.showMessage("Error",msg);
return false;
}
// }
for (int nAxial=((nTilt==startTilt)?startAxial:0);nAxial<rots[nTilt].length;nAxial++){
double axial=rots[nTilt][nAxial];
axial=0.1*Math.round(10*axial);
int axialMotorPosition= (int) Math.round(axial*this.goniometerParameters.goniometerMotors.stepsPerDegreeAxial);
status=(this.lastScanStep+1)+"( last="+(numStops-1)+") "+IJ.d2s(1E-9*(System.nanoTime()-startTime),3)+" sec. tilt:"+(nTilt+1)+"/"+numTiltSteps+" , "+
IJ.d2s(tilt,2)+" deg., axial:"+
(nAxial+1)+"/"+rots[nTilt].length+" , "+IJ.d2s(axial,2)+" deg.";
if (this.debugLevel>0) System.out.println(status+", axial motor steps: "+axialMotorPosition);
if (updateStatus) IJ.showStatus(status);
OK= this.goniometerParameters.goniometerMotors.moveMotorSetETA(axialMotor, axialMotorPosition);
if (!OK) {
String msg="Could not set motor "+(axialMotor+1)+" to move to "+axialMotorPosition+" - may be out of limit";
System.out.println("Error: "+msg);
IJ.showMessage("Error",msg);
return false;
}
OK=this.goniometerParameters.goniometerMotors.waitMotor(axialMotor);
if (!OK) {
String msg="Motor "+(axialMotor+1)+" failed to reach "+axialMotorPosition+".";
System.out.println("Error: "+msg);
IJ.showMessage("Error",msg);
return false;
}
// update motor positions in the image properties, acquire and save images.
// TODO: Make acquisition/decoding/laser identification multi-threaded
this.cameras.setMotorsPosition(this.goniometerParameters.goniometerMotors.getTargetPositions()); // Used target, not current to prevent minor variations
this.cameras.reportTiming=debugTiming;
this.cameras.acquire(this.distortionProcessConfiguration.sourceDirectory,true, updateStatus); // true - use lasers, updateStatus - make false?
this.lastScanStep++;
if (stopRequested.get()>1){
if (this.debugLevel>0) System.out.println("User interrupt");
stopRequested.set(0);
gd = new GenericDialog("User interrupt");
// this.serialNumber, // camera serial number string
gd.addMessage("User requested interrupt after step "+this.lastScanStep+" (last would be "+(numStops-1)+")");
System.out.println("User requested interrupt after step "+this.lastScanStep+" (last would be "+(numStops-1)+")");
gd.addMessage("Cancel will terminate the scanning leaving motros where they are.");
gd.enableYesNoCancel("Continue", "Motors Home");
gd.showDialog();
if (gd.wasCanceled()) return false;
if (!gd.wasOKed()){
motorsMove(
tiltMotor,
axialMotor,
0, //tiltMotorPosition,
0, //axialMotorPosition,
updateStatus);
return false;
}
}
}
}
OK=motorsMove(
tiltMotor,
axialMotor,
0, //tiltMotorPosition,
0, //axialMotorPosition,
updateStatus);
if (this.debugLevel>0) System.out.println("Scan finished in "+IJ.d2s(1E-9*(System.nanoTime()-startTime),3)+" seconds.");
return OK;
}
public boolean motorsMove(
int tiltMotor,
int axialMotor,
int tiltMotorPosition,
int axialMotorPosition,
boolean updateStatus){
String status="Movin axial motor to "+axialMotorPosition+"...";
if (updateStatus) IJ.showStatus(status);
boolean OK= this.goniometerParameters.goniometerMotors.moveMotorSetETA(axialMotor, axialMotorPosition);
if (!OK) {
String msg="Could not set motor "+(axialMotor+1)+" to move to "+axialMotorPosition+" - may be out of limit";
System.out.println("Error: "+msg);
IJ.showMessage("Error",msg);
return false;
}
OK=this.goniometerParameters.goniometerMotors.waitMotor(axialMotor);
if (!OK) {
String msg="Motor "+(axialMotor+1)+" failed to reach "+axialMotorPosition+".";
System.out.println("Error: "+msg);
IJ.showMessage("Error",msg);
return false;
}
OK= this.goniometerParameters.goniometerMotors.moveMotorSetETA(tiltMotor, tiltMotorPosition);
if (!OK) {
String msg="Could not set motor "+(tiltMotor+1)+" to move to "+tiltMotorPosition+" - may be out of limit";
System.out.println("Error: "+msg);
IJ.showMessage("Error",msg);
return false;
}
OK=this.goniometerParameters.goniometerMotors.waitMotor(tiltMotor);
if (!OK) {
String msg="Motor "+(tiltMotor+1)+" failed to reach "+tiltMotorPosition+".";
System.out.println("Error: "+msg);
IJ.showMessage("Error",msg);
return false;
}
return true;
}
public boolean testHintedTarget (
ImagePlus[] images,
Distortions lensDistortions, // should not be null
Distortions.DistortionCalibrationData distortionCalibrationData,
Distortions.PatternParameters patternParameters, // should not be null
boolean equalizeGreens,
int threadsMax,
boolean updateStatus,
int debug_level) {// debug level used inside loops
if (lensDistortions == null) {
String msg = "lensDistortions is not initialized";
IJ.showMessage("Error", msg);
throw new IllegalArgumentException(msg);
}
GenericDialog gd = new GenericDialog("Select image to try hint pattern");
int numNonNull=0;
for (int nImg=0;nImg<images.length;nImg++) if (images[nImg]!=null) numNonNull++;
String [] choices=new String[numNonNull];
int [] pointersArray=new int[images.length];
int [] indices=new int [numNonNull];
int index=0;
// if (this.debugLevel>1)System.out.println ("images.length="+images.length);
for (int nImg=0;nImg<images.length;nImg++) if (images[nImg]!=null){
int subCam=distortionCalibrationData.getImageChannel(images[nImg]);
// String imgTitle=images[nImg].getTitle();
pointersArray[nImg]=0;
if (images[nImg].getProperty("POINTERS")!=null) pointersArray[nImg]=Integer.parseInt((String) (images[nImg].getProperty("POINTERS")));
choices[index]=index+" sub-camera:"+subCam+" "+images[nImg].getTitle()+" - "+pointersArray[nImg]+" pointers";
// if (this.debugLevel>1)System.out.println ("Adding image, nImg="+nImg);
// if (this.debugLevel>1)System.out.println ("Adding image "+images[nImg].getTitle()+": "+choices[index]);
indices[index++]=nImg;
}
gd.addChoice("Image to process", choices, choices[0]);
double hintGridTolerance=0.0;
gd.addNumericField("grid match tolerance (0 - only direction)", hintGridTolerance, 3);
int stationNumber=0;
if (distortionCalibrationData.getNumStations()>1) {
gd.addNumericField("Station number (0.."+(distortionCalibrationData.getNumStations()-1)+")", stationNumber, 0);
}
gd.showDialog();
if (gd.wasCanceled()) return false;
index= gd.getNextChoiceIndex();
hintGridTolerance=gd.getNextNumber();
if (distortionCalibrationData.getNumStations()>1) {
stationNumber=(int) gd.getNextNumber();
if (stationNumber<0) stationNumber=0;
else if (stationNumber>=distortionCalibrationData.getNumStations()) stationNumber = distortionCalibrationData.getNumStations()-1;
}
int nImg=indices[index];
int subCam= distortionCalibrationData.getImageChannel(images[nImg]);
// int stationNumber= distortionCalibrationData.getImageStation(numGridImage), // station number
double timeStamp= distortionCalibrationData.getImageTimestamp(images[nImg]);
int numPointers=pointersArray[nImg];
double [] goniometerTiltAxial=distortionCalibrationData.getImagesetTiltAxial(timeStamp);
if (goniometerTiltAxial==null){
String msg= "No orientation data for timestamp="+IJ.d2s(timeStamp,6);
System.out.println(msg);
IJ.showMessage("Warning",msg);
return false;
}
if (Double.isNaN(goniometerTiltAxial[0])){
String msg= "Goniometer tilt angle is undefined for timestamp="+IJ.d2s(timeStamp,6);
System.out.println(msg);
IJ.showMessage("Warning",msg);
}
if (Double.isNaN(goniometerTiltAxial[1])){
String msg= "Goniometer axial angle is undefined for timestamp="+IJ.d2s(timeStamp,6);
System.out.println(msg);
IJ.showMessage("Warning",msg);
}
if ((Double.isNaN(goniometerTiltAxial[0])) || (Double.isNaN(goniometerTiltAxial[1]))) return false;
double [][][] hintGrid=lensDistortions.estimateGridOnSensor(
stationNumber, // station number
subCam,
goniometerTiltAxial[0], // Tilt, goniometerHorizontal
goniometerTiltAxial[1], // Axial,goniometerAxial
-1 // use camera parameters, not imageSet
);
if (hintGrid==null){
String msg= "Target is not visible";
System.out.println(msg);
IJ.showMessage("Warning",msg);
return false;
}
if (this.debugLevel>1) lensDistortions.showHintGrid(hintGrid);
MatchSimulatedPattern matchSimulatedPattern = new MatchSimulatedPattern(this.distortionParametersDefault.FFTSize); // new instance, all reset
// next 2 lines are not needed for the new instance, but can be
// used alternatively if keeping it
matchSimulatedPattern.invalidateFlatFieldForGrid(); // Reset Flat Filed calibration - different image.
matchSimulatedPattern.invalidateFocusMask();
matchSimulatedPattern.debugLevel = debug_level;
ImagePlus imp_eq = matchSimulatedPattern.applyFlatField(images[nImg]); // current image with grid flat-field correction
//TODO: it shows always 4 pointers (if>0) - the array is sparse
// public int getNumberOfPointers (int sensorNum){
// public int getNumberOfPointers (ImagePlus imp){
if (debug_level > 0){
System.out.println("\n ======= Looking for grid, matching pointers in image " +images[nImg].getTitle()+
", initial number of pointers was "+numPointers);
}
//matchSimulatedPatterns[numSensor].getChannel(images[numSensor])+" ");
MatchSimulatedPattern.DistortionParameters distortionParameters = modifyDistortionParameters();
SimulationPattern.SimulParameters simulParameters = modifySimulParameters();
boolean noMessageBoxes=true;
int numAbsolutePoints = matchSimulatedPattern.calculateDistortions(
// allow more of grid around pointers?
distortionParameters, //
this.patternDetectParameters,
simulParameters,
equalizeGreens, imp_eq,
this.laserPointers, // null, //LASER_POINTERS, //
// LaserPointer laserPointer, //
// LaserPointer object or null
true, // don't care -removeOutOfGridPointers
hintGrid, // double [][][] hintGrid, // predicted grid array (or null)
hintGridTolerance, // double hintGridTolerance, // allowed mismatch (fraction of period) or 0 - orientation only
threadsMax,
updateStatus,
debug_level,
distortionParameters.loop_debug_level, // debug level
noMessageBoxes);
if (numAbsolutePoints < 0) { // no pointers in this image
String msg = "*** No laser pointers matched for " + images[nImg].getTitle() + " - they are needed for absolute grid positioning";
if (debug_level > 0) System.out.println("Warning: " + msg);
if (debug_level > 2) IJ.showMessage("Warning", msg);
}
if (numAbsolutePoints == 0) { // no pointers in this image
String msg = "*** No laser pointers matched for " + images[nImg].getTitle() + ", but the grid fit hinted one with specified tolerance";
if (debug_level > 0) System.out.println("Warning: " + msg);
if (debug_level > 2) IJ.showMessage("Warning", msg);
}
return true;
}
/*
* private showDoubleFloatArrays SDFA_INSTANCE= new showDoubleFloatArrays(); // just for debugging?
this.SDFA_INSTANCE.showArrays(gridXYZCorr, getGridWidth(), getGridHeight(), true, "Grid corrections", titles);
*
gd.addChoice( // ArrayIndexOutOfBoundsException: 21
this.distortionCalibrationData.getParameterName(parIndex)+
" ("+sValue+" "+
this.distortionCalibrationData.getParameterUnits(parIndex)+")"+
(this.distortionCalibrationData.isSubcameraParameter(parIndex)?(" s"+subCam):"com "),
this.definedModes, this.definedModes[this.parameterMode[numSeries][i]]);
*
* this.parameterMode[numSeries][i]=gd.getNextChoiceIndex();
Distortions.PatternParameters patternParameters, // should not be null
boolean equalizeGreens,
int threadsMax,
boolean updateStatus,
int debug_level) {// debug level used inside loops
*
*
*/
public MatchSimulatedPattern.DistortionParameters modifyDistortionParameters(){
MatchSimulatedPattern.DistortionParameters distortionParameters = this.distortionParametersDefault.clone();
distortionParameters.refineInPlace = false;
distortionParameters.correlationMaxOffset = this.goniometerParameters.maxCorr;
distortionParameters.correlationSize = this.goniometerParameters.correlationSize;
distortionParameters.correlationGaussWidth = this.goniometerParameters.correlationGaussWidth;
distortionParameters.refineCorrelations = false;
distortionParameters.fastCorrelationOnFirstPass = true;
distortionParameters.fastCorrelationOnFinalPass = true;
distortionParameters.correlationAverageOnRefine = false;
distortionParameters.minUVSpan = this.goniometerParameters.minUVSpan;
distortionParameters.flatFieldCorrection = this.goniometerParameters.flatFieldCorrection;
distortionParameters.flatFieldExpand = this.goniometerParameters.flatFieldExpand;
distortionParameters.numberExtrapolated = 1; // measuring distortions -
distortionParameters.correlationMinInitialContrast=this.goniometerParameters.correlationMinInitialContrast;
distortionParameters.minimalPatternCluster=this.goniometerParameters.minimalPatternCluster;
distortionParameters.scaleMinimalInitialContrast=this.goniometerParameters.scaleMinimalInitialContrast;
distortionParameters.searchOverlap=this.goniometerParameters.searchOverlap;
return distortionParameters;
}
public SimulationPattern.SimulParameters modifySimulParameters(){
SimulationPattern.SimulParameters simulParameters = this.simulParametersDefault.clone();
simulParameters.smallestSubPix = this.goniometerParameters.smallestSubPix;
simulParameters.bitmapNonuniforityThreshold = this.goniometerParameters.bitmapNonuniforityThreshold;
simulParameters.subdiv = this.goniometerParameters.subdiv;
return simulParameters;
}
public double[] estimateOrientation(
ImagePlus[] images, // last acquire images with number of pointers
// detected>0
// MatchSimulatedPattern.DistortionParameters distortionParametersDefault,
// LensAdjustment.FocusMeasurementParameters focusMeasurementParameters,
// Goniometer.GoniometerParameters goniometerParameters,
// MatchSimulatedPattern.PatternDetectParameters patternDetectParameters,
// MatchSimulatedPattern.LaserPointer laserPointer, // null OK
// SimulationPattern.SimulParameters simulParametersDefault,
Distortions.DistortionCalibrationData distortionCalibrationData,
Distortions.PatternParameters patternParameters, // should not be null
Distortions lensDistortions, // should not be null
boolean equalizeGreens,
int threadsMax,
boolean updateStatus,
int debug_level) {// debug level used inside loops
long startTime = System.nanoTime();
if (lensDistortions == null) {
String msg = "lensDistortions is not initialized";
IJ.showMessage("Error", msg);
throw new IllegalArgumentException(msg);
}
// remove unneeded, copied from updateFocusGrid()
SimulationPattern.SimulParameters simulParameters = modifySimulParameters();
MatchSimulatedPattern.DistortionParameters distortionParameters = modifyDistortionParameters();
int numImages = 0;
for (int i = 0; i < images.length; i++)
if (images[i] != null)
numImages++;
if (numImages == 0) {
String msg = "No images with laser pointers";
System.out.println("Error: " + msg);
IJ.showMessage("Error", msg);
return null;
}
if (this.matchSimulatedPatterns == null) {
this.matchSimulatedPatterns = new MatchSimulatedPattern[images.length];
for (int i = 0; i < this.matchSimulatedPatterns.length; i++)
this.matchSimulatedPatterns[i] = null;
}
if (this.matchSimulatedPatterns.length < images.length) {
MatchSimulatedPattern[] matchSimulatedPatternsTmp = matchSimulatedPatterns.clone();
this.matchSimulatedPatterns = new MatchSimulatedPattern[images.length];
for (int i = 0; i < matchSimulatedPatternsTmp.length; i++)
this.matchSimulatedPatterns[i] = matchSimulatedPatternsTmp[i];
}
ImagePlus[] imp_calibrated = new ImagePlus[images.length];
int [] numPointers=new int [images.length];
for (int numSensor = 0; numSensor < imp_calibrated.length; numSensor++) {
imp_calibrated[numSensor] = null;
numPointers[numSensor] = 0;
}
boolean noMessageBoxes=true;
for (int numSensor = 0; numSensor < images.length; numSensor++)
if (images[numSensor] != null) {
// reset matchSimulatedPattern, so it will start from scratch
this.matchSimulatedPatterns[numSensor] = new MatchSimulatedPattern(
this.distortionParametersDefault.FFTSize); // new instance, all reset
// next 2 lines are not needed for the new instance, but can be
// used alternatively if keeping it
this.matchSimulatedPatterns[numSensor].invalidateFlatFieldForGrid(); // Reset Flat Filed calibration - different image.
this.matchSimulatedPatterns[numSensor].invalidateFocusMask();
if (matchSimulatedPatterns[numSensor].getPointersXY(images[numSensor],
this.laserPointers.laserUVMap.length) == null) { // no pointers in this image
String msg = "No laser pointers detected for "+ images[numSensor].getTitle()+ " - they are needed for absolute grid positioning";
if (debug_level > 0) System.out.println("Warning: " + msg);
IJ.showMessage("Warning", msg);
continue;
} else if (debug_level > 0) {
// System.out.println("Image "+numSensor+" has "+ matchSimulatedPatterns[numSensor].getPointersXY(images[numSensor],this.laserPointers.laserUVMap.length).length+ " pointers");
}
this.matchSimulatedPatterns[numSensor].debugLevel = debug_level;
ImagePlus imp_eq = this.matchSimulatedPatterns[numSensor].applyFlatField(images[numSensor]); // current image with grid flat-field correction
//TODO: it shows always 4 pointers (if>0) - the array is sparse
// public int getNumberOfPointers (int sensorNum){
// public int getNumberOfPointers (ImagePlus imp){
numPointers[numSensor]=0;
if (images[numSensor].getProperty("POINTERS")!=null) numPointers[numSensor]= Integer.parseInt((String) images[numSensor].getProperty("POINTERS"));
if (debug_level > 0){
System.out.println("\n ======= Looking for grid, matching pointers in image " +images[numSensor].getTitle()+
", initial number of pointers was "+numPointers[numSensor]);
}
// matchSimulatedPatterns[numSensor].getChannel(images[numSensor])+" ");
int numAbsolutePoints = this.matchSimulatedPatterns[numSensor].calculateDistortions(
// allow more of grid around pointers?
distortionParameters, //
this.patternDetectParameters,
simulParameters,
equalizeGreens, imp_eq,
this.laserPointers, // null, //LASER_POINTERS, //
// LaserPointer laserPointer, //
// LaserPointer object or null
true, // don't care -removeOutOfGridPointers
null, // double [][][] hintGrid, // predicted grid array (or null)
0, // double hintGridTolerance, // allowed mismatch (fraction of period) or 0 - orientation only
threadsMax,
updateStatus,
debug_level,
distortionParameters.loop_debug_level, // debug level
noMessageBoxes);
if (numAbsolutePoints <= 0) { // no pointers in this image
String msg = "*** No laser pointers matched for " + images[numSensor].getTitle() + " - they are needed for absolute grid positioning";
if (debug_level > 0) System.out.println("Warning: " + msg);
if (debug_level > 2) IJ.showMessage("Warning", msg);
continue;
}
numPointers[numSensor]=numAbsolutePoints;
// if (debug_level>1)
// System.out.println("calculateDistortions() for channel "+numSensor+" finished at "+
// IJ.d2s(0.000000001*(System.nanoTime()-startTime),3));
if (debug_level > 2) {
double[] test_uv = new double[this.matchSimulatedPatterns[numSensor].UV_INDEX.length];
for (int i = 0; i < this.matchSimulatedPatterns[numSensor].UV_INDEX.length; i++)
test_uv[i] = this.matchSimulatedPatterns[numSensor].UV_INDEX[i];
sdfaInstance.showArrays(test_uv,
this.matchSimulatedPatterns[numSensor].getImageWidth(),
this.matchSimulatedPatterns[numSensor].getImageHeight(), "UV_INDEX");
}
if (debug_level > 0)
System.out.println("Matched "
+ numAbsolutePoints
+ " laser pointers, grid generated at "
+ IJ.d2s(0.000000001 * (System.nanoTime() - startTime), 3));
// TODO - here - multiple images possible, not just one!
// First - create sparse, then remove nulls
imp_calibrated[numSensor] = this.matchSimulatedPatterns[numSensor].getCalibratedPatternAsImage(images[numSensor],numAbsolutePoints);
if (this.goniometerParameters.showAcquiredImages)
imp_calibrated[numSensor].show(); // DISTORTION_PROCESS_CONFIGURATION.showGridImages
} // for (int numSensor=0;numSensor<images.length;numSensor++) if
// (images[numSensor]!=null) {
int numCalibrated = 0;
for (int i = 0; i < imp_calibrated.length; i++)
if (imp_calibrated[i] != null)
numCalibrated++;
if (numCalibrated == 0) {
String msg = "No absolutely calibrated (with laser pointers) images available";
if (debug_level > 0)
System.out.println("Error: " + msg);
IJ.showMessage("Error", msg);
return null;
} else {
String msg = "Found " + numCalibrated + " images with the grid referenced by laser pointers";
if (debug_level > 1) System.out.println(msg);
if (debug_level > 2) IJ.showMessage("Info", msg);
}
int maxNumPointers=0;
int imgWithMaxPointers=0;
for (int numSensor=0;numSensor<numPointers.length;numSensor++){
if (numPointers[numSensor]>maxNumPointers){
maxNumPointers=numPointers[numSensor];
imgWithMaxPointers=numSensor;
}
}
if (numCalibrated < imp_calibrated.length) {
ImagePlus[] imp_tmp = imp_calibrated.clone();
imp_calibrated = new ImagePlus[numCalibrated];
numCalibrated = 0;
for (int i = 0; i < imp_tmp.length; i++) if (imp_tmp[i] != null){
if (i==imgWithMaxPointers) imgWithMaxPointers= numCalibrated; // may only be decreased or stay the same, so
// imgWithMaxPointes is a new index of the image with maximal number of recognized laser pointers
imp_calibrated[numCalibrated++] = imp_tmp[i];
}
}
/*
* Distortions.DistortionCalibrationData distortionCalibrationData= new
* Distortions.DistortionCalibrationData( imp_calibrated, //ImagePlus []
* images, // images in the memory patternParameters,
* //PatternParameters patternParameters, eyesisCameraParameters
* //EyesisCameraParameters eyesisCameraParameters );
*/
distortionCalibrationData.setImages(imp_calibrated, // ImagePlus [] images, // imagesin the memory
patternParameters); // PatternParameters patternParameters);
distortionCalibrationData.initImageSet(eyesisCameraParameters);
// Set initial azimuth and elevation
double [] initialAzEl=distortionCalibrationData.getAzEl(imgWithMaxPointers);
// set goniometer horizontal axis angle and goniometer axial angles in all images
distortionCalibrationData.setGHGA(-initialAzEl[1], -initialAzEl[0]);
if (debug_level > 1) System.out.println("Initial Azimuth and Elevation are set to az="+IJ.d2s(-initialAzEl[0],2)+", elvation="+IJ.d2s(-initialAzEl[1],2));
lensDistortions.copySensorConstants(eyesisCameraParameters); // copy from the first channel
// lensDistortions.fittingStrategy will be defined later, no need to
// update it with a reference to distortionCalibrationData now
// if (debug_level > 1) {
// System.out.println("distortionCalibrationData.setImages()");
// }
// fitting strategy
// distortionCalibrationData.pathName=this.goniometerParameters.initialCalibrationFile;
lensDistortions.debugLevel = this.debugLevel;
lensDistortions.fittingStrategy = new Distortions.FittingStrategy(true,
this.goniometerParameters.strategyFile,
distortionCalibrationData); // will use list of grid files
if (lensDistortions.fittingStrategy.pathName == null) { // failed to select/open the file
lensDistortions.fittingStrategy = null;
IJ.showMessage("Error", "Failed to open fitting strategy file: "+this.goniometerParameters.strategyFile);
return null;
}
if (debug_level > 1) System.out.println("Using fitting strategy template file: "+ lensDistortions.fittingStrategy.pathName);
// TODO: modify fitting strategy to include all grid images
lensDistortions.fittingStrategy.adjustNumberOfImages(imp_calibrated.length);
this.goniometerParameters.strategyFile = lensDistortions.fittingStrategy.pathName;
// saved fitting strategy maybe for different number of subcameras.
// it will be adjusted here - that works only for simple strategies that use all subcameras at each step.
// TODO: fix repeating subcamera parameters for all subcameras in each strategy
lensDistortions.fittingStrategy.updateNumberOfSubcameras();
// enable azimuth adjust for all but the first camera? not here, later
// Calculate Sensor Masks
distortionCalibrationData.debugLevel = debug_level;
distortionCalibrationData.updateStatus = updateStatus;
distortionCalibrationData.calculateSensorMasks();
if (debug_level > 0) System.out.println("Starting LMA at " + IJ.d2s(0.000000001 * (System.nanoTime() - startTime), 3));
lensDistortions.seriesNumber = 0; // start from 0;
lensDistortions.stopEachStep = false;
lensDistortions.stopEachSeries = false;
lensDistortions.thresholdFinish = this.goniometerParameters.thresholdFinish;
lensDistortions.numIterations = this.goniometerParameters.numIterations;
// TODO: Set initial values for the goniometer angles from the sensor
// (channel) number, average them if there are several in the list
lensDistortions.LevenbergMarquardt(false); // skip dialog
if (debug_level > 0)
System.out.println("Finished LMA at "
+ IJ.d2s(0.000000001 * (System.nanoTime() - startTime), 3));
// Read camera parameters
if (debug_level > 0)
System.out.println("estimateOrientation finished at "
+ IJ.d2s(0.000000001 * (System.nanoTime() - startTime), 3));
// TODO: see if needs to be changed
int stationNumber=0;
double[] result = { this.eyesisCameraParameters.goniometerHorizontal[stationNumber], // goniometer rotation around "horizontal" axis (tilting from the target - positive)
this.eyesisCameraParameters.goniometerAxial[stationNumber] // goniometer rotation around Eyesis axis (clockwise in plan - positive
};
return result;
}
public static class GoniometerParameters {
public String gridGeometryFile="";
public String initialCalibrationFile="";
public String strategyFile="";
public String resultsSuperDirectory=""; // directory with subdirectories named as serial numbers to store results
public String comment="no comments"; // Comment to add to the results
public int EEPROM_channel=1; // EEPROM channel to read serial number from
public boolean saveResults=true; // save focusing results
public boolean showResults=true; // show focusing (includingh intermediate) results
public String serialNumber=""; // camera serial number string
public double sensorTemperature=Double.NaN; // last measured sensor temperature
//other summary results to be saved with parameters
public double maxCorr=5; // maximal grid correction between images allowed (larger will trigger full grid rebuild)
public boolean showHistoryDetails=false; // show color info
public boolean showHistorySingleLine=true; // all parameters in a single line (easier to copy to spreadsheet)
public boolean showAcquiredImages=false;
public boolean showFittedParameters=true;
// when approximating PSF with a second degree polynomial:
public double psf_cutoffEnergy=0.5; // disregard pixels outside of this fraction of the total energy
public double psf_cutoffLevel= 0.2; // disregard pixels below this fraction of the maximal value
public int psf_minArea = 10; // continue increasing the selected area, even if beyound psf_cutoffEnergy and psf_cutoffLevel,
public double psf_blurSigma = 0.0; // optionally blur the calculated mask
// the following overwrite SimulParameters members
//TODO: Make initial pattern search more robust - if it first gets false positive, and number of detected cells is too low - increase threshold and re-try
public double correlationMinInitialContrast=3.0; // minimal contrast for the pattern of the center (initial point)
public int minimalPatternCluster=150; // minimal pattern cluster size (0 - disable retries)
public double scaleMinimalInitialContrast=2.0; // increase/decrease minimal contrast if initial cluster is >0 but less than minimalPatternCluster
public double searchOverlap=0.25; // when searching for grid, step this amount of the FFTSize
public double smallestSubPix=0.3; // subdivide pixels down to that fraction when simulating
public double bitmapNonuniforityThreshold=0.1 ; // subdivide pixels until difference between the corners is below this value
public int subdiv=4;
// overwrites public static class MultiFilePSF.overexposedMaxFraction
public double overexposedMaxFraction=0.1; // allowed fraction of the overexposed pixels in the PSF kernel measurement area
// overwrites public static class PSFParameters.minDefinedArea
public double minDefinedArea=0.75; // minimal (weighted) fraction of the defined patter pixels in the FFT area
public int PSFKernelSize=32; // size of the detected PSF kernel
public boolean approximateGrid=true; // approximate grid with polynomial
public boolean centerPSF=true; // Center PSF by modifying phase
public double mask1_sigma= 1.0;
public double mask1_threshold=0.25;
public double gaps_sigma= 1.0;
public double mask_denoise= 0.25;
// OTFFilterParameters
public double deconvInvert=0.03; // with good focus can go to 0.015 or smaller
// DistortionParameters
public int correlationSize=32;
public double correlationGaussWidth=0.75;
public double minUVSpan; // Minimal u/v span in correlation window that triggers increase of the correlation FFT size
public boolean flatFieldCorrection=true;
public double flatFieldExpand=4.0;
public double thresholdFinish=0.001; // Stop iterations if 2 last steps had less improvement (but not worsening )
public int numIterations= 100; // maximal number of iterations
/*
horizontal axis:
131 * 244 * 64 = 2045696
244 - worm gear
131 - motor
64 - pulses per revolution
5682.48889 per degree
*/
// motors rotate positive - look down, positive - CCW
CalibrationHardwareInterface.GoniometerMotors goniometerMotors=null;
// public double stepsPerDegreeTilt=-5682.48889; // minus that positive steps make negative elevation
// public double stepsPerDegreeAxial=-36.0; // minus that positive steps make rotate CCW when looking from Eyesis top
// public double scanStepTilt=20.0; // degrees (equal steps not larger than
// public double scanStepAxial=10.0; // degrees (equal steps not larger than
// public double scanLimitTiltStart= 30.0; // scan around horizontal axis from that angle
// public double scanLimitTiltEnd= -80.0; // scan around horizontal axis to that angle
public double targetDistance= 5817; //mm - foer overlap calculation
public double scanLatitudeLow=-90; // lowest camera latitude to calibrate (nadir=-90)
public double scanLatitudeHigh=90; // highest camera latitude to calibrate (zenith=90)
public double scanOverlapHorizontal= 0.5;
public double scanOverlapVertical= 0.5;
public double scanLimitAxialStart= -200.0; // scan around camera axis from that angle
public double scanLimitAxialEnd= 200.0; // scan around camera axis to that angle
public boolean scanBidirectional= true; // false - always move axial in the same direction, true - optimize movements
public boolean motorsSimultaneous= true; // true - move motors simultaneously, false - one at a time
public GoniometerParameters(CalibrationHardwareInterface.GoniometerMotors goniometerMotors){
this.goniometerMotors=goniometerMotors;
}
public GoniometerParameters(
CalibrationHardwareInterface.GoniometerMotors goniometerMotors,
String gridGeometryFile,
String initialCalibrationFile, // not needed
String strategyFile,
String resultsSuperDirectory, // directory with subdirectories named as serial numbers to stro results
int EEPROM_channel, // EEPROM channel to read serial number from
boolean saveResults, // save focusing results
boolean showResults, // show focusing (includingh intermediate) results
String serialNumber, // camera serial number string
double sensorTemperature, // last measured sensor temperature
String comment, // Comment to add to the results
double maxCorr, // maximal grid correction between images allowed (larger will trigger full grid rebuild)
boolean showHistoryDetails,
boolean showHistorySingleLine, // all parameters in a single line (easier to copy to spreadsheet)
boolean showAcquiredImages,
boolean showFittedParameters,
double psf_cutoffEnergy, // disregard pixels outside of this fraction of the total energy
double psf_cutoffLevel, // disregard pixels below this fraction of the maximal value
int psf_minArea, // continue increasing the selected area, even if beyound psf_cutoffEnergy and psf_cutoffLevel,
// if the selected area is smaller than this (so approximation wpuld work)
double psf_blurSigma, // optionally blur the calculated mask
double correlationMinInitialContrast, // minimal contrast for the pattern of the center (initial point)
int minimalPatternCluster, // minimal pattern cluster size (0 - disable retries)
double scaleMinimalInitialContrast, // increase/decrease minimal contrast if initial cluster is >0 but less than minimalPatternCluster
double searchOverlap, // when searching for grid, step this amount of the FFTSize
double smallestSubPix, // subdivide pixels down to that fraction when simulating
double bitmapNonuniforityThreshold, // subdivide pixels until difference between the corners is below this value
int subdiv,
double overexposedMaxFraction, // allowed fraction of the overexposed pixels in the PSF kernel measurement area
double minDefinedArea, // minimal (weighted) fraction of the defined patter pixels in the FFT area
int PSFKernelSize,
boolean approximateGrid, // approximate grid with polynomial
boolean centerPSF, // Center PSF by modifying phase
double mask1_sigma,
double mask1_threshold,
double gaps_sigma,
double mask_denoise,
double deconvInvert,
int correlationSize,
double correlationGaussWidth,
double minUVSpan, // Minimal u/v span in correlation window that triggers increase of the correlation FFT size
boolean flatFieldCorrection,
double flatFieldExpand,
double thresholdFinish,// (copied from series) stop iterations if 2 last steps had less improvement (but not worsening )
int numIterations, // maximal number of iterations
// double stepsPerDegreeTilt, // minus that positive steps make negative elevation
// double stepsPerDegreeAxial, // minus that positive steps make rotate CCW when looking from Eyesis top
double targetDistance,
double scanLatitudeLow, // lowest camera latitude to calibrate
double scanLatitudeHigh, // highest camera latitude to calibrate
double scanOverlapHorizontal,
double scanOverlapVertical,
double scanLimitAxialStart, // scan around camera axis from that angle
double scanLimitAxialEnd, // scan around camera axis to that angle
boolean scanBidirectional,
boolean motorsSimultaneous
){
this.gridGeometryFile=gridGeometryFile;
this.initialCalibrationFile=initialCalibrationFile;
this.strategyFile=strategyFile;
this.resultsSuperDirectory=resultsSuperDirectory; // directory with subdirectories named as serial numbers to stro results
this.EEPROM_channel=EEPROM_channel; // EEPROM channel to read serial number from
this.saveResults=saveResults; // save focusing results
this.showResults=showResults; // show focusing (includingh intermediate) results
this.serialNumber=serialNumber; // camera serial number string
this.sensorTemperature=sensorTemperature; // last measured sensor temperature
this.comment=comment; // Comment to add to the results
this.maxCorr=maxCorr;
this.showHistoryDetails=showHistoryDetails;
this.showHistorySingleLine=showHistorySingleLine; // all parameters in a single line (easier to copy to spreadsheet)
this.showAcquiredImages=showAcquiredImages;
this.showFittedParameters=showFittedParameters;
this.psf_cutoffEnergy=psf_cutoffEnergy;
this.psf_cutoffLevel= psf_cutoffLevel;
this.psf_minArea= psf_minArea;
this.psf_blurSigma= psf_blurSigma;
this.correlationMinInitialContrast=correlationMinInitialContrast; // minimal contrast for the pattern of the center (initial point)
this.minimalPatternCluster=minimalPatternCluster; // minimal pattern cluster size (0 - disable retries)
this.scaleMinimalInitialContrast=scaleMinimalInitialContrast; // increase/decrease minimal contrast if initial cluster is >0 but less than minimalPatternCluster
this.searchOverlap=searchOverlap; // when searching for grid, step this amount of the FFTSize
this.smallestSubPix=smallestSubPix;
this.bitmapNonuniforityThreshold=bitmapNonuniforityThreshold;
this.subdiv=subdiv;
this.overexposedMaxFraction=overexposedMaxFraction;
this.minDefinedArea=minDefinedArea;
this.PSFKernelSize=PSFKernelSize;
this.approximateGrid = approximateGrid; // approximate grid with polynomial
this.centerPSF = centerPSF; // approximate grid with polynomial
this.mask1_sigma = mask1_sigma;
this.mask1_threshold = mask1_threshold;
this.gaps_sigma=gaps_sigma;
this.mask_denoise=mask_denoise;
this.deconvInvert = deconvInvert;
this.correlationSize=correlationSize;
this.correlationGaussWidth=correlationGaussWidth;
this.minUVSpan=minUVSpan;
this.flatFieldCorrection=flatFieldCorrection;
this.flatFieldExpand=flatFieldExpand;
this.thresholdFinish=thresholdFinish;// (copied from series) stop iterations if 2 last steps had less improvement (but not worsening )
this.numIterations=numIterations; // maximal number of iterations
this.goniometerMotors=goniometerMotors;
// this.goniometerMotors.stepsPerDegreeTilt=stepsPerDegreeTilt; // minus that positive steps make negative elevation
// this.goniometerMotors.stepsPerDegreeAxial=stepsPerDegreeAxial; // minus that positive steps make rotate CCW when looking from Eyesis top
this.targetDistance=targetDistance;
this.scanLatitudeLow=scanLatitudeLow; // lowest camera latitude to calibrate
this.scanLatitudeHigh=scanLatitudeHigh; // highest camera latitude to calibrate
this.scanOverlapHorizontal=scanOverlapHorizontal;
this.scanOverlapVertical=scanOverlapVertical;
this.scanLimitAxialStart=scanLimitAxialStart; // scan around camera axis from that angle
this.scanLimitAxialEnd=scanLimitAxialEnd; // scan around camera axis to that angle
this.scanBidirectional=scanBidirectional;
this.motorsSimultaneous=motorsSimultaneous;
}
public GoniometerParameters clone(){
return new GoniometerParameters(
this.goniometerMotors,
this.gridGeometryFile,
this.initialCalibrationFile,
this.strategyFile,
this.resultsSuperDirectory, // directory with subdirectories named as serial numbers to stro results
this.EEPROM_channel,// EEPROM channel to read serial number from
this.saveResults, // save focusing results
this.showResults, // show focusing (includingh intermediate) results
this.serialNumber, // camera serial number string
this.sensorTemperature, // last measured sensor temperature
this.comment,
this.maxCorr,
this.showHistoryDetails,
this.showHistorySingleLine, // all parameters in a single line (easier to copy to spreadsheet)
this.showAcquiredImages,
this.showFittedParameters,
this.psf_cutoffEnergy,
this.psf_cutoffLevel,
this.psf_minArea,
this.psf_blurSigma,
this.correlationMinInitialContrast, // minimal contrast for the pattern of the center (initial point)
this.minimalPatternCluster, // minimal pattern cluster size (0 - disable retries)
this.scaleMinimalInitialContrast, // increase/decrease minimal contrast if initial cluster is >0 but less than minimalPatternCluster
this.searchOverlap, // when searching for grid, step this amount of the FFTSize
this.smallestSubPix,
this.bitmapNonuniforityThreshold,
this.subdiv,
this.overexposedMaxFraction,
this.minDefinedArea,
this.PSFKernelSize,
this.approximateGrid,
this.centerPSF,
this.mask1_sigma,
this.mask1_threshold,
this.gaps_sigma,
this.mask_denoise,
this.deconvInvert,
this.correlationSize,
this.correlationGaussWidth,
this.minUVSpan,
this.flatFieldCorrection,
this.flatFieldExpand,
this.thresholdFinish,
this.numIterations,
this.targetDistance,
this.scanLatitudeLow,
this.scanLatitudeHigh,
this.scanOverlapHorizontal,
this.scanOverlapVertical,
this.scanLimitAxialStart, // scan around camera axis from that angle
this.scanLimitAxialEnd, // scan around camera axis to that angle
this.scanBidirectional,
this.motorsSimultaneous
);
}
public void setProperties(String prefix,Properties properties){
properties.setProperty(prefix+"gridGeometryFile",this.gridGeometryFile+"");
properties.setProperty(prefix+"initialCalibrationFile",this.initialCalibrationFile+"");
properties.setProperty(prefix+"strategyFile",this.strategyFile+"");
properties.setProperty(prefix+"resultsSuperDirectory",this.resultsSuperDirectory+"");
properties.setProperty(prefix+"serialNumber",this.serialNumber);
if (!Double.isNaN(this.sensorTemperature))properties.setProperty(prefix+"sensorTemperature",this.sensorTemperature+"");
properties.setProperty(prefix+"EEPROM_channel",this.EEPROM_channel+"");
properties.setProperty(prefix+"saveResults",this.saveResults+"");
properties.setProperty(prefix+"showResults",this.showResults+"");
properties.setProperty(prefix+"comment","<![CDATA["+this.comment+ "]]>");
properties.setProperty(prefix+"maxCorr",this.maxCorr+"");
properties.setProperty(prefix+"showHistoryDetails",this.showHistoryDetails+"");
properties.setProperty(prefix+"showHistorySingleLine",this.showHistorySingleLine+"");
properties.setProperty(prefix+"showAcquiredImages",this.showAcquiredImages+"");
properties.setProperty(prefix+"showFittedParameters",this.showFittedParameters+"");
properties.setProperty(prefix+"psf_cutoffEnergy",this.psf_cutoffEnergy+"");
properties.setProperty(prefix+"psf_cutoffLevel",this.psf_cutoffLevel+"");
properties.setProperty(prefix+"psf_minArea",this.psf_minArea+"");
properties.setProperty(prefix+"psf_blurSigma",this.psf_blurSigma+"");
properties.setProperty(prefix+"correlationMinInitialContrast",this.correlationMinInitialContrast+"");
properties.setProperty(prefix+"minimalPatternCluster",this.minimalPatternCluster+"");
properties.setProperty(prefix+"scaleMinimalInitialContrast",this.scaleMinimalInitialContrast+"");
properties.setProperty(prefix+"searchOverlap",this.searchOverlap+"");
properties.setProperty(prefix+"smallestSubPix",this.smallestSubPix+"");
properties.setProperty(prefix+"bitmapNonuniforityThreshold",this.bitmapNonuniforityThreshold+"");
properties.setProperty(prefix+"subdiv",this.subdiv+"");
properties.setProperty(prefix+"overexposedMaxFraction",this.overexposedMaxFraction+"");
properties.setProperty(prefix+"minDefinedArea",this.minDefinedArea+"");
properties.setProperty(prefix+"PSFKernelSize",this.PSFKernelSize+"");
properties.setProperty(prefix+"approximateGrid",this.approximateGrid+"");
properties.setProperty(prefix+"centerPSF",this.centerPSF+"");
properties.setProperty(prefix+"mask1_sigma",this.mask1_sigma+"");
properties.setProperty(prefix+"mask1_threshold",this.mask1_threshold+"");
properties.setProperty(prefix+"gaps_sigma",this.gaps_sigma+"");
properties.setProperty(prefix+"mask_denoise",this.mask_denoise+"");
properties.setProperty(prefix+"deconvInvert",this.deconvInvert+"");
properties.setProperty(prefix+"correlationSize",this.correlationSize+"");
properties.setProperty(prefix+"correlationGaussWidth",this.correlationGaussWidth+"");
properties.setProperty(prefix+"minUVSpan",this.minUVSpan+"");
properties.setProperty(prefix+"flatFieldCorrection",this.flatFieldCorrection+"");
properties.setProperty(prefix+"flatFieldExpand",this.flatFieldExpand+"");
properties.setProperty(prefix+"thresholdFinish",this.thresholdFinish+"");
properties.setProperty(prefix+"numIterations",this.numIterations+"");
properties.setProperty(prefix+"goniometerMotors_ipAddress",this.goniometerMotors.ipAddress+"");
properties.setProperty(prefix+"goniometerMotors_stepsPerSecond", this.goniometerMotors.stepsPerSecond+"");
properties.setProperty(prefix+"goniometerMotors_stepsPerDegreeTilt", this.goniometerMotors.stepsPerDegreeTilt+"");
properties.setProperty(prefix+"goniometerMotors_stepsPerDegreeAxial",this.goniometerMotors.stepsPerDegreeAxial+"");
properties.setProperty(prefix+"targetDistance",this.targetDistance+"");
properties.setProperty(prefix+"scanLatitudeLow",this.scanLatitudeLow+"");
properties.setProperty(prefix+"scanLatitudeHigh",this.scanLatitudeHigh+"");
properties.setProperty(prefix+"scanOverlapHorizontal",this.scanOverlapHorizontal+"");
properties.setProperty(prefix+"scanOverlapVertical",this.scanOverlapVertical+"");
properties.setProperty(prefix+"scanLimitAxialStart",this.scanLimitAxialStart+"");
properties.setProperty(prefix+"scanLimitAxialEnd",this.scanLimitAxialEnd+"");
properties.setProperty(prefix+"scanBidirectional",this.scanBidirectional+"");
properties.setProperty(prefix+"motorsSimultaneous",this.motorsSimultaneous+"");
}
public void getProperties(String prefix,Properties properties){
if (properties.getProperty(prefix+"gridGeometryFile")!=null)
this.gridGeometryFile=properties.getProperty(prefix+"gridGeometryFile");
if (properties.getProperty(prefix+"initialCalibrationFile")!=null)
this.initialCalibrationFile=properties.getProperty(prefix+"initialCalibrationFile");
if (properties.getProperty(prefix+"strategyFile")!=null)
this.strategyFile=properties.getProperty(prefix+"strategyFile");
if (properties.getProperty(prefix+"resultsSuperDirectory")!=null)
this.resultsSuperDirectory=properties.getProperty(prefix+"resultsSuperDirectory");
if (properties.getProperty(prefix+"serialNumber")!=null)
this.serialNumber=properties.getProperty(prefix+"serialNumber");
// this.serialNumber is only written, but never read from the configuration file (only from device)
if (properties.getProperty(prefix+"sensorTemperature")!=null) this.sensorTemperature=Double.parseDouble(properties.getProperty(prefix+"sensorTemperature"));
else this.sensorTemperature=Double.NaN;
if (properties.getProperty(prefix+"EEPROM_channel")!=null)
this.EEPROM_channel=Integer.parseInt(properties.getProperty(prefix+"EEPROM_channel"));
if (properties.getProperty(prefix+"saveResults")!=null)
this.saveResults=Boolean.parseBoolean(properties.getProperty(prefix+"saveResults"));
if (properties.getProperty(prefix+"showResults")!=null)
this.showResults=Boolean.parseBoolean(properties.getProperty(prefix+"showResults"));
if (properties.getProperty(prefix+"comment")!=null)
this.comment=properties.getProperty(prefix+"comment");
if ((this.comment.length()>10) && this.comment.substring(0,9).equals("<![CDATA[")) this.comment=this.comment.substring(9,this.comment.length()-3);
if (properties.getProperty(prefix+"maxCorr")!=null)
this.maxCorr=Double.parseDouble(properties.getProperty(prefix+"maxCorr"));
if (properties.getProperty(prefix+"showHistoryDetails")!=null)
this.showHistoryDetails=Boolean.parseBoolean(properties.getProperty(prefix+"showHistoryDetails"));
if (properties.getProperty(prefix+"showHistorySingleLine")!=null)
this.showHistorySingleLine=Boolean.parseBoolean(properties.getProperty(prefix+"showHistorySingleLine"));
if (properties.getProperty(prefix+"showAcquiredImages")!=null)
this.showAcquiredImages=Boolean.parseBoolean(properties.getProperty(prefix+"showAcquiredImages"));
if (properties.getProperty(prefix+"showFittedParameters")!=null)
this.showFittedParameters=Boolean.parseBoolean(properties.getProperty(prefix+"showFittedParameters"));
if (properties.getProperty(prefix+"psf_cutoffEnergy")!=null)
this.psf_cutoffEnergy=Double.parseDouble(properties.getProperty(prefix+"psf_cutoffEnergy"));
if (properties.getProperty(prefix+"psf_cutoffLevel")!=null)
this.psf_cutoffLevel=Double.parseDouble(properties.getProperty(prefix+"psf_cutoffLevel"));
if (properties.getProperty(prefix+"psf_minArea")!=null)
this.psf_minArea=Integer.parseInt(properties.getProperty(prefix+"psf_minArea"));
if (properties.getProperty(prefix+"psf_blurSigma")!=null)
this.psf_blurSigma=Double.parseDouble(properties.getProperty(prefix+"psf_blurSigma"));
if (properties.getProperty(prefix+"correlationMinInitialContrast")!=null)
this.correlationMinInitialContrast=Double.parseDouble(properties.getProperty(prefix+"correlationMinInitialContrast"));
if (properties.getProperty(prefix+"minimalPatternCluster")!=null)
this.minimalPatternCluster=Integer.parseInt(properties.getProperty(prefix+"minimalPatternCluster"));
if (properties.getProperty(prefix+"scaleMinimalInitialContrast")!=null)
this.scaleMinimalInitialContrast=Double.parseDouble(properties.getProperty(prefix+"scaleMinimalInitialContrast"));
if (properties.getProperty(prefix+"searchOverlap")!=null)
this.searchOverlap=Double.parseDouble(properties.getProperty(prefix+"searchOverlap"));
if (properties.getProperty(prefix+"smallestSubPix")!=null)
this.smallestSubPix=Double.parseDouble(properties.getProperty(prefix+"smallestSubPix"));
if (properties.getProperty(prefix+"bitmapNonuniforityThreshold")!=null)
this.bitmapNonuniforityThreshold=Double.parseDouble(properties.getProperty(prefix+"bitmapNonuniforityThreshold"));
if (properties.getProperty(prefix+"subdiv")!=null)
this.subdiv=Integer.parseInt(properties.getProperty(prefix+"subdiv"));
if (properties.getProperty(prefix+"overexposedMaxFraction")!=null)
this.overexposedMaxFraction=Double.parseDouble(properties.getProperty(prefix+"overexposedMaxFraction"));
if (properties.getProperty(prefix+"minDefinedArea")!=null)
this.minDefinedArea=Double.parseDouble(properties.getProperty(prefix+"minDefinedArea"));
if (properties.getProperty(prefix+"PSFKernelSize")!=null)
this.PSFKernelSize=Integer.parseInt(properties.getProperty(prefix+"PSFKernelSize"));
if (properties.getProperty(prefix+"approximateGrid")!=null)
this.approximateGrid=Boolean.parseBoolean(properties.getProperty(prefix+"approximateGrid"));
if (properties.getProperty(prefix+"centerPSF")!=null)
this.centerPSF=Boolean.parseBoolean(properties.getProperty(prefix+"centerPSF"));
if (properties.getProperty(prefix+"mask1_sigma")!=null)
this.mask1_sigma=Double.parseDouble(properties.getProperty(prefix+"mask1_sigma"));
if (properties.getProperty(prefix+"mask1_threshold")!=null)
this.mask1_threshold=Double.parseDouble(properties.getProperty(prefix+"mask1_threshold"));
if (properties.getProperty(prefix+"gaps_sigma")!=null)
this.mask1_threshold=Double.parseDouble(properties.getProperty(prefix+"gaps_sigma"));
if (properties.getProperty(prefix+"mask_denoise")!=null)
this.mask_denoise=Double.parseDouble(properties.getProperty(prefix+"mask_denoise"));
if (properties.getProperty(prefix+"deconvInvert")!=null)
this.deconvInvert=Double.parseDouble(properties.getProperty(prefix+"deconvInvert"));
if (properties.getProperty(prefix+"correlationSize")!=null)
this.correlationSize=Integer.parseInt(properties.getProperty(prefix+"correlationSize"));
if (properties.getProperty(prefix+"correlationGaussWidth")!=null)
this.correlationGaussWidth=Double.parseDouble(properties.getProperty(prefix+"correlationGaussWidth"));
if (properties.getProperty(prefix+"minUVSpan")!=null)
this.minUVSpan=Double.parseDouble(properties.getProperty(prefix+"minUVSpan"));
if (properties.getProperty(prefix+"flatFieldCorrection")!=null)
this.flatFieldCorrection=Boolean.parseBoolean(properties.getProperty(prefix+"flatFieldCorrection"));
if (properties.getProperty(prefix+"flatFieldExpand")!=null)
this.flatFieldExpand=Double.parseDouble(properties.getProperty(prefix+"flatFieldExpand"));
if (properties.getProperty(prefix+"thresholdFinish")!=null)
this.thresholdFinish=Double.parseDouble(properties.getProperty(prefix+"thresholdFinish"));
if (properties.getProperty(prefix+"numIterations")!=null)
this.numIterations=Integer.parseInt(properties.getProperty(prefix+"numIterations"));
if (properties.getProperty(prefix+"goniometerMotors_ipAddress")!=null)
this.goniometerMotors.ipAddress=properties.getProperty(prefix+"goniometerMotors_ipAddress");
if (properties.getProperty(prefix+"goniometerMotors_stepsPerSecond")!=null)
this.goniometerMotors.stepsPerSecond=Double.parseDouble(properties.getProperty(prefix+"goniometerMotors_stepsPerSecond"));
if (properties.getProperty(prefix+"goniometerMotors_stepsPerDegreeTilt")!=null)
this.goniometerMotors.stepsPerDegreeTilt=Double.parseDouble(properties.getProperty(prefix+"goniometerMotors_stepsPerDegreeTilt"));
if (properties.getProperty(prefix+"goniometerMotors_stepsPerDegreeAxial")!=null)
this.goniometerMotors.stepsPerDegreeAxial=Double.parseDouble(properties.getProperty(prefix+"goniometerMotors_stepsPerDegreeAxial"));
if (properties.getProperty(prefix+"targetDistance")!=null)
this.targetDistance=Double.parseDouble(properties.getProperty(prefix+"targetDistance"));
if (properties.getProperty(prefix+"scanLatitudeLow")!=null)
this.scanLatitudeLow=Double.parseDouble(properties.getProperty(prefix+"scanLatitudeLow"));
if (properties.getProperty(prefix+"scanLatitudeHigh")!=null)
this.scanLatitudeHigh=Double.parseDouble(properties.getProperty(prefix+"scanLatitudeHigh"));
if (properties.getProperty(prefix+"scanOverlapHorizontal")!=null)
this.scanOverlapHorizontal=Double.parseDouble(properties.getProperty(prefix+"scanOverlapHorizontal"));
if (properties.getProperty(prefix+"scanOverlapVertical")!=null)
this.scanOverlapVertical=Double.parseDouble(properties.getProperty(prefix+"scanOverlapVertical"));
if (properties.getProperty(prefix+"scanLimitAxialStart")!=null)
this.scanLimitAxialStart=Double.parseDouble(properties.getProperty(prefix+"scanLimitAxialStart"));
if (properties.getProperty(prefix+"scanLimitAxialEnd")!=null)
this.scanLimitAxialEnd=Double.parseDouble(properties.getProperty(prefix+"scanLimitAxialEnd"));
if (properties.getProperty(prefix+"scanBidirectional")!=null)
this.scanBidirectional=Boolean.parseBoolean(properties.getProperty(prefix+"scanBidirectional"));
if (properties.getProperty(prefix+"motorsSimultaneous")!=null)
this.motorsSimultaneous=Boolean.parseBoolean(properties.getProperty(prefix+"motorsSimultaneous"));
}
public boolean showDialog(String title) {
GenericDialog gd = new GenericDialog(title);
// this.serialNumber, // camera serial number string
gd.addMessage("Sensor board serial number is "+(((this.serialNumber==null)||(this.serialNumber==""))?"not specified":this.serialNumber));
gd.addStringField ("Grid geometry file", this.gridGeometryFile,40);
gd.addStringField ("Initial camera intrinsic/extrinsic parametres file", this.initialCalibrationFile,40);
gd.addStringField ("Levenberg-Marquardt algorithm strategy file", this.strategyFile,40);
gd.addStringField ("Focusing results superdirectory (individual will be named by serial numbers)", this.resultsSuperDirectory,40);
gd.addNumericField("EEPROM channel to read sensor serial number from", this.EEPROM_channel, 0,4,"");
gd.addCheckbox ("Save goniometer results (including intermediate) ", this.saveResults);
gd.addCheckbox ("Show SFE focusing results (including intermediate) ", this.showResults);
gd.addStringField ("Comment to add to the result files", this.comment,80);
gd.addNumericField("Maximal grid correction between images", this.maxCorr, 3,5,"pix");
gd.addCheckbox ("Show history details (per color info)", this.showHistoryDetails);
gd.addCheckbox ("Show history details in a single line (for spreadheets)", this.showHistorySingleLine);
gd.addCheckbox ("Show acquired images", this.showAcquiredImages); // true; // ignore lateral chromatic aberration (center OTF to 0,0)
gd.addCheckbox ("Show LMA fitted parameters", this.showFittedParameters); // true; // ignore lateral chromatic aberration (center OTF to 0,0)
gd.addMessage("When approximating measured PSF for different areas/colors:");
gd.addNumericField("Disregard pixels outside of this fraction of the total energy", 100*this.psf_cutoffEnergy, 2,6,"%");
gd.addNumericField("Disregard pixels below this fraction of the maximal value", 100*this.psf_cutoffLevel, 2,6,"%");
gd.addNumericField("Minimal selection size (will continue even if previous conditions matched)", this.psf_minArea, 0,3,"sub-pix");
gd.addNumericField("Optionally blur the calculated selection mask", this.psf_blurSigma, 2,6,"sub-pix");
gd.addMessage ("The following parameters overwrite some defined for aberration measurements in other dialogs");
gd.addNumericField("Correlation minimal contrast for initial search:", this.correlationMinInitialContrast, 3);
gd.addNumericField("Minimal initial pattern cluster size (0 - disable retries)", this.minimalPatternCluster, 0);
gd.addNumericField("Scale minimal contrast if the initial cluster is nonzero but smaller", this.scaleMinimalInitialContrast, 3);
gd.addNumericField("Overlap of FFT areas when searching for pattern", this.searchOverlap, 3);
gd.addNumericField("Smallest fraction to subdivide pixels at simulation", this.smallestSubPix, 3,5,"sensor pix");
gd.addNumericField("Maximal difference of the pattern value in the corners that triggers subdivision", this.bitmapNonuniforityThreshold, 3);
gd.addNumericField("Subdivide simulated pattern by:", this.subdiv, 0);
gd.addNumericField("Allowed overexposed pixels (fraction of the area) ",this.overexposedMaxFraction,3); // 0.005; // allowed fraction of the overexposed pixels in the PSF kernel measurement area
gd.addNumericField("Min fraction of the FFT square (weighted) to have defined pattern", this.minDefinedArea, 3);
gd.addNumericField ("PSF kernel size", this.PSFKernelSize, 0);
gd.addCheckbox ("Approximate pattern grid with a polynomial",this.approximateGrid); // true; // ignore lateral chromatic aberration (center OTF to 0,0)
gd.addCheckbox ("Center PSF by modifying phase", this.centerPSF); // true; // ignore lateral chromatic aberration (center OTF to 0,0)
gd.addNumericField("Bluring power spectrum to remove pattern grid (in pattern base freq)", this.mask1_sigma, 3);
gd.addNumericField("Threshold to supress spectral points not present in the pattern ", this.mask1_threshold, 3);
gd.addNumericField("Sigma for filling the OTF ", this.gaps_sigma, 3);
gd.addNumericField("Denoise mask ", this.mask_denoise, 3);
gd.addNumericField("Invert deconvolution if less than", this.deconvInvert, 3);
gd.addNumericField("Correlation size:", this.correlationSize, 0); // 64
gd.addNumericField("Correlation Gauss width (relative):", this.correlationGaussWidth, 3);
gd.addNumericField("Minimal UV span in correlation window to trigger FFT size increase",this.minUVSpan, 3);
gd.addCheckbox ("Compensate uneven pattern intensity", this.flatFieldCorrection);
gd.addNumericField("Expand during extrapolation (relative to the average grid period)", this.flatFieldExpand, 3);
gd.addNumericField("Threshold RMS to exit LMA", this.thresholdFinish, 7,9,"pix");
gd.addNumericField("Maximal number of LMA iterations per series",this.numIterations, 0);
gd.addMessage("Parameters for scanning/acquisition");
gd.addStringField ("Goniometer motors IP address", this.goniometerMotors.ipAddress,40);
gd.addNumericField("Motors rotation speed ", this.goniometerMotors.stepsPerSecond,6,12,"steps/second");
gd.addNumericField("Motor steps per tilt angular degree (currently negative) ",this.goniometerMotors.stepsPerDegreeTilt,6,12,"steps");
gd.addNumericField("Motor steps per axial angular degree (currently negative) ",this.goniometerMotors.stepsPerDegreeAxial,6,12,"steps");
// gd.addNumericField("Tilt scan step (not larger than)", this.scanStepTilt,2,6,"degrees");
// gd.addNumericField("Axial scan step (not larger than)", this.scanStepAxial,2,6,"degrees");
gd.addNumericField("Distance to target (for overlap calculation)", this.targetDistance,2,7,"mm");
gd.addNumericField("Horizontal overlap", 100*this.scanOverlapHorizontal,2,5,"%");
gd.addNumericField("Vertical overlap", 100*this.scanOverlapVertical,2,5,"%");
gd.addNumericField("Lowest camera view latitude to calibrate (nadir=-90)", this.scanLatitudeLow,2,6,"degrees");
gd.addNumericField("Highest camera view latitude to calibrate (zenith=90)", this.scanLatitudeHigh,2,6,"degrees");
// gd.addNumericField("Tilt scan start angle", this.scanLimitTiltStart,2,6,"degrees");
// gd.addNumericField("Tilt scan end angle", this.scanLimitTiltEnd,2,6,"degrees");
gd.addNumericField("Axial scan start angle", this.scanLimitAxialStart,2,6,"degrees");
gd.addNumericField("Axial scan end angle", this.scanLimitAxialEnd,2,6,"degrees");
gd.addCheckbox ("Axial scan bidirectional", this.scanBidirectional);
gd.addCheckbox ("Allow simultaneous operation of motors", this.motorsSimultaneous);
if (!Double.isNaN(this.sensorTemperature)) gd.addMessage("Last measured sensor temperature is "+this.sensorTemperature+" C");
WindowTools.addScrollBars(gd);
gd.showDialog();
if (gd.wasCanceled()) return false;
this.gridGeometryFile= gd.getNextString();
this.initialCalibrationFile= gd.getNextString();
this.strategyFile= gd.getNextString();
this.resultsSuperDirectory= gd.getNextString();
this.EEPROM_channel= (int) gd.getNextNumber();
this.saveResults= gd.getNextBoolean();
this.showResults= gd.getNextBoolean();
// this.comment= gd.getNextString().replace(' ','_'); //TODO: - add escape
this.comment= gd.getNextString();
this.maxCorr= gd.getNextNumber();
this.showHistoryDetails= gd.getNextBoolean();
this.showHistorySingleLine= gd.getNextBoolean();
this.showAcquiredImages= gd.getNextBoolean();
this.showFittedParameters= gd.getNextBoolean();
this.psf_cutoffEnergy= 0.01*gd.getNextNumber();
this.psf_cutoffLevel= 0.01*gd.getNextNumber();
this.psf_minArea= (int) gd.getNextNumber();
this.psf_blurSigma= gd.getNextNumber();
this.correlationMinInitialContrast=gd.getNextNumber();
this.minimalPatternCluster=(int) gd.getNextNumber();
this.scaleMinimalInitialContrast=gd.getNextNumber();
this.searchOverlap= gd.getNextNumber();
this.smallestSubPix= gd.getNextNumber();
this.bitmapNonuniforityThreshold=gd.getNextNumber();
this.subdiv= (int) gd.getNextNumber();
this.overexposedMaxFraction= gd.getNextNumber();
this.minDefinedArea= gd.getNextNumber();
this.PSFKernelSize= (int) gd.getNextNumber();
this.approximateGrid= gd.getNextBoolean();
this.centerPSF= gd.getNextBoolean();
this.mask1_sigma= gd.getNextNumber();
this.mask1_threshold= gd.getNextNumber();
this.gaps_sigma= gd.getNextNumber();
this.mask_denoise= gd.getNextNumber();
this.deconvInvert= gd.getNextNumber();
this.correlationSize= (int) gd.getNextNumber();
this.correlationGaussWidth= gd.getNextNumber();
this.minUVSpan= gd.getNextNumber();
this.flatFieldCorrection= gd.getNextBoolean();
this.flatFieldExpand= gd.getNextNumber();
this.thresholdFinish= gd.getNextNumber();
this.numIterations= (int) gd.getNextNumber();
this.goniometerMotors.ipAddress= gd.getNextString();
this.goniometerMotors.stepsPerSecond= gd.getNextNumber();
this.goniometerMotors.stepsPerDegreeTilt= gd.getNextNumber();
this.goniometerMotors.stepsPerDegreeAxial=gd.getNextNumber();
this.targetDistance= gd.getNextNumber();
this.scanOverlapHorizontal= 0.01*gd.getNextNumber();
this.scanOverlapVertical= 0.01*gd.getNextNumber();
this.scanLatitudeLow= gd.getNextNumber();
this.scanLatitudeHigh= gd.getNextNumber();
this.scanLimitAxialStart= gd.getNextNumber();
this.scanLimitAxialEnd= gd.getNextNumber();
this.scanBidirectional= gd.getNextBoolean();
this.motorsSimultaneous= gd.getNextBoolean();
return true;
}
/** ======================================================================== */
}
}
/**
** -----------------------------------------------------------------------------**
** JP46_Reader_camera.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 <http://www.gnu.org/licenses/>.
** -----------------------------------------------------------------------------**
**
*/
import ij.*;
import ij.io.*;
import ij.process.*;
import ij.gui.*;
import ij.plugin.frame.*;
import ij.text.*;
import java.awt.*;
import java.awt.event.*;
import java.net.*;
import java.util.Iterator;
import java.util.Properties;
import java.util.Set;
import java.io.*;
import javax.swing.*;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
/** This plugin opens images in Elphel JP4/JP46 format (opens as JPEG, reads MakerNote and converts). */
public class JP46_Reader_camera extends PlugInFrame implements ActionListener {
/**
*
*/
private static final long serialVersionUID = 390855361964415147L;
Panel panel1;
Panel confpanel;
Frame instance;
String arg;
static File dir;
public String camera_url = "http://192.168.0.236:8081/";
public String camera_img = "bimg";
public String camera_img_new = "towp/wait/bimg"; // will always wait for the next image (repetitive acquisitions get new images)
public String camera_jp46settings = "";
public boolean IS_SILENT=true;
public boolean ABSOLUTELY_SILENT=false;
public boolean demux=true;
public String imageTitle="cameraImage";
private int ExifOffset=0x0c;
public JP46_Reader_camera() {
super("JP46 Reader Camera");
if (IJ.versionLessThan("1.39t")) return;
if (instance!=null) {
instance.toFront();
return;
}
instance = this;
addKeyListener(IJ.getInstance());
panel1 = new Panel();
panel1.setLayout(new GridLayout(6, 1, 50, 5));
addButton("Open JP4/JP46...",panel1);
addButton("Open JP4/JP46 from camera",panel1);
addButton("Configure...",panel1);
addButton("Show image properties",panel1);
addButton("Decode image info to properties",panel1);
addButton("Split Bayer",panel1);
add(panel1);
pack();
GUI.center(this);
setVisible(true);
}
public JP46_Reader_camera(boolean showGUI) {
super("JP46 Reader Camera");
if (IJ.versionLessThan("1.39t")) return;
if (instance!=null) {
instance.toFront();
return;
}
instance = this;
addKeyListener(IJ.getInstance());
panel1 = new Panel();
panel1.setLayout(new GridLayout(6, 1, 50, 5));
addButton("Open JP4/JP46...",panel1);
addButton("Open JP4/JP46 from camera",panel1);
addButton("Configure...",panel1);
addButton("Show image properties",panel1);
addButton("Decode image info to properties",panel1);
addButton("Split Bayer",panel1);
add(panel1);
pack();
GUI.center(this);
setVisible(showGUI);
}
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;
/** button */
if (label.equals("Open JP4/JP46...")) {
read_jp46(arg,true);
}else if (label.equals("Open JP4/JP46 (no scale)...")) {
read_jp46(arg,false);
}else if (label.equals("Configure...")) {
showConfigDialog(); // open configure dialog
}else if (label.equals("Open JP4/JP46 from camera")) {
openURL(camera_url + camera_img_new + camera_jp46settings, arg, true);
}else if (label.equals("Open JP4/JP46 from camera (no scale)")) {
openURL(camera_url + camera_img_new + camera_jp46settings, arg, false);
}else if (label.equals("Show image properties")) {
ImagePlus imp_sel = WindowManager.getCurrentImage();
if (imp_sel==null){
IJ.showMessage("Error","No images selected");
return;
}
listImageProperties (imp_sel);
}
else if (label.equals("Decode image info to properties")) {
ImagePlus imp_sel = WindowManager.getCurrentImage();
if (imp_sel==null){
IJ.showMessage("Error","No images selected");
return;
}
decodeProperiesFromInfo(imp_sel);
listImageProperties (imp_sel);
}
else if (label.equals("Split Bayer")) {
ImagePlus imp_sel = WindowManager.getCurrentImage();
if (imp_sel==null){
IJ.showMessage("Error","No images selected");
return;
}
splitShowBayer(imp_sel);
}
IJ.showStatus("DONE");
}
public void splitShowBayer(ImagePlus imp){
float [] pixels= (float[]) imp.getProcessor().getPixels();
int height=imp.getHeight();
int width= imp.getWidth();
int halfHeight=height/2;
int halfWidth=width/2;
float [][] bayerPixels=new float[4][halfHeight * halfWidth];
for (int iy=0;iy<halfHeight;iy++) for (int ix=0;ix<halfWidth;ix++){
int oIndex=iy*halfWidth+ix;
int iIndex=iy*2*width+ix*2;
bayerPixels[0][oIndex]=pixels[iIndex];
bayerPixels[1][oIndex]=pixels[iIndex+1];
bayerPixels[2][oIndex]=pixels[iIndex+width];
bayerPixels[3][oIndex]=pixels[iIndex+width+1];
}
ImageStack array_stack=new ImageStack(halfWidth,halfHeight);
for (int i=0;i<4;i++) array_stack.addSlice("chn-"+i, bayerPixels[i]);
ImagePlus imp_stack = new ImagePlus(imp.getTitle()+"-BAYER", array_stack);
imp_stack.getProcessor().resetMinAndMax();
imp_stack.show();
}
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();
for (int i=0; i<files.length; i++) {
open(path, files[i].getName(), arg, scale);
}
}
public boolean showConfigDialog() {
GenericDialog gd = new GenericDialog("Configure");
gd.addStringField ("Image title: ", getTitle(), 20);
gd.addStringField("Camera Address: ", getURL(), 20);
gd.addStringField("Image: ", camera_img, 20);
gd.addStringField("Image (new): ", camera_img_new, 20);
gd.addStringField("JP46 Parameters: ", camera_jp46settings, 50);
gd.addCheckbox("Demux composite frame? ", demux);
gd.addCheckbox("Silent? ", IS_SILENT);
// gd.addCheckbox("JP4 (not JP46)? ", IS_JP4);
confpanel = new Panel();
gd.addPanel(confpanel);
addButton("Open JP4/JP46 (no scale)...", confpanel);
addButton("Open JP4/JP46 from camera (no scale)", confpanel);
//Vector textfields = gd.getStringFields();
//((TextField)fields.elementAt(0)).SetWidth = 20;
gd.showDialog();
if (gd.wasCanceled()) return false;
setTitle(gd.getNextString());
setURL (gd.getNextString());
camera_img = gd.getNextString();
camera_img_new = gd.getNextString();
camera_jp46settings = gd.getNextString();
demux=gd.getNextBoolean();
IS_SILENT=gd.getNextBoolean();
return true;
}
public ImagePlus open(String directory, String fileName, String arg, boolean scale) {
return open(directory, fileName, arg, scale, null,true);
}
public ImagePlus open(String directory, String fileName, String arg, boolean scale, ImagePlus imp_src) {
return open(directory, fileName, arg, scale, imp_src,true);
}
public ImagePlus open(
String directory,
String fileName,
String arg,
boolean scale,
ImagePlus imp_src,
boolean showImage) {
long[] ElphelMakerNote=null;
ImagePlus imp = null;
boolean reuse_imp=false;
boolean showDemux=showImage && demux;
if (demux) showImage=false;
double [] xtraExif=new double[1]; // ExposureTime
try {
imp = openJpegOrGif(directory, fileName);
if (imp == null) {
IJ.showMessage("JP46 Reader Error", "Could not open "+directory+"" + fileName + " as JPEG/JP46");
} else {
if ((imp_src==null)&& showImage) imp.show(); /** Shows before re-ordering*/
ElphelMakerNote = readElphelMakerNote(directory, fileName, 16,xtraExif); /** after or 8.2.2 */
if (ElphelMakerNote==null) ElphelMakerNote = readElphelMakerNote(directory, fileName, 14,xtraExif); /** after or 8.0.8.32 */
if (ElphelMakerNote==null) ElphelMakerNote = readElphelMakerNote(directory, fileName, 12,xtraExif); /** after or 8.0.7.3 */
if (ElphelMakerNote==null) ElphelMakerNote = readElphelMakerNote(directory, fileName, 8 ,xtraExif); /** before 8.0.7.3 */
}
} catch (IOException e) {
IJ.showStatus("");
String error = e.getMessage();
if (error==null || error.equals("")) error = ""+e;
IJ.showMessage("JP46 Reader", ""+error);
return null;
}
if (imp!=null) {
reuse_imp=jp46Reorder(imp, ElphelMakerNote, scale, imp_src);
if (reuse_imp) {
imp=imp_src;
} else if ((imp_src!=null)&& showImage) { /** tried to reuse, but wrong size */
imp.show(); /** never did that before */
}
if ((xtraExif!=null) && !Double.isNaN(xtraExif[0])){
imp.setProperty("EXPOSURE", String.format("%f",xtraExif[0]));
}
if (showImage) imp.updateAndDraw(); /** Redisplays final image*/
if (showDemux) {
if (!this.ABSOLUTELY_SILENT) System.out.println("demuxing...");
ImagePlus imp_0 = demuxImage(imp, 0); if (imp_0!=null) imp_0.show();
ImagePlus imp_1 = demuxImage(imp, 1); if (imp_1!=null) imp_1.show();
ImagePlus imp_2 = demuxImage(imp, 2); if (imp_2!=null) imp_2.show();
if ((imp_0==null) && (imp_0==null) && (imp_0==null)) imp.show(); // Show original image if demux failed (single original)
}
return imp;
}
return null;
}
public ImagePlus openURL(ImagePlus imp_src) {
if (imp_src==null) return openURL(camera_url + camera_img_new + camera_jp46settings, arg, true);
return openURL(camera_url + camera_img_new + camera_jp46settings, arg, true, imp_src,true);
}
public ImagePlus openURL() {
return openURL(camera_url + camera_img_new + camera_jp46settings, arg, true);
}
public ImagePlus openURL(String url, String arg, boolean scale) {
return openURL(url, arg, scale, null,true);
}
public ImagePlus openURL(
String url,
String arg,
boolean scale,
ImagePlus imp_src,
boolean showImage) {
long[] ElphelMakerNote=null;
ImagePlus imp = null;
boolean reuse_imp=false;
boolean showDemux=showImage && demux;
if (demux) showImage=false;
double [] xtraExif=new double[1]; // ExposureTime
// System.out.println("imp_src is "+((imp_src!=null)?"not ":"")+"null");
try {
imp = openJpegOrGifUsingURL(url);
if (imp == null) {
IJ.showMessage("JP46 Reader Error", "Could not open the URL: " + url + " as JPEG/JP46");
} else {
if ((imp_src==null) && showImage) {
// System.out.println("show() 1");
imp.show(); /** Shows before re-ordering*/
}
/// get rid of the "/towp/wait" if any - there is a chance to re-read the same image
ElphelMakerNote = readElphelMakerNoteURL(url.replaceFirst("/towp/wait",""), 16,xtraExif); /** after or 8.2.2 */
if (ElphelMakerNote==null) ElphelMakerNote = readElphelMakerNoteURL(url.replaceFirst("/towp/wait",""), 14,xtraExif); /** after or 8.0.8.32 */
if (ElphelMakerNote==null) ElphelMakerNote = readElphelMakerNoteURL(url.replaceFirst("/towp/wait",""), 12,xtraExif); /** after or 8.0.7.3 */
if (ElphelMakerNote==null) ElphelMakerNote = readElphelMakerNoteURL(url.replaceFirst("/towp/wait",""), 8 ,xtraExif ); /** before 8.0.7.3 */
}
} catch (IOException e) {
IJ.showStatus("");
String error = e.getMessage();
if (error==null || error.equals(""))
error = ""+e;
IJ.showMessage("JP46 Reader", ""+error);
return null;
}
if (imp!=null) {
reuse_imp=jp46Reorder(imp, ElphelMakerNote, scale, imp_src);
if (reuse_imp) {
imp=imp_src;
} else if ((imp_src!=null) && showImage) { /** tried to reuse, but wrong size */
// System.out.println("show() 2");
imp.show(); /** never did that before */
}
if ((xtraExif!=null) && !Double.isNaN(xtraExif[0])){
imp.setProperty("EXPOSURE", String.format("%f",xtraExif[0]));
}
if (showImage) imp.updateAndDraw(); /** Redisplays final image*/
if (showDemux) {
if (!this.ABSOLUTELY_SILENT) System.out.println("demuxing...");
ImagePlus imp_0 = demuxImage(imp, 0); if (imp_0!=null) imp_0.show();
ImagePlus imp_1 = demuxImage(imp, 1); if (imp_1!=null) imp_1.show();
ImagePlus imp_2 = demuxImage(imp, 2); if (imp_2!=null) imp_2.show();
if ((imp_0==null) && (imp_0==null) && (imp_0==null)) imp.show(); // Show original image if demux failed (single original)
}
return imp;
}
return null;
}
boolean jp46Reorder(ImagePlus imp, long[] MakerNote, boolean scale) {
return jp46Reorder(imp, MakerNote, scale, null);
}
void swapArrayElements (double[]arr,int i, int j) {
double tmp=arr[i];
arr[i]=arr[j];
arr[j]=tmp;
}
void swapArrayElements (long[]arr,int i, int j) {
long tmp=arr[i];
arr[i]=arr[j];
arr[j]=tmp;
}
boolean jp46Reorder(ImagePlus imp, long[] MakerNote, boolean scale, ImagePlus imp_src) {
// int MARGIN=2; // 2 pixels in JP4/JP46 mode around WOI
double[] gains= new double[4];
double[] blacks= new double[4];
double[] blacks256= new double[4];
double[] gammas= new double[4];
long [] gamma_scales= new long[4]; /** now not used, was scale _after_ gamma is applied, 0x400(default) corersponds to 1.0 */
int i;
double[][] rgammas=new double[4][];
double min_gain;
long WOI_LEFT,WOI_WIDTH,WOI_TOP,WOI_HEIGHT,BAYER_MODE,DCM_HOR,DCM_VERT,BIN_HOR,BIN_VERT;
long COLOR_MODE=0;
long FLIPH=0;
long FLIPV=0;
long HEIGHT1=0;
long HEIGHT2=0;
long HEIGHT3=0;
long BLANK1=0;
long BLANK2=0;
boolean FLIPH1=false;
boolean FLIPV1=false;
boolean FLIPH2=false;
boolean FLIPV2=false;
boolean FLIPH3=false;
boolean FLIPV3=false;
boolean COMPOSITE=false;
boolean PORTRAIT=false;
boolean YTABLEFORC=false;
long QUALITY=0;
long CQUALITY=0;
long CORING_INDEX_Y=0;
long CORING_INDEX_C=0;
double [] satValue={255.0, 255.0, 255.0, 255.0};
if (MakerNote !=null) {
for (i=0;i<4;i++) { /** r,g,gb,b */
gains[i]= MakerNote[i]/65536.0;
blacks[i]=(MakerNote[i+4]>>24)/256.0;
gammas[i]=((MakerNote[i+4]>>16)&0xff)/100.0;
gamma_scales[i]=MakerNote[i+4] & 0xffff;
imp.setProperty("gains_"+i,String.format("%f",gains[i]));
imp.setProperty("blacks_"+i,String.format("%f",blacks[i]));
imp.setProperty("gammas_"+i,String.format("%f",gammas[i]));
imp.setProperty("gamma_scales_"+i,String.format("%d",gamma_scales[i]));
}
IJ.showStatus("R=" +(0.001*((int)(1000*gains[0])))+
" G=" +IJ.d2s(gains[1],3)+
" Gb="+IJ.d2s(gains[2],3)+
" B=" +IJ.d2s(gains[3],3)+
" Gamma[0]="+IJ.d2s(gammas[0],3)+
" 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>=14) {
COMPOSITE= ((MakerNote[10] & 0xc0000000)!=0);
if (COMPOSITE) {
HEIGHT1= MakerNote[11] & 0xffff;
BLANK1= (MakerNote[11]>>16) & 0xffff;
HEIGHT2= MakerNote[12] & 0xffff;
BLANK2= (MakerNote[12]>>16) & 0xffff;
// HEIGHT3=(( MakerNote[9]>>16)+2*MARGIN) - HEIGHT1-BLANK1-HEIGHT2-BLANK2;
HEIGHT3=( MakerNote[9]>>16) - HEIGHT1-BLANK1-HEIGHT2-BLANK2;
FLIPH1= (((MakerNote[10] >> 24) & 1)!=0); // Same value as FLIP_H
FLIPV1= (((MakerNote[10] >> 25) & 1)!=0); // Same value as FLIP_V
FLIPH2= (((MakerNote[10] >> 26) & 1)!=0);
FLIPV2= (((MakerNote[10] >> 27) & 1)!=0);
FLIPH3= (((MakerNote[10] >> 28) & 1)!=0);
FLIPV3= (((MakerNote[10] >> 29) & 1)!=0);
}
PORTRAIT= (((MakerNote[13] >> 7) & 1)!=0);
YTABLEFORC=(((MakerNote[13] >> 15) & 1)!=0);
QUALITY= (MakerNote[13] & 0x7f);
CQUALITY= ((MakerNote[13]>>8) & 0x7f);
if (CQUALITY==0) CQUALITY=QUALITY;
CORING_INDEX_Y= ((MakerNote[13]>>16) & 0x7f);
CORING_INDEX_C= ((MakerNote[13]>>24) & 0x7f);
if (CORING_INDEX_C==0) CORING_INDEX_C=CORING_INDEX_Y;
}
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"+ ((COLOR_MODE==2)?"JP46":((COLOR_MODE==5)?"JP4":((COLOR_MODE==0)?"MONO":"OTHER"))) +"\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";
imp.setProperty("WOI_LEFT", String.format("%d",WOI_LEFT));
imp.setProperty("WOI_WIDTH", String.format("%d",WOI_WIDTH));
imp.setProperty("WOI_TOP", String.format("%d",WOI_TOP));
imp.setProperty("WOI_HEIGHT",String.format("%d",WOI_HEIGHT));
imp.setProperty("FLIPH", String.format("%d",FLIPH));
imp.setProperty("FLIPV", String.format("%d",FLIPV));
imp.setProperty("BAYER_MODE",String.format("%d",BAYER_MODE));
imp.setProperty("COLOR_MODE",((COLOR_MODE==2)?"JP46":((COLOR_MODE==5)?"JP4":((COLOR_MODE==0)?"MONO":"OTHER"))));
imp.setProperty("DCM_HOR", String.format("%d",DCM_HOR));
imp.setProperty("DCM_VERT", String.format("%d",DCM_VERT));
imp.setProperty("BIN_HOR", String.format("%d",BIN_HOR));
imp.setProperty("BIN_VERT", String.format("%d",BIN_VERT));
}
if (MakerNote.length>=14) {
info+="COMPOSITE\t" + COMPOSITE+"\t\t\t\n";
info+="ORIENTATION\t" + (PORTRAIT?"PORTRAIT":"LANDSCAPE" )+"\t\t\t\n";
info+="JPEG quality\t" + QUALITY+"\t"+((CQUALITY!=QUALITY)?("("+CQUALITY+")"):"")+"\t"+(YTABLEFORC? "Color use Y table":"")+"\t\n";
info+="Coring index\t" + CORING_INDEX_Y+"\t"+((CORING_INDEX_C!=CORING_INDEX_Y)?("("+CORING_INDEX_C+")"):"")+"\t\t\n";
imp.setProperty("COMPOSITE",String.format("%d",COMPOSITE?1:0));
imp.setProperty("ORIENTATION",(PORTRAIT?"PORTRAIT":"LANDSCAPE" ));
imp.setProperty("QUALITY",String.format("%d",QUALITY)); //not full
imp.setProperty("CORING_INDEX_Y",String.format("%d",CORING_INDEX_Y));
imp.setProperty("CORING_INDEX_C",String.format("%d",CORING_INDEX_C));
}
if (MakerNote.length>=16) {
long [] iTemps={
(MakerNote[14]>> 0) & 0xffff,
(MakerNote[14]>>16) & 0xffff,
(MakerNote[15]>> 0) & 0xffff,
(MakerNote[15]>>16) & 0xffff};
for (i=0;i<iTemps.length;i++) if (iTemps[i]!=0xffff){
double temperature=(iTemps[i]&0xfff)/16.0;
if (i==0) info+="SYSTEM TEMPERATURE\t" + temperature+"\t\t\t\n";
else info+="SFE "+i+" TEMPERATURE\t" + temperature+"\t\t\t\n";
imp.setProperty("TEMPERATURE_"+i,""+temperature);
}
}
if (COMPOSITE) {
info+="SUB_FRAMES\t- 1 -\t- 2 -\t- 3 -\t\n";
info+="HEIGHTS\t"+HEIGHT1+"\t"+HEIGHT2+"\t"+HEIGHT3+"\t\n";
info+="BLANK_ROWS\t" +BLANK1+"\t"+BLANK2+"\t\t\n";
info+="FLIPH\t"+FLIPH1+"\t"+FLIPH2+"\t"+FLIPH3+"\t\n";
info+="FLIPV\t"+FLIPV1+"\t"+FLIPV2+"\t"+FLIPV3+"\t\n";
imp.setProperty("HEIGHT1",String.format("%d",HEIGHT1));
imp.setProperty("HEIGHT2",String.format("%d",HEIGHT2));
imp.setProperty("HEIGHT3",String.format("%d",HEIGHT3));
imp.setProperty("BLANK_ROWS1",String.format("%d",BLANK1));
imp.setProperty("BLANK_ROWS2",String.format("%d",BLANK2));
imp.setProperty("FLIPH1",FLIPH1?"1":"0");
imp.setProperty("FLIPH2",FLIPH2?"1":"0");
imp.setProperty("FLIPH3",FLIPH3?"1":"0");
imp.setProperty("FLIPV1",FLIPV1?"1":"0");
imp.setProperty("FLIPV2",FLIPV2?"1":"0");
imp.setProperty("FLIPV3",FLIPV3?"1":"0");
}
if (!IS_SILENT) new TextWindow(imp.getTitle()+" info", "Parameter\tRed\tGreen(R)\tGreen(B)\tBlue",info, 400, COMPOSITE?(600):((MakerNote.length>=12)?600:160));
//tw.setLocation(0,0);
// If there are FLIPH, FLIPV - swap gains, gammas, blacks accordingly. later the images will be also flipped
if (FLIPV!=0) {
swapArrayElements (gains, 1, 3);
swapArrayElements (gains, 0, 2);
swapArrayElements (blacks, 1, 3);
swapArrayElements (blacks, 0, 2);
swapArrayElements (gammas, 1, 3);
swapArrayElements (gammas, 0, 2);
swapArrayElements (gamma_scales,1, 3);
swapArrayElements (gamma_scales,0, 2);
}
if (FLIPH!=0) {
swapArrayElements (gains, 1, 0);
swapArrayElements (gains, 3, 2);
swapArrayElements (blacks, 1, 0);
swapArrayElements (blacks, 3, 2);
swapArrayElements (gammas, 1, 0);
swapArrayElements (gammas, 3, 2);
swapArrayElements (gamma_scales,1, 0);
swapArrayElements (gamma_scales,3, 2);
}
for (i=0;i<4;i++) rgammas[i]=elphel_gamma_calc (gammas[i], blacks[i], gamma_scales[i]);
} else {
IJ.showMessage("WARNING", "MakerNote not found");
}
/**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]);
// System.out.println("gains["+i+"]="+gains[i]+" min_gain="+min_gain);
}
for (i=0;i<4;i++) gains[i]/=min_gain;
for (i=0;i<4;i++) blacks256[i]=256.0*blacks[i];
// for (i=0;i<4;i++) {
// System.out.println("scaled gains["+i+"]="+gains[i]);
// }
for (i=0;i<4;i++) {
if (MakerNote !=null) {
if (scale) satValue[i]=((rgammas[i][255])-blacks256[i])/gains[i];
else satValue[i]=((rgammas[i][255])-blacks256[i]);
} else satValue[i]=255.0;
imp.setProperty("saturation_"+i,String.format("%f",satValue[i]));
// System.out.println("scaled gains["+i+"]="+gains[i]+" satValue["+i+"]="+satValue[i]);
}
// swap satValue to match FLIPH,FLIPV again
if (FLIPV!=0) {
swapArrayElements (satValue, 1, 3);
swapArrayElements (satValue, 0, 2);
}
if (FLIPH!=0) {
swapArrayElements (satValue, 1, 0);
swapArrayElements (satValue, 3, 2);
}
for (i=0;i<4;i++) {
imp.setProperty("saturation_"+i,String.format("%f",satValue[i]));
//System.out.println("saturation_"+i+"\t"+String.format("%f",satValue[i]));
}
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,nb,xbyr,ybyr;
float [] pixels = (float[])ip.getPixels();
float [][] macroblock=new float[16][16];
float [] pixels1= null;
boolean IS_JP4=(COLOR_MODE==5);
boolean IS_JP46=(COLOR_MODE==2);
if (IS_JP4) pixels1= pixels.clone(); ///JP4 mode
boolean use_imp_src= (imp_src!=null) && (imp_src.getWidth()==imp.getWidth()) && (imp_src.getHeight()==imp.getHeight());
ImageProcessor ip_src= use_imp_src? imp_src.getProcessor():ip;
if (ip_src==null) {
ip_src=ip;
use_imp_src=false;
}
for (yb=0;yb<(height>>4); yb++) for (xb=0;xb<(width>>4); xb++) { /** iterating macroblocks */
if (IS_JP4) {
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]=pixels1[offset+x];
}
}
}
} else if (IS_JP46) {
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];
}
}
} else { /// mono and other non-processed
for (y=0;y<16;y++) {
offset=((yb<<4)+y)*width+(xb<<4);
for (x=0;x<16;x++) {
macroblock[y][x]=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]);
}
}
}
if (ip_src==null) System.out.println("ip_src is null");
// else if (ip_src.setf==null) System.out.println("ip_src.setf is null");
for (y=0;y<16;y++) {
offset=(yb<<4)+y;
for (x=0;x<16;x++) {
ip_src.setf((xb<<4)+ x, offset, macroblock[y][x]); // here null pointer if image was closed
}
}
}
if (FLIPH!=0) ip_src.flipHorizontal(); /** To correct Bayer */
if (FLIPV!=0) ip_src.flipVertical(); /** To correct Bayer */
/** Is it needed here ? */
/** imp.draw();
imp.show(); **/
if (use_imp_src) copyProperties (imp, imp_src);
return use_imp_src;
}
/** 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]<outValue) && (indx<256)) indx++;
if (indx>=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, double [] xtraExif) throws IOException {
byte [] sig= {(byte) 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]=(byte) (len & 0xff);
sig[6]=(byte) ((len>>8) & 0xff);
sig[5]=(byte) ((len>>16) & 0xff);
sig[4]=(byte) ((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);
in.close(); // was no close()! -? "too many open files"
if ((head[this.ExifOffset]!=0x4d) || (head[this.ExifOffset+1]!=0x4d)) {
IJ.showMessage("JP46 Reader", "Exif Header not found in " + directory + fileName);
return null;
}
/** search for MakerNote */
long [] note=getExifData (sig, head, len);
if (xtraExif!=null){
if (xtraExif.length>0){ // get exposure time
byte [] exposureTime={
(byte) 0x82,(byte) 0x9a,0x00,0x05,
0x00,0x00,0x00,0x01};
long [] nomDenom=getExifData (exposureTime, head, 2);
if (nomDenom==null) xtraExif[0]=Double.NaN;
else {
xtraExif[0]=(1.0*nomDenom[0])/nomDenom[1];
}
}
}
return note;
}
long[] readElphelMakerNoteURL(String url, int len, double [] xtraExif) throws IOException {
URL camURL = null;
URLConnection urlConn = null;
byte[] data = new byte[4096];
// System.out.println("loading exif from: " + url);
try {
camURL = new URL(url);
urlConn = camURL.openConnection();
int contentLength = 4096; /** just read the beginning of the file */ //urlConn.getContentLength();
//inStream = new InputStreamReader(urlConn.getInputStream());
int bytesRead = 0;
int offset = 0;
InputStream raw = urlConn.getInputStream();
InputStream in = new BufferedInputStream(raw);
while (offset < contentLength) {
bytesRead = in.read(data, offset, data.length - offset);
if (bytesRead == -1)
break;
offset += bytesRead;
}
in.close();
} catch(MalformedURLException e){
System.out.println("Please check the URL:" + e.toString() );
} catch(IOException e1){
System.out.println("Can't read from the Internet: "+ e1.toString() );
}
byte [] sig= {(byte) 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]=(byte) (len & 0xff);
sig[6]=(byte) ((len>>8) & 0xff);
sig[5]=(byte) ((len>>16) & 0xff);
sig[4]=(byte) ((len>>24) & 0xff);
//in = new RandomAccess(RandomAccessFactory.createRO(camURL), "r");
byte[] head = new byte[4096]; /** just read the beginning of the file */
head = data;
//in.readFully(head);
if ((head[this.ExifOffset] != 0x4d) || (head[this.ExifOffset+1] != 0x4d)) {
IJ.showMessage("JP46 Reader", "Exif Header not found in " + url);
return null;
}
/** search for MakerNote */
// return getExifData (sig, head, len);
long [] note=getExifData (sig, head, len);
if (xtraExif!=null){
if (xtraExif.length>0){ // get exposure time
byte [] exposureTime={
(byte) 0x82,(byte) 0x9a,0x00,0x05,
0x00,0x00,0x00,0x01};
long [] nomDenom=getExifData (exposureTime, head, 2);
if (nomDenom==null) xtraExif[0]=Double.NaN;
else {
xtraExif[0]=(1.0*nomDenom[0])/nomDenom[1];
}
}
}
return note;
}
long [] getExifData (byte [] sig, byte [] head, int len){
/** search for sig array */
int i = this.ExifOffset + 2;
boolean match=false;
for (i = this.ExifOffset + 2; i < (head.length - sig.length); i++ ) {
match=true;
for (int j=0;j<sig.length;j++)if (head[i+j]!=sig[j]){
match=false;
break;
}
if (match) break;
}
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;
}
int offs=this.ExifOffset+(((head[i]<<24) & 0xff000000) |((head[i+1]<<16) & 0xff0000)| ((head[i+2]<<8) & 0xff00) | (head[i+3] & 0xff));
// IJ.showMessage("JP46 Reader Debug", "MakerNote starts at offset "+offs);
if (offs > (head.length-len) ) {
IJ.showMessage("JP46 Reader", "Error: data (i.e.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<len; i++) note[i]=((head[offs+(i<<2)]&0xff) << 24) | ((head[offs+(i<<2)+1]&0xff) << 16) | ((head[offs+(i<<2)+2]&0xff) << 8) | (head[offs+(i<<2)+3]&0xff);
return note;
}
/** Modified from Opener.java */
ImagePlus openJpegOrGif(String dir, String name) {
ImagePlus imp = null;
Image img = Toolkit.getDefaultToolkit().createImage(dir+name);
if (img!=null) {
try {
imp = new ImagePlus(name, img);
} catch (IllegalStateException e) {
return null; // error loading image
}
if (imp.getType()==ImagePlus.COLOR_RGB) {
checkGrayJpegTo32Bits(imp);
}
IJ.showStatus("Converting to 32-bits");
new ImageConverter(imp).convertToGray32();
FileInfo fi = new FileInfo();
fi.fileFormat = FileInfo.GIF_OR_JPG;
fi.fileName = name;
fi.directory = dir;
imp.setFileInfo(fi);
}
return imp;
}
public void setTitle (String title) {
imageTitle=title;
}
public String getTitle () {
return imageTitle;
}
public void setURL (String url) {
camera_url=url;
}
public String getURL () {
return camera_url;
}
ImagePlus openJpegOrGifUsingURL (String cameraurl) {
URL url = null;
ImagePlus imp = null;
Image img = null;
/** Validate URL */
try {
url = new URL(cameraurl);
} catch (MalformedURLException e) {
System.out.println("Bad URL: " + cameraurl);
return null;
}
img = Toolkit.getDefaultToolkit().createImage(url);
if (!this.ABSOLUTELY_SILENT) System.out.println("loading image from: " + url);
// imp = new ImagePlus("test", img);
imp = new ImagePlus(imageTitle, img);
if (imp.getType() == ImagePlus.COLOR_RGB) {
checkGrayJpegTo32Bits(imp);
}
IJ.showStatus("Converting to 32-bits");
new ImageConverter(imp).convertToGray32();
FileInfo fi = new FileInfo();
fi.fileFormat = FileInfo.GIF_OR_JPG;
fi.fileName = "aquired from camera";
fi.directory = cameraurl;
imp.setFileInfo(fi);
return imp;
}
public static void checkGrayJpegTo32Bits(ImagePlus imp) {
ImageProcessor ip = imp.getProcessor();
int width = ip.getWidth();
int height = ip.getHeight();
int[] pixels = (int[])ip.getPixels();
int c,r,g,b,offset;
for (int y=0; y<(height-8); y++) {
offset = y*width;
for (int x=0; x<(width-8); x++) {
c = pixels[offset+x];
r = (c&0xff0000)>>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();
}
/** =====Other methods =================================================================== */
/** ======================================================================== */
public void listImageProperties (ImagePlus imp) {
listImageProperties (imp,false);
}
public void listImageProperties (ImagePlus imp, boolean toConsole) {
StringBuffer sb = new StringBuffer();
Set<Object> jp4_set;
Properties jp4_prop;
Iterator<Object> itr;
String str;
jp4_prop=imp.getProperties();
if (jp4_prop!=null) {
jp4_set=jp4_prop.keySet();
itr=jp4_set.iterator();
while(itr.hasNext()) {
str = (String) itr.next();
sb.append(str+"\t"+jp4_prop.getProperty(str)+"\n");
// System.out.println(str + "=\t" +jp4_prop.getProperty(str));
}
}
if (toConsole){
System.out.println(imp.getTitle()+" properties\n"+sb.toString());
} else {
new TextWindow(imp.getTitle()+" properties", "name\tvalue", sb.toString(),400,800);
}
}
/** ======================================================================== */
public double fracOverExposed(double [] map, // map of overexposed pixels 0.0 - 0K, >0 (==1.0) - overexposed
int mapWidth, // width of the map
int x0, // X of the top left corner of the selection
int y0, // Y of the top left corner of the selection
int width, // selection width
int height){ // selection height
int index,i,j;
int y1=y0+height;
int over=0;
for (i=y0;i<y1;i++) {
index=i*mapWidth+x0;
for (j=0;j<width;j++) if (map[index++]>0.0) over++;
}
return (1.0*over)/width/height;
}
// returns 1.0 if there is overexposed pixel, 0.0 - if OK
/** ======================================================================== */
public double [] overexposedMap (ImagePlus imp) {
return overexposedMap (imp, 0.999);
}
public double [] overexposedMap (ImagePlus imp, double relativeThreshold) {
double [] satValues=new double[4];
boolean noProperties=false;
int i,j,index;
for (i=0;i<4;i++) {
//protect from Double.valueOf(null), move to a function
if (imp.getProperty("saturation_"+i)!=null) satValues[i]= Double.valueOf((String) imp.getProperty("saturation_"+i)).doubleValue();
else {
noProperties=true;
break;
}
}
if (noProperties) return null;
//0 - red, 1,2 - green (use Math.min()), 3 - blue
for (i=0;i<4;i++) satValues[i]*=relativeThreshold;
ImageProcessor ip=imp.getProcessor();
int width=imp.getWidth();
float []pixels=(float[]) ip.getPixels();
double [] overexposed= new double [pixels.length];
for (index=0;index<overexposed.length;index++){
i=(index / width) % 2;
j=1-((index % width) % 2);
overexposed[index]=(pixels[index]>=satValues[i*2+j])?1.0:0.0;
}
return overexposed;
}
public ImagePlus demuxImageOrClone(ImagePlus imp, int numImg) {
ImagePlus imp_new=demuxImage(imp, numImg);
if (imp_new!=null) return imp_new;
return demuxClone(imp);
}
public ImagePlus demuxClone(ImagePlus imp) {
ImageProcessor ip=imp.getProcessor().duplicate();
ImagePlus imp_new=new ImagePlus(imp.getTitle()+"-dup",ip);
Set<Object> jp4_set;
Properties jp4_prop;
Iterator<Object> itr;
String str;
jp4_prop=imp.getProperties();
if (jp4_prop!=null) {
jp4_set=jp4_prop.keySet();
itr=jp4_set.iterator();
while(itr.hasNext()) {
str = (String) itr.next();
imp_new.setProperty(str,jp4_prop.getProperty(str));
}
}
return imp_new;
}
public ImagePlus demuxImage(ImagePlus imp, int numImg) {
int width= imp.getWidth();
// int height=imp.getHeight();
int FLIPGV,FLIPGH;
int [] FLIPV= new int[3];
int [] FLIPH= new int[3];
int [] HEIGHTS=new int[3];
int [] BLANKS= new int[2];
Object timestamp=null;
if (imp.getProperty("FLIPV")!=null) FLIPGV= Integer.valueOf((String) imp.getProperty("FLIPV")).intValue(); else return null;
if (imp.getProperty("FLIPH")!=null) FLIPGH= Integer.valueOf((String) imp.getProperty("FLIPH")).intValue(); else return null;
int i;
for (i=1;i<=3;i++) {
if (imp.getProperty("FLIPV"+i)!=null) FLIPV[i-1]= Integer.valueOf((String) imp.getProperty("FLIPV"+i)).intValue(); else return null;
if (imp.getProperty("FLIPH"+i)!=null) FLIPH[i-1]= Integer.valueOf((String) imp.getProperty("FLIPH"+i)).intValue(); else return null;
if (imp.getProperty("HEIGHT"+i)!=null) HEIGHTS[i-1]= Integer.valueOf((String) imp.getProperty("HEIGHT"+i)).intValue(); else return null;
}
for (i=1;i<=2;i++) {
if (imp.getProperty("BLANK_ROWS"+i)!=null) BLANKS[i-1]= Integer.valueOf((String) imp.getProperty("BLANK_ROWS"+i)).intValue(); else return null;
}
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) {
if (HEIGHTS[2]>0) {
Rectangle r_tmp=r[0];
r[0]=r[2];
r[2]=r_tmp;
} else {
Rectangle r_tmp=r[0];
r[0]=r[1];
r[1]=r_tmp;
}
}
}
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;
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
at ij.process.FloatProcessor.crop(FloatProcessor.java:706)
at JP46_Reader_camera.demuxImage(JP46_Reader_camera.java:1104)
at CalibrationHardwareInterface$CamerasInterface$4.run(CalibrationHardwareInterface.java:1101)
*/
// ImageProcessor ip=imp.getProcessor();
// ip.setRoi(r[numImg]);
ImageProcessor ip_individual=imp.getProcessor().duplicate(); //java.lang.NegativeArraySizeException
ip_individual.setRoi(r[numImg]);
ip_individual=ip_individual.crop();
if (FLIPH[numImg]!=0) ip_individual.flipHorizontal();
if (FLIPV[numImg]!=0) ip_individual.flipVertical();
ImagePlus imp_result=new ImagePlus(imp.getTitle()+"-"+numImg,ip_individual);
//copy all defined properties of the composite image
Set<Object> jp4_set;
Properties jp4_prop;
Iterator<Object> itr;
String str;
jp4_prop=imp.getProperties();
if (jp4_prop!=null) {
jp4_set=jp4_prop.keySet();
itr=jp4_set.iterator();
while(itr.hasNext()) {
str = (String) itr.next();
imp_result.setProperty(str,jp4_prop.getProperty(str));
}
}
// Replaced by copying all properties from the composite image
/*
for (i=0;i<4;i++) {
//protect from Double.valueOf(null), move to a function
if (imp.getProperty("saturation_"+i)!=null) {
imp_result.setProperty("saturation_"+i, imp.getProperty("saturation_"+i));
}
}
if (timestamp!=null)imp_result.setProperty("timestamp", timestamp);
*/
// fill in meta data
return imp_result;
}
public void copyProperties (ImagePlus imp_src,ImagePlus imp_dst){
// copy all the properties to the new image
Set<Object> set;
Properties prop;
Iterator<Object> itr;
String str;
prop=imp_src.getProperties();
if (prop!=null) {
set=prop.keySet();
itr=set.iterator();
while(itr.hasNext()) {
str = (String) itr.next();
imp_dst.setProperty(str,prop.getProperty(str));
}
}
}
public ImagePlus encodeProperiesToInfo(ImagePlus imp){
String info="<?xml version=\"1.0\" encoding=\"UTF-8\"?><properties>";
Set<Object> jp4_set;
Properties jp4_prop;
Iterator<Object> itr;
String str;
jp4_prop=imp.getProperties();
if (jp4_prop!=null) {
jp4_set=jp4_prop.keySet();
itr=jp4_set.iterator();
while(itr.hasNext()) {
str = (String) itr.next();
// if (!str.equals("Info")) info+="<"+str+">\""+jp4_prop.getProperty(str)+"\"</"+str+">";
if (!str.equals("Info")) info+="<"+str+">"+jp4_prop.getProperty(str)+"</"+str+">";
}
}
info+="</properties>\n";
imp.setProperty("Info", info);
return imp;
}
public boolean decodeProperiesFromInfo(ImagePlus imp){
if (imp.getProperty("Info")==null) return false;
String xml= (String) imp.getProperty("Info");
DocumentBuilder db=null;
try {
db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
} catch (ParserConfigurationException e) {
return false;
}
InputSource is = new InputSource();
is.setCharacterStream(new StringReader(xml));
Document doc = null;
try {
doc = db.parse(is);
} catch (SAXException e) {
return false;
} catch (IOException e) {
return false;
}
NodeList allNodes=doc.getDocumentElement().getElementsByTagName("*");
for (int i=0;i<allNodes.getLength();i++) {
String name= allNodes.item(i).getNodeName();
String value=allNodes.item(i).getFirstChild().getNodeValue();
imp.setProperty(name, value);
}
return true;
}
}
This source diff could not be displayed because it is too large. You can view the blob instead.
/**
** -----------------------------------------------------------------------------**
** MTF_Bayer.java
**
** Calculates per-color, MTF for raw Bayer color images
** Based on original SE_MTF plugin
**
** Copyright (C) 2010 Elphel, Inc.
**
** -----------------------------------------------------------------------------**
**
** MTF_Bayer.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/>.
** -----------------------------------------------------------------------------**
**
*/
import ij.*;
import ij.process.*;
import ij.gui.*;
import java.awt.*;
import java.awt.event.*;
import ij.plugin.frame.*;
//import java.util.Random;
//import java.util.Arrays;
import ij.text.*;
//public class MTF_Bayer extends PlugInFrame implements ActionListener, DialogListener {
public class MTF_Bayer extends PlugInFrame implements ActionListener {
/**
*
*/
private static final long serialVersionUID = 2575167919068149071L;
Panel panel1,panel2;
int previousID;
static Frame instance;
Plot plotResult;
private static int DEBUG_LEVEL = 1;
private static int MASTER_DEBUG_LEVEL = 1;
private static int sSize=32; /** change name to something meaningful */
private static boolean IS_MONOCHROMATIC=true; /** in monochromatic mode chromatic aberrations are eliminated, edge position is common for all Bayer components */
private static int EMODE=2; /** trying different modes to determine the edge position on individual lines. 2 seems to be the best so far */
private static boolean TRUNCATE_ROWS=true; /** truncate number of processed rows to equlaize number of phase rotations */
private static double LSFHamming=0.5; /**Hamming window width for LSF calculation, fraction of the full window. 0.0 - no window */
private static boolean USE_COMPLEX=true;
private static boolean SHOW_edgeinfo=false;
private static boolean [] SHOW_ESF={false,true,true,false};
private static boolean [] SHOW_LSF={false,true,true,false};
private static boolean [] SHOW_MTF={true,true,false,false,false,false,false};
private static boolean [] SHOW_GGRB={false,true,true,true,true,true,true};
private static boolean SHOW_CROSSTALK=false;
private static boolean ALLOW_FULL_IMAGE=false;
private static double GB2GR_CORR=1.0; /** difference in Green sensitivity Gb/Gr*/
private int maxJump=8;
private ImagePlus imp_src;
private boolean horizontalEdges=false;
String title_src;
int selecWidth,selecHeight;
private double [][] ESFArray;
private int [][] posEdges;
private double[][][] color_coeff_abc;
private double[][][] binDataBoth;
private double[][][] binDataDiffBoth;
private double[][] MTFVectorsBoth = new double [8][]; /** Amplitudes */
private double[][] MTFPVectorsBoth = new double [8][]; /**Phases */
private double[][] MTFCVectorsBoth = new double [16][]; /**Complex */
private Complex[][] MTFComplex= new Complex [8][];
private double[][] MTFCRatios = new double [16][]; /**Complex, each MTF divided by maximal (separately for each direction) */
private double[][] MTFCRatiosA = new double [8][]; /** Amplitude */
private double[][] MTFCRatiosRe= new double [8][]; /** Real part */
private double[][] MTFCRatiosAMasked= new double [8][]; /** Amplitude, plots for main color zeroed */
private double[][] MTFCRatiosReMasked= new double [8][]; /** Real part, Masked */
private double[][] MTFCRatiosP = new double [8][]; /** Phase */
private Complex[][] MTFRatioComplex= new Complex [8][];
private Complex[][] MTF_GG_RB= new Complex [2][];
private double [][] MTF_GG_RB_REIM = new double [4][];
private int MTFMaximalChannel; /** Color number that has highest DC value - averaged for both directions */
private double[] MTF_DC = new double [8]; /** DC coefficinets of MTF - calculated separately from ESF to avoid window function skew */
private int [][][] bayer_loc={{{0,0}, {0,1},{1,0},{1,1}},
{{0,1}, {1,1},{0,0},{1,0}}};
Color [] bayer_colors= { Color.green, Color.red, Color.blue, Color.cyan, Color.black };
Color [] bayerColorsTwice= { Color.green, Color.green, Color.red, Color.red,
Color.blue, Color.blue, Color.cyan, Color.cyan, Color.black, Color.black };
Color [] redGreen_colors= { Color.red, Color.green, Color.black };
Color [] redGreenColorsTwice= { Color.red, Color.green, Color.red, Color.green, Color.black };
Color [] redBlue_colors= { Color.red, Color.blue, Color.black };
Color [] redBlueColorsTwice= { Color.red, Color.blue, Color.red, Color.blue, Color.black };
Color [] two_colors= { Color.black, Color.blue};
String colorCSVHeaders= "\tGreen (R)\tRed\tBlue\tGreen (B)";
String colorCSVHeadersBoth="\tGreen (R) _/\tGreen (R) \\_\tRed _/\tRed \\_\tBlue _/\tBlue \\_\tGreen (B) _/\tGreen (B) \\_";
String colorCSVHeadersBothComplex="\tRe(Gr _/)\tIm(Gr _/)\tRe(Gr \\_)\tIm(Gr \\_)\tRe(R _/)\tIm(R _/)\tRe(R \\_)\tIm(R \\_)\tRe(B _/)\tIm(B _/)\tRe(B \\_)\tIm(B \\_)\tRe(Gb _/)\tIm(Gb _/)\tRe(Gb \\_)\tIm(Gb \\_)";
// no yMax
String CSVHeadersBoth="\t _/\t \\_";
String CSVHeadersBothComplex="\tRe(_/)\tIm(_/)\tRe(\\_)\tIm(\\_)";
public MTF_Bayer() {
super("OTF for Bayer mosaic images");
if (IJ.versionLessThan("1.39t")) return;
if (instance!=null) {
instance.toFront();
return;
}
instance = this;
addKeyListener(IJ.getInstance());
setLayout(new FlowLayout());
panel1 = new Panel();
panel1.setLayout(new GridLayout(4, 1, 50, 5));
addButton("Configure",panel1);
addButton("Process",panel1);
addButton("Calculate Crosstalk",panel1);
addButton("OTF Ratio",panel1);
add(panel1);
panel2 = new Panel();
panel2.setLayout(new GridLayout(4, 3, 5, 5));
addButton("Show ESF",panel2);
addButton("Show ESF Normalized",panel2);
addButton("Show ESF Text",panel2);
addButton("Show LSF",panel2);
addButton("Show LSF Normalized",panel2);
addButton("Show LSF Text",panel2);
addButton("Show MTF",panel2);
addButton("Show MTF Text",panel2);
addButton("Show OTF Text",panel2);
addButton("Show Approximation",panel2);
addButton("Help",panel2);
add(panel2);
pack();
GUI.center(this);
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();
if (label==null) return;
if (label.equals("Configure")) {
showDialog();
return;
}
DEBUG_LEVEL=MASTER_DEBUG_LEVEL; /** Move to particular command if needed */
if (label.equals("Process")) {
if (!openImage()) {
// IJ.showMessage("Error","openImage() failed");
return;
}
DEBUG_LEVEL=MASTER_DEBUG_LEVEL;
double [][] esfV,esfH;
int [][] posEdgesV=null;
int [][] posEdgesH=null;
/** try both vertical and horizontal edges, use one that has more edges (if any) */
esfV = generateSelection(imp_src, false);
if (esfV!=null) posEdgesV=findEdges(esfV, maxJump);
esfH = generateSelection(imp_src, true);
if (esfH!=null) posEdgesH=findEdges(esfH, maxJump);
/** Both directions OK - use the best one */
if ((posEdgesV!=null) && (posEdgesH!=null)) {
if (posEdgesH[0].length>posEdgesV[0].length) {
ESFArray=esfH;
posEdges=posEdgesH;
esfV=null;
posEdgesV=null;
horizontalEdges=true;
} else {
ESFArray=esfV;
posEdges=posEdgesV;
esfH=null;
posEdgesH=null;
horizontalEdges=false;
}
} else if (posEdgesH!=null) {
ESFArray=esfH;
posEdges=posEdgesH;
horizontalEdges=true;
} else if (posEdgesV!=null) {
ESFArray=esfV;
posEdges=posEdgesV;
horizontalEdges=false;
} else {
IJ.showMessage("Error","No edges that run side-to-side found.");
return;
}
if (DEBUG_LEVEL>4) IJ.showMessage("Debug","Using "+(horizontalEdges?"horizontal":"vertical")+" edges, numer of edges="+posEdges[0].length);
//Restore selecWidth,selecHeight
selecWidth=ESFArray[0].length;
selecHeight=ESFArray.length;
color_coeff_abc=approximateEdges(posEdges);
if (SHOW_edgeinfo) showEdgesCoeff(color_coeff_abc, (ESFArray.length)>>1);
binESF();
if (SHOW_ESF[0]) showESF(SHOW_ESF[1],SHOW_ESF[2],SHOW_ESF[3], false,false) ;
binDataDiffBoth=diffColorVector4Both(binDataBoth, LSFHamming);
if (SHOW_LSF[0]) showLSF(SHOW_LSF[1],SHOW_LSF[2],SHOW_LSF[3], false,false) ;
makeMTF();
if (SHOW_MTF[0]) showMTF(SHOW_MTF[1],SHOW_MTF[2],SHOW_MTF[3],SHOW_MTF[4],SHOW_MTF[5],SHOW_MTF[6]);
if (SHOW_CROSSTALK) crosstalk();
return;
} else if (label.equals("Show ESF")) showESF(true, false, false, false, false) ;
else if (label.equals("Show ESF Normalized")) showESF(false, true, false, false, false) ;
else if (label.equals("Show ESF Text")) showESF(false, false, true, false, false) ;
else if (label.equals("Show LSF")) showLSF(true, false, false, false, false) ;
else if (label.equals("Show LSF Normalized")) showLSF(false, true, false, false, false) ;
else if (label.equals("Show LSF Text")) showLSF(false, false, true, false, false) ;
else if (label.equals("Show MTF")) showMTF(true, SHOW_MTF[2], SHOW_MTF[3], SHOW_MTF[4],false, false) ;
else if (label.equals("Show MTF Text")) showMTF(false, false, false, false, true, false) ;
else if (label.equals("Show OTF Text")) showMTF(false, false, false, false, false, true) ;
else if (label.equals("Show Approximation")) {
if ((color_coeff_abc==null) || (ESFArray==null)) {
IJ.showMessage("Error","No data available, please run \"Process\" first");
return;
}
showEdgesCoeff(color_coeff_abc, (ESFArray.length)>>1);
} else if (label.equals("Calculate Crosstalk")) {
crosstalk();
} else if (label.equals("OTF Ratio")) {
showMTFRatios(true,true,true,true);
} else if (label.equals("Help")) {
showOTFHelp();
}
}
public void crosstalk() {
String [] Bayer={"green","red","blue","green"};
int i,qf;
qf= sSize/4;
Complex [] quaterFreq =new Complex[8];
double[] av0= new double [8];
double[] av4= new double [8];
double[] aav0= new double [4];
Complex quaterFreqMC; /** average of two directions of main component */
if (MTFVectorsBoth[0]==null) {
IJ.showMessage("Error","No data available, please run \"Process\" first");
return;
}
for (i=0;i<8;i++) {
quaterFreq[i]= new Complex(MTFCVectorsBoth[i<<1][qf],MTFCVectorsBoth[(i<<1)+1][qf]);
av0[i]=MTFCVectorsBoth[i<<1][0]; //always real
av4[i]=quaterFreq[i].abs();
}
quaterFreqMC=new Complex(quaterFreq[MTFMaximalChannel<<1]);
quaterFreqMC=quaterFreqMC.plus(quaterFreq[(MTFMaximalChannel<<1)+1]);
quaterFreqMC=quaterFreqMC.scale(1.0/(av0[MTFMaximalChannel<<1]+av0[(MTFMaximalChannel<<1)+1]));
for (i=0;i<4;i++) aav0[i]=0.5*(av0[i<<1]+av0[(i<<1)+1]);
Complex GBmGR1 = quaterFreq[6].minus(quaterFreq[0]);
Complex GBmGR2 = quaterFreq[7].minus(quaterFreq[1]);
Complex RmB1 = quaterFreq[2].minus(quaterFreq[4]);
Complex RmB2 = quaterFreq[3].minus(quaterFreq[5]);
if ((RmB1.abs()==0.0) || (RmB2.abs()==0.0)) {
IJ.showMessage("Error","Divide by 0");
return;
}
Complex Kv1 = GBmGR1.divideBy(RmB1);
Complex Kv2 = GBmGR2.divideBy(RmB2);
Kv1=Kv1.scale(horizontalEdges?(-0.5):0.5);
Kv2=Kv2.scale(horizontalEdges?(-0.5):0.5);
Complex Kv=Kv1.plus(Kv2);
Kv=Kv.scale(0.5);
double KHV, Khv, Khv1, Khv2; /**Kh-Kv, considering Kd==0, Kv-Kh= (GR(0)-GB(0))/(R(0)-B(0))/2/(K0-4*Kd) */
/** next direction is always absolute, does not depend on horizontalEdges */
Khv1=0.5*(av0[0]-av0[6])/(av0[2]-av0[4]);
Khv2=0.5*(av0[1]-av0[7])/(av0[3]-av0[5]);
Khv=0.5*(Khv1+Khv2);
KHV=0.5*((MTF_DC[0]+MTF_DC[1])-(MTF_DC[6]+MTF_DC[7]))/((MTF_DC[2]+MTF_DC[3])-(MTF_DC[4]+MTF_DC[5]));
//(GB(0)-GR(0))/(R(0)-B(0))=2*(Kv-Kh)/(K0-4*kd)
//so KV/K0 = (Gb(1/4)-Gr(1/4))/(R(1/4)-B(1/4))/2
// private double[][] MTFCVectorsBoth = new double [16][]; /**Complex */
int precision=3;
new TextWindow(title_src+"_"+"pixel crosstalk", "Parameter \tValue",
((DEBUG_LEVEL>0)?("File\t"+title_src+"\n"):"")+
"Edge mode\t"+(IS_MONOCHROMATIC?"monochrome":"color")+"\n"+
((GB2GR_CORR!=1.0)?("Gb/Gr corr.\t"+IJ.d2s(GB2GR_CORR,precision+2)+"\n"):"")+
((DEBUG_LEVEL>2)?("Red[0]\t"+ IJ.d2s(aav0[1],precision)+"\n"):"")+
((DEBUG_LEVEL>2)?("GreenR[0]\t"+ IJ.d2s(aav0[0],precision)+"\n"):"")+
((DEBUG_LEVEL>2)?("GreenB[0]\t"+ IJ.d2s(aav0[3],precision)+"\n"):"")+
((DEBUG_LEVEL>2)?("Blue[0]\t"+ IJ.d2s(aav0[2],precision)+"\n"):"")+
((DEBUG_LEVEL>1)?("MTF_DC_GR _/\t"+ IJ.d2s(MTF_DC[0],precision)+"\n"):"")+
((DEBUG_LEVEL>1)?("MTF_DC_GR \\_\t"+ IJ.d2s(MTF_DC[1],precision)+"\n"):"")+
((DEBUG_LEVEL>1)?("MTF_DC_R _/\t"+ IJ.d2s(MTF_DC[2],precision)+"\n"):"")+
((DEBUG_LEVEL>1)?("MTF_DC_R \\_\t"+ IJ.d2s(MTF_DC[3],precision)+"\n"):"")+
((DEBUG_LEVEL>1)?("MTF_DC_B _/\t"+ IJ.d2s(MTF_DC[4],precision)+"\n"):"")+
((DEBUG_LEVEL>1)?("MTF_DC_B \\_\t"+ IJ.d2s(MTF_DC[5],precision)+"\n"):"")+
((DEBUG_LEVEL>1)?("MTF_DC_GB _/\t"+ IJ.d2s(MTF_DC[6],precision)+"\n"):"")+
((DEBUG_LEVEL>1)?("MTF_DC_GB \\_\t"+ IJ.d2s(MTF_DC[7],precision)+"\n"):"")+
((DEBUG_LEVEL>1)?("R["+qf+"] _/\t"+ stringComplexComplex(quaterFreq[2],precision)+"\n"):"")+
((DEBUG_LEVEL>1)?("R["+qf+"] \\_\t"+ stringComplexComplex(quaterFreq[3],precision)+"\n"):"")+
((DEBUG_LEVEL>1)?("GR["+qf+"] _/\t"+stringComplexComplex(quaterFreq[0],precision)+"\n"):"")+
((DEBUG_LEVEL>1)?("GR["+qf+"] \\_\t"+stringComplexComplex(quaterFreq[1],precision)+"\n"):"")+
((DEBUG_LEVEL>1)?("GB["+qf+"] _/\t"+stringComplexComplex(quaterFreq[6],precision)+"\n"):"")+
((DEBUG_LEVEL>1)?("GB["+qf+"] \\_\t"+stringComplexComplex(quaterFreq[7],precision)+"\n"):"")+
((DEBUG_LEVEL>1)?("B["+qf+"] _/\t"+ stringComplexComplex(quaterFreq[4],precision)+"\n"):"")+
((DEBUG_LEVEL>1)?("B["+qf+"] \\_\t"+ stringComplexComplex(quaterFreq[5],precision)+"\n"):"")+
((DEBUG_LEVEL>1)?("GB-GR _/\t"+ stringComplexComplex(GBmGR1,precision)+"\n"):"")+
((DEBUG_LEVEL>1)?("GB-GR \\_\t"+ stringComplexComplex(GBmGR2,precision)+"\n"):"")+
((DEBUG_LEVEL>1)?("R-B _/\t"+ stringComplexComplex(RmB1,precision)+"\n"):"")+
((DEBUG_LEVEL>1)?("R-B \\_\t"+ stringComplexComplex(RmB2,precision)+"\n"):"")+
((DEBUG_LEVEL>1)?("Kv _/\t"+ stringComplexComplex(Kv1,precision)+"\n"):"")+
((DEBUG_LEVEL>1)?("Kv \\_\t"+ stringComplexComplex(Kv2,precision)+"\n"):"")+
((DEBUG_LEVEL>1)?("Kv average\t"+ stringComplexComplex(Kv,precision)+"\n"):"")+
((DEBUG_LEVEL>1)?("dG["+qf+"]/G[0] _/\t"+IJ.d2s(GBmGR1.abs()/aav0[0],precision)+"\n"):"")+ /** quality factor */
((DEBUG_LEVEL>1)?("dG["+qf+"]/G[0] \\_\t"+IJ.d2s(GBmGR2.abs()/aav0[0],precision)+"\n"):"")+ /** quality factor */
((DEBUG_LEVEL>2)?("Kh-Kv _/1\t"+ IJ.d2s(Khv1,precision+1)+"\n"):"")+
((DEBUG_LEVEL>2)?("Kh-Kv \\_\t"+ IJ.d2s(Khv1,precision+1)+"\n"):"")+
"Filter\t"+ (((aav0[1] > aav0[0]) && (aav0[1] > aav0[2]) && (aav0[1]>aav0[3]))?"Red":(((aav0[2] > aav0[0]) && (aav0[2] > aav0[1]) && (aav0[2]>aav0[3]))? "Blue":"Green/None"))+"\n"+
((DEBUG_LEVEL>0)?(Bayer[MTFMaximalChannel]+"[1/4].abs\t"+IJ.d2s(quaterFreqMC.abs(),precision) +"\n"):"")+
((DEBUG_LEVEL>0)?(Bayer[MTFMaximalChannel]+"[1/4].phase\t"+IJ.d2s(quaterFreqMC.phase(),precision)+"\n"):"")+
// (horizontalEdges?"Horizontal (E+W)/2":"Vertical (S+N)/2")+" crosstalk\t"+(USE_COMPLEX? stringComplexComplex(Kv,precision+1): IJ.d2s(KV,precision+1))+"\n"+
(horizontalEdges?"Horizontal (E+W)/2":"Vertical (N+S)/2")+" crosstalk\t"+IJ.d2s(Kv.Re(),precision+1)+"\n"+
((DEBUG_LEVEL>2)?("Diff. H-V old\t"+IJ.d2s(Khv,precision+1)+"\n"):"")+
"Diff. H-V\t" +IJ.d2s(KHV,precision+1)+"\n"+
// (horizontalEdges?"Vertical (S+N)/2":"Horizontal (E+W)/2")+" crosstalk\t" + (USE_COMPLEX? stringComplexComplex(horizontalEdges?Kv.minus(new Complex(KHV,0)):Kv.plus(new Complex(KHV,0)) ,precision+1):IJ.d2s(KV +(horizontalEdges?-1:1)*KHV,precision+1))+"\n",
(horizontalEdges?"Vertical (N+S)/2":"Horizontal (E+W)/2")+" crosstalk\t" + IJ.d2s(Kv.Re() +(horizontalEdges?-1:1)*KHV,precision+1)+"\n"+
(horizontalEdges?"Vertical (N-S)/2":"Horizontal (E-W)/2")+" asymmetry\t" + IJ.d2s(Kv.Im() ,precision+1)+"\n",
500, (DEBUG_LEVEL>2)?925:((DEBUG_LEVEL>1)?825:325));
if (SHOW_GGRB[0]) {
showESF(false, false, false, SHOW_GGRB[1], SHOW_GGRB[2]) ;
showLSF(false, false, false, SHOW_GGRB[3], SHOW_GGRB[4]) ;
showMTFGGRB(SHOW_GGRB[5], SHOW_GGRB[6]);
}
}
//
public String stringComplexDouble(double re, double im, int precision) {
return IJ.d2s(re,precision)+((im>=0)?"+":"")+IJ.d2s(im,precision)+"i";
}
public String stringComplexComplex(Complex c, int precision) {
return stringComplexDouble(c.Re(), c.Im(), precision);
}
public void makeMTF() {
int n,i, n1;
int N=sSize*4;
int M=(N>>3)+1; /** Only quarter (because of bins) of the first half is needed */
Complex[] ArrayComplex = new Complex[N];
Complex[] VectorFFTC = new Complex[N];
boolean masked=false;
for (n=0;n<8;n++) {
MTFVectorsBoth[n]= new double[M];
MTFPVectorsBoth[n]= new double[M];
MTFCVectorsBoth[2*n]= new double[M];
MTFCVectorsBoth[2*n+1]=new double[M];
MTFComplex[n]=new Complex[M];
for (i=0;i<N;i++) {
ArrayComplex[(i+(N>>1))%N] = new Complex(binDataDiffBoth[n & 1][n>>1][i], 0);
}
/** Calculate DC coefficients of MTF using ESF (not LSF multiplied by window function. Precise DC coefficients are needed to determine crosstalk
assymmentry Horizontal-Vertical (may be caused by limited bandwidth of an ammplifier/ADC).
Use difference between average ESF in the first and the last quaters */
MTF_DC[n]=0.0;
for (i=0;i<sSize; i++) {
MTF_DC[n]+=binDataBoth[n & 1][n>>1][N-i-1];
MTF_DC[n]-=binDataBoth[n & 1][n>>1][i];
}
MTF_DC[n]/=sSize;
if ((n & 1) !=0) MTF_DC[n]=-MTF_DC[n];
VectorFFTC = fft(ArrayComplex);
for (i = 0; i < M; i++) {
MTFVectorsBoth[n][i]=VectorFFTC[i].abs();
MTFPVectorsBoth[n][i]=VectorFFTC[i].phase();
MTFCVectorsBoth[2*n][i]= VectorFFTC[i].Re();
MTFCVectorsBoth[2*n+1][i]=VectorFFTC[i].Im();
MTFComplex[n][i]=new Complex(VectorFFTC[i]);
}
}
for (n=0;n<2;n++) {
MTF_GG_RB[n]=new Complex[M];
MTF_GG_RB_REIM[(n<<1)]= new double[M];
MTF_GG_RB_REIM[(n<<1)+1]=new double[M];
for (i = 0; i < M; i++) {
MTF_GG_RB[n][i]=MTFComplex[6+n][i].minus(MTFComplex[0+n][i]).divideBy(MTFComplex[2+n][i].minus(MTFComplex[4+n][i]));
MTF_GG_RB_REIM[n<<1][i]=(horizontalEdges?(-1.0):1.0)*MTF_GG_RB[n][i].Re();
MTF_GG_RB_REIM[(n<<1)+1][i]=MTF_GG_RB[n][i].Im();
}
}
// private Complex[][] MTF_GG_RB= new Complex [2][];
// private double [][] MTF_GG_RB_REIM = new double [4][];
//divideBy
/** calculate results of division of each channel MTF by the channel that has the maximal total energy.
Based on assumption that:
1 - image was acquired with monochromatic light (no chromatic aberrations), and
2 - one channel has much higher signal, so it can be considered as "actual" light input
the other (lower) channels are considered to be a sum of the same signal as the main channel (just attenuated by mosaic filter) and
the crosstalk signal from the main channel pixels.
*/
double max=0.0;
MTFMaximalChannel=0;
for (n=0;n<4;n++) if ((MTF_DC[2*n]+MTF_DC[2*n+1])>max) {
max=MTF_DC[2*n]+MTF_DC[2*n+1];
MTFMaximalChannel=n;
}
for (n=0;n<8;n++) {
switch (MTFMaximalChannel) {
case 0: /** Green red line */
case 3: /** Green blue line */
masked= ((n >> 1) == MTFMaximalChannel) || ((n >> 1) == (MTFMaximalChannel^3));
break;
default: masked= ((n >> 1) == MTFMaximalChannel);
}
n1=(n & 1) + (MTFMaximalChannel << 1);
MTFCRatios[n<<1]= new double[M];
MTFCRatios[(n<<1)+1]= new double[M];
MTFCRatiosA[n] = new double [M]; /** Amplitude */
MTFCRatiosAMasked[n] = new double [M]; /** Amplitude, masked */
MTFCRatiosRe[n]= new double [M]; /** Real part */
MTFCRatiosReMasked[n]= new double [M]; /** Real part, Masked */
MTFCRatiosP[n] = new double [M]; /** Phase */
MTFRatioComplex[n]=new Complex[M];
for (i=0;i<M;i++) {
// d= (MTFCVectorsBoth[n1][i]*MTFCVectorsBoth[n1][i])+(MTFCVectorsBoth[n1+1][i]*MTFCVectorsBoth[n1+1][i]);
if (MTFComplex[n1][i].abs()>0) MTFRatioComplex[n][i]= MTFComplex[n][i].divideBy(MTFComplex[n1][i]);
else MTFRatioComplex[n][i]= new Complex(0.0,0.0);
MTFCRatios[n<<1][i]= MTFRatioComplex[n][i].Re();
MTFCRatios[(n<<1)+1][i]= MTFRatioComplex[n][i].Im();
MTFCRatiosA[n][i] = MTFRatioComplex[n][i].abs();
MTFCRatiosP[n][i] = MTFRatioComplex[n][i].phase();
MTFCRatiosAMasked[n][i] = masked?0.0:MTFCRatiosA[n][i];
MTFCRatiosRe[n][i]= MTFRatioComplex[n][i].Re();
MTFCRatiosReMasked[n][i]= masked?0.0:MTFCRatiosRe[n][i];
}
}
}
public void showMTFRatios(boolean plot_amplitude, boolean plot_phase, boolean text, boolean complex) {
if (MTFVectorsBoth[0]==null) {
IJ.showMessage("Error","No data available, please run \"Process\" first");
return;
}
if (plot_amplitude) generatePlots(MTFCRatiosReMasked,"RE","OTF_Ratio_Re", bayerColorsTwice,false);
if (plot_phase) generatePlots(MTFCRatiosP,"PHASE","OTF_Ratio_phase",bayerColorsTwice,false);
if (text) generatePlots(MTFCRatiosA, "MTF","OTF_Ratio",bayerColorsTwice,true,"Frequency"+colorCSVHeadersBoth,0,0);
if (complex) {
if (USE_COMPLEX) generatePlots(MTFCRatios, "MTF","OTF_Ratio_complex",bayerColorsTwice,true,"Frequency"+colorCSVHeadersBoth,0,0);
else generatePlots(MTFCRatios, "MTF","OTF_Ratio_complex",bayerColorsTwice,true,"Frequency"+colorCSVHeadersBothComplex,0,0);
}
}
public void showMTFGGRB(boolean plot_reim, boolean text) {
if (MTFVectorsBoth[0]==null) {
IJ.showMessage("Error","No data available, please run \"Process\" first");
return;
}
if (plot_reim) generatePlots(MTF_GG_RB_REIM,"REIM", "Crosstalk_Re_Im", redBlueColorsTwice,false);
if (text ) {
if (USE_COMPLEX) generatePlots(MTF_GG_RB_REIM, "MTF","Crosstalk_complex",redBlueColorsTwice,true,"Frequency"+CSVHeadersBoth,0,0);
else generatePlots(MTF_GG_RB_REIM, "MTF","Crosstalk_complex",redBlueColorsTwice,true,"Frequency"+CSVHeadersBothComplex,0,0);
}
}
//MTF_GG_RB_REIM
// private double[][] MTFPVectorsBoth = new double [8][]; /**Phases */
// private double[][] MTFCVectorsBoth = new double [16][]; /**Complex */
public void showMTF(boolean plot_amplitude, boolean plot_phase, boolean plot_reim, boolean plot_reim_scaled, boolean text, boolean complex) {
int n,i;
if (MTFVectorsBoth[0]==null) {
IJ.showMessage("Error","No data available, please run \"Process\" first");
return;
}
if (plot_amplitude) generatePlots(normalizePlots(MTFVectorsBoth,false),"MTF","MTF", bayerColorsTwice,false);
if (plot_phase) generatePlots(MTFPVectorsBoth,"PHASE","OTF_phase",bayerColorsTwice,false);
if (plot_reim || plot_reim_scaled){
double[][] MTF_Re = new double [8][MTFVectorsBoth[0].length];
double[][] MTF_Im = new double [8][MTFVectorsBoth[0].length];
for (n=0;n<8;n++) for (i=0;i<MTFVectorsBoth[0].length;i++) {
MTF_Re[n][i]=MTFCVectorsBoth[2*n][i];
MTF_Im[n][i]=MTFCVectorsBoth[2*n+1][i];
}
if (plot_reim) {
generatePlots(MTF_Re,"RE","re(OTF)", bayerColorsTwice,false);
generatePlots(MTF_Im,"IM","im(OTF)", bayerColorsTwice,false);
}
if (plot_reim_scaled) {
generatePlots(MTF_Re,"RES","re(OTF)_scaled", bayerColorsTwice,false);
generatePlots(MTF_Im,"IMS","im(OTF)_scaled", bayerColorsTwice,false);
}
}
if (text) generatePlots(MTFVectorsBoth, "MTF","MTF",bayerColorsTwice,true,"Frequency"+colorCSVHeadersBoth,0,0);
if (complex) {
if (USE_COMPLEX) generatePlots(MTFCVectorsBoth,"MTF","OTF",bayerColorsTwice,true,"Frequency"+colorCSVHeadersBoth,0,0);
else generatePlots(MTFCVectorsBoth,"MTF","OTF",bayerColorsTwice,true,"Frequency"+colorCSVHeadersBothComplex,0,0);
}
}
public void showESF(boolean actual, boolean normalized, boolean text, boolean actual_diff, boolean normalized_diff) {
int n, bin;
int sSize4=sSize*4;
double[][] ESFBin = new double[8][sSize*4];
double[][] ESFBinRmBGmG = new double[4][sSize*4];
if (binDataBoth==null) {
IJ.showMessage("Error","No data available, please run \"Process\" first");
return;
}
for (n=0;n<4;n++) for (bin=0; bin<sSize4; bin++) {
ESFBin[(n<<1) ][bin]=binDataBoth[0][n][bin];
ESFBin[(n<<1)+1][bin]=binDataBoth[1][n][bin];
}
for (bin=0; bin<sSize4; bin++) {
ESFBinRmBGmG[0][bin]= binDataBoth[0][1][bin]-binDataBoth[0][2][bin];
ESFBinRmBGmG[1][bin]= binDataBoth[0][3][bin]-binDataBoth[0][0][bin];
ESFBinRmBGmG[2][bin]= binDataBoth[1][1][bin]-binDataBoth[1][2][bin];
ESFBinRmBGmG[3][bin]= binDataBoth[1][3][bin]-binDataBoth[1][0][bin];
}
if (actual) generatePlots(ESFBin,"DV4","ESF",bayerColorsTwice,false);
if (normalized) generatePlots(normalizePlots(ESFBin,true),"DV4","ESF_normalized",bayerColorsTwice,false);
if (text) generatePlots(ESFBin,"DV4","ESF",bayerColorsTwice,true,"Pixel"+colorCSVHeadersBoth,0,0);
if (actual_diff) generatePlots(ESFBinRmBGmG,"DV4","ESF_diff",redGreenColorsTwice,false);
if (normalized_diff) generatePlots(normalizePlots(ESFBinRmBGmG,true),"DV4","ESF_normalized_diff",redGreenColorsTwice,false);
}
public void showLSF(boolean actual, boolean normalized, boolean text, boolean actual_diff, boolean normalized_diff) {
int n, bin,edgeSign;
int sSize4=sSize*4;
double[][] LSFBoth = new double[8][sSize4];
double[][] LSFRmBGmG = new double[4][sSize*4];
if (binDataDiffBoth==null) {
IJ.showMessage("Error","No data available, please run \"Process\" first");
return;
}
for (edgeSign=0; edgeSign<2; edgeSign++) for (n=0;n<4;n++) for (bin=0; bin<sSize*4; bin++) {
LSFBoth[(n<<1)+edgeSign][bin]=binDataDiffBoth[edgeSign][n][bin];
}
for (bin=0; bin<sSize4; bin++) {
LSFRmBGmG[0][bin]= binDataDiffBoth[0][1][bin]-binDataDiffBoth[0][2][bin];
// LSFRmBGmG[1][bin]= binDataDiffBoth[1][1][bin]-binDataDiffBoth[1][2][bin];
// LSFRmBGmG[2][bin]= binDataDiffBoth[0][3][bin]-binDataDiffBoth[0][0][bin];
LSFRmBGmG[1][bin]= binDataDiffBoth[0][3][bin]-binDataDiffBoth[0][0][bin];
LSFRmBGmG[2][bin]= binDataDiffBoth[1][1][bin]-binDataDiffBoth[1][2][bin];
LSFRmBGmG[3][bin]= binDataDiffBoth[1][3][bin]-binDataDiffBoth[1][0][bin];
}
if (actual) generatePlots(LSFBoth,"DV4","LSF",bayerColorsTwice,false);
if (normalized) generatePlots(normalizePlots(LSFBoth,false),"DV4","LSF_normalized",bayerColorsTwice,false);
if (text) generatePlots(LSFBoth,"DV4","LSF",bayerColorsTwice,true,"Pixel"+colorCSVHeadersBoth,0,0);
if (actual_diff) generatePlots(LSFRmBGmG,"DV4","LSF_diff",redGreenColorsTwice,false);
if (normalized_diff) generatePlots(normalizePlots(LSFRmBGmG,false),"DV4","LSF_normalized_diff",redGreenColorsTwice,false);
}
/*
Color [] redGreen_colors= { Color.red, Color.green, Color.black };
Color [] redGreenColorsTwice= { Color.red, Color.green, Color.red, Color.green, Color.black };
*/
public void processWindowEvent(WindowEvent e) {
super.processWindowEvent(e);
if (e.getID()==WindowEvent.WINDOW_CLOSING) {
instance = null;
}
}
public boolean showDialog() {
int i;
// private static boolean [] SHOW_GGRB={false,true,true,true,true,true,true};
String[] ESF_labels={"Show_ESF","Show_ESF_actual?","Show_ESF_normalized?","Show_ESF_as_text?"};
String[] LSF_labels={"Show_LSF","Show_LSF_actual?","Show_LSF_normalized?","Show_LSF_as_text?"};
String[] MTF_labels={"Show_MTF","Show_MTF_plot?","Show_OTF_phase?","Show_OTF_re/im?","Show_OTF_re/im scaled?","Show_MTF_as_text?","Show_OTF_as_text?"};
String[] GGRB_labels={"Show_crosstalk_plots?","GGRB_ESF?","GGRB_ESF_normalized?","GGRB_LSF?","GGRB_LSF_normalized?","F[(Gb-Gr)]/F[R-B]?","F[(Gb-Gr)]/F[R-B]_as_text?"};
String [] Edge_Algorithms={"Centroid","Two ESF crossing points","Four ESF crossing points", "Centroid with double windowing"};
GenericDialog gd = new GenericDialog("Pixel Crosstalk parameters");
gd.addStringField("Image title (updated automatically): ", title_src, 80);
gd.addNumericField("Conversion_strip_width:", sSize, 0);
gd.addCheckbox("Monochrome (color filtered) image? ", IS_MONOCHROMATIC);
// gd.addNumericField("Edge_location_algorithm_number:", EMODE, 0);
gd.addChoice("Edge_location_algorithm", Edge_Algorithms, Edge_Algorithms[EMODE]);
gd.addCheckbox("Truncate rows to equalize phase rotations? ", TRUNCATE_ROWS);
gd.addNumericField("LSF_calculation_window (fraction of full, 0.0 - no window):", LSFHamming, 2);
gd.addCheckbox("Output complex numbers?", USE_COMPLEX);
gd.addCheckbox("Show_edge_approximation_info?", SHOW_edgeinfo);
gd.addCheckboxGroup(1,4,ESF_labels, SHOW_ESF);
gd.addCheckboxGroup(1,4,LSF_labels, SHOW_LSF);
gd.addCheckboxGroup(1,7,MTF_labels, SHOW_MTF);
gd.addCheckboxGroup(1,7,GGRB_labels, SHOW_GGRB);
gd.addCheckbox("Show calculated crosstalk (monochrome mode, red or blue filter required)",SHOW_CROSSTALK);
gd.addCheckbox("Allow_full_image?", ALLOW_FULL_IMAGE);
// private static double bg2gr_corr=1.0; /** difference in Green sensitivity Gb/Gr*/
gd.addNumericField("Gb/Gr sensitivity correction:",GB2GR_CORR, 5);
gd.addNumericField("Debug_Level:", MASTER_DEBUG_LEVEL, 0);
gd.showDialog();
if (gd.wasCanceled()) return false;
title_src = gd.getNextString();
sSize=1;
for (i=(int) gd.getNextNumber(); i >1; i>>=1) sSize <<=1; /** make sSize to be power of 2*/
IS_MONOCHROMATIC=gd.getNextBoolean();
// EMODE=(int) gd.getNextNumber();
EMODE=gd.getNextChoiceIndex();
TRUNCATE_ROWS=gd.getNextBoolean();
LSFHamming= gd.getNextNumber();
USE_COMPLEX=gd.getNextBoolean();
SHOW_edgeinfo=gd.getNextBoolean();
SHOW_ESF[0]=gd.getNextBoolean();
SHOW_ESF[1]=gd.getNextBoolean();
SHOW_ESF[2]=gd.getNextBoolean();
SHOW_ESF[3]=gd.getNextBoolean();
SHOW_LSF[0]=gd.getNextBoolean();
SHOW_LSF[1]=gd.getNextBoolean();
SHOW_LSF[2]=gd.getNextBoolean();
SHOW_LSF[3]=gd.getNextBoolean();
SHOW_MTF[0]=gd.getNextBoolean();
SHOW_MTF[1]=gd.getNextBoolean();
SHOW_MTF[2]=gd.getNextBoolean();
SHOW_MTF[3]=gd.getNextBoolean();
SHOW_MTF[4]=gd.getNextBoolean();
SHOW_MTF[5]=gd.getNextBoolean();
SHOW_MTF[6]=gd.getNextBoolean();
SHOW_GGRB[0]=gd.getNextBoolean();
SHOW_GGRB[1]=gd.getNextBoolean();
SHOW_GGRB[2]=gd.getNextBoolean();
SHOW_GGRB[3]=gd.getNextBoolean();
SHOW_GGRB[4]=gd.getNextBoolean();
SHOW_GGRB[5]=gd.getNextBoolean();
SHOW_GGRB[6]=gd.getNextBoolean();
SHOW_CROSSTALK=gd.getNextBoolean();
ALLOW_FULL_IMAGE=gd.getNextBoolean();
GB2GR_CORR=gd.getNextNumber();
MASTER_DEBUG_LEVEL= (int) gd.getNextNumber();
return true;
}
/*
public boolean dialogItemChanged(GenericDialog gd, AWTEvent e) {
IJ.showMessage("Debug","dialogItemChanged");
return false;
}
*/
private void showEdgesCoeff(double[][][] color_coeff_abc, int halfHeight) {
int nValid=color_coeff_abc.length;
int i,n, edge;
double rmsMax=0.0;
StringBuffer sb = new StringBuffer();
double [][][] data_total=new double[2][4][6]; /** calculate totals for each edge, each Bayer component */
for (edge=0;edge<2;edge++) for (n=0;n<4;n++) for (i=0;i<6;i++) data_total[edge][n][i]=0.0;
for (i=0;i<nValid;i++) for (n=0;n<4;n++) {
edge=posEdges[halfHeight][i]; /** 0 - black-> white, 1 white->black*/
data_total[edge][n][0]+=color_coeff_abc[i][n][0]*color_coeff_abc[i][n][5];
data_total[edge][n][1]+=color_coeff_abc[i][n][1]*color_coeff_abc[i][n][5];
data_total[edge][n][2]+=color_coeff_abc[i][n][2]*color_coeff_abc[i][n][5];
data_total[edge][n][3]+=color_coeff_abc[i][n][3]*color_coeff_abc[i][n][5];
data_total[edge][n][4]+=color_coeff_abc[i][n][4]*color_coeff_abc[i][n][5];
data_total[edge][n][5]+= color_coeff_abc[i][n][5];
if (color_coeff_abc[i][n][3]>rmsMax) rmsMax=color_coeff_abc[i][n][3];
}
for (edge=0;edge<2;edge++) for (n=0;n<4;n++) {
data_total[edge][n][0]/=data_total[edge][n][5];
data_total[edge][n][1]/=data_total[edge][n][5];
data_total[edge][n][2]/=data_total[edge][n][5];
data_total[edge][n][3]/=data_total[edge][n][5];
data_total[edge][n][4]/=data_total[edge][n][5];
}
for (edge=0;edge<2;edge++) {
for (n=0;n<4;n++) {
sb.append("total"+
"\t"+((edge>0)?" \\_":" _/")+
"\t"+n+
"\t"+IJ.d2s((data_total[edge][n][2]*halfHeight*halfHeight),3)+
"\t"+IJ.d2s((data_total[edge][n][1]*halfHeight),3)+
"\t"+IJ.d2s((data_total[edge][n][0]),3)+
"\t"+IJ.d2s((data_total[edge][n][3]),3)+
(IS_MONOCHROMATIC?( "\t"+IJ.d2s((data_total[edge][n][4]),3)):"")+
"\t"+IJ.d2s((data_total[edge][n][5]),0)+
"\n");
}
sb.append("-\t-\t-\t-\t-\t-\t-\t\n");
}
for (i=0;i<nValid;i++) {
for (n=0;n<4;n++) {
sb.append( i+
"\t"+((posEdges[halfHeight][i]>0)?" \\_":" _/")+
"\t"+n+
"\t"+IJ.d2s((color_coeff_abc[i][n][2]*halfHeight*halfHeight),3)+
"\t"+IJ.d2s((color_coeff_abc[i][n][1]*halfHeight),3)+
"\t"+IJ.d2s((color_coeff_abc[i][n][0]),3)+
"\t"+IJ.d2s((color_coeff_abc[i][n][3]),3)+
(IS_MONOCHROMATIC?( "\t"+IJ.d2s((color_coeff_abc[i][n][4]),3)):"")+
"\t"+IJ.d2s((color_coeff_abc[i][n][5]),0)+
"\n");
}
sb.append("\t\t\t\t\t\t\t\n");
}
new TextWindow(title_src+" "+(horizontalEdges?"H-":"V-")+"Edges, RMS<"+IJ.d2s(rmsMax,3), "Edge#\tSlope\tBayer\tA\tB\tC\tRMS"+
(IS_MONOCHROMATIC?"\tSkew":"")+"\tUsed rows", sb.toString(), 600, 600);
}
//ALLOW_FULL_IMAGE
public boolean openImage(){
imp_src = WindowManager.getCurrentImage();
if (imp_src==null){
IJ.showMessage("Error","There are no images open\nProcess canceled");
return false;
}
title_src=imp_src.getTitle();
Roi roi_src= imp_src.getRoi();
if (imp_src.getType() !=ImagePlus.GRAY32 ) {
if ((imp_src.getType() ==ImagePlus.GRAY8 ) ||
(imp_src.getType() ==ImagePlus.GRAY16) ) {
IJ.showStatus("Converting source image to gray 32 bits (float)");
new ImageConverter(imp_src).convertToGray32();
} else {
IJ.showMessage("Error","Image should be Bayer array as a grayscale (8,16 or 32 bits)");
return false;
}
}
if (roi_src==null){
if (ALLOW_FULL_IMAGE) {
imp_src.setRoi(0, 0, imp_src.getWidth(), imp_src.getHeight());
roi_src= imp_src.getRoi();
} else {
IJ.showMessage("Error","No selection (line or rectangle) in the source image.\n"+
"You may allow processing of the full image in \"Configure\"");
return false; /** Full image selected */
}
}
Rectangle r=roi_src.getBounds();
/** align ROI to Bayer */
r.width &= ~1;
r.height &= ~1;
r.x &= ~1;
r.y &= ~1;
selecWidth=r.width;
selecHeight=r.height;
imp_src.setRoi(r);
return true;
}
double [][] generateSelection(ImagePlus imp, boolean horizontal) {
int i,i1,j,base;
Roi roi_src= imp.getRoi();
Rectangle r=roi_src.getBounds();
ImageProcessor ip=imp.getProcessor();
float [] pixels =(float[]) ip.getPixels();
selecWidth=horizontal?r.height:r.width;
selecHeight=horizontal?r.width:r.height;
int w=ip.getWidth();
if (selecWidth<sSize) return null; //sample size is bigger than selection width
/** selection already aligned to color mosaic */
double [][] esf=new double[selecHeight][selecWidth];
if (DEBUG_LEVEL>4) IJ.showMessage("Debug","selecWidth="+selecWidth+"\tselecHeight="+selecHeight);
if (horizontal) { /** (nearly) horizontal edges */
for (i=0; i< selecWidth; i++) {
base=((r.y+i) * w+r.x);
i1=selecWidth-i-1;
for (j=0;j< selecHeight; j++) {
if (((i&1)==0) && ((j&1)==0)) esf[j][i1]= GB2GR_CORR* pixels[base+j]; /** Gr*=GB2GR_CORR */
else esf[j][i1]= pixels[base+j];
}
//GB2GR_CORR
}
} else { /** (nearly) vertical edges */
for (i=0; i< selecHeight; i++) {
base=((r.y+i) * w+r.x);
for (j=0;j< selecWidth; j++) {
if (((i&1)==0) && ((j&1)==0)) esf[i][j]= GB2GR_CORR* pixels[base+j]; /** Gr*=GB2GR_CORR */
else esf[i][j]= pixels[base+j];
}
}
}
return esf;
}
/**
Calculates 2-d integer array of vertical (in esf) edges that run through all height (esf[][] first index)
maxJump - maximal difference between edge crossing subsequent lines.
Returns array of edge positions, null on failure
*/
int [][] findEdges(double[][] esf, int maxJump) {
int halfHeight=selecHeight/2;
int k,i,j,k2,k21, sign,pos;
int [][] intEdges=new int [halfHeight][selecWidth]; /** used later !!!*/
int [] initialSign=new int[halfHeight];
int [] numEdges=new int[halfHeight];
double [] smoothLine=new double [selecWidth]; // 0,1,selecWidth-1 - undefined
double mx,mn, thresh;
int halfSize=sSize/2;
// int quaterSize=sSize/4;
for (k=0;k<halfHeight;k++) for (i=0;i<selecWidth;i++) intEdges[k][i]=0;
for (k=0;k<halfHeight;k++) {
initialSign[k]=0;
numEdges[k]=0;
k2=2*k;
k21=k*2+1;
mx=0.0;
mn=0.0;
for (i=2; i<(selecWidth-1); i++ ) {
smoothLine[i]=esf[k2][i+1]+esf[k21][i+1]+esf[k2][i]+esf[k21][i]-esf[k2][i-2]-esf[k21][i-2]-esf[k2][i-1]-esf[k21][i-1];
if (smoothLine[i] > mx) mx=smoothLine[i];
if (smoothLine[i] < mn) mn=smoothLine[i];
}
/** mx and mn are supposed to be of opposite sign here, set thereshold 1/4 of the minimal of the absolute values of mx and mn */
if ((mn*mx)>=0.0) return null; /** no edges found */
thresh=mx/4;
if (thresh > -(mn/4)) thresh = -(mn/4);
sign=0; // 0 - unknown yet, 1 - last was max, -1 - last was min
mx=0.0;
pos=0;
for (i=2;i<(selecWidth-1);i++) {
if (smoothLine[i] > thresh) {
if (sign>0) {
if (smoothLine[i] > mx) {
mx=smoothLine[i];
pos=i;
}
} else {
if (sign<0) {
intEdges[k][pos]=-1;
numEdges[k]++;
} else initialSign[k]=1;
sign=1;
mx=smoothLine[i];
pos=i;
}
} else if (smoothLine[i] < -thresh) {
if (sign<0) {
if (smoothLine[i] < mx) {
mx=smoothLine[i];
pos=i;
}
} else {
if (sign>0) {
intEdges[k][pos]=1;
numEdges[k]++;
} else initialSign[k]=-1;
sign=-1;
mx=smoothLine[i];
pos=i;
}
}
}
if (sign<0) {
intEdges[k][pos]=-1;
numEdges[k]++;
} else if (sign>0) {
intEdges[k][pos]=1;
numEdges[k]++;
}
}
// IJ.showMessage("calcEdges() debug 1 halfHeight="+halfHeight,"First line has "+numEdges[0]+" edges");
/** for now - considering only edges that run the full height */
boolean [] valid=new boolean [numEdges[0]]; // if the edge runs all the way from top to bottom
// int [][] posEdges=new int [halfHeight][numEdges[0]]; // horizontal positions of edges
int [][] posEdges=new int [halfHeight+1][numEdges[0]]; // horizontal positions of edges, [halfHeight][i] - signs of edges (0/1)
for (i=0; i<numEdges[0];i++) valid[i]=true;
j=0;
for (i=0; i<(selecWidth-1);i++) if (intEdges[0][i] !=0) posEdges[0][j++]=i; // set first row
for (k=0; k<halfHeight;k++) {
/** Verify current line does not get out of halfSize from the ends (enough room on both sides of the edge) */
for (i=0; i<numEdges[0]; i++) if (valid[i]) {
if ((posEdges[k][i]<halfSize) || (posEdges[k][i]>= (selecWidth-halfSize))) valid[i] = false;
}
if (k< (halfHeight-1)) {
/** See if the edge is continued to the next line */
for (i=0; i<numEdges[0]; i++) if (valid[i]) {
posEdges[k+1][i]=-1;
for (j=0; j<=maxJump; j=-j+((j<=0)?1:0)) {
if (intEdges[k][posEdges[k][i]]==intEdges[k+1][posEdges[k][i]+j]) {
posEdges[k+1][i]=posEdges[k][i]+j;
break;
}
}
if (posEdges[k+1][i]<0) valid[i]=false;
}
}
}
/** compact posEdges by removing invalid columns, calculate nValid; */
int nValid=0;
for (i=0; i<numEdges[0]; i++) if (valid[i]) {
if (i!=nValid) for (k=0; k< halfHeight; k++) posEdges[k][nValid]=posEdges[k][i];
nValid++;
}
for (i=0;i<nValid; i++ ) posEdges[halfHeight][i]= (intEdges[0][posEdges[0][i]]>0)?0:1; /** 0 - black-> white, 1 white->black*/
if (DEBUG_LEVEL>4) IJ.showMessage("calcEdges() debug 2","First line has "+numEdges[0]+" edges, of the "+nValid+" run all the way");
if (nValid==0) return null;
/** Copy posEdges to a new array that has all columns valid, use this array as return value of this function.
Last row contains signes of the edges (0/1)*/
int [][] posEdges1=new int [halfHeight+1][nValid];
// if (DEBUG_LEVEL>10)IJ.showMessage("Debug-22","posEdges1.length="+((posEdges1==null)?"null":posEdges1.length)+"\nhalfHeight="+halfHeight );
for (k=0; k<=halfHeight; k++) for (i=0;i<nValid; i++ ) posEdges1[k][i]=posEdges[k][i];
return posEdges1;
}
/**
Find second order polinomial approximation for each edge.
returns polinomial coefficient (a,b,c), rms for each line, for each Bayer component
*/
private double[][][] approximateEdges(int [][] posEdges) {
int nValid=posEdges[0].length;
int halfHeight=posEdges.length-1;
int halfSize=sSize/2;
int quarterSize=sSize/4;
int HammingHalfWidth=(int) (0.5*LSFHamming*halfSize);
double [] LSFColorVector= new double [halfSize+1];
double [][][] posColorEdges=new double [halfHeight][4][nValid]; // horizontal positions of edges
double ax,a, cx,aw,s,s1,s2,y0,y1,y2,y3,yy,pa,pb,pc,pd,x0,dx;
// IJ.showMessage("Debug","approximateEdges: HammingHalfWidth="+HammingHalfWidth);
int i, k, k2, n, n1, pos, byr_h,byr_v, byr_index; //bayer position hor/vert (0/1)
for (k=0; k<halfHeight; k++) {
for (byr_v=0;byr_v<2; byr_v++) {
k2=2*k+byr_v;
for (byr_h=0;byr_h<2; byr_h++) {
byr_index=horizontalEdges?(((byr_h^1)<<1) + byr_v):((byr_v<<1)+byr_h);
for (n=0; n<nValid; n++) {
pos=2*(posEdges[k][n]/2)-halfSize+byr_h;
switch (EMODE) {
case 0: /** centroid -> multiply by window -> recalcualte centroid */
for (i=0; i<halfSize; i++) LSFColorVector[i]=ESFArray[k2][pos+2*i+2]-ESFArray[k2][pos+2*i];
a=0.0;
ax=0.0;
for(i=0;i<halfSize;i++){
a +=LSFColorVector[i];
ax+=LSFColorVector[i]*i;
}
cx=ax/a+0.5; /** center position without window */
if (HammingHalfWidth> 0) {
a=0.0;
ax=0.0;
for(i=0;i<halfSize;i++) if (Math.abs(i-cx)<=HammingHalfWidth){
aw=LSFColorVector[i]*(0.54+0.46*Math.cos(Math.PI*(i-cx)/HammingHalfWidth));
a +=aw;
ax+=aw*i;
}
}
posColorEdges[k][byr_index][n]= 2*(ax/a)+pos+0.5; ///pixel position (in original pixels) of the maximum
break;
case 3: /** centroid -> multiply by window -> recalcualte centroid, reduce window twice - recalculate again */
double HammingQuaterWidth=HammingHalfWidth/1;
for (i=0; i<halfSize; i++) LSFColorVector[i]=ESFArray[k2][pos+2*i+2]-ESFArray[k2][pos+2*i];
a=0.0;
ax=0.0;
for(i=0;i<halfSize;i++){
a +=LSFColorVector[i];
ax+=LSFColorVector[i]*i;
}
cx=ax/a+0.5; /** center position without window */
if (HammingHalfWidth> 0) {
a=0.0;
ax=0.0;
for(i=0;i<halfSize;i++) if (Math.abs(i-cx)<=HammingQuaterWidth){
aw=LSFColorVector[i]*(0.54+0.46*Math.cos(Math.PI*(i-cx)/HammingQuaterWidth));
a +=aw;
ax+=aw*i;
}
cx=ax/a+0.5; /** center position with full Hamming window */
a=0.0;
ax=0.0;
for(i=0;i<halfSize;i++) if (Math.abs(i-cx)<=HammingHalfWidth){
aw=LSFColorVector[i]*(0.54+0.46*Math.cos(Math.PI*(i-cx)/HammingHalfWidth));
a +=aw;
ax+=aw*i;
}
}
posColorEdges[k][byr_index][n]= 2*(ax/a)+pos+0.5; ///pixel position (in original pixels) of the maximum
break;
case 1: /** average leftmost quarter, rightmost quarter, find middle between them, find crossing point with linear interpolation.*/
case 2: /** average leftmost quarter, rightmost quarter, find middle between them, find crossing point with 3-rd polynome interpolation. */
s1=0.0;
s2=0.0;
for (i=0;i<quarterSize;i++) {
s1+=ESFArray[k2][pos+2*i];
s2+=ESFArray[k2][pos+2*i+halfSize];
}
s1/=quarterSize;
s2/=quarterSize;
s=0.5*(s1+s2);
for (i=quarterSize; (i< (sSize-quarterSize) && ((s1-s)*(ESFArray[k2][pos+2*i]-s) >0)); i++) ;
/** edge between i-1 and i*/
y1=ESFArray[k2][pos+2*i-2]-s;
y2=ESFArray[k2][pos+2*i]-s;
if (y2==0.0) {
posColorEdges[k][byr_index][n]= pos+2*i-0.5; /** unlikely exact match */
break;
}
if ((y1)*(y2)>=0) { /** Should not be so, retry wider */
for (i=2; (i< (sSize-2) && ((s1-s)*(ESFArray[k2][pos+2*i]-s) >0)); i++) ;
/** edge between i-1 and i*/
y1=ESFArray[k2][pos+2*i-2]-s;
y2=ESFArray[k2][pos+2*i]-s;
}
posColorEdges[k][byr_index][n]= pos+2*((i-1)+(y1)/(y1-y2))-0.5; /** linear, uase a backup for cubic */
if (EMODE==1) break;
y0=ESFArray[k2][pos+2*i-4]-s;
y3=ESFArray[k2][pos+2*i+2]-s;
/** y= pa*x^3+pb*x^2+pc*x+pd*/
pd= y0;
pa= -(1.0/6)*y0 +0.5*y1 -0.5*y2 +(1.0/6)*y3;
pb= y0 -2.5*y1 +2*y2 -0.5*y3;
pc=-(11.0/6)*y0 +3*y1 -1.5*y2 +(1.0/3)*y3;
/** solve cubic equation and select solution between 1 and 2, if none - use linear */
/** simplified solution - add the point from linear interpolation, linear interpolate before/after */
x0=y1/(y1-y2) +1.0;
yy=pa*x0*x0*x0+pb*x0*x0+pc*x0+pd;
if (y1*yy == 0.0) {
posColorEdges[k][byr_index][n]= pos+2*(i-2+x0)-0.5; /** unlikely exact match */
} else if (y1*yy > 0.0) { /** interpolate between yy and y2 */
posColorEdges[k][byr_index][n]= pos+2*((i-2+x0)+ (2-x0)*yy/(yy-y2))-0.5;
} else { /** interpolate between y1 and yy */
posColorEdges[k][byr_index][n]= pos+2*(i-1+ (x0-1)*y1/(y1-yy))-0.5;
}
break;
}
}
}
}
}
// color_coeff_abc=new double [nValid][4][4];
color_coeff_abc=new double [nValid][4][6]; /** [4] (monochromatic only) - average difference between approximated edge and this color component,
[5] - number of rows used */
double SX4,SYX2,SX3,SX2,SYX,SY,SX,S0;
double x,rms, skew, pmax,err;
for (i=0; i<nValid; i++) for (n=0;n<4;n++) if ((!IS_MONOCHROMATIC) || (n==3)) {
SX4=0.0;
SX3=0.0;
SX2=0.0;
SX=0.0;
SYX2=0.0;
SYX=0.0;
SY=0.0;
S0=0.0;
/** TODO: Make weighted contributions of different components (or even the main one) */
if (IS_MONOCHROMATIC) { /** gets here with (n==3) only, combine all Bayer components, taking care of shifts */
for (n1=0;n1<4;n1++) {
dx=0.5*bayer_loc[horizontalEdges?1:0][n1][0];
for(k=0;k<halfHeight;k++){
pmax=posColorEdges[k][n1][i];
x=k+dx;
SX4+=x*x*x*x;
SYX2+=pmax*x*x;
SX3+=x*x*x;
SX2+=x*x;
SYX+=pmax*x;
SY+=pmax;
SX+=x;
S0+=1.0;
}
}
} else {
for(k=0;k<halfHeight;k++){
pmax=posColorEdges[k][n][i];
x=k;
SX4+=x*x*x*x;
SYX2+=pmax*x*x;
SX3+=x*x*x;
SX2+=x*x;
SYX+=pmax*x;
SY+=pmax;
SX+=x;
S0+=1.0;
}
}
/**
(1) a*SX4 +b*SX3 + c*SX2 -SYX2 =0
(2) a*SX3 +b*SX2 + c*SX -SYX =0
(3) a*SX2 +b*SX + c*S0 -SY =0
(1a) a*SX4*SX +b*SX3*SX + c*SX2*SX -SYX2*SX =0
(2a) a*SX3*SX2 +b*SX2*SX2 + c*SX*SX2 -SYX*SX2 =0
(1a-2a) a*(SX4*SX-SX3*SX2) + b* (SX3*SX-SX2*SX2) - (SYX2*SX-SYX*SX2)
(1a2a) b= ((SYX2*SX-SYX*SX2) - a* (SX4*SX-SX3*SX2))/(SX3*SX-SX2*SX2)
(2b) a*SX3*S0 +b*SX2*S0 + c*SX*S0 -SYX*S0 =0
(3b) a*SX2*SX +b*SX*SX + c*S0*SX -SY*SX =0
(2b-3b) a*(SX3*S0-SX2*SX) +b*(SX2*S0-SX*SX) -(SYX*S0-SY*SX) =0
(2b3b1a2a) a*(SX3*S0-SX2*SX) +((SYX2*SX-SYX*SX2) - a* (SX4*SX-SX3*SX2))/(SX3*SX-SX2*SX2) * (SX2*S0-SX*SX) -(SYX*S0-SY*SX) =0
a*(SX3*S0-SX2*SX) +(SYX2*SX-SYX*SX2)/(SX3*SX-SX2*SX2) * (SX2*S0-SX*SX) - a* (SX4*SX-SX3*SX2)/(SX3*SX-SX2*SX2) * (SX2*S0-SX*SX) - (SYX*S0-SY*SX) =0
a*((SX3*S0-SX2*SX) - (SX4*SX-SX3*SX2)/(SX3*SX-SX2*SX2) * (SX2*S0-SX*SX)) + (SYX2*SX-SYX*SX2)/(SX3*SX-SX2*SX2) * (SX2*S0-SX*SX) - (SYX*S0-SY*SX) =0
a=((SYX*S0-SY*SX)-(SYX2*SX-SYX*SX2)/(SX3*SX-SX2*SX2) * (SX2*S0-SX*SX) )/((SX3*S0-SX2*SX) - (SX4*SX-SX3*SX2)/(SX3*SX-SX2*SX2) * (SX2*S0-SX*SX))
*/
color_coeff_abc[i][n][2]=((SYX*S0-SY*SX)-(SYX2*SX-SYX*SX2)/(SX3*SX-SX2*SX2) * (SX2*S0-SX*SX) )/((SX3*S0-SX2*SX) - (SX4*SX-SX3*SX2)/(SX3*SX-SX2*SX2) * (SX2*S0-SX*SX));
/**(1a2a) b= (SYX2*SX-SYX*SX2) - a* (SX4*SX-SX3*SX2)/(SX3*SX-SX2*SX2) */
color_coeff_abc[i][n][1]=((SYX2*SX-SYX*SX2) - color_coeff_abc[i][n][2]* (SX4*SX-SX3*SX2))/(SX3*SX-SX2*SX2);
/**(3) a*SX2 +b*SX + c*S0 -SY =0
c =(SY - a*SX2 - b*SX)/S0 */
color_coeff_abc[i][n][0]=(SY - color_coeff_abc[i][n][2]*SX2 - color_coeff_abc[i][n][1]*SX)/S0;
if (IS_MONOCHROMATIC) { /** gets here with (n==3) only, combine all Bayer components, taking care of shifts */
for (n1=0;n1<3;n1++) for (k=0;k<3;k++) color_coeff_abc[i][n1][k]=color_coeff_abc[i][3][k]; /** copy coefficients to all other components */
for (n1=0;n1<4;n1++) {
dx=0.5*bayer_loc[horizontalEdges?1:0][n1][0];
rms=0;
skew=0.0;
for(k=0;k<halfHeight;k++){
pmax=posColorEdges[k][n1][i];
x=k+dx;
err=pmax-color_coeff_abc[i][n1][2]*x*x-color_coeff_abc[i][n1][1]*x-color_coeff_abc[i][n1][0];
skew+=err;
rms+=err*err;
}
color_coeff_abc[i][n1][3]=Math.sqrt(rms/selecHeight);
color_coeff_abc[i][n1][4]=skew/selecHeight;
}
} else {
rms=0;
skew=0.0;
for(k=0;k<halfHeight;k++){
x=k;
pmax=posColorEdges[k][n][i];
err=pmax-color_coeff_abc[i][n][2]*x*x-color_coeff_abc[i][n][1]*x-color_coeff_abc[i][n][0];
skew+=err;
rms+=err*err;
}
color_coeff_abc[i][n][3]=Math.sqrt(rms/selecHeight);
color_coeff_abc[i][n][4]=skew/selecHeight; /** should be 0 */
}
}
return color_coeff_abc;
}
private void binESF() {
int nValid=color_coeff_abc.length;
binDataBoth = new double[2][4][sSize*4+1]; /** global double[][][] */
int [][][] binNumberBoth = new int [2][4][sSize*4+1];
/**
Scale data so all the ESF curves would have the same amplitude before adding to bins - that will decrease fluctuations caused by binNumber modulation
*/
double[][] binMinBoth = new double[2][4];
double[][] binAmpBoth = new double[2][4];
int[][] binScaleBothNumber = new int[2][4];
double FirstQuaterTotal,LastQuaterTotal,ThisESFMin,ThisESFAmp;
int FirstQuaterNumber, LastQuaterNumber;
int i,n,k,edgeSign,edgeNumber, binShift, hor, hor_min, hor_max, bin;
double dk;
int sSize4=sSize*4;
int halfHeight=selecHeight/2;
int []rb={1,3,0,2};
int nb;
double ph0,ph1,phLim;
int thisHeight;
int slantDir;
for (edgeSign=0;edgeSign<2;edgeSign++) for (n=0;n<4;n++){
binMinBoth[edgeSign][n]=0.0;
binAmpBoth[edgeSign][n]=0.0;
binScaleBothNumber[edgeSign][n]=0;
for (i=0;i<=sSize4;i++) {
binDataBoth[edgeSign][n][i]=0.0;
binNumberBoth[edgeSign][n][i]=0;
}
}
for (edgeNumber=0; edgeNumber<nValid; edgeNumber++) {
for (n=0;n<4;n++) {
thisHeight=halfHeight; /** Will backup to this if there are too few edges */
edgeSign=posEdges[halfHeight][edgeNumber]; /** 0 - black-> white, 1 white->black*/
/** Equalize number of phases, taking into account that we are binning each Bayer component separately,
so number of pixel crossings should be (approximately) multiple of 2
*/
dk=thisHeight-1;
ph0=color_coeff_abc[edgeNumber][n][0];
ph1=color_coeff_abc[edgeNumber][n][2]*dk*dk + color_coeff_abc[edgeNumber][n][1]*dk + color_coeff_abc[edgeNumber][n][0];
if (TRUNCATE_ROWS && (Math.abs(ph1-ph0)>2.0)) {/** reduce thisHeight */
slantDir=(ph1>ph0)?1:-1;
phLim=ph0+slantDir*2.0*Math.floor(0.5*Math.abs(ph1-ph0));
while (slantDir*(ph1-phLim) >0) {
thisHeight--;
dk=thisHeight-1;
ph1=color_coeff_abc[edgeNumber][n][2]*dk*dk + color_coeff_abc[edgeNumber][n][1]*dk + color_coeff_abc[edgeNumber][n][0];
}
}
color_coeff_abc[edgeNumber][n][5]=thisHeight; /** for statistics */
for(k=0;k<thisHeight;k++){
nb=horizontalEdges?rb[n]:n;
dk=k+0.5*(nb>>1);
binShift= (int) (4*(color_coeff_abc[edgeNumber][n][2]*dk*dk +
color_coeff_abc[edgeNumber][n][1]*dk +
color_coeff_abc[edgeNumber][n][0]+0.5)-2*sSize); /** where is the real center? */
// color_coeff_abc[edgeNumber][n][0]) +4.0 -2*sSize);
hor_min=(binShift)/4;
hor_max=binShift/4+sSize+4;
if (hor_min < 0) hor_min=0;
if (hor_max > selecWidth) hor_max=selecWidth;
if (hor_min > hor_max) hor_min=hor_max; /// or just fail??
// if (((hor_min ^ n) & 1) !=0) hor_min++;
if (((hor_min ^ nb) & 1) !=0) hor_min++;
FirstQuaterNumber= 0;
LastQuaterNumber= 0;
FirstQuaterTotal=0.0;
LastQuaterTotal= 0.0;
ThisESFMin=0.0;
ThisESFAmp=0.0;
for (hor=hor_min;hor<=hor_max;hor+=2) {
bin=hor*4-binShift;
if ((bin>=0) && (bin<=sSize)) {
FirstQuaterTotal+=ESFArray[2*k+(nb>>1)][hor];
FirstQuaterNumber++;
}
if ((bin>=(sSize4-sSize)) && (bin<=sSize4)) {
LastQuaterTotal+=ESFArray[2*k+(nb>>1)][hor];
LastQuaterNumber++;
}
}
if (edgeSign>0) {
ThisESFMin=LastQuaterTotal/LastQuaterNumber;
ThisESFAmp=FirstQuaterTotal/FirstQuaterNumber-ThisESFMin;
} else {
ThisESFMin=FirstQuaterTotal/FirstQuaterNumber;
ThisESFAmp=LastQuaterTotal/LastQuaterNumber-ThisESFMin;
}
binMinBoth[edgeSign][n]+=ThisESFMin; /** out of bounds here*/
binAmpBoth[edgeSign][n]+=ThisESFAmp;
binScaleBothNumber[edgeSign][n]++;
for (hor=hor_min;hor<=hor_max;hor+=2) {
bin=hor*4-binShift;
if ((bin>=0) && (bin<=sSize4)) {
// binDataBoth[edgeSign][n][bin] +=ESFArray[2*k+(n>>1)][hor];
binDataBoth[edgeSign][n][bin] +=(ESFArray[2*k+(nb>>1)][hor]-ThisESFMin)/ThisESFAmp;
binNumberBoth[edgeSign][n][bin]++;
}
}
}
}
}
for (edgeSign=0; edgeSign<2; edgeSign++)
for (n=0;n<4;n++)
for (bin=0; bin<=sSize4; bin++)
if (binNumberBoth[edgeSign][n][bin]>0)
binDataBoth[edgeSign][n][bin]/=binNumberBoth[edgeSign][n][bin];
/**
binDataBoth[edgeSign][n][bin] is now normalized, so average of the first quater is 0.0, last quater 1.0 (or opposite for white-to-black edges)
Next is restoration of the original shifts and scales applied before accumulating bins
*/
for (edgeSign=0;edgeSign<2;edgeSign++) for (n=0;n<4;n++){
binMinBoth[edgeSign][n]/=binScaleBothNumber[edgeSign][n];
binAmpBoth[edgeSign][n]/=binScaleBothNumber[edgeSign][n];
for (bin=0; bin<=sSize4; bin++)
binDataBoth[edgeSign][n][bin] = (binDataBoth[edgeSign][n][bin] * binAmpBoth[edgeSign][n]) + binMinBoth[edgeSign][n];
}
}
public double[][][] diffColorVector4Both(double[][][] dataArray, double window){ /**window = 0.0 - as is, 1.0 full width Hamming, 0.5 - middle half */
double[][][] diffData = new double[2][4][sSize*4];
int i,n;
double a;
for (n=0;n<4;n++) for (i=0;i<sSize*4;i++) diffData[0][n][i]=dataArray[0][n][i+1]-dataArray[0][n][i];
for (n=0;n<4;n++) for (i=0;i<sSize*4;i++) diffData[1][n][i]=dataArray[1][n][i]-dataArray[1][n][i+1];
if (window>0.0) for (n=0;n<4;n++) {
for (i=0;i<sSize*4;i++) {
a=(i-2*sSize)/(2*sSize*window);
if ((a< -1.0) || (a>1.0)) {
diffData[0][n][i]=0.0;
diffData[1][n][i]=0.0;
} else {
a=(0.54+0.46*Math.cos(Math.PI*a));
diffData[0][n][i]*=a;
diffData[1][n][i]*=a;
}
}
}
return diffData;
}
/** ============================== */
private double[][] normalizePlots (double[][] Vectors, boolean both ) {
double [][] result= new double [Vectors.length][Vectors[0].length];
int n,i;
double min,max, scale;
for (n=0;n<Vectors.length; n++) {
min=Vectors[n][0];
max=min;
for (i=0;i<Vectors[n].length;i++) {
if (Vectors[n][i]<min) min=Vectors[n][i];
if (Vectors[n][i]>max) max=Vectors[n][i];
}
if (!both) min=0;
if (max<=min) scale =1.0;
else scale = 1.0/(max-min);
for (i=0;i<Vectors[n].length;i++) result[n][i]=scale*(Vectors[n][i]-min);
}
return result;
}
public void generatePlots(double[][] Vectors, String plotType, String plotName, Color [] colors, String headers) {
generatePlots(Vectors, plotType, plotName, colors, true, headers);
}
public void generatePlots(double[][] Vectors, String plotType, String plotName, Color [] colors, boolean asText) {
generatePlots(Vectors, plotType, plotName, colors, asText, "");
}
public void generatePlots(double[][] Vectors, String plotType, String plotName, Color [] colors, boolean asText, String headers) {
generatePlots(Vectors, plotType, plotName, colors, asText, headers, 0,0);
}
public void generatePlots(double[][] Vectors, String plotType, String plotName, Color [] colors, boolean asText, String headers, int textWidth, int TextHeight) {
double[]xValues;
String ejeX="pixel";
String ejeY="";
String allTitle="";
boolean scaled=false;
double[][] finalVectors;
/// String plotType=plot.substring(0,3);
// ImageProcessor imgProc;
double thisYmin, thisYmax,thisXmin,thisXmax;
int i,n;
if (colors == null) colors=bayer_colors;
//If MTF plot, calculate the scale of cycles per pixel for x-axis values
if (plotType.equals("MTF")){
ejeY="Modulation Factor";
ejeX="cycles / pixel";
xValues=calculateXValues(Vectors[0],plotType);
} else if (plotType.equals("PHASE")){
ejeY="Phase";
ejeX="cycles / pixel";
xValues=calculateXValues(Vectors[0],"MTF");
} else if (plotType.equals("AMPLITUDE")){
ejeY="Amplitude";
ejeX="cycles / pixel";
xValues=calculateXValues(Vectors[0],"MTF");
} else if (plotType.equals("RE")){
ejeY="Re";
ejeX="cycles / pixel";
xValues=calculateXValues(Vectors[0],"MTF");
} else if (plotType.equals("REIM")){
ejeY="Re/Im";
ejeX="cycles / pixel";
xValues=calculateXValues(Vectors[0],"MTF");
} else if (plotType.equals("REIMS")){
ejeY="Re/Im (scaled)";
ejeX="cycles / pixel";
xValues=calculateXValues(Vectors[0],"MTF");
} else if (plotType.equals("RES")){
ejeY="Re (scaled)";
ejeX="cycles / pixel";
xValues=calculateXValues(Vectors[0],"MTF");
scaled=true;
} else if (plotType.equals("IM")){
ejeY="Im";
ejeX="cycles / pixel";
xValues=calculateXValues(Vectors[0],"MTF");
} else if (plotType.equals("IMS")){
ejeY="Im (scaled)";
ejeX="cycles / pixel";
xValues=calculateXValues(Vectors[0],"MTF");
scaled=true;
} else if (plotType.equals("EDG")){
xValues=calculateXValues(Vectors[0],"EDG");
} else {
xValues=calculateXValues(Vectors[0],"DV4"); // divide by 4 - pixels were subdivided into bins
}
//plot titles
if (plotType.equals("ESF")){
ejeY="Grey Value";
}
if (plotType.equals("LSF")){
ejeY="Grey Value / pixel";
}
if (plotType.equals("MTF")){
ejeY="Modulation Factor";
ejeX="cycles / pixel";
}
finalVectors=(scaled)?scaleReIm(Vectors):Vectors;
allTitle=plotName + "_" + title_src;
//plot limits
thisYmin=finalVectors[0][0];
thisYmax=finalVectors[0][0];
thisXmin=xValues[0];
thisXmax=xValues[0];
for (i=0; i<Vectors[0].length; i++) {
if (xValues[i] < thisXmin) thisXmin = xValues[i];
else if (xValues[i] > thisXmax) thisXmax = xValues[i];
for (n=0;n<Vectors.length; n++) {
if (finalVectors[n][i] < thisYmin) thisYmin = finalVectors[n][i];
else if (finalVectors[n][i] > thisYmax) thisYmax = finalVectors[n][i];
}
}
if (asText) {
int headerLength=0;
for (i=headers.indexOf("\t");i>=0;i=headers.indexOf("\t",i+1)) headerLength++;
// IJ.showMessage(headers,"headerLength="+headerLength+" i="+i+" finalVectors.length="+finalVectors.length);
if ((headerLength==(finalVectors.length>>1)) && USE_COMPLEX) createComplexCSV(allTitle, xValues, finalVectors, headers, textWidth, TextHeight);
else createCSV(allTitle, xValues, finalVectors, headers, textWidth, TextHeight);
return;
}
plotResult = new Plot(allTitle, ejeX, ejeY, (double []) null, (double []) null);
if (plotType.equals("ESF")){
plotResult.setLimits(1,finalVectors[0].length,0,thisYmax);
} else if (plotType.equals("LSF")){
plotResult.setLimits(1,finalVectors[0].length,0,thisYmax);
} else if (plotType.equals("MTF")){
plotResult.setLimits(0,0.5,0,1);
} else if (plotType.equals("PHASE")){
plotResult.setLimits(0,0.5,-Math.PI,Math.PI);
} else if (plotType.equals("AMPLITUDE")){
plotResult.setLimits(0,0.5,0,thisYmax);
} else if ((plotType.equals("RE")) || (plotType.equals("IM")) || (plotType.equals("REIM"))){
plotResult.setLimits(0,0.5,thisYmin,thisYmax);
} else {
plotResult.setLimits(thisXmin,thisXmax,thisYmin,thisYmax);
}
for (n=0; n<finalVectors.length; n++) {
plotResult.setColor(colors[n]);
plotResult.addPoints(xValues, finalVectors[n], Plot.LINE);
}
plotResult.draw();
plotResult.show();
}
double [][] scaleReIm (double [][] data) { /** Scale each plot, preserving 0 */
double [][] result = new double [data.length][data[0].length];
double mx;
int i,n;
for (n=0;n<data.length;n++) {
mx=Math.abs(data[n][0]);
for (i=1;i<data[0].length;i++) if (Math.abs(data[n][i]) > mx ) mx=Math.abs(data[n][i]);
if (mx>0) mx=1.0/mx;
for (i=0;i<data[0].length;i++) result[n][i]=mx*data[n][i];
}
return result;
}
private void createCSV(String title, double [] xValues, double[][] yValues, String headers, int Width, int Height) {
if ((xValues==null) || (xValues.length==0)) return;
if ((yValues==null) || (yValues.length==0)) return;
String hs=headers;
int i,n;
if (hs.equals("")) {
hs=new String();
hs+="X";
for (i=0;i<yValues.length; i++) hs+="\t"+(i+1);
}
StringBuffer sb = new StringBuffer();
for (i=0;i<xValues.length;i++) {
sb.append(IJ.d2s(xValues[i],2));
for (n=0;n<yValues.length;n++) sb.append("\t"+IJ.d2s(yValues[n][i],4));
sb.append("\n");
}
new TextWindow(title+"_(csv)", hs, sb.toString(), (Width>0)?Width:(84*(yValues.length+1)), (Height>0)?Height:600);
}
private void createComplexCSV(String title, double [] xValues, double[][] yValues, String headers, int Width, int Height) {
if ((xValues==null) || (xValues.length==0)) return;
if ((yValues==null) || (yValues.length==0)) return;
String hs=headers;
int i,n;
if (hs.equals("")) {
hs=new String();
hs+="X";
for (i=0;i<(yValues.length>>1); i++) hs+="\t"+(i+1);
}
StringBuffer sb = new StringBuffer();
for (i=0;i<xValues.length;i++) {
sb.append(IJ.d2s(xValues[i],2));
for (n=0;n<yValues.length;n+=2) sb.append("\t"+IJ.d2s(yValues[n][i],4)+((yValues[n+1][i]>=0)?"+":"")+IJ.d2s(yValues[n+1][i],4)+"i");
sb.append("\n");
}
new TextWindow(title+"_(csv)", hs, sb.toString(), (Width>0)?Width:(84*(yValues.length+1)), (Height>0)?Height:600);
}
public double[] calculateXValues(double[] Vector, String plot){
int i;
int N=Vector.length;
double[]xValues = new double[N];
if(plot.substring(0,3).equals("MTF")){
xValues[0]=0;
//Scale of values for x-axis
for(i=1;i<N;i++) xValues[i]=xValues[i-1]+(0.5/(N-1));
} else if(plot.substring(0,3).equals("DV4")){
for(i=0;i<N;i++) xValues[i]=0.25*(i+1); /** why is it from 1, not from 0 ? */
} else {
for(i=0;i<N;i++) xValues[i]=i+1; /** why is it from 1, not from 0 ? */
}
return xValues;
}
/** ============================== */
public class Complex {
private final double re; // the real part
private final double im; // the imaginary part
// create a copy olf a complex
public Complex(Complex c) {
this.re = c.Re();
this.im = c.Im();
}
// create a new object with the given real and imaginary parts
public Complex(double real, double imag) {
this.re = real;
this.im = imag;
}
// return a string representation of the invoking object
public String toString() {return re + " + " + im + "i"; }
// return a new object whose value is (this - b)
public Complex plus(Complex b) {
Complex a = this;
double real = a.re + b.re;
double imag = a.im + b.im;
Complex rslt = new Complex(real, imag);
return rslt;
}
// return a new object whose value is (this - b)
public Complex minus(Complex b) {
Complex a = this;
double real = a.re - b.re;
double imag = a.im - b.im;
Complex rslt = new Complex(real, imag);
return rslt;
}
// return a new object whose value is (this * b)
public Complex times(Complex b) {
Complex a = this;
double real = a.re * b.re - a.im * b.im;
double imag = a.re * b.im + a.im * b.re;
Complex rslt = new Complex(real, imag);
return rslt;
}
public Complex scale(double b) {
Complex a = this;
Complex rslt = new Complex(a.re *b, a.im *b );
return rslt;
}
// return a new object whose value is (this / b)
public Complex divideBy(Complex b) {
Complex a = this;
double div=b.re*b.re+b.im*b.im;
double real = (a.im * b.im + a.re * b.re)/div;
double imag = (a.im * b.re - a.re * b.im)/div;
Complex rslt = new Complex(real, imag);
return rslt;
}
// return |this|
public double abs() { return Math.sqrt(re*re + im*im); }
public double phase() { return Math.atan2(im,re); }
public double Re() {return this.re; }
public double Im() {return this.im; }
}
public Complex[] fft(Complex[] x) {
int N = x.length;
Complex[] y = new Complex[N];
// base case
if (N == 1) {
y[0] = x[0];
return y;
}
// radix 2 Cooley-Tukey FFT
if (N % 2 != 0) throw new RuntimeException("N is not a power of 2");
Complex[] even = new Complex[N/2];
Complex[] odd = new Complex[N/2];
for (int k = 0; k < N/2; k++) even[k] = x[2*k];
for (int k = 0; k < N/2; k++) odd[k] = x[2*k + 1];
Complex[] q = fft(even);
Complex[] r = fft(odd);
for (int k = 0; k < N/2; k++) {
double kth = -2 * k * Math.PI / N;
Complex wk = new Complex(Math.cos(kth), Math.sin(kth));
y[k] = q[k].plus(wk.times(r[k]));
y[k + N/2] = q[k].minus(wk.times(r[k]));
}
return y;
}
public void showOTFHelp() {
new TextWindow(title_src+"_"+"pixel crosstalk", "Item \tDescription",
"LICENSE"+
"\tMTF_Bayer.java is free software: you can redistribute it and/or modify\n"+
"\tit under the terms of the GNU General Public License as published by\n"+
"\tthe Free Software Foundation, either version 3 of the License, or\n"+
"\t(at your option) any later version.\n"+
"\t\n"+
"\tThis program is distributed in the hope that it will be useful,\n"+
"\tbut WITHOUT ANY WARRANTY; without even the implied warranty of\n"+
"\tMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"+
"\tGNU General Public License for more details.\n"+
"\t\n"+
"\tYou should have received a copy of the GNU General Public License\n"+
"\talong with this program. If not, see <http://www.gnu.org/licenses/>.\n"+
"\t\n"+
"\t\n"+
"Program\tThis program is designed to calculate optical transfer function (OTF) using slanted edge (SE) method\n"+
"\tbased on (but not exactly implemented) ISO standard 12233, it reuses parts of the code of SE_MTF plugin\n"+
"\tfor ImageJ (http://rsbweb.nih.gov/ij/plugins/se-mtf/index.html)\n"+
"\t\n"+
"\tThis program is designed to work only with the images acquired from the color sensors with Bayer mosaic\n"+
"\tfilters, the images should be presented as monochrome pixel array with repetitive 2x2 pixel pattern of GR/BG\n"+
"\tThat means that all pixels in even rows, even columns (starting from 0/0) correspond to green sensor pixels\n"+
"\t(referenced in the program as Gr - Green in \"red\" row), pixels in even rows/odd columns correspond to red (R) ones\n"+
"\tpixels in odd rows/even columns - blue (B) and odd rows/odd columns - green (Gb - green in \"blue\" row)."+
"\tImages that have different Bayer patterns can be converted to GR/BG by shifting by one pixel horizontally\n"+
"\tand/or vertically by ImageJ.\n"+
"\t\n"+
"\tAdditionally images are assumed to be linear, and to have the same signal gain in each of the color channels,\n"+
"\tyou may use JP46_reader plugin that can process jp46 format images from Elphel cameras and un-apply gamma and\n"+
"\tchannel gain settings (JPEG quality is recommended to be set to exactly 100%, gamma to be set to 0.5, color\n"+
"\tgains adjusted for approximate white balance (if possible), having both green channel gains set exactly\n"+
"\tto the same value.\n"+
"\t\n"+
"\tThe program is run when you select the desired area in a valid image window with rectangular selection tool of ImageJ\n"+
"\tand press the \"Process\" button. The program detects a number of near-vertical or near horizontal edges that should\n"+
"\trun all the width (or height) of the selection, approximates them with second-degree polynomials and then accumulates\n"+
"\tinto quarter-pixel bins (similarly to ISO 12233), separately for each edge polarity (black-to-white and white-to-black)\n"+
"\tand separately for each Bayer component (two greens are treated individually)\n"+
"\t\n"+
"\tThis processing depends on the settings that can be customized in \"Configure\" menu, you may select which of the\n"+
"\tplots and/or tables (exportable to spreadsheet programs) to be output each time the processing takes place.\n"+
"\t\n"+
"\tAdditionally you may use \"Show ...\" buttons, they display various plots/tables for the data being last processed,\n"+
"\tthey will apply to the same image selection and parameters until you explicitly press \"Process\" again.\n"+
"\t\n"+
"Configure"+
"\tSet parameters that modify image data processing\n"+
"\t\n"+
"Conversion strip"+
"\tFull width of the pixel row across the edges that are being processed. Should be a power of 2.\n"+
"width\t\n"+
"\t\n"+
"Monochrome"+
"\tThis box should be checked for the images acquired with near-monochromatic light, when the chromatic aberrations\n"+
"(color filtered)"+
"\tare negligible and the edge is approximated for all color component pixels together. Without the color filtering \n"+
"image"+
"\tlens lateral chromatic aberrations may require different edge approximation coefficients for each color separately.\n"+
"\t\n"+
"Edge location"+
"\t This selection box switches between different algorithms that are used to detect edge location in each processed\n"+
"algorithm"+
"\trow with sub-pixel accuracy. This selection is also modified by the \"LSF calculation window\" explained below.\n"+
"\t\n"+
"Centroid"+
"\tCalculate centroid of each line LSF obtained as a digital derivative, similarly to ISO 12233 recommendation.\n"+
"\tIf the \"LSF calculation window\" is set to a non-zero value, the centroid calculation takes place in two steps:\n"+
"\tfirst all the data is used, then the LSF data is multiplied by the Hamming window function around the first iteration\n"+
"\tposition, and centroid is re-calculated for the LSF modified by window function. This second step is designed to\n"+
"\treduce the influence of the far from the edge pixels in the edge locating, that also helps to reduce the skew caused\n"+
"\tby non-uniform illumination of the target\n"+
"\t\n"+
"Centroid with"+
"\tSimilarly to \"Centroid\" but with additional iteration step with a window function twice narrower than the selected\n"+
"double windowing"+
"\tone. So first centroid is calculated for all the data, second - multiplied by the Hamming of specified width and\n"+
"\tthe third one uses half-width window.\n"+
"\t\n"+
"Two ESF"+
"\tThe edge threshold value is calculated as the average value of the first and last quarter pixels (away from the edge).\n"+
"crossing points"+
"\tThen the edge location is determined by linear approximation between the two points immediately above and below the\n"+
"\tthreshold. In this method the far points have very little influence on the edge position\n"+
"\t\n"+
"Four ESF"+
"\tSimilar to the \"Two ESF crossing points\", but with the cubic interpolation between the 4 points - two from each\n"+
"crossing points"+
"\tside of the threshold crossing. Cubic equation is solved approximately using two linear iterations.\n"+
"\t\n"+
"Truncate rows"+
"\tWhen this box is checked, the program tries to follow ISO 12233 by truncating the bottom rows from the processing\n"+
"to equalize"+
"\tso the edges cross multiple of pixel columns (in this program for Bayer pixels it is multiple of two-pixel columns)\n"+
"phase rotations"+
"\tso during binning each phase gets fair representation. Actually in this program this has rather small effect because\n"+
"\tof an additional deviation from the ISO algorithm - each row ESF data is normalized (with normalization coefficient\n"+
"\tsaved) so the first and last quarter pixels average value map to 0.0 and 1.0, respectively. That reduces artifacts\n"+
"\tcaused by different number of samples accumulated in each bin when row data is subject to non-uniform illumination.\n"+
"\t\n"+
"LSF calculation"+
"\tWindow function (Hamming) width, applied to the LSF before calculating Discrete Fourier Transform to determine OTF.\n"+
"window"+
"\tWindow width is entered as a fraction of the \"Conversion strip width\", value 0.0 turns windowing off. This wimndow\n"+
"\talso applies to centroid calculations if selected in \"Edge location algorithm\".\n"+
"\t\n"+
"Show ..."+
"\tThe following check boxes determine which of the plots/tables are displayed automatically when you press \"Process\".\n"+
"\tThose plots/tables can be shown later by pressing \"Show ...\" buttons. \"Show OTF phase\" modifies \"Show MTF\"\n"+
"\tbox and button, so each time two plots are shown - MTF (OTF amplitude) and OTF phase.\n"+
"\t\n"+
"Show"+
"\tDisplay edge approximation coefficients, RMS error for each edge, as well as total number of rows used for ESF/LSF/OTF\n"+
"Approximation"+
"\tmeasurements. Worst of each edge RMS error is included in the window title bar.\n"+
"\t\n"+
"Gb/Gr sensitivity"+
"\tdebugging feature, allowing manual input for entering difference in the two green channels analog gain. Rather small\n"+
"correction"+
"\tunequity of these gains can influence crosstalk measurements\n"+
"\t\n"+
"Debug level"+
"\tHigher integer numbers increase amount of text output. Currently the highest value is 3\n"+
"",
1000, 800);
}
}
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
/**
** -----------------------------------------------------------------------------**
** PolarSpectrums.java
**
** Used in "scissors" frequency-domain demosaic
**
**
** Copyright (C) 2010 Elphel, Inc.
**
** -----------------------------------------------------------------------------**
**
** focus_tuning.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/>.
** -----------------------------------------------------------------------------**
**
*/
import java.util.HashSet;
public class PolarSpectrums {
public int radius=0;
public int iRadiusPlus1; // number of radius steps
public int iAngle;
public double aStep;
public double rStep;
public int size;
public int length;
// Make them private later, after debugging
private int [][] polar2CartesianIndices; // for each polar angle/radius (angle*iRadiusPlus1+radius) - 4 interpolation corners (0:0, dx:0, 0:dy, dx:dy), the first (0:0) being the closest to the polar point
private double [][] polar2CartesianFractions; // a pair of dx, dy for interpolations (used with ) polar2CartesianIndices[][]]
// private int [][][] polarAliases; // a,r,number triplet of the "master" polar cell and number of aliases (including this))- the one that returns by the decart-> polar conversion of the polar->decart
private int [][] cartesian2PolarIndices; // each per-pixel array is a list of indices in polar array pointing to this cell (may be empty)
private int [] cartesian2PolarIndex; // Cartesian->polar array index (cell closest to the center). Is it possible that cartesian2PolarIndices does not include this one?
private int [][] polarGreenMap=null ; // each element is a variable length integer array with a list of the alias indices
private int [][] polarRedBlueMap=null ; // each element is a variable length integer array with a list of the alias indices
private int [][] sameCartesian=null ; // each element is a variable length integer array with a list of indices of the other polar cells that belong (point to) the same cartesian cell
private int [] cartAmpList = null; // list of indices of the elements of the cartesian array (symmetrical around the center) so the distance is between ampRMinMax[0] and ampRMinMax[1]
private double [] ampRMinMax ={0.0,0.0};
public PolarSpectrums() { } // so "Compile and Run" will be happy
/** Convert cartesian to polar array, dimensions are set in the class constructor. Uses bi-linear interpolation */
public double [] cartesianToPolar (double [] cartPixels ) {
double [] polPixels=new double[iRadiusPlus1*(iAngle+1)];
int i;
for (i=0;i<polPixels.length;i++) {
// System.out.println("polar2CartesianFractions["+i+"].length="+polar2CartesianFractions[i].length);
// System.out.println("polar2CartesianIndices["+i+"].length="+ polar2CartesianIndices[i].length);
polPixels[i]=(1-polar2CartesianFractions[i][1])*( (1-polar2CartesianFractions[i][0])*cartPixels[polar2CartesianIndices[i][0]] + polar2CartesianFractions[i][0]*cartPixels[polar2CartesianIndices[i][1]])+
polar2CartesianFractions[i][1] *( (1-polar2CartesianFractions[i][0])*cartPixels[polar2CartesianIndices[i][2]] + polar2CartesianFractions[i][0]*cartPixels[polar2CartesianIndices[i][3]]) ;
}
return polPixels;
}
public double [] polarToCartesian (double [] polPixels , int height, double undefined ) { return polarToCartesian (polPixels , height, undefined, height==size); }
public double [] polarToCartesian (double [] polPixels , double undefined) { return polarToCartesian (polPixels ,size, undefined,false); }
public double [] polarToCartesian (double [] polPixels , int height ) { return polarToCartesian (polPixels , height, Double.NaN,height==size); }
public double [] polarToCartesian (double [] polPixels ) { return polarToCartesian (polPixels , size, Double.NaN,false); }
public double [] polarToCartesian (double [] polPixels,
int height, // for partial arrays
double undefined, // use this value in the undefined areas
boolean symmHalf){ // add center-symmetrical top to the bottom(spectrums of real signals)
int length=size*height;
double [] cartPixels=new double[length];
int i,j;
int [] sameCartCell;
double d;
int l=symmHalf?((size+1)*size/2+1) :(size*height);
int l2=(size+1)*size;
for (i=0;i<l;i++) {
sameCartCell=cartesian2PolarIndices[i];
if (sameCartCell==null) {
if (cartesian2PolarIndex[i]>=0) cartPixels[i]=polPixels[cartesian2PolarIndex[i]];
else cartPixels[i]=undefined;
} else {
d=0;
for (j=0;j<sameCartCell.length;j++) d+=polPixels[sameCartCell[j]];
cartPixels[i]=d/sameCartCell.length;
}
if (symmHalf) {
j=l2-i;
if (j<length) cartPixels[j] = cartPixels[i];
}
}
return cartPixels;
}
/** Caculates maximal value of a center-symmetrical array of the amplitudes in a ring. Uses cached table of indices, recalculates if it changed */
public double maxAmpInRing ( double []amps ){ return maxAmpInRing (amps,size*0.118,size*0.236);} // ~=1/3* (Math.sqrt(2)/4), 2/3* (Math.sqrt(2)/4) (center 1/3 ring between center and the closest alias for greens)
public double maxAmpInRing ( double []amps,
double rMin,
double rMax
){
int i,j,x,y;
if ((cartAmpList==null) || (rMin!=ampRMinMax[0]) || (rMax!=ampRMinMax[1])) {
ampRMinMax[0]=rMin;
ampRMinMax[1]=rMax;
double rMin2=rMin*rMin;
double rMax2=rMax*rMax;
double r2;
// pass 1 - count number of elements
int numMembers=0;
for (i=0;i<=size/2;i++) {
y=i-(size/2);
for (j=0;j<size;j++) {
x=j-(size/2);
r2=x*x+y*y;
if ((r2>=rMin2) && (r2<=rMax2)) numMembers++;
}
}
cartAmpList=new int [numMembers];
// pass 2 - count number of elements fill in the array
numMembers=0;
for (i=0;i<=size/2;i++) {
y=i-(size/2);
for (j=0;j<size;j++) {
x=j-(size/2);
r2=x*x+y*y;
if ((r2>=rMin2) && (r2<=rMax2)) cartAmpList[numMembers++]=i*size+j;
}
}
}
if (cartAmpList.length<1) return Double.NaN;
double max=amps[cartAmpList[0]];
for (i=1;i<cartAmpList.length;i++) if (max<amps[cartAmpList[i]]) max=amps[cartAmpList[i]];
return max;
}
/** return polar array width (== radius+1) */
public int getWidth() { return iRadiusPlus1; }
public int getHeight() { return iAngle+1; }
public double [] genPolarGreenMask(double [] polarAmps, // polar array of amplitude values, <0 - stop
int mode){ // result mode - 0: output mask as 0/1, 1 -output proportional, positive - pass, negative - rejected
return genPolarMask(polarAmps,0,mode);
}
public double [] genPolarRedBlueMask(double [] polarAmps, // polar array of amplitude values, <0 - stop
int mode){ // result mode - 0: output mask as 0/1, 1 -output proportional, positive - pass, negative - rejected
return genPolarMask(polarAmps,1,mode);
}
public double [] genPolarMask(double [] polarAmps, // polar array of amplitude values, <0 - stop
int type, // 0 - green, 1 red/blue
int mode){ // result mode - 0: output mask as 0/1, 1 -output proportional, positive - pass, negative - rejected
int [][] polarMap=(type>0)?polarRedBlueMap: polarGreenMap;
int length=iRadiusPlus1*(iAngle+1);
int [] intMap= new int[length];
int i,ia;
for (i=0;i<intMap.length;i++) intMap[i]=0;
int [] rayLength=new int[iAngle+1]; // index (radius)) of the current ray end for this angle
boolean [] rayOpen= new boolean[iAngle+1]; // this ray can grow (not blocked)
for (i=0;i<iAngle;i++) {
rayLength[i]=0;
rayOpen[i]=true;
}
int lastIndex;
int base;
int l;
double max;
int newVal;
int step=0;
int iMax=0; // index of the best ray
int index=0;
boolean good=true;
while (iMax>=0) {
step++;
/** add polar point index */
newVal=good?step:-step;
// index=iMax*iRadiusPlus1+rayLength[iMax]; // rayLength[iMax] should point to a new cell (with intMap[]==0) may ommit - set in the end of the loop and before the loop?
intMap[index]=newVal;
if (sameCartesian[index]!=null) for (i=0;i<sameCartesian[index].length;i++) intMap[sameCartesian[index][i]]=newVal;
/** add aliases of point index (as negative values) */
if ((good) &&(polarMap[index]!=null)) for (i=0;i<polarMap[index].length;i++) intMap[polarMap[index][i]]=-step;
/** update ray lengths and status */
max=-1.0;
iMax=-1;
for (ia=0;ia<=iAngle;ia++) if (rayOpen[ia]) {
base=ia*iRadiusPlus1+1; // second for this angle
l=base+rayLength[ia]; // first after the pointer
lastIndex=base+iRadiusPlus1; // first in the next row
while ((l<lastIndex) && (intMap[l]>0)) l++;
rayLength[ia]=l-base; // last "good" ( >0 and in the same row)
if ((l==lastIndex) || (intMap[l]<0) || (polarAmps[l]<0.0) ) rayOpen[ia]=false;
else {
if (polarAmps[l]>max) {
max=polarAmps[l];
iMax=ia;
}
}
}
if (iMax>=0) {
rayLength[iMax]++;
index=iMax*iRadiusPlus1+rayLength[iMax];
/** See if any of the aliases of the new point hit the positive value, then this point is prohibited (good=false). Otherwise add it with good=true */
good=true;
if (polarMap[index]!=null) for (i=0;i<polarMap[index].length;i++) {
if (intMap[polarMap[index][i]]>0) {
good=false;
break;
}
}
}
/** index is set if (iMax>=0) */
}
double [] result=new double [intMap.length];
if (mode==0) {
for (i=0;i<length;i++) result[i]=(intMap[i]>0)?1.0:0.0;
} else {
for (i=0;i<length;i++) result[i]=(intMap[i]>0)?(step-intMap[i]):-(step+intMap[i]);
}
return result;
}
public PolarSpectrums(
int isize, // size of the square array, centar is at size/2, size/2, only top half+line will be used
// double mask, //1d - array of pixels, maybe size*(size/2+1) or full
double fullAngle, // i.e. Math.PI, 2*Math.PI
int maxRadius, // width of the polar array - should be <= size/2-2
double outerStep, // maximal step in pixels on the maxRadius for 1 angular step (i.e. 0.5)
int symm // angular symmetry - 0- none,1 - pi corresponds to integer, 2 - pi/2 corresponds to integer, n - pi/n corresponds to integer angular step
) {
size= isize;
length=size*size;
if (maxRadius>(size/2-2)) maxRadius=(size/2-2);
radius=maxRadius;
if (symm==0) aStep=fullAngle/Math.ceil(fullAngle*radius/outerStep);
else aStep=Math.PI/symm/Math.ceil(Math.PI*radius/outerStep/symm);
iRadiusPlus1=(int) Math.ceil(radius/outerStep)+1;
rStep=radius/(iRadiusPlus1-1.0);
iAngle=(int) Math.round(fullAngle/aStep);
polar2CartesianIndices= new int [(iAngle+1)*iRadiusPlus1][4]; // [0] - closest one
polar2CartesianFractions=new double [(iAngle+1)*iRadiusPlus1][2];
int ia,ir,y,x,i,j; //,PolarIndex;
double a,r,cos,sin,dy,dx;
// HashSet [] polarList= new HashSet [length];
cartesian2PolarIndex= new int[length];
cartesian2PolarIndices=new int[length][];
@SuppressWarnings("unchecked")
HashSet <Integer> [] polarList= (HashSet <Integer> []) new HashSet[length];
for (i=0;i<length;i++) {
polarList[i]=new HashSet <Integer>(); // 16, 0.75
}
Integer PolarIndex,CartesianIndex;
for (ia=0;ia<=iAngle;ia++) {
a=ia*aStep;
cos=Math.cos(a);
sin=Math.sin(a);
for (ir=0;ir<iRadiusPlus1;ir++) {
PolarIndex=ia*iRadiusPlus1+ir;
r=ir*rStep;
dy=r*sin;
y=(int) Math.round(dy);
dy-=y;
i=size/2-y;
dx=r*cos;
x=(int) Math.round(dx);
dx-=x;
j=x+size/2;
CartesianIndex=i*size+j;
polar2CartesianIndices[PolarIndex][0]=CartesianIndex;
//if (PolarIndex<5) System.out.println(">>>>> x="+x+" y="+y+" dx="+dx+" dy="+dy+" i="+i+" j="+j+" CartesianIndex="+CartesianIndex+" polar2CartesianIndices["+PolarIndex+"][0]="+polar2CartesianIndices[PolarIndex][0]);
polarList[CartesianIndex].add(PolarIndex);
if (dx<0) {
polar2CartesianIndices[PolarIndex][1]=polar2CartesianIndices[PolarIndex][0]-1;
dx=-dx;
} else {
polar2CartesianIndices[PolarIndex][1]=polar2CartesianIndices[PolarIndex][0]+1;
}
if (dy<0) {
polar2CartesianIndices[PolarIndex][2]=polar2CartesianIndices[PolarIndex][0]+size;
polar2CartesianIndices[PolarIndex][3]=polar2CartesianIndices[PolarIndex][1]+size;
dy=-dy;
} else {
polar2CartesianIndices[PolarIndex][2]=polar2CartesianIndices[PolarIndex][0]-size;
polar2CartesianIndices[PolarIndex][3]=polar2CartesianIndices[PolarIndex][1]-size;
}
polar2CartesianFractions[PolarIndex][0]=dx;
polar2CartesianFractions[PolarIndex][1]=dy;
}
}
for (i=0;i<length;i++) {
y=size/2-(i/size);
x=(i % size)- size/2;
r=Math.sqrt(x*x+y*y);
a=Math.atan2(y,x);
if (a<0) a+=2*Math.PI;
ir =(int) Math.round(r/rStep);
ia= (int) Math.round(a/aStep);
if ((ir>=0) && (ir<iRadiusPlus1) && (ia>=0) && (ia<=iAngle)) {
cartesian2PolarIndex[i]=ia*iRadiusPlus1+ir;
if (polarList[i].size()==0) cartesian2PolarIndices[i]=null;
else {
cartesian2PolarIndices[i]=new int[polarList[i].size()];
j=0;
for (Integer val : polarList[i]) cartesian2PolarIndices[i][j++]=val;
}
} else {
cartesian2PolarIndex[i]=-1; // invalid
cartesian2PolarIndices[i]=null;
}
}
initSameCartesian();
polarGreenMap= new int [iRadiusPlus1*(iAngle+1)][];
initAliasMaps(0);
polarRedBlueMap=new int [iRadiusPlus1*(iAngle+1)][];
initAliasMaps(1);
}
public double [] testMapsLengths(int mode) { // 0 - return lengths of "sameCartesian[]", 1 - same for polarGreenMap
int [][] map= (mode==0)?sameCartesian:((mode==1)?polarGreenMap:polarRedBlueMap);
double [] result = new double [map.length];
for (int i=0; i<map.length;i++) {
result[i]=(map[i]==null)?0.0:map[i].length;
}
// System.out.println("testMapsLengths("+mode+").length="+result.length);
return result;
}
public double [] testGreenMap(int ia, int ir) {
double [] result = new double [polarGreenMap.length];
int i;
for ( i=0; i<result.length;i++) result[i]=0.0;
int index=ia*iRadiusPlus1+ir;
if (polarGreenMap[index]!=null){
for (i=0;i<polarGreenMap[index].length;i++) result [polarGreenMap[index][i]]+=1.0;
System.out.println("testGreenMap("+ia+","+ir+"): polarGreenMap["+index+"].length="+polarGreenMap[index].length);
} else {
System.out.println("testGreenMap("+ia+","+ir+"): polarGreenMap["+index+"]=null");
}
result [index]=-1.0;
return result;
}
public double [] testRedBlueMap(int ia, int ir) {
double [] result = new double [polarRedBlueMap.length];
int i;
for ( i=0; i<result.length;i++) result[i]=0.0;
int index=ia*iRadiusPlus1+ir;
if (polarRedBlueMap[index]!=null){
for (i=0;i<polarRedBlueMap[index].length;i++) result [polarRedBlueMap[index][i]]+=1.0;
System.out.println("testRedBlueMap("+ia+","+ir+"): polarRedBlueMap["+index+"].length="+polarRedBlueMap[index].length);
} else {
System.out.println("testRedBlueMap("+ia+","+ir+"): polarRedBlueMap["+index+"]=null");
}
result [index]=-1.0;
return result;
}
/** Create per-polar pixel list of aliases for green Bayer. For each polar point it shows the polar coordinates of the same (and rotated by pi) point of aliases */
/** current implementation - us cartesian (original) pixels as all/nothing, maybe it makes sense to go directly polar-polar, but then it may leave gaps */
public void initAliasMaps (int type) { // 0 - green, 1 - Red/Blue
/* int [][] aliasMapGreen= {{-2,-2},{-2,0},{-2,2},
{-1,-1},{-1,1},
{ 0,-2}, { 0,2},
{ 1,-1},{ 1,1},
{ 2,-2},{ 2,0},{ 2,2}};*/
int [][] aliasMapGreen= {{-2,-2},{-2,0}, // using rollover, so only unique aliases are needed
{-1,-1},{-1,1},
{ 0,-2},
{ 1,-1},{ 1,1}};
/* int [][] aliasMapRedBlue={{-1,-1},{-1,0},{-1,1},
{ 0,-1}, { 0,1},
{ 1,-1},{ 1,0},{ 1,1}};*/
int [][] aliasMapRedBlue={{-2,-2},{-2,-1},{-2,0},{-2,1},
{-1,-2},{-1,-1},{-1,0},{-1,1},
{ 0,-2},{ 0,-1}, { 0,1},
{ 1,-2},{ 1,-1},{ 1,0},{ 1,1}};
int [][] aliasMap=(type>0)?aliasMapRedBlue:aliasMapGreen;
int [][] polarMap=(type>0)?polarRedBlueMap: polarGreenMap;
HashSet <Integer> aliasList=new HashSet <Integer>();
int j,ix,iy, nAlias, dirAlias,ixa,iya, index, polarIndex;
for (polarIndex=0;polarIndex<polarMap.length;polarIndex++) {
iy= size/2- (polar2CartesianIndices[polarIndex][0] / size);
ix= (polar2CartesianIndices[polarIndex][0] % size)-size/2 ;
//if (polarIndex<5) System.out.println("ix="+ix+" iy="+iy+" polar2CartesianIndices["+polarIndex+"][0]="+polar2CartesianIndices[polarIndex][0]);
//if (polarIndex<5) System.out.println("ix="+ix+" iy="+iy+" polar2CartesianIndices["+polarIndex+"][1]="+polar2CartesianIndices[polarIndex][1]);
//if (polarIndex<5) System.out.println("ix="+ix+" iy="+iy+" polar2CartesianIndices["+polarIndex+"][2]="+polar2CartesianIndices[polarIndex][2]);
//if (polarIndex<5) System.out.println("ix="+ix+" iy="+iy+" polar2CartesianIndices["+polarIndex+"][3]="+polar2CartesianIndices[polarIndex][3]);
aliasList.clear();
for (nAlias=0;nAlias<aliasMap.length;nAlias++) for (dirAlias=-1;dirAlias<2;dirAlias+=2) {
ixa=(size+ size/2+ aliasMap[nAlias][0]*size/4+ dirAlias*ix) % size;
iya=(size+ size/2- aliasMap[nAlias][1]*size/4- dirAlias*iy) % size;
index=iya*size + ixa;
if (cartesian2PolarIndices[index]==null) {
//if (polarIndex<5) System.out.println("cartesian2PolarIndices["+index+"]=null");
if (cartesian2PolarIndex[index]>=0) {
aliasList.add (new Integer(cartesian2PolarIndex[index]));
//if (polarIndex<5) System.out.println("cartesian2PolarIndex["+index+"]="+cartesian2PolarIndex[index]+ " (ir="+(cartesian2PolarIndex[index] % iRadiusPlus1)+" ia="+(cartesian2PolarIndex[index] % iRadiusPlus1)+")");
}
} else {
for (j=0;j<cartesian2PolarIndices[index].length;j++) {
aliasList.add (new Integer(cartesian2PolarIndices[index][j]));
//if (polarIndex<5) System.out.println("cartesian2PolarIndices["+index+"]["+j+ "]="+cartesian2PolarIndices[index][j]+ " (ir="+(cartesian2PolarIndices[index][j] % iRadiusPlus1)+" ia="+(cartesian2PolarIndices[index][j] % iRadiusPlus1)+")");
}
}
}
/** convert set to int[] */
if (aliasList.size()==0) polarMap[polarIndex]=null;
else {
polarMap[polarIndex]=new int[aliasList.size()];
j=0;
for (Integer val : aliasList) polarMap[polarIndex][j++]=val;
}
}
}
public void initSameCartesian () {
int i,j, polarIndex, cartesianIndex;
sameCartesian=new int [iRadiusPlus1*(iAngle+1)][];
for (polarIndex=0;polarIndex<sameCartesian.length;polarIndex++) {
cartesianIndex=polar2CartesianIndices[polarIndex][0];
// System.out.println("polarIndex="+polarIndex+" cartesianIndex="+cartesianIndex+ " polar2CartesianIndices["+polarIndex+"][0]"+polar2CartesianIndices[polarIndex][0]);
// System.out.println("polar2CartesianIndices["+polarIndex+"]="+polar2CartesianIndices[polarIndex].length);
if ((cartesian2PolarIndices[cartesianIndex]==null) || (cartesian2PolarIndices[cartesianIndex].length<=1)) sameCartesian[polarIndex]=null;
else {
sameCartesian[polarIndex]=new int [cartesian2PolarIndices[cartesianIndex].length-1];
j=0;
/** copy all elements but this one - out of bounds may mean that it was not included - bug */
for (i=0;i<cartesian2PolarIndices[cartesianIndex].length;i++) if (cartesian2PolarIndices[cartesianIndex][i]!=polarIndex) sameCartesian[polarIndex][j++]=cartesian2PolarIndices[cartesianIndex][i];
}
}
}
}
import Jama.LUDecomposition;
import Jama.Matrix;
public class PolynomialApproximation {
public int debugLevel=1;
// TODO Move other methods here
public PolynomialApproximation(){}
public PolynomialApproximation(int debugLevel){
this.debugLevel=debugLevel;
}
public double [] polynomialApproximation1d(double [][] data, int N){
double [] S=new double [2*N+1];
double [] SF=new double [N+1];
for (int i=0;i<=2*N;i++) S[i]=0.0;
for (int i=0;i<=N;i++) SF[i]=0.0;
for (int i=0;i<data.length;i++){
double wxn=(data[i].length>2)?data[i][2]:1.0;
if (wxn>0.0){ // save time on 0.0 that can be used to mask out some samples
double f=data[i][1];
double x=data[i][0];
for (int j=0;j<=N;j++){
S[j]+=wxn;
SF[j]+=wxn*f;
wxn*=x;
}
for (int j=N+1;j<2*N;j++){
S[j]+=wxn;
wxn*=x;
}
S[2*N]+=wxn;
if (this.debugLevel>1){
System.out.println("polynomialApproximation1d() |"+i+"|: x=|"+data[i][0]+"| f(x)=|"+data[i][1]+"| (w=\t|"+data[i][2]+"|\t)");
}
}
}
double [][] aM=new double [N+1][N+1];
double [][] aB=new double [N+1][1];
for (int i=0;i<=N; i++) {
aB[i][0]=SF[i];
for (int j=0;j<=N;j++) aM[i][j]=S[i+j];
}
Matrix M=new Matrix(aM,N+1,N+1);
Matrix B=new Matrix(aB,N+1,1);
int N1=N;
// TODO: use try/catch with solve
if (this.debugLevel>1){
System.out.println("polynomialApproximation1d(data,"+N+") M:");
M.print(10, 5);
System.out.println("polynomialApproximation1d() B:");
B.print(10, 5);
}
// while (!(new LUDecomposition(M)).isNonsingular() && (N1>0)){
while (!(new LUDecomposition(M)).isNonsingular() && (N1>=0)){ // make N=0 legal ?
aM=new double [N1][N1];
aB=new double [N1][1];
N1--;
for (int i=0;i<=N1; i++) {
aB[i][0]=B.getArray()[i][0];
for (int j=0;j<=N1;j++) aM[i][j]=M.getArray()[i][j];
}
M=new Matrix(aM,N1+1,N1+1);
B=new Matrix(aB,N1+1,1);
if (this.debugLevel>1){
System.out.println("polynomialApproximation1d() Reduced degree: M:");
M.print(10, 5);
System.out.println("polynomialApproximation1d() Reduced degree: B:");
B.print(10, 5);
}
}
double [][] aR=M.solve(B).getArray();
if (this.debugLevel>1){
System.out.println("polynomialApproximation1d() solution=");
M.solve(B).print(10, 12);
}
double []result=new double [N+1];
for (int i=0;i<=N;i++) result[i]=(i<=N1)?aR[i][0]:0.0;
return result;
}
/**
* Linear approximates each of 3 functions of 3 variables and finds where they are all zero
* @param data: for each sample (1-st index):
* 0 - {x,y,z}
* 1 - {f1, f2, f3},
* 2 - {weight}
* @return {x0, y0, z0} where A1*x0+B1*y0+C1*z0+D1=0, A2*x0+B2*y0+C2*z0+D2=0, A3*x0+B3*y0+C3*z0+D3=0
*/
public double [] linear3d(double [][][] data){
/*
* Approximating each of the 3 measured parameters (Far/near, tilt x and tilt y) with linear approximation in the vicinity of the last position
* For each parameter F(x,y,z)=A*x + B*y +C*z + D, using Gaussian weight function with sigma= motorsSigma
*/
double [][] aM3=new double [3][3];
double [][] aB3=new double [3][1];
for (int parNum=0;parNum<aM3.length;parNum++){
double S0=0.0,SX=0.0,SY=0.0,SZ=0.0,SXY=0.0,SXZ=0.0,SYZ=0.0,SX2=0.0,SY2=0.0,SZ2=0.0,SF=0.0,SFX=0.0,SFY=0.0,SFZ=0.0;
for (int nSample=0; nSample< data.length;nSample++){
if (this.debugLevel>3){
System.out.println(
parNum+","+data[nSample][0][0]+","+data[nSample][0][1]+","+data[nSample][0][2]+","+
data[nSample][1][0]+","+data[nSample][1][1]+","+data[nSample][1][2]+","+data[nSample][2][0]);
}
//TODO replace with PolynomialApproximation class
double w=(data[nSample].length>2)?data[nSample][2][0]:1.0;
double [] xyz=data[nSample][0];
S0+=w;
SX+=w*xyz[0];
SY+=w*xyz[1];
SZ+=w*xyz[2];
SXY+=w*xyz[0]*xyz[1];
SXZ+=w*xyz[0]*xyz[2];
SYZ+=w*xyz[1]*xyz[2];
SX2+=w*xyz[0]*xyz[0];
SY2+=w*xyz[1]*xyz[1];
SZ2+=w*xyz[2]*xyz[2];
SF+=w*data[nSample][1][parNum];
SFX+=w*data[nSample][1][parNum]*xyz[0];
SFY+=w*data[nSample][1][parNum]*xyz[1];
SFZ+=w*data[nSample][1][parNum]*xyz[2];
}
double [][] aM={
{SX2,SXY,SXZ,SX},
{SXY,SY2,SYZ,SY},
{SXZ,SYZ,SZ2,SZ},
{SX, SY, SZ, S0}};
double [][] aB={
{SFX},
{SFY},
{SFZ},
{SF}};
Matrix M=new Matrix(aM);
Matrix B=new Matrix(aB);
// Check for singular (near-singular) here
double [] abcd= M.solve(B).getColumnPackedCopy();
if (this.debugLevel>2){
System.out.println(parNum+"M:");
M.print(10, 5);
System.out.println(parNum+"B:");
B.print(10, 5);
System.out.println(parNum+"A:");
M.solve(B).print(10, 7);
}
//believeLast
aM3[parNum][0]= abcd[0];
aM3[parNum][1]= abcd[1];
aM3[parNum][2]= abcd[2];
aB3[parNum][0]=-abcd[3];
if (this.debugLevel>1) System.out.println("** "+parNum+": A="+abcd[0]+" B="+abcd[1]+" C="+abcd[2]+" D="+abcd[3]);
}
Matrix M3=new Matrix(aM3);
Matrix B3=new Matrix(aB3);
double [] result=M3.solve(B3).getColumnPackedCopy();
if (this.debugLevel>2) {
System.out.println("M3:");
M3.print(10, 7);
System.out.println("B3:");
B3.print(10, 7);
System.out.println("A3:");
M3.solve(B3).print(10, 5);
}
return result;
}
public double[] quadraticMax2d (double [][][] data){
return quadraticMax2d (data,1.0E-15);
}
public double[] quadraticMax2d (double [][][] data,double thresholdQuad){
double [][] coeff=quadraticApproximation(data, false);
if (coeff==null) return null;
if (coeff[0].length<6) return null;
double [][] aM={
{2*coeff[0][0], coeff[0][2]}, // | 2A, C |
{ coeff[0][2],2*coeff[0][1]}}; // | C, 2B |
Matrix M=(new Matrix(aM));
double nmQ=normMatix(aM);
if (debugLevel>3) System.out.println("M.det()="+M.det()+" normMatix(aM)="+nmQ+" data.length="+data.length);
if ((nmQ==0.0) || (Math.abs(M.det())/normMatix(aM)<thresholdQuad)) {
if (debugLevel>3) System.out.println("quadraticMax2d() failed: M.det()="+M.det()+" normMatix(aM)="+normMatix(aM));
return null;
}
double [][] aB={
{-coeff[0][3]}, // | - D |
{-coeff[0][4]}}; // | - E |
double [] xy=M.solve(new Matrix(aB)).getColumnPackedCopy();
return xy;
}
/** ======================================================================== */
/**
* Approximate function z(x,y) as a second degree polynomial (or just linear)
* f(x,y)=A*x^2+B*y^2+C*x*y+D*x+E*y+F or f(x,y)=D*x+E*y+F
* data array consists of lines of either 2 or 3 vectors:
* 2-element vector x,y
* variable length vector z (should be the same for all samples)
* optional 1- element vector w (weight of the sample)
*
* returns array of vectors or null
* each vector (one per each z component) is either 6-element- (A,B,C,D,E,F) if quadratic is possible and enabled
* or 3-element - (D,E,F) if linear is possible and quadratic is not possible or disabled
* returns null if not enough data even for the linear approximation
*/
public double [][] quadraticApproximation(
double [][][] data,
boolean forceLinear // use linear approximation
){
return quadraticApproximation(
data,
forceLinear, // use linear approximation
1.0E-10, // threshold ratio of matrix determinant to norm for linear approximation (det too low - fail) 11.0E-10 failed where it shouldn't?
1.0E-15); // threshold ratio of matrix determinant to norm for quadratic approximation (det too low - fail)
/*
1.0E-12, // threshold ratio of matrix determinant to norm for linear approximation (det too low - fail) 11.0E-10 failed where it shouldn't?
1.0E-20); // threshold ratio of matrix determinant to norm for quadratic approximation (det too low - fail)
*/
}
public double [][] quadraticApproximation(
double [][][] data,
boolean forceLinear, // use linear approximation
double thresholdLin, // threshold ratio of matrix determinant to norm for linear approximation (det too low - fail)
double thresholdQuad // threshold ratio of matrix determinant to norm for quadratic approximation (det too low - fail)
){
if (this.debugLevel>3) System.out.println("quadraticApproximation(...), debugLevel="+this.debugLevel+":");
/* ix, iy - the location of the point with maximal value. We'll approximate the vicinity of that maximum using a
* second degree polynomial:
Z(x,y)~=A*x^2+B*y^2+C*x*y+D*x+E*y+F
by minimizing sum of squared differenceS00between the actual (Z(x,uy)) and approximated values.
and then find the maximum on the approximated surface. Here iS00the math:
Z(x,y)~=A*x^2+B*y^2+C*x*y+D*x+E*y+F
minimizing squared error, using W(x,y) aS00weight function
error=Sum(W(x,y)*((A*x^2+B*y^2+C*x*y+D*x+E*y+F)-Z(x,y))^2)
error=Sum(W(x,y)*(A^2*x^4 + 2*A*x^2*(B*y^2+C*x*y+D*x+E*y+F-Z(x,y)) +(...) )
0=derror/dA=Sum(W(x,y)*(2*A*x^4 + 2*x^2*(B*y^2+C*x*y+D*x+E*y+F-Z(x,y)))
0=Sum(W(x,y)*(A*x^4 + x^2*(B*y^2+C*x*y+D*x+E*y+F-Z(x,y)))
S40=Sum(W(x,y)*x^4), etc
(1) 0=A*S40 + B*S22 + C*S31 +D*S30 +E*S21 +F*S20 - SZ20
derror/dB:
error=Sum(W(x,y)*(B^2*y^4 + 2*B*y^2*(A*x^2+C*x*y+D*x+E*y+F-Z(x,y)) +(...) )
0=derror/dB=Sum(W(x,y)*(2*B*y^4 + 2*y^2*(A*x^2+C*x*y+D*x+E*y+F-Z(x,y)))
0=Sum(W(x,y)*(B*y^4 + y^2*(A*x^2+C*x*y+D*x+E*y+F-Z(x,y)))
(2) 0=B*S04 + A*S22 + C*S13 +D*S12 +E*S03 +F*SY2 - SZ02
(2) 0=A*S22 + B*S04 + C*S13 +D*S12 +E*S03 +F*SY2 - SZ02
derror/dC:
error=Sum(W(x,y)*(C^2*x^2*y^2 + 2*C*x*y*(A*x^2+B*y^2+D*x+E*y+F-Z(x,y)) +(...) )
0=derror/dC=Sum(W(x,y)*(2*C*x^2*y^2 + 2*x*y*(A*x^2+B*y^2+D*x+E*y+F-Z(x,y)) )
0=Sum(W(x,y)*(C*x^2*y^2 + x*y*(A*x^2+B*y^2+D*x+E*y+F-Z(x,y)) )
(3) 0= A*S31 + B*S13 + C*S22 + D*S21 + E*S12 + F*S11 - SZ11
derror/dD:
error=Sum(W(x,y)*(D^2*x^2 + 2*D*x*(A*x^2+B*y^2+C*x*y+E*y+F-Z(x,y)) +(...) )
0=derror/dD=Sum(W(x,y)*(2*D*x^2 + 2*x*(A*x^2+B*y^2+C*x*y+E*y+F-Z(x,y)) )
0=Sum(W(x,y)*(D*x^2 + x*(A*x^2+B*y^2+C*x*y+E*y+F-Z(x,y)) )
(4) 0= A*S30 + B*S12 + C*S21 + D*S20 + E*S11 + F*S10 - SZ10
derror/dE:
error=Sum(W(x,y)*(E^2*y^2 + 2*E*y*(A*x^2+B*y^2+C*x*y+D*x+F-Z(x,y)) +(...) )
0=derror/dE=Sum(W(x,y)*(2*E*y^2 + 2*y*(A*x^2+B*y^2+C*x*y+D*x+F-Z(x,y)) )
0=Sum(W(x,y)*(E*y^2 + y*(A*x^2+B*y^2+C*x*y+D*x+F-Z(x,y)) )
(5) 0= A*S21 + B*S03 + C*S12 + D*S11 + E*SY2 + F*SY - SZ01
derror/dF:
error=Sum(W(x,y)*(F^2 + 2*F*(A*x^2+B*y^2+C*x*y+D*x+E*y-Z(x,y)) +(...) )
0=derror/dF=Sum(W(x,y)*(2*F + 2*(A*x^2+B*y^2+C*x*y+D*x+E*y-Z(x,y)) )
0=Sum(W(x,y)*(F + (A*x^2+B*y^2+C*x*y+D*x+E*y-Z(x,y)) )
(6) 0= A*S20 + B*SY2 + C*S11 + D*S10 + E*SY + F*S00 - SZ00
(1) 0= A*S40 + B*S22 + C*S31 + D*S30 + E*S21 + F*S20 - SZ20
(2) 0= A*S22 + B*S04 + C*S13 + D*S12 + E*S03 + F*S02 - SZ02
(3) 0= A*S31 + B*S13 + C*S22 + D*S21 + E*S12 + F*S11 - SZ11
(4) 0= A*S30 + B*S12 + C*S21 + D*S20 + E*S11 + F*S10 - SZ10
(5) 0= A*S21 + B*S03 + C*S12 + D*S11 + E*S02 + F*S01 - SZ01
(6) 0= A*S20 + B*S02 + C*S11 + D*S10 + E*S01 + F*S00 - SZ00
*/
int zDim=data[0][1].length;
double w,z,x,x2,x3,x4,y,y2,y3,y4,wz;
int i,j,n=0;
double S00=0.0,
S10=0.0,S01=0.0,
S20=0.0,S11=0.0,S02=0.0,
S30=0.0,S21=0.0,S12=0.0,S03=0.0,
S40=0.0,S31=0.0,S22=0.0,S13=0.0,S04=0.0;
double [] SZ00=new double [zDim];
double [] SZ01=new double [zDim];
double [] SZ10=new double [zDim];
double [] SZ11=new double [zDim];
double [] SZ02=new double [zDim];
double [] SZ20=new double [zDim];
for (i=0;i<zDim;i++) {
SZ00[i]=0.0;
SZ01[i]=0.0;
SZ10[i]=0.0;
SZ11[i]=0.0;
SZ02[i]=0.0;
SZ20[i]=0.0;
}
for (i=0;i<data.length;i++) {
w=(data[i].length>2)? data[i][2][0]:1.0;
if (w>0) {
n++;
x=data[i][0][0];
y=data[i][0][1];
x2=x*x;
y2=y*y;
S00+=w;
S10+=w*x;
S01+=w*y;
S11+=w*x*y;
S20+=w*x2;
S02+=w*y2;
if (!forceLinear) {
x3=x2*x;
x4=x3*x;
y3=y2*y;
y4=y3*y;
S30+=w*x3;
S21+=w*x2*y;
S12+=w*x*y2;
S03+=w*y3;
S40+=w*x4;
S31+=w*x3*y;
S22+=w*x2*y2;
S13+=w*x*y3;
S04+=w*y4;
}
for (j=0;j<zDim;j++) {
z=data[i][1][j];
wz=w*z;
SZ00[j]+=wz;
SZ10[j]+=wz*x;
SZ01[j]+=wz*y;
if (!forceLinear) {
SZ20[j]+=wz*x2;
SZ11[j]+=wz*x*y;
SZ02[j]+=wz*y2;
}
}
}
}
//need to decide if there is enough data for linear and quadratic
double [][] mAarrayL= {
{S20,S11,S10},
{S11,S02,S01},
{S10,S01,S00}};
Matrix M=new Matrix (mAarrayL);
Matrix Z;
if (this.debugLevel>3) System.out.println(">>> n="+n+" det_lin="+M.det()+" norm_lin="+normMatix(mAarrayL));
double nmL=normMatix(mAarrayL);
if ((nmL==0.0) || (Math.abs(M.det())/nmL<thresholdLin)){
// return average value for each channel
if (S00==0.0) return null; // not even average
double [][] ABCDEF=new double[zDim][3];
for (i=0;i<zDim;i++) {
ABCDEF[i][0]=0.0;
ABCDEF[i][1]=0.0;
ABCDEF[i][2]=SZ00[i]/S00;
}
return ABCDEF;
}
double []zAarrayL=new double [3];
double [][] ABCDEF=new double[zDim][];
// double [] zAarrayL={SZ10,SZ01,SZ00};
for (i=0;i<zDim;i++) {
zAarrayL[0]=SZ10[i];
zAarrayL[1]=SZ01[i];
zAarrayL[2]=SZ00[i];
Z=new Matrix (zAarrayL,3);
ABCDEF[i]= M.solve(Z).getRowPackedCopy();
}
if (forceLinear) return ABCDEF;
// quote try quadratic approximation
double [][] mAarrayQ= {
{S40,S22,S31,S30,S21,S20},
{S22,S04,S13,S12,S03,S02},
{S31,S13,S22,S21,S12,S11},
{S30,S12,S21,S20,S11,S10},
{S21,S03,S12,S11,S02,S01},
{S20,S02,S11,S10,S01,S00}};
M=new Matrix (mAarrayQ);
if (debugLevel>3) System.out.println(" n="+n+" det_quad="+M.det()+" norm_quad="+normMatix(mAarrayQ)+" data.length="+data.length);
double nmQ=normMatix(mAarrayQ);
if ((nmQ==0.0) || (Math.abs(M.det())/normMatix(mAarrayQ)<thresholdQuad)) {
if (debugLevel>0) System.out.println("Using linear approximation, M.det()="+M.det()+" normMatix(mAarrayQ)="+normMatix(mAarrayQ)); //did not happen
return ABCDEF; // not enough data for the quadratic approximation, return linear
}
// double [] zAarrayQ={SZ20,SZ02,SZ11,SZ10,SZ01,SZ00};
double [] zAarrayQ=new double [6];
for (i=0;i<zDim;i++) {
zAarrayQ[0]=SZ20[i];
zAarrayQ[1]=SZ02[i];
zAarrayQ[2]=SZ11[i];
zAarrayQ[3]=SZ10[i];
zAarrayQ[4]=SZ01[i];
zAarrayQ[5]=SZ00[i];
Z=new Matrix (zAarrayQ,6);
ABCDEF[i]= M.solve(Z).getRowPackedCopy();
}
return ABCDEF;
}
// calcualte "volume" made of the matrix row-vectors, placed orthogonally
// to be compared to determinant
public double normMatix(double [][] a) {
double d,norm=1.0;
for (int i=0;i<a.length;i++) {
d=0;
for (int j=0;j<a[i].length;j++) d+=a[i][j]*a[i][j];
norm*=Math.sqrt(d);
}
return norm;
}
//RuntimeException
}
This source diff could not be displayed because it is too large. You can view the blob instead.
/*
* To the extent possible under law, the Fiji developers have waived
* all copyright and related or neighboring rights to this tutorial code.
*
* See the CC0 1.0 Universal license for details:
* http://creativecommons.org/publicdomain/zero/1.0/
*/
import ij.IJ;
import ij.ImageJ;
import ij.ImagePlus;
import ij.gui.GenericDialog;
import ij.plugin.filter.PlugInFilter;
import ij.process.ImageProcessor;
/**
* ProcessPixels
*
* A template for processing each pixel of either
* GRAY8, GRAY16, GRAY32 or COLOR_RGB images.
*
* @author The Fiji Team
*/
public class Process_Pixels implements PlugInFilter {
protected ImagePlus image;
// image property members
private int width;
private int height;
// plugin parameters
public double value;
public String name;
/**
* @see ij.plugin.filter.PlugInFilter#setup(java.lang.String, ij.ImagePlus)
*/
@Override
public int setup(String arg, ImagePlus imp) {
if (arg.equals("about")) {
showAbout();
return DONE;
}
image = imp;
return DOES_8G | DOES_16 | DOES_32 | DOES_RGB;
}
/**
* @see ij.plugin.filter.PlugInFilter#run(ij.process.ImageProcessor)
*/
@Override
public void run(ImageProcessor ip) {
// get width and height
width = ip.getWidth();
height = ip.getHeight();
if (showDialog()) {
process(ip);
image.updateAndDraw();
}
}
private boolean showDialog() {
GenericDialog gd = new GenericDialog("Process pixels");
// default value is 0.00, 2 digits right of the decimal point
gd.addNumericField("value", 0.00, 2);
gd.addStringField("name", "John");
gd.showDialog();
if (gd.wasCanceled())
return false;
// get entered values
value = gd.getNextNumber();
name = gd.getNextString();
return true;
}
/**
* Process an image.
*
* Please provide this method even if {@link ij.plugin.filter.PlugInFilter} does require it;
* the method {@link ij.plugin.filter.PlugInFilter#run(ij.process.ImageProcessor)} can only
* handle 2-dimensional data.
*
* If your plugin does not change the pixels in-place, make this method return the results and
* change the {@link #setup(java.lang.String, ij.ImagePlus)} method to return also the
* <i>DOES_NOTHING</i> flag.
*
* @param image the image (possible multi-dimensional)
*/
public void process(ImagePlus image) {
// slice numbers start with 1 for historical reasons
for (int i = 1; i <= image.getStackSize(); i++)
process(image.getStack().getProcessor(i));
}
// Select processing method depending on image type
public void process(ImageProcessor ip) {
int type = image.getType();
if (type == ImagePlus.GRAY8)
process( (byte[]) ip.getPixels() );
else if (type == ImagePlus.GRAY16)
process( (short[]) ip.getPixels() );
else if (type == ImagePlus.GRAY32)
process( (float[]) ip.getPixels() );
else if (type == ImagePlus.COLOR_RGB)
process( (int[]) ip.getPixels() );
else {
throw new RuntimeException("not supported");
}
}
// processing of GRAY8 images
public void process(byte[] pixels) {
for (int y=0; y < height; y++) {
for (int x=0; x < width; x++) {
// process each pixel of the line
// example: add 'number' to each pixel
pixels[x + y * width] += (byte)value;
}
}
}
// processing of GRAY16 images
public void process(short[] pixels) {
for (int y=0; y < height; y++) {
for (int x=0; x < width; x++) {
// process each pixel of the line
// example: add 'number' to each pixel
pixels[x + y * width] += (short)value;
}
}
}
// processing of GRAY32 images
public void process(float[] pixels) {
for (int y=0; y < height; y++) {
for (int x=0; x < width; x++) {
// process each pixel of the line
// example: add 'number' to each pixel
pixels[x + y * width] += (float)value;
}
}
}
// processing of COLOR_RGB images
public void process(int[] pixels) {
for (int y=0; y < height; y++) {
for (int x=0; x < width; x++) {
// process each pixel of the line
// example: add 'number' to each pixel
pixels[x + y * width] += (int)value;
}
}
}
public void showAbout() {
IJ.showMessage("ProcessPixels",
"a template for processing each pixel of an image"
);
}
/**
* Main method for debugging.
*
* For debugging, it is convenient to have a method that starts ImageJ, loads an
* image and calls the plugin, e.g. after setting breakpoints.
*
* @param args unused
*/
public static void main(String[] args) {
// set the plugins.dir property to make the plugin appear in the Plugins menu
Class<?> clazz = Process_Pixels.class;
String url = clazz.getResource("/" + clazz.getName().replace('.', '/') + ".class").toString();
String pluginsDir = url.substring(5, url.length() - clazz.getName().length() - 6);
System.setProperty("plugins.dir", pluginsDir);
// start ImageJ
new ImageJ();
// open the Clown sample
ImagePlus image = IJ.openImage("http://imagej.net/images/clown.jpg");
image.show();
// run the plugin
IJ.runPlugIn(clazz.getName(), "");
}
}
import java.awt.Rectangle;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicInteger;
import javax.swing.SwingUtilities;
import ij.IJ;
/*
**
** SimulationPattern.java - Generate simulated pattern
**
** Copyright (C) 2010-2011 Elphel, Inc.
**
** -----------------------------------------------------------------------------**
**
** SimulationPattern.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/>.
** -----------------------------------------------------------------------------**
**
*/
public class SimulationPattern {
// private double [] bPattern; // pattern bitmap (does not change)
// private double[][] barray; // high resolution boolean pattern array (specific to distortions in each area)
public double [] bPattern=null; // pattern bitmap (does not change)
public int bPatternSize=0;
/// public double[][] barray; // high resolution boolean pattern array (specific to distortions in each area)
public double[] barray; // high resolution boolean pattern array (specific to distortions in each area)
public double bPatternSigma=0.0;
public double barraySigma=0.0;
public int debugLevel=2;
private DoubleGaussianBlur gb = new DoubleGaussianBlur();
private showDoubleFloatArrays SDFA_INSTANCE= new showDoubleFloatArrays(); // just for debugging?
public SimulationPattern (){
this.bPattern=null;
}
public SimulationPattern (double [] bPattern){ // reuse the same barray
this.bPattern=bPattern;
}
public SimulationPattern(
SimulParameters simulParameters ) {
this.bPatternSigma=simulParameters.bPatternSigma;
this.barraySigma=simulParameters.barraySigma;
patternGenerator(simulParameters);
}
public SimulationPattern(
int size,
int patternNumber,
double patternModifier) {
patternGenerator(size,patternNumber,patternModifier);
}
/** ======================================================================== */
public double [] patternGenerator(
SimulParameters simulParameters ) {
return patternGenerator(
simulParameters.patternSize,
simulParameters.pattern_type,
simulParameters.pattern_modifier);
}
public double [] patternGenerator(int size,
int patternNumber,
double patternModifier) {
this.bPattern=new double [size*size];
this.bPatternSize=size;
int i,j,index,k;
double p;
double a,r,r2,h;
double qSize=size/4;
switch (patternNumber) {
case 1:
a=patternModifier*(Math.sqrt(2)-1.0);
r=(a*a+1)/(2*a)*qSize;
r2=r*r;
h=Math.sqrt(r2-qSize*qSize);
if (a>1.0) h=-h;
double [][] pattern1Centers={{qSize, -h},
{ size+h, qSize},
{ size-qSize, size+h},
{-h, size-qSize}};
index=0;
for (i=0;i<size;i++) for (j=0;j<size;j++) {
p=1.0;
for (k=0;k<pattern1Centers.length;k++) if ((((i-pattern1Centers[k][1])*(i-pattern1Centers[k][1])+(j-pattern1Centers[k][0])*(j-pattern1Centers[k][0])))<r2) p=0.0;
this.bPattern[index++]=p;
}
break;
case 2:
index=0;
for (i=0;i<size;i++) for (j=0;j<size;j++) {
p= ((i>=0.3*size) && (i<0.7*size) && (j>=0.3*size) && (j<0.7*size))?1.0:0.0;
this.bPattern[index++]=p;
}
break;
case 3:
index=0;
for (i=0;i<size;i++) for (j=0;j<size;j++) {
p= ((i>=0.1*size) && (i<0.9*size) && (j>=0.1*size) && (j<0.9*size))?1.0:0.0;
this.bPattern[index++]=p;
}
break;
default: for (index=0;index<this.bPattern.length;index++) this.bPattern[index]=1.0;
}
// blur pattern
if (this.bPatternSigma>0) {
if (this.bPatternSigma>0.25) this.bPatternSigma=0.25;
// 1 - add margins around the pattern
int i1,j1;
int margin= (int) Math.ceil(3*size*this.bPatternSigma);
int sizeM=size+2*margin;
boolean invertY,invertX;
double [] bPatternM=new double [sizeM*sizeM];
for (i=0;i<sizeM;i++) {
i1= (i+size-margin)%size;
invertY=(((i+size-margin)/size)&1)==0;
for (j=0;j<sizeM;j++) {
invertX=(((j+size-margin)/size)&1)==0;
j1= (j+size-margin)%size;
bPatternM[i*sizeM+j]= (invertX ^ invertY)?(1.0-this.bPattern[i1*size+j1]):this.bPattern[i1*size+j1];
}
}
// apply blur
if (this.debugLevel>3) SDFA_INSTANCE.showArrays(bPatternM,sizeM,sizeM, "bPatternM");
this.gb.blurDouble(bPatternM,sizeM,sizeM,size*this.bPatternSigma,size*this.bPatternSigma, 0.01);
if (this.debugLevel>3) SDFA_INSTANCE.showArrays(bPatternM,sizeM,sizeM, "bPatternM-blured");
// remove margins
for (i=0;i<size;i++) for (j=0;j<size;j++) {
this.bPattern[i*size+j]= bPatternM[(i+margin)*sizeM+(j+margin)];
}
}
return this.bPattern;
}
/** ======================================================================== */
public void simulatePatternFullPattern(
double freqX1,
double freqY1,
double phase1,
double freqX2,
double freqY2,
double phase2,
double [] corr,
int subdiv,
int size,
boolean center_for_g2) {
int patternSize= (this.bPattern!=null)?((int) Math.sqrt(this.bPattern.length)):0;
double twicePatternSize=2*patternSize;
int i,j;
int fullSize=subdiv*(size+4)*2;
// this.barray=new double [fullSize][fullSize];
this.barray=new double [fullSize*fullSize];
double xl,yl; //,x,y;//,p1,p2;
double [][] xy2uv= {{freqX1,freqY1},
{freqX2,freqY2}};
if (this.debugLevel>2) {
System.out.println("simulatePatternFullPattern:");
System.out.println(" Ax="+IJ.d2s(corr[0],5)+" Bx="+IJ.d2s(corr[1],5)+" Cx="+IJ.d2s(corr[2],5)+" Dx="+IJ.d2s(corr[6],5)+" Ex="+IJ.d2s(corr[7],5));
System.out.println(" Ay="+IJ.d2s(corr[3],5)+" By="+IJ.d2s(corr[4],5)+" Cy="+IJ.d2s(corr[5],5)+" Dy="+IJ.d2s(corr[8],5)+" Ey="+IJ.d2s(corr[9],5));
}
if (this.debugLevel>2) {
System.out.println("simulatePatternFullPattern: xy2uv[0][0]="+IJ.d2s(xy2uv[0][0],4)+" xy2uv[0][1]="+IJ.d2s(xy2uv[0][1],4));
System.out.println("simulatePatternFullPattern: xy2uv[1][0]="+IJ.d2s(xy2uv[1][0],4)+" xy2uv[1][1]="+IJ.d2s(xy2uv[1][1],4));
}
double []uv, xy;
xy=new double [2];
double [] phases={phase1/(Math.PI*2)+0.25,phase2/(Math.PI*2)+0.25}; // period=1.0;
int iu,iv;
boolean invert;
for (i=0;i<fullSize;i++) {
yl=(i-0.5*fullSize)/subdiv-(center_for_g2?0.5:1.0); // center in the middle of Bayer
for (j=0;j<fullSize;j++) {
xl=(j-0.5*fullSize)/subdiv-(center_for_g2?0.5:1.0); // center in the middle of Bayer
/** apply second order polynomial correction to x,y
x=xl+Ax*xl^2+Bx*yl^2+2*Cx*xl*yl;
y=xl+Ay*xl^2+By*yl^2+2*Cy*xl*yl; */
if (corr==null) {
xy[0]=xl;
xy[1]=yl;
} else {
xy[0]=xl + corr[0]*xl*xl + corr[1]*yl*yl + 2* corr[2]*xl*yl + corr[6]*xl + corr[7]*yl;
xy[1]=yl + corr[3]*xl*xl + corr[4]*yl*yl + 2* corr[5]*xl*yl + corr[8]*xl + corr[9]*yl;
}
uv= matrix2x2_mul(xy2uv, xy);
uv= vector_add(uv,phases);
uv[0]-=Math.floor(uv[0]);
uv[1]-=Math.floor(uv[1]);
invert=false;
if (uv[0]>=0.5){
invert=!invert;
uv[0]-=0.5;
}
if (uv[1]>=0.5){
invert=!invert;
uv[1]-=0.5;
}
if (this.bPattern==null) {
/// this.barray[i][j]=invert?0.0:1.0; //!invert;
this.barray[i*fullSize+j]=invert?0.0:1.0; //!invert;
} else {
iu= (int) Math.round(uv[0]*twicePatternSize);
iv= (int) Math.round(uv[1]*twicePatternSize);
if ((iu<0) || (iu>=patternSize)) {
invert=!invert;
iu=(iu+patternSize)% patternSize;
}
if ((iv<0) || (iv>=patternSize)) {
invert=!invert;
iv=(iv+patternSize)% patternSize;
}
// this.barray[i][j]=invert ^ this.bPattern[iv*patternSize + iu];
/// this.barray[i][j]=invert?(1.0-this.bPattern[iv*patternSize + iu]): this.bPattern[iv*patternSize + iu];
this.barray[i*fullSize+j]=invert?(1.0-this.bPattern[iv*patternSize + iu]): this.bPattern[iv*patternSize + iu];
}
}
}
// Blur barray pattern if sigma >0
if (this.barraySigma>0) {
double sigma=this.barraySigma*subdiv; //*/ 2?
if (this.debugLevel>3) SDFA_INSTANCE.showArrays(this.barray, "barray");
this.gb.blurDouble(this.barray,fullSize,fullSize,sigma,sigma, 0.01);
if (this.debugLevel>3) SDFA_INSTANCE.showArrays(this.barray, "barray-blured");
}
}
/** ======================================================================== */
public double [] recursiveFillPixels ( // invert pattern in the caller, return signed value (-1..1 - pattern is 0..1)
SimulParameters simulParameters,
double [] xy, // top-left corner
double [] dxy, // increments to other corners
double [][][] cornersXY, // xy pairs for the 4 corners of the square in UV (pattern) coordinates (u0v0,u1v0,u0v1,u1v1)
double [] uv, // UV value for the top-left corner (matching cornersXY[0][0])
double [] duv, // distances to the opposite corner in UV
// final boolean maskOnly, // just mark defined cells
int debug
){ //use this.bPattern, this.bPatternSize (side of the square)
double [][][] cornersUV=new double [2][2][];
double [] xy4=new double[2];
double [] result ={0.0,0.0};
int numInside=0;
for (int i=0;i<2;i++) for (int j=0;j<2;j++) {
xy4[0]=xy[0]+j*dxy[0];
xy4[1]=xy[1]+i*dxy[1];
cornersUV[i][j]=bilinearXY2UV(cornersXY,xy4,debug);
if ((cornersUV[i][j][0]>=0.0) && (cornersUV[i][j][0]<=1.0) && (cornersUV[i][j][1]>=0.0) && (cornersUV[i][j][1]<=1.0)) numInside++;
}
if (debug>21){
String dbgStr="";
// IJ.d2s(quarter_patterns[iq][0][0],4)
dbgStr+="xy={"+IJ.d2s(xy[0],2)+","+IJ.d2s(xy[1],2)+"} ";
dbgStr+=" dxy={"+IJ.d2s(dxy[0],2)+","+IJ.d2s(dxy[1],2)+"} ";
dbgStr+=" uv={"+IJ.d2s(uv[0],2)+","+IJ.d2s(uv[1],2)+"} ";
dbgStr+=" duv={"+IJ.d2s(duv[0],2)+","+IJ.d2s(duv[1],2)+"} ";
dbgStr+=" cornersXY={{{"+IJ.d2s(cornersXY[0][0][0],5)+","+IJ.d2s(cornersXY[0][0][1],5)+"},";
dbgStr+= "{"+IJ.d2s(cornersXY[0][1][0],5)+","+IJ.d2s(cornersXY[0][1][1],5)+"}},";
dbgStr+= "{{"+IJ.d2s(cornersXY[1][0][0],5)+","+IJ.d2s(cornersXY[1][0][1],5)+"},";
dbgStr+= "{"+IJ.d2s(cornersXY[1][1][0],5)+","+IJ.d2s(cornersXY[1][1][1],5)+"}}}";
dbgStr+=" cornersUV={{{"+IJ.d2s(cornersUV[0][0][0],3)+","+IJ.d2s(cornersUV[0][0][1],3)+"},";
dbgStr+= "{"+IJ.d2s(cornersUV[0][1][0],3)+","+IJ.d2s(cornersUV[0][1][1],3)+"}},";
dbgStr+= "{{"+IJ.d2s(cornersUV[1][0][0],3)+","+IJ.d2s(cornersUV[1][0][1],3)+"},";
dbgStr+= "{"+IJ.d2s(cornersUV[1][1][0],3)+","+IJ.d2s(cornersUV[1][1][1],3)+"}}}";
dbgStr+=" numInside="+numInside;
System.out.println(dbgStr);
}
if (numInside==0) return result; // all corners outside of the (sub)pattern cell
// if (maskOnly) {
if (simulParameters==null) {
result[1]=dxy[0]*dxy[1];
result[0]=result[1];
return result;
}
// recalculate to the full uv
boolean cornersInvert;
double [][] cornerValue=new double [2][2];
int [] iPat=new int [2];
double min=1.0,max=-1.0;
for (int i=0;i<2;i++) for (int j=0;j<2;j++) {
// cornersUV[i][j][0]=uv[0]+j*cornersUV[i][j][0]*duv[0];
// cornersUV[i][j][1]=uv[1]+i*cornersUV[i][j][1]*duv[1];
cornersUV[i][j][0]=uv[0]+cornersUV[i][j][0]*duv[0];
cornersUV[i][j][1]=uv[1]+cornersUV[i][j][1]*duv[1];
cornersInvert=false;
for (int k=0;k<2;k++) {
// iPat[k] = (int) Math.floor(cornersUV[i][j][k]*this.bPatternSize*2.0); // 0.5 ->bPatternSize
iPat[k] = (int) Math.floor(cornersUV[i][j][k]*this.bPatternSize); // 1.0 ->bPatternSize
if (iPat[k]<0){
iPat[k]+=this.bPatternSize;
cornersInvert=!cornersInvert;
} else if (iPat[k]>=this.bPatternSize){
iPat[k]-=this.bPatternSize;
cornersInvert=!cornersInvert;
}
if ((iPat[k]<0) || (iPat[k]>=this.bPatternSize)) {
if (debug>0) System.out.println("Too far, cornersUV["+i+"]["+j+"]["+k+"]="+cornersUV[i][j][k]);
return result; // {0,0} here
}
}
cornerValue[i][j]=(2.0*this.bPattern[this.bPatternSize*iPat[1]+iPat[0]]-1.0)*(cornersInvert?1.0:-1.0);
if (max<cornerValue[i][j]) max=cornerValue[i][j];
if (min>cornerValue[i][j]) min=cornerValue[i][j];
}
if (((max-min)>simulParameters.bitmapNonuniforityThreshold) &&
(dxy[0]>simulParameters.smallestSubPix) &&
(dxy[1]>simulParameters.smallestSubPix)) {
// divide this square into 4 quadrants, return sum of the recursively called method on them
double [][] quadrants={{0.0,0.0},{0.5,0.0},{0.0,0.5},{0.5,0.5}};
double [] subResult;
double [] subxy=new double [2];
double [] subdxy={0.5*dxy[0],0.5*dxy[1]};
if (debug>1){
System.out.println("---> Subdividing into "+subdxy[0]+"x"+subdxy[0]+" (max="+IJ.d2s(max,3)+" min="+IJ.d2s(min,3)+
" max-min="+IJ.d2s(max-min,3)+
" cornerValue: [0][0]="+IJ.d2s(cornerValue[0][0],3)+
" [0][1]="+IJ.d2s(cornerValue[0][1],3)+
" [1][0]="+IJ.d2s(cornerValue[1][0],3)+
" [1][1]="+IJ.d2s(cornerValue[1][1],3)+
" cornersUV={{{"+IJ.d2s(cornersUV[0][0][0],3)+","+IJ.d2s(cornersUV[0][0][1],3)+"},"+
"{"+IJ.d2s(cornersUV[0][1][0],3)+","+IJ.d2s(cornersUV[0][1][1],3)+"}},"+
"{{"+IJ.d2s(cornersUV[1][0][0],3)+","+IJ.d2s(cornersUV[1][0][1],3)+"},"+
"{"+IJ.d2s(cornersUV[1][1][0],3)+","+IJ.d2s(cornersUV[1][1][1],3)+"}}}");
}
for (int i=0;i<quadrants.length;i++) {
subxy[0]=xy[0]+dxy[0]*quadrants[i][0];
subxy[1]=xy[1]+dxy[1]*quadrants[i][1];
subResult= recursiveFillPixels ( // invert pattern in the caller, return signed value (-1..1 - pattern is 0..1)
simulParameters,
subxy, // top-left corner
subdxy, // increments to other corners
cornersXY, // xy pairs for the 4 corners of the square in UV (pattern) coordinates (u0v0,u1v0,u0v1,u1v1)
uv, // UV value for the top-left corner (matching cornersXY[0][0])
duv, // distances to the opposite corner in UV
// maskOnly, // just mark defined cells //always false - will never get here
debug
);
result[0]+=subResult[0];
result[1]+=subResult[1];
}
if (debug>1){
if (result[1]==0.0) System.out.println("<--- Combined results "+IJ.d2s(result[0],3)+" / "+IJ.d2s(result[1],3));
else System.out.println("<--- Combined results "+IJ.d2s(result[0],3)+" / "+IJ.d2s(result[1],3)+"="+IJ.d2s(result[0]/result[1],3));
}
} else { // no more subdivisions - calculate average value, taking into account partial pixels
for (int i=0;i<2;i++) for (int j=0;j<2;j++) result[0]+=0.25*cornerValue[i][j];
result[1]=dxy[0]*dxy[1];
result[0]*=result[1];
if (numInside <4) {
double f=((double) numInside)/4; // estimate fraction of the pixel - start with simple number of corners
result[0]*=f;
result[1]*=f;
}
if (debug>1){
if (result[1]==0.0)System.out.println("< === Returnimg "+IJ.d2s(result[0],3)+" / "+IJ.d2s(result[1],3)+" ("+numInside+" corners inside)");
else System.out.println("< === Returning "+IJ.d2s(result[0],5)+" / "+IJ.d2s(result[1],5)+"="+IJ.d2s(result[0]/result[1],3)+" ("+numInside+" corners inside)");
}
}
return result;
}
/** ======================================================================== */
/**
* @param cornersXY first index V, second index U, third index:0 - x, 1-y
* @param xy 0-x,1-y of the point, for which UV should be generated
* @return UV pair
*/
public double [] bilinearXY2UV(
double [][][] cornersXY, // first index V, second index U, third index:0 - x, 1-y
double [] xy, // 0-x,1-y of the point, for which
int debug
) {
return bilinearXY2UV(
cornersXY, // first index V, second index U, third index:0 - x, 1-y
xy, // 0-x,1-y of the point, for which
1E-6,
debug);
}
/**
* @param cornersXY first index V, second index U, third index:0 - x, 1-y
* @param xy 0-x,1-y of the point, for which UV should be generated
* @param quadThreshold if abs(4*a*c)/b^2 is less than this, use linear, not quadratic equations
* @return UV pair
*/
public double [] bilinearXY2UV(
double [][][] cornersXY, // first index V, second index U, third index:0 - x, 1-y
double [] xy, // 0-x,1-y of the point, for which
double quadThreshold, // if abs(4*a*c)/b^2 is less than this, use linear, not quadratic equations
int debug
) {
/*
x,y -> u,v
(1) x= v*u*Ax + v*Bx + u*Cx + Dx
(2) y= v*u*Ay + v*By + u*Cy + Dy
Ax=x11-x10-x01+x00
Bx=x10-x00
Cx=x01-x00
Dx=x00
Ay=y11-y10-y01+y00
By=y10-y00
Cy=y01-y00
Dy=y00
u*u*(Cy*Ax-Cx*Ay)+u*((-Cx*By-Ay*Dx+Cy*Bx+Ax*Dy)+Ay*x-Ax*y)+(By*x-Bx*y)+(-By*Dx+Bx*Dy)=0
Au*u*u+Bu*u+Cu=0
Av*v*v+Bv*v+Cv=0
Au=(Cy*Ax-Cx*Ay)
Bu=((-Cx*By-Ay*Dx+Cy*Bx+Ax*Dy)+Ay*x-Ax*y)
Cu=(By*x-Bx*y)+(-By*Dx+Bx*Dy)
Av=(By*Ax-Bx*Ay)
Bv=((-Bx*Cy-Ay*Dx+By*Cx+Ax*Dy)+Ay*x-Ax*y)
Cv=(Cy*x-Cx*y)+(-Cy*Dx+Cx*Dy)
*/
double Ax=cornersXY[1][1][0]-cornersXY[1][0][0]-cornersXY[0][1][0]+cornersXY[0][0][0];
double Bx=cornersXY[1][0][0]-cornersXY[0][0][0];
double Cx=cornersXY[0][1][0]-cornersXY[0][0][0];
double Dx=cornersXY[0][0][0];
double Ay=cornersXY[1][1][1]-cornersXY[1][0][1]-cornersXY[0][1][1]+cornersXY[0][0][1];
double By=cornersXY[1][0][1]-cornersXY[0][0][1];
double Cy=cornersXY[0][1][1]-cornersXY[0][0][1];
double Dy=cornersXY[0][0][1];
double Au=(Cy*Ax-Cx*Ay);
double Bu=((-Cx*By-Ay*Dx+Cy*Bx+Ax*Dy)+Ay*xy[0]-Ax*xy[1]);
double Cu=(By*xy[0]-Bx*xy[1])+(-By*Dx+Bx*Dy);
double Av=(By*Ax-Bx*Ay);
double Bv=((-Bx*Cy-Ay*Dx+By*Cx+Ax*Dy)+Ay*xy[0]-Ax*xy[1]);
double Cv=(Cy*xy[0]-Cx*xy[1])+(-Cy*Dx+Cx*Dy);
// double [] UV={-Cv/Bv,-Cu/Bu}; // linear solution - use for linear grid
double [] UV={-Cu/Bu,-Cv/Bv}; // linear solution - use for linear grid
double au=0.0,bu=0.0,av=0.0,bv=0.0;
if (Math.abs(Au*Cu)/(Bu*Bu)>quadThreshold) { // use quadratic equation for U
au=-Bu/(2*Au);
bu=Math.sqrt(Bu*Bu-4*Au*Cu)/Math.abs(2*Au);
// Use solution that is closer to linear one
if (UV[0]>au) UV[0]=au+bu;
else UV[0]=au-bu;
}
if (Math.abs(Av*Cv)/(Bv*Bv)>quadThreshold) { // use quadratic equation for V
av=-Bv/(2*Av);
bv=Math.sqrt(Bv*Bv-4*Av*Cv)/Math.abs(2*Av);
// Use solution that is closer to linear one
if (UV[1]>av) UV[1]=av+bv;
else UV[1]=av-bv;
}
if (debug>2){
String dbgStr="";
// IJ.d2s(quarter_patterns[iq][0][0],4)
dbgStr+=" Ax="+IJ.d2s(Ax,5)+", Bx="+IJ.d2s(Bx,5)+", Cx="+IJ.d2s(Cx,5)+", Dx="+IJ.d2s(Dx,5);
dbgStr+=" Ay="+IJ.d2s(Ay,5)+", By="+IJ.d2s(By,5)+", Cy="+IJ.d2s(Cy,5)+", Dy="+IJ.d2s(Dy,5);
dbgStr+=" Au="+IJ.d2s(Au,5)+", Bu="+IJ.d2s(Bu,5)+", Cu="+IJ.d2s(Cu,5);
dbgStr+=" Av="+IJ.d2s(Av,5)+", Bv="+IJ.d2s(Bv,5)+", Cv="+IJ.d2s(Cv,5);
dbgStr+=" LinU="+IJ.d2s(-Cu/Bu,3)+", LinV="+IJ.d2s(-Cv/Bv,5);
dbgStr+=" au="+IJ.d2s(au,5)+", bu="+IJ.d2s(bu,5);
dbgStr+=" av="+IJ.d2s(av,5)+", bv="+IJ.d2s(bv,5);
System.out.println(dbgStr);
}
return UV;
}
/** ======================================================================== */
private boolean isCellValid(
double [][][][] grid,
int [] uv){
if ((uv[1]>=0) && (uv[0]>=0) && (uv[1]<grid.length) && (uv[0]<grid[uv[1]].length)) {
double [][] cell = grid[uv[1]][uv[0]];
return ((cell!=null) && (cell.length>1));
}
return false;
}
/*
private boolean isCellDefined(
double [][][][] grid,
int [] uv){
return ((uv[1]>=0) && (uv[0]>=0) && (uv[1]<grid.length) && (uv[0]<grid[uv[1]].length) &&
(grid[uv[1]][uv[0]]!=null) && (grid[uv[1]][uv[0]][0]!=null));
}
*/
public float [] combineWithCanvas(
double canvasFill,
int width,
int height,
Rectangle woi,
float [] selection ){
float []canvas=new float[width*height];
for (int i=0;i<canvas.length;i++)canvas[i]= (float) canvasFill;
return combineWithCanvas(canvas, width, woi, selection );
}
public float [] combineWithCanvas(
float [] canvas,
int width,
Rectangle woi,
float [] selection ){
// debug
if (selection==null) System.out.println("combineWithCanvas(): selection==null");
if (woi==null) System.out.println("combineWithCanvas(): woi==null");
if (selection.length!=(woi.width*woi.height)) throw new IllegalArgumentException ("selection.length="+selection.length+", woi.width="+woi.width+", woi.height="+woi.height);
int i0=0;
int i1=width*woi.y+woi.x;
for (int y=0;y<woi.height;y++){
for (int x=0;x<woi.width;x++){
if ((i1>canvas.length) ||(i0>=selection.length)){
System.out.println("canvas.length="+canvas.length+" width="+width+" selection.length="+selection.length+" y="+y+" x="+x+" i0="+i0+" i1="+i1+
" woi.x="+woi.x+" woi.y="+woi.y+" woi.width="+woi.width+" woi.height="+woi.height);
}
canvas[i1++]=selection[i0++]; // OOB 18720
}
i1+=(width-woi.width);
}
return canvas;
}
public double [] combineWithCanvas(
double canvasFill,
int width,
int height,
Rectangle woi,
double [] selection ){
double []canvas=new double[width*height];
for (int i=0;i<canvas.length;i++)canvas[i]= canvasFill;
return combineWithCanvas(canvas, width, woi, selection );
}
public double [] combineWithCanvas(
double [] canvas,
int width,
Rectangle woi,
double [] selection ){
if (selection.length!=(woi.width*woi.height)) throw new IllegalArgumentException ("selection.length="+selection.length+", woi.width="+woi.width+", woi.height="+woi.height);
int i0=0;
int i1=width*woi.y+woi.x;
for (int y=0;y<woi.height;y++){
for (int x=0;x<woi.width;x++) canvas[i1++]=selection[i0++];
i1+=(width-woi.width);
}
return canvas;
}
//===================== Moved from Aberration_Calibration
public float[][] simulateGridAll (
int width, // extend to full image, width, height - original (not scaled) image size
int height,
MatchSimulatedPattern matchSimulatedPattern,
// double [][][][] patternGrid, // should be aligned to gridFrac
int gridFrac, // number of grid steps per pattern full period
SimulParameters simulParameters,
int threadsMax,
boolean updateStatus,
int globalDebugLevel,
int debug_level){// debug level used inside loops
// SimulationPattern simulationPattern=new SimulationPattern(simulParameters);
float [][] simArray0=simulateGridAll (
matchSimulatedPattern,
// patternGrid, // should be aligned to gridFrac
gridFrac, // number of grid steps per pattern full period
simulParameters,
// simulationPattern,
threadsMax,
updateStatus,
globalDebugLevel,
debug_level);
Rectangle woi=matchSimulatedPattern.getWOI();
if ((woi.x==0) && (woi.y==0) && (woi.width==width) && (woi.height==height)) return simArray0;
int k=simulParameters.subdiv/2;
Rectangle scaledWoi=new Rectangle(k*woi.x, k*woi.y, k*woi.width, k*woi.height);
float [][] simArray=new float [2][];
simArray[0]=(new SimulationPattern(simulParameters)).combineWithCanvas(0.0, k*width, k*height, scaledWoi,simArray0[0]);
simArray[1]=(new SimulationPattern(simulParameters)).combineWithCanvas(0.0, k*width, k*height, scaledWoi,simArray0[1]);
if (globalDebugLevel>1) SDFA_INSTANCE.showArrays(simArray,width*k,height*k,true, "full-simulation");
return simArray;
}
public float[][] simulateGridAll (
MatchSimulatedPattern matchSimulatedPattern,
// double [][][][] patternGrid, // should be aligned to gridFrac
int gridFrac, // number of grid steps per pattern full period
SimulationPattern.SimulParameters simulParameters,
// SimulationPattern simulationPattern, // or null
int threadsMax,
boolean updateStatus,
int globalDebugLevel,
int debug_level){// debug level used inside loops
long startTime=System.nanoTime();
double [][] xy0={{simulParameters.offsetX,simulParameters.offsetY},{simulParameters.offsetX-0.5,simulParameters.offsetY-0.5}} ;
// if (simulationPattern==null) simulationPattern=new SimulationPattern(simulParameters);
float[][] simArray=new float[2][];
simArray[0]= simulateGrid (
matchSimulatedPattern.getDArray(),
2, // gridFrac, // number of grid steps per pattern full period
simulParameters,
matchSimulatedPattern.getWOI(),
simulParameters.subdiv/2,
xy0[0], // add to patterGrid xy
threadsMax,
updateStatus,
debug_level); // debug level
simArray[1]= simulateGrid (
matchSimulatedPattern.getDArray(),
2, // gridFrac, // number of grid steps per pattern full period
simulParameters,
matchSimulatedPattern.getWOI(),
simulParameters.subdiv/2,
xy0[1], // add to patterGrid xy
threadsMax,
updateStatus,
debug_level); // debug level
if (globalDebugLevel>2) SDFA_INSTANCE.showArrays(simArray,matchSimulatedPattern.getWOI().width*simulParameters.subdiv/2,matchSimulatedPattern.getWOI().height*simulParameters.subdiv/2,true, "a-simulation");
if (globalDebugLevel>1) System.out.println("Grid simulation is finished at "+ IJ.d2s(0.000000001*(System.nanoTime()-startTime),3));
return simArray;
}
//========================
public float [] simulateGrid (
final double [][][][] patternGrid, // should be aligned to gridFrac
final int gridFrac, // number of grid steps per pattern full period: black+white
final SimulParameters simulParameters, // Try to use null here for maskOnly
final Rectangle woi,
final int subdiv, // subdivide output array from woi (normally 2)
double[] shift_xy, // add to patterGrid xy, null OK
// final boolean maskOnly, // just mark defined cells
final int threadsMax,
final boolean updateStatus,
final int debug_level){// debug level used inside loops
double []xy_zero={0.0,0.0};
if (patternGrid==null) return null;
final double [] xy0=(shift_xy==null)?xy_zero:shift_xy;
if ((simulParameters!=null) && (this.bPattern==null)){
System.out.println("simulateGrid(), running patternGenerator(simulParameters )");
patternGenerator(simulParameters ); // generate bPattern if it was not done yet
}
final Rectangle woiOut=new Rectangle(subdiv*woi.x,subdiv*woi.y,subdiv*woi.width,subdiv*woi.height);
final float [] pixels=new float[woiOut.width*woiOut.height];
final float [] pixelsDenom=new float[woiOut.width*woiOut.height];
for (int i=0;i<pixels.length;i++ ){
pixels[i]= 0.0F;
pixelsDenom[i]= 0.0F;
}
final Thread[] threads = newThreadArray(threadsMax);
final AtomicInteger cellNum = new AtomicInteger(0);
final int [] series = new int[1];
final int uvhwidth=(patternGrid[0].length+1)/2;
final int uvwidth= patternGrid[0].length;
final int uvhheight=(patternGrid.length+1)/2;
final int numInSeries=(uvhwidth-1)*(uvhheight-1);
final AtomicInteger debugCellNum = new AtomicInteger(0);
final AtomicInteger finishedAtomic = new AtomicInteger(1);
final int cellsToProcess=numInSeries*4;
final int debugCellNum0=0;
IJ.showStatus("Generating simulated pattern...");
for (series[0]=0;series[0]<4;series[0]++) { // split processing in 4 series (odd/even row/column to avoid races between threads
if (debug_level>2)System.out.println("**** series[0]="+series[0]);
cellNum.set(0);
for (int ithread = 0; ithread < threads.length; ithread++) {
threads[ithread] = new Thread() {
public void run() {
// String dbgStr="";
int [][][] iUV=new int [2][2][2];
double [][][] dUV=new double [2][2][2];
double [][][] xy=new double [2][2][2];
boolean invPattern;
double [] pixDXY={1.0,1.0};
for (int ncell=cellNum.getAndIncrement(); ncell<numInSeries;ncell=cellNum.getAndIncrement()){
iUV[0][0][0]=2*(ncell%(uvhwidth-1))+ (series[0] & 1);
iUV[0][0][1]=2*(ncell/(uvhwidth-1))+ ((series[0]>>1) & 1);
if ((updateStatus) && (debugLevel>1)) IJ.showStatus("Generating simulated pattern, series "+series[0]+" (of 4), row "+(iUV[0][0][1]/2+1)+"(of "+(uvhheight-1)+")");
if (debugLevel>2) System.out.println("Generating pattern, series "+series[0]+" (of 4), row "+(iUV[0][0][1]/2+1)+"(of "+(uvhheight-1)+")");
iUV[0][1][0]=iUV[0][0][0]+1;
iUV[0][1][1]=iUV[0][0][1];
iUV[1][0][0]=iUV[0][0][0];
iUV[1][0][1]=iUV[0][0][1]+1;
iUV[1][1][0]=iUV[0][0][0]+1;
iUV[1][1][1]=iUV[0][0][1]+1;
if ((isCellValid(patternGrid,iUV[0][0])) &&
(isCellValid(patternGrid,iUV[0][1])) &&
(isCellValid(patternGrid,iUV[1][0])) &&
(isCellValid(patternGrid,iUV[1][1]))){
// All 4 corners are valid
invPattern=((iUV[0][0][0]%gridFrac)>=(gridFrac/2))^((iUV[0][0][1]%gridFrac)>=(gridFrac/2));
if (debug_level>2)System.out.println("iUV[0][0][1]="+iUV[0][0][1]+" iUV[0][0][0]="+iUV[0][0][0]+" invert="+invPattern);
for (int i=0;i<2;i++) for (int j=0;j<2;j++) for (int k=0;k<2;k++) {
xy[i][j][k]=subdiv*patternGrid[iUV[i][j][1]][iUV[i][j][0]][0][k]+xy0[k];
}
for (int k=0;k<2;k++) {
dUV[0][0][k]=((double) (iUV[0][0][k]%(gridFrac/2)))/(gridFrac/2);
}
dUV[0][1][0]=dUV[0][0][0]+1.0/(gridFrac/2);
dUV[0][1][1]=dUV[0][0][1];
dUV[1][0][0]=dUV[0][0][0];
dUV[1][0][1]=dUV[0][0][1]+1.0/(gridFrac/2);
dUV[1][1][0]=dUV[0][1][0];
dUV[1][1][1]=dUV[1][0][1];
double [] minXY={xy[0][0][0],xy[0][0][1]};
double [] maxXY={xy[0][0][0],xy[0][0][1]};
for (int i=0;i<2;i++) for (int j=0;j<2;j++) for (int k=0;k<2;k++) {
if (minXY[k]>xy[i][j][k]) minXY[k]=xy[i][j][k];
if (maxXY[k]<xy[i][j][k]) maxXY[k]=xy[i][j][k];
}
Rectangle rcell=new Rectangle((int)minXY[0], // contains all pixels
(int)minXY[1],
((int) Math.ceil(maxXY[0]))-((int)minXY[0]) ,
((int) Math.ceil(maxXY[1]))-((int)minXY[1]));
double [] cornersDUV={1.0/(gridFrac/2),1.0/(gridFrac/2)};
boolean debugNow=debugCellNum.getAndIncrement()==debugCellNum0;
if (woiOut.intersects (rcell)) // do not bother if no
for (int iy=rcell.y;iy<(rcell.y+rcell.height);iy++) for (int ix=rcell.x;ix<(rcell.x+rcell.width);ix++)
if (woiOut.contains (ix,iy))
{
double [] pixXY={(double) ix,(double) iy};
double [] pixData=recursiveFillPixels ( // invert pattern in the caller, return signed value (-1..1 - pattern is 0..1)
simulParameters,
pixXY, // top-left corner
pixDXY, // increments to other corners
xy, // xy pairs for the 4 corners of the square in UV (pattern) coordinates (u0v0,u1v0,u0v1,u1v1)
dUV[0][0], // UV value for the top-left corner (matching cornersXY[0][0])
cornersDUV, // distances to the opposite corner in UV
// maskOnly, // just mark defined cells
debugNow?debug_level: debug_level-2
);
// int index=woiOut.width*iy+ix;
int index=woiOut.width*(iy-woiOut.y)+(ix-woiOut.x);
// pixels[index]+=((invPattern || maskOnly)?1.0:-1.0)*pixData[0];
if (index>pixels.length){
// final float [] pixels=new float[woiOut.width*woiOut.height];
System.out.println("simulateGrid(), pixels.length="+pixels.length+
" index="+index+
" iy="+iy+" ix="+ix+
" ncell="+ncell+
" subdiv="+subdiv+
" woiOut.x="+woiOut.x+
" woiOut.y="+woiOut.y+
" woiOut.width="+woiOut.width+
" woiOut.height="+woiOut.height+
" woi.x="+woi.x+
" woi.y="+woi.y+
" woi.width="+woi.width+
" woi.height="+woi.height+
" rcell.x="+rcell.x+
" rcell.y="+rcell.y+
" rcell.width="+rcell.width+
" rcell.height="+rcell.height);
}
// if (maskOnly) pixels[index]=patternGrid.length*iUV[0][0][1]+iUV[0][0][0]; // out of bounds
// OLD NASTY BUG!
// if (maskOnly) pixels[index]=uvwidth*iUV[0][0][1]+iUV[0][0][0]; // Should be width, not height!
if (simulParameters==null) pixels[index]=uvwidth*iUV[0][0][1]+iUV[0][0][0]; // Should be width, not height!
else pixels[index]+=(invPattern?1.0:-1.0)*pixData[0];
pixelsDenom[index]+=pixData[1];
}
}
final int numFinished=finishedAtomic.getAndIncrement();
SwingUtilities.invokeLater(new Runnable() {
public void run() {
IJ.showProgress(numFinished,cellsToProcess);
}
});
}
}
};
}
startAndJoin(threads);
}
for (int i=0;i<pixels.length;i++ ) {
// /(simulParameters!=null)
// if (maskOnly) {
if (simulParameters==null) {
if (pixelsDenom[i]==0.0F) pixels[i]=-1;
} else {
if (pixelsDenom[i]!=0.0F){
pixels[i]/= pixelsDenom[i];
pixels[i]=(float) ((pixels[i]+1.0)/2); // convert from -1..+1 to 0..1.0
}
}
}
return pixels;
}
/** 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);
}
}
/** ======================================================================== */
/** make it faster when outSubdiv =2*n (usually so) */
/** TODO: cleanup shifts - they seem now to work correctly */
public double [][] extractSimulPatterns (
SimulParameters simulParameters,
int outSubdiv, // subdivide output pixels
int size, // number of Bayer cells in width of the square selection (half number of pixels)
double x0, // selection center, X (in pixels)
double y0) {
int sampleWidth=(int) (Math.sqrt(simulParameters.fill)*simulParameters.subdiv);
int sampleN=sampleWidth*sampleWidth;
if (sampleWidth<1) sampleWidth=1;
else if (sampleWidth>simulParameters.subdiv)sampleWidth=simulParameters.subdiv;
double sampleAverage=0.5*sampleN;
int n,i,j;
// int fullSize=this.barray.length;
int fullSize=(int) Math.sqrt(this.barray.length);
double [][] simul_pixels=new double [5][size*size];
int ix,iy, iy0,ix0,px,py;
double bx,by;
double s;
double span=((double) size)/outSubdiv;
int sampLow=-sampleWidth/2;
int sampHigh=sampLow+sampleWidth;
for (n=0;n<4;n++) {
bx=(n&1)-0.5+0.5; // last 0.5 to make same center as for dual greens
by=((n>>1) & 1)-0.5-0.5;// last 0.5 to make same center as for dual greens
for (iy=0;iy<size;iy++) {
iy0=(fullSize/2) + (int) ((-span+y0+by +1.5 +2.0*iy/outSubdiv)*simulParameters.subdiv);
for (ix=0;ix<size;ix++) {
ix0=(fullSize/2) + (int) ((-span+x0+bx+0.5 +2.0*ix/outSubdiv)*simulParameters.subdiv);
s=0.0;
for (py=iy0+sampLow;py<iy0+sampHigh;py++) for (px=ix0+sampLow;px<ix0+sampHigh;px++) {
/// s+=this.barray[py][px];
s+=this.barray[py*fullSize+px];
}
simul_pixels[n][iy*size+ix]= (s-sampleAverage)/sampleAverage;
}
}
}
if (outSubdiv>1) {
if (this.debugLevel>2)System.out.println("Generating combined greens pattern greens from scratch");
n=4;
bx=0.0;
by=0.0;
for (iy=0;iy<size;iy++) {
for (ix=0;ix<size;ix++) {
iy0=(fullSize/2) + (int) ((-span+y0+by-1+1.5 +1.0*(size+iy-ix)/outSubdiv)*simulParameters.subdiv);
ix0=(fullSize/2) + (int) ((-span+x0+bx +0.5 +1.0*(iy+ix)/outSubdiv)*simulParameters.subdiv);
s=0.0;
for (py=iy0+sampLow;py<iy0+sampHigh;py++) for (px=ix0+sampLow;px<ix0+sampHigh;px++) {
/// s+=this.barray[py][px];
s+=this.barray[py*fullSize+px];
}
simul_pixels[n][iy*size+ix]= (s-sampleAverage)/sampleAverage;
}
}
} else { // just reuse available greens
if (this.debugLevel>2)System.out.println("Generating combined greens pattern from individual greens");
/** now combine greens - same as in splitBayer() */
int base, base_b;
base_b=0;
for (i=0;i<size/2; i++){
base=size*size/2+ i* (size+1);
for (j=0; j<size/2; j++) {
simul_pixels[4][base_b++]=simul_pixels[0][base];
base-=size;
simul_pixels[4][base_b++]=simul_pixels[3][base++];
}
base=size*size/2+ i* (size+1);
for (j=0; j<size/2; j++) {
//System.out.println("2:y="+y+" x="+x+" base_b="+base_b+" base="+base);
simul_pixels[4][base_b++]=simul_pixels[3][base++];
simul_pixels[4][base_b++]=simul_pixels[0][base];
base-=size;
}
}
}
if (this.debugLevel>2) {
System.out.println("extractSimulPatterns, x0="+x0+" y0="+y0+" fullSize="+fullSize+" size="+size+" subdiv="+simulParameters.subdiv+" outSubdiv="+outSubdiv);
System.out.println(" sampLow="+sampLow+" sampHigh="+sampHigh+" span="+span+" size="+size);
for (n=0;n<simul_pixels.length;n++) {
s=0.0;
for (i=0;i<simul_pixels[n].length;i++) s+=simul_pixels[n][i];
System.out.println(" component="+i+" sum of pixels="+s);
}
}
if (this.debugLevel>2) SDFA_INSTANCE.showArrays(simul_pixels,size,size, "SIMUL");
return simul_pixels;
}
private double [] matrix2x2_mul(double [][] a, double [] b ){
double [] rslt={a[0][0]*b[0]+a[0][1]*b[1],
a[1][0]*b[0]+a[1][1]*b[1]};
return rslt;
}
private double [] vector_add(double [] a, double [] b ){
double [] rslt= new double [a.length];
int i;
for (i=0;i<rslt.length;i++) rslt[i]=a[i]+b[i];
return rslt;
}
public double[] extractBayerSim (
float [][] spixels, // [0] - regular pixels, [1] - shifted by 1/2 diagonally, for checker greens
int full_width,
Rectangle woi,
int bayerPeriod, // 4
int colorComp) {
Rectangle r=new Rectangle(woi); // clone
int full_height=spixels[0].length/full_width; // full image height
if (debugLevel>10) IJ.showMessage("splitBayer","r.width="+r.width+
"\nr.height="+r.height+
"\nr.x="+r.x+
"\nr.y="+r.y+
"\nlength="+spixels[0].length);
if ((debugLevel>2) && ((r.x<0) || (r.y<0) || ((r.x+r.width)>=full_width) || ((r.y+r.height)>=full_height))) System.out.println("r.width="+r.width+
" r.height="+r.height+
" r.x="+r.x+
" r.y="+r.y);
if (colorComp==5) colorComp=0; // for compatibility, combined grees and green 0 generate the same result
double []result=new double[r.width*r.height];
int index;
if (colorComp==4) { // checkerboard greens
r.y+=r.width/2; // now it is the "top left" corner of the diagonal greens
for (index=0;index<result.length;index++){
// int iy=r.y+(index / r.width);
// int ix=r.x+(index % r.width);
int iyi=index / r.width;
int ixi=index % r.width;
int iy=r.y+(iyi+ixi)/2 -ixi;
int ix=r.x+(iyi+ixi)/2;
if (iy<0) iy=0;
else if (iy>=full_height) iy=full_height-1;
if (ix<0) ix=0;
else if (ix>=full_width) iy=full_width-1;
result[index]=spixels[(ixi+iyi) & 1][iy*full_width+ix];
}
} else { // components 0..3
r.x+=(bayerPeriod/2)*(colorComp &1);
r.y+=(bayerPeriod/2)*((colorComp>>1) &1);
if (debugLevel>2) System.out.println(">>> r.width="+r.width+
" r.height="+r.height+
" r.x="+r.x+
" r.y="+r.y+
" colorComp="+colorComp);
for (index=0;index<result.length;index++){
int iy=r.y+(index / r.width);
int ix=r.x+(index % r.width);
if (iy<0) iy=0;
else if (iy>=full_height) iy=full_height-1;
if (ix<0) ix=0;
else if (ix>=full_width) iy=full_width-1;
result[index]=spixels[0][iy*full_width+ix];
}
}
return result;
}
//=====================
public static class SimulParameters {
public int patternSize;
public int pattern_type;
public double pattern_modifier;
public double freq_x1;
public double freq_y1;
public double phase1;
public double freq_x2;
public double freq_y2;
public double phase2;
public int subdiv;
public double fill;
public boolean center_for_g2;
public double bPatternSigma; // blur bPattern with this sigma
public double barraySigma; // blur barray with this sigma, multiplied by subdiv
public double smallestSubPix; // subdivide pixels down to that fraction when simulating
public double bitmapNonuniforityThreshold; // subdivide pixels until difference between the corners is below this value
public double offsetX; // debug - add to X during simulation, in pixels
public double offsetY; // debug - add to Y during simulation, in pixels
public SimulParameters(
int patternSize,
int pattern_type,
double pattern_modifier,
double freq_x1,
double freq_y1,
double phase1,
double freq_x2,
double freq_y2,
double phase2,
int subdiv,
double fill,
boolean center_for_g2,
double bPatternSigma, // blur bPattern with this sigma
double barraySigma, // blur barray with this sigma, multiplied by subdiv
double smallestSubPix, // subdivide pixels down to that fraction when simulating
double bitmapNonuniforityThreshold, // subdivide pixels until difference between the corners is below this value
double offsetX, // debug - add to X during simulation, in pixels
double offsetY // debug - add to Y during simulation, in pixels
) {
this.patternSize= patternSize;
this.pattern_type= pattern_type;
this.pattern_modifier=pattern_modifier;
this.freq_x1= freq_x1;
this.freq_y1= freq_y1;
this.phase1= phase1;
this.freq_x2= freq_x2;
this.freq_y2= freq_y2;
this.phase2= phase2;
this.subdiv= subdiv;
this.fill= fill;
this.center_for_g2= center_for_g2;
this.bPatternSigma= bPatternSigma;
this.barraySigma=barraySigma;
this.smallestSubPix= smallestSubPix;
this.bitmapNonuniforityThreshold=bitmapNonuniforityThreshold;
this.offsetX= offsetX;
this.offsetY= offsetY;
}
public SimulParameters clone() {
return new SimulParameters(
this.patternSize,
this.pattern_type,
this.pattern_modifier,
this.freq_x1,
this.freq_y1,
this.phase1,
this.freq_x2,
this.freq_y2,
this.phase2,
this.subdiv,
this.fill,
this.center_for_g2,
this.bPatternSigma,
this.barraySigma,
this.smallestSubPix,
this.bitmapNonuniforityThreshold,
this.offsetX,
this.offsetY
);
}
public void setProperties(String prefix,Properties properties){
properties.setProperty(prefix+"patternSize",this.patternSize+"");
properties.setProperty(prefix+"pattern_type",this.pattern_type+"");
properties.setProperty(prefix+"pattern_modifier",this.pattern_modifier+"");
properties.setProperty(prefix+"freq_x1",this.freq_x1+"");
properties.setProperty(prefix+"freq_y1",this.freq_y1+"");
properties.setProperty(prefix+"phase1",this.phase1+"");
properties.setProperty(prefix+"freq_x2",this.freq_x2+"");
properties.setProperty(prefix+"freq_y2",this.freq_y2+"");
properties.setProperty(prefix+"phase2",this.phase2+"");
properties.setProperty(prefix+"subdiv",this.subdiv+"");
properties.setProperty(prefix+"fill",this.fill+"");
properties.setProperty(prefix+"center_for_g2",this.center_for_g2+"");
properties.setProperty(prefix+"bPatternSigma",this.bPatternSigma+"");
properties.setProperty(prefix+"barraySigma",this.barraySigma+"");
properties.setProperty(prefix+"smallestSubPix",this.smallestSubPix+"");
properties.setProperty(prefix+"bitmapNonuniforityThreshold",this.bitmapNonuniforityThreshold+"");
properties.setProperty(prefix+"offsetX",this.offsetX+"");
properties.setProperty(prefix+"offsetY",this.offsetY+"");
}
public void getProperties(String prefix,Properties properties){
if (properties.getProperty(prefix+"patternSize")!=null) this.patternSize=Integer.parseInt(properties.getProperty(prefix+"patternSize"));
if (properties.getProperty(prefix+"pattern_type")!=null) this.pattern_type=Integer.parseInt(properties.getProperty(prefix+"pattern_type"));
if (properties.getProperty(prefix+"pattern_modifier")!=null) this.pattern_modifier=Double.parseDouble(properties.getProperty(prefix+"pattern_modifier"));
if (properties.getProperty(prefix+"freq_x1")!=null) this.freq_x1=Double.parseDouble(properties.getProperty(prefix+"freq_x1"));
if (properties.getProperty(prefix+"freq_y1")!=null) this.freq_y1=Double.parseDouble(properties.getProperty(prefix+"freq_y1"));
if (properties.getProperty(prefix+"phase1")!=null) this.phase1=Double.parseDouble(properties.getProperty(prefix+"phase1"));
if (properties.getProperty(prefix+"freq_x2")!=null) this.freq_x2=Double.parseDouble(properties.getProperty(prefix+"freq_x2"));
if (properties.getProperty(prefix+"freq_y2")!=null) this.freq_y2=Double.parseDouble(properties.getProperty(prefix+"freq_y2"));
if (properties.getProperty(prefix+"phase2")!=null) this.phase2=Double.parseDouble(properties.getProperty(prefix+"phase2"));
if (properties.getProperty(prefix+"subdiv")!=null) this.subdiv=Integer.parseInt(properties.getProperty(prefix+"subdiv"));
if (properties.getProperty(prefix+"fill")!=null) this.fill=Double.parseDouble(properties.getProperty(prefix+"fill"));
if (properties.getProperty(prefix+"center_for_g2")!=null) this.center_for_g2=Boolean.parseBoolean(properties.getProperty(prefix+"center_for_g2"));
if (properties.getProperty(prefix+"bPatternSigma")!=null) this.bPatternSigma=Double.parseDouble(properties.getProperty(prefix+"bPatternSigma"));
if (properties.getProperty(prefix+"barraySigma")!=null) this.barraySigma=Double.parseDouble(properties.getProperty(prefix+"barraySigma"));
if (properties.getProperty(prefix+"smallestSubPix")!=null) this.smallestSubPix=Double.parseDouble(properties.getProperty(prefix+"smallestSubPix"));
if (properties.getProperty(prefix+"bitmapNonuniforityThreshold")!=null) this.bitmapNonuniforityThreshold=Double.parseDouble(properties.getProperty(prefix+"bitmapNonuniforityThreshold"));
if (properties.getProperty(prefix+"offsetX")!=null) this.offsetX=Double.parseDouble(properties.getProperty(prefix+"offsetX"));
if (properties.getProperty(prefix+"offsetY")!=null) this.offsetY=Double.parseDouble(properties.getProperty(prefix+"offsetY"));
}
}
}
//
// WindowTools.java
//
/*
LOCI Plugins for ImageJ: a collection of ImageJ plugins including the
Bio-Formats Importer, Bio-Formats Exporter, Bio-Formats Macro Extensions,
Data Browser and Stack Slicer. Copyright (C) 2005-@year@ Added plugin by
This program 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 2 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, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
//package loci.plugins.util;
import ij.IJ;
import ij.ImageJ;
import ij.gui.GenericDialog;
import java.awt.BorderLayout;
import java.awt.Checkbox;
import java.awt.Choice;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Panel;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.ScrollPane;
import java.awt.TextField;
import java.awt.Toolkit;
import java.awt.Window;
import java.util.List;
//import java.util.StringTokenizer;
//import loci.common.DebugTools;
//import loci.plugins.BF;
/**
* Utility methods for managing ImageJ dialogs and windows.
*
* <dl><dt><b>Source code:</b></dt>
* <dd><a href="http://trac.openmicroscopy.org.uk/ome/browser/bioformats.git/components/loci-plugins/src/loci/plugins/util/WindowTools.java">Trac</a>,
* <a href="http://git.openmicroscopy.org/?p=bioformats.git;a=blob;f=components/loci-plugins/src/loci/plugins/util/WindowTools.java;hb=HEAD">Gitweb</a></dd></dl>
*/
public final class WindowTools {
// -- Constructor --
private WindowTools() { }
// -- Utility methods --
/** Adds AWT scroll bars to the given container. */
@SuppressWarnings("serial")
public static void addScrollBars(Container pane) {
GridBagLayout layout = (GridBagLayout) pane.getLayout();
// extract components
int count = pane.getComponentCount();
Component[] c = new Component[count];
GridBagConstraints[] gbc = new GridBagConstraints[count];
for (int i=0; i<count; i++) {
c[i] = pane.getComponent(i);
gbc[i] = layout.getConstraints(c[i]);
}
// clear components
pane.removeAll();
layout.invalidateLayout(pane);
// create new container panel
Panel newPane = new Panel();
GridBagLayout newLayout = new GridBagLayout();
newPane.setLayout(newLayout);
for (int i=0; i<count; i++) {
newLayout.setConstraints(c[i], gbc[i]);
newPane.add(c[i]);
}
// HACK - get preferred size for container panel
// NB: don't know a better way:
// - newPane.getPreferredSize() doesn't work
// - newLayout.preferredLayoutSize(newPane) doesn't work
Frame f = new Frame();
f.setLayout(new BorderLayout());
f.add(newPane, BorderLayout.CENTER);
f.pack();
final Dimension size = newPane.getSize();
f.remove(newPane);
f.dispose();
// compute best size for scrollable viewport
size.width += 25;
size.height += 15;
Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
int maxWidth = 7 * screen.width / 8;
int maxHeight = 3 * screen.height / 4;
if (size.width > maxWidth) size.width = maxWidth;
if (size.height > maxHeight) size.height = maxHeight;
// create scroll pane
ScrollPane scroll = new ScrollPane() {
public Dimension getPreferredSize() {
return size;
}
};
scroll.add(newPane);
// add scroll pane to original container
GridBagConstraints constraints = new GridBagConstraints();
constraints.gridwidth = GridBagConstraints.REMAINDER;
constraints.fill = GridBagConstraints.BOTH;
constraints.weightx = 1.0;
constraints.weighty = 1.0;
layout.setConstraints(scroll, constraints);
pane.add(scroll);
}
/**
* Places the given window at a nice location on screen, either centered
* below the ImageJ window if there is one, or else centered on screen.
*/
public static void placeWindow(Window w) {
Dimension size = w.getSize();
Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
ImageJ ij = IJ.getInstance();
Point p = new Point();
if (ij == null) {
// center config window on screen
p.x = (screen.width - size.width) / 2;
p.y = (screen.height - size.height) / 2;
}
else {
// place config window below ImageJ window
Rectangle ijBounds = ij.getBounds();
p.x = ijBounds.x + (ijBounds.width - size.width) / 2;
p.y = ijBounds.y + ijBounds.height + 5;
}
// nudge config window away from screen edges
final int pad = 10;
if (p.x < pad) p.x = pad;
else if (p.x + size.width + pad > screen.width) {
p.x = screen.width - size.width - pad;
}
if (p.y < pad) p.y = pad;
else if (p.y + size.height + pad > screen.height) {
p.y = screen.height - size.height - pad;
}
w.setLocation(p);
}
/** Reports the given exception with stack trace in an ImageJ error dialog. */
public static void reportException(Throwable t) {
reportException(t, false, null);
}
/** Reports the given exception with stack trace in an ImageJ error dialog. */
public static void reportException(Throwable t, boolean quiet) {
reportException(t, quiet, null);
}
/** Reports the given exception with stack trace in an ImageJ error dialog. */
public static void reportException(Throwable t, boolean quiet, String msg) {
if (quiet) return;
// BF.status(quiet, "");
if (t != null) {
// String s = DebugTools.getStackTrace(t);
// StringTokenizer st = new StringTokenizer(s, "\n\r");
// while (st.hasMoreTokens()) IJ.log(st.nextToken());
}
if (msg != null) IJ.error("Bio-Formats Importer", msg);
}
@SuppressWarnings("unchecked")
public static List<TextField> getNumericFields(GenericDialog gd) {
return gd.getNumericFields();
}
@SuppressWarnings("unchecked")
public static List<Checkbox> getCheckboxes(GenericDialog gd) {
return gd.getCheckboxes();
}
@SuppressWarnings("unchecked")
public static List<Choice> getChoices(GenericDialog gd) {
return gd.getChoices();
}
}
This source diff could not be displayed because it is too large. You can view the blob instead.
/**
** -----------------------------------------------------------------------------**
** deBayerScissors.java
**
** Frequency-domain de-mosoaic filters generation
**
**
** Copyright (C) 2010 Elphel, Inc.
**
** -----------------------------------------------------------------------------**
**
** focus_tuning.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/>.
** -----------------------------------------------------------------------------**
**
*/
import ij.process.*;
import ij.plugin.filter.GaussianBlur;
import java.util.HashSet;
public class deBayerScissors {
private PolarSpectrums pol_instace=null;
private double [][][] lopass=null;
private int size;
private double lastMidEnergy; // last midrange spectral energy
private showDoubleFloatArrays SDFA_instance; // just for debugging?
private DoubleFHT fht_instance;
public double getMidEnergy() {return lastMidEnergy; } // instead of the DOUBLE_DEBUG_RESULT
public deBayerScissors(
int isize, // size of the square array, centar is at size/2, size/2, only top half+line will be used
double polarStep, // maximal step in pixels on the maxRadius for 1 angular step (i.e. 0.5)
double debayer_width_green, // result green mask mpy by scaled default (diamond)
double debayer_width_redblue, // result red/blue mask mpy by scaled default (square)
double debayer_width_redblue_main, // green mask when applied to red/blue, main (center)
double debayer_width_redblue_clones){// green mask when applied to red/blue, clones
size=isize;
fht_instance= new DoubleFHT();
SDFA_instance= new showDoubleFloatArrays();
pol_instace=new PolarSpectrums(size, // size of the square array, centar is at size/2, size/2, only top half+line will be used
Math.PI,
size/2-2, // width of the polar array - should be <= size/2-2
polarStep, //0.5, //0.75, //2.0, //0.5, // maximal step in pixels on the maxRadius for 1 angular step (i.e. 0.5)
4);// angular symmetry - 0- none,1 - pi corresponds to integer, 2 - pi/2 corresponds to integer, n - pi/n corresponds to integer angular step
lopass= createAliasFilters (debayer_width_green, // result green mask mpy by scaled default (diamond)
debayer_width_redblue, // result red/blue mask mpy by scaled default (square)
debayer_width_redblue_main, // green mask when applied to red/blue, main (center)
debayer_width_redblue_clones, // green mask when applied to red/blue, clones
size, // side of the square
4); // should be 4 now
}
/** returns 2 masks (0:0 in the top left corner, match fht) [0] - for greens, [1] - for red/blue */
/** Possible improvements: - 1 make the initial green mask (or actually "fan"-like image) to have sharper ends.
2. detect periodic (line of spots) on the spectrum aplitudes (corresponds to thin lines) and use this
info to confirm this area to belong to the main spectrum */
public double [][] aliasScissors(double [] green_fht, // fht array for green, will be masked in-place
double debayer_threshold, // no high frequencies - use default uniform filter
double debayer_gamma, // power function applied to the amplitudes before generating spectral masks
double debayer_bonus, // scale far pixels as (1.0+bonus*r/rmax)
double mainToAlias,// relative main/alias amplitudes to enable pixels (i.e. 0.5 means that if alias is >0.5*main, the pixel will be masked out)
double debayer_mask_blur, // for both masks sigma for Gaussian blur of the binary masks (<0 -do not use "scissors")
boolean debayer_use_scissors, // use "scissors", if false - just apply "diamond" ands "square" with DEBAYER_WIDTH_GREEN and DEBAYER_WIDTH_REDBLUE
int this_debug){ // internal debug level
int length=green_fht.length;
int size=(int) Math.sqrt(length);
double [] green_mask;
double [] red_blue_mask;
double [] green_amp=fht_instance.calculateAmplitude(green_fht);
int i,j;
/**normalize amplitudes, apply gamma */
double dmax=0.0;
for (i=0;i<green_amp.length;i++) if (green_amp[i]>dmax) dmax=green_amp[i];
dmax=1.0/dmax;
for (i=0;i<green_amp.length;i++) green_amp[i]= Math.pow(green_amp[i]*dmax,debayer_gamma);
if (this_debug>2) SDFA_instance.showArrays(green_amp, "DT-gam"); // only top half+1 will be used
double midRangeSpectral=pol_instace.maxAmpInRing (green_amp);
boolean useFancyDebayer=(midRangeSpectral>=debayer_threshold);
lastMidEnergy= midRangeSpectral; // for optional monitoring outside of this class
if (useFancyDebayer && debayer_use_scissors) { /** calculate and apply "scissors" masks */
green_mask= calcGreensAliasMaskRays (green_amp, // normalized amplitude spectrum, (0,0) in the center
pol_instace, // initialized instance
debayer_bonus, // hack - here it is "bonus"
this_debug);//
if (this_debug>3) SDFA_instance.showArrays(green_mask, "G-raw");
if (debayer_mask_blur>0) {
blurDouble(green_mask, size, debayer_mask_blur, debayer_mask_blur, 0.01);
if (this_debug>3) SDFA_instance.showArrays(green_mask, "G-blurred");
}
double [] green_mask_for_redblue_main= green_mask.clone();
double [] green_mask_for_redblue_clones=green_mask.clone();
for (i=0;i<green_mask.length;i++) {
green_mask_for_redblue_main[i]*= lopass[2][0][i];
green_mask_for_redblue_clones[i]*=lopass[2][1][i];
}
if (this_debug>2) {
SDFA_instance.showArrays(green_mask_for_redblue_main, "MAIN");
SDFA_instance.showArrays(green_mask_for_redblue_main, "CLONES");
}
/** Maybe here we need to unmasked (wide bandwidth) green_amp? */
red_blue_mask= calcRedBlueAliasMaskRays (green_amp, // both halves are needed ??
green_mask_for_redblue_main, // may be null if amp_pixels is already masked
green_mask_for_redblue_clones,
pol_instace, // initialized instance (if null - skip rays processing)
mainToAlias,// relative main/alias amplitudes to enable lixels (i.e. 0.5 means that if alias is >0.5*main, the pixel will be masked out)
debayer_bonus, // scale far pixels as (1.0+bonus*r/rmax)
this_debug);// relative main/alias amplitudes to enable lixels (i.e. 0.5 means that if alias is >0.5*main, the pixel will be masked out)
/** add double mainToAlias){// relative main/alias amplitudes to enable pixels (i.e. 0.5 means that if alias is >0.5*main, the pixel will be masked out) */
if (this_debug>3) SDFA_instance.showArrays(red_blue_mask, "RB-raw");
if (debayer_mask_blur>0) {
blurDouble(red_blue_mask, size,debayer_mask_blur, debayer_mask_blur, 0.01);
if (this_debug>3) SDFA_instance.showArrays(red_blue_mask, "RB-blurred");
}
for (i=0;i<red_blue_mask.length;i++) red_blue_mask[i]*=lopass[1][1][i]; // scaled, red-blue - was red_blue_lopass[i];
} else { // debayer_mask_blur<0 : use default masks
green_mask=lopass[1][0].clone(); //green_lopass.clone(); variable (wide) filter here)
red_blue_mask=lopass[1][1].clone(); //red_blue_lopass.clone();
if (!useFancyDebayer) for (i=0;i<green_mask.length;i++) { // no high-frequency componnets detected - reduce noise by extra (narrow) filtering
green_mask[i]*= lopass[0][0][i]; // *= green_lopass[i];
red_blue_mask[i]*=lopass[0][1][i]; // *=red_blue_lopass[i];
}
}
/** Swap quadrants in the masks to match FHT arrays (0:0 in the top left corner) */
fht_instance.swapQuadrants(green_mask);
fht_instance.swapQuadrants(red_blue_mask);
/** return both masks */
double [][] result =new double [2][];
result[0]= green_mask;
result[1]= red_blue_mask;
// if (this_debug>3) SDFA_instance.showArrays(result, "before_norm_masks");
/** normalize masks to have exactly 1.0 at 0:0 - it can be reduced by blurring */
for (i=0;i<result.length;i++) {
dmax=1.0/result[i][0];
for (j=0;j<result[i].length;j++) result[i][j]*=dmax;
}
// if (this_debug>3) SDFA_instance.showArrays(result, "masks");
return result;
}
public double [] calcRedBlueAliasMaskRays (double [] green_amp, // both halves are needed ??
double [] green_mask, // may be null if amp_pixels is already masked
double [] green_mask_clones, // mask (more inclusive than just green_mask) to be used with clones
PolarSpectrums pol_instace, // initialized instance (if null - skip rays processing)
double mainToAlias,// relative main/alias amplitudes to enable lixels (i.e. 0.5 means that if alias is >0.5*main, the pixel will be masked out)
double bonus, // scale far pixels as (1.0+bonus*r/rmax)
int this_debug){// relative main/alias amplitudes to enable lixels (i.e. 0.5 means that if alias is >0.5*main, the pixel will be masked out)
int length=green_amp.length;
int size = (int) Math.sqrt(length);
int hsize=size/2;
int subpixel=4; // hardwired - when changing it will need to change alias maps
int aliasX=size/subpixel;
int i,j,index,index_back,x,y;
double [] amp= green_amp.clone();
double [] amp_clones=green_amp.clone();
if (green_mask!=null) for (i=0;i<amp.length;i++) amp[i]*=green_mask[i];
if (green_mask_clones!=null) for (i=0;i<amp_clones.length;i++) amp_clones[i]*=green_mask_clones[i];
double [] mask= new double [length];
for (i=0;i<length;i++) mask[i]=0.0;
/** Combine into mask by comparing pixels[] from the zero and 7 aliases */
double d;
int nAlias;
int [][] aliasMapRedBlue={{-2,-2},{-2,-1},{-2,0},{-2,1},
{-1,-2},{-1,-1},{-1,0},{-1,1},
{ 0,-2},{ 0,-1}, { 0,1},
{ 1,-2},{ 1,-1},{ 1,0},{ 1,1}};
/* int [][] aliasMap={{-1,-1},{-1,0},{-1,1},
{ 0,-1}, { 0,1},
{ 1,-1},{ 1,0},{ 1,1}};*/
/** First step - mask out all the pixels where at least one of the alias amplitude is above the main one */
if (this_debug>2) SDFA_instance.showArrays(amp.clone(), "amp");
if (this_debug>2) SDFA_instance.showArrays(amp_clones, "amp_clones");
for (i=0;i<=hsize;i++) for (j=0;j<size;j++) {
index=i*size+j;
index_back=((size-i) % size) * size + ((size-j) % size);
d=amp[index]*mainToAlias;
if (d>0.0) {
mask[index]=1.0;
mask[index_back]=1.0;
// isGreater=true;
for(nAlias=0;nAlias<aliasMapRedBlue.length; nAlias++) {
y=(i-aliasX*aliasMapRedBlue[nAlias][0]+size) % size;
x=(j-aliasX*aliasMapRedBlue[nAlias][1]+size) % size;
if (y>hsize) {
y=size-y;
x=(size-x)%size;
}
if (amp_clones[y*size+x]>d) {
mask[index]=-1.0;
mask[index_back]=-1.0;
break;
}
}
}
}
if (this_debug>2) SDFA_instance.showArrays(mask, "mask");
if (pol_instace==null) return mask;
/** Now apply mask to amplitudes and use ray processing (same as with greens)*/
for (i=0;i<amp.length;i++) amp[i]*=mask[i];
if (this_debug>2) SDFA_instance.showArrays(amp, "amp-mask");
double [] polar_amp=pol_instace.cartesianToPolar(amp);
if (this_debug>2) SDFA_instance.showArrays(polar_amp.clone(),pol_instace.getWidth(),pol_instace.getHeight(), "RB-polar-amp");
double k= bonus/pol_instace.getWidth();
for (i=0;i<pol_instace.getHeight();i++) for (j=0;j<pol_instace.getWidth();j++) polar_amp[i*pol_instace.getWidth()+j]*=1.0+k*j;
double [] polar_mask_pixels=pol_instace.genPolarRedBlueMask(polar_amp,0); // 0 - just 1.0/0.0, 1 - "analog"
double [] cart_mask_pixels= pol_instace.polarToCartesian (polar_mask_pixels,size,0.0);
if (this_debug>2) {
SDFA_instance.showArrays(polar_amp, pol_instace.getWidth(),pol_instace.getHeight(), "RB-amp-bonus");
SDFA_instance.showArrays(polar_mask_pixels,pol_instace.getWidth(),pol_instace.getHeight(), "pRBm");
SDFA_instance.showArrays(cart_mask_pixels,size,size, "cRBm");
}
if (this_debug>2) {
double [] polar_mask_pixels1=pol_instace.genPolarRedBlueMask(polar_amp,1);
double [] cart_mask_pixels1= pol_instace.polarToCartesian (polar_mask_pixels1,size,0.0);
SDFA_instance.showArrays(polar_mask_pixels1,pol_instace.getWidth(),pol_instace.getHeight(), "pRBm1");
SDFA_instance.showArrays(cart_mask_pixels1,size,size, "cRBm1");
}
return cart_mask_pixels;
}
public double [] calcGreensAliasMaskRays (double [] amp_pixels, // normalized amplitude spectrum, (0,0) in the center
PolarSpectrums pol_instace, // initialized instance
double bonus, // scale far pixels as (1.0+bonus*r/rmax)
int this_debug){// relative main/alias amplitudes to enable lixels (i.e. 0.5 means that if alias is >0.5*main, the pixel will be masked out)
int length=amp_pixels.length;
int size = (int) Math.sqrt(length);
double [] polar_amp_pixels=pol_instace.cartesianToPolar(amp_pixels);
if (this_debug>2) SDFA_instance.showArrays(polar_amp_pixels.clone(),pol_instace.getWidth(),pol_instace.getHeight(), "polar-amp");
double k= bonus/pol_instace.getWidth();
for (int i=0;i<pol_instace.getHeight();i++) for (int j=0;j<pol_instace.getWidth();j++) polar_amp_pixels[i*pol_instace.getWidth()+j]*=1.0+k*j;
double [] polar_green_mask_pixels=pol_instace.genPolarGreenMask(polar_amp_pixels,0); // 0 - just 1.0/0.0, 1 - "analog"
double [] cart_green_mask_pixels= pol_instace.polarToCartesian (polar_green_mask_pixels,size,0.0);
if (this_debug>2) {
SDFA_instance.showArrays(polar_amp_pixels, pol_instace.getWidth(),pol_instace.getHeight(), "amp-bonus");
SDFA_instance.showArrays(polar_green_mask_pixels,pol_instace.getWidth(),pol_instace.getHeight(), "pgm");
SDFA_instance.showArrays(cart_green_mask_pixels,size,size, "cgm");
}
if (this_debug>2) {
double [] polar_green_mask_pixels1=pol_instace.genPolarGreenMask(polar_amp_pixels,1);
double [] cart_green_mask_pixels1= pol_instace.polarToCartesian (polar_green_mask_pixels1,size,0.0);
SDFA_instance.showArrays(polar_green_mask_pixels1,pol_instace.getWidth(),pol_instace.getHeight(), "PGM1");
SDFA_instance.showArrays(cart_green_mask_pixels1,size,size, "CGM1");
}
return cart_green_mask_pixels;
}
double [][][] createAliasFilters (double debayer_width_green, // result green mask mpy by scaled default (diamond)
double debayer_width_redblue, // result red/blue mask mpy by scaled default (square)
double debayer_width_redblue_main, // green mask when applied to red/blue, main (center), square
double debayer_width_redblue_clones, // green mask when applied to red/blue, clones , square
int size, // side of the square
int subpixel){ // should be 4 now
int i;
double [] cosMask= createCosMask (size, subpixel); // oversampling
double [][] [] lopass =new double [3][2][];
lopass[0][0]=new double [size*size];
for (i=0;i<lopass[0][0].length;i++) lopass[0][0][i]=1.0;
lopass[0][1]=lopass[0][0].clone();
lopass[1][0]=lopass[0][0].clone();
lopass[1][1]=lopass[0][0].clone();
lopass[2][0]=lopass[0][0].clone();
lopass[2][1]=lopass[0][0].clone();
maskBayerAliases (lopass[0][0], // FHT array to be filtered
cosMask, // cosine mask array
true); // this fht array is for the checkerboard greens
maskBayerAliases (lopass[0][1], // FHT array to be filtered
cosMask, // cosine mask array
false); // this fht array is for the checkerboard greens
cosMask= createCosMask ((int) Math.round(size*debayer_width_green), subpixel); // oversampling
maskBayerAliases (lopass[1][0], // FHT array to be filtered
cosMask, // cosine mask array
true); // this fht array is for the checkerboard greens
cosMask= createCosMask ((int) Math.round(size*debayer_width_redblue), subpixel); // oversampling
maskBayerAliases (lopass[1][1], // FHT array to be filtered
cosMask, // cosine mask array
false); // this fht array is for the checkerboard greens
cosMask= createCosMask ((int) Math.round(size*debayer_width_redblue_main), subpixel); // oversampling
maskBayerAliases (lopass[2][0], // FHT array to be filtered
cosMask, // cosine mask array
false); // this fht array is for the checkerboard greens
cosMask= createCosMask ((int) Math.round(size*debayer_width_redblue_clones), subpixel); // oversampling
maskBayerAliases (lopass[2][1], // FHT array to be filtered
cosMask, // cosine mask array
false); // this fht array is for the checkerboard greens
fht_instance.swapQuadrants(lopass[0][0]);
fht_instance.swapQuadrants(lopass[0][1]);
fht_instance.swapQuadrants(lopass[1][0]);
fht_instance.swapQuadrants(lopass[1][1]);
fht_instance.swapQuadrants(lopass[2][0]);
fht_instance.swapQuadrants(lopass[2][1]);
return lopass;
}
void maskBayerAliases (double [] fht, // FHT array to be filtered
double [] cosMask, // cosine mask array
boolean isChecker) { // this fht array is for the checkerboard greens
int size= (int) Math.sqrt(fht.length);
int iy,ix, ix1,iy1;
int tsize= (cosMask.length-1)/(isChecker?1:2);
int index=0;
int hsizeM1=(size/2)-1;
if (isChecker) {
for (iy=0;iy<size;iy++) {
iy1=(iy+hsizeM1)%size -hsizeM1;
for (ix=0;ix<size;ix++) {
ix1=(ix+hsizeM1)%size -hsizeM1;
if (((ix1+iy1)>-tsize) &&
((ix1-iy1)>-tsize) &&
((ix1+iy1)<=tsize) &&
((ix1-iy1)<=tsize)) fht[index++]*=cosMask[Math.abs(ix1+iy1)]*cosMask[Math.abs(ix1-iy1)];
else fht[index++]=0.0;
}
}
} else {
for (iy=0;iy<size;iy++) {
iy1=(iy+hsizeM1)%size -hsizeM1;
for (ix=0;ix<size;ix++) {
ix1=(ix+hsizeM1)%size -hsizeM1;
if ((iy1>-tsize) && (iy1<=tsize) && (ix1>-tsize) && (ix1<=tsize)) fht[index++]*=cosMask[2*Math.abs(iy1)]*cosMask[2*Math.abs(ix1)];
else fht[index++]=0.0;
}
}
}
}
double [] createCosMask (int fftsize, // FHT array to be filtered - just length is used
int subdiv // oversampling
) { // this fht array is for the checkerboard greens
int size= 2*fftsize/subdiv;
double [] cosMask=new double [size+1];
for (int i=0;i<=size;i++) cosMask[i]=0.5*(1.0+Math.cos(i*Math.PI/size));
return cosMask;
}
// temporary using float implementation in ImageJ - re-write to directly use double [] arrays
public void blurDouble(double[] pixels,
int width,
double sigmaX,
double sigmaY,
double precision) {
// public void blurFloat(red_blue_mask, DEBAYER_MASK_BLUR, DEBAYER_MASK_BLUR, 0.01);
int i;
int height = pixels.length/width;
float [] fpixels=new float [pixels.length];
for (i=0;i<pixels.length;i++) fpixels[i]= (float) pixels[i];
FloatProcessor fp = new FloatProcessor(width, height, fpixels, null);
GaussianBlur gb = new GaussianBlur();
gb.blurFloat(fp, sigmaX, sigmaY, precision);
for (i=0;i<pixels.length;i++) pixels[i]=fpixels[i];
}
/** ====================================================== */
public class PolarSpectrums {
public int radius=0;
public int iRadiusPlus1; // number of radius steps
public int iAngle;
public double aStep;
public double rStep;
public int size;
public int length;
// Make them private later, after debugging
private int [][] polar2CartesianIndices; // for each polar angle/radius (angle*iRadiusPlus1+radius) - 4 interpolation corners (0:0, dx:0, 0:dy, dx:dy), the first (0:0) being the closest to the polar point
private double [][] polar2CartesianFractions; // a pair of dx, dy for interpolations (used with ) polar2CartesianIndices[][]]
private int [][] cartesian2PolarIndices; // each per-pixel array is a list of indices in polar array pointing to this cell (may be empty)
private int [] cartesian2PolarIndex; // Cartesian->polar array index (cell closest to the center). Is it possible that cartesian2PolarIndices does not include this one?
private int [][] polarGreenMap=null ; // each element is a variable length integer array with a list of the alias indices
private int [][] polarRedBlueMap=null ; // each element is a variable length integer array with a list of the alias indices
private int [][] sameCartesian=null ; // each element is a variable length integer array with a list of indices of the other polar cells that belong (point to) the same cartesian cell
private int [] cartAmpList = null; // list of indices of the elements of the cartesian array (symmetrical around the center) so the distance is between ampRMinMax[0] and ampRMinMax[1]
private double [] ampRMinMax ={0.0,0.0};
public PolarSpectrums() { } // so "Compile and Run" will be happy
/** Convert cartesian to polar array, dimensions are set in the class constructor. Uses bi-linear interpolation */
public double [] cartesianToPolar (double [] cartPixels ) {
double [] polPixels=new double[iRadiusPlus1*(iAngle+1)];
int i;
for (i=0;i<polPixels.length;i++) {
polPixels[i]=(1-polar2CartesianFractions[i][1])*( (1-polar2CartesianFractions[i][0])*cartPixels[polar2CartesianIndices[i][0]] + polar2CartesianFractions[i][0]*cartPixels[polar2CartesianIndices[i][1]])+
polar2CartesianFractions[i][1] *( (1-polar2CartesianFractions[i][0])*cartPixels[polar2CartesianIndices[i][2]] + polar2CartesianFractions[i][0]*cartPixels[polar2CartesianIndices[i][3]]) ;
}
return polPixels;
}
public double [] polarToCartesian (double [] polPixels , int height, double undefined ) { return polarToCartesian (polPixels , height, undefined, height==size); }
public double [] polarToCartesian (double [] polPixels , double undefined) { return polarToCartesian (polPixels ,size, undefined,false); }
public double [] polarToCartesian (double [] polPixels , int height ) { return polarToCartesian (polPixels , height, Double.NaN,height==size); }
public double [] polarToCartesian (double [] polPixels ) { return polarToCartesian (polPixels , size, Double.NaN,false); }
public double [] polarToCartesian (double [] polPixels,
int height, // for partial arrays
double undefined, // use this value in the undefined areas
boolean symmHalf){ // add center-symmetrical top to the bottom(spectrums of real signals)
int length=size*height;
double [] cartPixels=new double[length];
int i,j;
int [] sameCartCell;
double d;
int l=symmHalf?((size+1)*size/2+1) :(size*height);
int l2=(size+1)*size;
for (i=0;i<l;i++) {
sameCartCell=cartesian2PolarIndices[i];
if (sameCartCell==null) {
if (cartesian2PolarIndex[i]>=0) cartPixels[i]=polPixels[cartesian2PolarIndex[i]];
else cartPixels[i]=undefined;
} else {
d=0;
for (j=0;j<sameCartCell.length;j++) d+=polPixels[sameCartCell[j]];
cartPixels[i]=d/sameCartCell.length;
}
if (symmHalf) {
j=l2-i;
if (j<length) cartPixels[j] = cartPixels[i];
}
}
return cartPixels;
}
/** Caculates maximal value of a center-symmetrical array of the amplitudes in a ring. Uses cached table of indices, recalculates if it changed */
public double maxAmpInRing ( double []amps ){ return maxAmpInRing (amps,size*0.118,size*0.236);} // ~=1/3* (Math.sqrt(2)/4), 2/3* (Math.sqrt(2)/4) (center 1/3 ring between center and the closest alias for greens)
public double maxAmpInRing ( double []amps,
double rMin,
double rMax
){
int i,j,x,y;
if ((cartAmpList==null) || (rMin!=ampRMinMax[0]) || (rMax!=ampRMinMax[1])) {
ampRMinMax[0]=rMin;
ampRMinMax[1]=rMax;
double rMin2=rMin*rMin;
double rMax2=rMax*rMax;
double r2;
// pass 1 - count number of elements
int numMembers=0;
for (i=0;i<=size/2;i++) {
y=i-(size/2);
for (j=0;j<size;j++) {
x=j-(size/2);
r2=x*x+y*y;
if ((r2>=rMin2) && (r2<=rMax2)) numMembers++;
}
}
cartAmpList=new int [numMembers];
// pass 2 - count number of elements fill in the array
numMembers=0;
for (i=0;i<=size/2;i++) {
y=i-(size/2);
for (j=0;j<size;j++) {
x=j-(size/2);
r2=x*x+y*y;
if ((r2>=rMin2) && (r2<=rMax2)) cartAmpList[numMembers++]=i*size+j;
}
}
}
if (cartAmpList.length<1) return Double.NaN;
double max=amps[cartAmpList[0]];
for (i=1;i<cartAmpList.length;i++) if (max<amps[cartAmpList[i]]) max=amps[cartAmpList[i]];
return max;
}
/** return polar array width (== radius+1) */
public int getWidth() { return iRadiusPlus1; }
public int getHeight() { return iAngle+1; }
public double [] genPolarGreenMask(double [] polarAmps, // polar array of amplitude values, <0 - stop
int mode){ // result mode - 0: output mask as 0/1, 1 -output proportional, positive - pass, negative - rejected
return genPolarMask(polarAmps,0,mode);
}
public double [] genPolarRedBlueMask(double [] polarAmps, // polar array of amplitude values, <0 - stop
int mode){ // result mode - 0: output mask as 0/1, 1 -output proportional, positive - pass, negative - rejected
return genPolarMask(polarAmps,1,mode);
}
public double [] genPolarMask(double [] polarAmps, // polar array of amplitude values, <0 - stop
int type, // 0 - green, 1 red/blue
int mode){ // result mode - 0: output mask as 0/1, 1 -output proportional, positive - pass, negative - rejected
int [][] polarMap=(type>0)?polarRedBlueMap: polarGreenMap;
int length=iRadiusPlus1*(iAngle+1);
int [] intMap= new int[length];
int i,ia;
for (i=0;i<intMap.length;i++) intMap[i]=0;
int [] rayLength=new int[iAngle+1]; // index (radius)) of the current ray end for this angle
boolean [] rayOpen= new boolean[iAngle+1]; // this ray can grow (not blocked)
for (i=0;i<iAngle;i++) {
rayLength[i]=0;
rayOpen[i]=true;
}
int lastIndex;
int base;
int l;
double max;
int newVal;
int step=0;
int iMax=0; // index of the best ray
int index=0;
boolean good=true;
while (iMax>=0) {
step++;
/** add polar point index */
newVal=good?step:-step;
// index=iMax*iRadiusPlus1+rayLength[iMax]; // rayLength[iMax] should point to a new cell (with intMap[]==0) may ommit - set in the end of the loop and before the loop?
intMap[index]=newVal;
if (sameCartesian[index]!=null) for (i=0;i<sameCartesian[index].length;i++) intMap[sameCartesian[index][i]]=newVal;
/** add aliases of point index (as negative values) */
if ((good) &&(polarMap[index]!=null)) for (i=0;i<polarMap[index].length;i++) intMap[polarMap[index][i]]=-step;
/** update ray lengths and status */
max=-1.0;
iMax=-1;
for (ia=0;ia<=iAngle;ia++) if (rayOpen[ia]) {
base=ia*iRadiusPlus1+1; // second for this angle
l=base+rayLength[ia]; // first after the pointer
lastIndex=base+iRadiusPlus1; // first in the next row
while ((l<lastIndex) && (intMap[l]>0)) l++;
rayLength[ia]=l-base; // last "good" ( >0 and in the same row)
if ((l==lastIndex) || (intMap[l]<0) || (polarAmps[l]<0.0) ) rayOpen[ia]=false;
else {
if (polarAmps[l]>max) {
max=polarAmps[l];
iMax=ia;
}
}
}
if (iMax>=0) {
rayLength[iMax]++;
index=iMax*iRadiusPlus1+rayLength[iMax];
/** See if any of the aliases of the new point hit the positive value, then this point is prohibited (good=false). Otherwise add it with good=true */
good=true;
if (polarMap[index]!=null) for (i=0;i<polarMap[index].length;i++) {
if (intMap[polarMap[index][i]]>0) {
good=false;
break;
}
}
}
/** index is set if (iMax>=0) */
}
double [] result=new double [intMap.length];
if (mode==0) {
for (i=0;i<length;i++) result[i]=(intMap[i]>0)?1.0:0.0;
} else {
for (i=0;i<length;i++) result[i]=(intMap[i]>0)?(step-intMap[i]):-(step+intMap[i]);
}
return result;
}
public PolarSpectrums(
int isize, // size of the square array, centar is at size/2, size/2, only top half+line will be used
double fullAngle, // i.e. Math.PI, 2*Math.PI
int maxRadius, // width of the polar array - should be <= size/2-2
double outerStep, // maximal step in pixels on the maxRadius for 1 angular step (i.e. 0.5)
int symm // angular symmetry - 0- none,1 - pi corresponds to integer, 2 - pi/2 corresponds to integer, n - pi/n corresponds to integer angular step
) {
size= isize;
length=size*size;
if (maxRadius>(size/2-2)) maxRadius=(size/2-2);
radius=maxRadius;
if (symm==0) aStep=fullAngle/Math.ceil(fullAngle*radius/outerStep);
else aStep=Math.PI/symm/Math.ceil(Math.PI*radius/outerStep/symm);
iRadiusPlus1=(int) Math.ceil(radius/outerStep)+1;
rStep=radius/(iRadiusPlus1-1.0);
iAngle=(int) Math.round(fullAngle/aStep);
polar2CartesianIndices= new int [(iAngle+1)*iRadiusPlus1][4]; // [0] - closest one
polar2CartesianFractions=new double [(iAngle+1)*iRadiusPlus1][2];
int ia,ir,y,x,i,j; //,PolarIndex;
double a,r,cos,sin,dy,dx;
cartesian2PolarIndex= new int[length];
cartesian2PolarIndices=new int[length][];
@SuppressWarnings("unchecked")
HashSet <Integer> [] polarList= (HashSet <Integer> []) new HashSet[length];
for (i=0;i<length;i++) {
polarList[i]=new HashSet <Integer>(); // 16, 0.75
}
Integer PolarIndex,CartesianIndex;
for (ia=0;ia<=iAngle;ia++) {
a=ia*aStep;
cos=Math.cos(a);
sin=Math.sin(a);
for (ir=0;ir<iRadiusPlus1;ir++) {
PolarIndex=ia*iRadiusPlus1+ir;
r=ir*rStep;
dy=r*sin;
y=(int) Math.round(dy);
dy-=y;
i=size/2-y;
dx=r*cos;
x=(int) Math.round(dx);
dx-=x;
j=x+size/2;
CartesianIndex=i*size+j;
polar2CartesianIndices[PolarIndex][0]=CartesianIndex;
polarList[CartesianIndex].add(PolarIndex);
if (dx<0) {
polar2CartesianIndices[PolarIndex][1]=polar2CartesianIndices[PolarIndex][0]-1;
dx=-dx;
} else {
polar2CartesianIndices[PolarIndex][1]=polar2CartesianIndices[PolarIndex][0]+1;
}
if (dy<0) {
polar2CartesianIndices[PolarIndex][2]=polar2CartesianIndices[PolarIndex][0]+size;
polar2CartesianIndices[PolarIndex][3]=polar2CartesianIndices[PolarIndex][1]+size;
dy=-dy;
} else {
polar2CartesianIndices[PolarIndex][2]=polar2CartesianIndices[PolarIndex][0]-size;
polar2CartesianIndices[PolarIndex][3]=polar2CartesianIndices[PolarIndex][1]-size;
}
polar2CartesianFractions[PolarIndex][0]=dx;
polar2CartesianFractions[PolarIndex][1]=dy;
}
}
for (i=0;i<length;i++) {
y=size/2-(i/size);
x=(i % size)- size/2;
r=Math.sqrt(x*x+y*y);
a=Math.atan2(y,x);
if (a<0) a+=2*Math.PI;
ir =(int) Math.round(r/rStep);
ia= (int) Math.round(a/aStep);
if ((ir>=0) && (ir<iRadiusPlus1) && (ia>=0) && (ia<=iAngle)) {
cartesian2PolarIndex[i]=ia*iRadiusPlus1+ir;
if (polarList[i].size()==0) cartesian2PolarIndices[i]=null;
else {
cartesian2PolarIndices[i]=new int[polarList[i].size()];
j=0;
for (Integer val : polarList[i]) cartesian2PolarIndices[i][j++]=val;
}
} else {
cartesian2PolarIndex[i]=-1; // invalid
cartesian2PolarIndices[i]=null;
}
}
initSameCartesian();
polarGreenMap= new int [iRadiusPlus1*(iAngle+1)][];
initAliasMaps(0);
polarRedBlueMap=new int [iRadiusPlus1*(iAngle+1)][];
initAliasMaps(1);
}
public double [] testMapsLengths(int mode) { // 0 - return lengths of "sameCartesian[]", 1 - same for polarGreenMap
int [][] map= (mode==0)?sameCartesian:((mode==1)?polarGreenMap:polarRedBlueMap);
double [] result = new double [map.length];
for (int i=0; i<map.length;i++) {
result[i]=(map[i]==null)?0.0:map[i].length;
}
return result;
}
public double [] testGreenMap(int ia, int ir) {
double [] result = new double [polarGreenMap.length];
int i;
for ( i=0; i<result.length;i++) result[i]=0.0;
int index=ia*iRadiusPlus1+ir;
if (polarGreenMap[index]!=null){
for (i=0;i<polarGreenMap[index].length;i++) result [polarGreenMap[index][i]]+=1.0;
System.out.println("testGreenMap("+ia+","+ir+"): polarGreenMap["+index+"].length="+polarGreenMap[index].length);
} else {
System.out.println("testGreenMap("+ia+","+ir+"): polarGreenMap["+index+"]=null");
}
result [index]=-1.0;
return result;
}
public double [] testRedBlueMap(int ia, int ir) {
double [] result = new double [polarRedBlueMap.length];
int i;
for ( i=0; i<result.length;i++) result[i]=0.0;
int index=ia*iRadiusPlus1+ir;
if (polarRedBlueMap[index]!=null){
for (i=0;i<polarRedBlueMap[index].length;i++) result [polarRedBlueMap[index][i]]+=1.0;
System.out.println("testRedBlueMap("+ia+","+ir+"): polarRedBlueMap["+index+"].length="+polarRedBlueMap[index].length);
} else {
System.out.println("testRedBlueMap("+ia+","+ir+"): polarRedBlueMap["+index+"]=null");
}
result [index]=-1.0;
return result;
}
/** Create per-polar pixel list of aliases for green Bayer. For each polar point it shows the polar coordinates of the same (and rotated by pi) point of aliases */
/** current implementation - us cartesian (original) pixels as all/nothing, maybe it makes sense to go directly polar-polar, but then it may leave gaps */
public void initAliasMaps (int type) { // 0 - green, 1 - Red/Blue
int [][] aliasMapGreen= {{-2,-2},{-2,0}, // using rollover, so only unique aliases are needed
{-1,-1},{-1,1},
{ 0,-2},
{ 1,-1},{ 1,1}};
int [][] aliasMapRedBlue={{-2,-2},{-2,-1},{-2,0},{-2,1},
{-1,-2},{-1,-1},{-1,0},{-1,1},
{ 0,-2},{ 0,-1}, { 0,1},
{ 1,-2},{ 1,-1},{ 1,0},{ 1,1}};
int [][] aliasMap=(type>0)?aliasMapRedBlue:aliasMapGreen;
int [][] polarMap=(type>0)?polarRedBlueMap: polarGreenMap;
HashSet <Integer> aliasList=new HashSet <Integer>();
int j,ix,iy, nAlias, dirAlias,ixa,iya, index, polarIndex;
for (polarIndex=0;polarIndex<polarMap.length;polarIndex++) {
iy= size/2- (polar2CartesianIndices[polarIndex][0] / size);
ix= (polar2CartesianIndices[polarIndex][0] % size)-size/2 ;
aliasList.clear();
for (nAlias=0;nAlias<aliasMap.length;nAlias++) for (dirAlias=-1;dirAlias<2;dirAlias+=2) {
ixa=(size+ size/2+ aliasMap[nAlias][0]*size/4+ dirAlias*ix) % size;
iya=(size+ size/2- aliasMap[nAlias][1]*size/4- dirAlias*iy) % size;
index=iya*size + ixa;
if (cartesian2PolarIndices[index]==null) {
if (cartesian2PolarIndex[index]>=0) {
aliasList.add (new Integer(cartesian2PolarIndex[index]));
}
} else {
for (j=0;j<cartesian2PolarIndices[index].length;j++) {
aliasList.add (new Integer(cartesian2PolarIndices[index][j]));
}
}
}
/** convert set to int[] */
if (aliasList.size()==0) polarMap[polarIndex]=null;
else {
polarMap[polarIndex]=new int[aliasList.size()];
j=0;
for (Integer val : aliasList) polarMap[polarIndex][j++]=val;
}
}
}
public void initSameCartesian () {
int i,j, polarIndex, cartesianIndex;
sameCartesian=new int [iRadiusPlus1*(iAngle+1)][];
for (polarIndex=0;polarIndex<sameCartesian.length;polarIndex++) {
cartesianIndex=polar2CartesianIndices[polarIndex][0];
if ((cartesian2PolarIndices[cartesianIndex]==null) || (cartesian2PolarIndices[cartesianIndex].length<=1)) sameCartesian[polarIndex]=null;
else {
sameCartesian[polarIndex]=new int [cartesian2PolarIndices[cartesianIndex].length-1];
j=0;
/** copy all elements but this one - out of bounds may mean that it was not included - bug */
for (i=0;i<cartesian2PolarIndices[cartesianIndex].length;i++) if (cartesian2PolarIndices[cartesianIndex][i]!=polarIndex) sameCartesian[polarIndex][j++]=cartesian2PolarIndices[cartesianIndex][i];
}
}
}
}
}
This source diff could not be displayed because it is too large. You can view the blob instead.
import java.awt.Rectangle;
import ij.*;
import ij.gui.GenericDialog;
import ij.gui.Roi;
import ij.process.*;
public class showDoubleFloatArrays {
// defaults for color conversion
public int sliceRminusG=1;
public int sliceBminusG=2;
public double colorSpan=-.25;
public double brightnessModulate=1.0;
public double Kr=0.299; // 0.299;
public double Kb=0.114; // 0.114;
public double brightness=0.5;
/** For square arrays */
public void showArrays(double[][] pixels, String title) { showArrays(pixels, false, title);}
public void showArrays(double[][] pixels, boolean asStack, String title) {
int width=0;
int i;
if (pixels==null) return;
for (i=0;i<pixels.length;i++) if (pixels[i]!=null) {
width= (int) Math.sqrt(pixels[i].length);
break;
}
showArrays(pixels, width, width, asStack, title);
}
public void showArrays(float[][] pixels, String title) { showArrays(pixels, false, title);}
public void showArrays(float[][] pixels, boolean asStack, String title) {
int width=0;
int i;
if (pixels==null) return;
for (i=0;i<pixels.length;i++) if (pixels[i]!=null) {
width= (int) Math.sqrt(pixels[i].length);
break;
}
showArrays(pixels, width, width, asStack, title);
}
public void showArrays(double[] pixels, String title) {
int width;
if (pixels!=null) {
width=(int) Math.sqrt(pixels.length);
showArrays(pixels, width,width, title);
}
}
public void showArrays(float[] pixels, String title) {
int width;
if (pixels!=null) {
width=(int) Math.sqrt(pixels.length);
showArrays(pixels, width,width, title);
}
}
public void showArrays(double[][] pixels, int width, int height, boolean asStack, String title) {
int i,j;
if (asStack) {
float [] fpixels;
ImageStack array_stack=new ImageStack(width,height);
for (i=0;i<pixels.length;i++) if (pixels[i]!=null) {
if (pixels[i].length!=(width*height)){
System.out.println("showArrays(): pixels["+i+"].length="+pixels[i].length+" != width (+"+width+") * height("+height+")="+(width*height));
return;
}
fpixels=new float[pixels[i].length];
for (j=0;j<fpixels.length;j++) fpixels[j]=(float)pixels[i][j];
array_stack.addSlice("chn-"+i, fpixels);
if (pixels[i].length!=(width*height)){
}
}
ImagePlus imp_stack = new ImagePlus(title, array_stack);
imp_stack.getProcessor().resetMinAndMax();
imp_stack.show();
return;
} else showArrays(pixels, width, height, title);
}
public void showArrays(double[][] pixels, int width, int height, boolean asStack, String title, String [] titles) {
int i,j;
if (pixels==null) {
System.out.println("showDoubleFloatArrays.showArrays(): - pixel array is null");
}
if (asStack) {
float [] fpixels;
ImageStack array_stack=new ImageStack(width,height);
for (i=0;i<pixels.length;i++) if (pixels[i]!=null) {
fpixels=new float[pixels[i].length];
for (j=0;j<fpixels.length;j++) fpixels[j]=(float)pixels[i][j];
array_stack.addSlice(titles[i], fpixels);
}
ImagePlus imp_stack = new ImagePlus(title, array_stack);
imp_stack.getProcessor().resetMinAndMax();
imp_stack.show();
return;
} else showArrays(pixels, width, height, titles);
}
public void showArrays(float[][] pixels, int width, int height, boolean asStack, String title, String [] titles) {
int i,j;
if (asStack) {
float [] fpixels;
ImageStack array_stack=new ImageStack(width,height);
for (i=0;i<pixels.length;i++) if (pixels[i]!=null) {
fpixels=new float[pixels[i].length];
for (j=0;j<fpixels.length;j++) fpixels[j]=(float)pixels[i][j];
array_stack.addSlice(titles[i], fpixels);
}
ImagePlus imp_stack = new ImagePlus(title, array_stack);
imp_stack.getProcessor().resetMinAndMax();
imp_stack.show();
return;
} else showArrays(pixels, width, height, titles);
}
public void showArraysSparse(double[][] pixels, int width, int height, boolean asStack, String title, String [] titles) {
int i,j;
float [] fpixels;
if (asStack) {
ImageStack array_stack=new ImageStack(width,height);
for (i=0;i<titles.length;i++) {
fpixels=new float[pixels.length];
for (j=0;j<fpixels.length;j++) fpixels[j]= (pixels[j]==null)?0.0f: ((float)pixels[j][i]);
array_stack.addSlice(titles[i], fpixels);
}
ImagePlus imp_stack = new ImagePlus(title, array_stack);
imp_stack.getProcessor().resetMinAndMax();
imp_stack.show();
} else {
ImagePlus[] imp= new ImagePlus[titles.length];
ImageProcessor[] ip= new ImageProcessor[titles.length];
for (i=0;i<titles.length;i++) {
fpixels=new float[pixels.length];
for (j=0;j<fpixels.length;j++) fpixels[j]= (pixels[j]==null)?0.0f: ((float)pixels[j][i]);
ip[i]=new FloatProcessor(width,height);
ip[i].setPixels(fpixels);
ip[i].resetMinAndMax();
imp[i]= new ImagePlus(title+"_"+i, ip[i]);
imp[i].show();
}
}
}
public void showArraysSparse(float[][] pixels, int width, int height, boolean asStack, String title, String [] titles) {
int i,j;
float [] fpixels;
if (asStack) {
ImageStack array_stack=new ImageStack(width,height);
for (i=0;i<titles.length;i++) {
fpixels=new float[pixels.length];
for (j=0;j<fpixels.length;j++) fpixels[j]= (pixels[j]==null)?0.0f: ((float)pixels[j][i]);
array_stack.addSlice(titles[i], fpixels);
}
ImagePlus imp_stack = new ImagePlus(title, array_stack);
imp_stack.getProcessor().resetMinAndMax();
imp_stack.show();
} else {
ImagePlus[] imp= new ImagePlus[titles.length];
ImageProcessor[] ip= new ImageProcessor[titles.length];
for (i=0;i<titles.length;i++) {
fpixels=new float[pixels.length];
for (j=0;j<fpixels.length;j++) fpixels[j]= (pixels[j]==null)?0.0f: ((float)pixels[j][i]);
ip[i]=new FloatProcessor(width,height);
ip[i].setPixels(fpixels);
ip[i].resetMinAndMax();
imp[i]= new ImagePlus(title+"_"+i, ip[i]);
imp[i].show();
}
}
}
public ImagePlus [] makeArrays(double[][] pixels, int width, int height, String title) {
int i,j;
float [] fpixels;
ImageProcessor[] ip= new ImageProcessor[pixels.length];
ImagePlus[] imp=new ImagePlus[pixels.length];
for (i=0;i<pixels.length;i++) if (pixels[i]!=null) {
fpixels=new float[pixels[i].length];
for (j=0;j<fpixels.length;j++) fpixels[j]=(float)pixels[i][j];
ip[i]=new FloatProcessor(width,height);
ip[i].setPixels(fpixels);
ip[i].resetMinAndMax();
imp[i]= new ImagePlus(title+"_"+i, ip[i]);
} else imp[i]=null;
return imp;
}
public ImagePlus [] makeArrays(double[][] pixels, int width, int height, String [] titles) {
int i,j;
float [] fpixels;
ImageProcessor[] ip= new ImageProcessor[pixels.length];
ImagePlus[] imp=new ImagePlus[pixels.length];
for (i=0;i<pixels.length;i++) if (pixels[i]!=null) {
fpixels=new float[pixels[i].length];
for (j=0;j<fpixels.length;j++) fpixels[j]=(float)pixels[i][j];
ip[i]=new FloatProcessor(width,height);
ip[i].setPixels(fpixels);
ip[i].resetMinAndMax();
imp[i]= new ImagePlus(titles[i], ip[i]);
} else imp[i]=null;
return imp;
}
public ImagePlus [] makeArrays(float[][] pixels, int width, int height, String [] titles) {
int i,j;
float [] fpixels;
ImageProcessor[] ip= new ImageProcessor[pixels.length];
ImagePlus[] imp=new ImagePlus[pixels.length];
for (i=0;i<pixels.length;i++) if (pixels[i]!=null) {
fpixels=new float[pixels[i].length];
for (j=0;j<fpixels.length;j++) fpixels[j]=(float)pixels[i][j];
ip[i]=new FloatProcessor(width,height);
ip[i].setPixels(fpixels);
ip[i].resetMinAndMax();
imp[i]= new ImagePlus(titles[i], ip[i]);
} else imp[i]=null;
return imp;
}
public void showArrays(double[][] pixels, int width, int height, String title) {
int i;
ImagePlus[] imp=makeArrays(pixels, width, height, title);
if (imp==null) return;
for (i=0;i<imp.length;i++) if (imp[i]!=null) {
imp[i].show();
}
}
public void showArrays(double[][] pixels, int width, int height, String [] titles) {
int i;
ImagePlus[] imp=makeArrays(pixels, width, height, titles);
if (imp==null) return;
for (i=0;i<imp.length;i++) if (imp[i]!=null) {
imp[i].show();
}
}
public void showArrays(float[][] pixels, int width, int height, String [] titles) {
int i;
ImagePlus[] imp=makeArrays(pixels, width, height, titles);
if (imp==null) return;
for (i=0;i<imp.length;i++) if (imp[i]!=null) {
imp[i].show();
}
}
public ImagePlus makeArrays(double[] pixels, int width, int height, String title) {
int j;
float [] fpixels;
if (pixels!=null) {
fpixels=new float[pixels.length];
for (j=0;j<pixels.length;j++) fpixels[j]=(float)pixels[j];
ImageProcessor ip=new FloatProcessor(width,height);
ip.setPixels(fpixels);
ip.resetMinAndMax();
ImagePlus imp= new ImagePlus(title, ip);
return imp;
}
return null;
}
public ImagePlus makeArrays(int[] pixels, int width, int height, String title) {
int j;
float [] fpixels;
if (pixels!=null) {
fpixels=new float[pixels.length];
for (j=0;j<pixels.length;j++) fpixels[j]=(float)pixels[j];
ImageProcessor ip=new FloatProcessor(width,height);
ip.setPixels(fpixels);
ip.resetMinAndMax();
ImagePlus imp= new ImagePlus(title, ip);
return imp;
}
return null;
}
public ImagePlus makeArrays(boolean[] pixels, int width, int height, String title) {
int j;
float [] fpixels;
if (pixels!=null) {
fpixels=new float[pixels.length];
for (j=0;j<pixels.length;j++) fpixels[j]=pixels[j]?1.0f:0.0f;
ImageProcessor ip=new FloatProcessor(width,height);
ip.setPixels(fpixels);
ip.resetMinAndMax();
ImagePlus imp= new ImagePlus(title, ip);
return imp;
}
return null;
}
public ImagePlus makeArrays(float[] pixels, int width, int height, String title) {
int j;
float [] fpixels;
if (pixels!=null) {
fpixels=new float[pixels.length];
for (j=0;j<pixels.length;j++) fpixels[j]=(float)pixels[j];
ImageProcessor ip=new FloatProcessor(width,height);
ip.setPixels(fpixels);
ip.resetMinAndMax();
ImagePlus imp= new ImagePlus(title, ip);
return imp;
}
return null;
}
public void showArrays(double[] pixels, int width, int height, String title) {
ImagePlus imp= makeArrays(pixels, width, height, title);
if (imp!=null) imp.show();
}
public void showArrays(int[] pixels, int width, int height, String title) {
ImagePlus imp= makeArrays(pixels, width, height, title);
if (imp!=null) imp.show();
}
public void showArrays(boolean[] pixels, int width, int height, String title) {
ImagePlus imp= makeArrays(pixels, width, height, title);
if (imp!=null) imp.show();
}
public void showArrays(float[][] pixels, int width, int height, boolean asStack, String title) {
int i;
if (asStack) {
ImageStack array_stack=new ImageStack(width,height);
for (i=0;i<pixels.length;i++) if (pixels[i]!=null) array_stack.addSlice("chn-"+i, pixels[i]);
ImagePlus imp_stack = new ImagePlus(title, array_stack);
imp_stack.getProcessor().resetMinAndMax();
imp_stack.show();
return;
} else showArrays(pixels, width, height, title);
}
public void showArrays(float[][] pixels, int width, int height, String title) {
int i;
ImageProcessor[] ip= new ImageProcessor[pixels.length];
ImagePlus[] imp=new ImagePlus[pixels.length];
for (i=0;i<pixels.length;i++) if (pixels[i]!=null) {
ip[i]=new FloatProcessor(width,height);
ip[i].setPixels(pixels[i]);
ip[i].resetMinAndMax();
imp[i]= new ImagePlus(title+"_"+i, ip[i]);
imp[i].show();
}
}
public void showArrays(float[] pixels, int width, int height, String title) {
if (pixels!=null) {
ImageProcessor ip=new FloatProcessor(width,height);
ip.setPixels(pixels);
ip.resetMinAndMax();
ImagePlus imp= new ImagePlus(title, ip);
imp.show();
}
}
public ImagePlus showImageStack(ImageStack stack, String title) {
if (stack==null) return null;
ImagePlus imp_stack = new ImagePlus(title, stack);
imp_stack.getProcessor().resetMinAndMax();
imp_stack.show();
return imp_stack;
}
public void showImageStackThree(ImageStack stack, String title) {
if (stack==null) return;
ImageStack stack3=new ImageStack(stack.getWidth(),stack.getHeight());
float [] fpixels_r= (float[]) stack.getPixels(1);
float [] fpixels_g= (float[]) stack.getPixels(2);
float [] fpixels_b= (float[]) stack.getPixels(3);
stack3.addSlice("red", fpixels_r);
stack3.addSlice("green",fpixels_g);
stack3.addSlice("blue", fpixels_b);
ImagePlus imp_stack = new ImagePlus(title, stack3);
imp_stack.getProcessor().resetMinAndMax();
imp_stack.show();
}
// additional methods to show 2-d "flows" in color
/*
public int sliceRminusG=1;
public int sliceBminusG=2;
public double colorSpan=-1.0;
public double brightnessModulate=0.5;
public double Kr=0.299; // 0.299;
public double Kb=0.114; // 0.114;
*/
public ImagePlus showFlowFromSlices(ImagePlus imp){
if ((imp==null) || (imp.getStackSize()<2)) {
String msg="Source image with at least two slices is required";
IJ.showMessage("Error",msg);
throw new IllegalArgumentException (msg);
}
GenericDialog gd = new GenericDialog("Select parameters to convert 2 slices (representing a vector field) to color");
gd.addMessage("Maximal values for normalization are calculated inside the selected area (or the full image if there is no selection");
gd.addNumericField("Slice number (1.."+imp.getStackSize()+" to convert to R/G", this.sliceRminusG, 0);
gd.addNumericField("Slice number (1.."+imp.getStackSize()+" to convert to B/G", this.sliceBminusG, 0);
gd.addNumericField("Color span - if positive - absolute value of the source data to get saturated color, -1 - calculate maximum", this.colorSpan, 3);
gd.addNumericField("Modulate brightness: 1.0 - use sqrt(x^2+y^2), 0.0 - constant brightness ", this.brightnessModulate, 3);
gd.addNumericField("Color conversion coefficient Kr (default =0.299) ", this.Kr, 3);
gd.addNumericField("Color conversion coefficient Kb (default =0.114) ", this.Kb, 3);
gd.addNumericField("Brightness (0..1.0)", this.brightness, 3);
gd.showDialog();
if (gd.wasCanceled()) return null;
this.sliceRminusG= (int) gd.getNextNumber();
this.sliceBminusG= (int) gd.getNextNumber();
this.colorSpan= gd.getNextNumber();
this.brightnessModulate= gd.getNextNumber();
this.Kr= gd.getNextNumber();
this.Kb= gd.getNextNumber();
this.brightness= gd.getNextNumber();
return showFlowFromSlices(
imp,
this.sliceRminusG,
this.sliceBminusG,
this.colorSpan,
this.brightnessModulate,
this.brightness,
this.Kr,
this.Kb);
}
/**
*
* @param imp image containing at least 2 slices, rectangular selection will be used to find min/max
* @param sliceRminusG slice number to be interpreted as R-G
* @param sliceBminusG slice number to be interpreted as B-G
* @param colorSpan when positive - absolute value in slice to have full color saturation, if negative -1 - full saturation
* @param brightnessModulate when 0 - same brightness, 1.0 - equals to sqrt(slice1^2 + slice2^2), normalized
* @return RGB24 color image
*/
public ImagePlus showFlowFromSlices(
ImagePlus imp,
int sliceRminusG,
int sliceBminusG,
double colorSpan,
double brightnessModulate){
return showFlowFromSlices(
imp,
sliceRminusG,
sliceBminusG,
colorSpan,
brightnessModulate,0.5,0.299,0.114);
}
public ImagePlus showFlowFromSlices(
ImagePlus imp,
int sliceRminusG,
int sliceBminusG,
double colorSpan,
double brightnessModulate,
double brightness,
double Kr, // 0.299;
double Kb // 0.114;
){
if ( (sliceRminusG<1) || (sliceRminusG>imp.getStackSize()) ||
(sliceBminusG<1) || (sliceBminusG>imp.getStackSize())) {
String msg="Source image does not contain specified slices";
IJ.showMessage("Error",msg);
throw new IllegalArgumentException (msg);
}
Roi roi= imp.getRoi();
Rectangle selection;
if (roi==null){
selection=new Rectangle(0, 0, imp.getWidth(), imp.getHeight());
} else {
selection=roi.getBounds();
}
float [][] pixels = new float[2][];
ImageStack stack = imp.getStack();
pixels[0]= (float[]) stack.getPixels(sliceRminusG);
pixels[1]= (float[]) stack.getPixels(sliceBminusG);
double maxR=0.0; //Math.abs(pixels[0][0]);
double maxB=0.0; //Math.abs(pixels[1][0]);
double maxA=0.0;
int width=imp.getWidth();
for (int y=selection.y;y<selection.y+selection.height;y++)
for (int x=selection.x;x<selection.x+selection.width;x++) {
int index=y*width+x;
if (maxR<Math.abs(pixels[0][index])) maxR=Math.abs(pixels[0][index]);
if (maxB<Math.abs(pixels[1][index])) maxB=Math.abs(pixels[1][index]);
double a=Math.sqrt(pixels[0][index]*pixels[0][index]+pixels[1][index]*pixels[1][index]);
if (maxA<a) maxA=a;
}
double maxRB=Math.max(maxR,maxB);
double Kg=1.0-Kr-Kb;
double colorScale=1.0;
if (colorSpan>0){
colorScale=1.0/colorSpan;
} else {
colorScale=(-colorSpan)* 1.0/maxRB;
}
/**
R= Y+ Pr*2.0*(1-Kr)
B= Y+ Pb*2.0*(1-Kb)
G= Y +Pr*(- 2*Kr*(1-Kr))/Kg + Pb*(-2*Kb*(1-Kb))/Kg
*/
double KPrR= 2.0*(1-Kr);
double KPbB= 2.0*(1-Kb);
double KPrG= -2.0*Kr*(1-Kr)/Kg;
double KPbG= -2.0*Kb*(1-Kb)/Kg;
System.out.println("maxR="+maxRB+" maxB="+maxB+" maxRB="+maxRB+" maxA="+maxA);
System.out.println("KPrR="+KPrR+" KPbB="+KPbB+" KPrG="+KPrG+" KPbG="+KPbG);
int [] pixelsRGB= new int [pixels[0].length];
double [] rgb=new double[3];
int debugIndex=selection.y*width+selection.x;
for (int index=0;index<pixelsRGB.length;index++){
double a=Math.sqrt(pixels[0][index]*pixels[0][index]+pixels[1][index]*pixels[1][index])/maxA;
double Y= brightness*((1.0-brightnessModulate)+brightnessModulate*a);
double Pr=colorScale*pixels[0][index];
double Pb=colorScale*pixels[1][index];
rgb[0]= (Y+ Pr*KPrR);
rgb[1]= (Y+ Pb*KPbB);
rgb[2]= (Y+ Pr*KPrG + Pb*KPbG);
pixelsRGB[index]=0;
for (int c=0;c<3;c++) {
int d= (int) Math.round(255.0* rgb[c]);
if (d>255) d=255;
else if (d<0) d=0;
pixelsRGB[index]|= d<<(8*(2-c));
}
if (index==debugIndex){
System.out.println("a="+a+" Y="+Y+" Pr="+Pr+" Pb="+Pb);
System.out.println("rgb[0]="+rgb[0]+" rgb[1]="+rgb[1]+" rgb[2]="+rgb[2]);
System.out.println("pixelsRGB["+index+"]="+pixelsRGB[index]);
}
}
String title=imp.getTitle()+".png";
ColorProcessor cp=new ColorProcessor(imp.getWidth(),imp.getHeight());
cp.setPixels(pixelsRGB);
ImagePlus imp_color=new ImagePlus(title,cp);
return imp_color;
}
}
/**
** -----------------------------------------------------------------------------**
** target_points.java
**
** Measures focus sharpnes at orthogonal directions, differenct colors,
** Displays results for manual focusing/image plane tilting.
** NOTE: Requires special targets !
**
** Copyright (C) 2010 Elphel, Inc.
**
** -----------------------------------------------------------------------------**
**
** target_points.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/>.
** -----------------------------------------------------------------------------**
**
*/
import ij.*;
import ij.process.*;
import ij.gui.*;
import java.awt.*;
import java.awt.event.*;
import ij.plugin.frame.*;
import java.util.List;
import java.util.ArrayList;
import ij.text.*;
import java.lang.Integer;
public class target_points extends PlugInFrame implements ActionListener {
private static final long serialVersionUID = -3057496866952930812L;
JP46_Reader_camera jp4_instance;
// MTF_Bayer MTF_Bayer_instance;
Panel panel;
static Frame instance;
public static int DEBUG_LEVEL = 1;
public static int MASTER_DEBUG_LEVEL = 1;
public static int FFTSize=64;
public static int FFTScanStep=8;
public static int test_x=FFTSize;
public static int test_y=FFTSize;
public static int displayWidth=800;
public static int displayHeight=600;
public static float [][] input_bayer=null;
public static float [][] convolved_bayer=null;
public static float [][] normalized_convolved_bayer=null;
public static float [] target_kernel=null;
public static int [][] clusterMaps=null;
public static double [][][] targetCoordinates;
public static double targetOuterDMin =38; // minimal outer diameter of the target image , in pixels
public static double targetOuterDMax =47; // maximal outer diameter of the target image , in pixels
public static int numTargetRings = 2; // number of target black rings (notg counting center black circle)
public static int pixelsSubdivide =10; // subdivide pixels by this number (each direction) when generating targets
public static double deconvInvert = 0.1; // when FFT component is lass than this fraction of the maximal value, replace 1/z with Z
public static double filteredRadius= 7.0; //pix - search maximums after convolution in (2*filteredRadius+1) squares
public static double backgroundRadius= 15.0; //pix - consider ring area between backgroundRadius and filteredRadius as reference
public static double clusterThreshold= 1.0; //1.2; // tested with 0.8 - many extras, but filtered out
public static int clusterSize= 20; // cluster size (will be expanded/shrank before finding centroid
public static int discrAngularFreq= 2 ; // pixels on FFT image of tragets converted polar (the smaller, the less angular variations)
/// these parameters are dependent on targets, use debug mode and manula fft for 64x64 polar coordinates target areas
public static int discrRadialMinFreq= 7 ; // pixels on FFT image of targets converted polar (radial component)
public static int discrRadialMaxFreq= 9 ; // pixels on FFT image of targets converted polar (radial component)
public static double discrThreshold= 0.1; // FFT energy fraction in selecter area should be > this threshold to pass the test
public static double maxChromaticDistance= 10.0; // Maximal distance between the same target on different color copmponents
public static double[][][] targets; // For each target {averageX, averageY, num_non_zero_components},{X1,Y1,Qulaity1},...,{X4,Y4,Qulaity4}
/**
discrRadialMinFreq=(size*(2* numTargetRings +1)/targetOuterDMax)-1
discrRadialMaxFreq=(size*(2* numTargetRings +1)/targetOuterDMin)+1
*/
private ImagePlus imp_src;
public ImageProcessor ip_display;
public ImagePlus imp_display;
public ImagePlus imp_camera=null;
Plot plotResult;
public target_points() {
super("target_points");
if (IJ.versionLessThan("1.39t")) return;
if (instance!=null) {
instance.toFront();
return;
}
instance = this;
addKeyListener(IJ.getInstance());
setLayout(new FlowLayout());
panel = new Panel();
addButton("Configure");
addButton("Split Bayer");
addButton("Create Target");
addButton("Split&Convolve");
add(panel);
pack();
GUI.center(this);
setVisible(true);
initHamming(FFTSize);
// initDisplay();
jp4_instance= new JP46_Reader_camera();
}
void addButton(String label) {
Button b = new Button(label);
b.addActionListener(this);
b.addKeyListener(IJ.getInstance());
panel.add(b);
}
public void actionPerformed(ActionEvent e) {
int i,j,ir2, size;
String label = e.getActionCommand();
if (label==null) return;
if (label.equals("Configure")) {
if (showDialog()) {
initHamming(FFTSize);
}
return;
}
if (label.equals("Create Target")) {
DEBUG_LEVEL=MASTER_DEBUG_LEVEL;
target_kernel=createTargetDialog ();
return;
}
imp_src = WindowManager.getCurrentImage();
String newTitle= imp_src.getTitle();
Rectangle r=new Rectangle(imp_src.getWidth(),imp_src.getHeight());
if (label.equals("Split Bayer")) {
DEBUG_LEVEL=MASTER_DEBUG_LEVEL;
input_bayer=splitBayer (imp_src);
showBayers(input_bayer, r.width>>1, r.height>>1, newTitle);
return;
}
if (label.equals("Split&Convolve")) {
DEBUG_LEVEL=MASTER_DEBUG_LEVEL;
input_bayer=splitBayer (imp_src);
if (DEBUG_LEVEL>5) showBayers(input_bayer, r.width>>1, r.height>>1, newTitle);
if (target_kernel==null) {
IJ.showMessage("Error","Target kernel does not exist, please generate one");
return;
}
size=(int) Math.sqrt(target_kernel.length);
//target_kernel
/** Convolve Bayer slices with prepared target template */
convolved_bayer=new float[input_bayer.length][];
for (i=0; i<input_bayer.length; i++) {
IJ.showStatus("Convolving Bayer "+i);
/** Double in convolution works twice faster than float!*/
convolved_bayer[i]=doubleConvolveWithTarget(input_bayer[i], target_kernel, r.width>>1, r.height>>1, size);
}
if (DEBUG_LEVEL>2) showBayers(convolved_bayer, r.width>>1, r.height>>1, newTitle+"_"+deconvInvert);
//
// filteredRadius =(int) gd.getNextNumber();
// backgroundRadius =(int) gd.getNextNumber();
/** normalize convolved Bayer slices */
/** prepare pixel mask for the normalization (ring) */
int filtSize= (int) filteredRadius;
boolean [][] mask=new boolean[2*filtSize+1][2*filtSize+1];
for (i=0;i<(2*filtSize+1);i++) for (j=0;j<(2*filtSize+1);j++) {
ir2=(i-filtSize)*(i-filtSize) + (j-filtSize)*(j-filtSize);
mask[i][j]=(ir2>(filteredRadius*filteredRadius)) && (ir2 < (backgroundRadius*backgroundRadius));
}
// public static float [][] normalized_convolved_bayer=null;
normalized_convolved_bayer=new float[input_bayer.length][];
for (i=0; i<input_bayer.length; i++) {
IJ.showStatus("Normalizing Bayer "+i);
normalized_convolved_bayer[i]=normalizeAtRing(convolved_bayer[i], r.width>>1, r.height>>1, mask);
}
if (DEBUG_LEVEL>1) showBayers(normalized_convolved_bayer, r.width>>1, r.height>>1, newTitle+"_"+deconvInvert+"_normalized");
// public static int [][] clusterMaps=null;
//clusterThreshold
clusterMaps= new int[4][];
targetCoordinates=new double[4][][];
for (i=0; i<input_bayer.length; i++) {
IJ.showStatus("Clusterizing Bayer "+i);
// clusterMaps[i]=clusteriseTargets(normalized_convolved_bayer[i], r.width>>1, r.height>>1,clusterThreshold,clusterSize);
targetCoordinates[i]=clusteriseTargets(normalized_convolved_bayer[i], r.width>>1, r.height>>1,clusterThreshold,clusterSize);
}
float[][]clusterPixels=new float[4][input_bayer[0].length];
if (DEBUG_LEVEL>2) {
// float[][]clusterPixels=new float[4][input_bayer[0].length];
for (i=0; i<clusterPixels.length; i++) {
for (j=0; j<clusterPixels[0].length; j++) clusterPixels[i][j]=0.0f;
for (j=0;j<targetCoordinates[i].length;j++) {
clusterPixels[i][((int)(Math.round(targetCoordinates[i][j][1])*(r.width>>1))) +((int)Math.round(targetCoordinates[i][j][0]))]=1.0f;
}
}
showBayers(clusterPixels, r.width>>1, r.height>>1, newTitle+"_"+deconvInvert+"_clusters");
}
float [] rectTarget;
ImageProcessor ip_dbg;
ImagePlus imp_dbg;
double likely;
double [] likelyness;
int numGoodTargets;
double [][] goodTargets; /// x/y/above Threshold
double r0=(targetOuterDMin+targetOuterDMax)/(2* numTargetRings +1)/8; /// copmpensate for the center circle twice wider than rings
/**
discrRadialMinFreq=(size*(2* numTargetRings +1)/targetOuterDMax)-1
discrRadialMaxFreq=(size*(2* numTargetRings +1)/targetOuterDMin)+1
*/
for (i=0; i<clusterPixels.length; i++) {
likelyness= new double[targetCoordinates[i].length];
numGoodTargets=0;
for (j=0;j<targetCoordinates[i].length;j++) {
rectTarget=circle2DoubleRect (input_bayer[i], r.width>>1, r.height>>1, size,targetCoordinates[i][j][0],targetCoordinates[i][j][1], r0);
likely=likelyTarget(rectTarget, size,discrAngularFreq,discrRadialMinFreq,discrRadialMaxFreq);
likelyness[j]=likely;
if (DEBUG_LEVEL>2) {
System.out.println("Cluster="+j+" x="+targetCoordinates[i][j][0]+" y="+targetCoordinates[i][j][1]+" likely="+likely);
}
if (DEBUG_LEVEL>3) {
if (i==0) { // just to reduce debug clutter
ip_dbg=new FloatProcessor(size,size);
ip_dbg.setPixels(rectTarget);
ip_dbg.resetMinAndMax();
imp_dbg= new ImagePlus(newTitle+"_rect_"+j, ip_dbg);
imp_dbg.show();
}
}
if (likely >=discrThreshold) numGoodTargets++;
}
goodTargets=new double[numGoodTargets][3];
numGoodTargets=0;
for (j=0;j<targetCoordinates[i].length;j++) {
if (likelyness[j]>=discrThreshold) {
goodTargets[numGoodTargets][2]=likelyness[j]/discrThreshold;
goodTargets[numGoodTargets][0]=2*targetCoordinates[i][j][0]+(i&1); // convert to full image, use Bayer shift
goodTargets[numGoodTargets][1]=2*targetCoordinates[i][j][1]+((i>>1)&1); // convert to full image, use Bayer shift
numGoodTargets++;
}
}
targetCoordinates[i]=goodTargets;
}
if (DEBUG_LEVEL>1) {
for (i=0; i<clusterPixels.length; i++) {
for (j=0; j<clusterPixels[0].length; j++) clusterPixels[i][j]=0.0f;
for (j=0;j<targetCoordinates[i].length;j++) {
clusterPixels[i][((int)(Math.round((targetCoordinates[i][j][1] - ((i>>1)&1))/2) *(r.width>>1))) +
((int)(Math.round((targetCoordinates[i][j][0] - ( i &1))/2)))]=1.0f;
System.out.println("Bayer="+i+" Target="+(j+1)+" x="+targetCoordinates[i][j][0]+" y="+targetCoordinates[i][j][1]+" quality="+targetCoordinates[i][j][2]);
}
}
showBayers(clusterPixels, r.width>>1, r.height>>1, newTitle+"_"+deconvInvert+"_clusters");
}
/** TODO: Verify that all Bayer components have the same targets (build composite table) */
targets= combineTargets(targetCoordinates, maxChromaticDistance);
showTargetsLocationTable(targets, newTitle, 2, (DEBUG_LEVEL>1));
return;
}
}
/** Combine target locations from 4 Bayer components */
double [][][] combineTargets(double[][][] targetCoord, ///[bayer][number][x,y,q>1]
double maxDistance) { /// maximal distance between the same target in different Bayer components
int [][] targetNumbers=new int[targetCoord.length][];
int i,i1,j,k,n,l,maxLen, bNum, numTargets;
double d2=maxDistance*maxDistance;
double [][][] targets;
double avX,avY;
maxLen=0;
bNum=-1;
for (i=0;i<targetCoord.length;i++) {
l=targetCoord[i].length;
targetNumbers[i]=new int [l];
for (j=0;j<l;j++) targetNumbers[i][j]=0;
if (maxLen<l){
maxLen=l;
bNum=i;
}
}
/** assign target number according to the component that has most of the targets (does not mean others do not have that this one is missing */
for (j=0;j<maxLen;j++) targetNumbers[bNum][j]=j+1;
numTargets=maxLen; // may increase later
/** compare all other color components with the coordinates in the seslected one (not too many to bother with good guess) */
for (i=0;i<targetNumbers.length;i++) if (i!=bNum) {
for (j=0;j<targetNumbers[i].length;j++) if (targetNumbers[i][j]==0){
for (k=0;k<targetNumbers[bNum].length;k++) {
if (((targetCoord[bNum][k][0]-targetCoord[i][j][0])*(targetCoord[bNum][k][0]-targetCoord[i][j][0])+
(targetCoord[bNum][k][1]-targetCoord[i][j][1])*(targetCoord[bNum][k][1]-targetCoord[i][j][1]))<=d2) {
targetNumbers[i][j]=targetNumbers[bNum][k];
break;
}
}
}
}
/** See if any targets are missing, add them */
for (i=0;i<targetNumbers.length;i++) if (i!=bNum) {
for (j=0;j<targetNumbers[i].length;j++) if (targetNumbers[i][j]==0){
numTargets++;
targetNumbers[i][j]=numTargets;
for (i1=i+1;i1<targetNumbers.length;i1++) if (i1!=bNum) {
for (k=0;k<targetNumbers[i1].length;k++) if (targetNumbers[i1][k]==0) {
targetNumbers[i1][k]=numTargets;
}
}
}
}
if (DEBUG_LEVEL>2) {
System.out.println("numTargets="+numTargets);
for (i=0;i<targetCoord.length;i++) for (j=0;j<targetCoord[i].length;j++) {
System.out.println("["+targetNumbers[i][j]+"] "+i+":"+j+" "+targetCoord[i][j][0]+","+targetCoord[i][j][1]+" :"+targetCoord[i][j][2]);
}
}
targets = new double [numTargets][targetNumbers.length+1][3];
for (i=0;i<numTargets;i++) for (j=0;j<targets[i].length;j++) for (k=0;k<3;k++) targets [i][j][k]=0.0;
for (i=0;i<targetNumbers.length;i++) for (j=0;j<targetNumbers[i].length;j++) if (targetNumbers[i][j]!=0){ // should be always non-zero
k=targetNumbers[i][j]-1;
targets[k][i+1][0]=targetCoord[i][j][0]; // x
targets[k][i+1][1]=targetCoord[i][j][1]; // y
targets[k][i+1][2]=targetCoord[i][j][2]; // quality >1.0
}
/** Calculate average values*/
for (i=0;i<numTargets;i++) {
avX=0.0;
avY=0.0;
n=0;
for (j=1;j< targets[i].length; j++) if (targets[i][j][2]>0){
avX+=targets[i][j][0];
avY+=targets[i][j][1];
n++;
}
if (n>0) {
targets[i][0][0]=avX/n;
targets[i][0][1]=avY/n;
targets[i][0][2]=n;
}
}
if (DEBUG_LEVEL>2) {
System.out.println("targets");
for (i=0;i<targets.length;i++) {
System.out.println(i+" | "+targets[i][0][0]+","+targets[i][0][1]+" :"+targets[i][0][2]+
" | "+targets[i][1][0]+","+targets[i][1][1]+" :"+targets[i][1][2]+
" | "+targets[i][2][0]+","+targets[i][2][1]+" :"+targets[i][2][2]+
" | "+targets[i][3][0]+","+targets[i][3][1]+" :"+targets[i][3][2]+
" | "+targets[i][4][0]+","+targets[i][4][1]+" :"+targets[i][4][2]);
}
}
return targets;
}
public void showTargetsLocationTable(double[][][] targets, String title, int precision, boolean showQuality) {
int i,n;
String header="#\tX\tY";
for (i=1;i<targets[0].length;i++) header+="\tdX"+i+"\tdY"+i+(showQuality?("\tQ"+i):"");
StringBuffer sb = new StringBuffer();
for (n=0;n<targets.length;n++) {
sb.append((n+1)+
"\t"+IJ.d2s(targets[n][0][0],precision)+ // Average X
"\t"+IJ.d2s(targets[n][0][1],precision)); // Average Y
for (i=1;i<targets[0].length;i++) {
if (targets[n][i][2]>0) {
sb.append( "\t"+(((targets[n][i][0]-targets[n][0][0])>0)?"+":"")+IJ.d2s(targets[n][i][0]-targets[n][0][0],precision)+ // X
"\t"+(((targets[n][i][1]-targets[n][0][1])>0)?"+":"")+IJ.d2s(targets[n][i][1]-targets[n][0][1],precision)); // Y
} else {
sb.append( "\t---\t---");
}
if (showQuality) sb.append("\t"+((targets[n][0][2]>0)?IJ.d2s(targets[n][i][2],precision):"---"));
}
sb.append( "\n");
}
new TextWindow(title+"_Target_Locations_Table", header, sb.toString(), showQuality?900:700,500);
}
/** determines if it was likely a target of concentric circles, after convertion to polar coordinates expect nearly horizontal b/w bands */
double likelyTarget(float[] pixels, // pixel array
int size, // image size (should be square for FFT
int hor, // horizontal selection area (half width)
int vertMin,
int vertMax) // vertical selection area (half height > half width for horizointal bands)
{
ImageProcessor ip;
FHT fht;
double[][][] fft;
double s1,s2,e;
int i,j;
ip=new FloatProcessor(size,size);
ip.setPixels(pixels);
fht = new FHT(ip);
// Swapping quadrants, so the center will be 0,0
fht.swapQuadrants();
// get to frequency
fht.transform();
// Convert from FHT to complex FFT
fft= FHT2FFTHalf (fht);
s1=0;
s2=0;
for (i=0;i<(size/2+1);i++) for (j=0;j<size;j++) {
if ((i>0) || (j>0)) { // skip DC
e=fft[i][j][0]*fft[i][j][0]+fft[i][j][1]*fft[i][j][1];
if ((i>=vertMin) && (i<=vertMax) && ((j<=hor) || (j>=size-hor))) s1+=e;
else s2+=e;
}
}
return s1/(s1+s2); // fraction inside selected area, use as likelyhood of the needed target
}
float [] circle2DoubleRect (float [] pixels, int width, int height, int size, double x0, double y0, double r0) {
float [] outPixels=new float[size*size];
int x,y;
double a,r;
int px,py;
for (y=0;y<size;y++) for (x=0;x<size;x++) {
if ((y>(size>>1)) || ((y==(size>>1)) && (x>=(size>>1)))){
r=y-(size>>1); // +0.5?
a=size-x-1;
} else {
r=(size>>1)-y; // -0.5?
a=size+x;
}
r+=r0; /// to match periodic pattern on both sides of zero (center circle is twice wider)
a*=Math.PI/size;
px=((int)Math.round(x0+r*Math.cos(a)) + width ) % width;
py=((int)Math.round(y0+r*Math.sin(a)) + height) % height;
outPixels[y*size+x]=pixels[py*width+px];
}
return outPixels;
}
double [][] clusteriseTargets(float [] pixels,int width, int height, double threshold, int clusterSize) {
if ((width*height) != pixels.length) {
IJ.showMessage("Error in clasteriseTargets","pixels.length ("+pixels.length+") does not match width ("+width+") x height ("+height+") = "+(width*height));
return null;
}
int x,y,i,j;
Integer Index, NewIndex, NextIndex;
int clusterNumber=1;
int []clusterMap=new int[width*height];
List <Integer> pixelList=new ArrayList<Integer>(100);
int [] dirs={1,-width+1,-width,-width-1,-1,+width-1,width,width+1};
int listIndex;
float f;
boolean first;
double cx,cy,cm; // for centroid calculation;
int ix,iy;
Double [] cxy=null;
for (i=0;i<clusterMap.length;i++) clusterMap[i]=0; /// 0 - unused, -1 - "do not use"
List <Double[]> Centroids=new ArrayList<Double[]>(100);
for (y=0;y<height;y++) for (x=0;x<width;x++) {
if ((pixels[y*width+x]>=threshold) && (clusterMap[y*width+x]==0)) {
/// mark all connected pixels above the threshold
Index=y*width+x;
pixelList.clear();
pixelList.add (Index);
clusterMap[Index]=clusterNumber;
listIndex=0;
while (listIndex<pixelList.size() ) {
Index=pixelList.get(listIndex++);
for (i=0;i<dirs.length;i++) {
NewIndex=Index+dirs[i];
if ((NewIndex>=0) && (NewIndex<clusterMap.length) && (clusterMap[NewIndex]==0) && (pixels[NewIndex]>=threshold)) {
pixelList.add (NewIndex);
clusterMap[NewIndex]=clusterNumber;
}
}
} // while (!pixelList.isEmpty() )
if (DEBUG_LEVEL>2) {
System.out.println("Cluster="+clusterNumber+", n="+pixelList.size()+" x="+x+" y="+y);
}
if (clusterSize>0) { // 0 - leave as is
if (pixelList.size()>clusterSize) { // shrink
while (pixelList.size()>clusterSize) {
i=0;
f=pixels[pixelList.get(i)];
for (j=1;j<pixelList.size();j++) if (pixels[pixelList.get(j)]<f){
i=j;
f=pixels[pixelList.get(j)];
}
clusterMap[pixelList.get(i)]=-1; // Do not use looking for the next cluster
pixelList.remove(i);
}
} else if (pixelList.size()<clusterSize) { // expand
while (pixelList.size()<clusterSize) {
first=true;
f=0.0f;
NextIndex=0;
for (j=0;j<pixelList.size();j++) {
Index= pixelList.get(j);
for (i=0;i<dirs.length;i++){
NewIndex=Index+dirs[i];
if ((NewIndex>=0) && (NewIndex<clusterMap.length) && (clusterMap[NewIndex]==0) && (first || (pixels[NewIndex]>f))) {
f=pixels[NewIndex];
NextIndex=NewIndex;
first=false;
}
}
}
pixelList.add (NextIndex);
clusterMap[NextIndex]=clusterNumber;
}
}
/** calculate centroid */
cx=0.0; cy=0.0; cm=0.0;
for (i=0;i<pixelList.size();i++) {
j=pixelList.get(i);
iy=j/width;
ix=j-width*iy;
// System.out.println("j="+j+" x="+ix+" y="+iy);
f=pixels[j];
cm+=f;
cx+=f*ix;
cy+=f*iy;
}
cx/=cm;
cy/=cm;
cxy=new Double[2];
cxy[0]=cx;
cxy[1]=cy;
Centroids.add(cxy);
// System.out.println("New cluster size="+pixelList.size()+" x="+cx+" y="+cy);
}
clusterNumber++;
}
}
double[][] coordList=new double[Centroids.size()][2];
for (i=0;i<coordList.length;i++) {
coordList[i][0]=Centroids.get(i)[0];
coordList[i][1]=Centroids.get(i)[1];
}
// Double[][] coordList= (Double[][]) Centroids.toArray();
if (DEBUG_LEVEL>2) {
for (i=0;i<coordList.length;i++) System.out.println(i+": x="+coordList[i][0]+" y="+coordList[i][1]);
}
// return clusterMap;
return coordList;
}
//// System.out.println("measureTargets(), N="+N);
/** Normalize pixels values as ratios of difference to average in the surrounding ring to variation in the ring*/
/** TODO: don't roll over, limit */
/// BUG: Seems something wrong - if convolution kernel had DC component - generated all "1.0"
float [] normalizeAtRing(float [] pixels, int width, int height, boolean[][] mask ) {
if ((width*height) != pixels.length) {
IJ.showMessage("Error in normalizeAtRing","pixels.length ("+pixels.length+") does not match width ("+width+") x height ("+height+") = "+(width*height));
return null;
}
int i,j,x,y,x1,y1,x2,y2, pre_x,pre_y;
int nFiltPix=0;
float [] result=new float [width*height];
double s,s2,d, mean,sigma, meang,sigmag;
double min=0.0;
double max=0.0;
boolean first;
int ir=(mask.length-1)>>1;
for (i=0;i<mask.length; i++) for (j=0;j<mask[0].length;j++) if (mask[i][j]) nFiltPix++;
s= 0.0;
s2=0.0;
for (y=0;y<height;y++) for (x=0;x<width;x++) {
d=pixels[y*width+x];
s+=d;
s2+=d*d;
}
meang= s/(width+height);
sigmag=Math.sqrt(s2/(width+height)-meang*meang);
for (y=0;y<height;y++) for (x=0;x<width;x++) {
s= 0.0;
s2=0.0;
pre_y=y+ir+height; // preparing for "%", making sure it will be positive
pre_x=x+ir+width; // preparing for "%", making sure it will be positive
first=true;
for (y1=0;y1 < mask.length; y1++) {
y2=(pre_y-y1)%height;
for (x1=0;x1<mask[0].length;x1++) if (mask[y1][x1]) {
x2=(pre_x-x1)%width;
d=pixels[y2*width+x2];
if (first) {
min=d;
max=d;
first=false;
}
if (d>max) max=d;
if (d<min) min=d;
s+=d;
s2+=d*d;
}
}
mean= s/nFiltPix;
sigma=Math.sqrt(s2/nFiltPix-mean*mean);
sigma=Math.sqrt(sigma*sigmag); // average with image-global sigma
// mean=max;
// sigma=max-min;
d=pixels[y*width+x];
if (sigma>0) { // should always be so
result[y*width+x]= (float) ((d-mean)/sigma);
} else {
result[y*width+x]=1.0f; // any number?
}
}
return result;
}
/** Convolve image (one Bayer slice) with the inverted target kernel
Center should be at size/2, size/2 - will convolve only (size-1)*(size-1) */
/**Which is faster - double or float? Double i TWICE faster!*/
float [] doubleConvolveWithTarget(float [] pixels, float [] kernel_full, int width, int height, int size) {
int hsize=size/2;
double [] kernel;
if ((width*height) != pixels.length) {
IJ.showMessage("Error","pixels.length ("+pixels.length+") does not match width ("+width+") x height ("+height+") = "+(width*height));
return null;
}
if ((size*size) != kernel_full.length) {
IJ.showMessage("Error","kernel.length ("+kernel_full.length+") does not match size ("+size+") ^2 = "+(size*size));
return null;
}
int i,j;
double d; // is float faster than double? or opposite (then it makes sesne to convert everything to double first
/** if kernel has even dimensions - ignore first (0) row and first (0) column */
if ((size & 1)!=0) {
kernel= new double[size*size];
for (i=0;i<kernel.length;i++) kernel[i]=kernel_full[i];
} else {
size-=1;
hsize-=1;
d=0.0f;
kernel= new double[size*size];
for (i=0;i<size;i++) for (j=0;j<size;j++) {
kernel[i*size+j]=kernel_full[(i+1)*(size+1)+(j+1)];
d+=kernel[i*size+j];
}
d/=size*size;
// System.out.println("Subtracting average value ("+d+") from the convolution kernel");
for (i=0;i<kernel.length;i++) kernel[i]-=d;
}
double [] dPixels=new double[pixels.length];
for (i=0;i<pixels.length;i++) dPixels[i]=pixels[i];
if (DEBUG_LEVEL>10) IJ.showMessage("Debug doubleConvolveWithTarget","pixels.length="+pixels.length+"\nwidth="+width+"\nheight="+height+"\nkernel.length="+kernel.length+"\nsize="+size);
float [] result=new float [width*height]; /** this is still float - one conversion on tghe output*/
int x,y,x1,y1, x2, y2, pre_y,pre_x;
// double d;
boolean yMiddle=false;
int index_kernel, index_source;
for (y=0;y<height;y++) {
/**/
yMiddle= (y>=hsize) && (y<(height-hsize));
if (yMiddle) { // calculate faster when no need to care about borders
for (x=hsize;x<width-hsize;x++) {
d=0;
index_kernel=0;
index_source=(y+hsize)*width+x+hsize;
for (y1=0;y1<size;y1++) {
for (x1=0;x1<size;x1++) {
// if (index_source<0) System.out.println("index_source="+index_source+" index_kernel="+index_kernel+" x="+x+" y="+y+" x1="+x1+" y1="+y1);
d+=dPixels[index_source--]*kernel[index_kernel++]; ///out of bounds: -834
}
index_source-=width-size;
}
result[y*width+x]= (float) d;
}
}
/**/
/** now finish beginnings/ends of the middle lines and process complete first/last lines*/
pre_y=y+hsize+height; // preparing for "%", making sure it will be positive
for (x=0;x<width;x++) if ((x<hsize) || (x>=(width-hsize)) || !yMiddle){
d=0;
pre_x=x+hsize+width; // preparing for "%", making sure it will be positive
for (y1=0;y1<size;y1++) {
y2=(pre_y-y1)%height;
for (x1=0;x1<size;x1++) {
x2=(pre_x-x1)%width;
d+=dPixels[y2*width+x2]*kernel[y1*size+x1];
}
result[y*width+x]= (float) d;
}
}
}
return result;
}
public void processWindowEvent(WindowEvent e) {
super.processWindowEvent(e);
if (e.getID()==WindowEvent.WINDOW_CLOSING) {
instance = null;
}
}
public boolean showDialog() {
int i;
GenericDialog gd = new GenericDialog("Target Points parameters");
gd.addStringField ("Filename prefix: ", jp4_instance.getTitle(), 20);
gd.addNumericField("FFT_Size: ", FFTSize, 0);
// gd.addNumericField("Target minimal outer diameter (pix)", targetOuterDMin, 2);
// gd.addNumericField("Target maximal outer diameter (pix)", targetOuterDMax, 2);
// gd.addNumericField("Number of target rings ", numTargetRings, 0);
// gd.addNumericField("Subdivide pixels for target generation ", pixelsSubdivide, 0);
gd.addNumericField("Filtered radius (pix) ", filteredRadius, 2); //3; //pix - search maximums after convolution in (2*filteredRadius+1) squares
gd.addNumericField("Background radius (pix) ", backgroundRadius, 3); //25; //pix - consider ring area between backgroundRadius and filteredRadius as reference
gd.addNumericField("Cluster threshold ", clusterThreshold, 3); //1.5
gd.addNumericField("Cluster size (pix) ", clusterSize, 0); //20
gd.addNumericField("Target discriminator angular freq. ", discrAngularFreq, 0); //2 ; // pixels on FFT image of tragets converted polar (the smaller, the less angular variations)
gd.addNumericField("Target discriminator radial min freq ", discrRadialMinFreq, 0); //10 ; // pixels on FFT image of tragets converted polar (radial component)
gd.addNumericField("Target discriminator radial max freq ", discrRadialMaxFreq, 0); //10 ; // pixels on FFT image of tragets converted polar (radial component)
gd.addNumericField("Target discriminator threshold ", discrThreshold, 3); //0.3; // FFT energy fraction in selecter area should be > this threshold to pass the test
gd.addNumericField("Max chromatic aberration (pix) ", maxChromaticDistance, 1); //10.0; // Maximal distance between the same target on different color copmponents
gd.addNumericField("Debug Level: ", MASTER_DEBUG_LEVEL, 0);
gd.showDialog();
if (gd.wasCanceled()) return false;
jp4_instance.setTitle(gd.getNextString());
FFTSize=1;
for (i= (int) gd.getNextNumber(); i >1; i>>=1) FFTSize <<=1; /** make FFTSize to be power of 2*/
// targetOuterDMin = gd.getNextNumber(); // minimal outer diameter of the target image , in pixels
// targetOuterDMax = gd.getNextNumber(); // maximal outer diameter of the target image , in pixels
// numTargetRings = (int) gd.getNextNumber(); // number of target black rings (notg counting center black circle)
// pixelsSubdivide = (int) gd.getNextNumber(); // Subdivide pixels for target generation
filteredRadius = gd.getNextNumber();
backgroundRadius = gd.getNextNumber();
clusterThreshold= gd.getNextNumber();
clusterSize= (int) gd.getNextNumber();
discrAngularFreq= (int) gd.getNextNumber();
discrRadialMinFreq= (int) gd.getNextNumber();
discrRadialMaxFreq= (int) gd.getNextNumber();
discrThreshold= gd.getNextNumber();
maxChromaticDistance= gd.getNextNumber();
MASTER_DEBUG_LEVEL= (int) gd.getNextNumber();
return true;
}
public float []createTargetDialog() {
int i;
GenericDialog gd = new GenericDialog("Target template parameters");
gd.addNumericField("FFT_Size: ", FFTSize, 0);
gd.addNumericField("Target minimal outer diameter (pix)", targetOuterDMin, 2);
gd.addNumericField("Target maximal outer diameter (pix)", targetOuterDMax, 2);
gd.addNumericField("Number of target rings ", numTargetRings, 0);
gd.addNumericField("Subdivide pixels for target generation ", pixelsSubdivide, 0);
gd.addNumericField("Invert deconvolution if less than", deconvInvert, 3);
// gd.addNumericField("Debug Level: ", MASTER_DEBUG_LEVEL, 0);
gd.showDialog();
if (gd.wasCanceled()) return null;
FFTSize=1;
for (i= (int) gd.getNextNumber(); i >1; i>>=1) FFTSize <<=1; /** make FFTSize to be power of 2*/
targetOuterDMin = gd.getNextNumber(); // minimal outer diameter of the target image , in pixels
targetOuterDMax = gd.getNextNumber(); // maximal outer diameter of the target image , in pixels
numTargetRings = (int) gd.getNextNumber(); // number of target black rings (notg counting center black circle)
pixelsSubdivide = (int) gd.getNextNumber(); // Subdivide pixels for target generation
deconvInvert= gd.getNextNumber(); //0.05; // when FFT component is lass than this fraction of the maximal value, replace 1/z with Z
// MASTER_DEBUG_LEVEL= (int) gd.getNextNumber();
return createTarget(FFTSize,pixelsSubdivide,targetOuterDMin,targetOuterDMax,numTargetRings,deconvInvert);
}
public float [] createTarget(int size, int subdiv, double DMin, double DMax, int nRings, double deconvInvert) {
ImageProcessor ip_target;
FHT fht_target;
double[][][] fft_target;
int hsizeP1= (size>>1)+1;
double [][] dpixels=new double [hsizeP1][hsizeP1];
double [] rMinIn= new double[nRings+1];
double [] rMaxIn= new double[nRings+1];
double [] rMinOut= new double[nRings+1];
double [] rMaxOut= new double[nRings+1];
int i,j,i1,j1,n;
double x,y,r,ks,ke;
double subFraction=1.0/(subdiv*subdiv);
double DCLevel=0.0;
double a,k,r2,k2;
if (DMin>DMax) {
x=DMin;
DMin=DMax;
DMax=x;
}
for (n=0;n<=nRings;n++) {
rMinIn[n]= DMin*(n*2 )/(2*(2*nRings+1));
rMinOut[n]=DMin*(n*2+1)/(2*(2*nRings+1));
rMaxIn[n]= DMax*(n*2 )/(2*(2*nRings+1));
rMaxOut[n]=DMax*(n*2+1)/(2*(2*nRings+1));
}
for (i=0;i<hsizeP1; i++) for (j=0;j<hsizeP1; j++) {
dpixels[i][j]=0.0;
for (i1=0;i1<subdiv; i1++) for (j1=0;j1<subdiv; j1++) {
x=j+0.1*j1;
y=i+0.1*i1;
r=Math.sqrt(x*x+y*y);
for (n=0;n<=nRings;n++) if ((rMinIn[n] <= r)&& (rMaxOut[n]>r)){
if (rMaxIn[n]>r) ke=(r-rMinIn[n])/(rMaxIn[n]-rMinIn[n]);
else ke=1.0;
if (rMinOut[n]<=r) ks=(r-rMinOut[n])/(rMaxOut[n]-rMinOut[n]);
else ks=0.0;
/// dpixels[i][j]+=subFraction*(ke-ks);
dpixels[i][j]-=subFraction*(ke-ks);
}
}
r=dpixels[i][j];
// some piuxels will appear once, some - twice, most - four times
if ((i>0) &&(i<(size>>1))) r*=2.0;
if ((j>0) &&(j<(size>>1))) r*=2.0;
DCLevel+=r;
}
DCLevel/=(size*size);
for (i=0;i<hsizeP1; i++) for (j=0;j<hsizeP1; j++) dpixels[i][j]-=DCLevel;
ip_target = new FloatProcessor(FFTSize,FFTSize);
for (i=0;i<size; i++) for (j=0;j<size; j++) {
// ip_target.putPixelValue(j,i, (float) dpixels[(i>=hsizeP1)?(size-i):i][(j>=hsizeP1)?(size-j):j]);
ip_target.putPixelValue(j ^ (size>>1),i ^ (size>>1), (float) dpixels[(i>=hsizeP1)?(size-i):i][(j>=hsizeP1)?(size-j):j]);
}
ip_target.resetMinAndMax();
if (DEBUG_LEVEL>5) {
ImagePlus imp_target= new ImagePlus("Target_direct_"+deconvInvert, ip_target);
imp_target.show();
}
fht_target = new FHT(ip_target);
// Swapping quadrants, so the center will be 0,0
fht_target.swapQuadrants();
// get to frequency
fht_target.transform();
float [] fht_target_pixels=(float []) fht_target.getPixels();
if (DEBUG_LEVEL>5) {
ImageProcessor ip_fht_target = new FloatProcessor(size,size);
ip_fht_target.setPixels(fht_target_pixels);
ip_fht_target.resetMinAndMax();
ImagePlus imp_fht_target= new ImagePlus("FHT_"+deconvInvert, ip_fht_target);
imp_fht_target.show();
}
// Convert from FHT to complex FFT
fft_target= FHT2FFTHalf (fht_target);
/* */
/// deconvInvert
/// Now tricky thing. Invert Z for large values, but make them Z - for small ones. So it will be a mixture of correlation and deconvolution
// here the targets are round, but what will the the correct way fo assymmetrical ones?
/// First - find maximal value
// double[][][] fft_target;
double fft_max=0;
for (i=0;i<fft_target.length; i++) for (j=0;j<fft_target[0].length;j++) {
r2=fft_target[i][j][0]*fft_target[i][j][0]+fft_target[i][j][1]*fft_target[i][j][1];
if (r2>fft_max) fft_max=r2;
}
k=Math.sqrt(fft_max)*deconvInvert;
k2=k*k;
for (i=0;i<fft_target.length; i++) for (j=0;j<fft_target[0].length;j++) {
r=Math.sqrt(fft_target[i][j][0]*fft_target[i][j][0]+fft_target[i][j][1]*fft_target[i][j][1]);
a=-Math.atan2(fft_target[i][j][1],fft_target[i][j][0]); /// will be zero for these targets)
r=r/(r*r+k2);
fft_target[i][j][0]=r*Math.cos(a);
fft_target[i][j][1]=r*Math.sin(a);
}
// Convert fft array back to fht array
/**/
fht_target_pixels= FFTHalf2FHT (fft_target);
// set fht_target pixels with new values
fht_target.setPixels (fht_target_pixels);
/// optionally show the result
if (DEBUG_LEVEL>5) {
ImageProcessor ip_fht_target1 = new FloatProcessor(size,size);
ip_fht_target1.setPixels(fht_target_pixels);
ip_fht_target1.resetMinAndMax();
ImagePlus imp_fht_target1= new ImagePlus("Inverted_FHT_"+deconvInvert, ip_fht_target1);
imp_fht_target1.show();
}
/// transform
fht_target.inverseTransform();
fht_target.swapQuadrants();
fht_target.resetMinAndMax();
// ImagePlus imp= new ImagePlus(title, ip_fht);
if (DEBUG_LEVEL>1) {
ImagePlus imp_target_inverted= new ImagePlus("Inverted_"+deconvInvert, fht_target);
imp_target_inverted.show();
}
// return direct_target;
return (float[])fht_target.getPixels();
}
/** ignore ROI, use whole image */
public float[][] splitBayer (ImagePlus imp) {
ImageProcessor ip=imp.getProcessor();
Rectangle r=new Rectangle(imp.getWidth(),imp.getHeight());
float [] pixels;
pixels=(float[])ip.getPixels();
if (DEBUG_LEVEL>10) IJ.showMessage("splitBayer","r.width="+r.width+
"\nr.height="+r.height+
"\nlength="+pixels.length);
float [][] bayer_pixels=new float[4][pixels.length>>2];
int x,y,base,base_b,bv;
int half_height=r.height>>1;
int half_width=r.width>>1;
for (y=0; y<half_height; y++) for (bv=0;bv<2;bv++){
base=r.width*((y<<1)+bv);
base_b=half_width*y;
if (bv==0) for (x=0; x<half_width; x++) {
bayer_pixels[0][base_b]= pixels[base++];
bayer_pixels[1][base_b++]=pixels[base++];
} else for (x=0; x<half_width; x++) {
bayer_pixels[2][base_b]= pixels[base++];
bayer_pixels[3][base_b++]=pixels[base++];
}
}
return bayer_pixels;
}
public void showBayers(float[][] bayer_pixels, int width, int height, String title) {
int i;
if (DEBUG_LEVEL>10) IJ.showMessage("showBayers","width="+width+
"\nheight="+height+
"\nlength="+bayer_pixels[0].length);
ImageProcessor[] ip= new ImageProcessor[4];
ImagePlus[] imp=new ImagePlus[4];
for (i=0;i<4;i++) {
ip[i]=new FloatProcessor(width,height);
ip[i].setPixels(bayer_pixels[i]);
ip[i].resetMinAndMax();
imp[i]= new ImagePlus(title+"_"+i, ip[i]);
imp[i].show();
}
}
public float[] initHamming(int size) {
float [] hamming =new float [size*size];
float [] hamming_line=new float [size];
int i,j;
for (i=0; i<size; i++) hamming_line[i]=(float) (0.54-0.46*Math.cos((i*2.0*Math.PI)/size));
for (i=0; i<size; i++) for (j=0; j<size; j++){
hamming[size*i+j]=hamming_line[i]*hamming_line[j];
}
return hamming;
}
public void initDisplay() {
if ((imp_display==null) || (imp_display.getWidth()!=displayWidth) || (imp_display.getHeight()!=displayHeight)) {
if (imp_display!=null) imp_display.close();
ip_display= new ColorProcessor (displayWidth,displayHeight);
imp_display= new ImagePlus("Target Points", ip_display);
imp_display.show();
}
}
/** converts FHT results (frequency space) to complex numbers of [FFTSize/2+1][FFTSize] */
private double[][][] FHT2FFTHalf (FHT fht) {
float[] fht_pixels=(float[])fht.getPixels();
double[][][] fftHalf=new double[(FFTSize>>1)+1][FFTSize][2];
int row1,row2,col1,col2;
for (row1=0;row1<=(FFTSize>>1);row1++) {
row2=(FFTSize-row1) %FFTSize;
for (col1=0;col1<FFTSize;col1++) {
col2=(FFTSize-col1) %FFTSize;
fftHalf[row1][col1][0]= 0.5*(fht_pixels[row1*FFTSize+col1] + fht_pixels[row2*FFTSize+col2]);
fftHalf[row1][col1][1]= 0.5*(fht_pixels[row2*FFTSize+col2] - fht_pixels[row1*FFTSize+col1]);
}
}
return fftHalf;
}
/** converts FFT arrays of complex numbers of [FFTSize/2+1][FFTSize] to FHT arrays */
private float[] FFTHalf2FHT (double [][][] fft) {
float[] fht_pixels=new float [FFTSize*FFTSize];
int row1,row2,col1,col2;
for (row1=0;row1<=(FFTSize>>1);row1++) {
row2=(FFTSize-row1) %FFTSize;
for (col1=0;col1 < FFTSize;col1++) {
col2=(FFTSize-col1) %FFTSize;
/** out of bounds */
fht_pixels[row1*FFTSize+col1]=(float)(fft[row1][col1][0]+fft[row1][col1][1]);
fht_pixels[row2*FFTSize+col2]=(float)(fft[row1][col1][0]-fft[row1][col1][1]);
}
}
return fht_pixels;
}
}
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program 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/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
This directory contains several plugins for the popular ImageJ image processing program to work with the images acquired with Elphel cameras. You may unzip them into "plugins" folder of ImageJ to make them accessible through this program.
JP46Reader.tar.gz contains two files: JP46_Reader.java and JP46_Reader_noscale.java. This plugin allows to open images compressed as JP46 - format designed for the raw Bayer mosaic images from the cameras. You may find more details about this format in the LinuxDevices article - http://www.linuxfordevices.com/c/a/Linux-For-Devices-Articles/Elphel-camera-under-the-hood-from-Verilog-to-PHP/ . Plugin makes use of Elphel MakerNote field in Exif header that contains per-channel values of the camera sensor analog gains, black levels and gamma, it takes decompressed 8 bpp images and um-applies gains and gammas resulting in the 32bpp (float) values that are very close to the amount of light that each pixel acquired.
For the best performance you may acquire images with the gamma set to 0.5 (sensor noise matched value) and analog gains set for the white balance (automatic or manual), so pixels of each color will use all the output range of 8 bits (compressed).
Crosstalk_Deconv.java.tar.gz is designed for experimenting with correction of inter-pixel crosstalk. It does not include automatic crosstalk measurement, that values have to be entered to the program in "Configure" menu. It is possible create test images, apply and un-apply crosstalk - all the modern sensors have significant amount of such crosstalk that both degrades resolution and colors in the digital images.
MTF_Bayer.java.tar.gz - plugin for measuring camera resolution. It started from the SE_MTF plugin for ImageJ (http://rsbweb.nih.gov/ij/plugins/se-mtf/index.html), with added functionality focused on measuring Bayer mosaic images. It accepts such images as monochrome and processes multiple edges in the selected rectangular area accumulating results from both black-to-white and white-to-black edges (separately). And as the camera resolution can be different in different directions, the plugin can accept near-horizontal slanted edges also. When the "Calculate MTF" button is pressed it detects (and counts) near-vertical edges that run all the selection height, then does the same in the horizontal direction. After that it selects the direction that has more edges (if any) running all the selection height/width.
With the edges are detected plugin approximates each edge by a quadratic (A*x^2+B*X+C) parabola as straight edges on the target become curves because of the lens distortions. Those edges approximations are calculated either individually for each color component (if "Monochromatic" mode is not selected) or together (useful when the camera had color band-pass filter so all the light that reached pixels was approximately the same wavelength and the chromatic aberrations of the lens can be neglected.
With the approximated edges multiple sensor rows/columns are binned with each bin being 1/4 of the pixel size (similarly to ISO-16067-1), the result of the binning is shown in edge-spread function (ESF) plots and/or tables - separately for each color and edge direction. Green lines on the plot shows green pixels in red rows of Bayer pattern, while cyan color is used for green in blue rows). The derivative of the ESF is line spread function (LSF) that also can be displayed - both in the same scale as the input pixel values or normalized to [0.0..1.0] interval for every color. The result modulation transfer function (MTF) is calculated as the absolute value of the Fourier transform of the LSF.
Some sample files that can be used with the plugins are available here - http://community.elphel.com/files/ImageJ_plugins/sample_files/ The images include shots of nearly-vertical (and nearly-horizontal) black/white stripes, acquired with red, blue, green filters (and no filter at all). These images can be opened with JP46_Reader.java plugin, them processed with MTF_Bayer.java (you need to select rectangular area on the image that includes stripes and press "Calculate MTF" button).
The most recent versions of these programs are available for download from the Git repository - http://elphel.git.sourceforge.net/git/gitweb.cgi?p=elphel/ImageJ-Elphel
\ No newline at end of file
<?php
/*!*******************************************************************************
*! FILE NAME : escher_pattern.php
*! DESCRIPTION: slanted curved checker board generator
*! requires php5-ps, ps2pdf
*! Copyright (C) 2010 Elphel, Inc
*! -----------------------------------------------------------------------------**
*!
*! This program 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/>.
*! -----------------------------------------------------------------------------**
*/
$orientation= 'P'; // P/L
$unit= 'mm' ; /// pt,mm,cm,in
//$format= array(40,20); /// width and height of current units
$unicode= true;
$encoding= "UTF-8";
$diskcache= false; /// use temporary files
$margin_top= 2;
$margin_bottom= 1;
$margin_left= 1;
$margin_right= 1;
$LPM=50;
$PAGE_WIDTH=270;
$PAGE_HEIGHT=210;
$ROTATE=5;
$ESCHER=2.0;
$tmpdir ='/tmp';
$filetype='PDF';
foreach ($_GET as $key=>$value) {
switch (strtoupper($key)) {
case 'PAGE_WIDTH': $PAGE_WIDTH=$value+0;break;
case 'PAGE_HEIGHT':$PAGE_HEIGHT=$value+0;break;
case 'LPM': $LPM=$value+0;break;
case 'ESCHER': $ESCHER=$value+0;break;
case 'ROTATE': $ROTATE=$value+0;break;
case 'MODE':
case 'TYPE': $filetype=$value;break;
}
}
$basename='escher-pattern-ESCHER'.$ESCHER.'-LPM'.$LPM.'-ROT'.$ROTATE.'-PAGE_WIDTH'.$PAGE_WIDTH.'-PAGE_HEIGHT'.$PAGE_HEIGHT;
$ps_name=$tmpdir.'/'.$basename.'.ps';
$pdf_name=$tmpdir.'/'.$basename.'.pdf';
$mm2points=72/25.4;
$PW_POINTS=$PAGE_WIDTH*$mm2points;
$PH_POINTS=$PAGE_HEIGHT*$mm2points;
$ps = ps_new();
if (!ps_open_file($ps, $ps_name)) {
print 'Cannot open PostScript file '.$ps_name."\n";
exit(1);
}
ps_set_parameter($ps, "warning", "true");
ps_set_info($ps, "Creator", " escher_pattern.php");
ps_set_info($ps, "Author", "Elphel");
ps_set_info($ps, "Title", "PSF measurement pattern");
$Size=1.0;
$qSize=$Size/4;
$hSize=$Size/2;
$a=$ESCHER*(sqrt(2)-1.0);
$angle=-$ROTATE; // PS starts from tyhe bottom left
if ($ESCHER>0) {
$r=($a*$a+1)/(2*$a)*$qSize;
$r2=$r*$r;
$h=sqrt($r2-$qSize*$qSize);
$dc=2*$qSize-$h;
$halfAangle=rad2deg(atan($qSize/$h));
//$center=1.5*$sideOfSquare;
$center=$dc+$r;
$pstemplate = ps_begin_template($ps, 2*$center, 2*$center);
ps_moveto($ps, $center-$hSize, $center-$hSize);
ps_arc ($ps , $center-$Size+$dc, $center-$qSize, $r, 0-$halfAangle , 0+$halfAangle );
ps_arcn ($ps , $center-$dc , $center+$qSize, $r, 180+$halfAangle , 180-$halfAangle );
ps_arc ($ps , $center-$qSize, $center+$Size-$dc, $r, 270-$halfAangle , 270+$halfAangle );
ps_arcn ($ps , $center+$qSize, $center+$dc, $r, 90+$halfAangle , 90-$halfAangle );
ps_arc ($ps , $center+$Size-$dc,$center+$qSize, $r, 180-$halfAangle , 180+$halfAangle );
ps_arcn ($ps , $center+$dc, $center-$qSize, $r, 0+$halfAangle , 0-$halfAangle );
ps_arc ($ps , $center+$qSize, $center-$Size+$dc, $r, 90-$halfAangle , 90+$halfAangle );
ps_arcn ($ps , $center-$qSize, $center-$dc, $r, 270+$halfAangle , 270-$halfAangle );
ps_fill($ps);
ps_end_template($ps);
} else {
$pstemplate = ps_begin_template($ps, $Size, $Size);
ps_moveto($ps, 0, 0);
ps_lineto($ps, 0, $Size);
ps_lineto($ps, $Size,$Size);
ps_lineto($ps, $Size,0);
ps_lineto($ps, 0,0);
ps_fill($ps);
ps_end_template($ps);
}
$s=$PW_POINTS+$PH_POINTS;
$sideOfSquare=500/$LPM*$mm2points;
$numberOfSquares=floor((4*$s-$sideOfSquare)/(2*$sideOfSquare));
$s=(2*$numberOfSquares+1)*$sideOfSquare;
ps_begin_page($ps, $PW_POINTS, $PH_POINTS);
ps_setcolor($ps, "fill", "cmyk", 0, 0, 0, 1);
ps_rotate($ps,$angle);
for ($y=-$s;$y<$s;$y+=2*$sideOfSquare) for ($x=-$s;$x<$s;$x+=2*$sideOfSquare) {
ps_place_image($ps, $pstemplate, $x, $y,$sideOfSquare);
ps_place_image($ps, $pstemplate, $x+$sideOfSquare, $y+$sideOfSquare,$sideOfSquare);
}
ps_end_page($ps);
ps_close($ps);
ps_delete($ps);
if ($filetype=='PDF') {
// echo 'ps2pdf -dDEVICEWIDTHPOINTS='.$PW_POINTS.' -dDEVICEHEIGHTPOINTS='.$PH_POINTS.' '.$ps_name.' '.$pdf_name;
exec('ps2pdf -dDEVICEWIDTHPOINTS='.$PW_POINTS.' -dDEVICEHEIGHTPOINTS='.$PH_POINTS.' '.$ps_name.' '.$pdf_name);
$outfile=$pdf_name;
header('Content-Type: application/pdf');
} else {
$outfile=$ps_name;
header('Content-Type: application/postscript');
}
if (headers_sent()) {
echo 'Some data has already been output to browser, can\'t send PDF file';
exit (1);
}
header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
header('Pragma: public');
header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
$fp = fopen($outfile, 'rb');
fseek($fp, 0, SEEK_END); /// file pointer at the end of the file (to find the file size)
$fsize = ftell($fp); /// get file size
fseek($fp, 0, SEEK_SET); /// rewind to the start of the file
header('Content-Length: '.$fsize);
header('Content-Disposition: inline; filename="'.basename($outfile).'";');
fpassthru($fp); /// send the raw data itself
fclose($fp);
//echo 'Done - see '.$pdf_name;
?>
# Name: Process Pixels
# Author: The Fiji Team
# Name: Elphel Image Processing
# Author: Andrey Filippov
# Version: 1.0.0
# A single .jar file can contain multiple plugins, specified in separate lines.
......@@ -9,4 +9,7 @@
# If something like ("<arg>") is appended to the class name, the setup() method
# will get that as arg parameter; otherwise arg is simply the empty string.
Process, "Process Pixels", Process_Pixels
Plugins, "Aberration Calibration", Aberration_Calibration
Plugins, "Aberration Correction", Aberration_Correction
Plugins, "Eyesis Correction", Eyesis_Correction
Plugins, "JP46 Reader camera", JP46_Reader_camera
<?php
/*!*******************************************************************************
*! FILE NAME : slanted_checker.php
*! DESCRIPTION: slanted checker board target
*! Copyright (C) 2010 Elphel, Inc
*! -----------------------------------------------------------------------------**
*!
*! This program 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/>.
*! -----------------------------------------------------------------------------**
*/
require_once('../tcpdf/config/lang/eng.php');
require_once('../tcpdf/tcpdf.php');
$orientation= 'P'; // P/L
$unit= 'mm' ; /// pt,mm,cm,in
//$format= array(40,20); /// width and height of current units
$unicode= true;
$encoding= "UTF-8";
$diskcache= false; /// use temporary files
$margin_top= 2;
$margin_bottom= 1;
$margin_left= 1;
$margin_right= 1;
$LPM=50;
$PAGE_WIDTH=270;
$PAGE_HEIGHT=210;
$ROTATE=5;
$ESCHER=2.0;
$NC=3; // number of curves for the circle (seems 3 is OK for small images, 2 - visible difference)
foreach ($_GET as $key=>$value) {
switch (strtoupper($key)) {
case 'PAGE_WIDTH': $PAGE_WIDTH=$value+0;break;
case 'PAGE_HEIGHT':$PAGE_HEIGHT=$value+0;break;
case 'LPM': $LPM=$value+0;break;
case 'ESCHER': $ESCHER=$value+0;break;
case 'NC': $NC=$value+0;break;
case 'ROTATE': $ROTATE=$value+0;break;
}
}
$format= array($PAGE_WIDTH,$PAGE_HEIGHT); /// width and height of current units 279.4 x 215.9 (11.5x8.5)
$pdf=new TCPDF ($orientation,
$unit,
$format,
$unicode,
$encoding,
$diskcache);
// set document information
$pdf->SetCreator(PDF_CREATOR);
$pdf->SetAuthor('Elphel');
$pdf->SetTitle('PSF chart');
/// set default header data
/// remove default header/footer
$pdf->setPrintHeader(false);
$pdf->setPrintFooter(false);
/// set default monospaced font
$pdf->SetDefaultMonospacedFont(PDF_FONT_MONOSPACED);
///set margins
$pdf->SetMargins($margin_left, $margin_top, $margin_right);
//set auto page breaks
$pdf->SetAutoPageBreak(FALSE, $margin_bottom);
$pdf->AddPage();
$pdf->StartTransform();
$h=$format[1]/2;
$w=$format[0]/2;
$pdf->Rotate(-$ROTATE,$w,$h);
//! $ROTATE>0!
$s=$w+$h;
$sideOfSquare=500/$LPM;
$numberOfSquares=floor((4*$s-$sideOfSquare)/(2*$sideOfSquare));
$s=(2*$numberOfSquares+1)*$sideOfSquare;
for ($y=-$s;$y<$s;$y+=2*$sideOfSquare) for ($x=-$s;$x<$s;$x+=2*$sideOfSquare) {
$pdf->Rect($x, $y, $sideOfSquare, $sideOfSquare,'F',array(),array(0,0,0));
$pdf->Rect($x+$sideOfSquare, $y+$sideOfSquare, $sideOfSquare, $sideOfSquare,'F',array(),array(0,0,0));
}
if ($ESCHER>0) {
$qSize=$sideOfSquare/4;
$a=$ESCHER*(sqrt(2)-1.0);
$r=($a*$a+1)/(2*$a)*$qSize;
$r2=$r*$r;
$h=sqrt($r2-$qSize*$qSize);
$dc=2*$qSize-$h;
$halfAangle=rad2deg(atan($qSize/$h));
for ($y=-$s;$y<$s;$y+=2*$sideOfSquare) for ($x=-$s;$x<$s;$x+=2*$sideOfSquare) {
for ($subY=0;$subY<2;$subY++) for ($subX=0;$subX<2;$subX++){
$cellColor=($subY==$subX)?array(0,0,0):array(255,255,255);
$xc=$x+$sideOfSquare*$subX+2*$qSize;
$yc=$y+$sideOfSquare*$subY+2*$qSize;
/** Limited precision make nasty lines between circle segments , so now they are always semi-circles) */
$pdf->Circle($xc-$dc, $yc-$qSize, $r, 180-$halfAangle, -$halfAangle, 'F', array(), $cellColor,$NC); // , $nc=8 - number of curves - last parameter
$pdf->Circle($xc+$qSize, $yc-$dc, $r, 90-$halfAangle, 270-$halfAangle, 'F', array(), $cellColor,$NC); // , $nc=8 - number of curves - last parameter
$pdf->Circle($xc+$dc, $yc+$qSize, $r, -$halfAangle, 180-$halfAangle, 'F', array(), $cellColor,$NC); // , $nc=8 - number of curves - last parameter
$pdf->Circle($xc-$qSize, $yc+$dc, $r, 270-$halfAangle, 90-$halfAangle, 'F', array(), $cellColor,$NC); // , $nc=8 - number of curves - last parameter
}
}
}
$pdf->StopTransform();
$pdf->Output('slanted-'.(($ESCHER>0)?('ESCHER-'.$ESCHER):'').'-'.$LPM.'LPM-'.$ROTATE.'grad.pdf', 'I');
?>
<?php
/*!*******************************************************************************
*! FILE NAME : target_circle.php
*! DESCRIPTION: Creates target of concentric circles
*! REQUIRES: tcpdf
*! Copyright (C) 2010 Elphel, Inc
*! -----------------------------------------------------------------------------**
*!
*! This program 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/>.
*! -----------------------------------------------------------------------------**
*/
require_once('tcpdf/config/lang/eng.php'); /// external dependency
require_once('tcpdf/tcpdf.php'); /// external dependency
$orientation= 'P'; // P/L
$unit= 'mm' ; /// pt,mm,cm,in
//$format= array(40,20); /// width and height of current units
$unicode= true;
$encoding= "UTF-8";
$diskcache= false; /// use temporary files
$margin_top= 2;
$margin_bottom= 1;
$margin_left= 1;
$margin_right= 1;
$TARGET_DIAMETER=100;
$TARGET_CIRCLES= 5;
$CENTER_WHITE= 1.0 ; //put white dot in the center
foreach ($_GET as $key=>$value) {
switch (strtoupper($key)) {
case 'D':
case 'TARGET_DIAMETER': $TARGET_DIAMETER=$value+0;break;
case 'C':
case 'N':
case 'TARGET_CIRCLES': $TARGET_CIRCLES=$value+0;break;
case 'W':
case 'CENTER_WHITE': $CENTER_WHITE=$value+0;break;
}
}
$PAGE_WIDTH= ceil($TARGET_DIAMETER+ $margin_left+$margin_right);
$PAGE_HEIGHT=ceil($TARGET_DIAMETER+$margin_top+ $margin_bottom);
$format= array($PAGE_WIDTH,$PAGE_HEIGHT); /// width and height of current units 279.4 x 215.9 (11.5x8.5)
$pdf=new TCPDF ($orientation,
$unit,
$format,
$unicode,
$encoding,
$diskcache);
// set document information
$pdf->SetCreator(PDF_CREATOR);
$pdf->SetAuthor('Elphel');
$pdf->SetTitle('Focusing Target');
/// set default header data
/// remove default header/footer
$pdf->setPrintHeader(false);
$pdf->setPrintFooter(false);
/// set default monospaced font
$pdf->SetDefaultMonospacedFont(PDF_FONT_MONOSPACED);
///set margins
$pdf->SetMargins($margin_left, $margin_top, $margin_right);
//set auto page breaks
$pdf->SetAutoPageBreak(FALSE, $margin_bottom);
$pdf->AddPage();
for ($i=$TARGET_CIRCLES*2; $i>=0; $i--) {
$r=($i+1)*$TARGET_DIAMETER/($TARGET_CIRCLES*2+1)/2;
$pdf->Circle($TARGET_DIAMETER/2,$TARGET_DIAMETER/2,$r,0,360,'FD', array(), ($i & 1)? array(255,255,255):array(BLACK));
}
if ($CENTER_WHITE>0) {
$pdf->Circle($TARGET_DIAMETER/2,$TARGET_DIAMETER/2,$CENTER_WHITE/2,0,360,'FD', array(), array(255,255,255));
}
$pdf->Output('target_circles_D'.$TARGET_DIAMETER.'_N'.$TARGET_CIRCLES.'.pdf', 'I');
exit(0);
/**
* Draws a circle.
* A circle is formed from n Bezier curves.
* @param float $x0 Abscissa of center point.
* @param float $y0 Ordinate of center point.
* @param float $r Radius.
* @param float $astart: Angle start of draw line. Default value: 0.
* @param float $afinish: Angle finish of draw line. Default value: 360.
* @param string $style Style of rendering. Possible values are:
* <ul>
* <li>D or empty string: Draw (default).</li>
* <li>F: Fill.</li>
* <li>DF or FD: Draw and fill.</li>
* <li>C: Draw close.</li>
* <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
* <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
* </ul>
* @param array $line_style Line style of circle. Array like for {@link SetLineStyle SetLineStyle}. Default value: default line style (empty array).
* @param array $fill_color Fill color. Format: array(red, green, blue). Default value: default color (empty array).
* @param integer $nc Number of curves used in circle. Default value: 8.
* @access public
* @since 2.1.000 (2008-01-08)
*/
/// public function Circle($x0, $y0, $r, $astart=0, $afinish=360, $style='', $line_style=array(), $fill_color=array(), $nc=8) {
?>
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