Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support slicing properties as properties (aka. support obj[i:j].shape). #44

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

anntzer
Copy link

@anntzer anntzer commented Jan 25, 2023

Currently, after sliced = pims.open(...)[i:j], the sliced object doesn't have a .shape attribute (#38), even though it does have a len() and a .frame_shape (the latter being propagated via propagated_attrs) and even though FramesSequence.shape is actually a property (return (len(self), *self.frame_shape)).

This PR adds the ability to mark properties as being propagated, not as values, but rather as properties in the sliced object too (the decorator name, @sliceable_property, can be bikeshedded). This is done by having __getitem__ not return a Slicerator, but a subclass of Slicerator with the relevant properties.

While we're at it, "standard" propagated attributes are also implemented using properties now (which directly call getattr on the ancestor, not going through any fget); this is a slight API change in that later changes to the attribute on the ancestor will now be reflected on the sliced object, but the keeping the implementation consistent with sliceable properties seems nice and having these attributes tab-completable (rather than being hidden behind __getattr__ is good for usability). In any case this could be changed back if the API change is problematic.

As a result, with this PR, pims/base_frames.py just needs a small extra patch:

diff --git i/pims/base_frames.py w/pims/base_frames.py
index d91df98..122b12d 100644
--- i/pims/base_frames.py
+++ w/pims/base_frames.py
@@ -1,6 +1,6 @@
 import numpy as np
 import itertools
-from slicerator import Slicerator, propagate_attr, index_attr
+from slicerator import Slicerator, propagate_attr, index_attr, sliceable_property
 from .frame import Frame
 from abc import ABC, abstractmethod, abstractproperty
 from warnings import warn
@@ -92,7 +92,7 @@ class FramesSequence(FramesStream):
     Must be finite length.

     """
-    propagate_attrs = ['frame_shape', 'pixel_type']
+    propagate_attrs = ['frame_shape', 'pixel_type', 'shape']

     def __getitem__(self, key):
         """__getitem__ is handled by Slicerator. In all pims readers, the data
@@ -102,7 +102,7 @@ class FramesSequence(FramesStream):
     def __iter__(self):
         return iter(self[:])

-    @property
+    @sliceable_property
     def shape(self):
         return (len(self), *self.frame_shape)

to support sliced = pims.open(...)[i:j]; sliced.shape.

I can add tests and make the corresponding change in pims if we agree on the design here.

Currently, after `sliced = pims.open(...)[i:j]`, the `sliced` object
doesn't have a `.shape` attribute, even though it does have a `len()`
and a `.frame_shape` (the latter being propagated via propagated_attrs)
*and* even though `FramesSequence.shape` is actually a property
(`return (len(self), *self.frame_shape)`).

This PR adds the ability to mark properties as being propagated, not as
values, but rather as properties in the sliced object too (the decorator
name, `@sliceable_property`, can be bikeshedded).  This is done by
having `__getitem__` not return a Slicerator, but a subclass of
Slicerator with the relevant properties.

While we're at it, "standard" propagated attributes are also implemented
using properties now (which directly call getattr on the ancestor, not
going through any fget); this is a slight API change in that later
changes to the attribute *on the ancestor* will now be reflected on the
sliced object, but the keeping the implementation consistent with
sliceable properties seems nice and having these attributes
tab-completable (rather than being hidden behind `__getattr__` is good
for usability).  In any case this could be changed back if the API
change is problematic.

As a result, with this PR, pims/base_frames.py just needs a small extra
patch:

    diff --git i/pims/base_frames.py w/pims/base_frames.py
    index d91df98..122b12d 100644
    --- i/pims/base_frames.py
    +++ w/pims/base_frames.py
    @@ -1,6 +1,6 @@
     import numpy as np
     import itertools
    -from slicerator import Slicerator, propagate_attr, index_attr
    +from slicerator import Slicerator, propagate_attr, index_attr, sliceable_property
     from .frame import Frame
     from abc import ABC, abstractmethod, abstractproperty
     from warnings import warn
    @@ -92,7 +92,7 @@ class FramesSequence(FramesStream):
         Must be finite length.

         """
    -    propagate_attrs = ['frame_shape', 'pixel_type']
    +    propagate_attrs = ['frame_shape', 'pixel_type', 'shape']

         def __getitem__(self, key):
             """__getitem__ is handled by Slicerator. In all pims readers, the data
    @@ -102,7 +102,7 @@ class FramesSequence(FramesStream):
         def __iter__(self):
             return iter(self[:])

    -    @Property
    +    @sliceable_property
         def shape(self):
             return (len(self), *self.frame_shape)

to support `sliced = pims.open(...)[i:j]; sliced.shape`.
@anntzer
Copy link
Author

anntzer commented Aug 30, 2023

@nkeim @tacaswell Any interest in pushing this PR (or something similar) forward? As noted above this is really motivated by adding support for .shape on sliced pims objects.

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.

1 participant