docstr: Compute the Rand Index for an IJV matrix
  
  This is in part based on the Omega Index:
  Collins, "Omega: A General Formulation of the Rand Index of Cluster
  Recovery Suitable for Non-disjoint Solutions", Multivariate Behavioral
  Research, 1988, 23, 231-242
  
  The basic idea of the paper is that a pair should be judged to
  agree only if the number of clusters in which they appear together
  is the same.
docstr_tok: ["Compute","the","Rand","Index","for","an","IJV","matrix","This","is","in","part","based","on","the","Omega","Index",":","Collins",",","``","Omega",":","A","General","Formulation","of","the","Rand","Index","of","Cluster","Recovery","Suitable","for","Non-disjoint","Solutions","''",",","Multivariate","Behavioral","Research",",","1988",",","23",",","231-242","The","basic","idea","of","the","paper","is","that","a","pair","should","be","judged","to","agree","only","if","the","number","of","clusters","in","which","they","appear","together","is","the","same","."]
code: def compute_rand_index_ijv(self, gt_ijv, test_ijv, shape):
      """Compute the Rand Index for an IJV matrix
  
      This is in part based on the Omega Index:
      Collins, "Omega: A General Formulation of the Rand Index of Cluster
      Recovery Suitable for Non-disjoint Solutions", Multivariate Behavioral
      Research, 1988, 23, 231-242
  
      The basic idea of the paper is that a pair should be judged to
      agree only if the number of clusters in which they appear together
      is the same.
      """
      #
      # The idea here is to assign a label to every pixel position based
      # on the set of labels given to that position by both the ground
      # truth and the test set. We then assess each pair of labels
      # as agreeing or disagreeing as to the number of matches.
      #
      # First, add the backgrounds to the IJV with a label of zero
      #
      gt_bkgd = numpy.ones(shape, bool)
      gt_bkgd[gt_ijv[:, 0], gt_ijv[:, 1]] = False
      test_bkgd = numpy.ones(shape, bool)
      test_bkgd[test_ijv[:, 0], test_ijv[:, 1]] = False
      gt_ijv = numpy.vstack(
          [
              gt_ijv,
              numpy.column_stack(
                  [
                      numpy.argwhere(gt_bkgd),
                      numpy.zeros(numpy.sum(gt_bkgd), gt_bkgd.dtype),
                  ]
              ),
          ]
      )
      test_ijv = numpy.vstack(
          [
              test_ijv,
              numpy.column_stack(
                  [
                      numpy.argwhere(test_bkgd),
                      numpy.zeros(
                          numpy.sum(test_bkgd), test_bkgd.dtype
                      ),
                  ]
              ),
          ]
      )
      #
      # Create a unified structure for the pixels where a fourth column
      # tells you whether the pixels came from the ground-truth or test
      #
      u = numpy.vstack(
          [
              numpy.column_stack(
                  [gt_ijv, numpy.zeros(gt_ijv.shape[0], gt_ijv.dtype)]
              ),
              numpy.column_stack(
                  [
                      test_ijv,
                      numpy.ones(test_ijv.shape[0], test_ijv.dtype),
                  ]
              ),
          ]
      )
      #
      # Sort by coordinates, then by identity
      #
      order = numpy.lexsort([u[:, 2], u[:, 3], u[:, 0], u[:, 1]])
      u = u[order, :]
      # Get rid of any duplicate labellings (same point labeled twice with
      # same label.
      #
      first = numpy.hstack(
          [[True], numpy.any(u[:-1, :] != u[1:, :], 1)]
      )
      u = u[first, :]
      #
      # Create a 1-d indexer to point at each unique coordinate.
      #
      first_coord_idxs = numpy.hstack(
          [
              [0],
              numpy.argwhere(
                  (u[:-1, 0] != u[1:, 0]) | (u[:-1, 1] != u[1:, 1])
              ).flatten()
              + 1,
              [u.shape[0]],
          ]
      )
      first_coord_counts = first_coord_idxs[1:] - first_coord_idxs[:-1]
      indexes = centrosome.index.Indexes([first_coord_counts])
      #
      # Count the number of labels at each point for both gt and test
      #
      count_test = numpy.bincount(indexes.rev_idx, u[:, 3]).astype(
          numpy.int64
      )
      count_gt = first_coord_counts - count_test
      #
      # For each # of labels, pull out the coordinates that have
      # that many labels. Count the number of similarly labeled coordinates
      # and record the count and labels for that group.
      #
      labels = []
      for i in range(1, numpy.max(count_test) + 1):
          for j in range(1, numpy.max(count_gt) + 1):
              match = (count_test[indexes.rev_idx] == i) & (
                  count_gt[indexes.rev_idx] == j
              )
              if not numpy.any(match):
                  continue
              #
              # Arrange into an array where the rows are coordinates
              # and the columns are the labels for that coordinate
              #
              lm = u[match, 2].reshape(
                  numpy.sum(match) // (i + j), i + j
              )
              #
              # Sort by label.
              #
              order = numpy.lexsort(lm.transpose())
              lm = lm[order, :]
              #
              # Find indices of unique and # of each
              #
              lm_first = numpy.hstack(
                  [
                      [0],
                      numpy.argwhere(
                          numpy.any(lm[:-1, :] != lm[1:, :], 1)
                      ).flatten()
                      + 1,
                      [lm.shape[0]],
                  ]
              )
              lm_count = lm_first[1:] - lm_first[:-1]
              for idx, count in zip(lm_first[:-1], lm_count):
                  labels.append((count, lm[idx, :j], lm[idx, j:]))
      #
      # We now have our sets partitioned. Do each against each to get
      # the number of true positive and negative pairs.
      #
      max_t_labels = reduce(max, [len(t) for c, t, g in labels], 0)
      max_g_labels = reduce(max, [len(g) for c, t, g in labels], 0)
      #
      # tbl is the contingency table from Table 4 of the Collins paper
      # It's a table of the number of pairs which fall into M sets
      # in the ground truth case and N in the test case.
      #
      tbl = numpy.zeros(((max_t_labels + 1), (max_g_labels + 1)))
      for i, (c1, tobject_numbers1, gobject_numbers1) in enumerate(
          labels
      ):
          for j, (c2, tobject_numbers2, gobject_numbers2) in enumerate(
              labels[i:]
          ):
              nhits_test = numpy.sum(
                  tobject_numbers1[:, numpy.newaxis]
                  == tobject_numbers2[numpy.newaxis, :]
              )
              nhits_gt = numpy.sum(
                  gobject_numbers1[:, numpy.newaxis]
                  == gobject_numbers2[numpy.newaxis, :]
              )
              if j == 0:
                  N = c1 * (c1 - 1) / 2
              else:
                  N = c1 * c2
              tbl[nhits_test, nhits_gt] += N
  
      N = numpy.sum(tbl)
      #
      # Equation 13 from the paper
      #
      min_JK = min(max_t_labels, max_g_labels) + 1
      rand_index = (
          numpy.sum(tbl[:min_JK, :min_JK] * numpy.identity(min_JK)) / N
      )
      #
      # Equation 15 from the paper, the expected index
      #
      e_omega = (
          numpy.sum(
              numpy.sum(tbl[:min_JK, :min_JK], 0)
              * numpy.sum(tbl[:min_JK, :min_JK], 1)
          )
          / N**2
      )
      #
      # Equation 16 is the adjusted index
      #
      adjusted_rand_index = (rand_index - e_omega) / (1 - e_omega)
      return rand_index, adjusted_rand_index
  
imports: from functools import reduce
  import centrosome.cpmorphology
  import centrosome.fastemd
  import centrosome.filter
  import centrosome.index
  import centrosome.propagate
  import numpy
  import scipy.ndimage
  import scipy.sparse
  from cellprofiler_core.constants.measurement import COLTYPE_FLOAT
  from cellprofiler_core.module import Module
  from cellprofiler_core.setting import Binary
  from cellprofiler_core.setting.choice import Choice
  from cellprofiler_core.setting.subscriber import LabelSubscriber
  from cellprofiler_core.setting.text import Integer
  from cellprofiler.modules import _help
label_desc: Use image node_id 5 for calling the MeasureObjectOverlap obj's underlying member method code with example usage: obj.compute_rand_index_ijv(gt_ijv, test_ijv, shape) and returns: rand_index,  adjusted_rand_index