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
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
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])
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)
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))})
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
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
def getMaskFile(self): # return mask as S2L_ImageFile object filepath = self.getMask() return S2L_ImageFile(filepath)
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)
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') })
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)
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
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