class_docstr: This module implements the task- and model-specific estimator for PyTorch GOTURN (object tracking).
class_docstr_tok: ["This","module","implements","the","task-","and","model-specific","estimator","for","PyTorch","GOTURN","(","object","tracking",")","."]
docstr: Track current frame.
  
  :param curr_frame: Current frame.
  :param prev_frame: Previous frame.
  :return: bounding box of previous frame
docstr_tok: ["Track","current","frame",".",":","param","curr_frame",":","Current","frame",".",":","param","prev_frame",":","Previous","frame",".",":","return",":","bounding","box","of","previous","frame"]
code: def _track_step(
      self,
      curr_frame: "torch.Tensor",
      prev_frame: "torch.Tensor",
      rect: "torch.Tensor",
  ) -> "torch.Tensor":
      """
      Track current frame.
  
      :param curr_frame: Current frame.
      :param prev_frame: Previous frame.
      :return: bounding box of previous frame
      """
      import torch
  
      prev_bbox = rect
  
      k_context_factor = 2
  
      def compute_output_height_f(
          bbox_tight: "torch.Tensor",
      ) -> "torch.Tensor":
          """
          Compute height of search/target region.
  
          :param bbox_tight: Coordinates of bounding box [x1, y1, x2, y2].
          :return: Output height.
          """
          bbox_height = bbox_tight[3] - bbox_tight[1]
          output_height = k_context_factor * bbox_height
  
          return torch.maximum(
              torch.tensor(1.0).to(self.device), output_height
          )
  
      def compute_output_width_f(
          bbox_tight: "torch.Tensor",
      ) -> "torch.Tensor":
          """
          Compute width of search/target region.
  
          :param bbox_tight: Coordinates of bounding box [x1, y1, x2, y2].
          :return: Output width.
          """
          bbox_width = bbox_tight[2] - bbox_tight[0]
          output_width = k_context_factor * bbox_width
  
          return torch.maximum(
              torch.tensor(1.0).to(self.device), output_width
          )
  
      def get_center_x_f(bbox_tight: "torch.Tensor") -> "torch.Tensor":
          """
          Compute x-coordinate of the bounding box center.
  
          :param bbox_tight: Coordinates of bounding box [x1, y1, x2, y2].
          :return: x-coordinate of the bounding box center.
          """
          return (bbox_tight[0] + bbox_tight[2]) / 2.0
  
      def get_center_y_f(bbox_tight: "torch.Tensor") -> "torch.Tensor":
          """
          Compute y-coordinate of the bounding box center
  
          :param bbox_tight: Coordinates of bounding box [x1, y1, x2, y2].
          :return: y-coordinate of the bounding box center.
          """
          return (bbox_tight[1] + bbox_tight[3]) / 2.0
  
      def compute_crop_pad_image_location(
          bbox_tight: "torch.Tensor", image: "torch.Tensor"
      ) -> Tuple[
          "torch.Tensor", "torch.Tensor", "torch.Tensor", "torch.Tensor"
      ]:
          """
          Get the valid image coordinates for the context region in target or search region in full image
  
          :param bbox_tight: Coordinates of bounding box [x1, y1, x2, y2].
          :param image: Frame to be cropped and padded.
          :return: x-coordinate of the bounding box center.
          """
  
          # Center of the bounding box
          # bbox_center_x = bbox_tight.get_center_x()
          # bbox_center_y = bbox_tight.get_center_y()
          bbox_center_x = get_center_x_f(bbox_tight)
          bbox_center_y = get_center_y_f(bbox_tight)
  
          image_height = image.shape[0]
          image_width = image.shape[1]
  
          # Padded output width and height
          # output_width = bbox_tight.compute_output_width()
          # output_height = bbox_tight.compute_output_height()
          output_width = compute_output_width_f(bbox_tight)
          output_height = compute_output_height_f(bbox_tight)
  
          roi_left = torch.maximum(
              torch.tensor(0.0).to(self.device),
              bbox_center_x - (output_width / 2.0),
          )
          roi_bottom = torch.maximum(
              torch.tensor(0.0).to(self.device),
              bbox_center_y - (output_height / 2.0),
          )
  
          # New ROI width
          # -------------
          # 1. left_half should not go out of bound on the left side of the
          # image
          # 2. right_half should not go out of bound on the right side of the
          # image
          left_half = torch.minimum(output_width / 2.0, bbox_center_x)
          right_half = torch.minimum(
              output_width / 2.0, image_width - bbox_center_x
          )
          roi_width = torch.maximum(
              torch.tensor(1.0).to(self.device), left_half + right_half
          )
  
          # New ROI height
          # Similar logic applied that is applied for 'New ROI width'
          top_half = torch.minimum(output_height / 2.0, bbox_center_y)
          bottom_half = torch.minimum(
              output_height / 2.0, image_height - bbox_center_y
          )
          roi_height = torch.maximum(
              torch.tensor(1.0).to(self.device), top_half + bottom_half
          )
  
          # Padded image location in the original image
          # objPadImageLocation = BoundingBox(roi_left, roi_bottom, roi_left + roi_width, roi_bottom + roi_height)
          #
          # return objPadImageLocation
          return (
              roi_left,
              roi_bottom,
              roi_left + roi_width,
              roi_bottom + roi_height,
          )
  
      def edge_spacing_x_f(
          bbox_tight: "torch.Tensor",
      ) -> "torch.Tensor":
          """
          Edge spacing X to take care of if search/target pad region goes out of bound.
  
          :param bbox_tight: Coordinates of bounding box [x1, y1, x2, y2].
          :return: Edge spacing X.
          """
          output_width = compute_output_width_f(bbox_tight)
          bbox_center_x = get_center_x_f(bbox_tight)
  
          return torch.maximum(
              torch.tensor(0.0).to(self.device),
              (output_width / 2) - bbox_center_x,
          )
  
      def edge_spacing_y_f(
          bbox_tight: "torch.Tensor",
      ) -> "torch.Tensor":
          """
          Edge spacing X to take care of if search/target pad region goes out of bound.
  
          :param bbox_tight: Coordinates of bounding box [x1, y1, x2, y2].
          :return: Edge spacing X.
          """
          output_height = compute_output_height_f(bbox_tight)
          bbox_center_y = get_center_y_f(bbox_tight)
  
          return torch.maximum(
              torch.tensor(0.0).to(self.device),
              (output_height / 2) - bbox_center_y,
          )
  
      def crop_pad_image(
          bbox_tight: "torch.Tensor", image: "torch.Tensor"
      ) -> Tuple[
          "torch.Tensor",
          Tuple[
              "torch.Tensor",
              "torch.Tensor",
              "torch.Tensor",
              "torch.Tensor",
          ],
          "torch.Tensor",
          "torch.Tensor",
      ]:
          """
          Around the bounding box, we define a extra context factor of 2, which we will crop from the original image.
  
          :param bbox_tight: Coordinates of bounding box [x1, y1, x2, y2].
          :param image: Frame to be cropped and padded.
          :return: Cropped and Padded image.
          """
          import math
          import torch
  
          pad_image_location = compute_crop_pad_image_location(
              bbox_tight, image
          )
          # roi_left = min(pad_image_location.x1, (image.shape[1] - 1))
          # roi_bottom = min(pad_image_location.y1, (image.shape[0] - 1))
          roi_left = torch.minimum(
              pad_image_location[0],
              torch.tensor(image.shape[1] - 1).to(self.device),
          )
          roi_bottom = torch.minimum(
              pad_image_location[1],
              torch.tensor(image.shape[0] - 1).to(self.device),
          )
          # roi_width = min(image.shape[1], max(1.0, math.ceil(pad_image_location.x2 - pad_image_location.x1)))
          # roi_height = min(image.shape[0], max(1.0, math.ceil(pad_image_location.y2 - pad_image_location.y1)))
          roi_width = min(
              image.shape[1],
              max(
                  1,
                  math.ceil(
                      pad_image_location[2] - pad_image_location[0]
                  ),
              ),
          )
          roi_height = min(
              image.shape[0],
              max(
                  1,
                  math.ceil(
                      pad_image_location[3] - pad_image_location[1]
                  ),
              ),
          )
  
          roi_bottom_int = int(roi_bottom)
          roi_bottom_height_int = roi_bottom_int + roi_height
  
          roi_left_int = int(roi_left)
          roi_left_width_int = roi_left_int + roi_width
  
          cropped_image = image[
              roi_bottom_int:roi_bottom_height_int,
              roi_left_int:roi_left_width_int,
          ]
  
          # output_width = max(math.ceil(bbox_tight.compute_output_width()), roi_width)
          # output_height = max(math.ceil(bbox_tight.compute_output_height()), roi_height)
          output_width = max(
              math.ceil(compute_output_width_f(bbox_tight)), roi_width
          )
          output_height = max(
              math.ceil(compute_output_height_f(bbox_tight)), roi_height
          )
          if image.ndim > 2:
              # output_image = np.zeros((int(output_height), int(output_width), image.shape[2]), dtype=image.dtype)
              output_image = torch.zeros(
                  (
                      int(output_height),
                      int(output_width),
                      image.shape[2],
                  ),
                  dtype=image.dtype,
              )
          else:
              # output_image = np.zeros((int(output_height), int(output_width)), dtype=image.dtype)
              output_image = torch.zeros(
                  (int(output_height), int(output_width)),
                  dtype=image.dtype,
              )
  
          # edge_spacing_x = min(bbox_tight.edge_spacing_x(), (image.shape[1] - 1))
          # edge_spacing_y = min(bbox_tight.edge_spacing_y(), (image.shape[0] - 1))
          edge_spacing_x = torch.minimum(
              edge_spacing_x_f(bbox_tight),
              torch.tensor(image.shape[1] - 1),
          )
          edge_spacing_y = torch.minimum(
              edge_spacing_y_f(bbox_tight),
              torch.tensor(image.shape[0] - 1),
          )
  
          # rounding should be done to match the width and height
          output_image[
              int(edge_spacing_y) : int(edge_spacing_y)
              + cropped_image.shape[0],
              int(edge_spacing_x) : int(edge_spacing_x)
              + cropped_image.shape[1],
          ] = cropped_image
  
          return (
              output_image,
              pad_image_location,
              edge_spacing_x,
              edge_spacing_y,
          )
  
      target_pad, _, _, _ = crop_pad_image(prev_bbox, prev_frame)
      (
          cur_search_region,
          search_location,
          edge_spacing_x,
          edge_spacing_y,
      ) = crop_pad_image(prev_bbox, curr_frame)
  
      target_pad_in = (
          self._preprocess(target_pad).unsqueeze(0).to(self.device)
      )
      cur_search_region_in = (
          self._preprocess(cur_search_region)
          .unsqueeze(0)
          .to(self.device)
      )
  
      pred_bb = self._model.forward(
          target_pad_in.float(), cur_search_region_in.float()
      )
  
      pred_bb = torch.squeeze(pred_bb)
  
      k_scale_factor = 10
      height = cur_search_region.shape[0]
      width = cur_search_region.shape[1]
  
      # Normalize the image bounding box
      pred_bb[0] = pred_bb[0] / k_scale_factor * width
      pred_bb[2] = pred_bb[2] / k_scale_factor * width
      pred_bb[1] = pred_bb[1] / k_scale_factor * height
      pred_bb[3] = pred_bb[3] / k_scale_factor * height
  
      # brings gradients to zero
      # pred_bb = torch.round(pred_bb)
  
      # move the bounding box to target/search region coordinates
      raw_image = curr_frame
      pred_bb[0] = max(
          0.0, pred_bb[0] + search_location[0] - edge_spacing_x
      )
      pred_bb[1] = max(
          0.0, pred_bb[1] + search_location[1] - edge_spacing_y
      )
      pred_bb[2] = min(
          raw_image.shape[1],
          pred_bb[2] + search_location[0] - edge_spacing_x,
      )
      pred_bb[3] = min(
          raw_image.shape[0],
          pred_bb[3] + search_location[1] - edge_spacing_y,
      )
  
      # brings gradients to zero
      # pred_bb = torch.round(pred_bb)
  
      return pred_bb
  
imports: import logging
  import time
  from typing import List, Dict, Optional, Tuple, Union, TYPE_CHECKING
  import numpy
  from art.estimators.object_tracking.object_tracker import ObjectTrackerMixin
  from art.estimators.pytorch import PyTorchEstimator
label_desc: Use image node_id 8 for calling the PyTorchGoturn obj's underlying member method code with example usage: obj._track_step(curr_frame, prev_frame, rect) and returns: pred_bb, torch, torch, unknown, unknown, roi_left,  roi_bottom,  unknown,  unknown, torch, torch, output_image,  pad_image_location,  edge_spacing_x,  edge_spacing_y