class_docstr: Module extracting line segments from junctions and line heatmaps.
  
  Args:
      detect_thresh: The probability threshold for mean activation (0. ~ 1.)
      num_samples: Number of sampling locations along the line segments.
      inlier_thresh: The min inlier ratio to satisfy (0. ~ 1.) => 0. means no threshold.
      heatmap_low_thresh: The lowest threshold for the pixel to be considered as candidate in junction recovery.
      heatmap_high_thresh: The higher threshold for NMS in junction recovery.
      max_local_patch_radius: The max patch to be considered in local maximum search.
      lambda_radius: The lambda factor in linear local maximum search formulation
      use_candidate_suppression: Apply candidate suppression to break long segments into short sub-segments.
      nms_dist_tolerance: The distance tolerance for nms. Decide whether the junctions are on the line.
      use_heatmap_refinement: Use heatmap refinement method or not.
      heatmap_refine_cfg: The configs for heatmap refinement methods.
      use_junction_refinement: Use junction refinement method or not.
      junction_refine_cfg: The configs for junction refinement methods.
docstr: Refine the line endpoints in a similar way as in LSD.
docstr_tok: ["Refine","the","line","endpoints","in","a","similar","way","as","in","LSD","."]
code: def refine_junction_perturb(
      self,
      junctions: Tensor,
      line_map: Tensor,
      heatmap: Tensor,
      H: int,
      W: int,
      device: torch.device,
  ) -> Tuple[Tensor, Tensor]:
      """Refine the line endpoints in a similar way as in LSD."""
      # Fetch refinement parameters
      if not isinstance(self.junction_refine_cfg, dict):
          raise TypeError(
              f"Expected to have a dict of config for junction. Gotcha {type(self.junction_refine_cfg)}"
          )
      num_perturbs = self.junction_refine_cfg["num_perturbs"]
      perturb_interval = self.junction_refine_cfg["perturb_interval"]
      side_perturbs = (num_perturbs - 1) // 2
  
      # Fetch the 2D perturb mat
      perturb_vec = torch.arange(
          start=-perturb_interval * side_perturbs,
          end=perturb_interval * (side_perturbs + 1),
          step=perturb_interval,
          device=device,
      )
  
      h1_grid, w1_grid, h2_grid, w2_grid = torch_meshgrid(
          [perturb_vec, perturb_vec, perturb_vec, perturb_vec],
          indexing="ij",
      )
  
      perturb_tensor = concatenate(
          [
              h1_grid[..., None],
              w1_grid[..., None],
              h2_grid[..., None],
              w2_grid[..., None],
          ],
          -1,
      )
      perturb_tensor_flat = perturb_tensor.view(-1, 2, 2)
  
      # Fetch all the detected lines
      detected_seg_indexes = where(torch.triu(line_map, diagonal=1))
      start_points = junctions[detected_seg_indexes[0]]
      end_points = junctions[detected_seg_indexes[1]]
      line_segments = stack([start_points, end_points], 1)
  
      line_segment_candidates = (
          line_segments.unsqueeze(dim=1) + perturb_tensor_flat[None]
      )
      # Clip the boundaries
      line_segment_candidates[..., 0] = torch.clamp(
          line_segment_candidates[..., 0], min=0, max=H - 1
      )
      line_segment_candidates[..., 1] = torch.clamp(
          line_segment_candidates[..., 1], min=0, max=W - 1
      )
  
      # Iterate through all the segments
      refined_segment_lst = []
      num_segments = len(line_segments)
      for idx in range(num_segments):
          segment = line_segment_candidates[idx]
          # Get the corresponding start and end junctions
          candidate_junc_start = segment[:, 0]
          candidate_junc_end = segment[:, 1]
  
          # Get the sampling locations (N x 64)
          sampler = self.torch_sampler.to(device)[None]
          cand_samples_h = candidate_junc_start[
              :, 0:1
          ] * sampler + candidate_junc_end[:, 0:1] * (1 - sampler)
          cand_samples_w = candidate_junc_start[
              :, 1:2
          ] * sampler + candidate_junc_end[:, 1:2] * (1 - sampler)
  
          # Clip to image boundary
          cand_h = torch.clamp(cand_samples_h, min=0, max=H - 1)
          cand_w = torch.clamp(cand_samples_w, min=0, max=W - 1)
  
          # Perform bilinear sampling
          segment_feat = self.detect_bilinear(heatmap, cand_h, cand_w)
          segment_results = torch.mean(segment_feat, dim=-1)
          max_idx = torch.argmax(segment_results)
          refined_segment_lst.append(segment[max_idx][None])
  
      # Concatenate back to segments
      refined_segments = concatenate(refined_segment_lst, 0)
  
      # Convert back to junctions and line_map
      junctions_new = concatenate(
          [refined_segments[:, 0, :], refined_segments[:, 1, :]], 0
      )
      junctions_new = torch.unique(junctions_new, dim=0)
      line_map_new = self.segments_to_line_map(
          junctions_new, refined_segments
      )
  
      return junctions_new, line_map_new
  
imports: import math
  from typing import Any, Dict, Optional, Tuple
  import torch
  from kornia.core import Module, Tensor, concatenate, sin, stack, tensor, where, zeros
  from kornia.core.check import KORNIA_CHECK_SHAPE
  from kornia.geometry.bbox import nms
  from kornia.utils import map_location_to_cpu, torch_meshgrid
  from .backbones import SOLD2Net
label_desc: Use image node_id 6 for calling the LineSegmentDetectionModule obj's underlying member method code with example usage: obj.refine_junction_perturb(junctions, line_map, heatmap, H, W, device) and returns: junctions_new,  line_map_new