There is no universally-adopted standard on how the bits in a mask are to be interpreted, and accordingly the LSST code tries to be flexible. The mapping name –> bitmask
is defined by a dictionary in the Mask class (e.g. EDGE -> 4 -> 2^4 == 0x10
).
When Mask is created, the dictionary is initialised with a number of useful values; a convenience function is provided to list them:
>>> import lsst.afw.image as afwImage
>>> Mask = afwImage.MaskU # Mask will make a "MaskU", a 16-bit Mask
>>> msk = Mask()
>>> msk.printMaskPlanes()
Plane 0 -> BAD
Plane 3 -> CR
Plane 5 -> DETECTED
Plane 6 -> DETECTED_NEGATIVE
Plane 4 -> EDGE
Plane 2 -> INTRP
>>>
>>> def showMask(msk, msg="???"):
... print
"%-15s" % msg,
" ".join(sorted([str(x)
for x in msk.getMaskPlaneDict().items()]))
(where we slipped in a convenient function showMask
to show masks in name-order)
You can add more mask planes:
>>> rhl = msk.addMaskPlane("RHL")
>>> showMask(msk, "msk")
msk ('BAD', 0) ('CR', 3) ('DETECTED', 5) ('DETECTED_NEGATIVE', 6) ('EDGE', 4) ('INTRP', 2) ('RHL', 7) ('
SAT', 1)
>>> msk |= Mask.getPlaneBitMask("RHL") # set the RHL bits
That last example was a little misleading; I could just as well have written Mask.addMaskPlane("RHL")
as addMaskPlane
is a class static function — it adds the named mask plane to all masks. I can remove a mask plane just as easily with Mask.removeMaskPlane("RHL")
:
>>> Mask.removeMaskPlane("RHL")
showMask(Mask(), "default Mask")
default Mask ('BAD', 0) ('CR', 3) ('DETECTED', 5) ('DETECTED_NEGATIVE', 6) ('EDGE', 4) ('INTRP', 2) ('SAT', 1)
(note that getMaskPlaneDict
is not static; we needed to create an object to be able to call it).
Unfortunately we have a problem; we set the beloved RHL
bit in msk
, but now it's gone. The resolution is that each mask remembers the version of the mask dictionary that was current when it was created, so:
>>> showMask(msk, "msk")
msk ('BAD', 0) ('CR', 3) ('DETECTED', 5) ('DETECTED_NEGATIVE', 6) ('EDGE', 4) ('INTRP', 2) ('RHL', 7) ('SAT', 1)
If you want to get rid of msk's
RHL
bit, use msk.removeAndClearMaskPlane("RHL")
or msk.removeAndClearMaskPlane("RHL", True)
if you want to drop RHL
from the default mask too. This does two things: It clears any RHL
bits that are set in the Mask (it isn't static, so it can do that), and it removes RHL
from the Mask's dictionary.
It's clear that you can make things inconsistent if you try:
>>> Mask.clearMaskPlaneDict()
>>> p0 = Mask.addMaskPlane("P0")
>>> p1 = Mask.addMaskPlane("P1")
>>> print "Planes:", p0, p1, Mask.addMaskPlane("P1") # a no-op -- re-adding a plane has no effect
Planes: 0 1 1
>>> msk = Mask()
>>> Mask.removeMaskPlane("P0")
>>> Mask.removeMaskPlane("P1")
>>> showMask(Mask(), "default Mask")
default Mask
>>> showMask(msk, "msk")
msk ('P0', 0) ('P1', 1)
>>> p1 = Mask.addMaskPlane("P1")
>>> p0 = Mask.addMaskPlane("P0")
>>>
>>> msk2 = Mask()
>>> showMask(msk2, "msk2")
msk2 ('P0', 1) ('P1', 0)
But you can't actually do much harm:
>>> msk |= msk2
File
"<stdin>",
line 1, in <module>
File
"/Users/rhl/LSST/afw/python/lsst/afw/image/imageLib.py",
line 5415, in __ior__
_imageLib.MaskU___ior__(*
args)
lsst.pex.exceptions.exceptionsLib.LsstCppException: 0: lsst::pex::exceptions::RuntimeErrorException thrown at src/
image/Mask.cc:896 in void lsst::afw::
image::Mask<unsigned short>::checkMaskDictionaries(const lsst::afw::
image::Mask<afwImage::
MaskPixel> &)
0: Message: Mask dictionary versions do not match; 3 v. 6
The version numbers aren't actually compared directly, rather a hash of the contents is computed, so:
>>> showMask(msk, "msk")
msk ('P0', 0) ('P1', 1)
>>> msk.removeAndClearMaskPlane("P0")
>>> msk.removeAndClearMaskPlane("P1")
>>> p0 = Mask.addMaskPlane("P0")
>>> p1 = Mask.addMaskPlane("P1")
>>> showMask(msk, "msk")
msk ('P0', 1) ('P1', 0)
>>> msk |= msk2
(We removed the errant planes from msk
, then re-added the ones that are already defined in the default dictionary)
Adding planes has no such difficulties, so they are added to all pre-existing dictionaries that don't have conflicts:
>>> msk = Mask()
>>> Mask.addMaskPlane("P2")
>>> showMask(msk, "msk")
msk ('P0', 1) ('P1', 0) ('P2', 2)
However, as expected,
>>> msk2 = Mask()
>>> msk.removeAndClearMaskPlane("P2")
>>> msk |= msk2
will raise an exception.
What did I mean by, "conflicts"? Here's an example:
>>> Mask.removeMaskPlane("P0")
>>> Mask.addMaskPlane("P3")
>>> Mask.addMaskPlane("P0")
>>> showMask(msk, "msk")
msk ('P0', 1) ('P1', 0) ('P2', 2)
>>> showMask(Mask(), "default Mask")
default Mask ('P0', 3) ('P1', 0) ('P2', 2) ('P3', 1)
Note that msk
hasn't acquired a P3
plane as plane 1
is already in use.