├── 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 | | File description |
7 | |
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 | 
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 | | File description | File description |
10 | |
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 | 
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 | 
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 |
--------------------------------------------------------------------------------