Skip to content

Commit

Permalink
Merge branch 'newsolver' into newexport
Browse files Browse the repository at this point in the history
  • Loading branch information
trautmane committed Jan 18, 2025
2 parents ef26be1 + c28048f commit 2df09b3
Show file tree
Hide file tree
Showing 59 changed files with 3,594 additions and 177 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package org.janelia.alignment.destreak;

import ij.IJ;
import ij.ImagePlus;
import ij.process.FloatProcessor;
import ij.process.ImageProcessor;

import java.io.Serializable;

/**
* This class detects streaks in an image and returns a corresponding mask.
* <p>
* The finder first applies a derivative filter in the x-direction to detect vertical edges. Then, it applies a mean
* filter in the y-direction to smooth out the edges in the y-direction. The resulting image is then thresholded
* (from above and below) to create a mask of the streaks. Finally, an optional Gaussian blur is applied to the mask to
* smooth it. The mask is 0 where there are no streaks and 255 where there are streaks.
* <p>
* There are three parameters that can be set:
* <ul>
* <li>meanFilterSize: the number of pixels to average in the y-direction (e.g., 0 means no averaging, 50 means averaging +/-50 pixels in y)</li>
* <li>threshold: the threshold used to convert the streak mask to a binary mask</li>
* <li>blurRadius: the radius of the Gaussian blur applied to the streak mask (0 means no smoothing)</li>
* </ul>
*/
public class StreakFinder implements Serializable {

private final int meanFilterSize;
private final double threshold;
private final int blurRadius;

public StreakFinder(final int meanFilterSize, final double threshold, final int blurRadius) {
if (meanFilterSize < 0) {
throw new IllegalArgumentException("meanFilterSize must be non-negative");
}
if (threshold < 0) {
throw new IllegalArgumentException("threshold must be non-negative");
}
if (blurRadius < 0) {
throw new IllegalArgumentException("blurRadius must be 0 (no blur) or positive");
}

this.meanFilterSize = meanFilterSize;
this.threshold = threshold;
this.blurRadius = blurRadius;
}

public ImagePlus createStreakMask(final ImagePlus input) {
ImageProcessor filtered = differenceFilterX(input.getProcessor());
filtered = meanFilterY(filtered, meanFilterSize);
filtered = bidirectionalThreshold(filtered, threshold);

final ImagePlus mask = new ImagePlus("Mask", filtered);
if (blurRadius > 0) {
IJ.run(mask, "Gaussian Blur...", String.format("sigma=%d", blurRadius));
}
return mask;
}

private static ImageProcessor differenceFilterX(final ImageProcessor in) {
final ImageProcessor out = new FloatProcessor(in.getWidth(), in.getHeight());
final int width = in.getWidth();
final int height = in.getHeight();

for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
final float left = in.getf(projectPeriodically(x - 1, width), y);
final float right = in.getf(projectPeriodically(x + 1, width), y);
out.setf(x, y, (right - left) / 2);
}
}
return out;
}

private static ImageProcessor meanFilterY(final ImageProcessor in, final int size) {
final ImageProcessor out = new FloatProcessor(in.getWidth(), in.getHeight());
final int width = in.getWidth();
final int height = in.getHeight();
final int n = 2 * size + 1;

for (int x = 0; x < width; x++) {
// initialize running sum
float sum = in.getf(x, 0);
for (int y = 1; y <= size; y++) {
sum += 2 * in.getf(x, y);
}
out.setf(x, 0, sum / n);

// update running sum by adding the next value and subtracting the oldest value
for (int y = 1; y < height; y++) {
final float oldest = in.getf(x, projectPeriodically(y - size - 1, height));
final float newest = in.getf(x, projectPeriodically(y + size, height));
sum += newest - oldest;
out.setf(x, y, sum / n);
}
}
return out;
}

private static ImageProcessor bidirectionalThreshold(final ImageProcessor in, final double threshold) {
final ImageProcessor out = new FloatProcessor(in.getWidth(), in.getHeight());
final int width = in.getWidth();
final int height = in.getHeight();

for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
final float value = Math.abs(in.getf(x, y));
out.setf(x, y, (value > threshold) ? 255 : 0);
}
}
return out;
}

private static int projectPeriodically(final int index, final int max) {
if (index < 0) {
return -index;
} else if (index >= max) {
return 2 * max - index - 2;
} else {
return index;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package org.janelia.alignment.filter;

import ij.process.ImageProcessor;

import java.util.LinkedHashMap;
import java.util.Map;

/**
* A simple filter that applies an affine transformation y = a*x + b to the intensity values of an image.
*/
public class AffineIntensityFilter implements Filter {

private double a;
private double b;

// empty constructor required to create instances from specifications
@SuppressWarnings("unused")
public AffineIntensityFilter() {
this(0.0, 0.0);
}

public AffineIntensityFilter(final double a, final double b) {
this.a = a;
this.b = b;
}

@Override
public void init(final Map<String, String> params) {
this.a = Filter.getDoubleParameter("a", params);
this.b = Filter.getDoubleParameter("b", params);
}

@Override
public Map<String, String> toParametersMap() {
final Map<String, String> map = new LinkedHashMap<>();
map.put("a", String.valueOf(a));
map.put("b", String.valueOf(b));
return map;
}

@Override
public void process(final ImageProcessor ip, final double scale) {
for (int y = 0; y < ip.getHeight(); y++) {
for (int x = 0; x < ip.getWidth(); x++) {
final float intensity = ip.getf(x, y);
ip.setf(x, y, (float) (a * intensity + b));
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,14 @@ public class CompositeFilter implements Filter {
// empty constructor required to create instances from specifications
@SuppressWarnings("unused")
public CompositeFilter() {
this((List<Filter>) null);
}

public CompositeFilter(final Filter... filters) {
this(List.of(filters));
}

public CompositeFilter(final List<Filter> filters) {
this.filters = filters;
this.filters = new ArrayList<>(filters);
}

@Override
Expand All @@ -50,7 +49,7 @@ public Map<String, String> toParametersMap() {
return map;
}

private static String filterKey(final int i) {
static String filterKey(final int i) {
return "filter" + i;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,5 +60,4 @@ public void process(final ImageProcessor ip, final double scale) {
}
}
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.janelia.alignment.filter;

import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;

import org.janelia.alignment.json.JsonUtils;
Expand Down Expand Up @@ -96,6 +97,33 @@ public static FilterSpec forFilter(final Filter filter) {
return new FilterSpec(filter.getClass().getName(), filter.toParametersMap());
}

public static FilterSpec combine(final FilterSpec first, final FilterSpec second) {
if (first == null || second == null) {
return first == null ? second : first;
}

final FilterSpec firstAsComposite = asCompositeSpec(first);
final FilterSpec secondAsComposite = asCompositeSpec(second);

// both are guaranteed to be composite filters -> unpack both, append, and repack
final Map<String, String> parameters = new HashMap<>(firstAsComposite.parameters);
final int shift = firstAsComposite.parameters.size();
for (int i = 0; i < secondAsComposite.parameters.size(); i++) {
final String key = CompositeFilter.filterKey(i + shift);
final String value = secondAsComposite.parameters.get(CompositeFilter.filterKey(i));
parameters.put(key, value);
}
return new FilterSpec(CompositeFilter.class.getName(), parameters);
}

private static FilterSpec asCompositeSpec(final FilterSpec filterSpec) {
if (filterSpec.className.equals(CompositeFilter.class.getName())) {
return filterSpec;
}
return new FilterSpec(CompositeFilter.class.getName(),
Map.of(CompositeFilter.filterKey(0), filterSpec.toJson()));
}

private Class<?> getClazz() throws IllegalArgumentException {
if (clazz == null) {
if (className == null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package org.janelia.alignment.filter;

import ij.process.ImageProcessor;
import org.janelia.alignment.filter.emshading.FourthOrderShading;
import org.janelia.alignment.filter.emshading.QuadraticShading;
import org.janelia.alignment.filter.emshading.ShadingModel;

import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

public class ShadingCorrectionFilter implements Filter {

public enum ShadingCorrectionMethod {
QUADRATIC(QuadraticShading::new),
FOURTH_ORDER(FourthOrderShading::new);

private final Function<double[], ShadingModel> modelFactory;

ShadingCorrectionMethod(final Function<double[], ShadingModel> modelFactory) {
this.modelFactory = modelFactory;
}

public ShadingModel create(final double[] coefficients) {
return modelFactory.apply(coefficients);
}

public static ShadingCorrectionMethod fromString(final String method) {
for (final ShadingCorrectionMethod shadingCorrectionMethod : values()) {
if (shadingCorrectionMethod.name().equalsIgnoreCase(method)) {
return shadingCorrectionMethod;
}
}
throw new IllegalArgumentException("Unknown shading correction method: " + method);
}

public static ShadingCorrectionMethod forModel(final ShadingModel model) {
if (model instanceof QuadraticShading) {
return QUADRATIC;
} else if (model instanceof FourthOrderShading) {
return FOURTH_ORDER;
} else {
throw new IllegalArgumentException("Unknown shading model class: " + model.getClass().getName());
}
}
}

private ShadingCorrectionMethod correctionMethod;
private double[] coefficients;

// empty constructor required to create instances from specifications
@SuppressWarnings("unused")
public ShadingCorrectionFilter() {
}

public ShadingCorrectionFilter(final ShadingModel model) {
this(ShadingCorrectionMethod.forModel(model), model.getCoefficients());
}

public ShadingCorrectionFilter(final ShadingCorrectionMethod correctionMethod, final double[] coefficients) {
this.correctionMethod = correctionMethod;
this.coefficients = coefficients;
}

@Override
public void init(final Map<String, String> params) {
this.correctionMethod = ShadingCorrectionMethod.fromString(Filter.getStringParameter("correctionMethod", params));
final String[] rawCoefficients = Filter.getCommaSeparatedStringParameter("coefficients", params);
this.coefficients = Arrays.stream(rawCoefficients).mapToDouble(Double::parseDouble).toArray();
}

@Override
public Map<String, String> toParametersMap() {
final Map<String, String> map = new LinkedHashMap<>();
map.put("correctionMethod", correctionMethod.name());
map.put("coefficients", Arrays.stream(coefficients).mapToObj(String::valueOf).collect(Collectors.joining(",")));
return map;
}

@Override
public void process(final ImageProcessor ip, final double scale) {
// transform pixel coordinates into [-1, 1] x [-1, 1]
final double scaleX = ip.getWidth() / 2.0;
final double scaleY = ip.getHeight() / 2.0;
final ShadingModel shadingModel = correctionMethod.create(coefficients);

// subtract shading model from image
final double[] location = new double[2];
for (int i = 0; i < ip.getWidth(); i++) {
for (int j = 0; j < ip.getHeight(); j++) {
location[0] = ShadingModel.scaleCoordinate(i, scaleX);
location[1] = ShadingModel.scaleCoordinate(j, scaleY);
shadingModel.applyInPlace(location);

final double value = ip.getPixelValue(i, j) - location[0];
ip.putPixelValue(i, j, value);
}
}
}
}
Loading

0 comments on commit 2df09b3

Please sign in to comment.