@@ -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