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
RegionProps.__setattr__
has non-negligible overhead
#6601
Comments
@anntzer How about we use your last suggestion (looks most elegant to me?), and then just have a workaround for the old names, with acomment to deprecate / remove that? Are you able to help us implement this? |
I can propose the following patch, but it's not a complete PR as you also need to fix the tests which currently explicitly test that the backcompat aliases are not real properties (something that this patch changes): diff --git a/skimage/measure/_regionprops.py b/skimage/measure/_regionprops.py
index c7a5855c4..b19f3e090 100644
--- a/skimage/measure/_regionprops.py
+++ b/skimage/measure/_regionprops.py
@@ -282,6 +282,21 @@ def _inertia_eigvals_to_axes_lengths_3D(inertia_tensor_eigvals):
return axis_lengths
+def _make_backcompat_prop(alias):
+ name = PROPS[alias]
+ if alias.lower() == alias:
+ def fget(self):
+ return getattr(self, name)
+ else:
+ def fget(self):
+ raise AttributeError(f"'{type(self)}' object has no attribute '{alias}'")
+
+ def fset(self, value):
+ setattr(self, name, value)
+
+ return property(fget, fset)
+
+
class RegionProperties:
"""Please refer to `skimage.measure.regionprops` for more information
on the available region properties.
@@ -363,27 +378,12 @@ class RegionProperties:
f'Custom regionprop function\'s number of arguments must '
f'be 1 or 2, but {attr} takes {n_args} arguments.'
)
- elif attr in PROPS and attr.lower() == attr:
- if (
- self._intensity_image is None
- and PROPS[attr] in _require_intensity_image
- ):
- raise AttributeError(
- f"Attribute '{attr}' unavailable when `intensity_image` "
- f"has not been specified."
- )
- # retrieve deprecated property (excluding old CamelCase ones)
- return getattr(self, PROPS[attr])
else:
raise AttributeError(
f"'{type(self)}' object has no attribute '{attr}'"
)
- def __setattr__(self, name, value):
- if name in PROPS:
- super().__setattr__(PROPS[name], value)
- else:
- super().__setattr__(name, value)
+ locals().update({alias: _make_backcompat_prop(alias) for alias in PROPS})
@property
@_cached
@@ -1355,9 +1355,10 @@ def _parse_docs():
def _install_properties_docs():
prop_doc = _parse_docs()
- for p in [member for member in dir(RegionProperties)
- if not member.startswith('_')]:
- getattr(RegionProperties, p).__doc__ = prop_doc[p]
+ for member in dir(RegionProperties):
+ if member.startswith('_') or member in PROPS:
+ continue
+ getattr(RegionProperties, member).__doc__ = prop_doc[member]
if __debug__: I don't have the time to make a full PR either right now, but you are more than welcome to just take over the patch. |
Hey, there hasn't been any activity on this issue for more than 180 days. For now, we have marked it as "dormant" until there is some new activity. You are welcome to reach out to people by mentioning them here or on our forum if you need more feedback! If you think that this issue is no longer relevant, you may close it by yourself; otherwise, we may do it at some point (either way, it will be done manually). In any case, thank you for your contributions so far! |
Thanks for the suggestion! I think there is an even simpler way. If I move the definition of def __init__(self, *args, **kwargs):
# initialize...
def __setattr__(self, name, value):
"""Translate older property names to current ones."""
object.__setattr__(PROPS.get(name, name), value)
# Override the default __setattr__ after initialization to speed it up
self.__setattr__ = __setattr__ |
Description:
RegionProps currently relies on
__setattr__
to provide backcompat access of old property names (e.g. mappingBoundingBox
tobbox
); but because this also affects all attribute initializations in__init__
, this ends up having a non-negligible overhead which could be avoided.Way to reproduce:
First check how much time it takes to label a random (1024, 1024) binary image.
-> 3673 labels (label(im).max()); 30ms/iteration
Then check how much time it takes just to instantiate the corresponding regionprops.
-> 31ms/iteration i.e. even more than the labeling itself
One easy way to skip
__setitem__
is to directly update the object's__dict__
directly, e.g. replace, inRegionProperties.__init__
,by
which speeds up the above timeit call to 18ms/iteration -- almost twice as fast.
Admittedly that's a bit ugly; probably the "proper" way to do this is to completely get rid of the custom
__setattr__
(and of backcompat alias handling in__getattr__
) and to instead define a bunch of pass-through properties, e.g.(which can probably be done in a loop in the class definition body in you prefer, e.g. something along the lines of
)
OTOH, I see that #6000 explicitly chose not to provide properties for the old aliases for the purpose of better hiding them.
I don't really care whether to fix this with the
vars(self).update(...)
approach or by introducing these properties.Traceback or output:
No response
Version information:
The text was updated successfully, but these errors were encountered: