anomalib icon indicating copy to clipboard operation
anomalib copied to clipboard

[Bug]: Tiler

Open AntiLibrary5 opened this issue 10 months ago • 5 comments

Describe the bug

With anomalib v1.2

where looking into the init of Tiler:

    def __init__(
        self,
        tile_size: int | Sequence,
        stride: int | Sequence | None = None,
        remove_border_count: int = 0,
        mode: ImageUpscaleMode = ImageUpscaleMode.PADDING,
    ) -> None:
        self.tile_size_h, self.tile_size_w = self.validate_size_type(tile_size)
        self.random_tile_count = 4

        if stride is not None:
            self.stride_h, self.stride_w = self.validate_size_type(stride)

        self.remove_border_count = remove_border_count
        self.overlapping = not (self.stride_h == self.tile_size_h and self.stride_w == self.tile_size_w)
        self.mode = mode

so stride_h is undefined when stride is None which is an undesired behaviour.

Dataset

N/A

Model

N/A

Steps to reproduce the behavior

from anomalib.data.utils.tiler import Tiler
image = # load image
tiler = Tiler(
            tile_size=tile_size,
            stride=stride,
        )
tiler.tile(image)

Throws the error

lib\site-packages\anomalib\data\utils\tiler.py", line 172, in __init__
    self.overlapping = not (self.stride_h == self.tile_size_h and self.stride_w == self.tile_size_w)
AttributeError: 'Tiler' object has no attribute 'stride_h'

OS information

OS information:

  • OS: Win11
  • Python version: 3.10
  • Anomalib version: 1.2
  • PyTorch version: 2.5

Expected behavior

Throws the error

lib\site-packages\anomalib\data\utils\tiler.py", line 172, in __init__
    self.overlapping = not (self.stride_h == self.tile_size_h and self.stride_w == self.tile_size_w)
AttributeError: 'Tiler' object has no attribute 'stride_h'

Screenshots

No response

Pip/GitHub

pip

What version/branch did you use?

No response

Configuration YAML

NA

Logs

NA

Code of Conduct

  • [X] I agree to follow this project's Code of Conduct

AntiLibrary5 avatar Jan 09 '25 14:01 AntiLibrary5

Hi, I think this part of code is not even used and should probably be removed. Another thing is that the tiler config callback docstring seems to be incorrect as well https://github.com/openvinotoolkit/anomalib/blob/9a0c7551013a0e9093833ded69c378c1230bc7ae/src/anomalib/callbacks/tiler_configuration.py#L57-L59

Whic doesn't match the actual Tiler docstring: https://github.com/openvinotoolkit/anomalib/blob/9a0c7551013a0e9093833ded69c378c1230bc7ae/src/anomalib/data/utils/tiler.py#L183-L184

So I think that we should just remove this overlapping attribute.

blaz-r avatar Jan 14 '25 09:01 blaz-r

Did that solve the issue? If so, we can open a PR to resolve this for everyone.

blaz-r avatar Feb 28 '25 10:02 blaz-r

No. untile() calls __fold() which uses a bunch of parameters. Much of which are initialized within other functions like tile For example: https://github.com/openvinotoolkit/anomalib/blob/9a0c7551013a0e9093833ded69c378c1230bc7ae/src/anomalib/data/utils/tiler.py#L440

        self.resized_h, self.resized_w = compute_new_image_size(
            image_size=(self.input_h, self.input_w),
            tile_size=(self.tile_size_h, self.tile_size_w),
            stride=(self.stride_h, self.stride_w),
        )

My work around was following: wrap anomalib tiler and return some relevant metadata to be used during untiling

        self.tiler = Tiler(
            tile_size=self.config.tile_size,
            stride=self.config.tile_size,
        )
        ...
        ...
        ...
        ...
        tiles = self.tiler.tile(img)
        metadata = {
            'num_tiles': num_tiles,
            'image_path': str(self.image_paths[idx]),
            'batch_size': self.tiler.batch_size,
            'input_h': self.tiler.input_h,
            'input_w': self.tiler.input_w,
            'num_channels': self.tiler.num_channels,
            'resized_h': self.tiler.resized_h,
            'resized_w': self.tiler.resized_w,
            'num_patches_h': self.tiler.num_patches_h,
            'num_patches_w': self.tiler.num_patches_w,
        }
        return tiles, metadata

Then for untiling:

    def untile_image(self, tiles: torch.Tensor, metadata: Dict) -> torch.Tensor:
        self.tiler.batch_size = metadata.get("batch_size", 1)
        self.tiler.num_channels = metadata.get("num_channels", 1)
        self.tiler.input_h = metadata.get("input_h", 1)
        self.tiler.input_w = metadata.get("input_w", 1)
        self.tiler.resized_h = metadata.get("resized_h", 1)
        self.tiler.resized_w = metadata.get("resized_w", 1)
        self.tiler.num_patches_w = metadata.get("num_patches_w", 1)
        self.tiler.num_patches_h = metadata.get("num_patches_h", 1)
        return self.tiler.untile(tiles)

AntiLibrary5 avatar Feb 28 '25 15:02 AntiLibrary5

Oh right, so this is not only an issue of arguments but problem of untiling with differen Tiler instance related to your issue #2496. As I said in that issue, when preparing tiled ensemble I avoided this by setting all these images within the init function:

    def __init__(self, tile_size: int | Sequence, stride: int | Sequence, image_size: int | Sequence) -> None:
        super().__init__(
            tile_size=tile_size,
            stride=stride,
        )

        # calculate final image size
        self.image_size = self.validate_size_type(image_size)
        self.input_h, self.input_w = self.image_size
        self.resized_h, self.resized_w = compute_new_image_size(
            image_size=(self.input_h, self.input_w),
            tile_size=(self.tile_size_h, self.tile_size_w),
            stride=(self.stride_h, self.stride_w),
        )
        # get number of patches in both dimensions
        self.num_patches_h = int((self.resized_h - self.tile_size_h) / self.stride_h) + 1
        self.num_patches_w = int((self.resized_w - self.tile_size_w) / self.stride_w) + 1
        self.num_tiles = self.num_patches_h * self.num_patches_w

That does however requires knowing image size beforehand, but for most models the image size should stay fixed anyway so I think this is a valid assumption. Maybe we should make the default tiler have this same behaviour, to make it more broadly applicable.

blaz-r avatar Feb 28 '25 15:02 blaz-r

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

github-actions[bot] avatar Jun 12 '25 05:06 github-actions[bot]

This issue was closed because it has been stalled for 14 days with no activity.

github-actions[bot] avatar Jun 26 '25 05:06 github-actions[bot]