Skip to content

1391 fix custom image subclass transforms#1400

Open
siddharth10ss wants to merge 7 commits intoTorchIO-project:mainfrom
siddharth10ss:1391-fix-custom-image-subclass-transforms
Open

1391 fix custom image subclass transforms#1400
siddharth10ss wants to merge 7 commits intoTorchIO-project:mainfrom
siddharth10ss:1391-fix-custom-image-subclass-transforms

Conversation

@siddharth10ss
Copy link

Fixes #1391

Summary

Implements the new_like() factory method to support custom Image subclasses with different constructor signatures, with comprehensive fallback mechanism and full spatial transform coverage.

Description

TorchIO spatial transforms previously reconstructed images using type(image)(tensor=..., affine=...), which failed for custom Image subclasses with different __init__ signatures (e.g., requiring additional parameters like history). This PR solves the issue by:

  1. Adding new_like() method to the base Image class as a factory method for creating new instances
  2. Fallback mechanism using copy.deepcopy() for subclasses that don't override new_like()
  3. Updating ALL spatial transforms to use the new pattern
  4. Comprehensive test suite with 12 test cases

Changes

Core Implementation (src/torchio/data/image.py)

  • Added new_like(tensor, affine=None) method to base Image class
  • Returns new instance with updated tensor/affine while preserving attributes
  • Fallback to copy.deepcopy() for robust subclass support
  • Subclasses can override to handle custom initialization

Transform Updates

Updated all spatial transforms to use new_like():

  • Crop (src/torchio/transforms/preprocessing/spatial/crop.py)
  • Pad (src/torchio/transforms/preprocessing/spatial/pad.py)
  • CropOrPad (uses Crop internally, works automatically)
  • Resample (src/torchio/transforms/preprocessing/spatial/resample.py)
  • ToOrientation (src/torchio/transforms/preprocessing/spatial/to_orientation.py)
  • Transpose (src/torchio/transforms/preprocessing/spatial/transpose.py)
  • ToReferenceSpace (src/torchio/transforms/preprocessing/spatial/to_reference_space.py)

Added subject.update_attributes() calls for proper Subject-Image synchronization.

Test Suite (tests/transforms/preprocessing/test_image_factory.py)

12 comprehensive test cases:

  • Custom subclass with required parameters
  • Custom subclass with optional parameters
  • Chained transforms preserving attributes
  • All spatial transforms (Crop, Pad, Resample, etc.)
  • Fallback mechanism verification
  • Subject attribute synchronization
  • Backward compatibility

Example Usage

import torchio as tio
import torch

class HistoryScalarImage(tio.ScalarImage):
    def __init__(self, tensor, affine, history, **kwargs):
        super().__init__(tensor=tensor, affine=affine, **kwargs)
        self.history = history
    
    def new_like(self, tensor, affine=None):
        """Override to preserve custom attributes."""
        return type(self)(
            tensor=tensor,
            affine=affine if affine is not None else self.affine,
            history=self.history,
            check_nans=self.check_nans,
            reader=self.reader,
        )

# Now works seamlessly with all transforms!
img = HistoryScalarImage(torch.rand(1,10,10,10), affine=torch.eye(4), history=["created"])
subject = tio.Subject(image=img)

transform = tio.Compose([
    tio.Crop(cropping=2),
    tio.Pad(padding=5),
    tio.Resample(target_shape=(64, 64, 64))
])

result = transform(subject)  #  Success! history preserved throughout
print(result.image.history)  # ["created"]

siddharth10ss and others added 6 commits December 10, 2025 23:09
Fixes TorchIO-project#1391

Problem:
Spatial transforms failed with custom Image subclasses that had
non-standard __init__ parameters. Transforms used type(image)(...)
which only passed standard parameters, causing TypeError for
subclasses requiring additional arguments.

Solution:
Added new_like() factory method to Image class that:
- Creates new image instances while preserving essential attributes
- Can be overridden by subclasses to handle custom parameters
- Maintains full backward compatibility

Changes:
- src/torchio/data/image.py: Add new_like() method
- src/torchio/transforms/preprocessing/spatial/crop.py: Use new_like()
- src/torchio/transforms/preprocessing/spatial/to_reference_space.py: Use new_like()
- tests/transforms/test_custom_image_subclass.py: Add 8 comprehensive tests

All new tests pass (8/8). Backward compatibility verified.
…al transforms

- Add fallback mechanism to new_like() using copy.deepcopy() for robust custom subclass support
- Update Pad, Resample, ToOrientation, Transpose transforms to use new_like() pattern
- Add subject.update_attributes() calls for proper Subject synchronization
- Add comprehensive test suite with 12 test cases covering all scenarios
- Ensure ALL spatial transforms support custom Image subclasses with additional constructor parameters

Key improvements:
- Fallback handles subclasses without new_like() override automatically
- Fixed Subject attribute/dictionary synchronization issue
- Complete coverage of spatial transforms: Crop, Pad, CropOrPad, Resample, ToOrientation, Transpose, ToReferenceSpace
- Maintains full backward compatibility with existing code

Fixes TorchIO-project#1391 completely - all spatial transforms now work with custom Image subclasses
- Fix import order in test files: pytest before torch, blank line before local imports
- Remove unused 'reference' variable in test_to_reference_space_with_custom_image
- Addresses pre-commit.ci formatting issues
- Merge automatic formatting fixes from pre-commit.ci
- Resolve conflict by keeping cleaner version without unused variable
- All formatting issues now resolved
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements a new_like() factory method in the base Image class to support custom Image subclasses with different constructor signatures. The issue arose when spatial transforms tried to reconstruct images using type(image)(tensor=..., affine=...), which failed for custom subclasses requiring additional parameters.

Changes:

  • Added new_like() method to the base Image class with a fallback mechanism using copy.deepcopy() for subclasses that don't override it
  • Updated all spatial transforms (Crop, Pad, Resample, ToOrientation, Transpose, ToReferenceSpace) to use new_like() instead of direct constructor calls
  • Added comprehensive test coverage with two test files containing 12+ test cases covering various scenarios

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/torchio/data/image.py Added new_like() factory method with try-except fallback mechanism and comprehensive documentation
src/torchio/transforms/preprocessing/spatial/crop.py Updated to use new_like() for creating cropped images
src/torchio/transforms/preprocessing/spatial/pad.py Updated to use new_like() and added subject.update_attributes() call
src/torchio/transforms/preprocessing/spatial/resample.py Updated to use new_like() and added subject.update_attributes() call
src/torchio/transforms/preprocessing/spatial/to_orientation.py Updated to use new_like() and added subject.update_attributes() call
src/torchio/transforms/preprocessing/spatial/transpose.py Updated to use new_like() and added subject.update_attributes() call
src/torchio/transforms/preprocessing/spatial/to_reference_space.py Updated build_image_from_reference() to use new_like() and added subject.update_attributes() call
tests/transforms/test_custom_image_subclass.py Comprehensive test suite for custom Image subclasses with various transforms
tests/transforms/test_custom_image_subclass_fix.py Additional test suite with similar coverage, potentially duplicating the other test file

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

…hadowing

- Remove duplicate test_custom_image_subclass_fix.py (keeping test_custom_image_subclass.py)
- Fix variable shadowing in build_image_from_reference function
- Use downsampled_reference instead of shadowing reference parameter
@siddharth10ss
Copy link
Author

Update: All Feedback Addressed

I've completed the changes requested in the Copilot code review in commit 33903bc:

Changes Made:

  1. Removed duplicate test file - Deleted tests/transforms/test_custom_image_subclass_fix.py
  2. Fixed variable shadowing - In to_reference_space.py, renamed the shadowing variable:
    # Before: reference = downsample(reference)  
    # After:  downsampled_reference = downsample(reference)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Some transforms (e.g., Crop, Pad, CropOrPad) not compatible with custom Image subclasses with different constructors

1 participant