"""Anomaly Map Generator for the PaDiM model implementation."""# Copyright (C) 2022 Intel Corporation# SPDX-License-Identifier: Apache-2.0fromtypingimportList,Tuple,Unionimporttorchimporttorch.nn.functionalasFfromomegaconfimportListConfigfromtorchimportTensor,nnfromanomalib.models.componentsimportGaussianBlur2d
[docs]classAnomalyMapGenerator(nn.Module):"""Generate Anomaly Heatmap. Args: image_size (Union[ListConfig, Tuple]): Size of the input image. The anomaly map is upsampled to this dimension. sigma (int, optional): Standard deviation for Gaussian Kernel. Defaults to 4. """def__init__(self,image_size:Union[ListConfig,Tuple],sigma:int=4):super().__init__()self.image_size=image_sizeifisinstance(image_size,tuple)elsetuple(image_size)kernel_size=2*int(4.0*sigma+0.5)+1self.blur=GaussianBlur2d(kernel_size=(kernel_size,kernel_size),sigma=(sigma,sigma),channels=1)@staticmethod
[docs]defcompute_distance(embedding:Tensor,stats:List[Tensor])->Tensor:"""Compute anomaly score to the patch in position(i,j) of a test image. Ref: Equation (2), Section III-C of the paper. Args: embedding (Tensor): Embedding Vector stats (List[Tensor]): Mean and Covariance Matrix of the multivariate Gaussian distribution Returns: Anomaly score of a test image via mahalanobis distance. """batch,channel,height,width=embedding.shapeembedding=embedding.reshape(batch,channel,height*width)# calculate mahalanobis distancesmean,inv_covariance=statsdelta=(embedding-mean).permute(2,0,1)distances=(torch.matmul(delta,inv_covariance)*delta).sum(2).permute(1,0)distances=distances.reshape(batch,1,height,width)distances=distances.clamp(0).sqrt()returndistances
[docs]defup_sample(self,distance:Tensor)->Tensor:"""Up sample anomaly score to match the input image size. Args: distance (Tensor): Anomaly score computed via the mahalanobis distance. Returns: Resized distance matrix matching the input image size """score_map=F.interpolate(distance,size=self.image_size,mode="bilinear",align_corners=False,)returnscore_map
[docs]defsmooth_anomaly_map(self,anomaly_map:Tensor)->Tensor:"""Apply gaussian smoothing to the anomaly map. Args: anomaly_map (Tensor): Anomaly score for the test image(s). Returns: Filtered anomaly scores """blurred_anomaly_map=self.blur(anomaly_map)returnblurred_anomaly_map
[docs]defcompute_anomaly_map(self,embedding:Tensor,mean:Tensor,inv_covariance:Tensor)->Tensor:"""Compute anomaly score. Scores are calculated based on embedding vector, mean and inv_covariance of the multivariate gaussian distribution. Args: embedding (Tensor): Embedding vector extracted from the test set. mean (Tensor): Mean of the multivariate gaussian distribution inv_covariance (Tensor): Inverse Covariance matrix of the multivariate gaussian distribution. Returns: Output anomaly score. """score_map=self.compute_distance(embedding=embedding,stats=[mean.to(embedding.device),inv_covariance.to(embedding.device)],)up_sampled_score_map=self.up_sample(score_map)smoothed_anomaly_map=self.smooth_anomaly_map(up_sampled_score_map)returnsmoothed_anomaly_map
[docs]defforward(self,**kwargs):"""Returns anomaly_map. Expects `embedding`, `mean` and `covariance` keywords to be passed explicitly. Example: >>> anomaly_map_generator = AnomalyMapGenerator(image_size=input_size) >>> output = anomaly_map_generator(embedding=embedding, mean=mean, covariance=covariance) Raises: ValueError: `embedding`. `mean` or `covariance` keys are not found Returns: torch.Tensor: anomaly map """ifnot("embedding"inkwargsand"mean"inkwargsand"inv_covariance"inkwargs):raiseValueError(f"Expected keys `embedding`, `mean` and `covariance`. Found {kwargs.keys()}")embedding:Tensor=kwargs["embedding"]mean:Tensor=kwargs["mean"]inv_covariance:Tensor=kwargs["inv_covariance"]returnself.compute_anomaly_map(embedding,mean,inv_covariance)