Commit 6abee726 by Chris Coughlin

Initial implementation of auto-thresholding in Canny edge detector

parent 552a6723
/*
* com.emphysic.myriad.core.data.ops.CannyOperation
*
* Copyright (c) 2016 Emphysic LLC.
* Copyright (c) 2017 Emphysic LLC.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......@@ -23,6 +23,7 @@ import com.emphysic.myriad.core.data.ops.math.GradientVector;
import com.emphysic.myriad.core.data.ops.math.Stats;
import com.emphysic.myriad.core.data.util.DatasetUtils;
import lombok.extern.slf4j.Slf4j;
import smile.math.Math;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;
......@@ -60,6 +61,26 @@ public class CannyOperation implements DatasetOperation {
private double lowerThreshold = 1;
/**
* Whether and how to attempt automatic calculation of the lower and upper thresholds.
* NONE - do not attempt to automatically calculate
* MEAN - use the mean of the input data to calculate lower and upper thresholds
* MEDIAN - use the median of the input data to calculate lower and upper thresholds
* OTSU - use Otsu's Method (https://en.wikipedia.org/wiki/Otsu%27s_method ) to calculate lower and upper thresholds
* (not yet implemented)
*/
public enum AutoThreshold {NONE, MEAN, MEDIAN, OTSU}
/**
* Whether and how to automatically calculate thresholds
*/
private AutoThreshold autoThreshold = AutoThreshold.NONE;
/**
* Standard deviation threshold for mean and median calculation of thresholds
*/
private double sigmaThreshold = 0.33;
/**
* Cardinal points - horizontal, vertical, and diagonal. Local gradient directions are rounded to one of
* these directions: 0, 45, 90, 135 (i.e. horizontal, vertical, or diagonal).
*/
......@@ -69,10 +90,21 @@ public class CannyOperation implements DatasetOperation {
* Constructs a new Canny Edge detector with the specified denoise operation and gradient operation
* @param blurOperation BlurOperation used to smooth and denoise input data
* @param convolutionOperation ConvolutionOperation used to calculate horizontal and vertical gradients
* @param autoThreshold whether/how to automatically determine thresholds
*/
public CannyOperation(BlurOperation blurOperation, SobelOperation convolutionOperation) {
public CannyOperation(BlurOperation blurOperation, SobelOperation convolutionOperation, AutoThreshold autoThreshold) {
this.blurOperation = blurOperation;
this.gradientOperation = convolutionOperation;
this.autoThreshold = autoThreshold;
}
/**
* Constructs a new Canny Edge detector with the specified denoise operation and gradient operation
* @param blurOperation BlurOperation used to smooth and denoise input data
* @param convolutionOperation ConvolutionOperation used to calculate horizontal and vertical gradients
*/
public CannyOperation(BlurOperation blurOperation, SobelOperation convolutionOperation) {
this(blurOperation, convolutionOperation, AutoThreshold.NONE);
}
/**
......@@ -96,6 +128,26 @@ public class CannyOperation implements DatasetOperation {
public Dataset run(Dataset input) {
if (input != null) {
Dataset result = new Dataset(input.getWidth(), input.getHeight());
switch (autoThreshold) {
case MEAN:
// Set thresholds w. mean
// (1-sigma)*[mean value] and set the high threshold to (1+sigma)*[mean value]
Double mean = Math.mean(result.getData());
setLowerThreshold((1 - sigmaThreshold) * mean);
setUpperThreshold((1 + sigmaThreshold) * mean);
break;
case MEDIAN:
// Set thresholds w. median
// (1-sigma)*[median value] and (1+sigma)*[median value]
Double median = Math.median(result.getData());
setLowerThreshold((1 - sigmaThreshold) * median);
setUpperThreshold((1 + sigmaThreshold) * median);
break;
case OTSU:
// Not yet implemented
default:
break;
}
// Denoise the input data
Dataset smoothed = blurOperation.run(input);
// Calculate the grdadient
......@@ -324,6 +376,39 @@ public class CannyOperation implements DatasetOperation {
this.lowerThreshold = lowerThreshold;
}
/**
* Returns the current settings for auto thresholding
* @return AutoThreshold enum
*/
public AutoThreshold getAutoThreshold() {
return autoThreshold;
}
/**
* Sets the auto thresholding
* @param autoThreshold new thresholding setting
*/
public void setAutoThreshold(AutoThreshold autoThreshold) {
this.autoThreshold = autoThreshold;
}
/**
* Returns the current threshold parameter for auto-thresholding based on the mean or median.
* @return threshold parameter (0-1)
*/
public double getSigmaThreshold() {
return sigmaThreshold;
}
/**
* Sets the threshold parameter for auto-thresholding based on the mean or median
* @param sigmaThreshold parameter between 0 and 1
*/
public void setSigmaThreshold(double sigmaThreshold) {
this.sigmaThreshold = sigmaThreshold;
}
@Override
public long getSerializationVersion() {
return serialVersionUID;
......@@ -339,6 +424,8 @@ public class CannyOperation implements DatasetOperation {
Map<String, Object> map = DatasetOperation.super.getObjectMap();
map.put("upper_threshold", upperThreshold);
map.put("lower_threshold", lowerThreshold);
map.put("auto_threshold", autoThreshold);
map.put("sigma_threshold", sigmaThreshold);
if (blurOperation != null) {
map.put("blurclz", blurOperation.getClass());
map.put("blurmap", blurOperation.getObjectMap());
......@@ -354,6 +441,8 @@ public class CannyOperation implements DatasetOperation {
public void initCurrentVersion(Map<String, Object> objectMap) {
upperThreshold = (double) objectMap.getOrDefault("upper_threshold", upperThreshold);
lowerThreshold = (double) objectMap.getOrDefault("lower_threshold", lowerThreshold);
autoThreshold = (AutoThreshold) objectMap.getOrDefault("auto_threshold", autoThreshold);
sigmaThreshold = (double) objectMap.getOrDefault("sigma_threshold", sigmaThreshold);
if (objectMap.containsKey("blurclz")) {
Class<? extends BlurOperation> clz = (Class<? extends BlurOperation>) objectMap.get("blurclz");
try {
......
/*
* com.emphysic.myriad.core.data.ops.CannyOperationTest
*
* Copyright (c) 2016 Emphysic LLC.
* Copyright (c) 2017 Emphysic LLC.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......@@ -150,10 +150,15 @@ public class CannyOperationTest {
Random random = new Random();
int blurRadius = random.nextInt(3);
CannyOperation original = new CannyOperation(blurRadius);
original.setAutoThreshold(CannyOperation.AutoThreshold.MEDIAN);
File out = File.createTempFile("tmpcanny", "dat");
original.save(out);
CannyOperation read = new CannyOperation();
read.load(out);
assertEquals(original.getAutoThreshold(), read.getAutoThreshold());
assertEquals(original.getSigmaThreshold(), read.getSigmaThreshold(), 0.05 * original.getSigmaThreshold());
assertEquals(original.getUpperThreshold(), read.getUpperThreshold(), 0.05 * original.getUpperThreshold());
assertEquals(original.getLowerThreshold(), read.getLowerThreshold(), 0.05 * original.getLowerThreshold());
assertArraysEqual(original.run(orig).getData(), read.run(orig).getData());
}
}
\ No newline at end of file
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