anomalib
anomalib copied to clipboard
[Bug]: Tiler
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
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.
Did that solve the issue? If so, we can open a PR to resolve this for everyone.
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)
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.
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.
This issue was closed because it has been stalled for 14 days with no activity.