Skip to content

Commit 758b1e9

Browse files
authored
Merge pull request #530 from imagej/diffraction_psf
Add diffraction based kernel op
2 parents b6bdda7 + 052440b commit 758b1e9

File tree

5 files changed

+447
-3
lines changed

5 files changed

+447
-3
lines changed

src/main/java/net/imagej/ops/create/CreateNamespace.java

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,10 @@
5050
import net.imglib2.type.NativeType;
5151
import net.imglib2.type.numeric.ComplexType;
5252
import net.imglib2.type.numeric.IntegerType;
53-
import net.imglib2.type.numeric.real.FloatType;
54-
import net.imglib2.type.numeric.real.DoubleType;
55-
import net.imglib2.type.numeric.complex.ComplexFloatType;
5653
import net.imglib2.type.numeric.complex.ComplexDoubleType;
54+
import net.imglib2.type.numeric.complex.ComplexFloatType;
55+
import net.imglib2.type.numeric.real.DoubleType;
56+
import net.imglib2.type.numeric.real.FloatType;
5757

5858
import org.scijava.plugin.Plugin;
5959

@@ -480,6 +480,22 @@ public <T extends ComplexType<T>> RandomAccessibleInterval<T> kernelLog(
480480
outType);
481481
return result;
482482
}
483+
484+
// -- kernelDiffraction --
485+
486+
@OpMethod(
487+
op = net.imagej.ops.create.kernelDiffraction.DefaultCreateKernelGibsonLanni.class)
488+
public <T extends ComplexType<T> & NativeType<T>> Img<T> kernelDiffraction(
489+
final Dimensions in, final double NA, final double lambda, final double ns,
490+
final double ni, final double resLateral, final double resAxial, double pZ,
491+
final T type)
492+
{
493+
@SuppressWarnings("unchecked")
494+
final Img<T> result = (Img<T>) ops().run(
495+
net.imagej.ops.Ops.Create.KernelDiffraction.class, in, NA, lambda, ns, ni,
496+
resLateral, resAxial, pZ, type);
497+
return result;
498+
}
483499

484500
// -- kernelBiGauss --
485501

Lines changed: 319 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,319 @@
1+
/*
2+
* #%L
3+
* ImageJ software for multidimensional image processing and analysis.
4+
* %%
5+
* Copyright (C) 2014 - 2017 ImageJ developers.
6+
* %%
7+
* Redistribution and use in source and binary forms, with or without
8+
* modification, are permitted provided that the following conditions are met:
9+
*
10+
* 1. Redistributions of source code must retain the above copyright notice,
11+
* this list of conditions and the following disclaimer.
12+
* 2. Redistributions in binary form must reproduce the above copyright notice,
13+
* this list of conditions and the following disclaimer in the documentation
14+
* and/or other materials provided with the distribution.
15+
*
16+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
20+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26+
* POSSIBILITY OF SUCH DAMAGE.
27+
* #L%
28+
*/
29+
30+
package net.imagej.ops.create.kernelDiffraction;
31+
32+
import net.imagej.ops.Ops;
33+
import net.imagej.ops.special.function.AbstractUnaryFunctionOp;
34+
import net.imagej.ops.special.function.BinaryFunctionOp;
35+
import net.imagej.ops.special.function.Functions;
36+
import net.imglib2.Dimensions;
37+
import net.imglib2.RandomAccess;
38+
import net.imglib2.img.Img;
39+
import net.imglib2.type.NativeType;
40+
import net.imglib2.type.numeric.ComplexType;
41+
42+
import org.apache.commons.math3.linear.Array2DRowRealMatrix;
43+
import org.apache.commons.math3.linear.DecompositionSolver;
44+
import org.apache.commons.math3.linear.RealMatrix;
45+
import org.apache.commons.math3.linear.SingularValueDecomposition;
46+
import org.apache.commons.math3.special.BesselJ;
47+
import org.scijava.plugin.Parameter;
48+
import org.scijava.plugin.Plugin;
49+
50+
/**
51+
* Creates a Gibson Lanni Kernel.
52+
*
53+
* @author Jizhou Li
54+
* @author Brian Northan
55+
* @param <T>
56+
*/
57+
@Plugin(type = Ops.Create.KernelDiffraction.class)
58+
public class DefaultCreateKernelGibsonLanni<T extends ComplexType<T> & NativeType<T>>
59+
extends AbstractUnaryFunctionOp<Dimensions, Img<T>> implements
60+
Ops.Create.KernelDiffraction
61+
{
62+
63+
private BinaryFunctionOp<Dimensions, T, Img<T>> createOp;
64+
65+
// //////// physical parameters /////////////
66+
67+
@Parameter
68+
private double NA = 1.4; // numerical aperture
69+
70+
@Parameter
71+
private double lambda = 610E-09; // wavelength
72+
73+
@Parameter
74+
private double ns = 1.33; // specimen refractive index
75+
76+
@Parameter
77+
private double ni = 1.5; // immersion refractive index, experimental
78+
79+
@Parameter
80+
private double resLateral = 100E-9; // lateral pixel size
81+
82+
@Parameter
83+
private double resAxial = 250E-9; // axial pixel size
84+
85+
@Parameter
86+
private double pZ = 2000E-9D; // position of particle
87+
88+
@Parameter
89+
// HACK: Must declare class as ComplexType rather than T,
90+
// to avoid current Ops generics matching limitations.
91+
// Without this, the ConvertService returns null when
92+
// attempting to convert an input type instance to T.
93+
private ComplexType<T> type; // pixel type of created kernel
94+
95+
private double ng0 = 1.5; // coverslip refractive index, design value
96+
private double ng = 1.5; // coverslip refractive index, experimental
97+
private double ni0 = 1.5; // immersion refractive index, design
98+
private double ti0 = 150E-06; // working distance of the objective,
99+
// desig
100+
// a bit precision lost if use 170*1.0E-6
101+
private double tg0 = 170E-6; // coverslip thickness, design value
102+
private double tg = 170E-06; // coverslip thickness, experimental value
103+
104+
// ////////approximation parameters /////////////
105+
private int numBasis = 100; // number of basis functions
106+
private int numSamp = 1000; // number of sampling
107+
private int overSampling = 2; // overSampling
108+
109+
@SuppressWarnings({ "rawtypes", "unchecked" })
110+
@Override
111+
public void initialize() {
112+
createOp = (BinaryFunctionOp) Functions.binary(ops(), Ops.Create.Img.class,
113+
Img.class, Dimensions.class, NativeType.class);
114+
}
115+
116+
@Override
117+
public Img<T> calculate(Dimensions size) {
118+
int nx = -1; // psf size
119+
int ny = -1;
120+
int nz = -1;
121+
122+
if (size.numDimensions() == 2) {
123+
nx = (int) size.dimension(0);
124+
ny = (int) size.dimension(1);
125+
nz = 1;
126+
}
127+
else if (size.numDimensions() == 3) {
128+
nx = (int) size.dimension(0);
129+
ny = (int) size.dimension(1);
130+
nz = (int) size.dimension(2);
131+
}
132+
133+
// compute the distance between the particle position (pZ) and the center
134+
int distanceFromCenter = (int) Math.abs(Math.ceil(pZ / resAxial));
135+
136+
// increase z size so that the PSF is large enough so that we can later
137+
// recrop a centered psf
138+
nz = nz + 2 * distanceFromCenter;
139+
140+
double x0 = (nx - 1) / 2.0D;
141+
double y0 = (ny - 1) / 2.0D;
142+
143+
double xp = x0;
144+
double yp = y0;
145+
146+
int maxRadius = (int) Math.round(Math.sqrt((nx - x0) * (nx - x0) + (ny -
147+
y0) * (ny - y0))) + 1;
148+
double[] r = new double[maxRadius * this.overSampling];
149+
double[][] h = new double[nz][r.length];
150+
151+
double a = 0.0D;
152+
double b = Math.min(1.0D, this.ns / this.NA);
153+
154+
double k0 = 2 * Math.PI / this.lambda;
155+
double factor1 = 545 * 1.0E-9 / this.lambda;
156+
double factor = factor1 * this.NA / 1.4;
157+
double deltaRho = (b - a) / (this.numSamp - 1);
158+
159+
// basis construction
160+
double rho = 0.0D;
161+
double am = 0.0;
162+
double[][] Basis = new double[this.numSamp][this.numBasis];
163+
164+
BesselJ bj0 = new BesselJ(0);
165+
BesselJ bj1 = new BesselJ(1);
166+
167+
for (int m = 0; m < this.numBasis; m++) {
168+
// am = (3 * m + 1) * factor;
169+
am = (3 * m + 1);
170+
for (int rhoi = 0; rhoi < this.numSamp; rhoi++) {
171+
rho = rhoi * deltaRho;
172+
Basis[rhoi][m] = bj0.value(am * rho);
173+
}
174+
}
175+
176+
// compute the function to be approximated
177+
178+
double ti = 0.0D;
179+
double OPD = 0;
180+
double W = 0;
181+
182+
double[][] Coef = new double[nz][this.numBasis * 2];
183+
double[][] Ffun = new double[this.numSamp][nz * 2];
184+
185+
double rhoNA2;
186+
187+
for (int z = 0; z < nz; z++) {
188+
ti = (this.ti0 + this.resAxial * (z - (nz - 1.0D) / 2.0D));
189+
190+
for (int rhoi = 0; rhoi < this.numSamp; rhoi++) {
191+
rho = rhoi * deltaRho;
192+
rhoNA2 = rho * rho * this.NA * this.NA;
193+
194+
OPD = this.pZ * Math.sqrt(this.ns * this.ns - rhoNA2);
195+
OPD += this.tg * Math.sqrt(this.ng * this.ng - rhoNA2) - this.tg0 * Math
196+
.sqrt(this.ng0 * this.ng0 - rhoNA2);
197+
OPD += ti * Math.sqrt(this.ni * this.ni - rhoNA2) - this.ti0 * Math
198+
.sqrt(this.ni0 * this.ni0 - rhoNA2);
199+
200+
W = k0 * OPD;
201+
202+
Ffun[rhoi][z] = Math.cos(W);
203+
Ffun[rhoi][z + nz] = Math.sin(W);
204+
}
205+
}
206+
207+
// solve the linear system
208+
// begin....... (Using Common Math)
209+
210+
RealMatrix coefficients = new Array2DRowRealMatrix(Basis, false);
211+
RealMatrix rhsFun = new Array2DRowRealMatrix(Ffun, false);
212+
DecompositionSolver solver = new SingularValueDecomposition(coefficients)
213+
.getSolver(); // slower
214+
// but
215+
// more
216+
// accurate
217+
// DecompositionSolver solver = new
218+
// QRDecomposition(coefficients).getSolver(); // faster, less accurate
219+
220+
RealMatrix solution = solver.solve(rhsFun);
221+
Coef = solution.getData();
222+
223+
// end.......
224+
225+
double[][] RM = new double[this.numBasis][r.length];
226+
double beta = 0.0D;
227+
228+
double rm = 0.0D;
229+
for (int n = 0; n < r.length; n++) {
230+
r[n] = (n * 1.0 / this.overSampling);
231+
beta = k0 * this.NA * r[n] * this.resLateral;
232+
233+
for (int m = 0; m < this.numBasis; m++) {
234+
am = (3 * m + 1) * factor;
235+
rm = am * bj1.value(am * b) * bj0.value(beta * b) * b;
236+
rm = rm - beta * b * bj0.value(am * b) * bj1.value(beta * b);
237+
RM[m][n] = rm / (am * am - beta * beta);
238+
239+
}
240+
}
241+
242+
// obtain one component
243+
double maxValue = 0.0D;
244+
for (int z = 0; z < nz; z++) {
245+
for (int n = 0; n < r.length; n++) {
246+
double realh = 0.0D;
247+
double imgh = 0.0D;
248+
for (int m = 0; m < this.numBasis; m++) {
249+
realh = realh + RM[m][n] * Coef[m][z];
250+
imgh = imgh + RM[m][n] * Coef[m][z + nz];
251+
252+
}
253+
h[z][n] = realh * realh + imgh * imgh;
254+
}
255+
}
256+
257+
// assign
258+
259+
double[][] Pixel = new double[nz][nx * ny];
260+
261+
for (int z = 0; z < nz; z++) {
262+
263+
for (int x = 0; x < nx; x++) {
264+
for (int y = 0; y < ny; y++) {
265+
double rPixel = Math.sqrt((x - xp) * (x - xp) + (y - yp) * (y - yp));
266+
int index = (int) Math.floor(rPixel * this.overSampling);
267+
268+
double value = h[z][index] + (h[z][(index + 1)] - h[z][index]) *
269+
(rPixel - r[index]) * this.overSampling;
270+
Pixel[z][(x + nx * y)] = value;
271+
if (value > maxValue) {
272+
maxValue = value;
273+
}
274+
}
275+
}
276+
//
277+
278+
}
279+
280+
// create an RAI to store the PSF
281+
@SuppressWarnings("unchecked")
282+
final Img<T> psf3d = createOp.calculate(size, (T) type);
283+
284+
// use a RandomAccess to access pixels
285+
RandomAccess<T> ra = psf3d.randomAccess();
286+
287+
int start, finish;
288+
289+
// if the particle position, pZ is negative crop the bottom part of the
290+
// larger PSF
291+
if (pZ < 0) {
292+
start = 2 * distanceFromCenter;
293+
finish = nz;
294+
}
295+
// if the particle position, pZ is positive crop the top part of the larger
296+
// PSF
297+
else {
298+
start = 0;
299+
finish = nz - 2 * distanceFromCenter;
300+
}
301+
302+
// loop and copy pixel values from the PSF array to the output PSF RAI
303+
for (int z = start; z < finish; z++) {
304+
305+
for (int x = 0; x < nx; x++) {
306+
for (int y = 0; y < ny; y++) {
307+
308+
double value = Pixel[z][(x + nx * y)] / maxValue;
309+
310+
ra.setPosition(new int[] { x, y, z - start });
311+
ra.get().setReal(value);
312+
}
313+
}
314+
}
315+
316+
return psf3d;
317+
}
318+
319+
}

src/main/templates/net/imagej/ops/Ops.list

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ namespaces = ```
7575
[name: "kernelLog", iface: "KernelLog"],
7676
[name: "kernelBiGauss", iface: "KernelBiGauss"],
7777
[name: "kernel2ndDerivBiGauss", iface: "Kernel2ndDerivBiGauss"],
78+
[name: "kernelDiffraction", iface: "KernelDiffraction"],
7879
[name: "kernelGabor", iface: "KernelGabor"],
7980
[name: "kernelSobel", iface: "KernelSobel"],
8081
[name: "labelingMapping", iface: "LabelingMapping"],

src/test/java/net/imagej/ops/AbstractOpTest.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import java.net.URL;
3939
import java.util.Iterator;
4040
import java.util.Random;
41+
import java.util.stream.StreamSupport;
4142

4243
import net.imglib2.Cursor;
4344
import net.imglib2.FinalInterval;
@@ -257,6 +258,11 @@ public <T extends RealType<T>> boolean areCongruent(final IterableInterval<T> in
257258
return true;
258259
}
259260

261+
public <T extends RealType<T>> double[] asArray(final Iterable<T> image) {
262+
return StreamSupport.stream(image.spliterator(), false).mapToDouble(t -> t
263+
.getRealDouble()).toArray();
264+
}
265+
260266
public static class NoOp extends AbstractOp {
261267

262268
@Override

0 commit comments

Comments
 (0)