code: def get_mask_edges(
      seg_pred: NdarrayOrTensor,
      seg_gt: NdarrayOrTensor,
      label_idx: int = 1,
      crop: bool = True,
      spacing: Sequence | None = None,
      always_return_as_numpy: bool = True,
  ) -> tuple[NdarrayTensor, NdarrayTensor]:
      """
      Compute edges from binary segmentation masks. This
      function is helpful to further calculate metrics such as Average Surface
      Distance and Hausdorff Distance.
      The input images can be binary or labelfield images. If labelfield images
      are supplied, they are converted to binary images using `label_idx`.
  
      In order to improve the computing efficiency, before getting the edges,
      the images can be cropped and only keep the foreground if not specifies
      ``crop = False``.
  
      We require that images are the same size, and assume that they occupy the
      same space (spacing, orientation, etc.).
  
      Args:
          seg_pred: the predicted binary or labelfield image.
          seg_gt: the actual binary or labelfield image.
          label_idx: for labelfield images, convert to binary with
              `seg_pred = seg_pred == label_idx`.
          crop: crop input images and only keep the foregrounds. In order to
              maintain two inputs' shapes, here the bounding box is achieved
              by ``(seg_pred | seg_gt)`` which represents the union set of two
              images. Defaults to ``True``.
          spacing: the input spacing. If not None, the subvoxel edges and areas will be computed.
              otherwise `scipy`'s binary erosion is used to calculate the edges.
          always_return_as_numpy: whether to a numpy array regardless of the input type.
              If False, return the same type as inputs.
      """
      if seg_pred.shape != seg_gt.shape:
          raise ValueError(
              f"seg_pred and seg_gt should have same shapes, got {seg_pred.shape} and {seg_gt.shape}."
          )
      converter: Any
      lib: ModuleType
      if (
          isinstance(seg_pred, torch.Tensor)
          and not always_return_as_numpy
      ):
          converter = partial(convert_to_tensor, device=seg_pred.device)
          lib = torch
      else:
          converter = convert_to_numpy
          lib = np
      use_cucim = (
          spacing is None
          and has_cucim_binary_erosion
          and isinstance(seg_pred, torch.Tensor)
          and seg_pred.device.type == "cuda"
      )
  
      # If not binary images, convert them
      if seg_pred.dtype not in (bool, torch.bool):
          seg_pred = seg_pred == label_idx
      if seg_gt.dtype not in (bool, torch.bool):
          seg_gt = seg_gt == label_idx
      if crop:
          or_vol = seg_pred | seg_gt
          if not or_vol.any():
              pred, gt = lib.zeros(
                  seg_pred.shape, dtype=bool
              ), lib.zeros(seg_gt.shape, dtype=bool)
              return (
                  (pred, gt)
                  if spacing is None
                  else (pred, gt, pred, gt)
              )
          channel_first = [seg_pred[None], seg_gt[None], or_vol[None]]
          if spacing is None and not use_cucim:  # cpu only erosion
              seg_pred, seg_gt, or_vol = convert_to_tensor(
                  channel_first, device="cpu", dtype=bool
              )
          else:  # pytorch subvoxel, maybe on gpu, but croppad boolean values on GPU is not supported
              seg_pred, seg_gt, or_vol = convert_to_tensor(
                  channel_first, dtype=torch.float16
              )
          cropper = CropForegroundD(
              ["pred", "gt"],
              source_key="src",
              margin=1,
              allow_smaller=False,
              start_coord_key=None,
              end_coord_key=None,
          )
          cropped = cropper({"pred": seg_pred, "gt": seg_gt, "src": or_vol})  # type: ignore
          seg_pred, seg_gt = cropped["pred"][0], cropped["gt"][0]
  
      if spacing is None:  # Do binary erosion and use XOR to get edges
          if not use_cucim:
              seg_pred, seg_gt = convert_to_numpy(
                  [seg_pred, seg_gt], dtype=bool
              )
              edges_pred = binary_erosion(seg_pred) ^ seg_pred
              edges_gt = binary_erosion(seg_gt) ^ seg_gt
          else:
              seg_pred, seg_gt = convert_to_cupy([seg_pred, seg_gt], dtype=bool)  # type: ignore[arg-type]
              edges_pred = cucim_binary_erosion(seg_pred) ^ seg_pred
              edges_gt = cucim_binary_erosion(seg_gt) ^ seg_gt
          return converter((edges_pred, edges_gt), dtype=bool)  # type: ignore
      code_to_area_table, k = get_code_to_measure_table(spacing, device=seg_pred.device)  # type: ignore
      spatial_dims = len(spacing)
      conv = (
          torch.nn.functional.conv3d
          if spatial_dims == 3
          else torch.nn.functional.conv2d
      )
      vol = torch.stack([seg_pred[None], seg_gt[None]], dim=0).float()  # type: ignore
      code_pred, code_gt = conv(vol, k.to(vol))
      # edges
      all_ones = len(code_to_area_table) - 1
      edges_pred = (code_pred != 0) & (code_pred != all_ones)
      edges_gt = (code_gt != 0) & (code_gt != all_ones)
      # areas of edges
      areas_pred = torch.index_select(
          code_to_area_table, 0, code_pred.view(-1).int()
      ).reshape(code_pred.shape)
      areas_gt = torch.index_select(
          code_to_area_table, 0, code_gt.view(-1).int()
      ).reshape(code_gt.shape)
      ret = (edges_pred[0], edges_gt[0], areas_pred[0], areas_gt[0])
      return converter(ret, wrap_sequence=False)
  
imports: from __future__ import annotations
  import warnings
  from functools import lru_cache, partial
  from types import ModuleType
  from typing import Any, Iterable, Sequence
  import numpy
  import torch
  from monai.config import NdarrayOrTensor, NdarrayTensor
  from monai.transforms.croppad.dictionary import CropForegroundD
  from monai.transforms.utils import distance_transform_edt
  from monai.utils import MetricReduction, convert_to_cupy, convert_to_dst_type, convert_to_numpy, convert_to_tensor, deprecated_arg, deprecated_arg_default, ensure_tuple_rep, look_up_option, optional_import
label_desc: Use image node_id 3 for calling a global function with example usage: get_mask_edges(seg_pred, seg_gt, label_idx, crop, spacing, always_return_as_numpy) and returns: converter, converter, unknown