def forward(self, batch: torch.Tensor) -> InferenceBatch:
        """Forward pass through AI-VAD model.

        The forward pass consists of the following steps:
        1. Extract first and last frame from input clip
        2. Extract optical flow between frames and detect regions of interest
        3. Extract features (velocity, pose, deep) for each region
        4. Estimate density and compute anomaly scores

        Args:
            batch (torch.Tensor): Input tensor of shape ``(N, L, C, H, W)`` where:
                - ``N``: Batch size
                - ``L``: Sequence length
                - ``C``: Number of channels
                - ``H``: Height
                - ``W``: Width

        Returns:
            InferenceBatch: Batch containing:
                - ``pred_score``: Per-image anomaly scores of shape ``(N,)``
                - ``anomaly_map``: Per-pixel anomaly scores of shape ``(N, H, W)``

        Example:
            >>> batch = torch.randn(32, 2, 3, 256, 256)
            >>> model = AiVadModel()
            >>> output = model(batch)
            >>> output.pred_score.shape, output.anomaly_map.shape
            (torch.Size([32]), torch.Size([32, 256, 256]))
        """
        self.flow_extractor.eval()
        self.region_extractor.eval()
        self.feature_extractor.eval()

        # 1. get first and last frame from clip
        first_frame = batch[:, 0, ...]
        last_frame = batch[:, -1, ...]

        # 2. extract flows and regions
        with torch.no_grad():
            flows = self.flow_extractor(first_frame, last_frame)
            regions = self.region_extractor(first_frame, last_frame)

        # 3. extract pose, appearance and velocity features
        features_per_batch = self.feature_extractor(first_frame, flows, regions)

        if self.training:
            return features_per_batch

        # 4. estimate density
        box_scores = []
        image_scores = []
        for features in features_per_batch:
            box, image = self.density_estimator(features)
            box_scores.append(box)
            image_scores.append(image)

        anomaly_map = torch.stack(
            [
                torch.amax(region["masks"] * scores.view(-1, 1, 1, 1), dim=0)
                for region, scores in zip(regions, box_scores, strict=False)
            ],
        )

        return InferenceBatch(
            pred_score=torch.stack(image_scores),
            anomaly_map=anomaly_map,
        )
Forward pass through AI-VAD model.

        The forward pass consists of the following steps:
        1. Extract first and last frame from input clip
        2. Extract optical flow between frames and detect regions of interest
        3. Extract features (velocity, pose, deep) for each region
        4. Estimate density and compute anomaly scores

        Args:
            batch (torch.Tensor): Input tensor of shape ``(N, L, C, H, W)`` where:
                - ``N``: Batch size
                - ``L``: Sequence length
                - ``C``: Number of channels
                - ``H``: Height
                - ``W``: Width

        Returns:
            InferenceBatch: Batch containing:
                - ``pred_score``: Per-image anomaly scores of shape ``(N,)``
                - ``anomaly_map``: Per-pixel anomaly scores of shape ``(N, H, W)``

        Example:
            >>> batch = torch.randn(32, 2, 3, 256, 256)
            >>> model = AiVadModel()
            >>> output = model(batch)
            >>> output.pred_score.shape, output.anomaly_map.shape
            (torch.Size([32]), torch.Size([32, 256, 256]))
        
src/anomalib/models/video/ai_vad/torch_model.py