@@ -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,11 @@ 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+ method_timer.start_timer (" autoParameters" );
466+ // autoParameters(local_scale_temp,grad_temp);
467+ autoParametersLiEntropy (local_scale_temp2, local_scale_temp, grad_temp);
468+ aAPR.parameters = par;
469+ method_timer.stop_timer ();
463470 }
464471
465472 applyParameters (aAPR,par);
@@ -587,92 +594,152 @@ inline bool APRConverter<ImageType>::get_apr(APR &aAPR, PixelData<T>& input_imag
587594 return true ;
588595}
589596
590- template <typename ImageType>
591- template <typename T,typename S>
592- void APRConverter<ImageType>::autoParameters(const PixelData<T> &localIntensityScale,const PixelData<S> &grad){
593- /*
594- * Assumes a dark background. Please use the Python interactive parameter selection for more detailed approaches.
595- *
596- * Finds the flatest (1% of the image) and estimates the noise level in the gradient and sets the grad threshold from that.
597- */
597+ template <typename T>
598+ void compute_means (const std::vector<T>& data, float threshold, float & mean_back, float & mean_fore) {
599+ float sum_fore=0 .f , sum_back=0 .f ;
600+ size_t count_fore=0 , count_back=0 ;
598601
602+ #ifdef HAVE_OPENMP
603+ #pragma omp parallel for default(none) shared(data, threshold) reduction(+:sum_fore, sum_back, count_fore, count_back)
604+ #endif
605+ for (size_t idx = 0 ; idx < data.size (); ++idx) {
606+ if (data[idx] > threshold) {
607+ sum_fore += data[idx];
608+ count_fore++;
609+ } else {
610+ sum_back += data[idx];
611+ count_back++;
612+ }
613+ }
614+ mean_fore = sum_fore / count_fore;
615+ mean_back = sum_back / count_back;
616+ }
599617
600- // need to select some pixels. we need some buffer room in the image.
601- const size_t total_required_pixels = std::min ((size_t ) 10 *512 *512 ,localIntensityScale.mesh .size ()/2 );
602- const size_t delta = localIntensityScale.mesh .size ()/total_required_pixels - 1 ;
603618
604- std::vector<T> lis_buffer (total_required_pixels);
605- std::vector<S> grad_buffer (total_required_pixels);
619+ /* *
620+ * Compute threshold value by Li's iterative Minimum Cross Entropy method [1]
621+ *
622+ * Note: it is assumed that the input elements are non-negative (as is the case for the gradient and local intensity
623+ * scale). To apply the method to data that may be negative, subtract the minimum value from the input, and then add
624+ * that value to the computed threshold.
625+ *
626+ * [1] Li, C. H., & Tam, P. K. S. (1998). "An iterative algorithm for minimum cross entropy thresholding."
627+ * Pattern recognition letters, 19(8), 771-776.
628+ */
629+ template <typename T>
630+ float threshold_li (const std::vector<T> &input) {
606631
607- uint64_t counter = 0 ;
608- uint64_t counter_sampled = 0 ;
632+ if (input.empty ()) { return 0 .f ; } // return 0 if input is empty
609633
610- while ((counter < localIntensityScale.mesh .size ()) && (counter_sampled < total_required_pixels)){
634+ const T image_min = *std::min_element (input.begin (), input.end ());
635+ const T image_max = *std::max_element (input.begin (), input.end ());
611636
612- lis_buffer[counter_sampled] = localIntensityScale.mesh [counter];
613- grad_buffer[counter_sampled] = grad.mesh [counter];
637+ if (image_min == image_max) { return image_min; } // if all inputs are equal, return that value
614638
615- counter+=delta;
616- counter_sampled++;
639+ float tolerance = 0 .5f ; // tolerance 0.5 should suffice for integer inputs
617640
641+ // For floating point inputs we set the tolerance to the lesser of 0.01 and a fraction of the data range
642+ // This could be improved, by instead taking e.g. half the smallest difference between any two non-equal elements
643+ if (std::is_floating_point<T>::value) {
644+ float range = image_max - image_min;
645+ tolerance = std::min (0 .01f , range*1e-4f );
618646 }
619647
648+ // Take the mean of the input as initial value
649+ float t_next = std::accumulate (input.begin (), input.end (), 0 .f ) / (float ) input.size ();
650+ float t_curr = -2 .f * tolerance; // this ensures that the first iteration is performed, since t_next is non-negative
620651
621- // float min_lis = *std::min_element(lis_buffer.begin(),lis_buffer.end());
622- float max_lis = *std::max_element (lis_buffer.begin (),lis_buffer.end ());
623-
624- std::vector<uint64_t > hist_lis;
625- hist_lis.resize (std::ceil (max_lis)+1 ,0 );
626-
627- for (uint64_t i = 0 ; i < total_required_pixels; ++i) {
628- auto lis_val = std::floor (lis_buffer[i]);
629- hist_lis[lis_val]++;
652+ // For integer inputs, we have to ensure a large enough initial value, such that mean_back > 0
653+ if (!std::is_floating_point<T>::value) {
654+ // if initial value is <1, try to increase it to 1.5 unless the range is too narrow
655+ if (t_next < 1 .f ) {
656+ t_next = std::min (1 .5f , (image_min+image_max)/2 .f );
657+ }
630658 }
631659
632- // Then find 5% therhold, and take the grad values from that.
633- uint64_t prop_values = 0.05 *total_required_pixels;
660+ // Perform Li iterations until convergence
661+ while (std::abs (t_next - t_curr) > tolerance) {
662+ t_curr = t_next;
634663
635- uint64_t cumsum = 0 ;
636- uint64_t freq_val=0 ;
637- while (cumsum < prop_values){
638- cumsum+= hist_lis[freq_val];
639- freq_val++;
664+ // Compute averages of values above and below the current threshold
665+ float mean_back, mean_fore;
666+ compute_means (input, t_curr, mean_back, mean_fore);
667+
668+ // Handle the edge case where all values < t_curr are 0
669+ if (mean_back == 0 ) {
670+ std::wcout << " log(0) encountered in APRConverter::threshold_li, returning current threshold" << std::endl;
671+ return t_curr;
672+ }
640673
674+ // Update the threshold (one-point iteration)
675+ t_next = (mean_fore - mean_back) / (std::log (mean_fore) - std::log (mean_back));
641676 }
677+ return t_next;
678+ }
642679
643- std::vector<S> grad_hist;
644- float grad_max = *std::max_element (grad_buffer.begin (),grad_buffer.end ());
645- // float grad_min = *std::max_element(grad_buffer.begin(),grad_buffer.end());
646680
647- grad_hist.resize (std::ceil (grad_max));
648- uint64_t grad_counter = 0 ;
681+ template <typename ImageType>
682+ template <typename T,typename S>
683+ void APRConverter<ImageType>::autoParametersLiEntropy(const PixelData<T> &image,
684+ const PixelData<T> &localIntensityScale,
685+ const PixelData<S> &grad) {
686+
687+ fine_grained_timer.start_timer (" autoparameters: subsample buffers" );
688+
689+ std::vector<S> grad_subsampled;
690+ std::vector<T> lis_subsampled;
691+
692+ { // intentional scope
693+ // / First we extract the gradient and local intensity scale values at all locations where the image
694+ // / intensity exceeds the intensity threshold. This allows better adaptation in certain cases, e.g.
695+ // / when there is significant background AND signal noise/autofluorescence (provided that par.Ip_th
696+ // / is set appropriately).
697+ std::vector<S> grad_foreground (grad.mesh .size ());
698+ std::vector<T> lis_foreground (localIntensityScale.mesh .size ());
699+
700+ const auto threshold = par.Ip_th + bspline_offset;
701+ size_t counter = 0 ;
702+ for (size_t idx = 0 ; idx < grad.mesh .size (); ++idx) {
703+ if (image.mesh [idx] > threshold) {
704+ grad_foreground[counter] = grad.mesh [idx];
705+ lis_foreground[counter] = localIntensityScale.mesh [idx];
706+ counter++;
707+ }
708+ }
649709
650- for (uint64_t i = 0 ; i < total_required_pixels; ++i) {
651- auto val = lis_buffer[i];
710+ const size_t num_foreground_pixels = counter;
652711
653- if (val <= freq_val){
654- auto grad_val = grad_buffer[i];
655- grad_hist[std::floor (grad_val)]++;
656- grad_counter++;
657- }
658- }
712+ grad_foreground.resize (num_foreground_pixels); // setting size to non-zero elements.
713+ lis_foreground.resize (num_foreground_pixels);
659714
660- auto max_it = std::max_element (grad_hist.begin (),grad_hist.end ());
661- uint64_t mode = std::distance (grad_hist.begin (),max_it);
715+ // / Then we uniformly subsample these signals, as we typically don't need all elements to compute the thresholds
716+ const size_t num_elements = std::min ((size_t )32 *512 *512 , num_foreground_pixels); // arbitrary number.
717+ const size_t delta = num_foreground_pixels / num_elements;
718+ grad_subsampled.resize (num_elements);
719+ lis_subsampled.resize (num_elements);
662720
663- float grad_th = std::round (4 *mode); // magic numbers.
721+ #ifdef HAVE_OPENMP
722+ #pragma omp parallel for default(shared)
723+ #endif
724+ for (size_t idx = 0 ; idx < num_elements; ++idx) {
725+ grad_subsampled[idx] = grad_foreground[idx*delta];
726+ lis_subsampled[idx] = lis_foreground[idx*delta];
727+ }
728+ }
729+ fine_grained_timer.stop_timer ();
664730
665- par. grad_th = grad_th ;
666- par.sigma_th = freq_val ;
667- par. sigma_th_max = 1 ;
731+ fine_grained_timer. start_timer ( " autoparameters: compute gradient threshold " ) ;
732+ par.grad_th = threshold_li (grad_subsampled) ;
733+ fine_grained_timer. stop_timer () ;
668734
669- std::cout << " Used parameters: " << std::endl;
670- std::cout << " I_th: " << par.Ip_th << std::endl;
671- std::cout << " sigma_th: " << par.sigma_th << std::endl;
672- std::cout << " grad_th: " << par.grad_th << std::endl;
673- std::cout << " relative error (E): " << par.rel_error << std::endl;
674- std::cout << " lambda: " << par.lambda << std::endl;
735+ fine_grained_timer.start_timer (" autoparameters: compute sigma threshold" );
736+ par.sigma_th = threshold_li (lis_subsampled);
737+ fine_grained_timer.stop_timer ();
675738
739+ if (verbose) {
740+ std::cout << " Automatic parameter tuning found sigma_th = " << par.sigma_th <<
741+ " and grad_th = " << par.grad_th << std::endl;
742+ }
676743}
677744
678745
0 commit comments