├── images ├── index_3800jet.png └── unet_ushape_ver2_legends_annotated_final-1.png ├── Visualization benchmark ├── Ilustration_image.png ├── dataset │ └── example_dataset.npy ├── Thunmpy │ ├── __init__.py │ ├── Thunmixing.py │ ├── Methods.py │ ├── ThunmFit.py │ └── Thunmcorr.py └── README.md ├── requirements.txt ├── dataset.py ├── tiff_process.py ├── modis_data_preprocessing.py ├── modis_downloader.py ├── run_inference.py ├── README.md ├── train.py ├── model.py └── utils.py /images/index_3800jet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IMT-Project-LTS-SR/MRUNet-for-MODIS-super-resolution/HEAD/images/index_3800jet.png -------------------------------------------------------------------------------- /Visualization benchmark/Ilustration_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IMT-Project-LTS-SR/MRUNet-for-MODIS-super-resolution/HEAD/Visualization benchmark/Ilustration_image.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | torch>=1.2 2 | torchvision 3 | matplotlib 4 | argparse 5 | path 6 | tqdm 7 | scipy 8 | pyModis 9 | pymp-pypi 10 | opencv-python 11 | gdal 12 | scikit-image 13 | -------------------------------------------------------------------------------- /Visualization benchmark/dataset/example_dataset.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IMT-Project-LTS-SR/MRUNet-for-MODIS-super-resolution/HEAD/Visualization benchmark/dataset/example_dataset.npy -------------------------------------------------------------------------------- /images/unet_ushape_ver2_legends_annotated_final-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IMT-Project-LTS-SR/MRUNet-for-MODIS-super-resolution/HEAD/images/unet_ushape_ver2_legends_annotated_final-1.png -------------------------------------------------------------------------------- /Visualization benchmark/Thunmpy/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | __all__ = ["ThunmFit.linear_fit", "ThunmFit.bilinear_fit", "ThunmFit.huts_fit", "ThunmFit.linear_fit_window", "ThunmFit.fit_byclass", "Thunmixing.linear_unmixing", "Thunmixing.bilinear_unmixing", "Thunmixing.huts_unmixing", "Thunmixing.aatprk_unmixing", "Thunmixing.linear_unmixing_byclass", "Thunmcorr.correction_avrg", "Thunmcorr.quality_correction", "Thunmcorr.correction_linreg", "Thunmcorr.correction_ATPRK", "Thunmcorr.correction_AATPRK", "Methods. TsHARP", "Methods.HUTS", "Methods.ATPRK", "Methods.AATPRK"] 3 | -------------------------------------------------------------------------------- /dataset.py: -------------------------------------------------------------------------------- 1 | import torch 2 | from torch.utils.data import Dataset 3 | 4 | # THE DATASET CLASS 5 | class LOADDataset(Dataset): 6 | def __init__(self, image_data, labels, transform=None): 7 | self.image_data = image_data 8 | self.labels = labels 9 | self.transform = transform 10 | 11 | def __len__(self): 12 | return (len(self.image_data)) 13 | def __getitem__(self, index): 14 | image = torch.tensor(self.image_data[index], dtype=torch.float) 15 | label = torch.tensor(self.labels[index], dtype=torch.float) 16 | if self.transform is not None: 17 | image = self.transform(image) 18 | 19 | return (image, label) -------------------------------------------------------------------------------- /tiff_process.py: -------------------------------------------------------------------------------- 1 | import pymp 2 | import time 3 | import os 4 | from utils import read_tif 5 | 6 | def tiff_process(data_path): 7 | count = 0 8 | cores = 4 9 | 10 | start = time.time() 11 | tifs = os.listdir(data_path) 12 | Y_day = pymp.shared.array((len(tifs),64,64), dtype='float64') 13 | Y_night = pymp.shared.array((len(tifs),64,64), dtype='float64') 14 | 15 | with pymp.Parallel(cores) as p: 16 | for idx in p.range(len(tifs)): 17 | tif = tifs[idx] 18 | if tif.endswith('tif'): 19 | tif_path = os.path.join(data_path,tif) 20 | LST_K_day,LST_K_night,cols,rows,projection,geotransform = read_tif(tif_path) 21 | Y_day[idx,:,:] = LST_K_day 22 | Y_night[idx,:,:] = LST_K_night 23 | 24 | 25 | end = time.time() 26 | print(f"Finished processing tif files in {((end-start)/60):.3f} minutes \n") 27 | return Y_day, Y_night 28 | -------------------------------------------------------------------------------- /Visualization benchmark/README.md: -------------------------------------------------------------------------------- 1 | ## Benchmark visualization 2 | 3 | This folder provides the code to benchmark the methods mentioned in the paper. 4 | 5 | 6 | 7 |
File description
8 | 9 | | File name | Description | 10 | |-----------|-------------| 11 | |Thunmpy|folder containing codes for ATPRK statistical super-resolution, please refer to [this Github repo by @cgranerob and @aumichel](https://github.com/cgranerob/ThUnmpy)| 12 | |Visualization_benchmark.ipynb|notebook for running the visualization| 13 | 14 |
15 | 16 | ## Requirement 17 | Before running the notebook, you first need to download the pre-trained models and put them in this folder using the following links: 18 | 19 | VDSR: [here](https://drive.google.com/file/d/17OKkTVxhD4GSuSArA9bJ6Uq8WkRe1Sb1/view?usp=sharing) 20 | 21 | DMCN: [here](https://drive.google.com/file/d/12XNOszkNoZTM3aZPwu_LbdLd1HWESwL1/view?usp=sharing) 22 | 23 | Multi-residual U-Net: [here](https://drive.google.com/file/d/1-BCc0-kj07p5FK4GbV_dzLb_0XEuKe6b/view?usp=sharing) or your trained model using MRUnet. 24 | 25 | You also need to format your dataset into an npy array with the size: 26 | (data_length, 2, 64, 64). 27 | Where data_length is the number of test data points. Each data points consists of two channels, index 0 represents NDVI and index 1 represents LST. Each channel has the size of 64 x 64 pixels. 28 | 29 | The example dataset can be found [here](https://drive.google.com/file/d/1np-1OogR8q9coWb-wMdlsFk-Rk6h4ouD/view?usp=sharing). 30 | 31 | You need to put your dataset into **dataset** folder. 32 | 33 | ## Run visualization 34 | 35 | After doing previous steps, you can run the notebook visualization_benchmark.ipynb to see the results. 36 | 37 | An example result: 38 | 39 | ![Example_result](Ilustration_image.png) 40 | 41 | -------------------------------------------------------------------------------- /modis_data_preprocessing.py: -------------------------------------------------------------------------------- 1 | import os 2 | # from pymodis import downmodis 3 | import numpy as np 4 | import time 5 | from utils import * 6 | from argparse import ArgumentParser 7 | 8 | def MODIS_Data_Preprocessing(year, product, num_threads): 9 | sensor = product.split(".")[0] 10 | root_dir = 'MODIS/MOD_{}_{}'.format(year,sensor) 11 | hdfs_path = os.path.join(root_dir, 'hdfs_files') 12 | tifs_1km_path = os.path.join(root_dir, 'tifs_files/1km') 13 | tifs_2km_path = os.path.join(root_dir, 'tifs_files/2km') 14 | tifs_4km_path = os.path.join(root_dir, 'tifs_files/4km') 15 | 16 | os.makedirs(hdfs_path,exist_ok=1) 17 | os.makedirs(tifs_1km_path,exist_ok=1) 18 | os.makedirs(tifs_2km_path,exist_ok=1) 19 | os.makedirs(tifs_4km_path,exist_ok=1) 20 | 21 | print("start to processing {}".format(hdfs_path)) 22 | hdfs = os.listdir(hdfs_path) 23 | hdfs.sort() 24 | start_time = time.time() 25 | # Core images with multi-core 26 | # with pymp.Parallel(num_threads) as p: 27 | # for index in p.range(0, len(hdfs)): 28 | 29 | for index in range(0, len(hdfs)): 30 | hdf = hdfs[index] 31 | if not hdf.endswith('hdf'): continue 32 | hdf_path = os.path.join(hdfs_path,hdf) 33 | if sensor=='MOD11A1': 34 | # try: 35 | crop_modis(hdf_path, hdf,tifs_1km_path, tifs_2km_path,tifs_4km_path, 64, (64,64)) 36 | # except: 37 | # shutil.rmtree(hdf_path) 38 | # print("problem", hdf_path) 39 | # pass 40 | elif sensor=='MOD13A2': 41 | # try: 42 | crop_modis_MOD13A2(hdf_path, hdf,tifs_1km_path, tifs_2km_path,tifs_4km_path, 64, (64,64)) 43 | # except: 44 | # shutil.rmtree(hdf_path) 45 | # print("problem", hdf_path) 46 | # pass 47 | 48 | print("Using {:.4f}s to process product = {}".format(time.time()-start_time, product)) 49 | 50 | if __name__ == "__main__": 51 | parser = ArgumentParser() 52 | parser.add_argument('--year_begin', type=int, default=2000) 53 | parser.add_argument('--year_end', type=int, default=2021) 54 | args = parser.parse_args() 55 | 56 | years = list(np.arange(args.year_begin, args.year_end)) 57 | # LST: "MOD11A1.061", NDVI: "MOD13A2.061" 58 | products = ["MOD11A1.061","MOD13A2.061"] 59 | # tiles to download, France is in h17v04 and h18v04 , string of tiles separated by comma 60 | tiles = "h18v04" 61 | # Cores number to use 62 | num_threads = 4 63 | for year in years: 64 | for product in products: 65 | MODIS_Data_Preprocessing(year, product, num_threads) -------------------------------------------------------------------------------- /modis_downloader.py: -------------------------------------------------------------------------------- 1 | import os 2 | from pymodis import downmodis 3 | import numpy as np 4 | import pymp 5 | import time 6 | from utils import * 7 | from argparse import ArgumentParser 8 | 9 | 10 | def MODIS_Parallel_Downloader(year,product, num_threads, user="projet3a", password="Projet3AIMT"): 11 | sensor = product.split(".")[0] 12 | hdfs_path = 'MODIS/MOD_{}_{}/hdfs_files'.format(year,sensor) 13 | tifs_1km_path = 'MODIS/MOD_{}_{}/tifs_files/1km'.format(year,sensor) 14 | tifs_2km_path = 'MODIS/MOD_{}_{}/tifs_files/2km'.format(year,sensor) 15 | tifs_4km_path = 'MODIS/MOD_{}_{}/tifs_files/4km'.format(year,sensor) 16 | 17 | os.makedirs(hdfs_path,exist_ok=1) 18 | os.makedirs(tifs_1km_path,exist_ok=1) 19 | os.makedirs(tifs_2km_path,exist_ok=1) 20 | os.makedirs(tifs_4km_path,exist_ok=1) 21 | 22 | # Download data with multi-core 23 | with pymp.Parallel(num_threads) as p: 24 | # if 1: 25 | for month in p.range(1, 13): 26 | startdate = "{}-{}-01".format(str(year),str(month).zfill(2)) 27 | if month != 12: 28 | enddate = "{}-{}-01".format(str(year),str(month+1).zfill(2)) 29 | else: 30 | enddate = "{}-{}-31".format(str(year),str(month).zfill(2)) 31 | 32 | start_time = time.time() 33 | print("Start to download {} From {} to {}".format(hdfs_path, startdate,enddate)) 34 | try: 35 | modisDown = downmodis.downModis(user=user,password=password,product=product,destinationFolder=hdfs_path, tiles=tiles, today=startdate, enddate=enddate) 36 | modisDown.connect() 37 | modisDown.downloadsAllDay() 38 | # modisDown.downloadsAllDay(clean=True, allDays=True) 39 | except: 40 | print("Download Error {} From {} to {}".format(hdfs_path, startdate,enddate)) 41 | print("Finish download {} From {} to {}, time cost: {:.4f}".format(hdfs_path, startdate,enddate,time.time()-start_time)) 42 | 43 | 44 | if __name__ == "__main__": 45 | parser = ArgumentParser() 46 | parser.add_argument('--year_begin', type=int, default=2000) 47 | parser.add_argument('--year_end', type=int, default=2021) 48 | parser.add_argument('--username', type=str, default="ganglina") 49 | parser.add_argument('--password', type=str, default="505166Tgl") 50 | args = parser.parse_args() 51 | 52 | years = list(np.arange(args.year_begin, args.year_end)) 53 | products = ["MOD11A1.061", "MOD13A2.061"] # LST: "MOD11A1.061", NDVI_1km: "MOD13A2.061", NDVI_250m: "MOD13Q1.061" 54 | tiles = "h18v04" # tiles to download, France is in h17v04 and h18v04 , string of tiles separated by comma 55 | num_threads = 6 # Cores number to use 56 | for year in years: 57 | for product in products: 58 | MODIS_Parallel_Downloader(year,product, num_threads, args.username, args.password) 59 | 60 | 61 | # python modis_downloader.py --username --password -------------------------------------------------------------------------------- /run_inference.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import cv2 3 | import numpy as np 4 | import argparse 5 | import os 6 | import matplotlib.pyplot as plt 7 | 8 | from model import MRUNet 9 | from tiff_process import tiff_process 10 | from utils import downsampling 11 | 12 | parser = argparse.ArgumentParser(description='PyTorch MR UNet inference on a folder of tif files.', 13 | formatter_class=argparse.ArgumentDefaultsHelpFormatter) 14 | parser.add_argument('--datapath', 15 | help='path to the folder containing the tif files for inference') 16 | parser.add_argument('--pretrained', help='path to pre-trained model') 17 | parser.add_argument('--savepath', help='path to save figure') 18 | parser.add_argument('--max_val', default=333.32000732421875, type=float, 19 | help='normalization factor for the input and output, which is the maximum pixel value of training data') 20 | args = parser.parse_args() 21 | 22 | 23 | def main(): 24 | device = 'cuda' if torch.cuda.is_available() else 'cpu' 25 | 26 | # Load pretrained model 27 | model_MRUnet = MRUNet(res_down=True, n_resblocks=1, bilinear=0).to(device) 28 | model_MRUnet.load_state_dict(torch.load(args.pretrained)) 29 | # Normalization factor and scale 30 | max_val = args.max_val 31 | scale = 4 32 | 33 | 34 | # Tiff process 35 | Y_day, Y_night = tiff_process(args.datapath) 36 | 37 | # Test model 38 | indx = 0 39 | for y_day, y_night in zip(Y_day, Y_night): 40 | y_day_4km = downsampling(y_day, scale) 41 | y_night_4km = downsampling(y_night, scale) 42 | bicubic_day = cv2.resize(y_day_4km, y_day.shape, cv2.INTER_CUBIC) 43 | bicubic_night = cv2.resize(y_night_4km, y_night.shape, cv2.INTER_CUBIC) 44 | input_day = torch.tensor(np.reshape(bicubic_day/max_val, (1,1,64,64)), dtype=torch.float).to(device) 45 | input_night = torch.tensor(np.reshape(bicubic_night/max_val, (1,1,64,64)), dtype=torch.float).to(device) 46 | out_day = model_MRUnet(input_day).cpu().detach().numpy()*max_val 47 | out_night = model_MRUnet(input_night).cpu().detach().numpy()*max_val 48 | 49 | # plot and save fig 50 | fontsize = 15 51 | clmap = 'jet' 52 | fig, ax = plt.subplots(nrows=2, ncols=2, figsize=(12, 10)) 53 | im1 = ax[0,0].imshow(y_day, vmin = y_day.min(), vmax = y_day.max(), cmap = clmap) 54 | ax[0,0].set_title("Ground truth day", fontsize=fontsize) 55 | ax[0,0].axis('off') 56 | 57 | im2 = ax[0,1].imshow(out_day[0,0,:,:], vmin = y_day.min(), vmax = y_day.max(), cmap = clmap) 58 | ax[0,1].set_title("SR day", fontsize=fontsize) 59 | ax[0,1].axis('off') 60 | 61 | im3 = ax[1,0].imshow(y_night, vmin = y_night.min(), vmax = y_night.max(), cmap = clmap) 62 | ax[1,0].set_title("Ground truth night", fontsize=fontsize) 63 | ax[1,0].axis('off') 64 | 65 | im4 = ax[1,1].imshow(out_night[0,0,:,:], vmin = y_night.min(), vmax = y_night.max(), cmap = clmap) 66 | ax[1,1].set_title("SR night", fontsize=fontsize) 67 | ax[1,1].axis('off') 68 | 69 | # cmap = plt.get_cmap('jet',20) 70 | # fig.tight_layout() 71 | # fig.subplots_adjust(right=0.7) 72 | # cbar_ax = fig.add_axes([0.65, 0.15, 0.03, 0.7]) 73 | # cbar_ax.tick_params(labelsize=15) 74 | # fig.colorbar(im1, cax=cbar_ax, cmap = clmap) 75 | 76 | plt.savefig(args.savepath + "/result_"+ str(indx)+ ".png", bbox_inches='tight') 77 | indx += 1 78 | 79 | if __name__ == '__main__': 80 | main() 81 | 82 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Convolutional Neural Network Modelling for MODIS Land Surface Temperature Super-Resolution 3 | This repository includes code implementation for our article: 4 | > [*Binh Minh Nguyen, Ganglin Tian, Minh-Triet Vo, Aurélie Michel, Thomas Corpetti, et al.*. **Convolutional Neural Network Modelling for MODIS Land Surface Temperature Super-Resolution. 2022**.](https://hal.archives-ouvertes.fr/hal-03580148) 5 | 6 | The brief description for each file is shown in the table below: 7 | 8 | 9 | 10 |
File description File description
11 | 12 | | File name | Description | 13 | |-----------|-------------| 14 | |modis_downloader.py|download MODIS LST image from NASA webpage (more information below)| 15 | |modis_data_preprocessing.py|preprocess the downloaded MODIS images, including cropping, cloud/sea elimination, etc.| 16 | |utils.py|contain all helper functions| 17 | |tiff_process.py|preprocess the tif files and transform them into npy arrays| 18 | 19 | 20 | 21 | | File name | Description | 22 | |-----------|-------------| 23 | |model.py|definition for Multi-residual U-Net model| 24 | |dataset.py|create Pytorch dataset object| 25 | |run_inference.py|run the trained network| 26 | |train.py|train the network| 27 | 28 |
29 | 30 | ## 0. Requirements 31 | 32 | ``` 33 | pip install -r requirements.txt 34 | ``` 35 | 36 | ## 1. Data downloading and database preparation 37 | User should firstly register on NASA's website[https://urs.earthdata.nasa.gov/users/new]. 38 | User could use the following command to download automatically MODIS dataset with the user name and corresponding password, the downloaded data will be placed folder "MODIS" in current directory. 39 | ``` 40 | python modis_downloader.py --username --password 41 | ``` 42 | 43 | By assigning values to '--year_begin' and '--year_end', user can select the time range for downloading data, from year_begin to year_end. Default 'year_begin' is 2020, 'year_end' is 2021. 44 | ``` 45 | python modis_downloader.py --year_begin --year_end --username --password 46 | ``` 47 | 48 | After downloading the raw data for the desired years, data need to be pre-processed, which involves cropping, cloud and sea pixels elimination and downsampling: 49 | ``` 50 | python modis_data_preprocessing.py --year_begin --year_end 51 | ``` 52 | 53 | ## 2. Train and test **Multi-residual U-Net** 54 | 55 | Our principle contribution in this project is to design and implement a new deep learning model based on U-net architecture called **Multi-residual U-Net**. The architecture of this model is shown as below: 56 | 57 | ![MRUnet](images/unet_ushape_ver2_legends_annotated_final-1.png) 58 | 59 | ### Train 60 | To train the network with your tif data, please make sure to put all of the data into a folder and run the following command: 61 | 62 | ``` 63 | python train.py --datapath --model_name --lr --epochs --batch_size --continue_train 64 | ``` 65 | *P/s: The checkpoint of training process lies in the same directory as the train.py*. 66 | 67 | ### Test 68 | To run the inference of the pretrained network on your tif data, please make sure to put all of the data into a folder and run the following command: 69 | 70 | ``` 71 | python run_inference.py --datapath --pretrained --savepath --max_val 72 | ``` 73 | *P/s:* 74 | 1. The max_val is the maximum pixel value of the data used for training the pretrained model. 75 | 2. A pretrained Multi-residual U-Net can be found in **Visualization benchmark**. 76 | 77 | ## 3. Result 78 | 79 | To quantify the results, we employed some famous metrics in image super resolution: PSNR and SSIM. We also mentioned RMSE because it is meaningful in remote sensing. 80 | 81 | Qualitative results: 82 | 83 | ![Results](images/index_3800jet.png) 84 | 85 | Quantitative results: 86 | 87 | | Method | PSNR | SSIM | RMSE | 88 | |:--------------------------:|:-------:|:----:|:----:| 89 | | Bicubic | 23.91 | 0.61 | 0.69 | 90 | | ATPRK | 21.59 | 0.61 | 0.90 | 91 | | VDSR | 25.42 | 0.72 | 0.58 | 92 | | DCMN | 25.05 | 0.71 | 0.61 | 93 | | **Multi-residual U-Net** | **28.40** | **0.85** | **0.39** | **(ours)** 94 | 95 | ## 4. Result visualization 96 | 97 | You can find the notebook as well as the instruction for the benchmarking visualization in the folder *Visualization benchmark*. The notebook provides functions to plot and benchmark several techniques mentioned in the paper. 98 | 99 | 100 | -------------------------------------------------------------------------------- /train.py: -------------------------------------------------------------------------------- 1 | from model import MRUNet 2 | from utils import * 3 | from tiff_process import tiff_process 4 | from dataset import LOADDataset 5 | import pymp 6 | import cv2 7 | import numpy as np 8 | from torch.utils.data import DataLoader 9 | from tqdm import tqdm 10 | import torch 11 | import torch.nn.functional as F 12 | import torch.optim as optim 13 | import time 14 | import argparse 15 | import os 16 | 17 | parser = argparse.ArgumentParser(description="PyTorch MR UNet training from tif files contained in a data folder", 18 | formatter_class=argparse.ArgumentDefaultsHelpFormatter) 19 | parser.add_argument('--datapath', help='path to directory containing training tif data') 20 | parser.add_argument('--lr', default=0.001, type=float, help='learning rate') 21 | parser.add_argument('--epochs', default=300, type=int, help='number of epochs') 22 | parser.add_argument('--batch_size', default=24, type=int, help='size of batch') 23 | parser.add_argument('--model_name', type=str, help='name of the model') 24 | parser.add_argument('--continue_train', choices=['True', 'False'], default='False', type=str, 25 | help="flag for continue training, if True - continue training the 'model_name' model, else - training from scratch") 26 | args = parser.parse_args() 27 | 28 | 29 | device = 'cuda' if torch.cuda.is_available() else 'cpu' 30 | model = MRUNet(res_down=True, n_resblocks=1, bilinear=0).to(device) 31 | 32 | scale = 4 33 | core = 4 34 | # Tiff process 35 | Y_day, Y_night = tiff_process(args.datapath) 36 | Y = np.concatenate((Y_day, Y_night),axis=0) 37 | Y_new = pymp.shared.list() 38 | 39 | # Eliminate images with cloud/sea pixels 40 | with pymp.Parallel(10) as p: 41 | for i in p.range(Y.shape[0]): 42 | if len(Y[i][Y[i] == 0]) <= 0: 43 | Y_new.append(Y[i]) 44 | Y_new = np.array(Y_new) 45 | start = time.time() 46 | 47 | # Create training and validating sets 48 | np.random.seed(1) 49 | np.random.shuffle(Y_new) # Shuffle dataset 50 | ratio = 0.75 # Proportion of training data 51 | y_train = Y_new[:int(Y_new.shape[0]*ratio)] 52 | y_val = Y_new[int(Y_new.shape[0]*ratio):] 53 | 54 | # DATA AUGMENTATION FOR TRAINING LABEL 55 | y_train_new = pymp.shared.list() 56 | with pymp.Parallel(core) as p: 57 | for i in p.range(y_train.shape[0]): 58 | y_train_new.append(y_train[i]) 59 | y_train_new.append(np.flip(y_train[i], 1)) 60 | y_train = np.array(y_train_new) 61 | max_val = np.max(y_train) # MAX VALUE IN TRAINING SET, WHICH IS USED FOR NORMALIZATION 62 | print('Max pixel value of training set is {},\nIMPORTANT: Please save it for later used as the normalization factor\n'.format(max_val)) 63 | 64 | # INIT TRAINING DATA AND TESTING DATA SHAPE 65 | x_train = pymp.shared.array((y_train.shape)) 66 | x_val = pymp.shared.array((y_val.shape)) 67 | 68 | # PREPROCESS TO CREATE BICUBIC VERSION FOR MODEL INPUT 69 | with pymp.Parallel(core) as p: 70 | for i in p.range(y_train.shape[0]): 71 | y_tr = y_train[i] 72 | a = downsampling(y_tr, scale) 73 | x_train[i,:,:] = upsampling(a , scale) 74 | x_train[i,:,:] = normalization(x_train[i,:,:], max_val) 75 | 76 | with pymp.Parallel(core) as p: 77 | for i in p.range(y_val.shape[0]): 78 | y_te = y_val[i] 79 | a = downsampling(y_te, scale) 80 | x_val[i,:,:] = upsampling(a , scale) 81 | x_val[i,:,:] = normalization(x_val[i,:,:], max_val) 82 | 83 | x_train = x_train.reshape((x_train.shape[0], 1, x_train.shape[1], x_train.shape[2])) 84 | x_val = x_val.reshape((x_val.shape[0], 1, x_val.shape[1], x_val.shape[2])) 85 | y_train = y_train.reshape((y_train.shape[0], 1, y_train.shape[1], y_train.shape[2])) 86 | y_val = y_val.reshape((y_val.shape[0], 1, y_val.shape[1], y_val.shape[2])) 87 | end = time.time() 88 | print(f"Finished processing data in additional {((end-start)/60):.3f} minutes \n") 89 | 90 | def train(model, dataloader, optimizer, train_data, max_val): 91 | # Train model 92 | model.train() 93 | running_loss = 0.0 94 | running_psnr = 0.0 95 | running_ssim = 0.0 96 | for bi, data in tqdm(enumerate(dataloader), total=int(len(train_data)/dataloader.batch_size)): 97 | image_data = data[0].to(device) 98 | label = data[1].to(device) 99 | 100 | # zero grad the optimizer 101 | optimizer.zero_grad() 102 | outputs = model(image_data) 103 | loss = get_loss(outputs*max_val, label) 104 | # backpropagation 105 | loss.backward() 106 | # update the parameters 107 | optimizer.step() 108 | # add loss of each item (total items in a batch = batch size) 109 | running_loss += loss.item() 110 | # calculate batch psnr (once every `batch_size` iterations) 111 | batch_psnr = psnr(label, outputs, max_val) 112 | running_psnr += batch_psnr 113 | batch_ssim = ssim(label, outputs, max_val) 114 | running_ssim += batch_ssim 115 | final_loss = running_loss/len(dataloader.dataset) 116 | final_psnr = running_psnr/int(len(train_data)/dataloader.batch_size) 117 | final_ssim = running_ssim/int(len(train_data)/dataloader.batch_size) 118 | return final_loss, final_psnr, final_ssim 119 | 120 | def validate(model, dataloader, epoch, val_data, max_val): 121 | model.eval() 122 | running_loss = 0.0 123 | running_psnr = 0.0 124 | running_ssim = 0.0 125 | with torch.no_grad(): 126 | for bi, data in tqdm(enumerate(dataloader), total=int(len(val_data)/dataloader.batch_size)): 127 | image_data = data[0].to(device) 128 | label = data[1].to(device) 129 | outputs = model(image_data) 130 | 131 | loss = get_loss(outputs*max_val, label) 132 | # add loss of each item (total items in a batch = batch size) 133 | running_loss += loss.item() 134 | # calculate batch psnr (once every `batch_size` iterations) 135 | batch_psnr = psnr(label, outputs, max_val) 136 | running_psnr += batch_psnr 137 | batch_ssim = ssim(label, outputs, max_val) 138 | running_ssim += batch_ssim 139 | outputs = outputs.cpu() 140 | # save_image(outputs, f"../outputs/val_sr{epoch}.png") 141 | final_loss = running_loss/len(dataloader.dataset) 142 | final_psnr = running_psnr/int(len(val_data)/dataloader.batch_size) 143 | final_ssim = running_ssim/int(len(val_data)/dataloader.batch_size) 144 | return final_loss, final_psnr, final_ssim 145 | 146 | def main(): 147 | # Load dataset and create data loader 148 | transform = None 149 | train_data = LOADDataset(x_train, y_train, transform=transform) 150 | val_data = LOADDataset(x_val, y_val, transform=transform) 151 | batch_size = args.batch_size 152 | train_loader = DataLoader(train_data, batch_size=batch_size, shuffle = True) 153 | val_loader = DataLoader(val_data, batch_size=batch_size) 154 | print('Length of training set: {} \n'.format(len(train_data))) 155 | print('Length of validating set: {} \n'.format(len(val_data))) 156 | print('Shape of input and output: ({},{}) \n'.format(x_train.shape[-2],x_train.shape[-1])) 157 | 158 | epochs = args.epochs 159 | lr = args.lr 160 | model_name = args.model_name 161 | continue_train = args.continue_train == 'True' 162 | 163 | optimizer = optim.Adam(model.parameters(), lr=lr) 164 | 165 | if os.path.exists("Metrics") == False: 166 | os.makedirs("Metrics") 167 | 168 | if not continue_train: 169 | # TRAINING CELL 170 | train_loss, val_loss = [], [] 171 | train_psnr, val_psnr = [], [] 172 | train_ssim, val_ssim = [], [] 173 | start = time.time() 174 | 175 | last_epoch = -1 176 | vloss = np.inf 177 | 178 | else: 179 | # Load the lists of last time training metrics 180 | metrics = np.load(os.path.join("./Metrics",model_name + ".npy")) 181 | train_loss, val_loss = metrics[0].tolist(), metrics[3].tolist() 182 | train_psnr, val_psnr = metrics[1].tolist(), metrics[4].tolist() 183 | train_ssim, val_ssim = metrics[2].tolist(), metrics[5].tolist() 184 | start = time.time() 185 | 186 | # Model loading 187 | checkpoint = torch.load(model_name) 188 | model.load_state_dict(checkpoint['model_state_dict']) 189 | optimizer.load_state_dict(checkpoint['optimizer_state_dict']) 190 | last_epoch = checkpoint['epoch'] 191 | losses = checkpoint['losses'] 192 | vloss = losses[3] 193 | 194 | for epoch in range(last_epoch+1,epochs): 195 | print(f"Epoch {epoch + 1} of {epochs}") 196 | train_epoch_loss, train_epoch_psnr, train_epoch_ssim = train(model, train_loader, optimizer, train_data, max_val) 197 | val_epoch_loss, val_epoch_psnr, val_epoch_ssim = validate(model, val_loader, epoch, val_data, max_val) 198 | print(f"Train loss: {train_epoch_loss:.6f}") 199 | print(f"Val loss: {val_epoch_loss:.6f}") 200 | train_loss.append(train_epoch_loss) 201 | train_psnr.append(train_epoch_psnr) 202 | train_ssim.append(train_epoch_ssim) 203 | val_loss.append(val_epoch_loss) 204 | val_psnr.append(val_epoch_psnr) 205 | val_ssim.append(val_epoch_ssim) 206 | if val_epoch_loss < vloss: 207 | print("Save model...") 208 | torch.save({ 209 | 'epoch': epoch, 210 | 'model_state_dict': model.state_dict(), 211 | 'optimizer_state_dict': optimizer.state_dict(), 212 | 'losses': [train_epoch_loss, train_epoch_psnr, train_epoch_ssim,val_epoch_loss, val_epoch_psnr, val_epoch_ssim], 213 | }, model_name) 214 | losses_path = os.path.join("./Metrics",model_name) 215 | metrics = [train_loss,train_psnr,train_ssim,val_loss,val_psnr,val_ssim] 216 | np.save(losses_path,metrics) 217 | vloss = val_epoch_loss 218 | end = time.time() 219 | print(f"Finished training in: {((end-start)/60):.3f} minutes") 220 | 221 | if __name__ == '__main__': 222 | main() 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | -------------------------------------------------------------------------------- /Visualization benchmark/Thunmpy/Thunmixing.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import cv2 3 | 4 | def linear_unmixing(index, Temp, fit_param, iscale, mask=0): 5 | 6 | ''' 7 | This function takes two images (index and temperature) and the linear regression 8 | parameters on fit_param and applies $T = a1*I +a0$, where T is the unknown 9 | a1 and a0 are the regression parameter and I is the index image. 10 | 11 | ############## Inputs: 12 | 13 | index (fine scale): reflective index image, ex: NDVI, NDBI ... 14 | 15 | Temp (large scale): Temperature image. the background pixels must be == 0 16 | 17 | fit_param: Fit parameters from linear model 18 | 19 | iscale: Ratio between coarse and fine scale. For coarse =40m and fine =20 iscale =2 20 | 21 | mask (fine scale): mask image to put the unmixed temperature values at zero where mask==0. 22 | If mask=0 then the mask is built in this script using the 23 | coarse temperature. 24 | 25 | ############## Outputs: 26 | 27 | T_unm= Sharpened LST image 28 | ## 29 | ### 30 | ###### 31 | ########### 32 | ############### C. Granero-Belinchon (IMT Atlantique) and A. Michel (Onera); Brest; 12/2021 33 | ''' 34 | 35 | (rows,cols)=index.shape 36 | 37 | ## Param[1] indicates that we call the intercept of the linear regression. 38 | a0 = fit_param[1] 39 | ## Param[0] indicates that we call the slope of the linear regression. 40 | a1 = fit_param[0] 41 | 42 | T_unm = a0 + a1*index 43 | 44 | ### MASK 45 | 46 | if mask == 0: 47 | maskt=cv2.resize(Temp, (T_unm.shape[1],T_unm.shape[0]), interpolation = cv2.INTER_NEAREST) 48 | maskt[maskt!=0]=1 49 | T_unm=T_unm*maskt 50 | else: 51 | zz=np.where(mask==0) 52 | T_unm[zz]=0 53 | 54 | 55 | print('Unmixing Done') 56 | 57 | return T_unm 58 | 59 | 60 | def bilinear_unmixing(index1, index2, Temp, fit_param, iscale, mask=0): 61 | 62 | ''' 63 | This function takes three images (path_index1, path_index2 and path_temperature) and 64 | the bilinear regression parameters on the text file (path_fit) and applies 65 | $T = a1*I1 +a2*I2 +a0$, where T is the unknown, a2, a1 and a0 are the regression 66 | parameters and I1 and I2 are the index images. 67 | 68 | ############## Inputs: 69 | 70 | index1 (fine scale): reflective index image, ex: NDVI, NDBI ... 71 | 72 | index2 (fine scale): reflective index image, ex: NDVI, NDBI ... 73 | 74 | Temp (coarse scale): Temperature image. the background pixels must be == 0 75 | 76 | fit_param: Fit parameters from linear model 77 | 78 | iscale: Ratio between coarse and fine scale. For coarse =40m and fine =20 iscale =2 79 | 80 | mask (fine scale): mask image to put the temperature values at zero where mask==0. 81 | If mask=0 then the mask is built in this script using the 82 | coarse temperature. 83 | 84 | ############## Outputs: 85 | 86 | T_unm: Sharpened temperature 87 | ## 88 | ### 89 | ###### 90 | ########### 91 | ############### C. Granero-Belinchon; IMT Atlantique, Brest; 12/2021 92 | ''' 93 | 94 | (rows,cols)=index1.shape 95 | 96 | ## Param[0] indicates that we call the intercept of the linear regression. 97 | p0 = fit_param[0] 98 | ## Param[1] (Param[2]) indicate that we call the slopes of the linear regression. 99 | p1 = fit_param[1] 100 | p2 = fit_param[2] 101 | 102 | T_unm = p0 + p1*index1 + p2*index2 103 | 104 | if mask == 0: 105 | maskt=cv2.resize(Temp, (T_unm.shape[1],T_unm.shape[0]), interpolation = cv2.INTER_NEAREST) 106 | maskt[maskt!=0]=1 107 | T_unm=T_unm*maskt 108 | else: 109 | zz=np.where(mask==0) 110 | T_unm[zz]=0 111 | 112 | print('Unmixing Done') 113 | 114 | return T_unm 115 | 116 | def linear_unmixing_byclass(index, Temp, Class, fit_param, iscale, mask=0): 117 | 118 | 119 | ''' 120 | This function takes three images (path_index, path_class and path_temperature) and 121 | the linear regression parameters for each class defined in path_class on the 122 | text file (path_fit) and applies $T = a1*I +a0$, where T is the unknown, a1 and 123 | a0 are the regression parameters (different for each class) and I the index image. 124 | 125 | ############## Inputs: 126 | 127 | index (fine scale): reflective index image, ex: NDVI, NDBI ... 128 | 129 | Temp (coarse scale): Temperature image. the background pixels must be == 0 130 | 131 | Class : classification image. Background pixels =0 132 | 133 | fit_param: Fit parameters from linear model per class 134 | 135 | iscale: Ratio between coarse and fine scale. For coarse =40m and fine =20 iscale =2 136 | 137 | mask (fine scale): mask image to put the temperature values at zero where mask==0. 138 | If mask=0 then the mask is built in this script using the 139 | coarse temperature. 140 | 141 | ############## Outputs: 142 | 143 | T_unm: Sharpened temperature 144 | 145 | ### 146 | ###### 147 | ########### 148 | ############### C. Granero-Belinchon; IMT Atlantique, Brest; 12/2021 149 | ''' 150 | 151 | (rows,cols)=index.shape 152 | 153 | # We obtain the number of classes and the value characterizing each class 154 | val_class = np.unique(Class) 155 | val_class = val_class[np.nonzero(val_class)] 156 | num_class = len(val_class) 157 | 158 | # We initialize the image result 159 | T_unm=np.zeros((rows,cols)) 160 | 161 | for i in range(num_class): 162 | ## Param[1] indicates that we call the intercept of the linear regression. 163 | a0 = fit_param[1,i] 164 | ## Param[0] indicates that we call the slope of the linear regression. 165 | a1 = fit_param[0,i] 166 | 167 | for icol in range(cols): 168 | for irow in range(rows): 169 | if Class[irow,icol] == val_class[i]: 170 | T_unm[irow,icol] = a0 + a1*index[irow,icol] 171 | 172 | ### MASK 173 | 174 | if mask == 0: 175 | maskt=cv2.resize(Temp, (T_unm.shape[1],T_unm.shape[0]), interpolation = cv2.INTER_NEAREST) 176 | maskt[maskt!=0]=1 177 | T_unm=T_unm*maskt 178 | else: 179 | zz=np.where(mask==0) 180 | T_unm[zz]=0 181 | 182 | print('Unmixing Done') 183 | 184 | return T_unm 185 | 186 | # Multivariate 4th Order Polynomial regression (HUTS) 187 | def huts(x,p1,p2,p3,p4,p5,p6,p7,p8,p9,p10,p11,p12,p13,p14,p15): 188 | f = p1*x[0,:,:]**4 + p2*x[0,:,:]**3*x[1,:,:] + p3*x[0,:,:]**2*x[1,:,:]**2 + p4*x[0,:,:]*x[1,:,:]**3 + p5*x[1,:,:]**4 +p6*x[0,:,:]**3 + p7*x[0,:,:]**2*x[1,:,:] + p8*x[0,:,:]*x[1,:,:]**2 + p9*x[1,:,:]**3 + p10*x[0,:,:]**2 + p11*x[0,:,:]*x[1,:,:] + p12*x[1,:,:]**2 + p13*x[0,:,:] + p14*x[1,:,:] + p15 189 | return f 190 | 191 | def huts_unmixing(index, albedo, Temp, Param, iscale, mask=0): 192 | 193 | ''' 194 | This function takes three images (path_index, path_albedo and path_temperature) and 195 | the HUTS regression parameters on the text file (path_fit) and applies 196 | the HUTS formula, where T is the unknown, p0, p1 ... p14 are the regression 197 | parameters and I and \alpha are the index and albedo images respectively. 198 | 199 | ############## Inputs: 200 | 201 | index (fine scale): reflective index image, ex: NDVI, NDBI ... 202 | 203 | albedo (fine scale): reflective albedo 204 | 205 | Temp (coarse scale): Temperature image. the background pixels must be == 0 206 | 207 | Class : classification image. Background pixels =0 208 | 209 | fit_param: Fit parameters from linear model per class 210 | 211 | iscale: Ratio between coarse and fine scale. For coarse =40m and fine =20 iscale =2 212 | 213 | mask (fine scale): mask image to put the temperature values at zero where mask==0. 214 | If mask=0 then the mask is built in this script using the 215 | coarse temperature. 216 | 217 | ############## Outputs: 218 | 219 | T_unm: Sharpened temperature 220 | 221 | ### 222 | ###### 223 | ########### 224 | ############### C. Granero-Belinchon; IMT Atlantique, Brest; 12/2021 225 | ''' 226 | 227 | (rows,cols)=index.shape 228 | 229 | xdata = np.zeros((2,len(index),len(index[0]))) 230 | xdata[0,:,:] = index 231 | xdata[1,:,:] = albedo 232 | 233 | T_unm = huts(xdata,Param[0],Param[1],Param[2],Param[3],Param[4],Param[5],Param[6],Param[7],Param[8],Param[9],Param[10],Param[11],Param[12],Param[13],Param[14]) 234 | 235 | ### MASK 236 | 237 | if mask == 0: 238 | maskt=cv2.resize(Temp, (T_unm.shape[1],T_unm.shape[0]), interpolation = cv2.INTER_NEAREST) 239 | maskt[maskt!=0]=1 240 | T_unm=T_unm*maskt 241 | else: 242 | zz=np.where(mask==0) 243 | T_unm[zz]=0 244 | 245 | print('Unmixing Done') 246 | 247 | return T_unm 248 | 249 | def aatprk_unmixing(index, Temp, Slope_Intercept, iscale): 250 | 251 | ''' 252 | This function takes two images (path_index and path_temperature) and the linear regression 253 | parameters on the text file (path_fit) and applies $T = a1*I +a0$, where T is the unknown 254 | a1 and a0 are the regression parameter and I is the index image. 255 | 256 | ############## Inputs/Outputs: 257 | 258 | index (fine scale): reflective index image, ex: NDVI, NDBI ... 259 | 260 | Temp (coarse scale): Temperature image. the background pixels must be == 0 261 | 262 | Slope_Intercept: Image of dimension [2,:,:] containing Slope and Intercept images. 263 | 264 | iscale: Ratio between coarse and fine scale. For coarse =40m and fine =20 iscale =2 265 | 266 | ############## Outputs: 267 | 268 | T_unm: Sharpened temperature 269 | 270 | ### 271 | ###### 272 | ########### 273 | ############### C. Granero-Belinchon; IMT Atlantique, Brest; 12/2021 274 | ''' 275 | 276 | (rows,cols)=index.shape 277 | 278 | I_mask=np.greater(np.absolute(index),0.0000000000) # We eliminate the background pixels (not in the image) 279 | T_unm=np.zeros((rows,cols)) 280 | for ic in range(int(np.floor(cols/iscale))): 281 | for ir in range(int(np.floor(rows/iscale))): 282 | for ir_2 in range(ir*iscale,(ir*iscale+iscale)): 283 | for ic_2 in range(ic*iscale,(ic*iscale+iscale)): 284 | if I_mask[ir_2,ic_2] == False: 285 | T_unm[ir_2,ic_2]=0 286 | else: 287 | T_unm[ir_2,ic_2] = Slope_Intercept[1,ir,ic] + Slope_Intercept[0,ir,ic] * index[ir_2,ic_2] 288 | 289 | 290 | print('Unmixing Done') 291 | 292 | return T_unm -------------------------------------------------------------------------------- /Visualization benchmark/Thunmpy/Methods.py: -------------------------------------------------------------------------------- 1 | ''' 2 | ################ Needed packages #### 3 | numpy 4 | gdal 5 | Thunmpy 6 | ''' 7 | import numpy as np 8 | from osgeo import gdal 9 | from Thunmpy import ThunmFit 10 | from Thunmpy import Thunmixing 11 | from Thunmpy import Thunmcorr 12 | 13 | def TsHARP(T_C, I_C, I_H, iscale, min_T=285, path_image=False): 14 | 15 | ''' 16 | This function applies TsHARP method to sharpen T_C with Index I_C and I_H. 17 | 18 | ############## Inputs: 19 | 20 | T_C: path to LST image at coarse resolution 21 | 22 | I_C: path to Index image at coarse resolution 23 | 24 | I_H: path to Index image at high resolution 25 | 26 | iscale: factor of upscaling. For example from 100m to 20m iscale=5 27 | 28 | min_T (optional): Minimum expected temperature in the image. All the temerpature 29 | below this one are considered spurious measures and not taken into account in the 30 | regression. 31 | 32 | path_image (optional): path with the folder and filename where saving the sharpened LST image. 33 | 34 | ############## Outputs: 35 | 36 | T_H_corrected : corrected LST at high resolution 37 | 38 | # 39 | ### 40 | ###### 41 | ########### 42 | ############### C. Granero-Belinchon; IMT Atlantique, Brest; 02/2022 43 | ''' 44 | 45 | # Reading index at coarse resolution 46 | filename_index = I_C 47 | dataset = gdal.Open(filename_index) 48 | cols = dataset.RasterXSize #Spatial dimension x 49 | rows = dataset.RasterYSize #Spatial dimension y 50 | index_c = dataset.ReadAsArray(0, 0, cols, rows).astype(np.float) 51 | 52 | # Reading temperature at coarse resolution 53 | TempFile = T_C 54 | dataset_Temp = gdal.Open(TempFile) 55 | cols_t = dataset_Temp.RasterXSize #Spatial dimension x 56 | rows_t = dataset_Temp.RasterYSize #Spatial dimension y 57 | Temp_c= dataset_Temp.ReadAsArray(0, 0, cols_t, rows_t).astype(np.float) 58 | 59 | # Reading index at high resolution 60 | filename_index = I_H 61 | dataset = gdal.Open(filename_index) 62 | cols = dataset.RasterXSize #Spatial dimension x 63 | rows = dataset.RasterYSize #Spatial dimension y 64 | projection_h = dataset.GetProjection() 65 | geotransform_h = dataset.GetGeoTransform() 66 | index_h = dataset.ReadAsArray(0, 0, cols, rows).astype(np.float) 67 | 68 | 69 | fit=ThunmFit.linear_fit(index_c, Temp_c, min_T, path_fit=False, path_plot=False) 70 | T_H= Thunmixing.linear_unmixing(index_h, Temp_c, fit, iscale, mask=0) 71 | T_H_corrected=Thunmcorr.correction_linreg(index_c, Temp_c, T_H, iscale, fit) 72 | 73 | if path_image!=False: 74 | driver = gdal.GetDriverByName("ENVI") 75 | outDs = driver.Create(path_image, int(cols), int(rows), 1, gdal.GDT_Float32) 76 | outDs.SetProjection(projection_h) 77 | outDs.SetGeoTransform(geotransform_h) 78 | outBand = outDs.GetRasterBand(1) 79 | outBand.WriteArray(T_H_corrected, 0, 0) 80 | outBand.FlushCache() 81 | outDs = None 82 | 83 | print('TsHARP Done') 84 | 85 | return T_H_corrected 86 | 87 | def HUTS(T_C, I_C, I_H, A_C, A_H, iscale, min_T=285, min_threshold=263, max_threshold=323, p=False, path_image=False): 88 | 89 | ''' 90 | This function applies HUTS method to sharpen T_C with Index I_C, A_C and I_H, A_H. 91 | 92 | ############## Inputs: 93 | 94 | T_C: path to LST image at coarse resolution 95 | 96 | I_C: path to Index image at coarse resolution 97 | 98 | I_H: path to Index image at high resolution 99 | 100 | A_C: path to Albedo (VNIR-SWIR Index) image at coarse resolution 101 | 102 | A_H: path to Albedo (VNIR-SWIR Index) image at high resolution 103 | 104 | iscale: factor of upscaling. For example from 100m to 20m iscale=5 105 | 106 | min_T (optional): Minimum expected temperature in the image. All the temerpature 107 | below this one are considered spurious measures and not taken into account in the 108 | regression. 109 | 110 | max_threshold and min_threshold (optional): maximum and minimum LSTs taken into account in the residual correction step. 111 | 112 | p : initial guess for fit parameters 113 | 114 | path_image (optional): path with the folder and filename where saving the sharpened LST image. 115 | 116 | ############## Outputs: 117 | 118 | T_H_corrected : corrected LST at high resolution 119 | 120 | # 121 | ### 122 | ###### 123 | ########### 124 | ############### C. Granero-Belinchon; IMT Atlantique, Brest; 02/2022 125 | ''' 126 | 127 | # Reading index at coarse resolution 128 | filename_index = I_C 129 | dataset = gdal.Open(filename_index) 130 | cols = dataset.RasterXSize #Spatial dimension x 131 | rows = dataset.RasterYSize #Spatial dimension y 132 | index_c = dataset.ReadAsArray(0, 0, cols, rows).astype(np.float) 133 | 134 | # Reading temperature at coarse resolution 135 | TempFile = T_C 136 | dataset_Temp = gdal.Open(TempFile) 137 | cols_t = dataset_Temp.RasterXSize #Spatial dimension x 138 | rows_t = dataset_Temp.RasterYSize #Spatial dimension y 139 | Temp_c= dataset_Temp.ReadAsArray(0, 0, cols_t, rows_t).astype(np.float) 140 | 141 | # Reading index at high resolution 142 | filename_index = I_H 143 | dataset = gdal.Open(filename_index) 144 | cols = dataset.RasterXSize #Spatial dimension x 145 | rows = dataset.RasterYSize #Spatial dimension y 146 | projection_h = dataset.GetProjection() 147 | geotransform_h = dataset.GetGeoTransform() 148 | index_h = dataset.ReadAsArray(0, 0, cols, rows).astype(np.float) 149 | 150 | # Reading albedo at coarse resolution 151 | filename_albedo = A_C 152 | dataset_alb = gdal.Open(filename_albedo) 153 | cols = dataset_alb.RasterXSize #Spatial dimension x 154 | rows = dataset_alb.RasterYSize #Spatial dimension y 155 | albedo_c = dataset_alb.ReadAsArray(0, 0, cols, rows).astype(np.float) 156 | 157 | # Reading index at high resolution 158 | filename_albedo = A_H 159 | dataset_alb = gdal.Open(filename_albedo) 160 | cols = dataset_alb.RasterXSize #Spatial dimension x 161 | rows = dataset_alb.RasterYSize #Spatial dimension y 162 | albedo_h = dataset_alb.ReadAsArray(0, 0, cols, rows).astype(np.float) 163 | 164 | fit=ThunmFit.huts_fit(index_c, albedo_c, Temp_c, min_T, p, path_fit=False) 165 | T_H= Thunmixing.huts_unmixing(index_h, albedo_h, Temp_c, fit, iscale, mask=0) 166 | T_H_corrected=Thunmcorr.quality_correction(Temp_c, T_H, iscale, max_threshold=max_threshold, min_threshold=min_threshold) 167 | 168 | if path_image!=False: 169 | driver = gdal.GetDriverByName("ENVI") 170 | outDs = driver.Create(path_image, int(cols), int(rows), 1, gdal.GDT_Float32) 171 | outDs.SetProjection(projection_h) 172 | outDs.SetGeoTransform(geotransform_h) 173 | outBand = outDs.GetRasterBand(1) 174 | outBand.WriteArray(T_H_corrected, 0, 0) 175 | outBand.FlushCache() 176 | outDs = None 177 | 178 | print('HUTS Done') 179 | 180 | return T_H_corrected 181 | 182 | def ATPRK(T_C, I_C, I_H, iscale, scc, block_size=5, sill=7, ran=1000, min_T=285, path_image=False): 183 | 184 | ''' 185 | This function applies ATPRK method to sharpen T_C with Index I_C and I_H. 186 | 187 | ############## Inputs: 188 | 189 | T_C: path to LST image at coarse resolution 190 | 191 | I_C: path to Index image at coarse resolution 192 | 193 | I_H: path to Index image at high resolution 194 | 195 | iscale: factor of upscaling. For example from 100m to 20m iscale=5 196 | 197 | scc: The coarse pixel has a size scc * scc 198 | 199 | block_size: The moving window used to compute the semivariograms and the weights in the 200 | ATPK procedures has a size block_size * block_size 201 | 202 | sill and ran are the parameters defining the shape of the semivariogram. 203 | 204 | min_T (optional): Minimum expected temperature in the image. All the temerpature 205 | below this one are considered spurious measures and not taken into account in the 206 | regression. 207 | 208 | path_image (optional): path with the folder and filename where saving the sharpened LST image. 209 | 210 | ############## Outputs: 211 | 212 | T_H_corrected : corrected LST at high resolution 213 | 214 | # 215 | ### 216 | ###### 217 | ########### 218 | ############### C. Granero-Belinchon; IMT Atlantique, Brest and A. Michel; ONERA-DOTA, Toulouse; 02/2022 219 | ''' 220 | 221 | # Reading index at coarse resolution 222 | filename_index = I_C 223 | dataset = gdal.Open(filename_index) 224 | cols = dataset.RasterXSize #Spatial dimension x 225 | rows = dataset.RasterYSize #Spatial dimension y 226 | index_c = dataset.ReadAsArray(0, 0, cols, rows).astype(np.float) 227 | 228 | # Reading temperature at coarse resolution 229 | TempFile = T_C 230 | dataset_Temp = gdal.Open(TempFile) 231 | cols_t = dataset_Temp.RasterXSize #Spatial dimension x 232 | rows_t = dataset_Temp.RasterYSize #Spatial dimension y 233 | Temp_c= dataset_Temp.ReadAsArray(0, 0, cols_t, rows_t).astype(np.float) 234 | 235 | # Reading index at high resolution 236 | filename_index = I_H 237 | dataset = gdal.Open(filename_index) 238 | cols = dataset.RasterXSize #Spatial dimension x 239 | rows = dataset.RasterYSize #Spatial dimension y 240 | projection_h = dataset.GetProjection() 241 | geotransform_h = dataset.GetGeoTransform() 242 | index_h = dataset.ReadAsArray(0, 0, cols, rows).astype(np.float) 243 | 244 | fit=ThunmFit.linear_fit(index_c, Temp_c, min_T, path_fit=False, path_plot=False) 245 | T_H= Thunmixing.linear_unmixing(index_h, Temp_c, fit, iscale, mask=0) 246 | T_H_corrected=Thunmcorr.correction_ATPRK(index_c, Temp_c, T_H, fit, iscale, scc, block_size, sill, ran, path_plot=False) 247 | 248 | if path_image!=False: 249 | driver = gdal.GetDriverByName("ENVI") 250 | outDs = driver.Create(path_image, int(cols), int(rows), 1, gdal.GDT_Float32) 251 | outDs.SetProjection(projection_h) 252 | outDs.SetGeoTransform(geotransform_h) 253 | outBand = outDs.GetRasterBand(1) 254 | outBand.WriteArray(T_H_corrected, 0, 0) 255 | outBand.FlushCache() 256 | outDs = None 257 | 258 | print('ATPRK Done') 259 | 260 | return T_H_corrected 261 | 262 | def AATPRK(T_C, I_C, I_H, iscale, scc, b_radius=2, block_size=5, sill=7, ran=1000, min_T=285, path_image=False): 263 | 264 | ''' 265 | This function applies AATPRK method to sharpen T_C with Index I_C and I_H. 266 | 267 | ############## Inputs: 268 | 269 | T_C: path to LST image at coarse resolution 270 | 271 | I_C: path to Index image at coarse resolution 272 | 273 | I_H: path to Index image at high resolution 274 | 275 | iscale: factor of upscaling. For example from 100m to 20m iscale=5 276 | 277 | scc: The coarse pixel has a size scc * scc 278 | 279 | block_size: The moving window used to compute the semivariograms and the weights in the 280 | ATPK procedures has a size block_size * block_size 281 | 282 | sill and ran are the parameters defining the shape of the semivariogram. 283 | 284 | min_T (optional): Minimum expected temperature in the image. All the temerpature 285 | below this one are considered spurious measures and not taken into account in the 286 | regression. 287 | 288 | path_image (optional): path with the folder and filename where saving the sharpened LST image. 289 | 290 | ############## Outputs: 291 | 292 | T_H_corrected : corrected LST at high resolution 293 | 294 | # 295 | ### 296 | ###### 297 | ########### 298 | ############### C. Granero-Belinchon; IMT Atlantique, Brest and A. Michel; ONERA-DOTA, Toulouse; 02/2022 299 | ''' 300 | 301 | # Reading index at coarse resolution 302 | filename_index = I_C 303 | dataset = gdal.Open(filename_index) 304 | cols = dataset.RasterXSize #Spatial dimension x 305 | rows = dataset.RasterYSize #Spatial dimension y 306 | index_c = dataset.ReadAsArray(0, 0, cols, rows).astype(np.float) 307 | 308 | # Reading temperature at coarse resolution 309 | TempFile = T_C 310 | dataset_Temp = gdal.Open(TempFile) 311 | cols_t = dataset_Temp.RasterXSize #Spatial dimension x 312 | rows_t = dataset_Temp.RasterYSize #Spatial dimension y 313 | Temp_c= dataset_Temp.ReadAsArray(0, 0, cols_t, rows_t).astype(np.float) 314 | 315 | # Reading index at high resolution 316 | filename_index = I_H 317 | dataset = gdal.Open(filename_index) 318 | cols = dataset.RasterXSize #Spatial dimension x 319 | rows = dataset.RasterYSize #Spatial dimension y 320 | projection_h = dataset.GetProjection() 321 | geotransform_h = dataset.GetGeoTransform() 322 | index_h = dataset.ReadAsArray(0, 0, cols, rows).astype(np.float) 323 | 324 | Intercept, Slope =ThunmFit.linear_fit_window(index_c, Temp_c, min_T, b_radius) 325 | T_H= Thunmixing.aatprk_unmixing(index_h, Temp_c, np.asarray([Slope,Intercept]), iscale) 326 | T_H_corrected=Thunmcorr.correction_AATPRK(index_c, Temp_c, T_H, np.asarray([Slope,Intercept]), iscale, scc, block_size, sill, ran, path_plot=False) 327 | 328 | if path_image!=False: 329 | driver = gdal.GetDriverByName("ENVI") 330 | outDs = driver.Create(path_image, int(cols), int(rows), 1, gdal.GDT_Float32) 331 | outDs.SetProjection(projection_h) 332 | outDs.SetGeoTransform(geotransform_h) 333 | outBand = outDs.GetRasterBand(1) 334 | outBand.WriteArray(T_H_corrected, 0, 0) 335 | outBand.FlushCache() 336 | outDs = None 337 | 338 | print('AATPRK Done') 339 | 340 | return T_H_corrected 341 | -------------------------------------------------------------------------------- /model.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import cv2 3 | import torch.optim as optim 4 | import torch.nn as nn 5 | import numpy as np 6 | import math 7 | from torchvision.utils import save_image 8 | from torchvision import transforms 9 | import torchvision.models as models 10 | import torch.utils.model_zoo as model_zoo 11 | from collections import OrderedDict 12 | import os 13 | import torch.nn.functional as F 14 | 15 | # VDSR and DMCN model 16 | 17 | from math import sqrt 18 | 19 | import torch.nn.init as init 20 | 21 | class Conv_ReLU_Block(nn.Module): 22 | def __init__(self): 23 | super(Conv_ReLU_Block, self).__init__() 24 | self.conv = nn.Conv2d(in_channels=64, out_channels=64, kernel_size=3, stride=1, padding=1, bias=False) 25 | self.relu = nn.ReLU(inplace=True) 26 | 27 | def forward(self, x): 28 | return self.relu(self.conv(x)) 29 | 30 | class VDSR(nn.Module): 31 | def __init__(self): 32 | super(VDSR, self).__init__() 33 | self.residual_layer = self.make_layer(Conv_ReLU_Block, 18) 34 | self.input = nn.Conv2d(in_channels=1, out_channels=64, kernel_size=3, stride=1, padding=1, bias=False) 35 | self.output = nn.Conv2d(in_channels=64, out_channels=1, kernel_size=3, stride=1, padding=1, bias=False) 36 | self.relu = nn.ReLU(inplace=True) 37 | 38 | for m in self.modules(): 39 | if isinstance(m, nn.Conv2d): 40 | n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels 41 | m.weight.data.normal_(0, sqrt(2. / n)) 42 | 43 | def make_layer(self, block, num_of_layer): 44 | layers = [] 45 | for _ in range(num_of_layer): 46 | layers.append(block()) 47 | return nn.Sequential(*layers) 48 | 49 | def forward(self, x): 50 | residual = x 51 | out = self.relu(self.input(x)) 52 | out = self.residual_layer(out) 53 | out = self.output(out) 54 | out = torch.add(out,residual) 55 | return out 56 | 57 | num = 64 58 | 59 | class DwSample(nn.Module): 60 | def __init__(self, inp, oup, stride, kernal_size = 3, groups=1, BN = False): 61 | super(DwSample, self).__init__() 62 | if BN == True: 63 | self.conv_dw = nn.Sequential( 64 | nn.Conv2d(inp, oup, kernal_size, stride, int((kernal_size - 1) / 2), groups=groups), 65 | nn.BatchNorm2d(oup), 66 | nn.PReLU(), 67 | ) 68 | else: 69 | self.conv_dw = nn.Sequential( 70 | nn.Conv2d(inp, oup, kernal_size, stride, int((kernal_size-1)/2), groups=groups), 71 | nn.PReLU(), 72 | ) 73 | 74 | def forward(self, x): 75 | residual = x 76 | out = self.conv_dw(x) 77 | return torch.add(out, residual) 78 | 79 | class BasicBlock(nn.Module): 80 | def __init__(self, inp, oup, stride, kernal_size=3, groups=1, BN = False): 81 | super(BasicBlock, self).__init__() 82 | if BN == True: 83 | self.conv_dw = nn.Sequential( 84 | nn.Conv2d(inp, oup, kernal_size, stride, int((kernal_size - 1) / 2), groups=groups), 85 | nn.BatchNorm2d(oup), 86 | nn.PReLU(), 87 | nn.Conv2d(oup, inp, kernal_size, stride, int((kernal_size - 1) / 2), groups=groups), 88 | ) 89 | else: 90 | self.conv_dw = nn.Sequential( 91 | nn.Conv2d(inp, oup, kernal_size, stride, int((kernal_size - 1) / 2), groups=groups), 92 | nn.PReLU(), 93 | nn.Conv2d(oup, inp, kernal_size, stride, int((kernal_size - 1) / 2), groups=groups), 94 | ) 95 | def forward(self, x): 96 | residual = x 97 | return torch.add(self.conv_dw(x), residual) 98 | 99 | class UpSample(nn.Module): 100 | def __init__(self, f, upscale_factor): 101 | super(UpSample, self).__init__() 102 | 103 | self.relu = nn.PReLU() 104 | self.conv = nn.Conv2d(f, f * (upscale_factor ** 2), (3, 3), (1, 1), (1, 1)) 105 | self.pixel_shuffle = nn.PixelShuffle(upscale_factor) 106 | 107 | def forward(self, x): 108 | x = self.relu(self.conv(x)) 109 | x = self.pixel_shuffle(x) 110 | return x 111 | 112 | class DMCN_prelu(nn.Module): 113 | def __init__(self, BN=True, width = 64): 114 | super(DMCN_prelu, self).__init__() 115 | self.input1 = nn.Conv2d(in_channels=1, out_channels=width, kernel_size=3, stride=1, padding=1, bias=False) 116 | self.input2 = nn.Conv2d(in_channels=width, out_channels=width, kernel_size=3, stride=1, padding=1, bias=False) 117 | self.BN1 = nn.BatchNorm2d(width) 118 | self.input3 = nn.Conv2d(in_channels=width, out_channels=width, kernel_size=3, stride=1, padding=1, bias=False) 119 | self.BN2 = nn.BatchNorm2d(width) 120 | self.input4 = nn.Conv2d(in_channels=width, out_channels=width, kernel_size=3, stride=1, padding=1, bias=False) 121 | self.BN3 = nn.BatchNorm2d(width) 122 | self.input5 = nn.Conv2d(in_channels=width, out_channels=width, kernel_size=3, stride=1, padding=1, bias=False) 123 | self.BN4 = nn.BatchNorm2d(width) 124 | self.down_sample1 = nn.Conv2d(in_channels=width, out_channels=width, kernel_size=3, stride=2, padding=1, bias=False) 125 | self.Conv_DW_layers1 = self.make_layer(DwSample, 5, BN, width) 126 | 127 | self.down_sample2 = nn.Conv2d(in_channels=width, out_channels=width, kernel_size=3, stride=2, padding=1, bias=False) 128 | self.Conv_DW_layers2 = self.make_layer(DwSample, 2, BN, width) 129 | 130 | self.up_sample1 = UpSample(width,2) 131 | 132 | self.choose1 = nn.Conv2d(in_channels=width*2, out_channels=width, kernel_size=1, stride=1, padding=0, bias=False) 133 | self.resudial_layers1 = self.make_layer(BasicBlock, 2, BN, width) 134 | 135 | self.up_sample2 = UpSample(width,2) 136 | 137 | self.choose2 = nn.Conv2d(in_channels=width*2, out_channels=width, kernel_size=1, stride=1, padding=0, bias=False) 138 | self.resudial_layers2 = self.make_layer(BasicBlock, 5, BN, width) 139 | 140 | self.output = nn.Conv2d(in_channels=width, out_channels=1, kernel_size=3, stride=1, padding=1, bias=False) 141 | 142 | self.relu = nn.PReLU() 143 | 144 | def make_layer(self, block, num_of_layer, BN, width): 145 | layers = [] 146 | for _ in range(num_of_layer): 147 | layers.append(block(width, width, 1, 3, 1, BN)) 148 | return nn.Sequential(*layers) 149 | 150 | def forward(self, x): 151 | residual = x 152 | s1 = self.relu(self.input1(x)) 153 | s1 = self.input2(s1) 154 | s1 = self.relu(self.BN1(s1)) 155 | s1 = self.input3(s1) 156 | s1 = self.relu(self.BN2(s1)) 157 | s1 = self.input4(s1) 158 | s1 = self.relu(self.BN3(s1)) 159 | s1 = self.input5(s1) 160 | s1 = self.relu(self.BN4(s1)) 161 | out = self.down_sample1(s1) 162 | s2 = self.Conv_DW_layers1(out) 163 | 164 | out = self.down_sample2(s2) 165 | out = self.Conv_DW_layers2(out) 166 | 167 | out = self.up_sample1(out) 168 | out = torch.cat((s2, out), 1) 169 | out = self.choose1(out) 170 | out = self.resudial_layers1(out) 171 | 172 | out = self.up_sample2(out) 173 | out = torch.cat((s1, out), 1) 174 | out = self.choose2(out) 175 | out = self.resudial_layers2(out) 176 | 177 | out = self.output(out) 178 | out = torch.add(out, residual) 179 | return out 180 | 181 | 182 | """ Parts of the U-Net model """ 183 | 184 | class DoubleConv(nn.Module): 185 | """(convolution => [BN] => ReLU) * 2""" 186 | 187 | def __init__(self, in_channels, out_channels, mid_channels=None): 188 | super(DoubleConv, self).__init__() 189 | if not mid_channels: 190 | mid_channels = out_channels 191 | self.double_conv = nn.Sequential( 192 | nn.Conv2d(in_channels, mid_channels, kernel_size=3, padding=1), 193 | nn.BatchNorm2d(mid_channels), 194 | nn.ReLU(inplace=True), 195 | nn.Conv2d(mid_channels, out_channels, kernel_size=3, padding=1), 196 | nn.BatchNorm2d(out_channels), 197 | nn.ReLU(inplace=True) 198 | ) 199 | 200 | def forward(self, x): 201 | return self.double_conv(x) 202 | 203 | class DoubleConv_Down(nn.Module): 204 | """(convolution => [BN] => ReLU) * 2""" 205 | 206 | def __init__(self, in_channels, out_channels, mid_channels=None): 207 | super(DoubleConv_Down, self).__init__() 208 | if not mid_channels: 209 | mid_channels = out_channels 210 | self.double_conv = nn.Sequential( 211 | nn.Conv2d(in_channels, mid_channels, kernel_size=3, padding=1), 212 | nn.BatchNorm2d(mid_channels), 213 | nn.ReLU(inplace=True), 214 | nn.Conv2d(mid_channels, out_channels, kernel_size=3, padding=1), 215 | ) 216 | 217 | def forward(self, x): 218 | return self.double_conv(x) 219 | 220 | 221 | class Down(nn.Module): 222 | """Downscaling with maxpool then double conv""" 223 | 224 | def __init__(self, in_channels, out_channels, res_down=False): 225 | super(Down, self).__init__() 226 | self.res_down = res_down 227 | self.maxpool_conv = nn.Sequential( 228 | nn.MaxPool2d(2), 229 | DoubleConv(in_channels, out_channels) 230 | ) 231 | if self.res_down: 232 | self.in_conv = nn.Conv2d(in_channels, in_channels, kernel_size=2, stride=2) 233 | self.mid_conv = DoubleConv(in_channels, in_channels) 234 | self.out_conv = nn.Sequential( 235 | nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1), 236 | nn.BatchNorm2d(out_channels), 237 | nn.ReLU(inplace=True) 238 | ) 239 | 240 | def forward(self, x): 241 | if self.res_down: 242 | return self.out_conv(self.mid_conv((self.in_conv(x))) + self.in_conv(x)) 243 | 244 | else: 245 | return self.maxpool_conv(x) 246 | 247 | 248 | class Up(nn.Module): 249 | """Upscaling then double conv""" 250 | 251 | def __init__(self, in_channels, out_channels, bilinear=True): 252 | super(Up, self).__init__() 253 | 254 | # if bilinear, use the normal convolutions to reduce the number of channels 255 | if bilinear: 256 | self.up = nn.Upsample(scale_factor=2, mode='bilinear', align_corners=True) 257 | self.conv = DoubleConv(in_channels, out_channels, in_channels // 2) 258 | else: 259 | self.up = nn.ConvTranspose2d(in_channels, in_channels // 2, kernel_size=2, stride=2) 260 | self.conv = DoubleConv(in_channels, out_channels) 261 | 262 | def forward(self, x1, x2): 263 | x1 = self.up(x1) 264 | # input is CHW 265 | diffY = x2.size()[2] - x1.size()[2] 266 | diffX = x2.size()[3] - x1.size()[3] 267 | 268 | x1 = F.pad(x1, [diffX // 2, diffX - diffX // 2, 269 | diffY // 2, diffY - diffY // 2],mode='reflect') 270 | # if you have padding issues, see 271 | # https://github.com/HaiyongJiang/U-Net-Pytorch-Unstructured-Buggy/commit/0e854509c2cea854e247a9c615f175f76fbb2e3a 272 | # https://github.com/xiaopeng-liao/Pytorch-UNet/commit/8ebac70e633bac59fc22bb5195e513d5832fb3bd 273 | x = torch.cat([x2, x1], dim=1) 274 | return self.conv(x) 275 | 276 | class OutConv(nn.Module): 277 | def __init__(self, in_channels, out_channels): 278 | super(OutConv, self).__init__() 279 | self.conv = nn.Conv2d(in_channels, out_channels, kernel_size=1) 280 | # self.conv = nn.Sequential( 281 | # nn.Conv2d(in_channels, out_channels, kernel_size=1), 282 | # nn.Sigmoid()) 283 | 284 | def forward(self, x): 285 | return self.conv(x) 286 | 287 | class ResnetBlock(nn.Module): 288 | """Define a Resnet block""" 289 | 290 | def __init__(self, dim, padding_type, norm_layer, use_dropout, use_bias): 291 | """Initialize the Resnet block 292 | A resnet block is a conv block with skip connections 293 | We construct a conv block with build_conv_block function, 294 | and implement skip connections in function. 295 | Original Resnet paper: https://arxiv.org/pdf/1512.03385.pdf 296 | """ 297 | super(ResnetBlock, self).__init__() 298 | self.conv_block = self.build_conv_block(dim, padding_type, norm_layer, use_dropout, use_bias) 299 | 300 | def build_conv_block(self, dim, padding_type, norm_layer, use_dropout, use_bias): 301 | """Construct a convolutional block. 302 | Parameters: 303 | dim (int) -- the number of channels in the conv layer. 304 | padding_type (str) -- the name of padding layer: reflect | replicate | zero 305 | norm_layer -- normalization layer 306 | use_dropout (bool) -- if use dropout layers. 307 | use_bias (bool) -- if the conv layer uses bias or not 308 | Returns a conv block (with a conv layer, a normalization layer, and a non-linearity layer (ReLU)) 309 | """ 310 | conv_block = [] 311 | p = 0 312 | if padding_type == 'reflect': 313 | conv_block += [nn.ReflectionPad2d(1)] 314 | elif padding_type == 'replicate': 315 | conv_block += [nn.ReplicationPad2d(1)] 316 | elif padding_type == 'zero': 317 | p = 1 318 | else: 319 | raise NotImplementedError('padding [%s] is not implemented' % padding_type) 320 | 321 | conv_block += [nn.Conv2d(dim, dim, kernel_size=3, padding=p, bias=use_bias), norm_layer(dim), nn.ReLU(True)] 322 | if use_dropout: 323 | conv_block += [nn.Dropout(0.5)] 324 | 325 | p = 0 326 | if padding_type == 'reflect': 327 | conv_block += [nn.ReflectionPad2d(1)] 328 | elif padding_type == 'replicate': 329 | conv_block += [nn.ReplicationPad2d(1)] 330 | elif padding_type == 'zero': 331 | p = 1 332 | else: 333 | raise NotImplementedError('padding [%s] is not implemented' % padding_type) 334 | conv_block += [nn.Conv2d(dim, dim, kernel_size=3, padding=p, bias=use_bias), norm_layer(dim)] 335 | 336 | return nn.Sequential(*conv_block) 337 | 338 | def forward(self, x): 339 | """Forward function (with skip connections)""" 340 | out = x + self.conv_block(x) # add skip connections 341 | return out 342 | 343 | class MRUNet(nn.Module): 344 | def __init__(self, n_channels=1, n_classes=1, res_down=False, n_resblocks=1, padding_type="reflect", norm_layer=nn.BatchNorm2d, use_dropout=False, use_bias=True, bilinear=False): 345 | super(MRUNet, self).__init__() 346 | self.n_channels = n_channels 347 | self.n_classes = n_classes 348 | self.bilinear = bilinear 349 | 350 | self.inc = DoubleConv(n_channels, 64) 351 | 352 | ### Encoder 353 | self.down1 = Down(64, 128, res_down=res_down) 354 | self.down2 = Down(128, 256, res_down=res_down) 355 | self.down3 = Down(256, 512, res_down=res_down) 356 | factor = 2 if bilinear else 1 357 | self.down4 = Down(512, 1024 // factor, res_down=res_down) 358 | 359 | ### Residual blocks 360 | resblocks = [] 361 | for i in range(n_resblocks): 362 | resblocks += [ResnetBlock(1024 // factor, padding_type, norm_layer, use_dropout, use_bias)] 363 | self.resblocks = nn.Sequential(*resblocks) 364 | 365 | ### Decoder 366 | self.up1 = Up(1024, 512 // factor, bilinear) 367 | self.up2 = Up(512, 256 // factor, bilinear) 368 | self.up3 = Up(256, 128 // factor, bilinear) 369 | self.up4 = Up(128, 64, bilinear) 370 | self.outc = OutConv(64, n_classes) 371 | self.up = nn.ConvTranspose2d(64, 64, kernel_size=2, stride=2) 372 | self.up_dc = DoubleConv(64, 64) 373 | self.drop = nn.Dropout(p=0.3) 374 | 375 | def forward(self, x): 376 | x1 = self.inc(x) #64 377 | x2 = self.down1(x1) #128 378 | x3 = self.down2(x2) #256 379 | x4 = self.down3(x3) #512 380 | x5 = self.down4(x4) #1024 381 | 382 | x5 = self.resblocks(x5) 383 | 384 | xp1 = self.up1(x5, x4) #512 385 | xp2 = self.up2(xp1, x3) #256 386 | xp3 = self.up3(xp2, x2) #128 387 | xp4 = self.up4(xp3, x1) #64 388 | # x = self.up(x) 389 | # x = self.up_dc(x) 390 | logits = self.outc(xp4)+x 391 | return logits -------------------------------------------------------------------------------- /Visualization benchmark/Thunmpy/ThunmFit.py: -------------------------------------------------------------------------------- 1 | ''' 2 | ################ Needed packages #### 3 | import numpy as np 4 | import matplotlib.pyplot as plt 5 | from scipy.stats import linregress 6 | import scipy.optimize as opt 7 | ''' 8 | import numpy as np 9 | from scipy.stats import linregress 10 | import matplotlib.pyplot as plt 11 | import scipy.optimize as opt 12 | 13 | def linear_fit(index, Temp, min_T, path_fit=False, path_plot=False): 14 | 15 | ''' 16 | This function takes two images (index and Temp) and computes the linear 17 | regression parameters $T = a1*I + a0$. It also applies two masks on the data. 18 | The first mask eliminates all the pixels with temperatures below min_T and 19 | pixels where Temp==nan, and the second masks eliminates all the pixels 20 | with index == nan. 21 | 22 | ############## Inputs: 23 | 24 | index: reflective index image, ex: NDVI, NDBI ... 25 | 26 | Temp: Temperature image 27 | 28 | min_T: Minimum expected temperature in the image. All the temperatures 29 | below this one are considered spurious measures and not taken into account in the 30 | regression. 31 | 32 | path_fit: String with the adress where saving the txt file with the regression parameters. 33 | 34 | path_plot: String with the adress where saving the plot of T vs I and the linear regression. 35 | 36 | ############## Outputs: 37 | 38 | fit : Linear fit parameters 39 | 40 | # 41 | ### 42 | ###### 43 | ########### 44 | ############### C. Granero-Belinchon; IMT Atlantique, Brest; 12/2021 45 | ''' 46 | 47 | ########### PROCESSED VERSION OF T AND I AND LINEAR REGRESSION ####### 48 | 49 | T=Temp.flatten() 50 | 51 | T_great_mask=np.greater(T,min_T) # We eliminate the background pixels and those with a temperature under min_T 52 | T=T[T_great_mask] 53 | 54 | I=index.flatten() 55 | I=I[T_great_mask] 56 | 57 | # We look for nans in the index and we eliminate those pixels from the index and the temperature 58 | NaN=np.isfinite(I) 59 | I=I[NaN] 60 | T=T[NaN] 61 | 62 | # linear regression 63 | 64 | fit=linregress(I,T) 65 | 66 | ################################################################### 67 | ########### SAVING RESULTS ######################################## 68 | ################################################################### 69 | 70 | if path_fit != False: 71 | np.savetxt(path_fit,(fit[0],fit[1],fit[2],fit[3],fit[4]),fmt='%3.6f') 72 | print('Fit parameters saved in file') 73 | 74 | ################################################################### 75 | ########### PLOT ################################################## 76 | ################################################################### 77 | 78 | if path_plot != False: 79 | 80 | plt.figure(1) 81 | plt.scatter(I,T,s=1,c='blue') 82 | plt.plot(I,fit[0]*I+fit[1],'--k') 83 | plt.xlabel('I') 84 | plt.ylabel('T') 85 | plt.grid() 86 | plt.savefig(path_plot) 87 | plt.close() 88 | 89 | print('Fit Done') 90 | 91 | else: 92 | 93 | print('Fit Done') 94 | 95 | return fit 96 | 97 | 98 | ################# DEFINITION OF THE BILINEAR EXPRESSION ############## 99 | 100 | def bilinear(x,p0,p1,p2): 101 | f = p1*x[:,0] + p2*x[:,1] + p0 102 | return f 103 | 104 | def bilinear_fit(index1, index2, Temp, min_T, path_fit): 105 | 106 | ''' 107 | This function takes three images (path_index1, apth_index2 and path_temperature) and 108 | computes the linear regression parameters $T = a1*I1 +a2*I2 + a0$. It also 109 | applies two masks on the data. The first mask eliminates all the 110 | pixels with temperatures below min_T and pixels where Temp==nan, 111 | and the second masks eliminates all the pixels with index1 == nan or index2 ==nan. 112 | 113 | ############## Inputs: 114 | 115 | index: reflective index image, ex: NDVI, NDBI ... 116 | 117 | index2: reflective index image, ex: NDVI, NDBI ... 118 | 119 | Temp: Temperature image 120 | 121 | min_T: Minimum expected temperature in the image. All the temperatures 122 | below this one are considered spurious measures and not taken into account in the 123 | regression. 124 | 125 | path_fit: String with the adress where saving the txt file with the regression parameters. 126 | 127 | ############## Outputs: 128 | 129 | param: bilinear fit parameters 130 | 131 | ### 132 | ###### 133 | ########### 134 | ############### C. Granero-Belinchon; IMT Atlantique, Brest; 12/2021 135 | ''' 136 | 137 | ########### PROCESSED VERSION OF T AND I AND LINEAR REGRESSION ####### 138 | 139 | T=Temp.flatten() 140 | 141 | T_great_mask=np.greater(T,min_T) # We eliminate the background pixels and those with a temperature under min_T 142 | T=T[T_great_mask] 143 | 144 | I1=index1.flatten() 145 | I1=I1[T_great_mask] 146 | I2=index2.flatten() 147 | I2=I2[T_great_mask] 148 | 149 | # We look for nans in the index and we eliminate those pixels from the index and the temperature 150 | NaN=np.isfinite(I1) 151 | I1=I1[NaN] 152 | I2=I2[NaN] 153 | T=T[NaN] 154 | 155 | NaN=np.isfinite(I2) 156 | I1=I1[NaN] 157 | I2=I2[NaN] 158 | T=T[NaN] 159 | 160 | ################ LINEAR REGRESSION ################################## 161 | 162 | ydata = T 163 | xdata = np.zeros((len(I1),2)) 164 | xdata[:,0] = I1 165 | xdata[:,1] = I2 166 | 167 | p = [1.,1.,1.] 168 | 169 | param, pcov = opt.curve_fit(bilinear, xdata, ydata,p,method='lm') 170 | 171 | ################################################################### 172 | ########### SAVING RESULTS ######################################## 173 | ################################################################### 174 | 175 | if path_fit != False: 176 | np.savetxt(path_fit,(param),fmt='%3.6f') 177 | print('Fit parameters saved in file') 178 | 179 | ################################################################### 180 | ########### PLOT ################################################## 181 | ################################################################### 182 | 183 | print('Fit Done') 184 | 185 | return param 186 | 187 | def linear_fit_window(index, Temp, min_T, b_radius): 188 | 189 | ''' 190 | This function takes two images (index and temperature) and 191 | computes the linear regression parameters $T = a1*I + a0$ within a moving 192 | window of size (2*b_radius+1)^2. The obtained slope and intercept values 193 | are assigned to the center pixel of the window. It also 194 | applies two masks on the data. The first mask eliminates all the 195 | pixels with temperatures below min_T and pixels where Temp==nan, 196 | and the second masks eliminates all the pixels with index == nan. 197 | The code returns two images, a0 and a1 images, equal in size to the input images. 198 | 199 | ############## Inputs: 200 | 201 | index: reflective index image, ex: NDVI, NDBI ... 202 | 203 | Temp: Temperature image 204 | 205 | min_T: Minimum expected temperature in the image. All the temperatures 206 | below this one are considered spurious measures and not taken into account in the 207 | regression. 208 | 209 | b_radius: The moving window used to compute the regression parameters 210 | has a size (2*b_radius+1)^2 211 | 212 | ############## Outputs: 213 | 214 | a0: Image with intercepts 215 | a1: Image with slopes 216 | 217 | # 218 | ## 219 | ### 220 | ###### 221 | ########### 222 | ############### C. Granero-Belinchon (IMT Atlantique) and A. Michel (Onera); Brest; 12/2021 223 | ''' 224 | 225 | ########### PROCESSED VERSION OF T AND I AND LINEAR REGRESSION ####### 226 | 227 | (rows,cols)=Temp.shape 228 | 229 | T=Temp.flatten() 230 | 231 | T_great_mask=np.greater(T,min_T) # We eliminate the background pixels and those with a temperature under min_T 232 | T=T[T_great_mask] 233 | 234 | I=index.flatten() 235 | I=I[T_great_mask] 236 | 237 | # We look for nans in the index and we eliminate those pixels from the index and the temperature 238 | NaN=np.isfinite(I) 239 | I=I[NaN] 240 | T=T[NaN] 241 | 242 | # Two different linear regressions 243 | 244 | fit2=linregress(I,T) 245 | 246 | ##################################################################### 247 | ################### WINDOW DEFINITION , ############################# 248 | ########### AND LINEAR REGRESSION FOR EACH WINDOW ################### 249 | 250 | a1 = np.zeros((rows, cols)) 251 | a0 = np.zeros((rows, cols)) 252 | 253 | for irow in range(b_radius,rows-b_radius): 254 | print(irow, end=" ") 255 | for icol in range(b_radius,cols-b_radius): 256 | 257 | Tw = Temp[irow-b_radius:irow+b_radius+1,icol-b_radius:icol+b_radius+1] 258 | Iw = index[irow-b_radius:irow+b_radius+1,icol-b_radius:icol+b_radius+1] 259 | Tw = Tw.flatten() 260 | Iw = Iw.flatten() 261 | mask=np.greater(Tw,min_T) # We eliminate the background pixels and those with a temperature under min_T K 262 | Tw=Tw[mask] 263 | Iw=Iw[mask] 264 | NaN=np.isfinite(Iw) # We look for nans in the index and we eliminate those pixels from the index and the temperature 265 | Iw=Iw[NaN] 266 | Tw=Tw[NaN] 267 | if len(Tw) > 2/3*(2*b_radius+1)**2: 268 | fit=linregress(Iw,Tw) 269 | a1[irow,icol]=fit[0] 270 | a0[irow,icol]=fit[1] 271 | if len(Tw) <= 2/3*(2*b_radius+1)**2: 272 | a1[irow,icol]=fit2[0] 273 | a0[irow,icol]=fit2[1] 274 | 275 | for irow in range(0,b_radius): 276 | for icol in range(cols): 277 | a1[irow,icol]=fit2[0] 278 | a0[irow,icol]=fit2[1] 279 | 280 | for irow in range(rows-b_radius,rows): 281 | for icol in range(cols): 282 | a1[irow,icol]=fit2[0] 283 | a0[irow,icol]=fit2[1] 284 | 285 | for icol in range(0,b_radius): 286 | for irow in range(rows): 287 | a1[irow,icol]=fit2[0] 288 | a0[irow,icol]=fit2[1] 289 | 290 | for icol in range(cols-b_radius,cols): 291 | for irow in range(rows): 292 | a1[irow,icol]=fit2[0] 293 | a0[irow,icol]=fit2[1] 294 | 295 | 296 | print('Fit Done') 297 | 298 | return a0, a1 299 | 300 | # Multivariate 4th Order Polynomial regression (HUTS) 301 | def huts(x,p1,p2,p3,p4,p5,p6,p7,p8,p9,p10,p11,p12,p13,p14,p15): 302 | f = p1*x[:,0]**4 + p2*x[:,0]**3*x[:,1] + p3*x[:,0]**2*x[:,1]**2 + p4*x[:,0]*x[:,1]**3 + p5*x[:,1]**4 +p6*x[:,0]**3 + p7*x[:,0]**2*x[:,1] + p8*x[:,0]*x[:,1]**2 + p9*x[:,1]**3 + p10*x[:,0]**2 + p11*x[:,0]*x[:,1] + p12*x[:,1]**2 + p13*x[:,0] + p14*x[:,1] + p15 303 | return f 304 | 305 | def huts_fit(index, albedo, Temp, min_T, p=False, path_fit=False): 306 | 307 | ''' 308 | This function takes three images (index, albedo and temperature) and 309 | computes the polynomial regression parameters of HUTS model. It also 310 | applies two masks on the data. The first mask eliminates all the 311 | pixels with temperatures below min_T and where T==nan, and the second masks 312 | eliminates all the pixels with index1 == nan. 313 | 314 | ############## Inputs: 315 | 316 | index: reflective index image, ex: NDVI, NDBI ... 317 | 318 | albedo: albedo from reflective domain 319 | 320 | Temp: Temperature image 321 | 322 | min_T: Minimum expected temperature in the image. All the temperatures 323 | below this one are considered spurious measures and not taken into account in the 324 | regression. 325 | 326 | p : initial guess for fit parameters 327 | 328 | path_fit: String with the adress where saving the txt file with the regression parameters. 329 | 330 | ############## Outputs: 331 | 332 | ## 333 | ### 334 | ###### 335 | ########### 336 | ############### C. Granero-Belinchon; IMT Atlantique, Brest; 12/2021 337 | ''' 338 | 339 | ########### PROCESSED VERSION OF T AND I AND LINEAR REGRESSION ####### 340 | 341 | T=Temp.flatten() 342 | 343 | T_great_mask=np.greater(T,min_T) # We eliminate the background pixels and those with a temperature under min_T 344 | T=T[T_great_mask] 345 | 346 | I=index.flatten() 347 | I=I[T_great_mask] 348 | A=albedo.flatten() 349 | A=A[T_great_mask] 350 | 351 | # We look for nans in the index and we eliminate those pixels from the index and the temperature 352 | NaN=np.isfinite(I) 353 | I=I[NaN] 354 | A=A[NaN] 355 | T=T[NaN] 356 | 357 | NaN=np.isfinite(A) 358 | I=I[NaN] 359 | A=A[NaN] 360 | T=T[NaN] 361 | 362 | ################ POLYNOMIAL REGRESSION ################################## 363 | 364 | ydata = T 365 | xdata = np.zeros((len(I),2)) 366 | xdata[:,0] = I 367 | xdata[:,1] = A 368 | 369 | if p==False: 370 | p = [1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.,1.] 371 | 372 | param, pcov = opt.curve_fit(huts, xdata, ydata,p,method='lm') 373 | 374 | ################################################################### 375 | ########### SAVING RESULTS ######################################## 376 | ################################################################### 377 | 378 | if path_fit != False: 379 | np.savetxt(path_fit,(param),fmt='%3.6f') 380 | print('Fit parameters saved in file') 381 | 382 | print('Fit Done') 383 | 384 | return param 385 | 386 | def fit_byclass(index, Temp, Class, min_T, path_fit=False, path_plot=False): 387 | 388 | ''' 389 | This function takes three images (index, class and temperature) and 390 | computes the linear regression parameters $T = a1*I +a0$ separately for 391 | each class defined in Class. It also applies two masks on the data. 392 | The first mask eliminates all the pixels with temperatures below min_T 393 | and where T==nan, and the second masks eliminates all the pixels with 394 | index == nan. 395 | 396 | ############## Inputs: 397 | 398 | index: reflective index image, ex: NDVI, NDBI ... 399 | 400 | Temp: Temperature image 401 | 402 | Class : Classification image file. Background pixels =0 403 | 404 | min_T: Minimum expected temperature in the image. All the temperatures 405 | below this one are considered spurious measures and not taken into account in the 406 | regression. 407 | 408 | path_fit: String with the adress where saving the txt file with the regression parameters. 409 | 410 | path_plot: String with the adress where saving the plot of T vs I and the linear regression. 411 | 412 | ############## Outputs: 413 | 414 | fit_c: matrix of size (number of classes x fit parameters) with the fit parameters per class 415 | 416 | ## 417 | ### 418 | ###### 419 | ########### 420 | ############### C. Granero-Belinchon; IMT Atlantique, Brest and A. Michel; DOTA-ONERA, Toulouse; 12/2021 421 | ''' 422 | 423 | # We obtain the number of classes and the value characterizing each class 424 | val_class = np.unique(Class) 425 | val_class = val_class[np.nonzero(val_class)] 426 | num_class = len(val_class) 427 | 428 | ########### FINAL VERSION OF T AND I AND LINEAR REGRESSION ########## 429 | 430 | T = Temp.flatten() 431 | I = index.flatten() 432 | C = Class.flatten() 433 | 434 | # Classification 435 | 436 | fit_c=np.zeros((num_class,5)) 437 | #creation of the header for the filetext 438 | linehead = '' 439 | 440 | for i in range(num_class): 441 | linehead = linehead + 'Class'+str(i+1)+' ' #creation of the header for the filetext 442 | cc = np.where(C==val_class[i]) 443 | Ic = I[cc] 444 | Tc = T[cc] 445 | 446 | # Mask 447 | 448 | T_great_mask_c=np.greater(Tc,min_T) # We eliminate the background pixels and those with a temperature under 285K 449 | Tc=Tc[T_great_mask_c] 450 | Ic=Ic[T_great_mask_c] 451 | NaN=np.isfinite(Ic) 452 | Ic=Ic[NaN] 453 | Tc=Tc[NaN] 454 | 455 | fit_c[i]=linregress(Ic,Tc) 456 | 457 | ################################################################### 458 | ########### PLOT ################################################## 459 | ################################################################### 460 | 461 | if path_plot !=False: 462 | 463 | plt.figure(1) 464 | plt.scatter(Ic,Tc,s=1,c='blue') 465 | plt.plot(Ic,fit_c[i,0]*Ic + fit_c[i,1],'--k') 466 | plt.xlabel('I') 467 | plt.ylabel('T') 468 | plt.grid() 469 | plt.savefig(path_plot[:-4] + "_class"+ str(i+1) + path_plot[-4:]) 470 | plt.close() 471 | 472 | print('Fit by class Done') 473 | else: 474 | print('Fit by class Done') 475 | 476 | if path_fit!=False: 477 | np.savetxt(path_fit,(fit_c[:,0],fit_c[:,1],fit_c[:,2],fit_c[:,3],fit_c[:,4]),header=linehead,fmt='%3.6f') 478 | 479 | return fit_c.T 480 | -------------------------------------------------------------------------------- /Visualization benchmark/Thunmpy/Thunmcorr.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | from scipy.spatial.distance import pdist, squareform 4 | import scipy.optimize as opt 5 | import cv2 6 | 7 | 8 | def correction_avrg(Temp, TT_unm, iscale): 9 | 10 | ''' 11 | This function takes two images (path_temperature and path_unm) and 12 | computes the residuals of each coarse and fine pixel. For the coarse pixels 13 | it computes the residuals as the difference between the measured coarse 14 | temperature of the pixel and the mean of the fine pixel temperatures within 15 | the coarse pixel $R_coarse=T_measured - mean(T_unm)$. Then the residuals for each 16 | fine pixel resolution inside a coarse pixel is the residual of the coarse 17 | pixel (scale invariance hypothesis). 18 | 19 | ############## Inputs: 20 | 21 | Temp (large scale): Temperature image. the background pixels must be == 0 22 | 23 | TT_unm (fine scale): Sharpened temperature image. the background pixels must be == 0 24 | 25 | iscale: size factor between measured pixel and unmixed pixel. 26 | Ex: if the initial resolution of Temp is 90m and the resolution 27 | after unmixing is 10m iscale=9. 28 | 29 | ## 30 | ### 31 | ###### 32 | ########### 33 | ############### C. Granero-Belinchon; IMT Atlantique, Brest; 12/2021 34 | ''' 35 | 36 | (rows,cols)=TT_unm.shape 37 | 38 | # We recreate a low resolution temperature image from the unmixed one 39 | T_unm_add = cv2.resize(TT_unm, (Temp.shape[1],Temp.shape[0]), interpolation = cv2.INTER_AREA) 40 | 41 | # Because in the borders of the image we can have background pixels, we have to dont take into account them. 42 | # To do that, we take the measured temperatures at coarse scale and impose zero 43 | # values to all the pixels of the "Added temperature" that are zero for the measured temperature. 44 | mask=np.greater(Temp,0) # We eliminate the background pixels (not in the image) 45 | T_unm_add[~mask]=0 46 | 47 | mask2=np.greater(T_unm_add,0) # We eliminate the background pixels (not in the image) 48 | Temp[~mask2]=0 49 | 50 | # We define the delta at the scale of the measured temperature as in the model. 51 | Delta_T = Temp[0:int(np.floor(rows/iscale)),0:int(np.floor(cols/iscale))] - T_unm_add[0:int(np.floor(rows/iscale)),0:int(np.floor(cols/iscale))] 52 | 53 | # We obtain the delta at the scale of the unmixed temperature 54 | Delta_T_final =np.zeros((rows,cols)) 55 | for ic in range(int(np.floor(cols/iscale))): 56 | for ir in range(int(np.floor(rows/iscale))): 57 | for ir_2 in range(ir*iscale,(ir*iscale+iscale)): 58 | for ic_2 in range(ic*iscale,(ic*iscale+iscale)): 59 | Delta_T_final[ir_2,ic_2] = Delta_T[ir,ic] 60 | 61 | # We define a mask in order to not use the background pixels. If a pixel of the finer scale image is zero, 62 | # the coarse pixel which contains this one is also zero. 63 | mask_TT_unm=np.zeros((rows,cols)) 64 | mask_TT_unm[np.nonzero(TT_unm)]=1 65 | 66 | Delta_T_final=Delta_T_final*mask_TT_unm 67 | 68 | # We correct the unmixed temperature using the delta 69 | TT_unmixed_corrected = TT_unm + Delta_T_final 70 | 71 | print('Correction Done') 72 | 73 | return TT_unmixed_corrected 74 | 75 | def correction_linreg(index, Temp, TT_unm, iscale, fit_param): 76 | 77 | 78 | ''' 79 | This function takes three images (path_index, path_temperature and path_unm) and 80 | computes the residuals of each coarse and fine pixel. For the coarse pixels 81 | it computes the residuals as the difference between the measured coarse 82 | temperature of the pixel and the coarse temperature obtained with the linear regression 83 | $R_coarse=T_measured_coarse - (a1*I_measured_coarse + a0)$. Then the residuals for each 84 | fine pixel resolution inside a coarse pixel is the residual of the coarse 85 | pixel (scale invariance hypothesis). 86 | 87 | ############## Inputs: 88 | 89 | index (large scale): reflective index image, ex: NDVI, NDBI ... 90 | 91 | Temp (large scale): Temperature image. the background pixels must be == 0 92 | 93 | 94 | TT_unm (fine scale): Sharpened temperature image. the background pixels must be == 0 95 | 96 | iscale: size factor between measured pixel and unmixed pixel. 97 | Ex: if the initial resolution of Temp is 90m and the resolution 98 | after unmixing is 10m iscale=9. 99 | 100 | fit_param: fit parameters used to retrieve LST from index 101 | 102 | ## 103 | ### 104 | ###### 105 | ########### 106 | ############### C. Granero-Belinchon; IMT Atlantique, Brest; 12/2021 107 | ''' 108 | 109 | (rows,cols)=TT_unm.shape 110 | 111 | ## Param[1] indicates that we call the intercept of the linear regression. 112 | a0 = fit_param[1] 113 | ## Param[0] indicates that we call the slope of the linear regression. 114 | a1 = fit_param[0] 115 | 116 | # We define the matrix of model temperature 117 | T_unm_add = a0 + a1*index 118 | 119 | mask=np.greater(Temp,0) # We eliminate the background pixels (not in the image) 120 | T_unm_add[~mask]=0 121 | 122 | # We define the delta at the scale of the measured temperature as in the model. 123 | Delta_T = Temp - T_unm_add 124 | 125 | # We define a mask in order to not use the background pixels. If a pixel of the finer scale image is zero, 126 | # the coarse pixel which contains this one is also zero. We have to choose this mask or the mask of line 188 127 | mask_TT_unm=np.zeros((rows,cols)) 128 | mask_TT_unm[np.nonzero(TT_unm)]=1 129 | 130 | # We obtain the delta at the scale of the unmixed temperature 131 | Delta_T_final =np.zeros((rows,cols)) 132 | for ic in range(int(np.floor(cols/iscale))): 133 | for ir in range(int(np.floor(rows/iscale))): 134 | for ir_2 in range(ir*iscale,(ir*iscale+iscale)): 135 | for ic_2 in range(ic*iscale,(ic*iscale+iscale)): 136 | if mask_TT_unm[ir_2,ic_2]==0: 137 | Delta_T_final[ir_2,ic_2] = 0 138 | else: 139 | Delta_T_final[ir_2,ic_2] = Delta_T[ir,ic] 140 | 141 | # We correct the unmixed temperature using the delta 142 | 143 | TT_unmixed_corrected = TT_unm + Delta_T_final 144 | 145 | print('Correction Done') 146 | 147 | return TT_unmixed_corrected 148 | 149 | def quality_correction(Temp, TT_unm, iscale, max_threshold=323, min_threshold=263): 150 | 151 | ''' 152 | This function takes two images (path_temperature and path_unm) and 153 | computes the residuals of each coarse and fine pixel. For the coarse pixels 154 | it computes the residuals as the difference between the measured coarse 155 | temperature of the pixel and the mean of the fine pixel temperatures within 156 | the coarse pixel $R_coarse=T_measured - mean(T_unm)$. Then the residuals for each 157 | fine pixel resolution inside a coarse pixel is the residual of the coarse 158 | pixel (scale invariance hypothesis). 159 | 160 | Before computing the residuals, this function looks for pixels with temperature values 161 | above max_thre +5 or below min_thre - 5 and recalculates the temperature of these 162 | pixels by averaging the temperatures of the 5x5 squares containing each non-desired 163 | temperature pixel in the center. 164 | 165 | ############## Inputs: 166 | 167 | index (large scale): reflective index image, ex: NDVI, NDBI ... 168 | 169 | Temp (large scale): Temperature image. the background pixels must be == 0 170 | 171 | 172 | TT_unm (fine scale): Sharpened temperature image. the background pixels must be == 0 173 | 174 | iscale: size factor between measured pixel and unmixed pixel. 175 | Ex: if the initial resolution of Temp is 90m and the resolution 176 | after unmixing is 10m iscale=9. 177 | 178 | ## 179 | ### 180 | ###### 181 | ########### 182 | ############### C. Granero-Belinchon; IMT Atlantique, Brest; 12/2021 183 | ''' 184 | 185 | (rows,cols)=TT_unm.shape 186 | 187 | ##################### QUALITY CONTROL ############################## 188 | 189 | poids=np.zeros((5,5)) 190 | for i in range(0,5): 191 | for j in range(0,5): 192 | poids[i,j]=2*np.sqrt((i-2)**2+(j-2)**2) 193 | #poids=poids.flatten() 194 | 195 | # Quality control 196 | ind=np.where(TT_unm > max_threshold+5) 197 | for i in range(len(ind[0])): 198 | rowi=ind[0][i] 199 | coli=ind[1][i] 200 | 201 | if rowi>1 and coli>1 and rowi 0)) 205 | for i in range(len(ind[0])): 206 | rowi=ind[0][i] 207 | coli=ind[1][i] 208 | 209 | if rowi>1 and coli>1 and rowi thres*num_px or len(LST_K_night[LST_K_night==0.0]) > thres*num_px: 100 | return False 101 | 102 | bands = 2 103 | day_night_imgs = np.zeros((2,LST_K_day.shape[0],LST_K_day.shape[1])) 104 | day_night_imgs[0,:,:]= LST_K_day 105 | day_night_imgs[1,:,:]= LST_K_night 106 | 107 | driver = gdal.GetDriverByName("GTiff") 108 | outDs = driver.Create(out_file, day_night_imgs.shape[1], day_night_imgs.shape[2], bands, gdal.GDT_Float32) 109 | outDs.SetProjection(projection) 110 | outDs.SetGeoTransform(geotransform) 111 | 112 | for i in range(1,bands+1): 113 | outBand = outDs.GetRasterBand(i) 114 | outBand.WriteArray(day_night_imgs[i-1,:,:]) 115 | outDs.FlushCache() 116 | 117 | return True 118 | 119 | def read_modis(in_file): 120 | """ 121 | This function is for reading modis file in format "x.hdf" contained in 122 | in_file parameter. 123 | INPUT: 124 | in_file: path to hdf file 125 | to be completed if we need more stuffs 126 | 127 | OUTPUT: 128 | LST_K_day and LST_K_night: Day and Night LST image arrays 129 | cols, rows: numbers of pixels alongs x,y axes of each raster 130 | projection: projection information 131 | geotransform: georeferencing information 132 | # georef format = (x-coor top left pixel,resX,0,y-coor top left pixel,rexY,0) 133 | to be completed if we need more information (georeference,bands,etc.) 134 | """ 135 | # open dataset Day 136 | dataset = gdal.Open(in_file,gdal.GA_ReadOnly) 137 | subdataset = gdal.Open(dataset.GetSubDatasets()[0][0], gdal.GA_ReadOnly) 138 | 139 | cols =subdataset.RasterXSize 140 | rows = subdataset.RasterYSize 141 | projection = subdataset.GetProjection() 142 | geotransform = subdataset.GetGeoTransform() 143 | 144 | # Coordinates of top left pixel of the image (Lat, Lon) 145 | # coords=np.asarray((geotransform[0],geotransform[3])) 146 | 147 | # We read the Image as an array 148 | band = subdataset.GetRasterBand(1) 149 | LST_raw = band.ReadAsArray(0, 0, cols, rows).astype(np.float) 150 | # bandtype = gdal.GetDataTypeName(band.DataType) 151 | 152 | # To convert LST MODIS units to Kelvin 153 | LST_K_day=0.02*LST_raw 154 | 155 | dataset = None 156 | subdataset = None 157 | # open dataset Night 158 | dataset = gdal.Open(in_file,gdal.GA_ReadOnly) 159 | subdataset = gdal.Open(dataset.GetSubDatasets()[4][0], gdal.GA_ReadOnly) 160 | 161 | # We read the Image as an array 162 | band = subdataset.GetRasterBand(1) 163 | LST_raw = band.ReadAsArray(0, 0, cols, rows).astype(np.float) 164 | # bandtype = gdal.GetDataTypeName(band.DataType) 165 | 166 | # To convert LST MODIS units to Kelvin 167 | LST_K_night=0.02*LST_raw 168 | dataset = None 169 | subdataset = None 170 | 171 | # return LST_K_day, LST_K_night 172 | return LST_K_day, LST_K_night, cols, rows, projection, geotransform 173 | 174 | 175 | def crop_modis(hdf_path, hdf_name, save_dir, save_dir_downsample_2, save_dir_downsample_4,step=64,size=(64,64)): 176 | """ 177 | INPUT: 178 | hdf_path = input image path to be cropped | or hdf file path ("/a/b/c.hdf") 179 | save_dir = directory for saving cropped images 180 | step, size: parameters of "sliding_window()" 181 | 182 | OUTPUT: images cropped from the image in hdf_path, saved to save_dir 183 | """ 184 | if not hdf_path.endswith('hdf'): 185 | print("Not hdf file Sorry!") 186 | return 187 | 188 | img_day, img_night, cols, rows, projection, geotransform = read_modis(hdf_path) 189 | 190 | img_days = [] 191 | img_nights = [] 192 | img_cropped_names = [] 193 | geotransform2s = [] 194 | cols2, rows2 = size 195 | 196 | if img_day is None or img_night is None: 197 | print("Cannot handle this MODIS file: ", hdf_path, ". Please check it again") 198 | return 199 | # For day image 200 | win_count = 0 201 | for (x,y,window) in sliding_window(img_day, step, size): 202 | if window.shape[0] != size[0] or window.shape[1] != size[1]: 203 | continue 204 | 205 | img_cropped_name = hdf_name + ".{}.tif".format(str(win_count).zfill(4)) 206 | img_cropped = window 207 | geotransform2 = np.asarray(geotransform) 208 | geotransform2[0] = geotransform[0]+x*geotransform[1] # 1st coordinate of top left pixel of the image 209 | geotransform2[3] = geotransform[3]+y*geotransform[5] # 2nd coordinate of top left pixel of the image 210 | geotransform2=tuple(geotransform2) 211 | 212 | img_cropped_names.append(img_cropped_name) 213 | img_days.append(img_cropped) 214 | geotransform2s.append(geotransform2) 215 | 216 | win_count += 1 217 | # print("Number of cropped day images", win_count) 218 | 219 | # For night image 220 | win_count = 0 221 | for (x,y,window) in sliding_window(img_night, step, size): 222 | if window.shape[0] != size[0] or window.shape[1] != size[1]: 223 | continue 224 | # save_path = os.path.join(save_dir,img_cropped_name) 225 | img_cropped = window 226 | # np.save(save_path,img_cropped) 227 | img_nights.append(img_cropped) 228 | win_count += 1 229 | # print("Number of cropped night images", win_count) 230 | 231 | # Save images and metadata into .tif file 232 | for i in range(len(img_cropped_names)): 233 | save_path = os.path.join(save_dir,img_cropped_names[i]) 234 | succes = save_tif(save_path, img_days[i], img_nights[i], cols2, rows2, projection, geotransform2s[i]) 235 | 236 | if succes: 237 | save_path_downsample_2 = os.path.join(save_dir_downsample_2,img_cropped_names[i]) 238 | save_path_downsample_4 = os.path.join(save_dir_downsample_4,img_cropped_names[i]) 239 | 240 | img_day_downsample_2 = skimage.measure.block_reduce(img_days[i],(2,2),norm4_f2) 241 | img_night_downsample_2 = skimage.measure.block_reduce(img_nights[i],(2,2),norm4_f2) 242 | img_day_downsample_4 = skimage.measure.block_reduce(img_days[i],(4,4),norm4_f4) 243 | img_night_downsample_4 = skimage.measure.block_reduce(img_nights[i],(4,4),norm4_f4) 244 | 245 | _ = save_tif(save_path_downsample_2, img_day_downsample_2, img_night_downsample_2, cols2, rows2, projection, geotransform2s[i]) 246 | _ = save_tif(save_path_downsample_4, img_day_downsample_4, img_night_downsample_4, cols2, rows2, projection, geotransform2s[i]) 247 | 248 | # print("img_night_downsample",img_night_downsample) 249 | # # Display image 250 | # tif_1km_path = save_path 251 | # tif_2km_path = save_path_downsample 252 | # LST_K_day_1km, LST_K_night_1km, cols_1km, rows_1km, projection_1km, geotransform_1km = read_tif(tif_1km_path) 253 | # LST_K_day_2km, LST_K_night_2km, cols_2km, rows_2km, projection_2km, geotransform_2km = read_tif(tif_2km_path) 254 | 255 | # plt.figure() 256 | # plt.subplot(121) 257 | # plt.imshow(LST_K_day_1km) 258 | # plt.clim(260,301) 259 | # plt.colorbar() 260 | # plt.title("1km") 261 | # plt.subplot(122) 262 | # plt.imshow(LST_K_day_2km) 263 | # plt.clim(260,301) 264 | # plt.colorbar() 265 | # plt.title("2km") 266 | # plt.show() 267 | else: 268 | # print("Not success!") 269 | pass 270 | 271 | 272 | def save_tif_MOD13A2(out_file, red_downsample, NIR_downsample, MIR_downsample, cols, rows, projection, geotransform): 273 | # def save_tif(out_file, LST_K_day, LST_K_night, cols, rows, projection, geotransform): 274 | 275 | # Eliminate the clouds' pixel 276 | num_px = red_downsample.shape[0]*red_downsample.shape[1] 277 | thres = 0 # Threshold: maximum number of sea/cloud pixels 278 | # thres = 0.15 # Threshold: maximum number of sea/cloud pixels 279 | if len(red_downsample[red_downsample==0.0]) > thres*num_px or len(NIR_downsample[NIR_downsample==0.0]) > thres*num_px or len(MIR_downsample[MIR_downsample==0.0]) > thres*num_px: 280 | return False 281 | 282 | bands = 3 283 | red_NIR_MIR_imgs = np.zeros((3,red_downsample.shape[0],red_downsample.shape[1])) 284 | red_NIR_MIR_imgs[0,:,:]= red_downsample 285 | red_NIR_MIR_imgs[1,:,:]= NIR_downsample 286 | red_NIR_MIR_imgs[2,:,:]= MIR_downsample 287 | 288 | driver = gdal.GetDriverByName("GTiff") 289 | outDs = driver.Create(out_file, red_NIR_MIR_imgs.shape[1], red_NIR_MIR_imgs.shape[2], bands, gdal.GDT_Float32) 290 | outDs.SetProjection(projection) 291 | outDs.SetGeoTransform(geotransform) 292 | 293 | for i in range(1,bands+1): 294 | outBand = outDs.GetRasterBand(i) 295 | outBand.WriteArray(red_NIR_MIR_imgs[i-1,:,:]) 296 | outDs.FlushCache() 297 | 298 | return True 299 | 300 | def read_modis_MOD13A2(in_file): 301 | # in_file = "MODIS/MOD_2020/hdfs_files/MOD13A2.A2020001.h18v04.061.2020326033640.hdf" 302 | # in_file = '/content/drive/MyDrive/Ganglin/1_IMT/MCE_Projet3A/MODIS/MOD_2020/hdfs_files/MOD11A1.A2020001.h18v04.061.2021003092415.hdf' 303 | dataset = gdal.Open(in_file,gdal.GA_ReadOnly) 304 | subdataset = gdal.Open(dataset.GetSubDatasets()[3][0], gdal.GA_ReadOnly) 305 | 306 | cols =subdataset.RasterXSize 307 | rows = subdataset.RasterYSize 308 | projection = subdataset.GetProjection() 309 | geotransform = subdataset.GetGeoTransform() 310 | 311 | # We read the Image as an array 312 | band = subdataset.GetRasterBand(1) 313 | LST_raw = band.ReadAsArray(0, 0, cols, rows).astype(np.float) 314 | 315 | # To convert LST MODIS units to Kelvin 316 | red =0.02*LST_raw 317 | 318 | # open dataset Night 319 | dataset = gdal.Open(in_file,gdal.GA_ReadOnly) 320 | subdataset = gdal.Open(dataset.GetSubDatasets()[4][0], gdal.GA_ReadOnly) 321 | 322 | # We read the Image as an array 323 | band = subdataset.GetRasterBand(1) 324 | LST_raw = band.ReadAsArray(0, 0, cols, rows).astype(np.float) 325 | # bandtype = gdal.GetDataTypeName(band.DataType) 326 | # To convert LST MODIS units to Kelvin 327 | NIR =0.02*LST_raw 328 | 329 | # open dataset Night 330 | dataset = gdal.Open(in_file,gdal.GA_ReadOnly) 331 | subdataset = gdal.Open(dataset.GetSubDatasets()[6][0], gdal.GA_ReadOnly) 332 | 333 | # We read the Image as an array 334 | band = subdataset.GetRasterBand(1) 335 | LST_raw = band.ReadAsArray(0, 0, cols, rows).astype(np.float) 336 | # bandtype = gdal.GetDataTypeName(band.DataType) 337 | # To convert LST MODIS units to Kelvin 338 | MIR =0.02*LST_raw 339 | return red, NIR, MIR, cols, rows, projection, geotransform 340 | 341 | def crop_modis_MOD13A2(hdf_path, hdf_name, save_dir, save_dir_downsample_2, save_dir_downsample_4,step=64,size=(64,64)): 342 | """ 343 | INPUT: 344 | hdf_path = input image path to be cropped | or hdf file path ("/a/b/c.hdf") 345 | save_dir = directory for saving cropped images 346 | step, size: parameters of "sliding_window()" 347 | 348 | OUTPUT: images cropped from the image in hdf_path, saved to save_dir 349 | """ 350 | if not hdf_path.endswith('hdf'): 351 | print("Not hdf file Sorry!") 352 | return 353 | 354 | img_day, img_night, cols, rows, projection, geotransform = read_modis(hdf_path) 355 | 356 | img_days = [] 357 | img_nights = [] 358 | img_cropped_names = [] 359 | geotransform2s = [] 360 | cols2, rows2 = size 361 | 362 | if img_day is None or img_night is None: 363 | print("Cannot handle this MODIS file: ", hdf_path, ". Please check it again") 364 | return 365 | 366 | red, NIR, MIR, cols, rows, projection, geotransform = read_modis_MOD13A2(hdf_path) 367 | 368 | reds = [] 369 | NIRs = [] 370 | MIRs = [] 371 | img_cropped_names = [] 372 | geotransform2s = [] 373 | cols2, rows2 = size 374 | 375 | if red is None or NIR is None or MIR is None: 376 | print("Cannot handle this MODIS file: ", hdf_path, ". Please check it again") 377 | # For day image 378 | win_count = 0 379 | for (x,y,window) in sliding_window(red, step, size): 380 | if window.shape[0] != size[0] or window.shape[1] != size[1]: 381 | continue 382 | 383 | img_cropped_name = hdf_name + ".{}.tif".format(str(win_count).zfill(4)) 384 | img_cropped = window 385 | geotransform2 = np.asarray(geotransform) 386 | geotransform2[0] = geotransform[0]+x*geotransform[1] # 1st coordinate of top left pixel of the image 387 | geotransform2[3] = geotransform[3]+y*geotransform[5] # 2nd coordinate of top left pixel of the image 388 | geotransform2=tuple(geotransform2) 389 | 390 | img_cropped_names.append(img_cropped_name) 391 | reds.append(img_cropped) 392 | geotransform2s.append(geotransform2) 393 | 394 | win_count += 1 395 | # print("Number of cropped day images", win_count) 396 | 397 | # For NIR image 398 | win_count = 0 399 | for (x,y,window) in sliding_window(NIR, step, size): 400 | if window.shape[0] != size[0] or window.shape[1] != size[1]: 401 | continue 402 | img_cropped = window 403 | # np.save(save_path,img_cropped) 404 | NIRs.append(img_cropped) 405 | win_count += 1 406 | 407 | # For MIR image 408 | win_count = 0 409 | for (x,y,window) in sliding_window(MIR, step, size): 410 | if window.shape[0] != size[0] or window.shape[1] != size[1]: 411 | continue 412 | img_cropped = window 413 | # np.save(save_path,img_cropped) 414 | MIRs.append(img_cropped) 415 | win_count += 1 416 | 417 | # Save images and metadata into .tif file 418 | for i in range(len(img_cropped_names)): 419 | save_path = os.path.join(save_dir,img_cropped_names[i]) 420 | succes = save_tif_MOD13A2(save_path, reds[i], NIRs[i], MIRs[i], cols2, rows2, projection, geotransform2s[i]) 421 | 422 | if succes: 423 | save_path_downsample_2 = os.path.join(save_dir_downsample_2,img_cropped_names[i]) 424 | save_path_downsample_4 = os.path.join(save_dir_downsample_4,img_cropped_names[i]) 425 | red_downsample_2 = skimage.measure.block_reduce(reds[i],(2,2),norm4_f2) 426 | NIR_downsample_2 = skimage.measure.block_reduce(NIRs[i],(2,2),norm4_f2) 427 | MIR_downsample_2 = skimage.measure.block_reduce(MIRs[i],(2,2),norm4_f2) 428 | 429 | red_downsample_4 = skimage.measure.block_reduce(reds[i],(4,4),norm4_f4) 430 | NIR_downsample_4 = skimage.measure.block_reduce(NIRs[i],(4,4),norm4_f4) 431 | MIR_downsample_4 = skimage.measure.block_reduce(MIRs[i],(4,4),norm4_f4) 432 | 433 | 434 | _ = save_tif_MOD13A2(save_path_downsample_2, red_downsample_2, NIR_downsample_2, MIR_downsample_2, cols2, rows2, projection, geotransform2s[i]) 435 | _ = save_tif_MOD13A2(save_path_downsample_4, red_downsample_4, NIR_downsample_4, MIR_downsample_4, cols2, rows2, projection, geotransform2s[i]) 436 | 437 | 438 | def downsampling(img, scale): 439 | down_img = np.zeros((int(img.shape[0]/scale), int(img.shape[1]/scale))) 440 | for y in range(0, img.shape[0], scale): 441 | for x in range(0, img.shape[1], scale): 442 | window = img[y:y+scale, x:x+scale].reshape(-1) 443 | if 0 not in window: 444 | sum4 = np.sum(window**4) 445 | norm4 = (sum4/(scale**2))**0.25 446 | else: 447 | norm4 = 0.0 448 | down_img[int(y/scale), int(x/scale)] = norm4 449 | 450 | return down_img 451 | 452 | 453 | def upsampling(img, scale): 454 | up_img = cv2.resize(img, (img.shape[0]*scale, img.shape[1]*scale), cv2.INTER_CUBIC) 455 | return up_img 456 | 457 | 458 | def normalization(image, max_val): 459 | nml_image = image/max_val 460 | return nml_image 461 | 462 | 463 | def psnr(label, outputs, max_val): 464 | """ 465 | Compute Peak Signal to Noise Ratio (the higher the better). 466 | PSNR = 20 * log10(MAXp) - 10 * log10(MSE). 467 | https://en.wikipedia.org/wiki/Peak_signal-to-noise_ratio#Definition 468 | First we need to convert torch tensors to NumPy operable. 469 | """ 470 | label = label.cpu().detach().numpy() 471 | outputs = outputs.cpu().detach().numpy()*max_val 472 | psnr_batch = [] 473 | for i in range(label.shape[0]): 474 | psnr_img = peak_signal_noise_ratio(label[i,0,:,:], outputs[i,0,:,:], data_range=label.max()-label.min()) 475 | psnr_batch.append(psnr_img) 476 | return np.array(psnr_batch).mean() 477 | 478 | def psnr_notorch(label, outputs): 479 | """ 480 | Compute Peak Signal to Noise Ratio (the higher the better). 481 | PSNR = 20 * log10(MAXp) - 10 * log10(MSE). 482 | https://en.wikipedia.org/wiki/Peak_signal-to-noise_ratio#Definition 483 | First we need to convert torch tensors to NumPy operable. 484 | """ 485 | # label = label.cpu().detach().numpy() 486 | # outputs = outputs.cpu().detach().numpy() 487 | img_diff = outputs - label 488 | rmse = math.sqrt(np.mean((img_diff) ** 2)) 489 | if rmse == 0 or rmse==np.inf: 490 | return 0, np.inf 491 | else: 492 | PSNR = 20 * np.log10((np.max(label) - np.min(label)) / rmse) 493 | return PSNR, rmse 494 | 495 | 496 | def ssim(label, outputs, max_val): 497 | label = label.cpu().detach().numpy() 498 | outputs = outputs.cpu().detach().numpy()*max_val 499 | ssim_batch = [] 500 | for i in range(label.shape[0]): 501 | ssim_img = ssim_sk(label[i,0,:,:], outputs[i,0,:,:], data_range=label.max()-label.min()) 502 | ssim_batch.append(ssim_img) 503 | return np.array(ssim_batch).mean() 504 | 505 | 506 | def ssim_notorch(label, outputs): 507 | ssim_map = ssim_sk(label, outputs, data_range=label.max() - label.min()) 508 | return ssim_map 509 | 510 | 511 | 512 | def get_loss(disp, img): 513 | # THE FINAL LOSS FUNCTION 514 | mse_img = ((disp - img)**2).mean() 515 | return mse_img 516 | 517 | def linear_fit_test(path_index, path_temperature, min_T, path_fit, plot, path_plot): 518 | from osgeo import gdal 519 | import numpy as np 520 | from scipy.stats import linregress 521 | import matplotlib.pyplot as plt 522 | ################### INDEX ########################################## 523 | 524 | # Reading index data 525 | 526 | cols = path_index.shape[0] #Spatial dimension x 527 | rows = path_index.shape[1] #Spatial dimension y 528 | 529 | # Read as array the index 530 | index = path_index 531 | 532 | ################### Temperature ###################################### 533 | 534 | # Reading temperature data 535 | 536 | cols_t = path_temperature.shape[0] #Spatial dimension x 537 | rows_t = path_temperature.shape[1] #Spatial dimension y 538 | 539 | # Read as array 540 | Temp= path_temperature 541 | 542 | 543 | ########### PROCESSED VERSION OF T AND I AND LINEAR REGRESSION ####### 544 | 545 | T=Temp.flatten() 546 | 547 | T_great_mask=np.greater(T,0) # We eliminate the background pixels and those with a temperature under min_T 548 | T=T[T_great_mask] 549 | 550 | I=index.flatten() 551 | I=I[T_great_mask] 552 | 553 | # We look for nans in the index and we eliminate those pixels from the index and the temperature 554 | # NaN=np.isfinite(I) 555 | # I=I[NaN] 556 | # T=T[NaN] 557 | 558 | # Two different linear regressions 559 | 560 | fit = np.polyfit(I,T,1) 561 | fit_fn = np.poly1d(fit) 562 | fit2=linregress(I,T) 563 | 564 | ################################################################### 565 | ########### SAVING RESULTS ######################################## 566 | ################################################################### 567 | 568 | np.savetxt(path_fit,(fit2[0],fit2[1],fit2[2],fit2[3],fit2[4]),fmt='%3.6f') 569 | 570 | ################################################################### 571 | ########### PLOT ################################################## 572 | ################################################################### 573 | 574 | if plot == 1: 575 | 576 | # plt.figure(1) 577 | # plt.scatter(I,T,s=1,c='blue') 578 | # plt.plot(I,fit_fn(I),'--k') 579 | # plt.xlabel('I') 580 | # plt.ylabel('T') 581 | # plt.grid() 582 | # plt.savefig(path_plot) 583 | # plt.close() 584 | 585 | print('Fit Done') 586 | 587 | else: 588 | pass 589 | # print('Fit Done') 590 | # print("index",index.shape) 591 | # print("Temp",Temp.shape) 592 | return 593 | 594 | 595 | 596 | def linear_unmixing_test(path_index, path_temperature, path_fit, iscale, path_mask, path_out): 597 | 598 | import numpy as np 599 | from osgeo import gdal, gdalconst 600 | 601 | #################################################################### 602 | ################ LOADING DATA ###################################### 603 | #################################################################### 604 | 605 | ################### INDEX ########################################## # fine scale 606 | cols = path_index.shape[1] #Spatial dimension x 607 | rows = path_index.shape[0] #Spatial dimension y 608 | # Read as array the index 609 | index = path_index 610 | 611 | ################### Temperature ###################################### 612 | 613 | # Reading temperature data 614 | 615 | cols_t = path_temperature.shape[1] #Spatial dimension x 616 | rows_t = path_temperature.shape[0] #Spatial dimension y 617 | 618 | # Read as array 619 | Temp= path_temperature 620 | 621 | ################### Mask ###################################### # fine scale 622 | 623 | if path_mask !=0: 624 | # Reading mask data 625 | MaskFile = path_mask 626 | dataset_Mask = gdal.Open(MaskFile) 627 | 628 | cols_m = dataset_Mask.RasterXSize #Spatial dimension x 629 | rows_m = dataset_Mask.RasterYSize #Spatial dimension y 630 | 631 | # Read as array 632 | mask= dataset_Mask.ReadAsArray(0, 0, cols_m, rows_m).astype(np.float) 633 | 634 | ################################################################### 635 | ########### UNMIXING ############################################## 636 | ################################################################### 637 | 638 | Param = np.genfromtxt(path_fit) 639 | 640 | ## Param[1] indicates that we call the intercept of the linear regression. 641 | a0 = Param[1] 642 | ## Param[0] indicates that we call the slope of the linear regression. 643 | a1 = Param[0] 644 | 645 | T_unm = a0 + a1*index 646 | 647 | ### MASK 648 | 649 | if path_mask == 0: 650 | 651 | for ir in range(int(np.floor(rows/iscale))): 652 | for ic in range(int(np.floor(cols/iscale))): 653 | if Temp[ir,ic] == 0: 654 | for ir_2 in range(ir*iscale,(ir*iscale+iscale)): 655 | for ic_2 in range(ic*iscale,(ic*iscale+iscale)): 656 | T_unm[ir_2,ic_2]=0 657 | else: 658 | zz=np.where(mask==0) 659 | T_unm[zz]=0 660 | 661 | # This mask doesn't work because we don't have acces to the 20m temperature 662 | #I_mask = np.greater(Temp_jour_20m,0) # We eliminate the background pixels (not in the image) 663 | #for icol in range(cols): 664 | # for irow in range(rows): 665 | # if I_mask[irow,icol] == False: 666 | # T_unm[irow,icol]=0 667 | 668 | ################################################################### 669 | ########### SAVING RESULTS ######################################## 670 | ################################################################### 671 | 672 | # print('Unmixing Done') 673 | # print("index",index.shape) 674 | # print("Temp",Temp.shape) 675 | return T_unm 676 | 677 | 678 | import numpy as np 679 | import matplotlib.pyplot as plt 680 | from osgeo import gdal, gdalconst 681 | import math 682 | from scipy.spatial.distance import pdist, squareform 683 | import scipy.optimize as opt 684 | 685 | def correction_ATPRK_test(path_index, path_fit, path_temperature, path_unm, iscale, scc, block_size, sill, ran, path_out, path_out1, path_out2, path_out3,path_plot,T_unm): 686 | 687 | 688 | #################################################################### 689 | ################ LOADING DATA ###################################### 690 | #################################################################### 691 | 692 | b_radius = int(np.floor(block_size/2)) 693 | 694 | ################### INDEX Coarse ################################### 695 | 696 | cols_i = path_index.shape[1] #Spatial dimension x 697 | rows_i = path_index.shape[0] #Spatial dimension y 698 | # Read as array the index 699 | index = path_index 700 | 701 | cols_t = path_temperature.shape[1] #Spatial dimension x 702 | rows_t = path_temperature.shape[0] #Spatial dimension y 703 | Temp= path_temperature 704 | 705 | 706 | Tm=Temp 707 | # TempFile = '/content/drive/MyDrive/Ganglin/1_IMT/MCE_Projet3A/MODIS/MOD_2020/tifs_files/2km_of_MOD11A1.A2020001.h18v04.061.2021003092415.hdf.0015.tif' 708 | # dataset_Temp = gdal.Open(TempFile) 709 | 710 | # dtset=dataset_Temp 711 | 712 | # ################### UNMIXED Temperature ########################### 713 | 714 | # #dataset 715 | # T_unmix = gdal.Open(path_unm) 716 | 717 | cols = T_unm.shape[1] #Spatial dimension x 718 | rows = T_unm.shape[0] #Spatial dimension y 719 | 720 | # Read as array 721 | TT_unm = T_unm 722 | 723 | ################# CORRECTION ####################################### 724 | 725 | # We define the temperature at the coarse scale obtained from the regression. 726 | Param = np.genfromtxt(path_fit) 727 | # We take the parameters at the coarse scale 728 | a0 = Param[1] 729 | a1 = Param[0] 730 | # We define the matrix of added temperature 731 | T_unm_add =np.zeros((rows_t,cols_t)) 732 | mask=np.greater(Tm,0) # We eliminate the background pixels (not in the image) 733 | 734 | for i in range(rows_t): 735 | for j in range(cols_t): 736 | T_unm_add[i,j] = a0 + a1*index[i,j] 737 | if mask[i,j] == False: 738 | T_unm_add[i,j] = 0 739 | 740 | # We define the delta at the scale of the measured temperature as in the model. 741 | 742 | Delta_T = Tm - T_unm_add 743 | 744 | ##################################################################### 745 | ################ ATPK METHOD ######################################## 746 | ##################################################################### 747 | 748 | ##################################################################### 749 | ######### 1 Coarse semivariogram estimation ######################### 750 | ##################################################################### 751 | 752 | 753 | # We define the matrix of distances 754 | dist=np.zeros((2*block_size-1,2*block_size-1)) 755 | for i in range(-(block_size-1),block_size): 756 | for j in range(-(block_size-1),block_size): 757 | dist[i+(block_size-1),j+(block_size-1)]=20*iscale*np.sqrt(i**2+j**2) 758 | # We define the vector of different distances 759 | 760 | distances= np.unique(dist) 761 | 762 | new_matrix=np.zeros(((block_size)**2,3)) 763 | 764 | Gamma_coarse_temp=np.zeros((rows_t,cols_t,len(distances))) 765 | 766 | for irow in range(b_radius,rows_t-b_radius): 767 | for icol in range(b_radius,cols_t-b_radius): 768 | dt = Delta_T[irow-b_radius:irow+b_radius+1,icol-b_radius:icol+b_radius+1] 769 | for ir in range(block_size): 770 | for ic in range(block_size): 771 | new_matrix[ir*(block_size) + ic,0] = scc*ir 772 | new_matrix[ir*(block_size) + ic,1] = scc*ic 773 | new_matrix[ir*(block_size) + ic,2] = dt[ir,ic] 774 | pd_c = squareform( pdist( new_matrix[:,:2] ) ) 775 | pd_uni_c = np.unique(pd_c) 776 | N_c = pd_c.shape[0] 777 | for idist in range(len(pd_uni_c)): 778 | idd=pd_uni_c[idist] 779 | if idd==0: 780 | Gamma_coarse_temp[irow,icol,idist]=0 781 | else: 782 | ii=0 783 | for i in range(N_c): 784 | for j in range(i+1,N_c): 785 | if pd_c[i,j] == idd: 786 | ii=ii+1 787 | Gamma_coarse_temp[irow,icol,idist] = Gamma_coarse_temp[irow,icol,idist] + ( new_matrix[i,2] - new_matrix[j,2] )**2.0 788 | Gamma_coarse_temp[irow,icol,idist] = Gamma_coarse_temp[irow,icol,idist]/(2*ii) 789 | 790 | #Gamma_coarse_temp[np.isnan(Gamma_coarse_temp)]=0 791 | 792 | Gamma_coarse=np.zeros(len(distances)) 793 | for idist in range(len(pd_uni_c)): 794 | zz=np.nonzero(Gamma_coarse_temp[:,:,idist]) 795 | gg=Gamma_coarse_temp[zz[0],zz[1],idist] 796 | Gamma_coarse[idist]=np.mean(gg) 797 | 798 | Gamma_coarse[np.isnan(Gamma_coarse)]=0 799 | 800 | def Func_Gamma_cc(pd_uni_c, sill, ran): 801 | 802 | Gamma_c_temp = sill * (1 - np.exp(-pd_uni_c/(ran/3))) # EXPONENTIAL 803 | 804 | return Gamma_c_temp 805 | 806 | sillran_c=np.array([sill,ran]) 807 | 808 | ydata=Gamma_coarse 809 | xdata=pd_uni_c 810 | 811 | param_c, pcov_c = opt.curve_fit(Func_Gamma_cc, xdata, ydata,sillran_c,method='lm') 812 | # plt.plot(distances,param_c[0] * (1 - np.exp(-distances/(param_c[1]/3)))) 813 | # plt.plot(distances,Gamma_coarse) 814 | # plt.savefig(path_plot) 815 | # plt.close() 816 | 817 | ##################################################################### 818 | ######### 2 Deconvolution ########################################### 819 | ##################################################################### 820 | 821 | dist_matrix=np.zeros(((iscale*block_size)**2,2)) 822 | 823 | for ir in range(iscale*block_size): 824 | for ic in range(iscale*block_size): 825 | dist_matrix[ir*(iscale*block_size) + ic,0] = scc/iscale * ir 826 | dist_matrix[ir*(iscale*block_size) + ic,1] = scc/iscale * ic 827 | 828 | pd_f = squareform( pdist( dist_matrix[:,:2] ) ) 829 | pd_uni_f = np.unique(pd_f) 830 | N_f = pd_f.shape[0] 831 | 832 | dis_f=np.zeros((N_c,N_c,iscale*iscale,iscale,iscale)) 833 | 834 | for ii in range(block_size): # We select a coarse pixel 835 | for jj in range(block_size): # We select a coarse pixel 836 | temp_variable=[] 837 | for iiii in range(iscale): # We take all the fine pixels inside the chosen coarse pixel 838 | count=0 839 | for counter in range(jj*iscale+ii*block_size*iscale**2+iiii*block_size*iscale,jj*iscale+ii*block_size*iscale**2+iiii*block_size*iscale+iscale): 840 | res=np.reshape(pd_f[counter,:],(block_size*iscale,block_size*iscale)) 841 | for icoarse in range(block_size): 842 | for jcoarse in range(block_size): 843 | dis_f[ii*(block_size)+jj,icoarse*(block_size) + jcoarse,iiii*iscale+count,:,:] = res[icoarse*iscale:icoarse*iscale+iscale,jcoarse*iscale:jcoarse*iscale+iscale] 844 | count=count+1 845 | 846 | ####### Comment: dis_f is a matrix of distances. The first dimension select the coarse i pixel, the second dimension select the coarse j pixel, the third dimension, 847 | ####### select the fine pixel inside the i coarse pixel and the two last dimensions give us the distance from the fine pixel (third dimension) of i to all 848 | ####### the fine pixels of j. 849 | 850 | ####### We reshape dis_f to convert the last two dimensions iscale, iscale into one dimension iscale*iscale 851 | dis_f= np.reshape(dis_f,(N_c,N_c,iscale*iscale,iscale*iscale)) 852 | 853 | 854 | # Now we define Gamma_ff 855 | # We model the variogram gamma_ff as a exponential 856 | 857 | def Gamma_ff(pd_uni_c, sill, ran): 858 | 859 | Gamma_ff_temp=np.zeros(iscale*iscale) 860 | Gamma_cc=np.zeros((N_c,N_c)) 861 | 862 | for i_coarse in range(N_c): 863 | for j_coarse in range(N_c): 864 | temp_var=0 865 | for i_fine in range(iscale*iscale): 866 | for i in range(iscale*iscale): 867 | Gamma_ff_temp[i] = sill * (1 - np.exp(-dis_f[i_coarse,j_coarse,i_fine,i]/(ran/3))) # EXPONENTIAL 868 | temp_var = sum(Gamma_ff_temp) + temp_var 869 | Gamma_cc[i_coarse,j_coarse] = 1/(iscale**4) * temp_var 870 | 871 | # We need to classify the Gamma_cc values of pixels i and j in function of the distance between the coarse 872 | # i pixel and the coarse j pixel. 873 | 874 | Gamma_cc_m=np.zeros(len(pd_uni_c)) 875 | 876 | for idist in range(len(pd_uni_c)): 877 | ii=0 878 | for i_coarse in range(N_c): 879 | for j_coarse in range(N_c): 880 | if pd_c[i_coarse,j_coarse] == pd_uni_c[idist]: 881 | ii=ii+1 882 | Gamma_cc_m[idist] = Gamma_cc_m[idist] + Gamma_cc[i_coarse,j_coarse] 883 | Gamma_cc_m[idist] = Gamma_cc_m[idist]/ii 884 | 885 | Gamma_cc_r=Gamma_cc_m-Gamma_cc_m[0] 886 | 887 | return Gamma_cc_r 888 | 889 | sill=param_c[0] 890 | ran=param_c[1] 891 | 892 | sillran=np.array([sill,ran]) 893 | 894 | ydata=Gamma_coarse 895 | xdata=pd_uni_c 896 | 897 | param, pcov= opt.curve_fit(Gamma_ff, xdata, ydata,sillran,method='lm') 898 | 899 | ##################################################################### 900 | ######### 3 Estimation Gamma_cc and Gamma_fc ######################## 901 | ##################################################################### 902 | 903 | ####################### Gamma_cc estimation ######################### 904 | Gamma_ff_temp=np.zeros(iscale*iscale) 905 | Gamma_cc=np.zeros((N_c,N_c)) 906 | 907 | for i_coarse in range(N_c): 908 | for j_coarse in range(N_c): 909 | temp_var=0 910 | for i_fine in range(iscale*iscale): 911 | for i in range(iscale*iscale): 912 | Gamma_ff_temp[i] = param[0] * (1 - np.exp(-dis_f[i_coarse,j_coarse,i_fine,i]/(param[1]/3))) # EXPONENTIAL 913 | temp_var = sum(Gamma_ff_temp) + temp_var 914 | Gamma_cc[i_coarse,j_coarse] = temp_var/(iscale**4) 915 | 916 | # We need to classify the Gamma_cc values of pixels i and j in function of the distance between the coarse 917 | # i pixel and the coarse j pixel. 918 | 919 | # Gamma_cc_regularized estimation and comparison with Gamma_coarse 920 | Gamma_cc_m=np.zeros(len(pd_uni_c)) 921 | 922 | for idist in range(len(pd_uni_c)): 923 | ii=0 924 | for i_coarse in range(N_c): 925 | for j_coarse in range(N_c): 926 | if pd_c[i_coarse,j_coarse] == pd_uni_c[idist]: 927 | ii=ii+1 928 | Gamma_cc_m[idist] = Gamma_cc_m[idist] + Gamma_cc[i_coarse,j_coarse] 929 | Gamma_cc_m[idist] = Gamma_cc_m[idist]/ii 930 | 931 | Gamma_cc_r=Gamma_cc_m-Gamma_cc_m[0] 932 | 933 | # gamma_cc_regul compared to gamma_coarse 934 | Diff = Gamma_coarse - Gamma_cc_r 935 | 936 | ####################### Gamma_fc estimation ######################### 937 | 938 | Gamma_ff_temp=np.zeros(iscale*iscale) 939 | Gamma_fc=np.zeros((iscale*iscale,N_c)) 940 | 941 | for j_coarse in range(N_c): 942 | temp_var=0 943 | for i_fine in range(iscale*iscale): 944 | for i in range(iscale*iscale): 945 | Gamma_ff_temp[i] = param[0] * (1 - np.exp(-dis_f[int(np.floor(0.5*block_size**2)),j_coarse,i_fine,i]/(param[1]/3))) # EXPONENTIAL 946 | temp_var = sum(Gamma_ff_temp) 947 | Gamma_fc[i_fine,j_coarse] = temp_var/(iscale**2) 948 | 949 | ##################################################################### 950 | ######### 4 Weight estimation (lambdas) ############################# 951 | ##################################################################### 952 | 953 | vec_right = np.ones((block_size**2,1)) 954 | vec_bottom = np.append(np.ones(block_size**2),0) 955 | 956 | A = np.vstack((np.hstack((Gamma_cc,vec_right)),vec_bottom)) 957 | 958 | Ainv = np.linalg.inv(A) 959 | 960 | B=np.zeros((iscale*iscale,N_c+1)) 961 | lambdas_temp=np.zeros((iscale*iscale,N_c+1)) 962 | for i_fine in range(iscale*iscale): 963 | B[i_fine,:] = np.append(Gamma_fc[i_fine,:],1) 964 | lambdas_temp[i_fine,:] = np.dot(Ainv,B[i_fine,:]) 965 | 966 | # lambdas first dimension is the fine pixel inside the central coarse pixel in a (block_size x block_size) pixels block. 967 | # lambdas second dimension is the coars pixels in this block. 968 | 969 | lambdas =lambdas_temp[:,0:len(lambdas_temp[0])-1] 970 | 971 | ##################################################################### 972 | ######### 5 Fine Residuals estimation ############################### 973 | ##################################################################### 974 | 975 | mask_TT_unm=np.zeros((rows,cols)) 976 | indice_mask_TT=np.nonzero(TT_unm) 977 | mask_TT_unm[indice_mask_TT]=1 978 | 979 | # We obtain the delta at the scale of the unmixed temperature 980 | Delta_T_final =np.zeros((rows,cols)) 981 | # for ic in range(b_radius,math.floor(cols/iscale)-b_radius): 982 | # for ir in range(b_radius,math.floor(rows/iscale)-b_radius): 983 | # for ir_2 in range((ir-b_radius)*iscale,(ir+b_radius)*iscale+iscale): 984 | # for ic_2 in range((ic-b_radius)*iscale-2,(ic+b_radius)*iscale+iscale): 985 | # if mask_TT_unm[ir_2,ic_2]==0: 986 | # Delta_T_final[ir_2,ic_2] = 0 987 | # else: 988 | # temp_var = np.reshape(Delta_T[ir-b_radius:ir+b_radius+1,ic-b_radius:ic+b_radius+1],block_size**2) # We take the block of coarse pixels 989 | # # We multiply each coarse pixel in the block by the corresponding lambda and sum 990 | # Delta_T_final[ir_2,ic_2] = np.sum(lambdas[((ir_2-ir*iscale)*iscale+ic_2-ic*iscale)%lambdas.shape[0],:]*temp_var) 991 | 992 | 993 | for ic in range(b_radius,math.floor(cols/iscale)-b_radius): 994 | for ir in range(b_radius,math.floor(rows/iscale)-b_radius): 995 | for ir_2 in range(ir*iscale,(ir*iscale+iscale)): 996 | for ic_2 in range(ic*iscale,(ic*iscale+iscale)): 997 | if mask_TT_unm[ir_2,ic_2]==0: 998 | Delta_T_final[ir_2,ic_2] = 0 999 | else: 1000 | temp_var = np.reshape(Delta_T[ir-b_radius:ir+b_radius+1,ic-b_radius:ic+b_radius+1],block_size**2) # We take the block of coarse pixels 1001 | # We multiply each coarse pixel in the block by the corresponding lambda and sum 1002 | Delta_T_final[ir_2,ic_2] = np.sum(lambdas[(ir_2-ir*iscale)*iscale+ic_2-ic*iscale,:]*temp_var) 1003 | 1004 | # We correct the unmixed temperature using the delta 1005 | 1006 | TT_unmixed_corrected = TT_unm + Delta_T_final 1007 | 1008 | # plt.figure() 1009 | # plt.imshow(Temp) 1010 | # plt.colorbar() 1011 | # plt.savefig("tmp_fig/Temp.png") 1012 | 1013 | # plt.figure() 1014 | # plt.imshow(TT_unm) 1015 | # plt.colorbar() 1016 | # plt.savefig("tmp_fig/TT_unm.png") 1017 | 1018 | # plt.figure() 1019 | # plt.imshow(Delta_T_final) 1020 | # plt.colorbar() 1021 | # plt.savefig("tmp_fig/Delta_T_final.png") 1022 | 1023 | # plt.figure() 1024 | # plt.imshow(TT_unmixed_corrected) 1025 | # plt.colorbar() 1026 | # plt.savefig("tmp_fig/TT_unmixed_corrected.png") 1027 | # print("index",index.shape) 1028 | # print("Temp",Temp.shape) 1029 | return TT_unmixed_corrected, Delta_T_final, TT_unm 1030 | 1031 | def setup_seed(seed): 1032 | import random 1033 | torch.manual_seed(seed) 1034 | torch.cuda.manual_seed_all(seed) 1035 | np.random.seed(seed) 1036 | random.seed(seed) 1037 | torch.backends.cudnn.deterministic = True 1038 | 1039 | --------------------------------------------------------------------------------