docs: def otsu3(data, min_threshold=None, max_threshold=None,bins=128):    
      """Compute a threshold using a 3-category Otsu-like method
      
      data           - an array of intensity values between zero and one
      min_threshold  - only consider thresholds above this minimum value
      max_threshold  - only consider thresholds below this maximum value
      bins           - we bin the data into this many equally-spaced bins, then pick
                       the bin index that optimizes the metric
      
      We find the maximum weighted variance, breaking the histogram into
      three pieces.
      Returns the lower and upper thresholds
      """
      assert min_threshold is None or max_threshold is None or min_threshold < max_threshold
      
      #
      # Compute the running variance and reverse running variance.
      # 
      data = np.atleast_1d(data)
      data = data[~ np.isnan(data)]
      data.sort()
      if len(data) == 0:
          return 0
      var = running_variance(data)
      rvar = np.flipud(running_variance(np.flipud(data)))
      if bins > len(data):
          bins = len(data)
      bin_len = int(len(data)//bins) 
      thresholds = data[0:len(data):bin_len]
      score_low = (var[0:len(data):bin_len] * 
                   np.arange(0,len(data),bin_len))
      score_high = (rvar[0:len(data):bin_len] *
                    (len(data) - np.arange(0,len(data),bin_len)))
      #
      # Compute the middles
      #
      cs = data.cumsum()
      cs2 = (data**2).cumsum()
      i,j = np.mgrid[0:score_low.shape[0],0:score_high.shape[0]]*bin_len
      diff = (j-i).astype(float)
      w = diff
      mean = (cs[j] - cs[i]) / diff
      mean2 = (cs2[j] - cs2[i]) / diff
      score_middle = w * (mean2 - mean**2)
      score_middle[i >= j] = np.Inf
      score = score_low[i*bins//len(data)] + score_middle + score_high[j*bins//len(data)]
      best_score = np.min(score)
      best_i_j = np.argwhere(score==best_score)
      return (thresholds[best_i_j[0,0]],thresholds[best_i_j[0,1]])