docstr: train method (also used for validation monitoring). wrapper around forward pass of network. prepares input data
  for processing, computes losses, and stores outputs in a dictionary.
  :param batch: dictionary containing 'data', 'seg', etc.
  :return: results_dict: dictionary with keys:
          'boxes': list over batch elements. each batch element is a list of boxes. each box is a dictionary:
                  [[{box_0}, ... {box_n}], [{box_0}, ... {box_n}], ...]
          'seg_preds': pixel-wise class predictions (b, 1, y, x, (z)) with values [0, n_classes].
          'torch_loss': 1D torch tensor for backprop.
          'class_loss': classification loss for monitoring.
docstr_tok: ["train","method","(","also","used","for","validation","monitoring",")",".","wrapper","around","forward","pass","of","network",".","prepares","input","data","for","processing",",","computes","losses",",","and","stores","outputs","in","a","dictionary",".",":","param","batch",":","dictionary","containing","'data","'",",","'seg","'",",","etc",".",":","return",":","results_dict",":","dictionary","with","keys",":","'boxes","'",":","list","over","batch","elements",".","each","batch","element","is","a","list","of","boxes",".","each","box","is","a","dictionary",":","[","[","{","box_0","}",",","...","{","box_n","}","]",",","[","{","box_0","}",",","...","{","box_n","}","]",",","...","]","'seg_preds","'",":","pixel-wise","class","predictions","(","b",",","1",",","y",",","x",",","(","z",")",")","with","values","[","0",",","n_classes","]",".","'torch_loss","'",":","1D","torch","tensor","for","backprop",".","'class_loss","'",":","classification","loss","for","monitoring","."]
code: def train_forward(self, batch, is_validation=False):
      """
      train method (also used for validation monitoring). wrapper around forward pass of network. prepares input data
      for processing, computes losses, and stores outputs in a dictionary.
      :param batch: dictionary containing 'data', 'seg', etc.
      :return: results_dict: dictionary with keys:
              'boxes': list over batch elements. each batch element is a list of boxes. each box is a dictionary:
                      [[{box_0}, ... {box_n}], [{box_0}, ... {box_n}], ...]
              'seg_preds': pixel-wise class predictions (b, 1, y, x, (z)) with values [0, n_classes].
              'torch_loss': 1D torch tensor for backprop.
              'class_loss': classification loss for monitoring.
      """
      img = batch["data"]
      gt_class_ids = batch["roi_labels"]
      gt_boxes = batch["bb_target"]
      axes = (0, 2, 3, 1) if self.cf.dim == 2 else (0, 2, 3, 4, 1)
      var_seg_ohe = torch.FloatTensor(
          mutils.get_one_hot_encoding(
              batch["seg"], self.cf.num_seg_classes
          )
      ).cuda()
      var_seg = torch.LongTensor(batch["seg"]).cuda()
  
      img = torch.from_numpy(img).float().cuda()
      batch_rpn_class_loss = torch.FloatTensor([0]).cuda()
      batch_rpn_bbox_loss = torch.FloatTensor([0]).cuda()
  
      # list of output boxes for monitoring/plotting. each element is a list of boxes per batch element.
      box_results_list = [[] for _ in range(img.shape[0])]
  
      # forward passes. 1. general forward pass, where no activations are saved in second stage (for performance
      # monitoring and loss sampling). 2. second stage forward pass of sampled rois with stored activations for backprop.
      (
          rpn_class_logits,
          rpn_pred_deltas,
          proposal_boxes,
          detections,
          seg_logits,
      ) = self.forward(img)
      (
          mrcnn_class_logits,
          mrcnn_pred_deltas,
          target_class_ids,
          mrcnn_target_deltas,
          sample_proposals,
      ) = self.loss_samples_forward(gt_class_ids, gt_boxes)
  
      # loop over batch
      for b in range(img.shape[0]):
          if len(gt_boxes[b]) > 0:
              # add gt boxes to output list for monitoring.
              for ix in range(len(gt_boxes[b])):
                  box_results_list[b].append(
                      {
                          "box_coords": batch["bb_target"][b][ix],
                          "box_label": batch["roi_labels"][b][ix],
                          "box_type": "gt",
                      }
                  )
  
              # match gt boxes with anchors to generate targets for RPN losses.
              rpn_match, rpn_target_deltas = mutils.gt_anchor_matching(
                  self.cf, self.np_anchors, gt_boxes[b]
              )
  
              # add positive anchors used for loss to output list for monitoring.
              pos_anchors = mutils.clip_boxes_numpy(
                  self.np_anchors[np.argwhere(rpn_match == 1)][:, 0],
                  img.shape[2:],
              )
              for p in pos_anchors:
                  box_results_list[b].append(
                      {"box_coords": p, "box_type": "pos_anchor"}
                  )
  
          else:
              rpn_match = np.array([-1] * self.np_anchors.shape[0])
              rpn_target_deltas = np.array([0])
  
          rpn_match = torch.from_numpy(rpn_match).cuda()
          rpn_target_deltas = (
              torch.from_numpy(rpn_target_deltas).float().cuda()
          )
  
          # compute RPN losses.
          rpn_class_loss, neg_anchor_ix = compute_rpn_class_loss(
              rpn_match, rpn_class_logits[b], self.cf.shem_poolsize
          )
          rpn_bbox_loss = compute_rpn_bbox_loss(
              rpn_target_deltas, rpn_pred_deltas[b], rpn_match
          )
          batch_rpn_class_loss += rpn_class_loss / img.shape[0]
          batch_rpn_bbox_loss += rpn_bbox_loss / img.shape[0]
  
          # add negative anchors used for loss to output list for monitoring.
          neg_anchors = mutils.clip_boxes_numpy(
              self.np_anchors[np.argwhere(rpn_match == -1)][
                  0, neg_anchor_ix
              ],
              img.shape[2:],
          )
          for n in neg_anchors:
              box_results_list[b].append(
                  {"box_coords": n, "box_type": "neg_anchor"}
              )
  
          # add highest scoring proposals to output list for monitoring.
          rpn_proposals = proposal_boxes[b][
              proposal_boxes[b, :, -1].argsort()
          ][::-1]
          for r in rpn_proposals[: self.cf.n_plot_rpn_props, :-1]:
              box_results_list[b].append(
                  {"box_coords": r, "box_type": "prop"}
              )
  
      # add positive and negative roi samples used for mrcnn losses to output list for monitoring.
      if 0 not in sample_proposals.shape:
          rois = (
              mutils.clip_to_window(self.cf.window, sample_proposals)
              .cpu()
              .data.numpy()
          )
          for ix, r in enumerate(rois):
              box_results_list[int(r[-1])].append(
                  {
                      "box_coords": r[:-1] * self.cf.scale,
                      "box_type": "pos_class"
                      if target_class_ids[ix] > 0
                      else "neg_class",
                  }
              )
  
      batch_rpn_class_loss = batch_rpn_class_loss
      batch_rpn_bbox_loss = batch_rpn_bbox_loss
  
      # compute mrcnn losses.
      mrcnn_class_loss = compute_mrcnn_class_loss(
          target_class_ids, mrcnn_class_logits
      )
      mrcnn_bbox_loss = compute_mrcnn_bbox_loss(
          mrcnn_target_deltas, mrcnn_pred_deltas, target_class_ids
      )
  
      # mrcnn can be run without pixelwise annotations available (Faster R-CNN mode).
      # In this case, the mask_loss is taken out of training.
      # if not self.cf.frcnn_mode:
      #     mrcnn_mask_loss = compute_mrcnn_mask_loss(target_mask, mrcnn_pred_mask, target_class_ids)
      # else:
      #     mrcnn_mask_loss = torch.FloatTensor([0]).cuda()
  
      seg_loss_dice = 1 - mutils.batch_dice(
          F.softmax(seg_logits, dim=1), var_seg_ohe
      )
      seg_loss_ce = F.cross_entropy(seg_logits, var_seg[:, 0])
  
      loss = (
          batch_rpn_class_loss
          + batch_rpn_bbox_loss
          + mrcnn_class_loss
          + mrcnn_bbox_loss
          + (seg_loss_dice + seg_loss_ce) / 2
      )
  
      # monitor RPN performance: detection count = the number of correctly matched proposals per fg-class.
      dcount = [
          list(target_class_ids.cpu().data.numpy()).count(c)
          for c in np.arange(self.cf.head_classes)[1:]
      ]
  
      # run unmolding of predictions for monitoring and merge all results to one dictionary.
      results_dict = get_results(
          self.cf, img.shape, detections, seg_logits, box_results_list
      )
      results_dict["torch_loss"] = loss
      results_dict["monitor_values"] = {
          "loss": loss.item(),
          "class_loss": mrcnn_class_loss.item(),
      }
      results_dict["logger_string"] = (
          "loss: {0:.2f}, rpn_class: {1:.2f}, rpn_bbox: {2:.2f}, mrcnn_class: {3:.2f}, "
          "mrcnn_bbox: {4:.2f}, dice_loss: {5:.2f}, dcount {6}".format(
              loss.item(),
              batch_rpn_class_loss.item(),
              batch_rpn_bbox_loss.item(),
              mrcnn_class_loss.item(),
              mrcnn_bbox_loss.item(),
              seg_loss_dice.item(),
              dcount,
          )
      )
  
      return results_dict
  
imports: import utils.model_utils
  import utils.exp_utils
  from cuda_functions.nms_2D.pth_nms import nms_gpu
  from cuda_functions.nms_3D.pth_nms import nms_gpu
  from cuda_functions.roi_align_2D.roi_align.crop_and_resize import CropAndResizeFunction
  from cuda_functions.roi_align_3D.roi_align.crop_and_resize import CropAndResizeFunction
  import numpy
  import torch
  import torch.nn
  import torch.nn.functional
  import torch.utils
label_desc: Use image node_id 3 for calling the net obj's underlying member method code with example usage: obj.train_forward(batch, is_validation) and returns: results_dict