class_docstr: Perception block normalizing the input and de-normalizing the output tensor dimensions.
  
  Examples where this interface needs to be implemented are Dense Layers (batch-dim, feature-dim)
  or Convolution Blocks (batch-dim, feature-dim, row-dim, column-dim)
  
  :param in_keys: Keys identifying the input tensors.
  :param out_keys: Keys identifying the output tensors.
  :param in_shapes: List of input shapes.
  :param in_num_dims: Required number of dimensions for corresponding input.
  :param out_num_dims: Required number of dimensions for corresponding output.
class_docstr_tok: ["Perception","block","normalizing","the","input","and","de-normalizing","the","output","tensor","dimensions",".","Examples","where","this","interface","needs","to","be","implemented","are","Dense","Layers","(","batch-dim",",","feature-dim",")","or","Convolution","Blocks","(","batch-dim",",","feature-dim",",","row-dim",",","column-dim",")",":","param","in_keys",":","Keys","identifying","the","input","tensors",".",":","param","out_keys",":","Keys","identifying","the","output","tensors",".",":","param","in_shapes",":","List","of","input","shapes",".",":","param","in_num_dims",":","Required","number","of","dimensions","for","corresponding","input",".",":","param","out_num_dims",":","Required","number","of","dimensions","for","corresponding","output","."]
docstr: Normalizes the tensor dictionary to the requested dimensions.
          
docstr_tok: ["Normalizes","the","tensor","dictionary","to","the","requested","dimensions","."]
code: def _normalize(
      self, tensor_dict: Dict[str, torch.Tensor]
  ) -> Tuple[Dict[str, torch.Tensor], Sequence[int]]:
      """Normalizes the tensor dictionary to the requested dimensions."""
  
      # prepare all input tensors
      normalized_block_input = dict()
      original_shapes = dict()
      original_in_batch_shapes = []
  
      for i, in_key in enumerate(self.in_keys):
          # collect original input shape
          in_tensor = tensor_dict[in_key]
          original_shapes[in_key] = list(in_tensor.shape)
          original_num_dims = in_tensor.ndim
  
          # reshape to target shape
          required_num_dims = self.in_num_dims[i]
          num_feat_dims = required_num_dims - 1
          num_batch_dims = original_num_dims - num_feat_dims
          original_batch_shape = original_shapes[in_key][
              :num_batch_dims
          ]
  
          # actual reshaping
          if in_tensor.ndim != required_num_dims:
              batch_dim = int(np.prod(original_batch_shape))
              new_shape = [batch_dim] + original_shapes[in_key][
                  -num_feat_dims:
              ]
              in_tensor = in_tensor.reshape(new_shape)
  
          # keep normalized block input
          normalized_block_input[in_key] = in_tensor
          original_in_batch_shapes.append(original_batch_shape)
  
      assert all(
          [
              original_in_batch_shapes[0] == b
              for b in original_in_batch_shapes[1:]
          ]
      ), "All inputs should have the same batch dimension"
      original_in_batch_shape = original_in_batch_shapes[0]
  
      return normalized_block_input, original_in_batch_shape
  
file_path: maze/maze/perception/blocks/shape_normalization.py
imports: from abc import ABC, abstractmethod
  from typing import Dict, Union, List, Tuple, Sequence
  import numpy
  import torch
  from maze.core.annotations import override
  from maze.perception.blocks.base import PerceptionBlock
label_desc: Use image node_id 4 for calling the ShapeNormalizationBlock obj's underlying member method code with example usage: obj._normalize(tensor_dict) and returns: normalized_block_input,  original_in_batch_shape