Skip to content

Switch BadPixels{,FF} base class to enum.IntFlag

David Hammer requested to merge feat/use-enum-intflag into master

Description

Casually scrolling through Python docs, I saw enum.IntFlag (available since Python 3.6) which could make all our flag-related code nicer.

The two advantages are:

  • Int: the resulting enum constants we get are also subclasses of int. This means that if we for instance want to put them into numpy arrays, we can assign them directly, dropping .value.
  • Flag: enables bitwise operations between enum constants resulting in enum constants. Again, this means we can drop .value (which would throw away the "semantic" value of the enum class).

I've gone over cal_tools and dropped all .value suffixes. Given that work is ongoing with notebooks, I'd suggest they can be updated later as we refactor anyway.

Side note: what to call a merge request which is just refactoring / code style stuff? This is hardly a feature or a fix.

How Has This Been Tested?

Existing code using .value is not impacted. Let's compare the two base classes:

from enum import Enum, IntFlag
import numpy as np


class OldBadPixels(Enum):
    OFFSET_OUT_OF_THRESHOLD  = 0b000000000000000000001 # bit 1
    NOISE_OUT_OF_THRESHOLD   = 0b000000000000000000010 # bit 2
	...

class NewBadPixels(IntFlag):
    OFFSET_OUT_OF_THRESHOLD  = 0b000000000000000000001 # bit 1
    NOISE_OUT_OF_THRESHOLD   = 0b000000000000000000010 # bit 2
	...

We can use the IntFlag-based ones directly as numbers:

a1 = np.zeros(1, dtype=np.uint32)
a1[0] |= NewBadPixels.NON_LIN_RESPONSE_REGION
a1[0] |= NewBadPixels.CI_LINEAR_DEVIATION

Or use .value (so existing code doing this is not affected):

a2 = np.zeros(1, dtype=np.uint32)
a2[0] |= NewBadPixels.NON_LIN_RESPONSE_REGION.value
a2[0] |= NewBadPixels.CI_LINEAR_DEVIATION.value

So far, a1 == a2. For completeness, note that the old Enum-based flags are not numbers:

b1 = np.zeros(1, dtype=np.uint32)
b1[0] |= OldBadPixels.NON_LIN_RESPONSE_REGION
b1[0] |= OldBadPixels.CI_LINEAR_DEVIATION
Traceback (most recent call last):
  File "test-440.py", line 68, in <module>
    b1[0] |= OldBadPixels.NON_LIN_RESPONSE_REGION
TypeError: unsupported operand type(s) for |: 'int' and 'OldBadPixels'

And as expected, the .value has not changed at all:

b2 = np.zeros(1, dtype=np.uint32)
b2[0] |= OldBadPixels.NON_LIN_RESPONSE_REGION.value
b2[0] |= OldBadPixels.CI_LINEAR_DEVIATION.value

So a1 == a2 == b2.

As advertised, we can do bitwise operations directly on members and get BadPixels bad instead of untypely numbers:

> print(NewBadPixels.DATA_STD_IS_ZERO | NewBadPixels.INTERPOLATED)
NewBadPixels.INTERPOLATED|DATA_STD_IS_ZERO

Bonus: we can recover a BadPixels instance:

> print(NewBadPixels(int(a1[0])))
NewBadPixels.NON_LIN_RESPONSE_REGION|CI_LINEAR_DEVIATION

And we can still use is between the enumerable things:

> print(NewBadPixels(int(a1[0])) is NewBadPixels.NON_LIN_RESPONSE_REGION | NewBadPixels.CI_LINEAR_DEVIATION)
True

Types of changes

Code improvement (non-breaking).

Checklist:

  • My code follows the code style of this project.

Reviewers

@danilevc @ahmedk @roscar

Edited by David Hammer

Merge request reports