Example #1
0
    def get_valid_pixel_mask(self, mask_filename, res=20):
        """
        :param res:
        :param mask_filename:
        :return:
        """
        resolution = self.resolutions.get(res)
        mask_band = self.classif_band.get(res)

        log.info('Read validity and nodata masks')
        log.debug(f'Read mask: {mask_band}')

        # No data mask
        edge = S2L_ImageFile(
            os.path.join(self.product_path, self.edge_mask[resolution]))
        edge_arr = edge.array
        defective = S2L_ImageFile(
            os.path.join(self.product_path, self.nodata_mask[mask_band]))
        defective_arr = defective.array

        nodata = np.zeros(edge_arr.shape, np.uint8)
        nodata[edge_arr == 1] = 1
        nodata[defective_arr == 1] = 1

        del edge_arr
        del defective_arr

        nodata_mask_filename = os.path.join(os.path.dirname(mask_filename),
                                            'nodata_pixel_mask.tif')
        mask = edge.duplicate(nodata_mask_filename, array=nodata)
        mask.write(creation_options=['COMPRESS=LZW'])
        self.nodata_mask_filename = mask_filename

        # Validity mask
        cloud = S2L_ImageFile(
            os.path.join(self.product_path, self.cloud_mask[resolution]))
        cloud_arr = cloud.array
        saturation = S2L_ImageFile(
            os.path.join(self.product_path, self.saturation_mask[mask_band]))
        saturation_arr = saturation.array

        valid_px_mask = np.ones(cloud_arr.shape, np.uint8)
        valid_px_mask[cloud_arr == 1] = 0
        valid_px_mask[cloud_arr == 2] = 0
        valid_px_mask[cloud_arr == 4] = 0
        valid_px_mask[cloud_arr == 8] = 0
        valid_px_mask[saturation_arr == 1] = 0
        valid_px_mask[nodata == 1] = 0

        mask = cloud.duplicate(mask_filename, array=valid_px_mask)
        mask.write(creation_options=['COMPRESS=LZW'])
        self.mask_filename = mask_filename

        return True
Example #2
0
    def _fusion(self, L8_HLS, S2_HLS_img, S2_HLS_plus_img, mask_filename=None):

        log.info('fusion')
        # read array
        L8_HLS_img = L8_HLS.array

        # resize low res to high res
        L8_HLS_plus_BILINEAR_img = skit_resize(
            L8_HLS_img.clip(min=-1.0, max=1.0),
            S2_HLS_plus_img.shape).astype(np.float32)
        S2_HLS_plus_LOWPASS_img = S2_HLS_img

        # high pass of high res
        S2_HLS_plus_HIGHPASS_img = S2_HLS_plus_img - S2_HLS_plus_LOWPASS_img

        # fusion
        L8_HLS_plus_FUSION_img = L8_HLS_plus_BILINEAR_img + S2_HLS_plus_HIGHPASS_img

        # masking
        if mask_filename:
            mask_file = S2L_ImageFile(mask_filename)
            msk = mask_file.array
            if msk.shape != L8_HLS_plus_FUSION_img.shape:
                msk = skit_resize(msk.clip(min=-1.0, max=1.0),
                                  L8_HLS_plus_FUSION_img.shape,
                                  order=0,
                                  preserve_range=True).astype(np.uint8)
            L8_HLS_plus_FUSION_img[msk == 0] = 0

        return L8_HLS_plus_FUSION_img
Example #3
0
 def get_band_file(self, band):
     # check if not already known
     if band not in self.filenames:
         filepath = self.get_band_filepath(band)
         if filepath is None:
             return None
         # save in class internal dictionary
         self.filenames[band] = filepath
     return S2L_ImageFile(self.filenames[band])
Example #4
0
    def preprocess(self, product):
        self.get_new_product(product)
        if self.new_product is None:
            return
        # Stitch validity mask and angles
        product_validity_masks = []
        product_nodata_masks = []
        product_angles = []
        for _product in [product, self.new_product.reader(self.new_product.path)]:
            is_mask_valid = True
            # Validity mask
            if _product.mtl.mask_filename is None:
                is_mask_valid = _product.mtl.get_valid_pixel_mask(
                    os.path.join(config.get("wd"), _product.name, 'valid_pixel_mask.tif'))
            if is_mask_valid:
                product_validity_masks.append(self.reframe(S2L_ImageFile(_product.mtl.mask_filename), _product))
                product_nodata_masks.append(self.reframe(S2L_ImageFile(_product.mtl.nodata_mask_filename), _product))
            # Angles
            if _product.mtl.angles_file is None:
                _product.mtl.get_angle_images(os.path.join(config.get("wd"), _product.name, 'tie_points.tif'))
            filepath_out = os.path.join(config.get('wd'), _product.name, 'tie_points_PREREFRAMED.TIF')
            mgrs_framing.reframeMulti(_product.mtl.angles_file, self.tile, filepath_out=filepath_out, order=0)
            product_angles.append(filepath_out)

        if None not in product_validity_masks:
            stitched_mask = self.stitch(product, product_validity_masks[0], product_validity_masks[1])
            stitched_mask.write(creation_options=['COMPRESS=LZW'])
            product.mtl.mask_filename = stitched_mask.filepath

        if None not in product_nodata_masks:
            stitched_mask = self.stitch(product, product_nodata_masks[0], product_nodata_masks[1])
            stitched_mask.write(creation_options=['COMPRESS=LZW'])
            product.mtl.nodata_mask_filename = stitched_mask.filepath

        stitched_angles = self.stitch_multi(product, product_angles[0], product_angles[1])
        product.mtl.angles_file = stitched_angles

        # Stitch reference band (needed by geometry module)
        band = config.get('reference_band', 'B04')
        if config.getboolean('doMatchingCorrection') and config.get('refImage'):
            image = product.get_band_file(band)
            self.process(product, image, band)
Example #5
0
    def preprocess(self, product):

        # reinit dx/dy
        config.set('dx', 0)
        config.set('dy', 0)

        if product.sensor != 'S2':

            # Reframe angles and masks
            filepath_out = os.path.join(config.get('wd'), product.name, 'tie_points_REFRAMED.TIF')
            mgrs_framing.reframeMulti(product.mtl.angles_file, product.mtl.mgrs, filepath_out, config.getfloat('dx'),
                                      config.getfloat('dy'), order=0)
            product.mtl.angles_file = filepath_out

            # Reframe mask
            if product.mtl.mask_filename:
                filepath_out = os.path.join(config.get('wd'), product.name, 'valid_pixel_mask_REFRAMED.TIF')
                image = S2L_ImageFile(product.mtl.mask_filename)
                imageout = mgrs_framing.reframe(image, product.mtl.mgrs, filepath_out, config.getfloat('dx'),
                                                config.getfloat('dy'), order=0)
                imageout.write(creation_options=['COMPRESS=LZW'])
                product.mtl.mask_filename = filepath_out

            # Reframe nodata mask
            if product.mtl.nodata_mask_filename:
                filepath_out = os.path.join(config.get('wd'), product.name, 'nodata_pixel_mask_REFRAMED.TIF')
                image = S2L_ImageFile(product.mtl.nodata_mask_filename)
                imageout = mgrs_framing.reframe(image, product.mtl.mgrs, filepath_out, config.getfloat('dx'),
                                                config.getfloat('dy'), order=0)
                imageout.write(creation_options=['COMPRESS=LZW'])
                product.mtl.nodata_mask_filename = filepath_out

        # Matching for dx/dy correction?
        band = config.get('reference_band', 'B04')
        if config.getboolean('doMatchingCorrection') and config.get('refImage'):
            config.set('freeze_dx_dy', False)
            image = product.get_band_file(band)
            self.process(product, image, band)
            # goal is to feed dx, dy in config
            config.set('freeze_dx_dy', True)
            metadata.qi.update({'COREGISTRATION_BEFORE_CORRECTION': self._tmp_stats.get('MEAN'.format(band))})
Example #6
0
    def process(self, pd, image, band):
        """
        Write final product in the archive directory
        'archive_dir' is defined in config.ini file
        Naming convention from Design Document
        :param pd: instance of S2L_Product class
        :param image: input instance of S2L_ImageFile class
        :param band: band being processed
        :return: outpu  t instance of instance of S2L_ImageFile class
        """

        # TODO : add production date?
        # TODO : change L8 band numbers to S2 numbers convention?

        # /data/HLS_DATA/Archive/Site_Name/TILE_ID/S2L_DATEACQ_DATEPROD_SENSOR/S2L_DATEACQ_DATEPROD_SENSOR
        res = image.xRes
        outdir, tilecode = self.base_path(pd)
        outfile = "_".join([outdir, band, '{}m'.format(int(res))]) + '.TIF'
        tsdir = os.path.join(config.get('archive_dir'), tilecode)  # ts = temporal series
        newpath = os.path.join(tsdir, outdir, outfile)

        log.debug('New: ' + newpath)
        image.write(creation_options=['COMPRESS=LZW'], filepath=newpath)

        # declare output internally
        self.images[band] = image.filepath
        # declare output in config file
        config.set('imageout_dir', image.dirpath)
        config.set('imageout_' + band, image.filename)

        if config.getboolean('hlsplus'):
            res = 30
            outfile_30m = "_".join([outdir, band, '{}m'.format(int(res))]) + '.TIF'
            newpath_30m = os.path.join(tsdir, outdir, outfile_30m)
            if pd.sensor == 'S2':
                # create 30m band as well
                # resampling
                log.info('Resampling to 30m: Start...')
                image_30m = mgrs_framing.resample(S2L_ImageFile(newpath), res, newpath_30m)
                image_30m.write(creation_options=['COMPRESS=LZW'], DCmode=True)  # digital count
                log.info('Resampling to 30m: End')

            if pd.sensor == 'L8' and band in pd.image30m:
                # copy 30m band as well
                # write
                pd.image30m[band].write(creation_options=['COMPRESS=LZW'], filepath=newpath_30m)
                del pd.image30m[band]

        return image
Example #7
0
    def process(self, product, image, band):
        wd = os.path.join(config.get('wd'), product.name)
        self._output_file = self.output_file(product, band)
        self._tmp_stats = {}

        log.info('Start')

        # MGRS reframing for Landsat8
        if product.sensor == 'L8':
            log.debug('{} {}'.format(config.getfloat('dx'), config.getfloat('dy')))
            image = self._reframe(product, image, config.getfloat('dx'), config.getfloat('dy'))

        # Resampling to 30m for S2 (HLS)
        elif product.sensor == 'S2':
            if not config.getboolean('hlsplus'):
                image = self._resample(image)
            else:
                # refine geometry
                # if config.getfloat('dx') > 0.3 or config.getfloat('dy') > 0.3:
                log.debug("{} {}".format(config.getfloat('dx'), config.getfloat('dy')))
                image = self._reframe(product, image, config.getfloat('dx'), config.getfloat('dy'))

        # matching for having QA stats
        if config.get('refImage'):

            # try to adapt resolution, changing end of reference filename
            refImage_path = config.get('refImage')
            if not os.path.exists(refImage_path):
                return image

            # open image ref
            imageref = S2L_ImageFile(refImage_path)

            # if refImage resolution does not fit
            if imageref.xRes != image.xRes:
                # new refImage filepath
                refImage_noext = os.path.splitext(refImage_path)[0]
                if refImage_noext.endswith(f"_{int(imageref.xRes)}m"):
                    refImage_noext = refImage_noext[:-len(f"_{int(imageref.xRes)}m")]
                refImage_path = refImage_noext + f"_{int(image.xRes)}m.TIF"

                # compute (resample), or load if exists
                if not os.path.exists(refImage_path):
                    log.info("Resampling of the reference image")
                    # compute
                    imageref = mgrs_framing.resample(imageref, image.xRes, refImage_path)
                    # write for reuse
                    imageref.write(DCmode=True, creation_options=['COMPRESS=LZW'])
                else:
                    # or load if exists
                    log.info("Change reference image to:" + refImage_path)
                    imageref = S2L_ImageFile(refImage_path)

            # open mask
            mask = S2L_ImageFile(product.mtl.mask_filename)
            if config.getboolean('freeze_dx_dy'):
                # do Geometry Assessment only if required
                assess_geometry_bands = config.get('doAssessGeometry', default='').split(',')
                if product.sensor != 'S2':
                    assess_geometry_bands = [product.reverse_bands_mapping.get(band) for band in assess_geometry_bands]
                if assess_geometry_bands and band in assess_geometry_bands:
                    log.info("Geometry assessment for band %s" % band)
                    # Coarse resolution of correlation grid (only for stats)
                    self._matching(imageref, image, wd, mask)

            else:
                # Fine resolution of correlation grid (for accurate dx dy computation)
                dx, dy = self._matching(imageref, image, wd, mask)
                # save values for correction on bands
                config.set('dx', dx)
                config.set('dy', dy)
                log.info("Geometrical Offsets (DX/DY): {}m {}m".format(config.getfloat('dx'), config.getfloat('dy')))

            # Append bands name to keys
            for key, item in self._tmp_stats.items():
                if config.get('reference_band') != band:
                    self._tmp_stats[key+'_{}'.format(band)] = self._tmp_stats.pop(key)
            metadata.qi.update(self._tmp_stats)

        log.info('End')
        return image
Example #8
0
    def getMaskFile(self):

        # return mask as S2L_ImageFile object
        filepath = self.getMask()
        return S2L_ImageFile(filepath)
Example #9
0
    def get_band_file(self, band, plus=False):
        # get band
        filepath = self.get_band_filepath(band, plus)

        if filepath is not None:
            return S2L_ImageFile(filepath)
Example #10
0
    def preprocess(self, product):
        # No geometric correction for refined products
        if product.mtl.is_refined:
            if S2L_config.config.getboolean('force_geometric_correction'):
                log.info(
                    "Product is refined but geometric correction is forced.")
            else:
                log.info(
                    "Product is refined: no additional geometric correction.")
                return

        # reinit dx/dy
        S2L_config.config.set('dx', 0)
        S2L_config.config.set('dy', 0)

        if product.sensor != 'S2':

            # Reframe angles and masks
            filepath_out = os.path.join(S2L_config.config.get('wd'),
                                        product.name,
                                        'tie_points_REFRAMED.TIF')
            mgrs_framing.reframeMulti(product.mtl.angles_file,
                                      product.mtl.mgrs,
                                      filepath_out,
                                      S2L_config.config.getfloat('dx'),
                                      S2L_config.config.getfloat('dy'),
                                      order=0)
            product.mtl.angles_file = filepath_out

            # Reframe mask
            if product.mtl.mask_filename:
                filepath_out = os.path.join(S2L_config.config.get('wd'),
                                            product.name,
                                            'valid_pixel_mask_REFRAMED.TIF')
                image = S2L_ImageFile(product.mtl.mask_filename)
                imageout = mgrs_framing.reframe(
                    image,
                    product.mtl.mgrs,
                    filepath_out,
                    S2L_config.config.getfloat('dx'),
                    S2L_config.config.getfloat('dy'),
                    order=0)
                imageout.write(creation_options=['COMPRESS=LZW'])
                product.mtl.mask_filename = filepath_out

            # Reframe nodata mask
            if product.mtl.nodata_mask_filename:
                filepath_out = os.path.join(S2L_config.config.get('wd'),
                                            product.name,
                                            'nodata_pixel_mask_REFRAMED.TIF')
                image = S2L_ImageFile(product.mtl.nodata_mask_filename)
                imageout = mgrs_framing.reframe(
                    image,
                    product.mtl.mgrs,
                    filepath_out,
                    S2L_config.config.getfloat('dx'),
                    S2L_config.config.getfloat('dy'),
                    order=0)
                imageout.write(creation_options=['COMPRESS=LZW'])
                product.mtl.nodata_mask_filename = filepath_out

        # Matching for dx/dy correction?
        band = S2L_config.config.get('reference_band', 'B04')
        if S2L_config.config.getboolean(
                'doMatchingCorrection') and S2L_config.config.get('refImage'):
            S2L_config.config.set('freeze_dx_dy', False)
            image = product.get_band_file(band)
            self.process(product, image, band)
            # goal is to feed dx, dy in config
            S2L_config.config.set('freeze_dx_dy', True)
            metadata.qi.update({
                'COREGISTRATION_BEFORE_CORRECTION':
                self._tmp_stats.get('MEAN')
            })
Example #11
0
    def postprocess(self, pd):
        """
        Copy auxiliary files in the final output like mask, angle files
        Input product metadata file is also copied.
        :param pd: instance of S2L_Product class
        """

        # output directory
        product_name, granule_compact_name, tilecode, datatake_sensing_start = self.base_path_S2L(
            pd)

        tsdir = os.path.join(S2L_config.config.get('archive_dir'),
                             tilecode)  # ts = temporal series
        outdir = product_name
        product_path = os.path.join(tsdir, outdir)
        qi_dir = os.path.join(product_path, 'GRANULE', granule_compact_name,
                              'QI_DATA')

        # copy angles file
        outfile = "_".join([metadata.mtd.get('band_rootName_H'), 'ANG'
                            ]) + '.TIF'
        metadata.mtd['ang_filename'] = outfile
        shutil.copyfile(pd.mtl.angles_file, os.path.join(qi_dir, outfile))

        # copy mask files
        if "S2" in pd.sensor and pd.mtl.tile_metadata is not None:
            tree_in = ElementTree.parse(
                pd.mtl.tile_metadata)  # Tree of the input mtd (S2 MTD.xml)
            root_in = tree_in.getroot()
            mask_elements = find_element_by_path(
                root_in,
                './Quality_Indicators_Info/Pixel_Level_QI/MASK_FILENAME')
            for element in mask_elements:
                mask_file = os.path.join(pd.path, element.text)
                if os.path.exists(mask_file):
                    shutil.copyfile(
                        mask_file,
                        os.path.join(qi_dir, os.path.basename(mask_file)))
                    metadata.mtd.get('masks_H').append({
                        "tag": "MASK_FILENAME",
                        "attribs": element.attrib,
                        "text": element.text
                    })

        # copy valid pixel mask
        outfile = "_".join(
            [metadata.mtd.get('band_rootName_H'), pd.sensor, 'MSK']) + '.TIF'

        fpath = os.path.join(qi_dir, outfile)
        metadata.mtd.get('masks_H').append({
            "tag":
            "MASK_FILENAME",
            "attribs": {
                "type": "MSK_VALPIX"
            },
            "text":
            os.path.relpath(fpath, product_path)
        })

        if S2L_config.config.get('output_format') == 'COG':
            img_object = S2L_ImageFile(pd.mtl.mask_filename, mode='r')
            img_object.write(filepath=fpath, output_format='COG', band='MASK')
        else:
            shutil.copyfile(pd.mtl.mask_filename, fpath)

        # QI directory
        qipath = os.path.join(tsdir, 'QI')
        if not os.path.exists(qipath):
            os.makedirs(qipath)

        # save config file in QI
        cfgname = "_".join([outdir, 'INFO']) + '.cfg'
        cfgpath = os.path.join(tsdir, 'QI', cfgname)
        S2L_config.config.savetofile(
            os.path.join(S2L_config.config.get('wd'), pd.name, cfgpath))

        # save correl file in QI
        if os.path.exists(
                os.path.join(S2L_config.config.get('wd'), pd.name,
                             'correl_res.txt')):
            corrname = "_".join([outdir, 'CORREL']) + '.csv'
            corrpath = os.path.join(tsdir, 'QI', corrname)
            shutil.copy(
                os.path.join(S2L_config.config.get('wd'), pd.name,
                             'correl_res.txt'), corrpath)

        if len(self.images.keys()) > 1:
            # true color QL
            band_list = ["B04", "B03", "B02"]
            qlname = "_".join(
                [metadata.mtd.get('band_rootName_H'), 'QL', 'B432']) + '.jpg'
            qlpath = os.path.join(qi_dir, qlname)
            quicklook(pd, self.images, band_list, qlpath,
                      S2L_config.config.get("quicklook_jpeg_quality", 95))
            metadata.mtd.get('quicklooks_H').append(qlpath)

            # false color QL
            band_list = ["B12", "B11", "B8A"]
            qlname = "_".join([
                metadata.mtd.get('band_rootName_H'), 'QL', 'B12118A'
            ]) + '.jpg'
            qlpath = os.path.join(qi_dir, qlname)
            quicklook(pd, self.images, band_list, qlpath,
                      S2L_config.config.get("quicklook_jpeg_quality", 95))
            metadata.mtd.get('quicklooks_H').append(qlpath)
        else:
            # grayscale QL
            band_list = list(self.images.keys())
            qlname = "_".join([
                metadata.mtd.get('band_rootName_H'), 'QL', band_list[0]
            ]) + '.jpg'
            qlpath = os.path.join(qi_dir, qlname)
            quicklook(pd, self.images, band_list, qlpath,
                      S2L_config.config.get("quicklook_jpeg_quality", 95))
            metadata.mtd.get('quicklooks_H').append(qlpath)

        # PVI
        band_list = ["B04", "B03", "B02"]
        pvi_filename = "_".join([metadata.mtd.get('band_rootName_H'), 'PVI'
                                 ]) + '.TIF'
        qlpath = os.path.join(qi_dir, pvi_filename)
        quicklook(pd,
                  self.images,
                  band_list,
                  qlpath,
                  S2L_config.config.get("quicklook_jpeg_quality", 95),
                  xRes=320,
                  yRes=320,
                  creationOptions=['COMPRESS=LZW'],
                  format='GTIFF')
        metadata.mtd.get('quicklooks_H').append(qlpath)

        # Clear images as packager is the last process
        self.images.clear()

        # Write QI report as XML
        bb_QI_path = metadata.hardcoded_values.get('bb_QIH_path')
        out_QI_path = os.path.join(qi_dir, 'L2H_QI_Report.xml')
        in_QI_path = glob.glob(
            os.path.join(pd.path, 'GRANULE', '*', 'QI_DATA',
                         'L2A_QI_Report.xml'))
        log.info(
            'QI report for input product found : {} (searched at {})'.format(
                len(in_QI_path) != 0,
                os.path.join(pd.path, 'GRANULE', '*', 'QI_DATA',
                             'L2A_QI_Report.xml')))

        in_QI_path = in_QI_path[0] if len(in_QI_path) != 0 else None

        Qi_Writer = QiWriter(bb_QI_path,
                             outfile=out_QI_path,
                             init_QI_path=in_QI_path,
                             H_F='H')
        Qi_Writer.manual_replaces(pd)
        Qi_Writer.write(pretty_print=True, json_print=False)
        # TODO UNCOMMENT BELOW FOR XSD CHECK
        product_QI_xsd = metadata.hardcoded_values.get('product_QIH_xsd')
        log.info('QI Report is valid : {}'.format(
            Qi_Writer.validate_schema(product_QI_xsd, out_QI_path)))

        # Write tile MTD
        bb_S2_tile = metadata.hardcoded_values.get('bb_S2H_tile')
        bb_L8_tile = metadata.hardcoded_values.get('bb_L8H_tile')
        tile_mtd_path = 'MTD_TL_L2H.xml'
        tile_MTD_outpath = os.path.join(product_path, 'GRANULE',
                                        granule_compact_name, tile_mtd_path)

        mtd_tl_writer = MTD_tile_writer_S2(bb_S2_tile, pd.mtl.tile_metadata, H_F='H') if pd.sensor == 'S2' \
            else MTD_tile_writer_LS8(bb_L8_tile, H_F='H')
        mtd_tl_writer.manual_replaces(pd)
        mtd_tl_writer.write(tile_MTD_outpath, pretty_print=True)
        # TODO UNCOMMENT BELOW FOR XSD CHECK
        # product_tl_xsd = metadata.hardcoded_values.get('product_tl_xsd')
        # log.info('Tile MTD is valid : {}'.format(mtd_tl_writer.validate_schema(product_tl_xsd, tile_MTD_outpath)))

        # Write product MTD
        bb_S2_product = metadata.hardcoded_values.get('bb_S2H_product')
        bb_L8_product = metadata.hardcoded_values.get('bb_L8H_product')
        product_mtd_path = 'MTD_{}L2H.xml'.format(
            pd.mtl.sensor[0:3])  # MSI / OLI/ OLI_TIRS
        product_MTD_outpath = os.path.join(tsdir, product_name,
                                           product_mtd_path)
        mtd_pd_writer = MTD_writer_S2(bb_S2_product, pd.mtl.mtl_file_name, H_F='H') if pd.sensor == 'S2' \
            else MTD_writer_LS8(bb_L8_product, H_F='H')
        mtd_pd_writer.manual_replaces(pd)
        mtd_pd_writer.write(product_MTD_outpath, pretty_print=True)
        # TODO UNCOMMENT BELOW FOR XSD CHECK
        # product_mtd_xsd = metadata.hardcoded_values.get('product_mtd_xsd')
        # log.info('Product MTD is valid : {}'.format(mtd_pd_writer.validate_schema(product_mtd_xsd,
        #                                                                           product_MTD_outpath)))

        # Write stac
        stac_writer = STACWriter()
        stac_writer.write_product(
            pd, os.path.join(tsdir,
                             product_name), metadata.mtd['bands_path_H'],
            f"{metadata.mtd['band_rootName_H']}_QL_B432.jpg",
            granule_compact_name)
Example #12
0
    def get_valid_pixel_mask(self, mask_filename):
        """
        Depending on collection / processing level, provide the cloud / sea mask
        Set self.mask_filename
        """

        # Open QA Image
        if self.bqa_filename != 'not found':
            self.bqa_filename = os.path.join(self.product_path,
                                             self.bqa_filename)
            log.info('Generating validity and nodata masks from BQA band')
            log.debug(f'Read cloud mask: {self.bqa_filename}')
            bqa = S2L_ImageFile(self.bqa_filename)
            bqa_array = bqa.array

            # Process Pixel valid 'pre collection
            # Process Land Water Mask 'collection 1
            if self.collection != 'Pre Collection':
                th = 2720  # No land sea mask given with Collection products
                log.debug(th)
            else:
                th = 20480

            valid_px_mask = np.zeros(bqa_array.shape, np.uint8)
            valid_px_mask[bqa_array <= th] = 1
            valid_px_mask[bqa_array == 1] = 0  # Remove background
            valid_px_mask[bqa_array > th] = 0

            mask = bqa.duplicate(mask_filename, array=valid_px_mask)
            mask.write(creation_options=['COMPRESS=LZW'], nodata_value=None)
            self.mask_filename = mask_filename

            # nodata mask (not good when taking it from BQA, getting from B01)
            mask_filename = os.path.join(os.path.dirname(mask_filename),
                                         'nodata_pixel_mask.tif')
            if self.data_type == 'L2A':
                image_filename = self.surf_image_list[0]
            else:
                image_filename = self.dn_image_list[0]
            image = S2L_ImageFile(image_filename)
            array = image.array.clip(0, 1).astype(np.uint8)
            mask = image.duplicate(mask_filename, array=array.astype(np.uint8))
            mask.write(creation_options=['COMPRESS=LZW'], nodata_value=None)
            self.nodata_mask_filename = mask_filename

            return True
        elif self.scl:
            log.info('Generating validity and nodata masks from SCL band')
            log.debug(f'Read SCL: {self.scene_classif_band}')
            scl = S2L_ImageFile(self.scene_classif_band)
            scl_array = scl.array

            valid_px_mask = np.zeros(scl_array.shape, np.uint8)
            # Consider as valid pixels :
            #                VEGETATION et NOT_VEGETATED (valeurs 4 et 5)
            #                UNCLASSIFIED (7) et SNOW (11) -
            valid_px_mask[scl_array == 4] = 1
            valid_px_mask[scl_array == 5] = 1
            valid_px_mask[scl_array == 7] = 1
            valid_px_mask[scl_array == 11] = 1

            mask = scl.duplicate(mask_filename, array=valid_px_mask)
            mask.write(creation_options=['COMPRESS=LZW'])
            self.mask_filename = mask_filename

            # nodata mask
            mask_filename = os.path.join(os.path.dirname(mask_filename),
                                         'nodata_pixel_mask.tif')
            nodata = np.ones(scl_array.shape, np.uint8)
            nodata[scl_array == 0] = 0
            mask = scl.duplicate(mask_filename, array=nodata)
            mask.write(creation_options=['COMPRESS=LZW'])
            self.nodata_mask_filename = mask_filename

            return True
        return False
Example #13
0
    def get_valid_pixel_mask(self, mask_filename, res=20):
        """
        :param res:
        :param mask_filename:
        :return:
        """

        if self.scene_classif_band:
            log.info('Generating validity and nodata masks from SCL band')
            log.debug(f'Read SCL: {self.scene_classif_band}')
            scl = S2L_ImageFile(self.scene_classif_band)
            scl_array = scl.array

            valid_px_mask = np.zeros(scl_array.shape, np.uint8)
            # Consider as valid pixels :
            #                VEGETATION et NOT_VEGETATED (valeurs 4 et 5)
            #                UNCLASSIFIED (7) et SNOW (11) -
            valid_px_mask[scl_array == 4] = 1
            valid_px_mask[scl_array == 5] = 1
            valid_px_mask[scl_array == 7] = 1
            valid_px_mask[scl_array == 11] = 1

            mask = scl.duplicate(mask_filename, array=valid_px_mask)
            mask.write(creation_options=['COMPRESS=LZW'])
            self.mask_filename = mask_filename

            # nodata mask
            mask_filename = os.path.join(os.path.dirname(mask_filename),
                                         'nodata_pixel_mask.tif')
            nodata = np.ones(scl_array.shape, np.uint8)
            nodata[scl_array == 0] = 0
            mask = scl.duplicate(mask_filename, array=nodata)
            mask.write(creation_options=['COMPRESS=LZW'])
            self.nodata_mask_filename = mask_filename

            return True

        # L1C case for instance -> No SCL, but NODATA and CLD mask
        else:
            # Nodata Mask
            nodata_ref_band = 'B01'
            band_path = self.bands[nodata_ref_band]
            log.info(f'Generating nodata mask from band {nodata_ref_band}')
            log.debug(f'Read cloud mask: {band_path}')
            image = S2L_ImageFile(band_path)
            array = image.array
            nodata_mask_filename = os.path.join(
                os.path.dirname(mask_filename),
                f'nodata_pixel_mask_{nodata_ref_band}.tif')
            nodata = np.ones(array.shape, np.uint8)
            # shall be 0, but due to compression artefact, threshold increased to 4:
            nodata[array <= 4] = 0

            # resize nodata to output res
            shape = (int(nodata.shape[0] * -image.yRes / res),
                     int(nodata.shape[1] * image.xRes / res))
            log.debug(shape)
            nodata = skit_resize(nodata, shape, order=0,
                                 preserve_range=True).astype(np.uint8)

            # save to image
            mask = image.duplicate(nodata_mask_filename, array=nodata, res=res)
            mask.write(creation_options=['COMPRESS=LZW'], nodata_value=None)
            self.nodata_mask_filename = nodata_mask_filename

            if self.cloudmask:
                # Cloud mask
                rname, ext = os.path.splitext(self.cloudmask)
                if ext == '.gml':
                    log.info('Generating validity mask from cloud mask')
                    log.debug(f'Read cloud mask: {self.cloudmask}')
                    # Check if any cloud feature in gml
                    dom = minidom.parse(self.cloudmask)
                    nClouds = len(dom.getElementsByTagName('eop:MaskFeature'))

                    # rasterize
                    # make byte mask 0/1, LZW compression
                    if nClouds > 0:
                        outputBounds = [self.ULX, self.LRY, self.LRX, self.ULY]
                        if not os.path.exists(os.path.dirname(mask_filename)):
                            os.makedirs(os.path.dirname(mask_filename))
                        gdal.Rasterize(mask_filename,
                                       self.cloudmask,
                                       outputType=gdal.GDT_Byte,
                                       creationOptions=['COMPRESS=LZW'],
                                       burnValues=0,
                                       initValues=1,
                                       outputBounds=outputBounds,
                                       outputSRS=self.epsg,
                                       xRes=res,
                                       yRes=res)

                        # apply nodata to validity mask
                        dataset = gdal.Open(mask_filename, gdal.GA_Update)
                        array = dataset.GetRasterBand(1).ReadAsArray()
                        array[nodata == 0] = 0
                        dataset.GetRasterBand(1).WriteArray(array)
                        dataset = None
                    else:
                        # no cloud mask, copy nodata mask
                        shutil.copy(self.nodata_mask_filename, mask_filename)
                    log.info('Written: {}'.format(mask_filename))
                    self.mask_filename = mask_filename

            return True