Exemplo n.º 1
0
def footprintToImage(fp, mi=None, mask=False):
    if fp.isHeavy():
        try:
            fp = afwDet.cast_HeavyFootprintF(fp) # not needed with pybind11
        except AttributeError:
            pass
        pass
    elif mi is None:
        print("Unable to make a HeavyFootprint as image is None", file=sys.stderr)
    else:
        fp = afwDet.makeHeavyFootprint(fp, mi)
    bb = fp.getBBox()
    if mask:
        im = afwImage.MaskedImageF(bb.getWidth(), bb.getHeight())
    else:
        im = afwImage.ImageF(bb.getWidth(), bb.getHeight())
    im.setXY0(bb.getMinX(), bb.getMinY())

    try:
        fp.insert(im)
    except AttributeError:              # we failed to make it heavy
        assert not mi
        pass
    
    if mask:
        im = im.getMask()
    return im
Exemplo n.º 2
0
def footprintToImage(fp, mi=None, mask=False):
    if fp.isHeavy():
        try:
            fp = afwDet.cast_HeavyFootprintF(fp)  # not needed with pybind11
        except AttributeError:
            pass
        pass
    elif mi is None:
        print >> sys.stderr, "Unable to make a HeavyFootprint as image is None"
    else:
        fp = afwDet.makeHeavyFootprint(fp, mi)
    bb = fp.getBBox()
    if mask:
        im = afwImage.MaskedImageF(bb.getWidth(), bb.getHeight())
    else:
        im = afwImage.ImageF(bb.getWidth(), bb.getHeight())
    im.setXY0(bb.getMinX(), bb.getMinY())

    try:
        fp.insert(im)
    except AttributeError:  # we failed to make it heavy
        assert not mi
        pass

    if mask:
        im = im.getMask()
    return im
def makePortionFigure(deblend, origMimg, origMimgB, pedestal=0.0):
    
    portions = []
    centers = []
    boxes = []
    for i, peak in enumerate(deblend.peaks):
        # make an image matching the size of the original
        portionedImg    = afwImage.ImageF(origMimg.getBBox())

        # get the heavy footprint for the flux aportioned to this peak
        heavyFoot       = afwDet.cast_HeavyFootprintF(peak.getFluxPortion())
        footBox         = heavyFoot.getBBox()
        pk = peak.peak
        centers.append((pk.getIx(), pk.getIy()))
        boxes.append(( (footBox.getMinX(), footBox.getMinY()), footBox.getWidth(), footBox.getHeight()))
        print i, peak, pk.getIx(), pk.getIy(), footBox, "skip:", peak.skip
        
        # make a sub-image for this peak, and put the aportioned flux into it
        portionedSubImg = afwImage.ImageF(portionedImg, footBox)
        portionedSubImg += pedestal
        heavyFoot.insert(portionedSubImg)
        
        portions.append(portionedImg)


    fig = figure.Figure(figsize=(8,10))
    canvas = FigCanvas(fig)

    g = int(numpy.ceil(numpy.sqrt(len(deblend.peaks))))
    gx, gy = g, g+1

    def makeAx(ax, im, title):
        im = im.getArray()
        a = ax.imshow(im, cmap='gray')
        cb = fig.colorbar(a)
        ax.set_title(title, size='small')
        ax.set_xlim((0, im.shape[0]))
        ax.set_ylim((0, im.shape[1]))
        for t in ax.get_xticklabels() + ax.get_yticklabels() + cb.ax.get_yticklabels():
            t.set_size('x-small')
        return ax
        
    # show the originals
    makeAx(fig.add_subplot(gy, gx, 1), origMimg.getImage(), "Orig")
    makeAx(fig.add_subplot(gy, gx, 2), origMimgB.getImage(), "Missing Src")
    
    # show each aportioned image
    i = gy
    for i_p in range(len(portions)):
        im = portions[i_p]
        ax = makeAx(fig.add_subplot(gy, gx, i), im, "")
        xy, w, h = boxes[i_p]
        ax.add_patch(Rectangle(xy, w, h, fill=False, edgecolor='#ff0000'))
        for x,y in centers:
            ax.plot(x, y, '+', color='#00ff00')
        i += 1

    return fig
Exemplo n.º 4
0
    def test1(self):
        im = afwImage.ImageF(100, 100)
        im += 42.
        fp = afwDet.Footprint(afwGeom.Point2I(50,50), 10.)
        #seed = 42
        #rand = afwMath.Random(afwMath.Random.MT19937, seed)
        #afwMath.randomGaussianImage(im, rand)
        mi = afwImage.MaskedImageF(im)
        # set a mask bit before grabbing the heavyfootprint
        mi.getMask().set(50, 50, 1)
        heavy = afwDet.makeHeavyFootprint(fp, mi)
        # reset it
        mi.getMask().set(50, 50, 0)

        schema = afwTable.SourceTable.makeMinimalSchema()
        table = afwTable.SourceTable.make(schema)
        table.preallocate(10)
        catalog = afwTable.SourceCatalog(table)
        catalog.addNew()
        # This used to segfault
        catalog[0].setFootprint(heavy)

        # However, we still have to up-cast
        fp = catalog[0].getFootprint()
        hfp = afwDet.cast_HeavyFootprintF(fp)
        # change one pixel...
        self.assertEqual(mi.getImage().get(50, 50), 42)
        self.assertEqual(mi.getMask().get(50, 50), 0)
        mi.getImage().set(50, 50, 100)
        mi.getMask().set(50, 50, 2)
        mi.getMask().set(51, 50, 2)
        self.assertEqual(mi.getImage().get(50, 50), 100)
        self.assertEqual(mi.getMask().get(50, 50), 2)
        self.assertEqual(mi.getMask().get(51, 50), 2)
        # reinsert the heavy footprint; it should reset the pixel value.
        # insert(MaskedImage)
        hfp.insert(mi)
        self.assertEqual(mi.getImage().get(50, 50), 42)
        self.assertEqual(mi.getMask().get(50, 50), 1)
        self.assertEqual(mi.getMask().get(51, 50), 0)

        # Also test insert(Image)
        im = mi.getImage()
        self.assertEqual(im.get(50, 50), 42)
        im.set(50, 50, 100)
        self.assertEqual(im.get(50, 50), 100)
        self.assertEqual(mi.getImage().get(50, 50), 100)
        # reinsert the heavy footprint; it should reset the pixel value.
        hfp.insert(im)
        self.assertEqual(im.get(50, 50), 42)
        self.assertEqual(mi.getImage().get(50, 50), 42)
        self.assertEqual(mi.getMask().get(50, 50), 1)
        self.assertEqual(mi.getMask().get(51, 50), 0)
Exemplo n.º 5
0
    def testMakeHeavy(self):
        """Test that we can make a FootprintSet heavy"""
        fs = afwDetect.FootprintSet(self.mi, afwDetect.Threshold(1))

        ctrl = afwDetect.HeavyFootprintCtrl(afwDetect.HeavyFootprintCtrl.NONE)
        fs.makeHeavy(self.mi, ctrl)

        if display:
            ds9.mtv(self.mi, frame=0, title="input")
            #ds9.mtv(omi, frame=1, title="output")

        omi = self.mi.Factory(self.mi.getDimensions())

        for foot in fs.getFootprints():
            self.assertNotEqual(afwDetect.cast_HeavyFootprint(foot, self.mi), None)
            afwDetect.cast_HeavyFootprint(foot, self.mi).insert(omi)

        for foot in fs.getFootprints():
            self.assertNotEqual(afwDetect.cast_HeavyFootprintF(foot), None)
            afwDetect.cast_HeavyFootprintF(foot).insert(omi)

        self.assertTrue(np.all(np.equal(self.mi.getImage().getArray(), omi.getImage().getArray())))
Exemplo n.º 6
0
def footprintToImage(fp, mi=None, mask=False):
    if fp.isHeavy():
        fp = afwDet.cast_HeavyFootprintF(fp)
    else:
        fp = afwDet.makeHeavyFootprint(fp, mi)
    bb = fp.getBBox()
    if mask:
        im = afwImage.MaskedImageF(bb.getWidth(), bb.getHeight())
    else:
        im = afwImage.ImageF(bb.getWidth(), bb.getHeight())
    im.setXY0(bb.getMinX(), bb.getMinY())
    fp.insert(im)
    if mask:
        im = im.getMask()
    return im
Exemplo n.º 7
0
    def testCast_HeavyFootprint(self):
        """Test that we can cast a Footprint to a HeavyFootprint"""

        hfoot = afwDetect.makeHeavyFootprint(self.foot, self.mi)

        ctrl = afwDetect.HeavyFootprintCtrl(afwDetect.HeavyFootprintCtrl.NONE)
        hfoot = afwDetect.makeHeavyFootprint(self.foot, self.mi, ctrl)
        #
        # This isn't quite a full test, as hfoot is already a HeavyFootprint,
        # the complete test is in testMakeHeavy
        #        
        self.assertNotEqual(afwDetect.cast_HeavyFootprint(hfoot, self.mi), None,
                            "Cast to the right sort of HeavyFootprint")
        self.assertNotEqual(afwDetect.cast_HeavyFootprintF(hfoot), None,
                            "Cast to the right sort of HeavyFootprint")

        self.assertEqual(afwDetect.cast_HeavyFootprint(self.foot, self.mi), None,
                         "Can't cast a Footprint to a HeavyFootprint")
        self.assertEqual(afwDetect.cast_HeavyFootprintI(hfoot), None,
                         "Cast to the wrong sort of HeavyFootprint")
Exemplo n.º 8
0
    def begin(self, exposure, sources, noiseImage=None, noiseMeanVar=None):
        # creates heavies, replaces all footprints with noise
        # We need the source table to be sorted by ID to do the parent lookups
        # (sources.find() below)
        if not sources.isSorted():
            sources.sort()
        mi = exposure.getMaskedImage()
        im = mi.getImage()
        mask = mi.getMask()

        # Add temporary Mask planes for THISDET and OTHERDET
        self.removeplanes = []
        bitmasks = []
        for maskname in ['THISDET', 'OTHERDET']:
            try:
                # does it already exist?
                plane = mask.getMaskPlane(maskname)
                self.log.logdebug('Mask plane "%s" already existed' % maskname)
            except:
                # if not, add it; we should delete it when done.
                plane = mask.addMaskPlane(maskname)
                self.removeplanes.append(maskname)
            mask.clearMaskPlane(plane)
            bitmask = mask.getPlaneBitMask(maskname)
            bitmasks.append(bitmask)
            self.log.logdebug('Mask plane "%s": plane %i, bitmask %i = 0x%x' %
                              (maskname, plane, bitmask, bitmask))
        self.thisbitmask, self.otherbitmask = bitmasks
        del bitmasks

        # Start by creating HeavyFootprints for each source.
        #
        # The "getParent()" checks are here because top-level
        # sources (ie, those with no parents) are not supposed to
        # have HeavyFootprints, but child sources (ie, those that
        # have been deblended) should have HeavyFootprints
        # already.
        self.heavies = []
        for source in sources:
            fp = source.getFootprint()
            hfp = afwDet.cast_HeavyFootprintF(fp)
            if source.getParent() and hfp is not None:
                # this source has been deblended; "fp" should
                # already be a HeavyFootprint (but may not be
                # if read from disk).
                # Swig downcasts it to Footprint, so we have to re-cast.
                self.heavies.append(hfp)
            else:
                # top-level source: copy pixels from the input
                # image.
                ### FIXME: the heavy footprint includes the mask
                ### and variance planes, which we shouldn't need
                ### (I don't think we ever want to modify them in
                ### the input image).  Copying them around is
                ### wasteful.
                heavy = afwDet.makeHeavyFootprint(fp, mi)
                self.heavies.append(heavy)

        # We now create a noise HeavyFootprint for each top-level Source.
        # We'll put the noisy footprints in a map from id -> HeavyFootprint:
        self.heavyNoise = {}
        noisegen = self.getNoiseGenerator(exposure, noiseImage, noiseMeanVar)
        self.log.logdebug('Using noise generator: %s' % (str(noisegen)))
        for source in sources:
            if source.getParent():
                continue
            fp = source.getFootprint()
            heavy = noisegen.getHeavyFootprint(fp)
            self.heavyNoise[source.getId()] = heavy
            # Also insert the noisy footprint into the image now.
            # Notice that we're just inserting it into "im", ie,
            # the Image, not the MaskedImage.
            heavy.insert(im)
            # Also set the OTHERDET bit
            afwDet.setMaskFromFootprint(mask, fp, self.otherbitmask)
Exemplo n.º 9
0
    def __init__(self,
                 config,
                 exposure,
                 footprints,
                 noiseImage=None,
                 exposureId=None,
                 log=None):
        """!
        Initialize the NoiseReplacer.

        @param[in]      config       instance of NoiseReplacerConfig
        @param[in,out]  exposure     Exposure to be noise replaced. (All sources replaced on return)
        @param[in]      footprints   dict of {id: (parent, footprint)};
        @param[in]      noiseImage   an afw.image.ImageF used as a predictable noise replacement source
                                     (for tests only)
        @param[in]      log          pex.logging.Log object to use for status messages; no status messages
                                     will be printed if None

        'footprints' is a dict of {id: (parent, footprint)}; when used in SFM, the ID will be the
        source ID, but in forced photometry, this will be the reference ID, as that's what we used to
        determine the deblend families.  This routine should create HeavyFootprints for any non-Heavy
        Footprints, and replace them in the dict.  It should then create a dict of HeavyFootprints
        containing noise, but only for parent objects, then replace all sources with noise.
        This should ignore any footprints that lay outside the bounding box of the exposure,
        and clip those that lie on the border.

        NOTE: as the code currently stands, the heavy footprint for a deblended object must be available
        from the input catalog.  If it is not, it cannot be reproduced here.  In that case, the
        topmost parent in the objects parent chain must be used.  The heavy footprint for that source
        is created in this class from the masked image.
        """
        noiseMeanVar = None
        self.noiseSource = config.noiseSource
        self.noiseOffset = config.noiseOffset
        self.noiseSeedMultiplier = config.noiseSeedMultiplier
        self.noiseGenMean = None
        self.noiseGenStd = None
        self.log = log

        # creates heavies, replaces all footprints with noise
        # We need the source table to be sorted by ID to do the parent lookups
        self.exposure = exposure
        self.footprints = footprints
        mi = exposure.getMaskedImage()
        im = mi.getImage()
        mask = mi.getMask()
        # Add temporary Mask planes for THISDET and OTHERDET
        self.removeplanes = []
        bitmasks = []
        for maskname in ['THISDET', 'OTHERDET']:
            try:
                # does it already exist?
                plane = mask.getMaskPlane(maskname)
                if self.log:
                    self.log.logdebug('Mask plane "%s" already existed' %
                                      maskname)
            except:
                # if not, add it; we should delete it when done.
                plane = mask.addMaskPlane(maskname)
                self.removeplanes.append(maskname)
            mask.clearMaskPlane(plane)
            bitmask = mask.getPlaneBitMask(maskname)
            bitmasks.append(bitmask)
            if self.log:
                self.log.logdebug(
                    'Mask plane "%s": plane %i, bitmask %i = 0x%x' %
                    (maskname, plane, bitmask, bitmask))
        self.thisbitmask, self.otherbitmask = bitmasks
        del bitmasks
        self.heavies = {}
        # Start by creating HeavyFootprints for each source which has no parent
        # and just use them for children which do not already have heavy footprints.
        # If a heavy footprint is available for a child, we will use it. Otherwise,
        # we use the first parent in the parent chain which has a heavy footprint,
        # which with the one level deblender will alway be the topmost parent
        # NOTE: heavy footprints get destroyed by the transform process in forcedPhotImage.py,
        # so they are never available for forced measurements.

        # Create in the dict heavies = {id:heavyfootprint}
        for id in footprints.keys():
            fp = footprints[id]
            if fp[1].isHeavy():
                self.heavies[id] = afwDet.cast_HeavyFootprintF(fp[1])
            elif fp[0] == 0:
                self.heavies[id] = afwDet.makeHeavyFootprint(fp[1], mi)

        ### FIXME: the heavy footprint includes the mask
        ### and variance planes, which we shouldn't need
        ### (I don't think we ever want to modify them in
        ### the input image).  Copying them around is
        ### wasteful.

        # We now create a noise HeavyFootprint for each source with has a heavy footprint.
        # We'll put the noise footprints in a dict heavyNoise = {id:heavyNoiseFootprint}
        self.heavyNoise = {}
        noisegen = self.getNoiseGenerator(exposure,
                                          noiseImage,
                                          noiseMeanVar,
                                          exposureId=exposureId)
        #  The noiseGenMean and Std are used by the unit tests
        self.noiseGenMean = noisegen.mean
        self.noiseGenStd = noisegen.std
        if self.log:
            self.log.logdebug('Using noise generator: %s' % (str(noisegen)))
        for id in self.heavies.keys():
            fp = footprints[id][1]
            noiseFp = noisegen.getHeavyFootprint(fp)
            self.heavyNoise[id] = noiseFp
            # Also insert the noisy footprint into the image now.
            # Notice that we're just inserting it into "im", ie,
            # the Image, not the MaskedImage.
            noiseFp.insert(im)
            # Also set the OTHERDET bit
            afwDet.setMaskFromFootprint(mask, fp, self.otherbitmask)
Exemplo n.º 10
0
    def __init__(self, config, exposure, footprints, noiseImage=None, exposureId=None, log=None):
        """!
        Initialize the NoiseReplacer.

        @param[in]      config       instance of NoiseReplacerConfig
        @param[in,out]  exposure     Exposure to be noise replaced. (All sources replaced on return)
        @param[in]      footprints   dict of {id: (parent, footprint)};
        @param[in]      noiseImage   an afw.image.ImageF used as a predictable noise replacement source
                                     (for tests only)
        @param[in]      log          pex.logging.Log object to use for status messages; no status messages
                                     will be printed if None

        'footprints' is a dict of {id: (parent, footprint)}; when used in SFM, the ID will be the
        source ID, but in forced photometry, this will be the reference ID, as that's what we used to
        determine the deblend families.  This routine should create HeavyFootprints for any non-Heavy
        Footprints, and replace them in the dict.  It should then create a dict of HeavyFootprints
        containing noise, but only for parent objects, then replace all sources with noise.
        This should ignore any footprints that lay outside the bounding box of the exposure,
        and clip those that lie on the border.

        NOTE: as the code currently stands, the heavy footprint for a deblended object must be available
        from the input catalog.  If it is not, it cannot be reproduced here.  In that case, the
        topmost parent in the objects parent chain must be used.  The heavy footprint for that source
        is created in this class from the masked image.
        """
        noiseMeanVar=None
        self.noiseSource = config.noiseSource
        self.noiseOffset = config.noiseOffset
        self.noiseSeedMultiplier = config.noiseSeedMultiplier
        self.noiseGenMean = None
        self.noiseGenStd = None
        self.log = log

        # creates heavies, replaces all footprints with noise
        # We need the source table to be sorted by ID to do the parent lookups
        self.exposure = exposure
        self.footprints = footprints
        mi = exposure.getMaskedImage()
        im = mi.getImage()
        mask = mi.getMask()
        # Add temporary Mask planes for THISDET and OTHERDET
        self.removeplanes = []
        bitmasks = []
        for maskname in ['THISDET', 'OTHERDET']:
            try:
                # does it already exist?
                plane = mask.getMaskPlane(maskname)
                if self.log: self.log.logdebug('Mask plane "%s" already existed' % maskname)
            except:
                # if not, add it; we should delete it when done.
                plane = mask.addMaskPlane(maskname)
                self.removeplanes.append(maskname)
            mask.clearMaskPlane(plane)
            bitmask = mask.getPlaneBitMask(maskname)
            bitmasks.append(bitmask)
            if self.log: self.log.logdebug('Mask plane "%s": plane %i, bitmask %i = 0x%x'
                % (maskname, plane, bitmask, bitmask))
        self.thisbitmask,self.otherbitmask = bitmasks
        del bitmasks
        self.heavies = {}
        # Start by creating HeavyFootprints for each source which has no parent
        # and just use them for children which do not already have heavy footprints.
        # If a heavy footprint is available for a child, we will use it. Otherwise,
        # we use the first parent in the parent chain which has a heavy footprint,
        # which with the one level deblender will alway be the topmost parent
        # NOTE: heavy footprints get destroyed by the transform process in forcedPhotImage.py,
        # so they are never available for forced measurements.

        # Create in the dict heavies = {id:heavyfootprint}
        for id in footprints.keys():
            fp = footprints[id]
            if fp[1].isHeavy():
                self.heavies[id] = afwDet.cast_HeavyFootprintF(fp[1])
            elif fp[0] == 0:
                self.heavies[id] = afwDet.makeHeavyFootprint(fp[1], mi)

        ### FIXME: the heavy footprint includes the mask
        ### and variance planes, which we shouldn't need
        ### (I don't think we ever want to modify them in
        ### the input image).  Copying them around is
        ### wasteful.

        # We now create a noise HeavyFootprint for each source with has a heavy footprint.
        # We'll put the noise footprints in a dict heavyNoise = {id:heavyNoiseFootprint}
        self.heavyNoise = {}
        noisegen = self.getNoiseGenerator(exposure, noiseImage, noiseMeanVar, exposureId=exposureId)
        #  The noiseGenMean and Std are used by the unit tests
        self.noiseGenMean = noisegen.mean
        self.noiseGenStd = noisegen.std
        if self.log: self.log.logdebug('Using noise generator: %s' % (str(noisegen)))
        for id in self.heavies.keys():
            fp = footprints[id][1]
            noiseFp = noisegen.getHeavyFootprint(fp)
            self.heavyNoise[id] = noiseFp
            # Also insert the noisy footprint into the image now.
            # Notice that we're just inserting it into "im", ie,
            # the Image, not the MaskedImage.
            noiseFp.insert(im)
            # Also set the OTHERDET bit
            afwDet.setMaskFromFootprint(mask, fp, self.otherbitmask)
Exemplo n.º 11
0
    def begin(self, exposure, sources, noiseImage=None, noiseMeanVar=None):
        # creates heavies, replaces all footprints with noise
        # We need the source table to be sorted by ID to do the parent lookups
        # (sources.find() below)
        if not sources.isSorted():
            sources.sort()
        mi = exposure.getMaskedImage()
        im = mi.getImage()
        mask = mi.getMask()

        # Add temporary Mask planes for THISDET and OTHERDET
        self.removeplanes = []
        bitmasks = []
        for maskname in ['THISDET', 'OTHERDET']:
            try:
                # does it already exist?
                plane = mask.getMaskPlane(maskname)
                self.log.logdebug('Mask plane "%s" already existed' % maskname)
            except:
                # if not, add it; we should delete it when done.
                plane = mask.addMaskPlane(maskname)
                self.removeplanes.append(maskname)
            mask.clearMaskPlane(plane)
            bitmask = mask.getPlaneBitMask(maskname)
            bitmasks.append(bitmask)
            self.log.logdebug('Mask plane "%s": plane %i, bitmask %i = 0x%x' %
                              (maskname, plane, bitmask, bitmask))
        self.thisbitmask,self.otherbitmask = bitmasks
        del bitmasks

        # Start by creating HeavyFootprints for each source.
        #
        # The "getParent()" checks are here because top-level
        # sources (ie, those with no parents) are not supposed to
        # have HeavyFootprints, but child sources (ie, those that
        # have been deblended) should have HeavyFootprints
        # already.
        self.heavies = []
        for source in sources:
            fp = source.getFootprint()
            hfp = afwDet.cast_HeavyFootprintF(fp)
            if source.getParent() and hfp is not None:
                # this source has been deblended; "fp" should
                # already be a HeavyFootprint (but may not be
                # if read from disk).
                # Swig downcasts it to Footprint, so we have to re-cast.
                self.heavies.append(hfp)
            else:
                # top-level source: copy pixels from the input
                # image.
                ### FIXME: the heavy footprint includes the mask
                ### and variance planes, which we shouldn't need
                ### (I don't think we ever want to modify them in
                ### the input image).  Copying them around is
                ### wasteful.
                heavy = afwDet.makeHeavyFootprint(fp, mi)
                self.heavies.append(heavy)

        # We now create a noise HeavyFootprint for each top-level Source.
        # We'll put the noisy footprints in a map from id -> HeavyFootprint:
        self.heavyNoise = {}
        noisegen = self.getNoiseGenerator(exposure, noiseImage, noiseMeanVar)
        self.log.logdebug('Using noise generator: %s' % (str(noisegen)))
        for source in sources:
            if source.getParent():
                continue
            fp = source.getFootprint()
            heavy = noisegen.getHeavyFootprint(fp)
            self.heavyNoise[source.getId()] = heavy
            # Also insert the noisy footprint into the image now.
            # Notice that we're just inserting it into "im", ie,
            # the Image, not the MaskedImage.
            heavy.insert(im)
            # Also set the OTHERDET bit
            afwDet.setMaskFromFootprint(mask, fp, self.otherbitmask)