Skip to content

Commit a41145b

Browse files
committed
add autoparameters method based on Li minimum cross entropy thresholding
1 parent 996b205 commit a41145b

File tree

1 file changed

+155
-1
lines changed

1 file changed

+155
-1
lines changed

src/algorithm/APRConverter.hpp

Lines changed: 155 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,9 @@ class APRConverter {
113113
template<typename T,typename S>
114114
void autoParameters(const PixelData<T> &localIntensityScale,const PixelData<S> &grad);
115115

116+
template<typename T,typename S>
117+
void autoParametersLiEntropy(const PixelData<T> &image, const PixelData<T> &localIntensityScale, const PixelData<S> &grad);
118+
116119
template<typename T>
117120
bool check_input_dimensions(PixelData<T> &input_image);
118121

@@ -459,7 +462,8 @@ inline bool APRConverter<ImageType>::get_apr(APR &aAPR, PixelData<T>& input_imag
459462
computation_timer.start_timer("apply_parameters");
460463

461464
if( par.auto_parameters ) {
462-
autoParameters(local_scale_temp,grad_temp);
465+
// autoParameters(local_scale_temp,grad_temp);
466+
autoParametersLiEntropy(local_scale_temp2, local_scale_temp, grad_temp);
463467
}
464468

465469
applyParameters(aAPR,par);
@@ -676,6 +680,156 @@ void APRConverter<ImageType>::autoParameters(const PixelData<T> &localIntensityS
676680
}
677681

678682

683+
template<typename T>
684+
void compute_means(const std::vector<T>& data, const float threshold, float& mean_back, float& mean_fore) {
685+
float sum_fore=0.f, sum_back=0.f;
686+
size_t count_fore=0, count_back=0;
687+
688+
#ifdef HAVE_OPENMP
689+
#pragma omp parallel for default(none) shared(data, threshold) reduction(+:sum_fore, sum_back, count_fore, count_back)
690+
#endif
691+
for(size_t idx = 0; idx < data.size(); ++idx) {
692+
if(data[idx] > threshold) {
693+
sum_fore += data[idx];
694+
count_fore++;
695+
} else {
696+
sum_back += data[idx];
697+
count_back++;
698+
}
699+
}
700+
mean_fore = sum_fore / count_fore;
701+
mean_back = sum_back / count_back;
702+
}
703+
704+
705+
/**
706+
* Compute threshold value by Li's iterative Minimum Cross Entropy method [1]
707+
*
708+
* Note: it is assumed that the input elements are non-negative (as is the case for the gradient and local intensity
709+
* scale). To apply the method to data that may be negative, subtract the minimum value from the input, and then add
710+
* that value to the computed threshold.
711+
*
712+
* [1] Li, C. H., & Tam, P. K. S. (1998). "An iterative algorithm for minimum cross entropy thresholding."
713+
* Pattern recognition letters, 19(8), 771-776.
714+
*/
715+
template<typename T>
716+
float threshold_li(const std::vector<T> &input) {
717+
718+
if(input.empty()) { return 0.f; } // return 0 if input is empty
719+
720+
const T image_min = *std::min_element(input.begin(), input.end());
721+
const T image_max = *std::max_element(input.begin(), input.end());
722+
723+
if(image_min == image_max) { return image_min; } // if all inputs are equal, return that value
724+
725+
float tolerance = 0.5f; // tolerance 0.5 should suffice for integer inputs
726+
727+
// For floating point inputs we set the tolerance to the lesser of 0.01 and a fraction of the data range
728+
// This could be improved, by instead taking e.g. half the smallest difference between any two non-equal elements
729+
if(std::is_floating_point<T>::value) {
730+
float range = image_max - image_min;
731+
tolerance = std::min(0.01f, range*1e-4f);
732+
}
733+
734+
// Take the mean of the input as initial value
735+
float t_next = std::accumulate(input.begin(), input.end(), 0.f) / (float) input.size();
736+
float t_curr = -2.f * tolerance; //this ensures that the first iteration is performed, since t_next is non-negative
737+
738+
// For integer inputs, we have to ensure a large enough initial value, such that mean_back > 0
739+
if(!std::is_floating_point<T>::value) {
740+
// if initial value is <1, try to increase it to 1.5 unless the range is too narrow
741+
if(t_next < 1.f) {
742+
t_next = std::min(1.5f, (image_min+image_max)/2.f);
743+
}
744+
}
745+
746+
// Perform Li iterations until convergence
747+
while(std::abs(t_next - t_curr) > tolerance) {
748+
t_curr = t_next;
749+
750+
// Compute averages of values above and below the current threshold
751+
float mean_back, mean_fore;
752+
compute_means(input, t_curr, mean_back, mean_fore);
753+
754+
// Handle the edge case where all values < t_curr are 0
755+
if(mean_back == 0) {
756+
std::wcout << "log(0) encountered in APRConverter::threshold_li, returning current threshold" << std::endl;
757+
return t_curr;
758+
}
759+
760+
// Update the threshold (one-point iteration)
761+
t_next = (mean_fore - mean_back) / (std::log(mean_fore) - std::log(mean_back));
762+
}
763+
return t_next;
764+
}
765+
766+
767+
template<typename ImageType>
768+
template<typename T,typename S>
769+
void APRConverter<ImageType>::autoParametersLiEntropy(const PixelData<T> &image,
770+
const PixelData<T> &localIntensityScale,
771+
const PixelData<S> &grad) {
772+
APRTimer li_timer(false);
773+
774+
li_timer.start_timer("subsample");
775+
776+
std::vector<S> grad_subsampled;
777+
std::vector<T> lis_subsampled;
778+
779+
{ // intentional scope
780+
/// First we extract the gradient and local intensity scale values at all locations where the image
781+
/// intensity exceeds the intensity threshold. This allows better adaptation in certain cases, e.g.
782+
/// when there is significant background AND signal noise/autofluorescence (provided that par.Ip_th
783+
/// is set appropriately).
784+
std::vector<S> grad_foreground(grad.mesh.size());
785+
std::vector<T> lis_foreground(localIntensityScale.mesh.size());
786+
787+
const auto threshold = par.Ip_th + bspline_offset;
788+
size_t counter = 0;
789+
for(size_t idx = 0; idx < grad.mesh.size(); ++idx) {
790+
if(image.mesh[idx] > threshold) {
791+
grad_foreground[counter] = grad.mesh[idx];
792+
lis_foreground[counter] = localIntensityScale.mesh[idx];
793+
counter++;
794+
}
795+
}
796+
797+
grad_foreground.resize(counter);
798+
lis_foreground.resize(counter);
799+
800+
/// Then we uniformly subsample these signals, as we typically don't need all elements to compute the thresholds
801+
const size_t num_elements = std::min((size_t)32*512*512, counter);
802+
const size_t delta = grad_foreground.size() / num_elements;
803+
grad_subsampled.resize(num_elements);
804+
lis_subsampled.resize(num_elements);
805+
806+
#ifdef HAVE_OPENMP
807+
#pragma omp parallel for default(shared)
808+
#endif
809+
for(size_t idx = 0; idx < num_elements; ++idx) {
810+
grad_subsampled[idx] = grad_foreground[idx*delta];
811+
lis_subsampled[idx] = lis_foreground[idx*delta];
812+
}
813+
}
814+
li_timer.stop_timer();
815+
816+
li_timer.start_timer("threshold_gradient");
817+
par.grad_th = threshold_li(grad_subsampled);
818+
li_timer.stop_timer();
819+
820+
li_timer.start_timer("threshold_lis");
821+
par.sigma_th = threshold_li(lis_subsampled);
822+
li_timer.stop_timer();
823+
824+
std::cout << "Used parameters: " << std::endl;
825+
std::cout << "I_th: " << par.Ip_th << std::endl;
826+
std::cout << "sigma_th: " << par.sigma_th << std::endl;
827+
std::cout << "grad_th: " << par.grad_th << std::endl;
828+
std::cout << "relative error (E): " << par.rel_error << std::endl;
829+
std::cout << "lambda: " << par.lambda << std::endl;
830+
}
831+
832+
679833
/**
680834
* Checks if the memory dimension (y) is filled
681835
*/

0 commit comments

Comments
 (0)