class_docstr: SIFT feature detection and descriptor extraction.
  
  Parameters
  ----------
  upsampling : int, optional
      Prior to the feature detection the image is upscaled by a factor
      of 1 (no upscaling), 2 or 4. Method: Bi-cubic interpolation.
  n_octaves : int, optional
      Maximum number of octaves. With every octave the image size is
      halved and the sigma doubled. The number of octaves will be
      reduced as needed to keep at least 12 pixels along each dimension
      at the smallest scale.
  n_scales : int, optional
      Maximum number of scales in every octave.
  sigma_min : float, optional
      The blur level of the seed image. If upsampling is enabled
      sigma_min is scaled by factor 1/upsampling
  sigma_in : float, optional
      The assumed blur level of the input image.
  c_dog : float, optional
      Threshold to discard low contrast extrema in the DoG. It's final
      value is dependent on n_scales by the relation:
      final_c_dog = (2^(1/n_scales)-1) / (2^(1/3)-1) * c_dog
  c_edge : float, optional
      Threshold to discard extrema that lie in edges. If H is the
      Hessian of an extremum, its "edgeness" is described by
      tr(H)²/det(H). If the edgeness is higher than
      (c_edge + 1)²/c_edge, the extremum is discarded.
  n_bins : int, optional
      Number of bins in the histogram that describes the gradient
      orientations around keypoint.
  lambda_ori : float, optional
      The window used to find the reference orientation of a keypoint
      has a width of 6 * lambda_ori * sigma and is weighted by a
      standard deviation of 2 * lambda_ori * sigma.
  c_max : float, optional
      The threshold at which a secondary peak in the orientation
      histogram is accepted as orientation
  lambda_descr : float, optional
      The window used to define the descriptor of a keypoint has a width
      of 2 * lambda_descr * sigma * (n_hist+1)/n_hist and is weighted by
      a standard deviation of lambda_descr * sigma.
  n_hist : int, optional
      The window used to define the descriptor of a keypoint consists of
      n_hist * n_hist histograms.
  n_ori : int, optional
      The number of bins in the histograms of the descriptor patch.
  
  Attributes
  ----------
  delta_min : float
      The sampling distance of the first octave. It's final value is
      1/upsampling.
  float_dtype : type
      The datatype of the image.
  scalespace_sigmas : (n_octaves, n_scales + 3) array
      The sigma value of all scales in all octaves.
  keypoints : (N, 2) array
      Keypoint coordinates as ``(row, col)``.
  positions : (N, 2) array
      Subpixel-precision keypoint coordinates as ``(row, col)``.
  sigmas : (N,) array
      The corresponding sigma (blur) value of a keypoint.
  scales : (N,) array
      The corresponding scale of a keypoint.
  orientations : (N,) array
      The orientations of the gradient around every keypoint.
  octaves : (N,) array
      The corresponding octave of a keypoint.
  descriptors : (N, n_hist*n_hist*n_ori) array
      The descriptors of a keypoint.
  
  Notes
  -----
  The SIFT algorithm was developed by David Lowe [1]_, [2]_ and later
  patented by the University of British Columbia. Since the patent expired in
  2020 it's free to use. The implementation here closely follows the
  detailed description in [3]_, including use of the same default parameters.
  
  References
  ----------
  .. [1] D.G. Lowe. "Object recognition from local scale-invariant
         features", Proceedings of the Seventh IEEE International
         Conference on Computer Vision, 1999, vol.2, pp. 1150-1157.
         :DOI:`10.1109/ICCV.1999.790410`
  
  .. [2] D.G. Lowe. "Distinctive Image Features from Scale-Invariant
         Keypoints", International Journal of Computer Vision, 2004,
         vol. 60, pp. 91–110.
         :DOI:`10.1023/B:VISI.0000029664.99615.94`
  
  .. [3] I. R. Otero and M. Delbracio. "Anatomy of the SIFT Method",
         Image Processing On Line, 4 (2014), pp. 370–396.
         :DOI:`10.5201/ipol.2014.82`
  
  Examples
  --------
  >>> from skimage.feature import SIFT, match_descriptors
  >>> from skimage.data import camera
  >>> from skimage.transform import rotate
  >>> img1 = camera()
  >>> img2 = rotate(camera(), 90)
  >>> detector_extractor1 = SIFT()
  >>> detector_extractor2 = SIFT()
  >>> detector_extractor1.detect_and_extract(img1)
  >>> detector_extractor2.detect_and_extract(img2)
  >>> matches = match_descriptors(detector_extractor1.descriptors,
  ...                             detector_extractor2.descriptors,
  ...                             max_ratio=0.6)
  >>> matches[10:15]
  array([[ 10, 412],
         [ 11, 417],
         [ 12, 407],
         [ 13, 411],
         [ 14, 406]])
  >>> detector_extractor1.keypoints[matches[10:15, 0]]
  array([[ 95, 214],
         [ 97, 211],
         [ 97, 218],
         [102, 215],
         [104, 218]])
  >>> detector_extractor2.keypoints[matches[10:15, 1]]
  array([[297,  95],
         [301,  97],
         [294,  97],
         [297, 102],
         [293, 104]])
class_docstr_tok: ["SIFT","feature","detection","and","descriptor","extraction",".","Parameters","--","--","--","--","--","upsampling",":","int",",","optional","Prior","to","the","feature","detection","the","image","is","upscaled","by","a","factor","of","1","(","no","upscaling",")",",","2","or","4",".","Method",":","Bi-cubic","interpolation",".","n_octaves",":","int",",","optional","Maximum","number","of","octaves",".","With","every","octave","the","image","size","is","halved","and","the","sigma","doubled",".","The","number","of","octaves","will","be","reduced","as","needed","to","keep","at","least","12","pixels","along","each","dimension","at","the","smallest","scale",".","n_scales",":","int",",","optional","Maximum","number","of","scales","in","every","octave",".","sigma_min",":","float",",","optional","The","blur","level","of","the","seed","image",".","If","upsampling","is","enabled","sigma_min","is","scaled","by","factor","1\/upsampling","sigma_in",":","float",",","optional","The","assumed","blur","level","of","the","input","image",".","c_dog",":","float",",","optional","Threshold","to","discard","low","contrast","extrema","in","the","DoG",".","It","'s","final","value","is","dependent","on","n_scales","by","the","relation",":","final_c_dog","=","(","2^","(","1\/n_scales",")","-1",")","\/","(","2^","(","1\/3",")","-1",")","*","c_dog","c_edge",":","float",",","optional","Threshold","to","discard","extrema","that","lie","in","edges",".","If","H","is","the","Hessian","of","an","extremum",",","its","``","edgeness","''","is","described","by","tr","(","H",")","\u00b2\/det","(","H",")",".","If","the","edgeness","is","higher","than","(","c_edge","+","1",")","\u00b2\/c_edge",",","the","extremum","is","discarded",".","n_bins",":","int",",","optional","Number","of","bins","in","the","histogram","that","describes","the","gradient","orientations","around","keypoint",".","lambda_ori",":","float",",","optional","The","window","used","to","find","the","reference","orientation","of","a","keypoint","has","a","width","of","6","*","lambda_ori","*","sigma","and","is","weighted","by","a","standard","deviation","of","2","*","lambda_ori","*","sigma",".","c_max",":","float",",","optional","The","threshold","at","which","a","secondary","peak","in","the","orientation","histogram","is","accepted","as","orientation","lambda_descr",":","float",",","optional","The","window","used","to","define","the","descriptor","of","a","keypoint","has","a","width","of","2","*","lambda_descr","*","sigma","*","(","n_hist+1",")","\/n_hist","and","is","weighted","by","a","standard","deviation","of","lambda_descr","*","sigma",".","n_hist",":","int",",","optional","The","window","used","to","define","the","descriptor","of","a","keypoint","consists","of","n_hist","*","n_hist","histograms",".","n_ori",":","int",",","optional","The","number","of","bins","in","the","histograms","of","the","descriptor","patch",".","Attributes","--","--","--","--","--","delta_min",":","float","The","sampling","distance","of","the","first","octave",".","It","'s","final","value","is","1\/upsampling",".","float_dtype",":","type","The","datatype","of","the","image",".","scalespace_sigmas",":","(","n_octaves",",","n_scales","+","3",")","array","The","sigma","value","of","all","scales","in","all","octaves",".","keypoints",":","(","N",",","2",")","array","Keypoint","coordinates","as","``","(","row",",","col",")","``",".","positions",":","(","N",",","2",")","array","Subpixel-precision","keypoint","coordinates","as","``","(","row",",","col",")","``",".","sigmas",":","(","N",",",")","array","The","corresponding","sigma","(","blur",")","value","of","a","keypoint",".","scales",":","(","N",",",")","array","The","corresponding","scale","of","a","keypoint",".","orientations",":","(","N",",",")","array","The","orientations","of","the","gradient","around","every","keypoint",".","octaves",":","(","N",",",")","array","The","corresponding","octave","of","a","keypoint",".","descriptors",":","(","N",",","n_hist","*","n_hist","*","n_ori",")","array","The","descriptors","of","a","keypoint",".","Notes","--","--","-","The","SIFT","algorithm","was","developed","by","David","Lowe","[","1","]","_",",","[","2","]","_","and","later","patented","by","the","University","of","British","Columbia",".","Since","the","patent","expired","in","2020","it","'s","free","to","use",".","The","implementation","here","closely","follows","the","detailed","description","in","[","3","]","_",",","including","use","of","the","same","default","parameters",".","References","--","--","--","--","--","..","[","1","]","D.G",".","Lowe",".","``","Object","recognition","from","local","scale-invariant","features","''",",","Proceedings","of","the","Seventh","IEEE","International","Conference","on","Computer","Vision",",","1999",",","vol.2",",","pp",".","1150-1157.",":","DOI",":","`","10.1109\/ICCV.1999.790410","`","..","[","2","]","D.G",".","Lowe",".","``","Distinctive","Image","Features","from","Scale-Invariant","Keypoints","''",",","International","Journal","of","Computer","Vision",",","2004",",","vol",".","60",",","pp",".","91\u2013110",".",":","DOI",":","`","10.1023\/B",":","VISI.0000029664.99615.94","`","..","[","3","]","I.","R.","Otero","and","M.","Delbracio",".","``","Anatomy","of","the","SIFT","Method","''",",","Image","Processing","On","Line",",","4","(","2014",")",",","pp",".","370\u2013396",".",":","DOI",":","`","10.5201\/ipol.2014.82","`","Examples","--","--","--","--",">",">",">","from","skimage.feature","import","SIFT",",","match_descriptors",">",">",">","from","skimage.data","import","camera",">",">",">","from","skimage.transform","import","rotate",">",">",">","img1","=","camera","(",")",">",">",">","img2","=","rotate","(","camera","(",")",",","90",")",">",">",">","detector_extractor1","=","SIFT","(",")",">",">",">","detector_extractor2","=","SIFT","(",")",">",">",">","detector_extractor1.detect_and_extract","(","img1",")",">",">",">","detector_extractor2.detect_and_extract","(","img2",")",">",">",">","matches","=","match_descriptors","(","detector_extractor1.descriptors",",","...","detector_extractor2.descriptors",",","...","max_ratio=0.6",")",">",">",">","matches","[","10:15","]","array","(","[","[","10",",","412","]",",","[","11",",","417","]",",","[","12",",","407","]",",","[","13",",","411","]",",","[","14",",","406","]","]",")",">",">",">","detector_extractor1.keypoints","[","matches","[","10:15",",","0","]","]","array","(","[","[","95",",","214","]",",","[","97",",","211","]",",","[","97",",","218","]",",","[","102",",","215","]",",","[","104",",","218","]","]",")",">",">",">","detector_extractor2.keypoints","[","matches","[","10:15",",","1","]","]","array","(","[","[","297",",","95","]",",","[","301",",","97","]",",","[","294",",","97","]",",","[","297",",","102","]",",","[","293",",","104","]","]",")"]
docstr: Source: "Anatomy of the SIFT Method" Alg. 11
  Calculates the orientation of the gradient around every keypoint
docstr_tok: ["Source",":","``","Anatomy","of","the","SIFT","Method","''","Alg",".","11","Calculates","the","orientation","of","the","gradient","around","every","keypoint"]
code: def _compute_orientation(
      self,
      positions_oct,
      scales_oct,
      sigmas_oct,
      octaves,
      gaussian_scalespace,
  ):
      """Source: "Anatomy of the SIFT Method" Alg. 11
      Calculates the orientation of the gradient around every keypoint
      """
      gradient_space = []
      # list for keypoints that have more than one reference orientation
      keypoint_indices = []
      keypoint_angles = []
      keypoint_octave = []
      orientations = np.zeros_like(sigmas_oct, dtype=self.float_dtype)
      key_count = 0
      for o, (octave, delta) in enumerate(
          zip(gaussian_scalespace, self.deltas)
      ):
          gradient_space.append(np.gradient(octave))
  
          in_oct = octaves == o
          if not np.any(in_oct):
              continue
          positions = positions_oct[in_oct]
          scales = scales_oct[in_oct]
          sigmas = sigmas_oct[in_oct]
  
          oshape = octave.shape[:2]
          # convert to octave's dimensions
          yx = positions / delta
          sigma = sigmas / delta
  
          # dimensions of the patch
          radius = 3 * self.lambda_ori * sigma
          p_min = np.maximum(
              0, yx - radius[:, np.newaxis] + 0.5
          ).astype(int)
          p_max = np.minimum(
              yx + radius[:, np.newaxis] + 0.5,
              (oshape[0] - 1, oshape[1] - 1),
          ).astype(int)
          # orientation histogram
          hist = np.empty(self.n_bins, dtype=self.float_dtype)
          avg_kernel = np.full((3,), 1 / 3, dtype=self.float_dtype)
          for k in range(len(yx)):
              hist[:] = 0
  
              # use the patch coordinates to get the gradient and then
              # normalize them
              r, c = np.meshgrid(
                  np.arange(p_min[k, 0], p_max[k, 0] + 1),
                  np.arange(p_min[k, 1], p_max[k, 1] + 1),
                  indexing="ij",
                  sparse=True,
              )
              gradient_row = gradient_space[o][0][r, c, scales[k]]
              gradient_col = gradient_space[o][1][r, c, scales[k]]
              r = r.astype(self.float_dtype, copy=False)
              c = c.astype(self.float_dtype, copy=False)
              r -= yx[k, 0]
              c -= yx[k, 1]
  
              # gradient magnitude and angles
              magnitude = np.sqrt(
                  np.square(gradient_row) + np.square(gradient_col)
              )
              theta = np.mod(
                  np.arctan2(gradient_col, gradient_row), 2 * np.pi
              )
  
              # more weight to center values
              kernel = np.exp(
                  np.divide(
                      r * r + c * c,
                      -2 * (self.lambda_ori * sigma[k]) ** 2,
                  )
              )
  
              # fill the histogram
              bins = np.floor(
                  (theta / (2 * np.pi) * self.n_bins + 0.5)
                  % self.n_bins
              ).astype(int)
              np.add.at(hist, bins, kernel * magnitude)
  
              # smooth the histogram and find the maximum
              hist = np.concatenate((hist[-6:], hist, hist[:6]))
              for _ in range(6):  # number of smoothings
                  hist = np.convolve(hist, avg_kernel, mode="same")
              hist = hist[6:-6]
              max_filter = ndi.maximum_filter(hist, [3], mode="wrap")
  
              # if an angle is in 80% percent range of the maximum, a
              # new keypoint is created for it
              maxima = np.nonzero(
                  np.logical_and(
                      hist >= (self.c_max * np.max(hist)),
                      max_filter == hist,
                  )
              )
  
              # save the angles
              for c, m in enumerate(maxima[0]):
                  neigh = np.arange(m - 1, m + 2) % len(hist)
                  # use neighbors to fit a parabola, to get more accurate
                  # result
                  ori = (
                      (m + self._fit(hist[neigh]) + 0.5)
                      * 2
                      * np.pi
                      / self.n_bins
                  )
                  if ori > np.pi:
                      ori -= 2 * np.pi
                  if c == 0:
                      orientations[key_count] = ori
                  else:
                      keypoint_indices.append(key_count)
                      keypoint_angles.append(ori)
                      keypoint_octave.append(o)
              key_count += 1
      self.positions = np.concatenate(
          (positions_oct, positions_oct[keypoint_indices])
      )
      self.scales = np.concatenate(
          (scales_oct, scales_oct[keypoint_indices])
      )
      self.sigmas = np.concatenate(
          (sigmas_oct, sigmas_oct[keypoint_indices])
      )
      self.orientations = np.concatenate(
          (orientations, keypoint_angles)
      )
      self.octaves = np.concatenate((octaves, keypoint_octave))
      # return the gradient_space to reuse it to find the descriptor
      return gradient_space
  
imports: import math
  import numpy
  import scipy.ndimage
  from .._shared.utils import check_nD, _supported_float_type
  from ..feature.util import DescriptorExtractor, FeatureDetector
  from .._shared.filters import gaussian
  from ..transform import rescale
  from ..util import img_as_float
  from ._sift import _local_max, _ori_distances, _update_histogram
label_desc: Use image node_id 8 for calling the SIFT obj's underlying member method code with example usage: obj._compute_orientation(positions_oct, scales_oct, sigmas_oct, octaves, gaussian_scalespace) and returns: gradient_space