class_docstr: Patchcore Module.
class_docstr_tok: ["Patchcore","Module","."]
docstr: Compute Image-Level Anomaly Score.
  
  Args:
      patch_scores (Tensor): Patch-level anomaly scores
      locations: Memory bank locations of the nearest neighbor for each patch location
      embedding: The feature embeddings that generated the patch scores
  Returns:
      Tensor: Image-level anomaly scores
docstr_tok: ["Compute","Image-Level","Anomaly","Score",".","Args",":","patch_scores","(","Tensor",")",":","Patch-level","anomaly","scores","locations",":","Memory","bank","locations","of","the","nearest","neighbor","for","each","patch","location","embedding",":","The","feature","embeddings","that","generated","the","patch","scores","Returns",":","Tensor",":","Image-level","anomaly","scores"]
code: def compute_anomaly_score(
      self, patch_scores: Tensor, locations: Tensor, embedding: Tensor
  ) -> Tensor:
      """Compute Image-Level Anomaly Score.
  
      Args:
          patch_scores (Tensor): Patch-level anomaly scores
          locations: Memory bank locations of the nearest neighbor for each patch location
          embedding: The feature embeddings that generated the patch scores
      Returns:
          Tensor: Image-level anomaly scores
      """
  
      # Don't need to compute weights if num_neighbors is 1
      if self.num_neighbors == 1:
          return patch_scores.amax(1)
      batch_size, num_patches = patch_scores.shape
      # 1. Find the patch with the largest distance to it's nearest neighbor in each image
      max_patches = torch.argmax(
          patch_scores, dim=1
      )  # indices of m^test,* in the paper
      # m^test,* in the paper
      max_patches_features = embedding.reshape(
          batch_size, num_patches, -1
      )[torch.arange(batch_size), max_patches]
      # 2. Find the distance of the patch to it's nearest neighbor, and the location of the nn in the membank
      score = patch_scores[
          torch.arange(batch_size), max_patches
      ]  # s^* in the paper
      nn_index = locations[
          torch.arange(batch_size), max_patches
      ]  # indices of m^* in the paper
      # 3. Find the support samples of the nearest neighbor in the membank
      nn_sample = self.memory_bank[nn_index, :]  # m^* in the paper
      # indices of N_b(m^*) in the paper
      memory_bank_effective_size = self.memory_bank.shape[
          0
      ]  # edge case when memory bank is too small
      _, support_samples = self.nearest_neighbors(
          nn_sample,
          n_neighbors=min(
              self.num_neighbors, memory_bank_effective_size
          ),
      )
      # 4. Find the distance of the patch features to each of the support samples
      distances = self.euclidean_dist(
          max_patches_features.unsqueeze(1),
          self.memory_bank[support_samples],
      )
      # 5. Apply softmax to find the weights
      weights = (1 - F.softmax(distances.squeeze(1), 1))[..., 0]
      # 6. Apply the weight factor to the score
      score = weights * score  # s in the paper
      return score
  
file_path: anomalib/src/anomalib/models/patchcore/torch_model.py
imports: from __future__ import annotations
  import torch
  import torch.nn.functional
  from torch import Tensor, nn
  from anomalib.models.components import DynamicBufferModule, FeatureExtractor, KCenterGreedy
  from anomalib.models.patchcore.anomaly_map import AnomalyMapGenerator
  from anomalib.pre_processing import Tiler
label_desc: Use image node_id 8 for calling the PatchcoreModel obj's underlying member method code with example usage: obj.compute_anomaly_score(patch_scores, locations, embedding) and returns: score, patch_scores