├── BufferMasks.m ├── CDI.m ├── CheckImagesPath.m ├── DetectAbsSnow.m ├── DetectPotentialCloud.m ├── DetectPotentialCloudShadow.m ├── DetectPotentialFalsePositivePixels.m ├── DetectPotentialPixels.m ├── DetectSnow.m ├── DetectWater.m ├── EnhanceLine.m ├── ErodeCommissons.m ├── FmaskParameters.m ├── Fmask_4_7.m ├── GRIDobj.m ├── GRIDobj2geotiff.m ├── LICENSE ├── LoadAuxiData.m ├── LoadData.m ├── LoadSensorType.m ├── MatchCloudShadow.m ├── NDBI.m ├── NDSI.m ├── NDVI.m ├── NormalizaCirrusDEM.m ├── NormalizeBT.m ├── ObjMeta.m ├── ObjTOABT.m ├── ObservMask.m ├── ProjectDEM2Plane.m ├── README.md ├── ReadMetadataMSI.m ├── ReadS2InspireXML.m ├── ReadS2RadiometricOffset.m ├── ReadSunViewGeometryMSI.m ├── ReadSunViewGeometryMSIBaseline04.m ├── Readopentopo.m ├── Saturate.m ├── arcslope.m ├── aspect.m ├── autoFmask.m ├── autoFmaskBatch.m ├── findMatdetecFootprint.asv ├── findMatdetecFootprint.m ├── geoimread_lcog_matlab2020a.m ├── geotiffinfo_lcog.m ├── geotiffinfo_lcog_matlab2020a.m ├── geotiffread_lcog.m ├── geotiffread_lcog_matlab2020a.m ├── getRealCloudPosition.m ├── getRealCloudPositionS2.m ├── getSensorViewGeo.m ├── getcoordinates.m ├── imcircle.m ├── inpaint_nans.m ├── lndhdrread.m ├── nd2toarbt.m ├── nd2toarbt_msi.m ├── nd2toarbt_msi2.m ├── pixel2pixv.m ├── probThin.m ├── problBrightness.m ├── problSpectralVaribility.m ├── problTemperature.m ├── probwBrightness.m ├── probwTemperature.m ├── refmat2XY.m ├── reproject2utm.m ├── stratiedSampleHanlder.m └── xml2struct.m /BufferMasks.m: -------------------------------------------------------------------------------- 1 | function [cloud,shadow,snow] = BufferMasks(pcloud,cdpix,pshadow,csdpix,psnow,sdpix) 2 | % BUFFERMASKS Dilate cloud/cloud shadow/snow with cdpix/csdpix/sdpix with 3 | % default value of 3/3/3. 4 | % 5 | % Syntax 6 | % 7 | % [cloud,shadow,snow] = 8 | % BufferMasks(pcloud,cdpix,pshadow,csdpix,psnow,sdpix) 9 | % 10 | % Description 11 | % 12 | % Dilate cloud/cloud shadow/snow with cdpix/csdpix/sdpix with 13 | % default value of 3/3/3. 14 | % 15 | % Input arguments 16 | % pcloud Cloud mask. 17 | % cldpix Dilated number of pixels for cloud with default value of 3. 18 | % pshadow Cloud shadow mask. 19 | % sdpix Dilated number of pixels for cloud shadow value of 3. 20 | % psnow Snow mask. 21 | % snpix Dilated number of pixels for snow value of 3. 22 | % 23 | % Output arguments 24 | % 25 | % cloud Final dilated cloud mask. 26 | % shadow Final dilated cloud shadow mask. 27 | % snow Final dilated snow mask. 28 | % 29 | % Example 30 | % 31 | % [cloud,shadow,snow] = 32 | % BufferMasks(pcloud,3,pshadow,3,psnow,3); 33 | % 34 | % 35 | % Author: Shi Qiu (shi.qiu@ttu.edu) 36 | % Date: 2. November, 2017 37 | 38 | % buffer cloud 39 | if cdpix>0 40 | CEs=strel('square',2*cdpix+1); 41 | cloud=imdilate(pcloud,CEs); 42 | clear pcloud CEs cdpix; 43 | else 44 | cloud=pcloud; 45 | clear pcloud 46 | end 47 | % buffer cloud shadow 48 | if csdpix>0 49 | CSEs=strel('square',2*csdpix+1); 50 | shadow=imdilate(pshadow,CSEs); 51 | clear pshadow CSEs csdpix; 52 | else 53 | shadow=pshadow; 54 | clear pshadow 55 | end 56 | % buffer snow 57 | if sdpix>0 58 | SEs=strel('square',2*sdpix+1); 59 | snow=imdilate(psnow,SEs); 60 | clear psnow SEs sdpix; 61 | else 62 | snow=psnow; 63 | clear psnow 64 | end 65 | end 66 | 67 | -------------------------------------------------------------------------------- /CDI.m: -------------------------------------------------------------------------------- 1 | function cdi = CDI(S2band7,S2band8,S2band8A) 2 | %CDI This is used seprate bright surface from cloud by following David, 3 | %2018 RSE 4 | if ~isempty(S2band7)&&~isempty(S2band8) 5 | ratio_8A_8 = S2band8./S2band8A; 6 | clear S2band8; 7 | ratio_8A_7 = S2band7./S2band8A; 8 | clear S2band7 S2band8A; 9 | 10 | std_ratio_8A_8 =stdfilt(ratio_8A_8, true(7)); 11 | clear ratio_8A_8; 12 | var_ratio_8A_8 = std_ratio_8A_8 .^2; 13 | clear std_ratio_8A_8; 14 | 15 | std_ratio_8A_7 = stdfilt(ratio_8A_7, true(7)); 16 | clear ratio_8A_7; 17 | var_ratio_8A_7 = std_ratio_8A_7 .^2; 18 | clear std_ratio_8A_7; 19 | 20 | cdi = (var_ratio_8A_7-var_ratio_8A_8)./(var_ratio_8A_8+var_ratio_8A_7); 21 | 22 | % dim = size(cdi); 23 | % cdi = imresize(cdi,1/7,'box'); 24 | % cdi = ordfilt2(cdi,1,ones(3,3)); 25 | % cdi = imresize(cdi,dim,'nearest'); 26 | % clear dim; 27 | 28 | 29 | % h = fspecial('average', 21); 30 | % cdi = filter2(h, cdi); 31 | else 32 | cdi = []; 33 | end 34 | end 35 | 36 | 37 | -------------------------------------------------------------------------------- /CheckImagesPath.m: -------------------------------------------------------------------------------- 1 | function [num_all_images, sensors, paths, info_count_text] = CheckImagesPath(path_data) 2 | %CHECKIMAGEPATH Ensure the input path is right to find a Landsats 4-8, and 3 | %Sentinel 2 image(s). 4 | % path_data - the input path 5 | 6 | % searching deeps. 0 is default. 7 | [image_types_paths] = CheckImagePath(path_data,0); 8 | 9 | % all count info foe searched images at current folder. 10 | num_all_images = size(image_types_paths,1); 11 | num_L4_tm = 0; 12 | num_L5_tm = 0; 13 | num_L6_tm = 0; 14 | num_L7_tm_plus = 0; 15 | num_L8_oli_tirs = 0; 16 | num_S2A_msi = 0; 17 | num_S2B_msi = 0; 18 | 19 | % key info: the senor and path are needed as outputs, the first one is 20 | % to determine the default parameters for Fmask, and the second one is 21 | % to determine the path loading all data. 22 | sensors = []; 23 | paths = []; 24 | info_count_text = []; 25 | 26 | for i_all = 1:num_all_images 27 | % c: current. 28 | cimage_sensor = image_types_paths{i_all,1}; 29 | cimage_num = image_types_paths{i_all,2}; 30 | cimage_type = Convert2ImageType(cimage_sensor,cimage_num); 31 | clear cimage_num; 32 | cimage_path = image_types_paths{i_all,3}; 33 | switch cimage_type 34 | case {'Landsat 4 TM'} 35 | num_L4_tm=num_L4_tm+1; 36 | case {'Landsat 5 TM'} 37 | num_L5_tm=num_L5_tm+1; 38 | case {'Landsat 6 TM'} 39 | num_L6_tm=num_L6_tm+1; 40 | case {'Landsat 7 ETM+'} 41 | num_L7_tm_plus=num_L7_tm_plus+1; 42 | case {'Landsat 8 OLI/TIRS'} 43 | num_L8_oli_tirs=num_L8_oli_tirs+1; 44 | case {'Sentinel 2A MSI'} 45 | % further check Sentinel 2 image folder, see there is 46 | % .SAFE. 47 | if contains(cimage_path,'.SAFE') 48 | num_S2A_msi=num_S2A_msi+1; 49 | else 50 | cimage_sensor = [];% no available Sentinel 2 data. 51 | end 52 | case {'Sentinel 2B MSI'} 53 | % see there is .SAFE. 54 | if contains(cimage_path,'.SAFE') 55 | num_S2B_msi=num_S2B_msi+1; 56 | else 57 | cimage_sensor = [];% no available Sentinel 2 data. 58 | end 59 | end 60 | if ~isempty(cimage_sensor) 61 | sensors = [sensors;{cimage_sensor}]; 62 | paths = [paths;{cimage_path}]; 63 | end 64 | clear cimage_sensor; 65 | end 66 | % renew num_all_images 67 | num_all_images = length(sensors); 68 | % used to notice user. 69 | text_line = 0; 70 | % multide 71 | if isequal(num_all_images,num_L4_tm)||... 72 | isequal(num_all_images,num_L5_tm)||... 73 | isequal(num_all_images,num_L6_tm)||... 74 | isequal(num_all_images,num_L7_tm_plus)||... 75 | isequal(num_all_images,num_L8_oli_tirs)||... 76 | isequal(num_all_images,num_S2A_msi)||... 77 | isequal(num_all_images,num_S2B_msi) 78 | % only for 1 type image 79 | if num_L4_tm > 0 80 | text_line = text_line+1; 81 | info_count_text{text_line} = sprintf('%s Landsat 4 TM images are found at ''%s''\n',... 82 | num2str(num_L4_tm),path_data); 83 | end 84 | if num_L5_tm > 0 85 | text_line = text_line+1; 86 | info_count_text{text_line} = sprintf('%s Landsat 5 TM images are found at ''%s''\n',... 87 | num2str(num_L5_tm),path_data); 88 | end 89 | if num_L6_tm > 0 90 | text_line = text_line+1; 91 | info_count_text{text_line} = sprintf('%s Landsat 6 TM images are found at ''%s''\n',... 92 | num2str(num_L6_tm),path_data); 93 | end 94 | if num_L7_tm_plus > 0 95 | text_line = text_line+1; 96 | info_count_text{text_line} = sprintf('%s Landsat 7 ETM+ images are found at ''%s''\n',... 97 | num2str(num_L7_tm_plus),path_data); 98 | end 99 | if num_L8_oli_tirs > 0 100 | text_line = text_line+1; 101 | info_count_text{text_line} = sprintf('%s Landsat 8 OLI/TIRS images are found at ''%s''\n',... 102 | num2str(num_L8_oli_tirs),path_data); 103 | end 104 | if num_S2A_msi > 0 105 | text_line = text_line+1; 106 | info_count_text{text_line} = sprintf('%s Sentinel 2A MSI images are found at ''%s''\n',... 107 | num2str(num_S2A_msi),path_data); 108 | end 109 | if num_S2B_msi > 0 110 | text_line = text_line+1; 111 | info_count_text{text_line} = sprintf('%s Sentinel 2B MSI images are found at ''%s''\n',... 112 | num2str(num_S2B_msi),path_data); 113 | end 114 | else 115 | text_line = text_line+1; 116 | info_count_text{text_line} = sprintf(... 117 | 'A total of %s images (as follows) are found at ''%s''\n',... 118 | num2str(num_all_images), path_data); 119 | if num_L4_tm > 0 120 | text_line = text_line+1; 121 | info_count_text{text_line} = sprintf('%s Landsat 4 TM images\n',... 122 | num2str(num_L4_tm)); 123 | end 124 | if num_L5_tm > 0 125 | text_line = text_line+1; 126 | info_count_text{text_line} = sprintf('%s Landsat 5 TM images\n',... 127 | num2str(num_L5_tm)); 128 | end 129 | if num_L6_tm > 0 130 | text_line = text_line+1; 131 | info_count_text{text_line} = sprintf('%s Landsat 6 TM images\n',... 132 | num2str(num_L6_tm)); 133 | end 134 | if num_L7_tm_plus > 0 135 | text_line = text_line+1; 136 | info_count_text{text_line} = sprintf('%s Landsat 7 ETM+ images\n',... 137 | num2str(num_L7_tm_plus)); 138 | end 139 | if num_L8_oli_tirs > 0 140 | text_line = text_line+1; 141 | info_count_text{text_line} = sprintf('%s Landsat 8 OLI/TIRS images\n',... 142 | num2str(num_L8_oli_tirs)); 143 | end 144 | if num_S2A_msi > 0 145 | text_line = text_line+1; 146 | info_count_text{text_line} = sprintf('%s Sentinel 2A MSI images\n',... 147 | num2str(num_S2A_msi)); 148 | end 149 | if num_S2B_msi > 0 150 | text_line = text_line+1; 151 | info_count_text{text_line} = sprintf('%s Sentinel 2B MSI images\n',... 152 | num2str(num_S2B_msi)); 153 | end 154 | end 155 | 156 | % if no data is found, give mention info. 157 | if isempty(sensors) 158 | text_line = text_line+1; 159 | info_count_text{text_line} = sprintf('%s available images are found at ''%s''\n',... 160 | '0',path_data); 161 | text_line = text_line+1; 162 | info_count_text{text_line} = sprintf('Please ensure the path is correct\n'); 163 | end 164 | % fprintf(info_count); 165 | end 166 | 167 | function [image_types_paths] = CheckImagePath(path_data,subfolder_level) 168 | %CHECKIMAGEPATH Ensure the input path is right to find a Landsats 4-8, and 169 | %Sentinel 2 image(s). 170 | % path_data - the input path 171 | % subfolder_level - the level of subfolders that can be used to limit 172 | % searching deeps. 0 is default. 173 | 174 | % If the searching deeps are more than 5, stop and return; 175 | if subfolder_level > 5 176 | image_types_paths = []; 177 | return; 178 | end 179 | image_types_paths = []; 180 | % first search the image(s) at current folder.image_types_paths 181 | [sensor,num_Lst,~,~] = LoadSensorType(path_data); 182 | if isempty(sensor) 183 | % if no available image at current folder, 184 | % and search its subfolders. 185 | subfolders = dir(path_data); 186 | for i_sub=1:length(subfolders) 187 | % filter out the file names starting with '.', that is not 188 | % right folder (system crashes). 189 | if strcmp(subfolders(i_sub).name(1),'.') 190 | continue; 191 | end 192 | % go to search the images at each subfolder 193 | path_subfoler = fullfile(subfolders(i_sub).folder,... 194 | subfolders(i_sub).name); 195 | [image_types_paths_sub] = CheckImagePath(path_subfoler,subfolder_level+1); 196 | if ~isempty(image_types_paths_sub) 197 | image_types_paths =[image_types_paths;image_types_paths_sub]; 198 | end 199 | end 200 | 201 | else 202 | % successfully searched a supported image. 203 | % and, return the sensor and the image path 204 | image_types_paths = [image_types_paths;{sensor,num_Lst,path_data}]; 205 | end 206 | end 207 | function image_type = Convert2ImageType(sensor,num_Lst) 208 | % CONVERT@IMAGETYPE Contruct image type from the sensor and num 209 | 210 | switch sensor 211 | case 'L_TM' 212 | image_type = ['Landsat ',num_Lst,' TM']; 213 | case 'L_ETM_PLUS' 214 | image_type = ['Landsat ',num_Lst,' ETM+']; 215 | case 'L_OLI_TIRS' 216 | image_type = ['Landsat ',num_Lst,' OLI/TIRS']; 217 | case 'S_MSI' 218 | image_type = ['Sentinel ',num_Lst,' MSI']; 219 | otherwise 220 | image_type=[]; 221 | % error(['Errors occur when searching images at ''', path_data],'''.'); 222 | end 223 | end 224 | 225 | -------------------------------------------------------------------------------- /DetectAbsSnow.m: -------------------------------------------------------------------------------- 1 | function abs_snow = DetectAbsSnow(band_green,band_green_statu,ndsi,psnow,resolution) 2 | %DETECTABSSNOW Select absolute snow/ice pixels using spectral-contextual 3 | % features. 4 | % 5 | % Syntax 6 | % 7 | % abs_snow = 8 | % DetectAbsSnow(band_green,band_green_statu,ndsi,psnow,resolution) 9 | % 10 | % Description 11 | % 12 | % A spectral-contextual snow index (SCSI) is used to select 100% 13 | % snow/ice pixels. SCSI10 is computed here, of which 10 indicates 10 14 | % kilometers-by-10 kilometers window size, that 333-by-333 pixels for 15 | % Landsat 30 meters image and 501-by-501 pixels for Sentinel-2 20 16 | % meters image (Fmask runs at this 20 meters). That window size is 17 | % large enough to capture the various contexts for clouds, but still 18 | % give us soomth variations for pure snow/ice. 19 | % 20 | % Input arguments 21 | % 22 | % band_green Green band. 23 | % band_green_statu Statured pixels at green band. 24 | % psnow Potential snow/ice. 25 | % resolution Spatial resolution of input image. 26 | % 27 | % Output arguments 28 | % 29 | % abs_snow Absolute snow/ice. 30 | % 31 | % Example 32 | % 33 | % abs_snow = 34 | % DetectAbsSnow(band_green,band_green_statu,ndsi,psnow,resolution) 35 | % 36 | % 37 | % Author: Shi Qiu (shi.qiu@uconn.edu) 38 | % Date: 21. January, 2018 39 | 40 | % which is equal to 51*51 % when have snow/ice pixels. 41 | if sum(psnow(:))> 110889 42 | % sometimes Nan will appear in this green band. 43 | band_green_filled = fillmissing(band_green,'constant',0); 44 | clear band_green; 45 | % window size is 10 km. 46 | radius = fix(5000/resolution); 47 | clear resolution; 48 | win_size=2*radius+1; 49 | % compute SCSI. 50 | % clip to small arrays (16 smalls); 51 | num_clips = 5; % 5 cippes 52 | [size_row, size_colum] =size(band_green_filled); 53 | num_per_row = round((size_row)/num_clips); 54 | 55 | row_nums = zeros([1,num_clips],'double'); 56 | row_nums(1:num_clips-1)=num_per_row; 57 | % the last one 58 | row_nums(num_clips) = size_row - num_per_row*(num_clips-1); 59 | 60 | scsi = zeros([size_row, size_colum],'double'); 61 | % the first clip 62 | 63 | 64 | for i_row = 1:num_clips 65 | row_ed = sum(row_nums(1:i_row)); 66 | row_st = row_ed - row_nums(i_row) + 1; 67 | 68 | row_st_exp = row_st - radius; 69 | row_ed_exp = row_ed + radius; 70 | row_st_small = radius + 1; 71 | row_ed_small = radius + row_nums(i_row); 72 | % the first clip 73 | if row_st_exp < 1 74 | row_st_exp =1; 75 | row_st_small =1; 76 | row_ed_small = row_nums(i_row);% no the first exp 77 | end 78 | % the last clip 79 | if row_ed_exp > size_row 80 | row_ed_exp = size_row; 81 | % row_ed_small = radius + row_nums(i_row); 82 | end 83 | scsi_small= stdfilt(band_green_filled(row_st_exp:row_ed_exp,:),... 84 | true(win_size)).*(1-ndsi(row_st_exp:row_ed_exp,:)); 85 | scsi(row_st:row_ed,:) = scsi_small(row_st_small:row_ed_small,:); 86 | clear scsi_small; 87 | end 88 | % scsi=stdfilt(band_green_filled,true(win_size)).*(1-ndsi); 89 | clear band_green_filled ndsi win_size; 90 | % only get snow/ice pixels from all potential snow/ice pixels, and 91 | % do not select the saturated pixels which may be cloud! 92 | abs_snow=scsi<9&psnow&(~band_green_statu); 93 | clear scsi psnow band_green_statu; 94 | else 95 | abs_snow=[]; 96 | end 97 | end 98 | -------------------------------------------------------------------------------- /DetectPotentialCloud.m: -------------------------------------------------------------------------------- 1 | function [sum_clr,cloud,idused,t_templ,t_temph]=DetectPotentialCloud(... 2 | data_meta,mask,water,data_toabt, dem, ndvi,ndsi,ndbi,idplcd,... 3 | whiteness,HOT,wpt,cldprob) 4 | %DETECTPOTENTIALCLOUD Detect potential clouds using scene-based method. 5 | % 6 | % Syntax 7 | % 8 | % [sum_clr,cloud,idlnd,t_templ,t_temph]= 9 | % DetectPotentialCloud(data_meta,mask,water,data_toabt, dem, ndvi,ndsi, 10 | % ndbi,idplcd,whiteness,HOT,wpt,cldprob) 11 | % 12 | % Description 13 | % 14 | % Several cloud probabilities are combinated together to capture cloud 15 | % features of white, bright, cold, and/or high. 16 | % 17 | % Input arguments 18 | % 19 | % data_meta Metadata including [row size, column size]. 20 | % mask Observation mask (outside). 21 | % water Water mask. 22 | % data_toabt TOA reflectance and BT. 23 | % dem DEM data. 24 | % ndvi NDVI. 25 | % ndsi NDSI. 26 | % ndbi NDBI. 27 | % idplcd Absolute clear sky pixels. 28 | % whiteness Whitness. 29 | % HOT Data derived from the HOT transform. 30 | % wpt Weight of thin probability (0.3 for Landsat 8 and 31 | % 0.5 for Sentinel 2). 32 | % cldprob Cloud probability threshold to segment clouds from 33 | % surface. 34 | % 35 | % Output arguments 36 | % 37 | % sum_clr The total number of clear sky pixels. 38 | % cloud Potential clouds. 39 | % idlnd Clear sky land pixels. 40 | % t_templ Low level temperature (78.5 percentile). 41 | % t_temph High level temperature (81.5 percentile). 42 | % 43 | % 44 | % 45 | % Author: Shi Qiu (shi.qiu@uconn.edu) 46 | % Date: 20. January, 2018 47 | 48 | % inputs: BandCirrus BandBT BandSWIR1 SatuGreen SatuRed 49 | cloud = zeros(data_meta.Dim,'uint8'); % cloud mask 50 | %% Constants Parameters 51 | l_pt=0.175; % low percent 52 | h_pt=1-l_pt; % high percent 53 | 54 | %% Step 2: calcualte cloud probability 55 | % select clear sky pixels for land and water, respectively. 56 | idclr=idplcd==false&mask==1; 57 | sum_clr=sum(idclr(:)); 58 | idlnd = idclr&water==false; 59 | idwt = idclr&water==true;%&data(:,:,6)<=300; 60 | 61 | t_templ = 0; 62 | t_temph = 0; 63 | idused = []; 64 | % 99.9% TO 99.99% 65 | if sum_clr <= 40000 % when potential cloud cover less than 0.1%, directly screen all PCPs out. 66 | cloud(idplcd==true)=1; % all cld 67 | cloud(mask==0)=0; 68 | else 69 | %%%%%%%%%%% thin cloud prob for both water and land%%%%%%%%%%% 70 | prob_thin = 0; % there is no contribution from the new bands 71 | if ~isempty(data_toabt.BandCirrus) % Landsat 4~7 72 | prob_thin = probThin(data_toabt.BandCirrus); 73 | data_toabt.BandCirrus = []; 74 | end 75 | 76 | %%%%%%%%%%%%%%%%%%%%%%cloud prob over water%%%%%%%%%%%%%%%%%%% 77 | wprob_temp=1; 78 | if ~isempty(data_toabt.BandBT) 79 | wprob_temp = probwTemperature(data_toabt.BandBT,idwt,h_pt); 80 | end 81 | 82 | wprob_brightness = probwBrightness(data_toabt.BandSWIR1); 83 | data_toabt.BandSWIR1 = []; 84 | 85 | %%%%%%%%%%%%%%%%%%%%%%cloud prob over land%%%%%%%%%%%%%%%%%%%% 86 | lndptm=100*sum(idlnd(:))/sum(mask(:)); 87 | if lndptm >= 0.1 88 | idused=idlnd; 89 | else % when having no enough clear land pixels, we used all PCPs to calculate clear land basics. 90 | idused=idclr; 91 | end 92 | clear lndptm; 93 | 94 | lprob_temp=1; 95 | lprob_brightness=1; 96 | 97 | 98 | if ~isempty(data_toabt.BandBT) % if have BT. normalize it using DEMs and use it to calcualte temperature probability. 99 | data_toabt.BandBT = NormalizeBT( data_meta.Dim,dem,mask,data_toabt.BandBT,idused,l_pt,h_pt,data_meta.Resolution(1)); 100 | [lprob_temp, t_templ,t_temph]=problTemperature(data_toabt.BandBT,idused,l_pt,h_pt); 101 | else % if have no BT, use HOT probability instead of temperature probability. 102 | lprob_brightness = problBrightness(HOT,idused,l_pt,h_pt); 103 | clear HOT l_pt; 104 | end 105 | % clear idused; 106 | 107 | lprob_vari = problSpectralVaribility(ndvi,ndsi,ndbi,whiteness,data_toabt.SatuGreen,data_toabt.SatuRed); 108 | clear ndvi ndsi whiteness; 109 | 110 | %%%%%%%%%%%%%%%%%%%%%%%%%%final clouds%%%%%%%%%%%%%%%%%%%%%%% 111 | % [Final prob mask (water)] 112 | wprob_final=wprob_temp.*wprob_brightness + wpt.*prob_thin; % cloud over water probability 113 | clear wprob_temp wprob_brightness; 114 | wprob_final=100.*wprob_final; % convert percentage 115 | wclr_h=prctile(wprob_final(idwt),100*h_pt); 116 | clear idwt; 117 | % Final prob mask (land) 118 | lprob_final=lprob_temp.*lprob_vari.*lprob_brightness + wpt.*prob_thin; % cloud over land probability 119 | % clear lprob_temp lprob_vari lprob_brightness prob_thin wpt; 120 | clear lprob_temp lprob_vari prob_thin wpt; 121 | lprob_final=100.*lprob_final; % convert percentage 122 | clr_h=prctile(lprob_final(idlnd),100*h_pt); 123 | clear h_pt; 124 | 125 | wclr_max=wclr_h+cldprob;% dynamic threshold (water) 126 | clr_max=clr_h+cldprob;% dynamic threshold (land) 127 | clear cldprob; 128 | clear idclr; 129 | 130 | % all potential clouds 131 | % % cloud(idclr==true)=-1; 132 | 133 | id_final_cld = SegementClouds(idplcd,water,data_toabt.BandBT,t_templ,lprob_final,wprob_final,clr_max,wclr_max); 134 | clear clr_max wclr_max; 135 | cloud(id_final_cld)=1; 136 | clear id_final_cld; 137 | 138 | cloud(mask==0)=0; 139 | clear mask; 140 | end 141 | end 142 | 143 | %%%%%%%%%%%%%%%%%%%%%%%%%%final clouds%%%%%%%%%%%%%%%%%%%%%%% 144 | function id_final_cld = SegementClouds(idplcd,water,bt,t_templ,lprob,wprob,clr_max,wclr_max) 145 | % final clouds 146 | id_final_cld=idplcd==true&((lprob>clr_max&water==0)|...% cloud over land 147 | (wprob>wclr_max&water==1));% thin cloud over water 148 | if ~isempty(bt) % if have BT. 149 | id_final_cld=id_final_cld|(bt200)=1; 58 | masker_shadow(masker_observation==0)=255; 59 | end 60 | 61 | % CSC+C stratied on cos i with 0.1 increasement. a total of 50,000 pixels. 62 | function [data_nir,data_swir] = getDataTopoCorrected(data_nir_ori,data_swir_ori,index_exclude_cloud_water,sun_zenith_deg,sun_azimuth_deg, slope_data,aspect_data,dim,resl ) 63 | % History: 64 | % 1. Create this function. (1. January, 2017) 65 | % 2. A total of samples are revised as 40,000 from 50,000. (8. March, 2018) 66 | % 3. When c is calculated as Nan, this function will not make the topo 67 | % correction. (8. March, 2018) 68 | sun_zenith_rad=deg2rad(sun_zenith_deg); 69 | sun_zenith_cos=cos(sun_zenith_rad); 70 | sun_zenith_sin=sin(sun_zenith_rad); 71 | clear sun_zenith_deg sun_zenith_rad sun_zenith_rad; 72 | cos_sita=sun_zenith_cos.*cos(deg2rad(slope_data))+sun_zenith_sin.*sin(deg2rad(slope_data)).*cos(deg2rad(sun_azimuth_deg-aspect_data)); 73 | clear aspect_data; 74 | cos_sita_exclude_cloud=cos_sita(index_exclude_cloud_water); 75 | % random stratied sampling 76 | cos_sita_min=min(cos_sita_exclude_cloud); 77 | cos_sita_max=max(cos_sita_exclude_cloud); 78 | % total_sample=50000; 79 | total_sample=40000; 80 | cos_sita_interval=0.1; 81 | samples_ids= stratiedSampleHanlder(cos_sita_exclude_cloud,cos_sita_min,cos_sita_max,dim,total_sample,cos_sita_interval,0,resl); 82 | cos_sita_samples=cos_sita_exclude_cloud(samples_ids); 83 | clear cos_sita_exclude_cloud cos_sita_min cos_sita_max total_sample cos_sita_interval; 84 | % for NIR 85 | data_nir_ori_tmp=data_nir_ori(index_exclude_cloud_water); 86 | data_samples_nir=data_nir_ori_tmp(samples_ids); 87 | clear data_nir_ori_tmp; 88 | c_fitted=polyfit(double(cos_sita_samples),double(data_samples_nir),1); 89 | % figure;plot(double(cos_sita_samples),double(data_samples_nir),'r.'); 90 | c=c_fitted(1,2)/c_fitted(1,1); 91 | 92 | clear c_fitted; 93 | if isnan(c) 94 | data_nir=data_nir_ori; 95 | clear data_nir_ori; 96 | else 97 | data_nir=double(data_nir_ori).*(cos(deg2rad(slope_data)).*sun_zenith_cos+c)./(cos_sita+c); 98 | clear data_nir_ori; 99 | end 100 | % for SWIR 101 | data_swir_ori_tmp=data_swir_ori(index_exclude_cloud_water); 102 | data_samples_swir=data_swir_ori_tmp(samples_ids); 103 | clear data_swir_ori_tmp; 104 | c_fitted=polyfit(double(cos_sita_samples),double(data_samples_swir),1); 105 | c=c_fitted(1,2)/c_fitted(1,1); 106 | clear c_fitted samples_ids; 107 | if isnan(c) 108 | data_swir=data_swir_ori; 109 | clear data_swir_ori; 110 | else 111 | data_swir=double(data_swir_ori).*(cos(deg2rad(slope_data)).*sun_zenith_cos+c)./(cos_sita+c); 112 | clear data_swir_ori; 113 | end 114 | end 115 | -------------------------------------------------------------------------------- /DetectPotentialFalsePositivePixels.m: -------------------------------------------------------------------------------- 1 | function pfpl = DetectPotentialFalsePositivePixels(mask, psnow,slope, ndbi,ndvi,bt,cdi,water,resolution) 2 | %DETECTPOTENTIALFALSEPOSITIVEPIXELS 3 | % remove the water pixels at the final layer. by Shi Mar.27, 2018. 4 | %% urban 5 | ndbi = EnhanceLine(ndbi); % enhance the ndbi to high urban/built-up area. 6 | pfpl = (ndbi > 0)&(ndbi > ndvi)&mask&water==0;% urban 7 | 8 | %% remove cloudy pixels using ostu method for thermal band. 9 | if sum(pfpl(:))>0 % have urban pixels 10 | if ~isempty(bt) 11 | % ostu's method to determine the temperature therhold to 12 | % seperate clouds from urban/built-up area. 13 | bt_pfpl = bt(pfpl==1); 14 | t = graythresh(bt_pfpl); 15 | % get the thershold 16 | BW = imbinarize(bt_pfpl,t); 17 | clear t; 18 | cold_tmp = min(bt_pfpl(BW)); 19 | clear bt_pfpl BW; 20 | if ~isempty(cold_tmp) 21 | clouds_tmp = bt < cold_tmp(1); 22 | pfpl(clouds_tmp==1)=0; 23 | end 24 | clear bt cold_tmp 25 | % figure; imshow(pfpl); 26 | elseif ~isempty(cdi) 27 | % pfpl(cdi < -1)=0; % by following David, 2018 RSE 28 | pfpl(cdi < -0.8)=0; % by following David, 2018 RSE 29 | end 30 | end 31 | 32 | % remove the isolate urban/built-up pixels 33 | % % pfpl = bwareaopen(pfpl,2); 34 | 35 | %% add poential snow/ice pixels in mountain. 36 | if ~isempty(slope) 37 | psnow_mountain = psnow==1& slope > 20; 38 | % 20 is from Burbank D W, Leland J, Fielding E, et al. Bedrock incision, rock uplift and threshold hillslopes in the northwestern Himalayas[J]. Nature, 1996, 379(6565): 505. 39 | pfpl = pfpl|psnow_mountain; 40 | end 41 | 42 | %% buffer urban pixels with 1 kilometer window. 43 | witdh=250; 44 | width_pixels=fix(witdh/resolution);% 1 km 33 Landsat pixel 500 meters 17 Landsat pixels. 200 meters 7 pixels. 45 | SEw=strel('square',2*width_pixels+1); 46 | pfpl=imdilate(pfpl,SEw); 47 | 48 | %% must over land this will be runned in commission process function. 49 | % some coastline may be classified as water, that is easily detected as 50 | % cloud when it is grouped into water layer. 51 | % pfpl = pfpl&mask&water==0; % remove the water pixels at the final. also remove the absolute cloud pixels. 52 | 53 | % % % dilate user-defined buffers for erosion more commission errors. 54 | % % if pfbpix > 0 55 | % % SEw=strel('square',2*pfbpix+1); 56 | % % pfpl=imdilate(pfpl,SEw); 57 | % % end 58 | 59 | pfpl = pfpl|psnow;%% add the snow/ice pixels located in normal areas. 60 | 61 | pfpl = pfpl&mask; % remove the water pixels at the final. also remove the absolute cloud pixels. 62 | end -------------------------------------------------------------------------------- /DetectPotentialPixels.m: -------------------------------------------------------------------------------- 1 | function [idplcd,BandCirrusNormal,whiteness,HOT] = DetectPotentialPixels(mask,data_toabt,dem,ndvi,ndsi,satu_Bv) 2 | % DETECTPOTENTIALCLOUD detect potential cloud pixels (PCPs) 3 | 4 | % Cirrus Probability This is unavailable here because some high 5 | % mountianus have high cirrus values. 6 | 7 | % inputs: BandSWIR2 BandBT BandBlue BandGreen BandRed BandNIR BandSWIR1 8 | % BandCirrus 9 | %% Step 1: detect possible cloud pixels (PCPs) 10 | 11 | % [Basic cloud test] 12 | idplcd=ndsi<0.8&ndvi<0.8&data_toabt.BandSWIR2>300; 13 | clear ndsi ndvi; data_toabt.BandSWIR2 = []; % memory. 14 | % when have BT data. 15 | if ~isempty(data_toabt.BandBT) 16 | idplcd=idplcd==true&data_toabt.BandBT<2700; 17 | data_toabt.BandBT = []; 18 | end 19 | 20 | % [Whiteness test] 21 | % visible bands flatness (sum(abs)/mean < 0.6 => brigt and dark cloud ) 22 | visimean=(data_toabt.BandBlue+data_toabt.BandGreen+data_toabt.BandRed)/3; 23 | whiteness=(abs(data_toabt.BandBlue-visimean)+abs(data_toabt.BandGreen-visimean)+... 24 | abs(data_toabt.BandRed-visimean))./visimean; 25 | data_toabt.BandGreen = []; 26 | clear visimean; 27 | % update idplcd 28 | whiteness(satu_Bv==1)=0;% If one visible is saturated whiteness == 0 29 | idplcd=idplcd==true&whiteness<0.7; 30 | 31 | % [Haze test] 32 | HOT=data_toabt.BandBlue-0.5.*data_toabt.BandRed-800;% Haze test 33 | data_toabt.BandBlue = []; 34 | data_toabt.BandRed = []; 35 | 36 | idplcd=idplcd==true&(HOT>0|satu_Bv==1); 37 | clear satu_Bv; % need to find thick warm cloud 38 | 39 | % [Ratio4/5>0.75 cloud test] 40 | Ratio4_5=data_toabt.BandNIR./data_toabt.BandSWIR1; 41 | data_toabt.BandNIR = []; 42 | data_toabt.BandSWIR1 = []; 43 | idplcd=idplcd==true&Ratio4_5>0.75; 44 | clear Ratio4_5; 45 | 46 | BandCirrusNormal=[]; 47 | % normalize Cirrus band [Cirrus test] from Landsat 8 and Sentinel 2 images 48 | if ~isempty(data_toabt.BandCirrus) 49 | BandCirrusNormal=NormalizaCirrusDEM( mask, idplcd, data_toabt.BandCirrus, dem ); 50 | % BandCirrusNormal= data_toabt.BandCirrus; 51 | clear data_toabt mask dem; 52 | idplcd=idplcd==true|BandCirrusNormal > 100; % When TOA at Cirrus band is more than 0.01, it may be cloudy. 53 | end 54 | 55 | end 56 | 57 | -------------------------------------------------------------------------------- /DetectSnow.m: -------------------------------------------------------------------------------- 1 | function snow = DetectSnow(dim,band_green,band_nir,band_bt,ndsi) 2 | %DETECSNOW Summary of this function goes here 3 | % Detailed explanation goes here 4 | 5 | % % snow=zeros(dim,'uint8'); % Snow mask 6 | % It takes every snow pixels including snow pixel under thin clouds or icy clouds 7 | snow=ndsi>0.15&band_nir>1100&band_green>1000; 8 | if ~isempty(band_bt) 9 | snow=snow&band_bt<1000; 10 | end 11 | % snow(ids_snow)=1; 12 | end 13 | 14 | -------------------------------------------------------------------------------- /DetectWater.m: -------------------------------------------------------------------------------- 1 | function [water, waterAll] = DetectWater( dim, mask, nir, NDVI, psnow, slope, gswater) 2 | %DETECTWATER Detect water by combining spectral-derived water and 3 | %GSWO-derived water togeter. 4 | % 5 | % Syntax 6 | % 7 | % water = DetectWater( dim, mask, nir, NDVI, psnow, slope, gswater) 8 | % 9 | % Description 10 | % 11 | % History: 12 | % 1. Create this function. (1. January, 2018) 13 | % 2. The sepctral-derived water may be incorrect, resulting in a 100% 14 | % absolutely wrong low level GSWO (equal to 0). The GWSO will be used 15 | % only when the low level GSWO is larger than 0. (9. March, 2018) 16 | % 3. Remove the coastline because of its frequent changes. (6. May, 2018) 17 | % 4. Add a water layer which does not snoe/ice because some clouds may 18 | % be like snoe/ice. This will be used to exclude processing of cloud 19 | % shadow over water. (17. March, 2020) 20 | % 21 | % 22 | % Input arguments 23 | % 24 | % dim Dim for data. 25 | % mask Mask for observations. 26 | % nir NIR. 27 | % NDVI NDVI. 28 | % psnow Potential snow. 29 | % slope Slope. 30 | % gswater GSWO. 31 | % 32 | % Output arguments 33 | % 34 | % water Water map. 35 | % 36 | % 37 | % Author: Shi Qiu (shi.qiu@uconn.edu) 38 | % Date: 17. March, 2020 39 | 40 | water=zeros(dim,'uint8'); % Water msk 41 | %% Zhe's water test (works over thin cloud) 42 | water((NDVI<0.01&nir<1100)|(NDVI<0.1&NDVI>0&nir<500))=1; 43 | clear resolution; 44 | 45 | % within observation. 46 | water(~mask)=0; 47 | % do not exclude snow over water because clouds may be like snow and 48 | % will be excluded ... 49 | waterAll = water; 50 | %% the GSWO data to enhance water map. 51 | if sum(water(:))>0&&~isempty(gswater) 52 | if sum(gswater(:))>0 % have water occurences. 53 | % assume the water occurances are same in each whole scene. 54 | % global surface water occurance (GSWO) 55 | % low level to exclude the commssion errors as water. 56 | % 5% tolerances. 57 | gswater_occur=prctile(gswater(water==1),17.5)-5; 58 | 59 | if gswater_occur>0 % must be more than 0. 60 | water_gs = gswater>gswater_occur; 61 | clear gswater gswater_occur; 62 | 63 | waterAll(water_gs==1)=1;% extend water regions based on GSWO, but do not exclude snow/ice 64 | 65 | % sometimes ice may be over water. Snow covered sea ice is determined 66 | % using the potential snow/ice. 67 | water_gs(psnow == 1)=0; % remove out ice water. 68 | clear psnow; 69 | 70 | water(water_gs==1)=1;% extend water regions based on GSWO. 71 | % equal to the theshold because it may be 100%. 72 | 73 | % water(psnow)=0; % remove out ice water. I think this snow 74 | % cannot be removed because there are sometimes ice clouds over 75 | % water. 76 | water(~mask)=0; 77 | waterAll(~mask)=0; 78 | end 79 | end 80 | % note that 255 indicates no data in GSWO, that is ocean pixels or 81 | % permenate snow/ice pixels (which can be identified as land pixels). 82 | end 83 | 84 | end 85 | 86 | -------------------------------------------------------------------------------- /EnhanceLine.m: -------------------------------------------------------------------------------- 1 | function line_enhanced = EnhanceLine(band) 2 | %ENHANCELINE enhance NDBI to light urban/built-up areas and dark other 3 | %bright surface, such as desert, rock. ref Guindon et. RSE 2004 4 | 5 | % also can see the details at 6 | % https://homepages.inf.ed.ac.uk/rbf/HIPR2/linedet.htm. 7 | % band=data_toabt.BandGreen; 8 | % 9 | % band=ndbi; 10 | 11 | %% line with a length of three pixels 12 | % template1=[-1 1 0; 13 | % -1 1 0; 14 | % -1 1 0;]; 15 | % template2=[0 1 -1; 16 | % 0 1 -1; 17 | % 0 1 -1;]; 18 | % line_enhanced1 = imfilter(band,template1); 19 | % line_enhanced2 = imfilter(band,template2); 20 | % line_enhanced=max(line_enhanced1,line_enhanced2); 21 | % 22 | % template1=[-1 -1 -1; 23 | % 1 1 1; 24 | % 0 0 0;]; 25 | % template2=[0 0 0; 26 | % 1 1 1; 27 | % -1 -1 -1;]; 28 | % line_enhanced1 = imfilter(band,template1); 29 | % line_enhanced2 = imfilter(band,template2); 30 | % line_enhanced=max(line_enhanced1,line_enhanced); 31 | % line_enhanced=max(line_enhanced2,line_enhanced); 32 | % 33 | % template1=[1 -1 -1; 34 | % 0 1 -1; 35 | % 0 0 1;]; 36 | % template2=[1 0 0; 37 | % -1 1 0; 38 | % -1 -1 1;]; 39 | % line_enhanced1 = imfilter(band,template1); 40 | % line_enhanced2 = imfilter(band,template2); 41 | % line_enhanced=max(line_enhanced1,line_enhanced); 42 | % line_enhanced=max(line_enhanced2,line_enhanced); 43 | % 44 | % template1=[-1 -1 1; 45 | % -1 1 0; 46 | % 1 0 0;]; 47 | % template2=[0 0 1; 48 | % 0 1 -1; 49 | % 1 -1 -1;]; 50 | % 51 | % line_enhanced1 = imfilter(band,template1); 52 | % line_enhanced2 = imfilter(band,template2); 53 | % line_enhanced=max(line_enhanced1,line_enhanced); 54 | % line_enhanced=max(line_enhanced2,line_enhanced); 55 | % line_enhanced = line_enhanced./3; 56 | 57 | 58 | template=[-1 2 -1; 59 | -1 2 -1; 60 | -1 2 -1;]; 61 | template = template./6; 62 | line_enhanced = imfilter(band,template); 63 | 64 | template=[-1 -1 -1; 65 | 2 2 2; 66 | -1 -1 -1;]; 67 | template = template./6; 68 | line_enhanced_new = imfilter(band,template); 69 | line_enhanced=max(line_enhanced_new,line_enhanced); 70 | 71 | template =[2 -1 -1; 72 | -1 2 -1; 73 | -1 -1 2;]; 74 | template = template./6; 75 | line_enhanced_new = imfilter(band,template); 76 | line_enhanced=max(line_enhanced_new,line_enhanced); 77 | 78 | template =[-1 -1 2; 79 | -1 2 -1; 80 | 2 -1 -1;]; 81 | template = template./6; 82 | line_enhanced_new = imfilter(band,template); 83 | line_enhanced=max(line_enhanced_new,line_enhanced); 84 | end 85 | 86 | -------------------------------------------------------------------------------- /ErodeCommissons.m: -------------------------------------------------------------------------------- 1 | 2 | % output: cloud 1:cloud; 2: potential cloud can be eroded, which will be 3 | % identified using cloud shadow match method. 4 | function cloud = ErodeCommissons(data_meta,pcloud,pfpl,water,cdi,erdpix) 5 | %REMOVECOMMISSONS remove most of commission errors from bright and low 6 | %tempareture features. 7 | % 8 | % use the optimal erosion size for Landsat and Sentinel 2 images. Shi 9 | % 4/21/2018. 10 | % remove the cloud objects with less than 3 pixels after eroding 1 pixel. 11 | % Shi 4/10/2018 12 | % pcloud potential cloud layer 13 | % pccl potential commissions as cloud layer 14 | 15 | % 2-by-2 pixels window for Landsat 8 16 | % 3-by-3 pixels window for Landsats 4-7 and Sentinel 2 17 | cipix = erdpix; 18 | 19 | cloud = zeros(data_meta.Dim,'uint8'); % the clouds needed to be eroded. 20 | cloud(pcloud>0)=1; 21 | clear pcloud; 22 | 23 | 24 | %% erode and dilate back. 25 | % CEs = strel('square',2*cipix+1); 26 | CEs = strel('disk',cipix); 27 | % erode to remove the potential false pixels 28 | cloud_eroded = imerode(cloud,CEs); 29 | clear CEs; 30 | pixels_eroded = ~cloud_eroded & pfpl; 31 | clear cloud_eroded; 32 | % only remove the potential false positive pixels 33 | cloud_eroded = cloud; 34 | cloud_eroded(pixels_eroded) = 0; 35 | clear pixels_eroded; 36 | 37 | %% dilate back to orginal cloud shape of which size is dual to the erosion. 38 | CEs = strel('disk',2*cipix); 39 | cloud_dilated = imdilate(cloud_eroded,CEs); 40 | 41 | %% remover the clouds gone forever. 42 | % Segmentate each cloud to remove the small objs. 43 | cloud_objs=bwlabeln(cloud,8); 44 | clouds_remaining = cloud_objs; 45 | clouds_remaining(~cloud_eroded)=0; % remove the clouds gone. 46 | clear cloud_eroded; 47 | idx_clouds_remaining = unique(clouds_remaining(:)); 48 | clear clouds_remaining; 49 | 50 | cloud_remaining = zeros(data_meta.Dim,'uint8'); % the clouds needed to be eroded. 51 | if ~isempty(idx_clouds_remaining) 52 | if idx_clouds_remaining(1)==0 53 | idx_clouds_remaining(1) = []; 54 | end 55 | if ~isempty(idx_clouds_remaining) 56 | cloud_remaining = ismember(cloud_objs,idx_clouds_remaining); 57 | end 58 | end 59 | clear cloud_objs idx_clouds_remaining; 60 | 61 | % only for land 62 | 63 | clear cloud_eroded; 64 | cloud = (cloud_dilated&cloud_remaining)|(water&cloud); % add clouds over water. 65 | clear cloud_dilate cipix CEs water; 66 | 67 | %% remove small object with minum CDI < 0.5 only for Sentinel 2 68 | if ~isempty(cdi) 69 | % exclude small objects of which minimum CDI is still larger than 70 | % -0.5. 71 | % get small clouds 72 | large_obj = bwareaopen(cloud,10000); 73 | small_obj = cloud==1&large_obj==0; 74 | clear large_obj; 75 | % segment small clouds 76 | small_obj_init=bwlabeln(small_obj,8); 77 | 78 | % produce all non confident cloud pixels using CDI 79 | confident_cloud = cdi < -0.5; 80 | 81 | clear cdi; 82 | 83 | % remove the non confident cloud pixels again 84 | small_obj_exd_urban = small_obj_init; 85 | small_obj_exd_urban(confident_cloud==0) = 0; 86 | clear confident_cloud; 87 | 88 | % a true cloud can be determined when any confident pixels are 89 | % remaining. 90 | idx = unique(small_obj_exd_urban); 91 | clear small_obj_exd_urban; 92 | 93 | true_cloud = ismember(small_obj_init,idx); 94 | clear small_obj_init idx; 95 | 96 | % remove bright surfaces 97 | bright_surfaces = true_cloud==0&small_obj==1; 98 | clear small_obj true_cloud; 99 | cloud(bright_surfaces) =0; 100 | end 101 | 102 | %% remove very small object. 103 | cloud = bwareaopen(cloud,3); 104 | 105 | end -------------------------------------------------------------------------------- /FmaskParameters.m: -------------------------------------------------------------------------------- 1 | classdef FmaskParameters 2 | %FMASKPARAMETERS Save all fmask parameters here. 3 | 4 | properties 5 | CloudBuffer % pixels 6 | CloudShadowBuffer % pixels 7 | SnowBuffer % pixels 8 | ThinWeight 9 | CloudProbabilityThershold 10 | PFPCLayerExtensinRadius % meters 11 | PFPCErosionRadius % meters 12 | OutputResolution % meters 13 | ShadowWater % yes or no for masking cloud shadow over water 14 | end 15 | 16 | methods 17 | function obj = FmaskParameters(sensor) 18 | %FMASKPARAMETERS Construct an instance of this class according 19 | %to input image. 20 | 21 | % public and constant paras. 22 | obj.CloudBuffer=3; 23 | obj.CloudShadowBuffer=3; 24 | obj.SnowBuffer=0; 25 | obj.PFPCLayerExtensinRadius=0; 26 | 27 | % mask out the shadow of the cloud over water? 28 | % default: we do not provide the cloud shadow over water since 29 | % this processing will be very time-comsuing but less meanful. 30 | obj.ShadowWater=0; 31 | 32 | % different paras for different sensors. 33 | switch sensor 34 | case 'S_MSI' 35 | obj.ThinWeight=0.5; 36 | obj.CloudProbabilityThershold=20.00; 37 | obj.OutputResolution=20; 38 | obj.PFPCErosionRadius=90;% mirrored from Landsat 8. 39 | case 'L_OLI_TIRS' 40 | obj.ThinWeight=0.3; 41 | obj.CloudProbabilityThershold=17.50; 42 | obj.OutputResolution=30; 43 | obj.PFPCErosionRadius=90; 44 | case {'L_TM','L_ETM_PLUS'} 45 | obj.ThinWeight=0.0; 46 | obj.CloudProbabilityThershold=10.00; 47 | obj.OutputResolution=30; 48 | obj.PFPCErosionRadius=150; 49 | end 50 | end 51 | end 52 | end 53 | 54 | -------------------------------------------------------------------------------- /Fmask_4_7.m: -------------------------------------------------------------------------------- 1 | function Fmask_4_7(input1, input2, input3, input4, input5) 2 | %% Standalone of Fmask 4.7 version 3 | % Input formats 4 | % 5 | % 1) Set default program 6 | % Fmask_4_7() 7 | % 8 | % 2) Set buffers of cloud, shadow, and snow/ice 9 | % Fmask_4_7(3, 3, 1) 10 | % 11 | % 3) Set buffers of cloud, shadow, and snow/ice, and threshold of cloud probability 12 | % Fmask_4_7(3, 3, 3, 22.5) 13 | % 14 | % 15 | % Below cases are to setup the directory of the auxiliary data (the folder 16 | % ) for the implement which fails to locate the directory by the 17 | % default settings. To examine this, please process an image using the 18 | % default program (like case 1), and if a warning message 'Fail to locate 19 | % the directory of auxiliary data' presents, this means the default 20 | % settings do not work, and then you need to custumize the directory of 21 | % auxiliary data using below inputs. 22 | % 23 | 24 | % This problem usually occurs in Linux system. 25 | % (a warning message 'Fail to locate the directory of auxiliary data' will presents). 26 | % 27 | % Note: Start up from the Matlab code will not be affected, and please 28 | % ignore the below cases 29 | % 30 | % Please consider them only when a warning message 'Fail to locate the auxiliary data' present at default model 31 | % 32 | % 4) Set the directory of the auxiliary data 33 | % Fmask_4_7('C:\Users\xxx\xxx\Fmask_4_4\AuxiData') 34 | % 35 | % 5) Set buffers of cloud, shadow, and snow/ice, and the auxiliary data 36 | % Fmask_4_7(3, 3, 0, 'C:\Users\xxx\xxx\Fmask_4_4\AuxiData') 37 | % 38 | % 6) Set buffers of cloud, shadow, and snow/ice, threshold of cloud probability, and the auxiliary data 39 | % Fmask_4_7(3, 3, 0, 22.5, 'C:\Users\xxx\xxx\Fmask_4_4\AuxiData') 40 | % 41 | 42 | %% Case 1) 43 | if ~exist('input1', 'var') 44 | autoFmask(); % default buffering pixels for cloud, cloud shadow, and snow 45 | return; 46 | end 47 | 48 | %% Case 2) 49 | if exist('input3', 'var')&& ~exist('input4', 'var') 50 | autoFmask('cloud', force2num(input1),'shadow', force2num(input2), 'snow', force2num(input3)); 51 | return; 52 | end 53 | 54 | if exist('input4', 'var')&& ~exist('input5', 'var') 55 | if isnumeric(input4) || ~isfolder(input4) 56 | %% Case 3) 57 | autoFmask('cloud', force2num(input1),'shadow', force2num(input2), 'snow', force2num(input3), 'p', force2num(input4)); 58 | else 59 | %% Case 5) 60 | if isfolder(fullfile(input4, 'GTOPO30ZIP')) && isfolder(fullfile(input4, 'GSWO150ZIP')) 61 | autoFmask('cloud', force2num(input1),'shadow', force2num(input2), 'snow', force2num(input3), 'auxi', input4); 62 | else 63 | fprintf('Do not find the directory of the auxiliary data. Please input a correct one. \r'); 64 | end 65 | end 66 | return; 67 | end 68 | 69 | %% Case 4) 70 | if exist('input1', 'var') && ~exist('input2', 'var') 71 | if isfolder(input1) && isfolder(fullfile(input1, 'GTOPO30ZIP')) && isfolder(fullfile(input1, 'GSWO150ZIP')) 72 | autoFmask('auxi', input1); 73 | else 74 | fprintf('Do not find the directory of the auxiliary data. Please correct it. \r'); 75 | end 76 | return; 77 | end 78 | 79 | %% Case 6) 80 | if exist('input5', 'var') 81 | % cldpix =input1; sdpix = input2; snpix = input3; cldprob = input4; pathauxi = input5; 82 | if isfolder(input5) && isfolder(fullfile(input5, 'GTOPO30ZIP')) && isfolder(fullfile(input5, 'GSWO150ZIP')) 83 | autoFmask('cloud',force2num(input1),'shadow',force2num(input2),'snow',force2num(input3),'p',force2num(input4), 'auxi', input5); 84 | else 85 | fprintf('Do not find the directory of the auxiliary data. Please correct it. \r'); 86 | end 87 | return; 88 | end 89 | end 90 | 91 | function input2 = force2num(input2) 92 | %% convert to number input if not 93 | if ~isnumeric(input2) 94 | input2 = str2num(input2); 95 | end 96 | end 97 | 98 | % old version 99 | % 100 | % if exist('cldpix','var')==1&&exist('sdpix','var')==1&&exist('snpix','var')==1 101 | % if exist('cldprob','var')==1 102 | % autoFmask('cloud',str2num(cldpix),'shadow',str2num(sdpix),'snow',str2num(snpix),'p',str2num(cldprob)); 103 | % else 104 | % autoFmask('cloud',str2num(cldpix),'shadow',str2num(sdpix),'snow',str2num(snpix)); 105 | % end 106 | % else 107 | % % default buffering pixels for cloud, cloud shadow, and snow 108 | % autoFmask(); 109 | % end 110 | -------------------------------------------------------------------------------- /GRIDobj2geotiff.m: -------------------------------------------------------------------------------- 1 | function GRIDobj2geotiff(A,file) 2 | 3 | %GRIDobj2geotiff Exports an instance of GRIDobj to a geotiff file 4 | % 5 | % Syntax 6 | % 7 | % GRIDobj2geotiff(DEM) 8 | % GRIDobj2geotiff(DEM,filename) 9 | % 10 | % Description 11 | % 12 | % GeoTIFF is a common image file format that stores coordinates and 13 | % projection information to be read by most GIS software. 14 | % GRIDobj2geotiff writes an instance of GRIDobj to a GeoTIFF file. 15 | % 16 | % GRIDobj2geotiff requires the function geotiffwrite available with 17 | % the Mapping Toolbox. If geotiffwrite does not exist on the search 18 | % path, the function will write a standard tif together with a 19 | % '.tfw'-file (worldfile, http://en.wikipedia.org/wiki/World_file ) to 20 | % the disk. 21 | % 22 | % GRIDobj2geotiff(DEM) opens a dialogue box to save the GeoTIFF 23 | % 24 | % GRIDobj2geotiff(DEM,filename) saves the DEM to the specified 25 | % filename 26 | % 27 | % Input arguments 28 | % 29 | % DEM instance of GRIDobj 30 | % filename absolute or relative path and filename 31 | % 32 | % See also: GRIDobj 33 | % 34 | % Author: Wolfgang Schwanghart (w.schwanghart[at]geo.uni-potsdam.de) 35 | % Date: 17. August, 2017 36 | 37 | narginchk(1,2) 38 | 39 | % if only 1 argument, open file dialog box 40 | if nargin == 1 41 | [FileName,PathName] = uiputfile({'*.tif'}); 42 | if FileName == 0 43 | disp(' no output written to disk') 44 | return 45 | end 46 | file = [PathName FileName]; 47 | end 48 | 49 | % try to use geotiffwrite, which comes with the Mapping Toolbox 50 | try 51 | if isempty(A.georef); 52 | geotiffwrite(file,A.Z,A.refmat); 53 | else 54 | geotiffwrite(file,A.Z,A.georef.SpatialRef,... 55 | 'GeoKeyDirectoryTag',A.georef.GeoKeyDirectoryTag); 56 | end 57 | catch ME 58 | warning('TopoToolbox:GRIDobj',... 59 | ['GRIDobj2geotiff is unable to write a geotiff. Either you don''t \n'... 60 | 'have the mapping toolbox, or there was another issue with geotiffwrite. \n'... 61 | 'GRIDobj2geotiff instead writes a tif-image together with a world \n'... 62 | 'file (*.tfw) which contains data on spatial referencing of the \n' ... 63 | 'image, yet which lacks information on the type of projection used.']); 64 | 65 | 66 | % if geotiffwrite is not available or any other error occurs 67 | % a tif file will be written to the disk together with a worldfile 68 | % .tfw-file. 69 | [pathstr, name, ~] = fileparts(file); 70 | k = refmat2worldfile(A.refmat); 71 | dlmwrite(fullfile(pathstr,[name '.tfw']),k,'precision', '%.10f'); 72 | A = A.Z; 73 | 74 | 75 | siz = size(A); 76 | cla = class(A); 77 | 78 | switch cla; 79 | case 'double' 80 | BpS = 64; 81 | TSF = Tiff.SampleFormat.IEEEFP; 82 | case 'single' 83 | BpS = 32; 84 | TSF = Tiff.SampleFormat.IEEEFP; 85 | otherwise 86 | if islogical(A); 87 | A = uint32(A); 88 | cla = 'uint32'; 89 | end 90 | BpS = round(log2(double(intmax(cla)))); 91 | TSF = Tiff.SampleFormat.UInt; 92 | 93 | end 94 | 95 | t = Tiff(file,'w'); 96 | tagstruct.ImageLength = siz(1); 97 | tagstruct.ImageWidth = siz(2); 98 | tagstruct.BitsPerSample = BpS; 99 | tagstruct.SampleFormat = TSF; 100 | tagstruct.SamplesPerPixel = 1; 101 | tagstruct.RowsPerStrip = 16; 102 | tagstruct.PlanarConfiguration = Tiff.PlanarConfiguration.Chunky; 103 | tagstruct.Software = 'MATLAB'; 104 | tagstruct.Photometric = 0; 105 | 106 | t.setTag(tagstruct); 107 | t.write(A); 108 | t.close; 109 | end 110 | 111 | end 112 | 113 | function k = refmat2worldfile(r) 114 | % does not support rotation 115 | 116 | k(1,1) = r(2,1); 117 | k(4,1) = r(1,2); 118 | k(5,1) = r(3,1)+k(1); 119 | k(6,1) = r(3,2)+k(4); 120 | end -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Global Environmental Remote Sensing Lab 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LoadData.m: -------------------------------------------------------------------------------- 1 | function [data_meta,data_toabt,angles_view,trgt] = LoadData(path_data,sensor,InputFile,main_meta) 2 | %LOADDATA Read all used bands and metadata for Landsats 4-8 and Sentinel 2. 3 | % 4 | % Syntax 5 | % 6 | % [data_meta,data_toabt,trgt] = LoadData(path_data) 7 | % 8 | % Description 9 | % 10 | % For Landsat Level-1 images, Digital Number (DN) values are converted 11 | % to TOA reflectances and BT (Celsius degree) by using the LEDAPS 12 | % atmosphere correction tool (Masek et al., 2006). This function is 13 | % derived from Fmask 3.3 for Landsat. 14 | % For Sentinel 2 Level-1C images, TOA reflectances are provided with a 15 | % spatial resolution of 10, 20 and 60 meters of the different spectral 16 | % bands, that will be resampled into 20 meters at Fmask 4.0 rountine. 17 | % This read function is derived from Fmask 3.3 for Sentinel 2. 18 | % 19 | % History 20 | % 21 | % 2. Modified for new Sentinel-2 data format, baseline 4.00 (26. Dec, 2021) 22 | % 1. Create this function. (17. May, 2017) 23 | % 24 | % Input arguments 25 | % 26 | % path_data Path of images. 27 | % Landsat: the directory where you save the Landsat scene. 28 | % Sentinel 2: the directory reaching to '~/S2*/GRANULE/S2*/IMG_DATA/' 29 | % 30 | % Output arguments 31 | % 32 | % data_meta Metadata 33 | % data_toabt TOA reflectances (x10000) and BT(x100 Celsius degree) data. 34 | % trgt The GRIDobj used as a targart, which is useful when 35 | % projecting and mosaicing the auxi data. 36 | % 37 | % See also: nd2toarbt nd2toarbt_msi 38 | % 39 | % 40 | % Author: Shi Qiu (shi.qiu@ttu.edu) 41 | % Date: 26. Dec, 2021 42 | 43 | trgt=[]; 44 | angles_view=[]; 45 | if strcmp(sensor , 'S_MSI' ) 46 | 47 | % fprintf('Load TOA reflectances from the Level-1C product.\n'); 48 | 49 | % offset or not 50 | % if offset available, the data should be BASELINE 4.00---- the new sentinel-2 data 51 | % if offset unavailable, the function of processing old sentinel=2 data will be triggered 52 | [band_offsets, quantif] = ReadS2RadiometricOffset(InputFile); 53 | 54 | % Load data for Sentinel 2 55 | if isempty(band_offsets) 56 | [~,data,trgt,dim,bbox,ul,zen,azi,zc,Angles,satu_B1,satu_B2,satu_B3,resolu]=nd2toarbt_msi(InputFile); 57 | else 58 | [~,data,trgt,dim,bbox,ul,zen,azi,zc,Angles,satu_B1,satu_B2,satu_B3,resolu]=nd2toarbt_msi2(InputFile, band_offsets, quantif); 59 | end 60 | 61 | %% Input data class defination 62 | data_meta=ObjMeta; 63 | data_toabt=ObjTOABT; 64 | %% Parameters 65 | 66 | norln=strread(main_meta.name,'%s','delimiter','.'); 67 | n_name=char(norln(1)); 68 | data_meta.Name=n_name; 69 | data_meta.Sensor=sensor; 70 | data_meta.Dim=dim; 71 | data_meta.UL=ul; 72 | data_meta.Zen=zen; 73 | data_meta.Azi=azi; 74 | data_meta.ZC=zc; 75 | data_meta.Resolution=resolu; 76 | data_meta.BBox=bbox; 77 | 78 | fmask_dir='FMASK_DATA'; 79 | % see the FMASK_DATA is available 80 | if ~exist(fullfile(InputFile.pathh,fmask_dir),'dir') 81 | %have no the Fmask directory, create it here. 82 | status_mkdir = mkdir(fullfile(InputFile.pathh,fmask_dir)); 83 | % cannot make it, give the results here directly. 84 | if ~status_mkdir 85 | fmask_dir=''; 86 | end 87 | end 88 | data_meta.Output=fmask_dir; 89 | clear fmask_dir; 90 | 91 | %% TOA 92 | data_toabt.BandBlue=data(:,:,1); 93 | data_toabt.BandGreen=data(:,:,2); 94 | data_toabt.BandRed=data(:,:,3); 95 | data_toabt.BandNIR=data(:,:,4); % band 8A 96 | data_toabt.BandSWIR1=data(:,:,5); 97 | data_toabt.BandSWIR2=data(:,:,6); 98 | data_toabt.BandCirrus=data(:,:,7); 99 | 100 | data_toabt.BandVRE3 = data(:,:,8); % band 07 101 | data_toabt.BandNIR8 = data(:,:,9); % band 08 102 | 103 | %% View angles 104 | angles_view=Angles; 105 | 106 | %% Saturated at visible bands 107 | data_toabt.SatuBlue=satu_B1; 108 | data_toabt.SatuGreen=satu_B2; 109 | data_toabt.SatuRed=satu_B3; 110 | else 111 | % fprintf('Calculate TOA reflectances and BT from the Collection 1 product.\n'); 112 | % Load data for Landsat 4-8 113 | [Temp,data,trgt,dim,ul,bbox,zen,azi,zc,satu_B1,satu_B2,satu_B3,resolu]=nd2toarbt(path_data,main_meta.name); 114 | 115 | %% Input data class defination 116 | data_meta=ObjMeta; 117 | data_toabt=ObjTOABT; 118 | %% Parameters 119 | 120 | % reedit dir_im 121 | norln=strread(main_meta.name,'%s','delimiter','.'); 122 | n_name=char(norln(1)); 123 | data_meta.Name=n_name(1:end-4); 124 | data_meta.Sensor=sensor; 125 | data_meta.Dim=dim; 126 | data_meta.UL=ul; 127 | data_meta.Zen=zen; 128 | data_meta.Azi=azi; 129 | data_meta.ZC=zc; 130 | data_meta.Resolution=resolu; 131 | data_meta.BBox=bbox; 132 | data_meta.Output=''; 133 | 134 | %% TOA 135 | if strcmp(sensor,'L_ETM_PLUS')||strcmp(sensor,'L_TM') 136 | data_toabt.BandBlue=data(:,:,1); 137 | data_toabt.BandGreen=data(:,:,2); 138 | data_toabt.BandRed=data(:,:,3); 139 | data_toabt.BandNIR=data(:,:,4); 140 | data_toabt.BandSWIR1=data(:,:,5); 141 | data_toabt.BandSWIR2=data(:,:,6); 142 | else 143 | if strcmp(sensor,'L_OLI_TIRS') 144 | data_toabt.BandBlue=data(:,:,1); 145 | data_toabt.BandGreen=data(:,:,2); 146 | data_toabt.BandRed=data(:,:,3); 147 | data_toabt.BandNIR=data(:,:,4); 148 | data_toabt.BandSWIR1=data(:,:,5); 149 | data_toabt.BandSWIR2=data(:,:,6); 150 | data_toabt.BandCirrus=data(:,:,7); 151 | end 152 | end 153 | 154 | %% BT 155 | 156 | % fprintf('BT, '); 157 | data_toabt.BandBT=Temp; 158 | %% Saturated at visible bands 159 | data_toabt.SatuBlue=satu_B1; 160 | data_toabt.SatuGreen=satu_B2; 161 | data_toabt.SatuRed=satu_B3; 162 | end 163 | end 164 | 165 | -------------------------------------------------------------------------------- /LoadSensorType.m: -------------------------------------------------------------------------------- 1 | function [sensor,num_Lst,InputFile,main_meta] = LoadSensorType(path_data) 2 | %LOADSENSORTYPE Basic metadata should be loaded first to see which sensor 3 | %here. 4 | % Add Landsat 9 , Feb., 17, 2022 5 | 6 | %% Search metadate file for Landsat 4-8 7 | main_meta=dir(fullfile(path_data,'L*MTL.txt')); 8 | existMTL=size(main_meta); 9 | InputFile=[]; 10 | if existMTL(1)==0 11 | main_meta=dir(fullfile(path_data, 'S*MTD*TL*.xml')); 12 | if isempty(main_meta) 13 | main_meta=dir(fullfile(path_data, 'MTD*TL*.xml')); 14 | end 15 | if ~isempty(main_meta) 16 | txtstart = strfind(path_data,'S2A') ; 17 | num_Lst='2A'; 18 | if isempty(txtstart) 19 | txtstart = strfind(path_data,'S2B') ; % S2A or S2B by Shi 10/18/2017 20 | num_Lst='2B'; 21 | end 22 | if isempty(txtstart) 23 | txtstart = strfind(path_data,'S2C') ; % or S2C by Shi 1/16/2025 24 | num_Lst='2C'; 25 | end 26 | txtend = strfind(path_data,'.SAFE')-1 ; 27 | InputFile.DataStrip = path_data(txtstart:txtend) ; 28 | 29 | InputFile.Dmain = path_data(1:txtstart-1) ; 30 | 31 | txtstart = strfind(path_data,'GRANULE')+8 ; 32 | 33 | InputFile.InspireXML=path_data(1:txtstart-10); % extra add to read inspire xml. 34 | 35 | txtend = length(path_data); 36 | InputFile.Granule = path_data(txtstart:txtend) ; 37 | InputFile.pathh = path_data ; 38 | main_meta.name=InputFile.Granule;% return file name fro Fmask results. 39 | else 40 | % fprintf('No available data in the current folder!\n'); 41 | sensor=[]; % no supported image. 42 | num_Lst=[]; 43 | InputFile=[]; 44 | main_meta=[]; 45 | return; 46 | end 47 | else 48 | % determine sensor type 49 | % open and read hdr file 50 | fid_in=fopen(fullfile(path_data,main_meta.name),'r'); 51 | geo_char=fscanf(fid_in,'%c',inf); 52 | fclose(fid_in); 53 | geo_char=geo_char'; 54 | geo_str=strread(geo_char,'%s'); 55 | 56 | % Identify Landsat Number (Lnum = 4, 5, 7, or 8) 57 | LID=char(geo_str(strmatch('SPACECRAFT_ID',geo_str)+2)); 58 | % num_Lst=str2double(LID(end-1)); 59 | num_Lst=(LID(end-1)); 60 | end 61 | % define Landsat sensor. 62 | sensor=''; 63 | if strcmp(num_Lst,'8') | strcmp(num_Lst,'9') 64 | sensor='L_OLI_TIRS'; 65 | else 66 | if strcmp(num_Lst,'4')||strcmp(num_Lst,'5')||strcmp(num_Lst,'6') 67 | sensor='L_TM'; 68 | else 69 | if strcmp(num_Lst,'7') 70 | sensor='L_ETM_PLUS'; 71 | end 72 | end 73 | end 74 | if strcmp(num_Lst,'2A')||strcmp(num_Lst,'2B')||strcmp(num_Lst,'2C') 75 | sensor='S_MSI'; 76 | end 77 | end 78 | 79 | -------------------------------------------------------------------------------- /NDBI.m: -------------------------------------------------------------------------------- 1 | function ndbi = NDBI( nir, swir ) 2 | %NDBI Calculate Normalized Difference Build-up Index (NDBI) using NIR and 3 | % SWIR bands. 4 | % Syntax 5 | % 6 | % ndbi = NDBI(swir,nir) 7 | % 8 | % Description 9 | % 10 | % This function calculates Normalized Difference Build-up Index (NDBI) 11 | % using SWIR and NIR bands (as following equation). 12 | % 13 | % Input arguments 14 | % 15 | % swir Short-wave infrared band 16 | % nir Near infrared band 17 | % 18 | % Output arguments 19 | % 20 | % ndbi Normalized Difference Build-up Index 21 | % 22 | % 23 | % Author: Shi Qiu (shi.qiu@ttu.edu) 24 | % Date: 19. Dec., 2017 25 | 26 | % calculate NDBI 27 | ndbi=(swir-nir)./(swir+nir); 28 | 29 | % fix unnormal pixels 30 | % not 0.01 any more because we will identify urban pixel using ndbi more than 0. 31 | % ndbi((nir+swir)==0)=0.0; 32 | 33 | end 34 | 35 | -------------------------------------------------------------------------------- /NDSI.m: -------------------------------------------------------------------------------- 1 | function ndsi = NDSI( green,swir ) 2 | %NDSI calculate Normalized Difference Snow Index (NDSI) using Green and 3 | % SWIR bands. 4 | % Syntax 5 | % 6 | % ndsi = NDSI(green,swir) 7 | % 8 | % Description 9 | % 10 | % This function calculates Normalized Difference Snow Index (NDSI) 11 | % using Green and SWIR bands (as following equation). This can be used 12 | % to detect snow, as the atmosphere is transparent at both these 13 | % wavelengths, while snow is very reflective at 0.66 mm and not 14 | % reflective at 1.6mm. At visible wavelengths (e.g. 0.66 microns), snow 15 | % cover is just as bright as clouds, and is therefore difficult to 16 | % distinguish from cloud cover. However, at 1.6 microns, snow cover 17 | % absorbs sunlight, and therefore appears much darker than clouds. 18 | % 19 | % NDSI=(Green-SWIR)/(Green+SWIR). 20 | % 21 | % Input arguments 22 | % 23 | % green Green band 24 | % swir Short-wave infrared band 25 | % 26 | % Output arguments 27 | % 28 | % ndsi Normalized Difference Snow Index 29 | % 30 | % 31 | % Author: Shi Qiu (shi.qiu@ttu.edu) 32 | % Date: 19. October, 2017 33 | 34 | % calculate NDSI 35 | ndsi=(green-swir)./(green+swir); 36 | 37 | % fix unnormal pixels 38 | ndsi((green+swir)==0)=0.01; 39 | 40 | end 41 | 42 | -------------------------------------------------------------------------------- /NDVI.m: -------------------------------------------------------------------------------- 1 | function ndvi = NDVI( red,nir ) 2 | %NDVI Calculate Normalized Difference Vegetation Index (NDVI) using NIR and 3 | % Red bands. 4 | % 5 | % Syntax 6 | % 7 | % ndvi = NDVI(red,nir) 8 | % 9 | % Description 10 | % 11 | % This function calculates Normalized Difference Vegetation Index (NDVI) 12 | % using NIR and Red bands (as following equation). This range is between 13 | % -1 and 1. 14 | % NDVI=(NIR-Red)/(NIR+Red). 15 | % 16 | % Input arguments 17 | % 18 | % red Red band 19 | % nir Near-infrared band 20 | % 21 | % Output arguments 22 | % 23 | % ndvi Normalized Difference Vegetation Index 24 | % 25 | % 26 | % Author: Shi Qiu (shi.qiu@ttu.edu) 27 | % Date: 19. October, 2017 28 | 29 | % calculate NDVI 30 | ndvi=(nir-red)./(nir+red); 31 | 32 | % fix unnormal pixels 33 | ndvi((nir+red)==0)=0.01; 34 | 35 | end 36 | 37 | -------------------------------------------------------------------------------- /NormalizaCirrusDEM.m: -------------------------------------------------------------------------------- 1 | function cirrus_noml = NormalizaCirrusDEM( mask, idplcd, cirrus, dem ) 2 | %NORMALIZACIRRUSDEM normalize Cirrus band using most dark object at Cirrus 3 | %band based on DEMs. 4 | cirrus_noml=zeros(size(cirrus),'double'); 5 | idclr=idplcd==false&mask; % clear sky pixels 6 | l_pt=2; 7 | if isempty(dem) % when no DEMs, we also adjust the cirrus TOA to be around 0; 8 | cirrus_noml(mask)=cirrus(mask)-prctile(cirrus(idclr),l_pt); 9 | clear mask cirrus idclr l_pt; 10 | else 11 | dem_start=prctile(dem(mask),0.001); dem_end=prctile(dem(mask),99.999); % further exclude errors in DEM. 12 | clear mask; 13 | dem_step=100; 14 | dem_intvl=dem_start:dem_step:dem_end; 15 | clear dem_start dem_end; 16 | n_slice=length(dem_intvl); 17 | dem_LUT=dem_intvl; 18 | clear dem_intvl; 19 | cirrus_lowest=0; 20 | cirrus_lowest_al=0; 21 | for i=1:n_slice 22 | % the dem intervals. 23 | if i==n_slice 24 | ids_inter=dem>dem_LUT(i)-dem_step/2; 25 | else 26 | if i==1 27 | ids_inter=demdem_LUT(i)-dem_step/2)&(dem0 37 | cirrus_lowest=prctile(cirrus(ids_inter_clr),l_pt); 38 | if cirrus_lowest_al==0 % the first local lowest cirrus value will be given 39 | cirrus_lowest_al=cirrus_lowest; 40 | end 41 | end 42 | clear ids_inter_clr; 43 | 44 | cirrus_noml(ids_inter)=cirrus(ids_inter)-cirrus_lowest; 45 | clear ids_inter; 46 | end 47 | clear cirrus cirrus_lowest idclr dem_LUT dem_step n_slice; 48 | % % % when no DEMs, we also adjust the cirrus TOA to be around 0, 49 | % % % based on the full lowest cirrus value. 50 | % % cirrus_noml(~dem_have)=cirrus(~dem_have)-cirrus_lowest_al; 51 | end 52 | % the normalized cirrus value will be set as 0 when it is negative. 53 | cirrus_noml(cirrus_noml<0)=0; 54 | end -------------------------------------------------------------------------------- /NormalizeBT.m: -------------------------------------------------------------------------------- 1 | function Tempd = NormalizeBT( dim, dem,mask,Temp,ratelaspe_cl,l_pt,h_pt,resl) 2 | %NORMALIZEBT Normalize BT along elevation (DEM) by using a linear model. 3 | % 4 | % Syntax 5 | % 6 | % Tempd = NormalizeBT( dim, dem,mask,Temp,ratelaspe_cl,l_pt,h_pt ) 7 | % 8 | % Description 9 | % 10 | % A linear model is used to normalize BT along with DEM (Qiu et al., 11 | % 2017). 12 | % History: 13 | % 1. Create this function. (1. January, 2017) 14 | % 2. This stratied sampling method sometimes results in no enough 15 | % samples. If the stratied samples are less than 40,000 (not 50,000), 16 | % the stratied sampling method will not be used anymore. (8. March, 17 | % 2018) 18 | % 3. no normalization if no dem data. (20. March, 2018) 19 | % 20 | % Input arguments 21 | % 22 | % dim Dim for data. 23 | % dem Digital Elevation Model (DEM). 24 | % Temp Temperature (BT). 25 | % ratelaspe_cl Clear sky (land) pixels, which are used for this 26 | % normalization. 27 | % l_pt Low level (17.5 percentile). 28 | % h_pt High level (81.5 percentile). 29 | % resl Spatial resolution (Landsat 30 meters; Sentinel-2 20 meters). 30 | % 31 | % Output arguments 32 | % 33 | % Tempd Nomalized Temperature (BT). 34 | % 35 | % 36 | % Author: Shi Qiu (shi.qiu@ttu.edu) 37 | % Date: 8. March, 2018 38 | 39 | if isempty (dem) 40 | Tempd = Temp; 41 | else 42 | mask(isnan(dem))=0;% exclude nan dem pixel. 43 | dem_b=double(prctile(dem(mask),0.0001)); 44 | temp_cl=Temp(ratelaspe_cl); 45 | % clear ratelaspe_cl; 46 | temp_min=prctile(temp_cl,l_pt*100); 47 | temp_max=prctile(temp_cl,h_pt*100); 48 | clear temp_cl l_pt h_pt; 49 | cls=(Temp>temp_min&Temptemp_min&temp_cl-9999; 32 | % 33 | % obs_mask=band~=0; 34 | % 35 | % %% simulate Sentinel 2 scene by 100 km x 100 km. 36 | % % spatial resolution is 30 m for each pixel. That means there will be 37 | % % 3333 x 3333 30m pixels should be selected expanding from the centre. 38 | % s2_pixels=[3333 3333]; 39 | % s2_pixels_half=round(s2_pixels./2); 40 | % obs_dim=size(obs_mask); 41 | % pixel_centre=round(obs_dim./2); 42 | % 43 | % s2_mask=obs_mask.*0; 44 | % r_start=pixel_centre(1)-s2_pixels_half(1); 45 | % r_end=pixel_centre(1)+s2_pixels_half(1); 46 | % c_start=pixel_centre(2)-s2_pixels_half(2); 47 | % c_end=pixel_centre(2)+s2_pixels_half(2); 48 | % s2_mask(r_start:r_end,c_start:c_end)=1; 49 | % obs_mask=s2_mask&obs_mask; 50 | end 51 | 52 | -------------------------------------------------------------------------------- /ProjectDEM2Plane.m: -------------------------------------------------------------------------------- 1 | % verison 2 | % change to project the pixels from the potential shadow layer. Mar., 3, 2018 by Shi Qiu 3 | % sometimes the projection will generate Nan values. fixed this bug by 4 | % Feb., 13, 2018 by Shi Qiu 5 | % fix the bug some pixels out of image. Shi 9/12/2017 6 | % 7 | function [dem_plane_r,dem_plane_c]= ProjectDEM2Plane(dim,pshadow,dem,dem_base,... 8 | sun_elevation_rad,sun_tazi_rad,sun_azimuth_deg,dim_expd,resolu) 9 | % DEM is vertically obervated by scene. 10 | % assume resolu.x=resolu.y 11 | spatial_resol=resolu(1); 12 | % get the location of orginal pixel 13 | dem_loc=zeros(dim,'uint8')+1; 14 | [loc_i,loc_j]=find(dem_loc); 15 | 16 | % must project all pixels. 17 | % [loc_i,loc_j]=find(pshadow==1); 18 | % loc_i=uint16(loc_i); 19 | % loc_j=uint16(loc_j); 20 | % if isequal(dem,0) % when no DEMs, no cloud shadow shape correction. 21 | if isempty(dem) % when no DEMs, no cloud shadow shape correction. 22 | loc_i_plane=loc_i; 23 | loc_j_plane=loc_j; 24 | else % when having DEMs 25 | % dem_dif=dem(pshadow==1)-dem_base; 26 | dem_dif=dem-dem_base; 27 | dem_dif=dem_dif(:); 28 | clear dem_loc dem dem_base; 29 | % sun angle geometry 30 | % sun_elevation_deg=90-sun_zenith_deg; 31 | % sun_elevation_rad=deg2rad(sun_elevation_deg); 32 | d_ij=dem_dif./(spatial_resol*tan(sun_elevation_rad)); 33 | % Sun_tazi=sun_azimuth_deg-90; 34 | % sun_tazi_rad=deg2rad(Sun_tazi); 35 | % if sun_azimuth_deg< 180 36 | 37 | if sun_azimuth_deg< 180 38 | di=0-d_ij.*sin(sun_tazi_rad); % i for row, Y 39 | dj=0-d_ij.*cos(sun_tazi_rad); % j for col, X 40 | else 41 | di=d_ij.*sin(sun_tazi_rad); % i for row, Y 42 | dj=d_ij.*cos(sun_tazi_rad); % j for col, X 43 | end 44 | di=double(di); 45 | dj=double(dj); 46 | clear dem_dif d_ij Sun_tazi sun_elevation_rad sun_zenith_deg Sun_tazi sun_tazi_rad; 47 | 48 | % original location adds the bias. 49 | loc_i_plane=round(loc_i+di); 50 | loc_j_plane=round(loc_j+dj); 51 | clear di dj; 52 | end 53 | % dim_expd=1000;% 1000 buffer 54 | dim_plane_expd=dim+(2*dim_expd); 55 | % expand the range of observations 56 | loc_i_plane_expd=loc_i_plane+dim_expd; 57 | loc_j_plane_expd=loc_j_plane+dim_expd; 58 | 59 | % remove the pixels out of box. 60 | % % out_ids=loc_i_plane_expd(:)<1|loc_j_plane_expd(:)<1|... 61 | % % loc_i_plane_expd(:)>dim_plane_expd(1)|loc_j_plane_expd(:)>dim_plane_expd(2); 62 | % % loc_i_plane_expd(out_ids)=[]; 63 | % % loc_j_plane_expd(out_ids)=[]; 64 | % % loc_i(out_ids)=[]; 65 | % % loc_j(out_ids)=[]; 66 | 67 | loc_i_plane_expd(loc_i_plane_expd(:)<1)=1; 68 | loc_j_plane_expd(loc_j_plane_expd(:)<1)=1; 69 | loc_i_plane_expd(loc_i_plane_expd(:)>dim_plane_expd(1))=dim_plane_expd(1); 70 | loc_j_plane_expd(loc_j_plane_expd(:)>dim_plane_expd(2))=dim_plane_expd(2); 71 | 72 | clear loc_i_plane loc_j_plane dj dim_expd; 73 | dem_plane_r=zeros(dim_plane_expd); 74 | dem_plane_c=zeros(dim_plane_expd); 75 | % dem_plane_r = NaN(dim_plane_expd,'double'); 76 | % dem_plane_c = NaN(dim_plane_expd,'double'); 77 | % recorders. 78 | tmp_id_plane=sub2ind(dim_plane_expd,double(loc_i_plane_expd),double(loc_j_plane_expd)); 79 | clear loc_i_plane_expd loc_j_plane_expd; 80 | 81 | % sometimes there may be some Nan values! 82 | nan_ids=isnan(tmp_id_plane); 83 | if sum(nan_ids(:))>0 84 | tmp_id_plane(nan_ids)=[]; 85 | loc_i(nan_ids)=[]; 86 | loc_j(nan_ids)=[]; 87 | end 88 | dem_plane_r(tmp_id_plane)=loc_i; 89 | dem_plane_c(tmp_id_plane)=loc_j; 90 | 91 | 92 | 93 | clear loc_i loc_j tmp_id_plane; 94 | % filled the holes due to the dicretization projection. 95 | % dem_plane_r=fillmissing(dem_plane_r,'nearest',1);% by column. 96 | % dem_plane_c=fillmissing(dem_plane_c,'nearest',1);% by column. 97 | % fill remainings as 0 value. 98 | % dem_plane_r=fillmissing(dem_plane_r,'constant',0);% by column. 99 | % dem_plane_c=fillmissing(dem_plane_c,'constant',0);% by column. 100 | 101 | % dem_plane_r=round(dem_plane_r); 102 | % dem_plane_c=round(dem_plane_c); 103 | 104 | 105 | % dem_plane_r=imfill(dem_plane_r,'holes'); 106 | % dem_plane_c=imfill(dem_plane_c,'holes'); 107 | end 108 | 109 | -------------------------------------------------------------------------------- /ReadMetadataMSI.m: -------------------------------------------------------------------------------- 1 | function MSIinfo=ReadMetadataMSI(FilesInfo) 2 | % DataStrip='S2A_OPER_PRD_MSIL1C_PDMC_20150818T101237_R022_V20150813T102406_20150813T102406'; 3 | % Granule='S2A_OPER_MSI_L1C_TL_MTI__20150813T201603_A000734_T32TPS_N01'; 4 | 5 | % Debug the time conventor at line # 31(16/12/2021 Shi) 6 | 7 | DirIn=FilesInfo.DirIn; 8 | % Num=FilesInfo.Num; 9 | DataStrip = FilesInfo.DataStrip; 10 | Granule=FilesInfo.Granule; 11 | 12 | Dmain = DirIn ; 13 | 14 | if Granule(1) == 'L' 15 | FileName = fullfile(Dmain, [DataStrip '.SAFE'],'GRANULE', Granule, 'MTD_TL.xml'); 16 | % FileName = [Dmain DataStrip '.SAFE/GRANULE/' Granule '/' 'MTD_TL.xml']; 17 | else 18 | % FileName = [Dmain DataStrip '.SAFE/GRANULE/' Granule '/' strrep(Granule(1:55),'MSI','MTD') '.xml']; 19 | FileName = fullfile(Dmain,[DataStrip '.SAFE'],'GRANULE', Granule,[strrep(Granule(1:55),'MSI','MTD') '.xml']); 20 | end 21 | 22 | S = xml2struct(FileName); 23 | 24 | S = S.n1_colon_Level_dash_1C_Tile_ID; 25 | 26 | TileID = S.n1_colon_General_Info.TILE_ID.Text; 27 | 28 | ds = S.n1_colon_General_Info.SENSING_TIME.Text; 29 | 30 | DN = datenum( ds ,'yyyy-mm-ddTHH:MM:SS'); 31 | % DN = (datenum( ds ,'yyyy-mm-ddTHH:MM:SS')+str2double(ds(end-4:end-1))/3600/24); % not unique format for date in Sentinel-2 data 32 | 33 | DS = datestr(DN); 34 | 35 | bandIdList = {'01' '02' '03' '04' '05' '06' '07' '08' '8A' '09' '10' '11' '12'} ; 36 | 37 | GeoInfo.UTM_zone = S.n1_colon_Geometric_Info.Tile_Geocoding.HORIZONTAL_CS_NAME.Text(end-2:end); 38 | 39 | %% 40 | M = S.n1_colon_Quality_Indicators_Info.Pixel_Level_QI.MASK_FILENAME ; 41 | 42 | for i=1:length(M) 43 | if isfield(M{i}.Attributes,'bandId') 44 | Mask.(M{i}.Attributes.type).(['B' M{i}.Attributes.bandId]) = M{i}.Text ; 45 | else 46 | Mask.(M{i}.Attributes.type) = M{i}.Text ; 47 | end 48 | end 49 | %% 50 | GeoInfo.Size.R10 = [str2num(S.n1_colon_Geometric_Info.Tile_Geocoding.Size{1}.NCOLS.Text) str2num(S.n1_colon_Geometric_Info.Tile_Geocoding.Size{1}.NROWS.Text)] ; 51 | GeoInfo.Size.R20 = [str2num(S.n1_colon_Geometric_Info.Tile_Geocoding.Size{2}.NCOLS.Text) str2num(S.n1_colon_Geometric_Info.Tile_Geocoding.Size{2}.NROWS.Text)] ; 52 | GeoInfo.Size.R60 = [str2num(S.n1_colon_Geometric_Info.Tile_Geocoding.Size{3}.NCOLS.Text) str2num(S.n1_colon_Geometric_Info.Tile_Geocoding.Size{3}.NROWS.Text)] ; 53 | 54 | GeoInfo.Xstart.R10 = str2num(S.n1_colon_Geometric_Info.Tile_Geocoding.Geoposition{1}.ULX.Text); 55 | GeoInfo.Xstart.R20 = str2num(S.n1_colon_Geometric_Info.Tile_Geocoding.Geoposition{2}.ULX.Text); 56 | GeoInfo.Xstart.R60 = str2num(S.n1_colon_Geometric_Info.Tile_Geocoding.Geoposition{3}.ULX.Text); 57 | 58 | GeoInfo.Ystart.R10 = str2num(S.n1_colon_Geometric_Info.Tile_Geocoding.Geoposition{1}.ULY.Text); 59 | GeoInfo.Ystart.R20 = str2num(S.n1_colon_Geometric_Info.Tile_Geocoding.Geoposition{2}.ULY.Text); 60 | GeoInfo.Ystart.R60 = str2num(S.n1_colon_Geometric_Info.Tile_Geocoding.Geoposition{3}.ULY.Text); 61 | 62 | %% 63 | clear dum 64 | for i=1:23 65 | dum(i,:) = str2num(S.n1_colon_Geometric_Info.Tile_Angles.Sun_Angles_Grid.Zenith.Values_List.VALUES{i}.Text) ; 66 | end 67 | Angles.SZA = dum ; 68 | 69 | clear dum 70 | for i=1:length(S.n1_colon_Geometric_Info.Tile_Angles.Sun_Angles_Grid.Azimuth.Values_List.VALUES) 71 | dum(i,:) = str2num(S.n1_colon_Geometric_Info.Tile_Angles.Sun_Angles_Grid.Azimuth.Values_List.VALUES{i}.Text) ; 72 | end 73 | Angles.SAA = dum ; 74 | 75 | bandIdList = 0:12 ; 76 | DeteIdList = 1:12 ; 77 | 78 | Angles.VZA = nan(23,23,length(bandIdList),length(DeteIdList)); 79 | Angles.VAA = nan(23,23,length(bandIdList),length(DeteIdList)); 80 | 81 | Sdum = S.n1_colon_Geometric_Info.Tile_Angles.Viewing_Incidence_Angles_Grids; 82 | for j=1:length(Sdum) 83 | bandId = str2num(Sdum{j}.Attributes.bandId) ; 84 | DeteId = str2num(Sdum{j}.Attributes.detectorId) ; 85 | clear dum 86 | for i=1:23 87 | dum(i,:) = str2num(Sdum{j}.Zenith.Values_List.VALUES{i}.Text) ; 88 | end 89 | Angles.VZA(:,:,bandIdList==bandId,DeteIdList==DeteId) = dum ; 90 | clear dum 91 | for i=1:23 92 | dum(i,:) = str2num(Sdum{j}.Azimuth.Values_List.VALUES{i}.Text) ; 93 | end 94 | Angles.VAA(:,:,bandIdList==bandId,DeteIdList==DeteId) = dum ; 95 | end 96 | 97 | Angles.Mean.SZA = str2num(S.n1_colon_Geometric_Info.Tile_Angles.Mean_Sun_Angle.ZENITH_ANGLE.Text) ; 98 | Angles.Mean.SAA = str2num(S.n1_colon_Geometric_Info.Tile_Angles.Mean_Sun_Angle.AZIMUTH_ANGLE.Text) ; 99 | 100 | Angles.Mean.VZA2 = nan(length(bandIdList),1); 101 | Angles.Mean.VAA2 = nan(length(bandIdList),1); 102 | 103 | Sdum = S.n1_colon_Geometric_Info.Tile_Angles.Mean_Viewing_Incidence_Angle_List.Mean_Viewing_Incidence_Angle; 104 | for j=1:length(Sdum) 105 | bandId = str2num(Sdum{j}.Attributes.bandId) ; 106 | Angles.Mean.VZA2(bandIdList==bandId) = str2num(Sdum{j}.ZENITH_ANGLE.Text) ; 107 | Angles.Mean.VAA2(bandIdList==bandId) = str2num(Sdum{j}.AZIMUTH_ANGLE.Text) ; 108 | end 109 | 110 | Angles.Mean.VZA = mean(Angles.Mean.VZA2); 111 | Angles.Mean.VAA = mean(Angles.Mean.VAA2); 112 | 113 | Angles.bandIdList=bandIdList; 114 | Angles.DeteIdList=DeteIdList; 115 | %% 116 | MSIinfo.TileID = TileID; 117 | MSIinfo.DN = DN; 118 | MSIinfo.DS = DS; 119 | MSIinfo.bandIdList = bandIdList ; 120 | MSIinfo.Angles = Angles; 121 | MSIinfo.GeoInfo = GeoInfo; 122 | MSIinfo.Mask = Mask ; 123 | -------------------------------------------------------------------------------- /ReadS2InspireXML.m: -------------------------------------------------------------------------------- 1 | function bbox = ReadS2InspireXML(xml_path) 2 | %READS2INSPIREXML Load INSPIRE XML data at .SAFE directory, and can give us 3 | %image extent. 4 | % The range of Sentinel2's longtitude is [0 360] sometimes, which need to be [-180 180] by Shi 3/17/2020 5 | FileName = fullfile(xml_path,'INSPIRE.xml'); 6 | S = xml2struct(FileName); 7 | clear FileName; 8 | S = S.gmd_colon_MD_Metadata.gmd_colon_identificationInfo.gmd_colon_MD_DataIdentification; 9 | extentS=S.gmd_colon_extent.gmd_colon_EX_Extent.gmd_colon_geographicElement.gmd_colon_EX_GeographicBoundingBox; 10 | clear S; 11 | % bbox=[north,south,west,east]; 12 | bbox=[str2double(extentS.gmd_colon_northBoundLatitude.gco_colon_Decimal.Text),... 13 | str2double(extentS.gmd_colon_southBoundLatitude.gco_colon_Decimal.Text),... 14 | str2double(extentS.gmd_colon_westBoundLongitude.gco_colon_Decimal.Text),... 15 | str2double(extentS.gmd_colon_eastBoundLongitude.gco_colon_Decimal.Text)]; 16 | 17 | if bbox(3) > 180 18 | bbox(3) = bbox(3) - 360; 19 | end 20 | if bbox(4) > 180 21 | bbox(4) = bbox(4) - 360; 22 | end 23 | end 24 | 25 | -------------------------------------------------------------------------------- /ReadS2RadiometricOffset.m: -------------------------------------------------------------------------------- 1 | function [band_offsets, quantif] = ReadS2RadiometricOffset(FilesInfo) 2 | % This funciton is to read the Sentinel-2's radiometric offset for the 3 | % version with Basline 4.00 4 | % 5 | % Return Empty means old version data 6 | % 7 | % Created on 16/12/2021 8 | 9 | filepath_meta = dir(fullfile(FilesInfo.InspireXML, 'M*L1C.xml')); % which is same location as inspire xml 10 | S = xml2struct(fullfile(FilesInfo.InspireXML, filepath_meta.name)); 11 | quantif = str2num(S.n1_colon_Level_dash_1C_User_Product.n1_colon_General_Info.Product_Image_Characteristics.QUANTIFICATION_VALUE.Text); 12 | try 13 | Radiometric_Offset_List = S.n1_colon_Level_dash_1C_User_Product.n1_colon_General_Info.Product_Image_Characteristics.Radiometric_Offset_List.RADIO_ADD_OFFSET; 14 | 15 | band_offsets = zeros(length(Radiometric_Offset_List), 2); 16 | for i = 1: length(Radiometric_Offset_List) 17 | offset_value = str2num(Radiometric_Offset_List{1, i}.Text); 18 | band_id = str2num(Radiometric_Offset_List{1, i}.Attributes.band_id); 19 | band_offsets(i,1) = band_id; 20 | band_offsets(i,2) = offset_value; 21 | end 22 | catch 23 | band_offsets = []; 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /ReadSunViewGeometryMSI.m: -------------------------------------------------------------------------------- 1 | function [MSIinfo Angles CM] = ReadSunViewGeometryMSI (DataStrip,Granule,BandSel,PsizeOut,Dmain) 2 | % DataStrip='S2A_OPER_PRD_MSIL1C_PDMC_20150818T101451_R080_V20150817T114755_20150817T114755'; 3 | % Granule='S2A_OPER_MSI_L1C_TL_SGS__20150817T131818_A000792_T28QBG_N01'; 4 | % this is derived from Fmask 3.3 for Sentinel 2. 5 | 6 | % debug Matdetec==DetectFootPrint.Ndect(i) from Matdetec==i 7 | InterpMethod = 'linear';'nearest'; 8 | 9 | % Dmain = '/nobackupp6/jju/S2SAFE/'; 10 | Pppplot = 0 ; 11 | % PsizeOut = 20 ; 12 | if nargin<5 13 | FilesInfo.DirIn = CodeStartingLinesSentinel(-1); 14 | else 15 | FilesInfo.DirIn = Dmain; 16 | end 17 | % FilesInfo.Num = '.04'; 18 | FilesInfo.DataStrip = DataStrip; 19 | FilesInfo.Granule=Granule; 20 | 21 | if Granule(1) == 'L' 22 | mainpath = fullfile(Dmain,[DataStrip '.SAFE']); 23 | else 24 | mainpath = fullfile(Dmain, [DataStrip '.SAFE'],'GRANULE', Granule, 'QI_DATA'); 25 | end 26 | 27 | try 28 | MSIinfo=ReadMetadataMSI(FilesInfo); 29 | catch 30 | error('The metadata was wrongly loaded. Please input Sentinel-2 TOA data.'); 31 | end 32 | 33 | if nargout==1 34 | return 35 | end 36 | bandIdList = {'01' '02' '03' '04' '05' '06' '07' '08' '8A' '09' '10' '11' '12'} ; 37 | BandList = 0:12 ; 38 | if exist('BandSel','var') 39 | bandIdList = bandIdList(BandSel); 40 | BandList = BandList(BandSel); 41 | end 42 | if ~exist('PsizeOut','var') 43 | PsizeOut = 10 ; 44 | end 45 | % bandIdList = bandIdList (8:9); 46 | % BandList = BandList (8:9); 47 | 48 | 49 | % MethInterp100m10m = 'nearest'; 50 | 51 | % [BandList]=find(MSIinfo.Angles.Mean.VZA2==median(MSIinfo.Angles.Mean.VZA2)) ; 52 | 53 | %% 54 | X5km = MSIinfo.GeoInfo.Xstart.R10 : 5000 : MSIinfo.GeoInfo.Xstart.R10 + 5000*22 ; 55 | Y5km = MSIinfo.GeoInfo.Ystart.R10 :-5000 : MSIinfo.GeoInfo.Ystart.R10 - 5000*22 ; 56 | % [X5kmat Y5kmat]=meshgrid(X5km,Y5km); 57 | % Psize = 100 ; 58 | % X100m = single(MSIinfo.GeoInfo.Xstart.R20+Psize/2 : Psize : MSIinfo.GeoInfo.Xstart.R20-Psize/2 + Psize*(MSIinfo.GeoInfo.Size.R10(2)/10))'; 59 | % Y100m = single(MSIinfo.GeoInfo.Ystart.R20-Psize/2 :-Psize : MSIinfo.GeoInfo.Ystart.R20+Psize/2 - Psize*(MSIinfo.GeoInfo.Size.R10(1)/10))'; 60 | % [X100mat Y100mat]=meshgrid(X100m,Y100m); 61 | 62 | Psize = 10 ; 63 | X10m = single(MSIinfo.GeoInfo.Xstart.R20 : Psize : MSIinfo.GeoInfo.Xstart.R20+(MSIinfo.GeoInfo.Size.R10(2)-1)*Psize) + Psize/2 ; 64 | Y10m = single(MSIinfo.GeoInfo.Ystart.R20 :-Psize : MSIinfo.GeoInfo.Ystart.R20-(MSIinfo.GeoInfo.Size.R10(1)-1)*Psize)' - Psize/2 ; 65 | 66 | % X100m = imresize(X10m,10/100,'box') ; 67 | % Y100m = imresize(Y10m,10/100,'box') ; 68 | 69 | XPsizeOut = imresize(X10m,10/PsizeOut,'box') ; 70 | YPsizeOut = imresize(Y10m,10/PsizeOut,'box') ; 71 | clear PsizeOut; 72 | 73 | % Psize = PsizeOut ; 74 | 75 | % [X100mat Y100mat]=meshgrid(X100m,Y100m); 76 | [XPsizeOutmat YPsizeOutmat]=meshgrid(XPsizeOut,YPsizeOut); 77 | 78 | 79 | if Pppplot == 1 80 | % Psize = PsizeOut ; 81 | % X10m = single(MSIinfo.GeoInfo.Xstart.R20+Psize/2 : Psize : MSIinfo.GeoInfo.Xstart.R20-Psize/2 + (MSIinfo.GeoInfo.Size.R10(2)*10/Psize))'; 82 | % Y10m = single(MSIinfo.GeoInfo.Ystart.R20-Psize/2 :-Psize : MSIinfo.GeoInfo.Ystart.R20+Psize/2 - (MSIinfo.GeoInfo.Size.R10(1)*10/Psize))'; 83 | % [X10mat Y100mat]=meshgrid(X100m,Y100m); 84 | [X5kmat Y5kmat]=meshgrid(X5km,Y5km); 85 | end 86 | % % kern = ones(20);imcircle(20); 87 | 88 | %% cloud Mask 89 | 90 | if nargout==3 91 | 92 | FileName = fullfile(mainpath, MSIinfo.Mask.MSK_CLOUDS); 93 | S = xml2struct(FileName); 94 | 95 | if isfield(S.eop_colon_Mask,'eop_colon_maskMembers') 96 | 97 | S = S.eop_colon_Mask.eop_colon_maskMembers; 98 | 99 | 100 | 101 | 102 | 103 | if length(S)==1 104 | % DetectFootPrint.Ndect(1) = str2num(S(1).eop_colon_MaskFeature.Attributes.gml_colon_id(24:25)); 105 | % disp('attention') 106 | % DetectFootPrint.Nband(1) = find(strcmp(MSIinfo.bandIdList , S(1).eop_colon_MaskFeature.Attributes.gml_colon_id(21:22))); 107 | Vect = str2num(S(1).eop_colon_MaskFeature.eop_colon_extentOf.gml_colon_Polygon.gml_colon_exterior.gml_colon_LinearRing.gml_colon_posList.Text); 108 | Vect = reshape(Vect' , [2 length(Vect)/2 ])'; 109 | CloudMask.Data{1,1} = Vect ; 110 | % Z = MSIinfo.Angles.VZA(:,:,MSIinfo.Angles.bandIdList==DetectFootPrint.Nband(1),MSIinfo.Angles.DeteIdList==DetectFootPrint.Ndect(1)) ; 111 | else 112 | 113 | for i = 1:length(S) 114 | % DetectFootPrint.Ndect(i,1) = str2num(S{i}.eop_colon_MaskFeature.Attributes.gml_colon_id(24:25)); 115 | % DetectFootPrint.Nband(i,1) = strcmp(MSIinfo.bandIdList , S{i}.eop_colon_MaskFeature.Attributes.gml_colon_id(21:22)); 116 | Vect = str2num(S{i}.eop_colon_MaskFeature.eop_colon_extentOf.gml_colon_Polygon.gml_colon_exterior.gml_colon_LinearRing.gml_colon_posList.Text); 117 | Vect = reshape(Vect' , [2 length(Vect)/2 ])'; 118 | CloudMask.Data{i,1} = Vect ; 119 | % if Pppplot == 1 120 | % IDX = knnsearch(X10m,DetectFootPrint.Data{i,1}(:,1)); 121 | % Ximg = X10m(IDX); 122 | % IDX = knnsearch(Y10m,DetectFootPrint.Data{i,1}(:,2)); 123 | % Yimg = Y10m(IDX); 124 | % plot(Ximg,Yimg,'k') 125 | % Z = MSIinfo.Angles.VAA(:,:,MSIinfo.Angles.bandIdList==DetectFootPrint.Nband(i),MSIinfo.Angles.DeteIdList==DetectFootPrint.Ndect(i)) ; 126 | % scatter(X5kmat(:),Y5kmat(:),30,Myreshape(Z),sym(i)) 127 | % end 128 | 129 | end 130 | end 131 | 132 | Vect = [NaN NaN]; 133 | for i = 1:length(S) 134 | Vect = cat(1,Vect ,CloudMask.Data{i},[NaN NaN]); 135 | end 136 | 137 | % X10m = single(MSIinfo.GeoInfo.Xstart.R20+Psize/2 : Psize : MSIinfo.GeoInfo.Xstart.R10-Psize/2 + Psize*(MSIinfo.GeoInfo.Size.R10(2)))'; 138 | % Y10m = single(MSIinfo.GeoInfo.Ystart.R20-Psize/2 :-Psize : MSIinfo.GeoInfo.Ystart.R10+Psize/2 - Psize*(MSIinfo.GeoInfo.Size.R10(1)))'; 139 | 140 | IDX = knnsearch(XPsizeOut',Vect(:,1)); 141 | IDY = knnsearch(YPsizeOut,Vect(:,2)); 142 | CM = uint8(poly2mask(double(IDX),double(IDY),length(YPsizeOut),length(XPsizeOut))); 143 | clear IDX IDY; 144 | else 145 | CM = zeros( length(YPsizeOut),length(XPsizeOut) ,'uint8'); 146 | end 147 | end 148 | 149 | 150 | %% 151 | %% 152 | %% 153 | %% 154 | %% Angles 155 | for iB = 1:length(BandList) 156 | % tic 157 | FileName = fullfile(mainpath,... 158 | MSIinfo.Mask.MSK_DETFOO.(['B' num2str(BandList(iB))])); 159 | 160 | S = xml2struct(FileName); 161 | clear FileName; 162 | 163 | if isfield(S.eop_colon_Mask.eop_colon_maskMembers,'eop_colon_MaskFeature') 164 | S = S.eop_colon_Mask.eop_colon_maskMembers.eop_colon_MaskFeature; 165 | else 166 | S = S.eop_colon_Mask.eop_colon_maskMembers; 167 | end 168 | % figure 169 | 170 | if length(S)==1 171 | 172 | try 173 | % the following codes are unworkable anymore. 174 | if ~(isfield(S,'eop_colon_MaskFeature')) 175 | S{1}.eop_colon_MaskFeature = S{1}; 176 | end 177 | DetectFootPrint.Ndect(1) = str2num(S{1}.eop_colon_MaskFeature.Attributes.gml_colon_id(24:25)); 178 | disp('attention') 179 | DetectFootPrint.Nband(1) = find(strcmp(MSIinfo.bandIdList , S(1).eop_colon_MaskFeature.Attributes.gml_colon_id(21:22))); 180 | Vect = str2num(S(1).eop_colon_MaskFeature.eop_colon_extentOf.gml_colon_Polygon.gml_colon_exterior.gml_colon_LinearRing.gml_colon_posList.Text); 181 | Vect = reshape(Vect' , [3 length(Vect)/3 ])'; 182 | DetectFootPrint.Data{1,1} = Vect ; 183 | clear Vect; 184 | catch 185 | if ~(isfield(S,'eop_colon_MaskFeature')) 186 | S.eop_colon_MaskFeature = S; 187 | end 188 | DetectFootPrint.Ndect(1) = str2num(S.eop_colon_MaskFeature.Attributes.gml_colon_id(24:25)); 189 | disp('attention') 190 | DetectFootPrint.Nband(1) = find(strcmp(bandIdList , S.eop_colon_MaskFeature.Attributes.gml_colon_id(21:22))); 191 | Vect = str2num(S.eop_colon_MaskFeature.eop_colon_extentOf.gml_colon_Polygon.gml_colon_exterior.gml_colon_LinearRing.gml_colon_posList.Text); 192 | Vect = reshape(Vect' , [3 length(Vect)/3 ])'; 193 | DetectFootPrint.Data{1,1} = Vect ; 194 | clear Vect; 195 | end 196 | % Z = MSIinfo.Angles.VZA(:,:,MSIinfo.Angles.bandIdList==DetectFootPrint.Nband(1),MSIinfo.Angles.DeteIdList==DetectFootPrint.Ndect(1)) ; 197 | else 198 | if Pppplot == 1 199 | figure 200 | hold on 201 | sym = 'oxoxoxoxox'; 202 | end 203 | for i = 1:length(S) 204 | if ~(isfield(S{i},'eop_colon_MaskFeature')) 205 | S{i}.eop_colon_MaskFeature = S{i}; 206 | end 207 | DetectFootPrint.Ndect(i,1) = str2num(S{i}.eop_colon_MaskFeature.Attributes.gml_colon_id(24:25)); 208 | % DetectFootPrint.Nband(i,1) = strcmp(MSIinfo.bandIdList , S{i}.eop_colon_MaskFeature.Attributes.gml_colon_id(21:22)); 209 | DetectFootPrint.Nband(i,1) = strcmp(bandIdList , S{i}.eop_colon_MaskFeature.Attributes.gml_colon_id(21:22)); 210 | 211 | Vect = str2num(S{i}.eop_colon_MaskFeature.eop_colon_extentOf.gml_colon_Polygon.gml_colon_exterior.gml_colon_LinearRing.gml_colon_posList.Text); 212 | Vect = reshape(Vect' , [3 length(Vect)/3 ])'; 213 | DetectFootPrint.Data{i,1} = Vect ; 214 | clear Vect; 215 | if Pppplot == 1 216 | 217 | IDX = knnsearch(X10m,DetectFootPrint.Data{i,1}(:,1)); 218 | Ximg = X10m(IDX); 219 | IDX = knnsearch(Y10m,DetectFootPrint.Data{i,1}(:,2)); 220 | Yimg = Y10m(IDX); 221 | clear IDX; 222 | plot(Ximg,Yimg,'k') 223 | clear Ximg Yimg; 224 | Z = MSIinfo.Angles.VAA(:,:,MSIinfo.Angles.bandIdList==DetectFootPrint.Nband(i),MSIinfo.Angles.DeteIdList==DetectFootPrint.Ndect(i)) ; 225 | scatter(X5kmat(:),Y5kmat(:),30,Myreshape(Z),sym(i)) 226 | end 227 | end 228 | end 229 | clear S; 230 | % % axis image 231 | 232 | 233 | %% 5km => 10m 234 | Matdetec = findMatdetecFootprint(DetectFootPrint,XPsizeOut,YPsizeOut); 235 | 236 | % JJ.B04_detfoo = hdfread('D:\UMD\Data\HLS\Products\detfoo.T36JTT.2015350.v1.0.hdf', '/B04_detfoo', 'Index', {[1 1],[1 1],[1830 1830]}); 237 | 238 | Angles.Matdetec(:,:,iB) = uint8(Matdetec) ; 239 | Angles.DetectFootPrint{iB} = DetectFootPrint ; 240 | % return 241 | clear dum* 242 | 243 | % figure 244 | % hold on 245 | % imagesc(X10m,Y10m,Matdetec) 246 | % for i = 1:length(S) 247 | % plot(DetectFootPrint.Data{i,1}(:,1),DetectFootPrint.Data{i,1}(:,2),'k') 248 | % end 249 | 250 | Chp = {'VAA' 'VZA'} ; 251 | 252 | for iC=1:2 253 | Angles.([Chp{iC} '_B' bandIdList{iB}]) = nan(size(Matdetec),'single'); 254 | for i = 1:length(DetectFootPrint.Ndect) 255 | 256 | Z = MSIinfo.Angles.(Chp{iC})(:,:,BandSel,MSIinfo.Angles.DeteIdList==DetectFootPrint.Ndect(i)) ; 257 | Z = single(inpaint_nans(Z)); 258 | 259 | testMatdetec = Matdetec==DetectFootPrint.Ndect(i) ; 260 | [Ista dum] = find(max(testMatdetec,[],2),1,'first'); 261 | [Iend dum] = find(max(testMatdetec,[],2),1,'last'); 262 | [dum Jsta] = find(max(testMatdetec,[],1),1,'first'); 263 | [dum Jend] = find(max(testMatdetec,[],1),1,'last'); 264 | clear dum; 265 | % Ista = floor(Ista/10)+1; 266 | % Jsta = floor(Jsta/10)+1; 267 | % Iend = ceil(Iend/10); 268 | % Jend = ceil(Jend/10); 269 | 270 | ZI = zeros(size(Matdetec),'single'); 271 | 272 | ZI(Ista:Iend,Jsta:Jend) = interp2(X5km,Y5km,Z,XPsizeOutmat(Ista:Iend,Jsta:Jend),YPsizeOutmat(Ista:Iend,Jsta:Jend),InterpMethod); 273 | clear Z Ista Iend Jsta Jend; 274 | % ZI(Ista:Iend,Jsta:Jend) = interp2(X5km,Y5km,Z,X10mat(Ista:Iend,Jsta:Jend),Y100mat(Ista:Iend,Jsta:Jend),InterpMethod); 275 | % ZI = imresize(ZI,10,MethInterp100m10m); 276 | 277 | Angles.([Chp{iC} '_B' bandIdList{iB}])(testMatdetec) = ZI(testMatdetec) ; 278 | clear ZI testMatdetec; 279 | end 280 | end 281 | clear Matdetec; 282 | % toc 283 | end 284 | 285 | 286 | Z = MSIinfo.Angles.SZA ; 287 | ZI = interp2(X5km,Y5km,Z,XPsizeOutmat,YPsizeOutmat,InterpMethod); 288 | clear Z; 289 | % ZI=imresize(ZI,10,MethInterp100m10m); 290 | Angles.SZA = ZI; 291 | 292 | Z = MSIinfo.Angles.SAA ; 293 | ZI = interp2(X5km,Y5km,Z,XPsizeOutmat,YPsizeOutmat,InterpMethod); 294 | clear Z X5km Y5km XPsizeOutmat YPsizeOutmat InterpMethod; 295 | % ZI=imresize(ZI,10,MethInterp100m10m); 296 | Angles.SAA = ZI; 297 | clear ZId; 298 | 299 | % for iB = BandList 300 | % Angles.(['RAA_B' bandIdList{iB+1}]) = abs(Angles.(['VAA_B' num2str(iB)]) - Angles.SAA) ; 301 | % Angles=rmfield(Angles,['VAA_B' num2str(iB)]); 302 | % end 303 | % Angles=rmfield(Angles,'SAA'); 304 | 305 | fn = fieldnames(Angles); 306 | dum = cellfun(@(x) isempty(strfind(x,'A')),fn); 307 | fn(dum) = [] ; 308 | clear dum; 309 | for i=1:length(fn)-1 310 | dum = Angles.(fn{i}) ; 311 | dum(dum<0) = 655.36 ; 312 | Angles.(fn{i}) = uint16(dum*100); 313 | clear dum; 314 | end 315 | clear fn; 316 | 317 | Angles.BandList = BandList ; 318 | Angles.bandIdList = bandIdList ; 319 | clear BandList bandIdList; 320 | 321 | return 322 | 323 | y = structfun(@(x) max(x(:)), Angles) 324 | -------------------------------------------------------------------------------- /ReadSunViewGeometryMSIBaseline04.m: -------------------------------------------------------------------------------- 1 | function [MSIinfo Angles CM] = ReadSunViewGeometryMSIBaseline04 (DataStrip,Granule,BandSel,PsizeOut,Dmain) 2 | % DataStrip='S2A_OPER_PRD_MSIL1C_PDMC_20150818T101451_R080_V20150817T114755_20150817T114755'; 3 | % Granule='S2A_OPER_MSI_L1C_TL_SGS__20150817T131818_A000792_T28QBG_N01'; 4 | % this is derived from Fmask 3.3 for Sentinel 2. 5 | % 6 | % Debug the selection of Matdetec== DetectFootPrint.Ndect(i) from Matdetec==i (Jan 24, 2022) 7 | % 8 | InterpMethod = 'linear';'nearest'; 9 | 10 | % Dmain = '/nobackupp6/jju/S2SAFE/'; 11 | Pppplot = 0 ; 12 | % PsizeOut = 20 ; 13 | if nargin<5 14 | FilesInfo.DirIn = CodeStartingLinesSentinel(-1); 15 | else 16 | FilesInfo.DirIn = Dmain; 17 | end 18 | % FilesInfo.Num = '.04'; 19 | FilesInfo.DataStrip = DataStrip; 20 | FilesInfo.Granule=Granule; 21 | 22 | if Granule(1) == 'L' 23 | mainpath = fullfile(Dmain,[DataStrip '.SAFE']); 24 | else 25 | mainpath = fullfile(Dmain, [DataStrip '.SAFE'],'GRANULE', Granule, 'QI_DATA'); 26 | end 27 | 28 | try 29 | MSIinfo=ReadMetadataMSI(FilesInfo); 30 | catch 31 | error('The metadata was wrongly loaded. Please input Sentinel-2 TOA data.'); 32 | end 33 | 34 | if nargout==1 35 | return 36 | end 37 | bandIdList = {'01' '02' '03' '04' '05' '06' '07' '08' '8A' '09' '10' '11' '12'} ; 38 | BandList = 0:12 ; 39 | if exist('BandSel','var') 40 | bandIdList = bandIdList(BandSel); 41 | BandList = BandList(BandSel); 42 | end 43 | if ~exist('PsizeOut','var') 44 | PsizeOut = 10 ; 45 | end 46 | % bandIdList = bandIdList (8:9); 47 | % BandList = BandList (8:9); 48 | 49 | 50 | % MethInterp100m10m = 'nearest'; 51 | 52 | % [BandList]=find(MSIinfo.Angles.Mean.VZA2==median(MSIinfo.Angles.Mean.VZA2)) ; 53 | 54 | %% 55 | X5km = MSIinfo.GeoInfo.Xstart.R10 : 5000 : MSIinfo.GeoInfo.Xstart.R10 + 5000*22 ; 56 | Y5km = MSIinfo.GeoInfo.Ystart.R10 :-5000 : MSIinfo.GeoInfo.Ystart.R10 - 5000*22 ; 57 | % [X5kmat Y5kmat]=meshgrid(X5km,Y5km); 58 | % Psize = 100 ; 59 | % X100m = single(MSIinfo.GeoInfo.Xstart.R20+Psize/2 : Psize : MSIinfo.GeoInfo.Xstart.R20-Psize/2 + Psize*(MSIinfo.GeoInfo.Size.R10(2)/10))'; 60 | % Y100m = single(MSIinfo.GeoInfo.Ystart.R20-Psize/2 :-Psize : MSIinfo.GeoInfo.Ystart.R20+Psize/2 - Psize*(MSIinfo.GeoInfo.Size.R10(1)/10))'; 61 | % [X100mat Y100mat]=meshgrid(X100m,Y100m); 62 | 63 | Psize = 10 ; 64 | X10m = single(MSIinfo.GeoInfo.Xstart.R20 : Psize : MSIinfo.GeoInfo.Xstart.R20+(MSIinfo.GeoInfo.Size.R10(2)-1)*Psize) + Psize/2 ; 65 | Y10m = single(MSIinfo.GeoInfo.Ystart.R20 :-Psize : MSIinfo.GeoInfo.Ystart.R20-(MSIinfo.GeoInfo.Size.R10(1)-1)*Psize)' - Psize/2 ; 66 | 67 | % X100m = imresize(X10m,10/100,'box') ; 68 | % Y100m = imresize(Y10m,10/100,'box') ; 69 | 70 | XPsizeOut = imresize(X10m,10/PsizeOut,'box') ; 71 | YPsizeOut = imresize(Y10m,10/PsizeOut,'box') ; 72 | clear PsizeOut; 73 | 74 | % Psize = PsizeOut ; 75 | 76 | % [X100mat Y100mat]=meshgrid(X100m,Y100m); 77 | [XPsizeOutmat YPsizeOutmat]=meshgrid(XPsizeOut,YPsizeOut); 78 | 79 | 80 | if Pppplot == 1 81 | % Psize = PsizeOut ; 82 | % X10m = single(MSIinfo.GeoInfo.Xstart.R20+Psize/2 : Psize : MSIinfo.GeoInfo.Xstart.R20-Psize/2 + (MSIinfo.GeoInfo.Size.R10(2)*10/Psize))'; 83 | % Y10m = single(MSIinfo.GeoInfo.Ystart.R20-Psize/2 :-Psize : MSIinfo.GeoInfo.Ystart.R20+Psize/2 - (MSIinfo.GeoInfo.Size.R10(1)*10/Psize))'; 84 | % [X10mat Y100mat]=meshgrid(X100m,Y100m); 85 | [X5kmat Y5kmat]=meshgrid(X5km,Y5km); 86 | end 87 | % % kern = ones(20);imcircle(20); 88 | 89 | %% cloud Mask 90 | 91 | if nargout==3 92 | 93 | FileName = fullfile(mainpath, MSIinfo.Mask.MSK_CLOUDS); 94 | 95 | S = xml2struct(FileName); 96 | 97 | if isfield(S.eop_colon_Mask,'eop_colon_maskMembers') 98 | 99 | S = S.eop_colon_Mask.eop_colon_maskMembers; 100 | 101 | if length(S)==1 102 | % DetectFootPrint.Ndect(1) = str2num(S(1).eop_colon_MaskFeature.Attributes.gml_colon_id(24:25)); 103 | % disp('attention') 104 | % DetectFootPrint.Nband(1) = find(strcmp(MSIinfo.bandIdList , S(1).eop_colon_MaskFeature.Attributes.gml_colon_id(21:22))); 105 | Vect = str2num(S(1).eop_colon_MaskFeature.eop_colon_extentOf.gml_colon_Polygon.gml_colon_exterior.gml_colon_LinearRing.gml_colon_posList.Text); 106 | Vect = reshape(Vect' , [2 length(Vect)/2 ])'; 107 | CloudMask.Data{1,1} = Vect ; 108 | % Z = MSIinfo.Angles.VZA(:,:,MSIinfo.Angles.bandIdList==DetectFootPrint.Nband(1),MSIinfo.Angles.DeteIdList==DetectFootPrint.Ndect(1)) ; 109 | else 110 | 111 | for i = 1:length(S) 112 | % DetectFootPrint.Ndect(i,1) = str2num(S{i}.eop_colon_MaskFeature.Attributes.gml_colon_id(24:25)); 113 | % DetectFootPrint.Nband(i,1) = strcmp(MSIinfo.bandIdList , S{i}.eop_colon_MaskFeature.Attributes.gml_colon_id(21:22)); 114 | Vect = str2num(S{i}.eop_colon_MaskFeature.eop_colon_extentOf.gml_colon_Polygon.gml_colon_exterior.gml_colon_LinearRing.gml_colon_posList.Text); 115 | Vect = reshape(Vect' , [2 length(Vect)/2 ])'; 116 | CloudMask.Data{i,1} = Vect ; 117 | % if Pppplot == 1 118 | % IDX = knnsearch(X10m,DetectFootPrint.Data{i,1}(:,1)); 119 | % Ximg = X10m(IDX); 120 | % IDX = knnsearch(Y10m,DetectFootPrint.Data{i,1}(:,2)); 121 | % Yimg = Y10m(IDX); 122 | % plot(Ximg,Yimg,'k') 123 | % Z = MSIinfo.Angles.VAA(:,:,MSIinfo.Angles.bandIdList==DetectFootPrint.Nband(i),MSIinfo.Angles.DeteIdList==DetectFootPrint.Ndect(i)) ; 124 | % scatter(X5kmat(:),Y5kmat(:),30,Myreshape(Z),sym(i)) 125 | % end 126 | 127 | end 128 | end 129 | 130 | Vect = [NaN NaN]; 131 | for i = 1:length(S) 132 | Vect = cat(1,Vect ,CloudMask.Data{i},[NaN NaN]); 133 | end 134 | 135 | % X10m = single(MSIinfo.GeoInfo.Xstart.R20+Psize/2 : Psize : MSIinfo.GeoInfo.Xstart.R10-Psize/2 + Psize*(MSIinfo.GeoInfo.Size.R10(2)))'; 136 | % Y10m = single(MSIinfo.GeoInfo.Ystart.R20-Psize/2 :-Psize : MSIinfo.GeoInfo.Ystart.R10+Psize/2 - Psize*(MSIinfo.GeoInfo.Size.R10(1)))'; 137 | 138 | IDX = knnsearch(XPsizeOut',Vect(:,1)); 139 | IDY = knnsearch(YPsizeOut,Vect(:,2)); 140 | CM = uint8(poly2mask(double(IDX),double(IDY),length(YPsizeOut),length(XPsizeOut))); 141 | clear IDX IDY; 142 | else 143 | CM = zeros( length(YPsizeOut),length(XPsizeOut) ,'uint8'); 144 | end 145 | end 146 | 147 | 148 | %% 149 | %% 150 | %% 151 | %% 152 | %% Angles 153 | for iB = 1:length(BandList) 154 | % tic 155 | FileName = fullfile(mainpath,... 156 | MSIinfo.Mask.MSK_DETFOO.(['B' num2str(BandList(iB))])); 157 | 158 | %% 5km => 10m 159 | Matdetec = imread(FileName); 160 | 161 | % JJ.B04_detfoo = hdfread('D:\UMD\Data\HLS\Products\detfoo.T36JTT.2015350.v1.0.hdf', '/B04_detfoo', 'Index', {[1 1],[1 1],[1830 1830]}); 162 | 163 | Angles.Matdetec(:,:,iB) = uint8(Matdetec) ; 164 | DetectFootPrint.Ndect = unique(Matdetec(Matdetec> 0)); 165 | DetectFootPrint.Nband = ones(size(DetectFootPrint.Ndect)); 166 | Angles.DetectFootPrint{iB} = DetectFootPrint ; 167 | % return 168 | clear dum* 169 | 170 | % figure 171 | % hold on 172 | % imagesc(X10m,Y10m,Matdetec) 173 | % for i = 1:length(S) 174 | % plot(DetectFootPrint.Data{i,1}(:,1),DetectFootPrint.Data{i,1}(:,2),'k') 175 | % end 176 | 177 | Chp = {'VAA' 'VZA'} ; 178 | 179 | for iC=1:2 180 | Angles.([Chp{iC} '_B' bandIdList{iB}]) = nan(size(Matdetec),'single'); 181 | for i = 1:length(DetectFootPrint.Ndect) 182 | 183 | Z = MSIinfo.Angles.(Chp{iC})(:,:,BandSel,MSIinfo.Angles.DeteIdList==DetectFootPrint.Ndect(i)) ; 184 | Z = single(inpaint_nans(Z)); 185 | 186 | testMatdetec = Matdetec== DetectFootPrint.Ndect(i); 187 | [Ista dum] = find(max(testMatdetec,[],2),1,'first'); 188 | [Iend dum] = find(max(testMatdetec,[],2),1,'last'); 189 | [dum Jsta] = find(max(testMatdetec,[],1),1,'first'); 190 | [dum Jend] = find(max(testMatdetec,[],1),1,'last'); 191 | clear dum; 192 | % Ista = floor(Ista/10)+1; 193 | % Jsta = floor(Jsta/10)+1; 194 | % Iend = ceil(Iend/10); 195 | % Jend = ceil(Jend/10); 196 | 197 | ZI = zeros(size(Matdetec),'single'); 198 | 199 | ZI(Ista:Iend,Jsta:Jend) = interp2(X5km,Y5km,Z,XPsizeOutmat(Ista:Iend,Jsta:Jend),YPsizeOutmat(Ista:Iend,Jsta:Jend),InterpMethod); 200 | clear Z Ista Iend Jsta Jend; 201 | % ZI(Ista:Iend,Jsta:Jend) = interp2(X5km,Y5km,Z,X10mat(Ista:Iend,Jsta:Jend),Y100mat(Ista:Iend,Jsta:Jend),InterpMethod); 202 | % ZI = imresize(ZI,10,MethInterp100m10m); 203 | 204 | Angles.([Chp{iC} '_B' bandIdList{iB}])(testMatdetec) = ZI(testMatdetec) ; 205 | clear ZI testMatdetec; 206 | end 207 | end 208 | clear Matdetec; 209 | % toc 210 | end 211 | 212 | 213 | Z = MSIinfo.Angles.SZA ; 214 | ZI = interp2(X5km,Y5km,Z,XPsizeOutmat,YPsizeOutmat,InterpMethod); 215 | clear Z; 216 | % ZI=imresize(ZI,10,MethInterp100m10m); 217 | Angles.SZA = ZI; 218 | 219 | Z = MSIinfo.Angles.SAA ; 220 | ZI = interp2(X5km,Y5km,Z,XPsizeOutmat,YPsizeOutmat,InterpMethod); 221 | clear Z X5km Y5km XPsizeOutmat YPsizeOutmat InterpMethod; 222 | % ZI=imresize(ZI,10,MethInterp100m10m); 223 | Angles.SAA = ZI; 224 | clear ZId; 225 | 226 | % for iB = BandList 227 | % Angles.(['RAA_B' bandIdList{iB+1}]) = abs(Angles.(['VAA_B' num2str(iB)]) - Angles.SAA) ; 228 | % Angles=rmfield(Angles,['VAA_B' num2str(iB)]); 229 | % end 230 | % Angles=rmfield(Angles,'SAA'); 231 | 232 | fn = fieldnames(Angles); 233 | dum = cellfun(@(x) isempty(strfind(x,'A')),fn); 234 | fn(dum) = [] ; 235 | clear dum; 236 | for i=1:length(fn)-1 237 | dum = Angles.(fn{i}) ; 238 | dum(dum<0) = 655.36 ; 239 | Angles.(fn{i}) = uint16(dum*100); 240 | clear dum; 241 | end 242 | clear fn; 243 | 244 | Angles.BandList = BandList ; 245 | Angles.bandIdList = bandIdList ; 246 | clear BandList bandIdList; 247 | 248 | return 249 | 250 | y = structfun(@(x) max(x(:)), Angles) 251 | -------------------------------------------------------------------------------- /Readopentopo.m: -------------------------------------------------------------------------------- 1 | function DEM = Readopentopo(varargin) 2 | 3 | %READOPENTOPO read DEM using the opentopography.org API 4 | % 5 | % Syntax 6 | % 7 | % DEM = readopentopo(pn,pv,...) 8 | % 9 | % Description 10 | % 11 | % readopentopo reads DEMs from opentopography.org using the API 12 | % described on: 13 | % http://www.opentopography.org/developers 14 | % The DEM comes in geographic coordinates (WGS84) and should be 15 | % projected to a projected coordinate system (use reproject2utm) before 16 | % analysis in TopoToolbox. 17 | % 18 | % Input arguments 19 | % 20 | % Parameter name values 21 | % 'filename' provide filename. By default, the function will save 22 | % the DEM to a temporary file in the system's temporary 23 | % folder. 24 | % 'north' northern boundary in geographic coordinates (WGS84) 25 | % 'south' southern boundary 26 | % 'west' western boundary 27 | % 'east' eastern boundary 28 | % 'demtype' The global raster dataset - SRTM GL3 (90m) is 29 | % 'SRTMGL3', SRTM GL1 (30m) is 'SRTMGL1', SRTM GL1 30 | % (Ellipsoidal) is 'SRTMGL1_E', and ALOS World 3D 30m 31 | % is 'AW3D30' 32 | % 'deletefile' 'true' or false. True, if file should be deleted 33 | % after it was downloaded and added to the workspace. 34 | % 35 | % Output arguments 36 | % 37 | % DEM Digital elevation model in geographic coordinates 38 | % (GRIDobj) 39 | % 40 | % See also: GRIDobj, websave 41 | % 42 | % Reference: http://www.opentopography.org/developers 43 | % 44 | % Author: Wolfgang Schwanghart (w.schwanghart[at]geo.uni-potsdam.de) 45 | % Date: 19. June, 2017 46 | 47 | 48 | p = inputParser; 49 | addParameter(p,'filename',[tempname '.tif']); 50 | % addParameter(p,'interactive',false); 51 | addParameter(p,'north',37.091337); 52 | addParameter(p,'south',36.738884); 53 | addParameter(p,'west',-120.168457); 54 | addParameter(p,'east',-119.465576); 55 | addParameter(p,'demtype','SRTMGL3'); 56 | addParameter(p,'deletefile',true); 57 | parse(p,varargin{:}); 58 | 59 | demtype = validatestring(p.Results.demtype,{'SRTMGL3','SRTMGL1','SRTMGL1_E','AW3D30'},'readopentopo'); 60 | 61 | url = 'http://opentopo.sdsc.edu/otr/getdem'; 62 | 63 | % create output file 64 | f = fullfile(p.Results.filename); 65 | 66 | % save to drive 67 | options = weboptions('Timeout',inf); 68 | 69 | west = p.Results.west; 70 | east = p.Results.east; 71 | south = p.Results.south; 72 | north = p.Results.north; 73 | 74 | % if any([isempty(west) isempty(east) isempty(south) isempty(north)]) || p.Results.interactive; 75 | % wm = webmap; 76 | % % get dialog box 77 | % messagetext = ['Zoom and resize the webmap window to choose DEM extent. ' ... 78 | % 'Click the close button when you''re done.']; 79 | % d = waitdialog(messagetext); 80 | % uiwait(d); 81 | % [latlim,lonlim] = wmlimits(wm); 82 | % west = lonlim(1); 83 | % east = lonlim(2); 84 | % south = latlim(1); 85 | % north = latlim(2); 86 | % end 87 | 88 | 89 | 90 | websave(f,url,'west',west,... 91 | 'east',east,... 92 | 'north',north,... 93 | 'south',south,... 94 | 'outputFormat', 'GTiff', ... 95 | 'demtype', demtype, ... 96 | options); 97 | 98 | DEM = GRIDobj(f); 99 | DEM.name = demtype; 100 | 101 | if p.Results.deletefile 102 | delete(f); 103 | end 104 | end 105 | 106 | % function d = waitdialog(messagetext) 107 | % d = dialog('Position',[300 300 250 150],'Name','Choose rectangle region',... 108 | % 'WindowStyle','normal'); 109 | % 110 | % txt = uicontrol('Parent',d,... 111 | % 'Style','text',... 112 | % 'Position',[20 80 210 40],... 113 | % 'String',messagetext); 114 | % 115 | % btn = uicontrol('Parent',d,... 116 | % 'Position',[85 20 70 25],... 117 | % 'String','Close',... 118 | % 'Callback','delete(gcf)'); 119 | % end 120 | 121 | 122 | -------------------------------------------------------------------------------- /Saturate.m: -------------------------------------------------------------------------------- 1 | function satu_Bv = Saturate( satu_blue,satu_green,satu_red ) 2 | %SATURE Summary of this function goes here 3 | % Detailed explanation goes here 4 | satu_Bv=satu_blue+satu_green+satu_red>=1; % saturated pixes at any visible bands. 5 | clear satu_blue satu_green satu_red; 6 | end 7 | 8 | -------------------------------------------------------------------------------- /arcslope.m: -------------------------------------------------------------------------------- 1 | function SLP = arcslope(DEM,unit) 2 | 3 | %ARCSLOPE mean gradient from a digital elevation model sensu ArcGIS 4 | % 5 | % Syntax 6 | % 7 | % SLP = arcslope(DEM) 8 | % SLP = arcslope(DEM,unit) 9 | % 10 | % Description 11 | % 12 | % ARCSLOPE returns the gradient as calculated by ArcGIS (mean slope of 13 | % 8 connected neighborhood). Default unit is as tangent, but you can 14 | % specify alternative units identical to gradient8 function. 15 | % 16 | % Input 17 | % 18 | % DEM digital elevation model (class: GRIDobj) 19 | % unit 'tan' --> tangent (default) 20 | % 'rad' --> radian 21 | % 'deg' --> degree 22 | % 'sin' --> sine 23 | % 'per' --> percent 24 | % 25 | % Output 26 | % 27 | % SLP mean gradient (class: GRIDobj) 28 | % 29 | % Example 30 | % 31 | % DEM = GRIDobj('srtm_bigtujunga30m_utm11.tif'); 32 | % SLP = arcslope(DEM); 33 | % imageschs(DEM,SLP); 34 | % 35 | % See also: GRIDobj/gradient8 36 | % 37 | % Author: Wolfgang Schwanghart (w.schwanghart[at]geo.uni-potsdam.de) and 38 | % Adam M. Forte (aforte[a]asu.edu) 39 | % Date: 6. February, 2017 40 | 41 | 42 | z=DEM.Z; 43 | 44 | % Pad array to avoid edge effects 45 | zp=padarray(z,[1 1],'replicate'); 46 | 47 | % Handle nans 48 | I = isnan(zp); 49 | in = any(I(:)); 50 | if in 51 | [~,L] = bwdist(~I); 52 | zp = zp(L); 53 | end 54 | 55 | % Define anon function to calculate the mean gradient of 8 connected neighborhood 56 | % by same algorithm as the ArcGIS slope function 57 | kernel = [ -1 -2 -1; 0 0 0; 1 2 1]'; 58 | rr = sqrt((conv2(zp,kernel','valid')./(8*DEM.cellsize)).^2 + ... 59 | (conv2(zp,kernel,'valid')./(8*DEM.cellsize)).^2); 60 | 61 | % Package output 62 | if nargin == 1 63 | unit = 'tangent'; 64 | else 65 | unit = validatestring(unit,{'tangent' 'degree' 'radian' 'percent' 'sine'},'gradient8','unit',2); 66 | end 67 | 68 | if in 69 | rr(I(2:end-1,2:end-1)) = nan; 70 | end 71 | 72 | % create a copy of the DEM instance 73 | SLP = DEM; 74 | SLP.name = 'slope (arcgis)'; 75 | SLP.zunit = unit; 76 | SLP.Z=rr; 77 | 78 | switch unit 79 | case 'tangent' 80 | % do nothing 81 | case 'degree' 82 | SLP.Z = atand(SLP.Z); 83 | case 'radian' 84 | SLP.Z = atan(SLP.Z); 85 | case 'sine' 86 | SLP.Z = sin(atan(SLP.Z)); 87 | case 'percent' 88 | SLP.Z = SLP.Z*100; 89 | end 90 | 91 | -------------------------------------------------------------------------------- /aspect.m: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GERSL/Fmask/77b5b27284c4aec3b93d322c96800b00eaf6ad8e/aspect.m -------------------------------------------------------------------------------- /autoFmask.m: -------------------------------------------------------------------------------- 1 | function clr_pct = autoFmask(varargin) 2 | % AUTOFMASK Automatedly detect clouds, cloud shadows, snow, and water for 3 | % Landsats 4-7 TM/EMT+, Landsat 8 OLI/TIRS, and Sentinel 2 MSI images. 4 | % 5 | % 6 | % Description 7 | % 8 | % This 4.7 version has better cloud, cloud shadow, and snow detection 9 | % results for Sentinel-2 data and better results (compared to the 3.3 10 | % version that is being used by USGS as the Colection 1 QA Band) for 11 | % Landsats 4-8 data as well. 12 | % 13 | % If any questions, please do not hesitate to contact 14 | % Shi Qiu (shi.qiu@uconn.edu) and Zhe Zhu (zhe@uconn.edu) 15 | % 16 | % 17 | % Input arguments 18 | % 19 | % cloud Dilated number of pixels for cloud with default value of 3. 20 | % shadow Dilated number of pixels for cloud shadow with default value of 3. 21 | % snow Dilated number of pixels for snow with default value of 0. 22 | % p Cloud probability threshold with default values of 10.0 for 23 | % Landsats 4~7, 17.5 for Landsat 8, and 20.0 for Sentinel 2. 24 | % d Radius of dilation for Potential False Positive Cloud such as 25 | % urban/built-up and (mountian) snow/ice. Default: 0 meters. 26 | % Higher the value, Larger the potential false positive cloud 27 | % layer. This is used for the places where the orginal Potential 28 | % False Positive Cloud Layer fails to include the false 29 | % posistive clouds. 30 | % e Radius of erosion for Potential False Positive Cloud such as 31 | % urban/built-up and (mountian) snow/ice. Default: 150 meters 32 | % for Landsats 4-7 and 90 meters for Landsat 8 and 33 | % Sentinel-2. 34 | % sw ShadowWater (SW) means the shadow of cloud over water. 35 | % Default: False 36 | % We do not suggest mask out the cloud shadow over water 37 | % since it is less meanful and very time-comsuing. 38 | % udem The path of User's DEM data. (.tiff). If users provide 39 | % local DEM data, Fmask 4.1 will process the image along with this DEM 40 | % data; or, the default USGS GTOPO30 will be used. 41 | % auxi The path of the auxiliary data ('AuxiData'). (for standalone only) 42 | % 43 | % Output arguments 44 | % 45 | % fmask 0: clear land 46 | % 1: clear water 47 | % 2: cloud shadow 48 | % 3: snow 49 | % 4: cloud 50 | % 255: filled (outside) 51 | % 52 | % Examples 53 | % 54 | % clr_pct = autoFmask('cloud',0, 'shadow', 0) will produce the mask without buffers. 55 | % clr_pct = autoFmask('p',20) forces cloud probablity thershold as 20. 56 | % clr_pct = autoFmask('e',500) forces erosion radius for Potential False Positive Cloud as 500 meters to remove the large commission errors. 57 | % 58 | % 59 | % Author: Shi Qiu (shi.qiu@uconn.edu) and Zhe Zhu (zhe@uconn.edu) 60 | % Last Date: Jan. 16, 2025 61 | % Copyright @ GERS Lab, UCONN. 62 | 63 | warning('off','all'); % do not show warning information 64 | tic 65 | fmask_soft_name='Fmask 4.7'; 66 | fprintf('%s start ...\n',fmask_soft_name); 67 | path_data=pwd; 68 | 69 | %% get parameters from inputs 70 | p = inputParser; 71 | p.FunctionName = 'FmaskParas'; 72 | % optional 73 | % default values. 74 | addParameter(p,'cloud',3); 75 | addParameter(p,'shadow',3); 76 | addParameter(p,'snow',0); 77 | 78 | %% read info from .xml. 79 | [sensor,~,InputFile,main_meta] = LoadSensorType(path_data); 80 | if isempty(sensor) 81 | error('%s works only for Landsats 4-7, Landsat 8, and Sentinel 2 images.\n',fmask_soft_name); 82 | end 83 | 84 | default_paras = FmaskParameters(sensor); 85 | tpw = default_paras.ThinWeight; 86 | addParameter(p,'d',default_paras.PFPCLayerExtensinRadius); 87 | addParameter(p,'e',default_paras.PFPCErosionRadius); 88 | addParameter(p,'p',default_paras.CloudProbabilityThershold); 89 | addParameter(p,'resolution',default_paras.OutputResolution); 90 | 91 | addParameter(p,'sw',default_paras.ShadowWater); 92 | 93 | % user's path for DEM 94 | addParameter(p,'udem',''); 95 | 96 | % user's path for the auxiliaray data 97 | addParameter(p,'auxi',''); 98 | 99 | % request user's input 100 | parse(p,varargin{:}); 101 | resolution=p.Results.resolution; 102 | cldpix=p.Results.cloud; 103 | sdpix=p.Results.shadow; 104 | snpix=p.Results.snow; 105 | expdpix = round(p.Results.d/resolution); 106 | erdpix=round(p.Results.e/resolution); 107 | cldprob=p.Results.p; 108 | isShadowater = p.Results.sw; 109 | 110 | % users can use the local dem. 111 | userdem = p.Results.udem; 112 | % input the folder of auxiliaray data 113 | pathauxi = p.Results.auxi; 114 | clear p; 115 | 116 | fprintf('Cloud/cloud shadow/snow dilated by %d/%d/%d pixels.\n',cldpix,sdpix,snpix); 117 | fprintf('Cloud probability threshold of %.2f%%.\n',cldprob); 118 | 119 | fprintf('Load or calculate TOA reflectances.\n'); 120 | 121 | %% load data 122 | [data_meta,data_toabt,angles_view,trgt] = LoadData(path_data,sensor,InputFile,main_meta); 123 | clear InputFile norMTL; 124 | 125 | if isempty(userdem) 126 | % default DEM 127 | [dem,slope,aspect,water_occur] = LoadAuxiData(fullfile(path_data,data_meta.Output),data_meta.Name,data_meta.BBox,trgt,false, 'auxi', pathauxi); % true false 128 | else 129 | [dem,slope,aspect,water_occur] = LoadAuxiData(fullfile(path_data,data_meta.Output),data_meta.Name,data_meta.BBox,trgt,false, 'userdem',userdem, 'auxi', pathauxi); % true false 130 | end 131 | 132 | fprintf('Detect potential clouds, cloud shadows, snow, and water.\n'); 133 | 134 | %% public data 135 | mask=ObservMask(data_toabt.BandBlue); 136 | 137 | % a pixel's DEM can be set as the lowest value derived from the all workable pixels. 138 | if ~isempty(dem) 139 | dem_nan=isnan(dem); 140 | dem(dem_nan)=double(prctile(dem(~dem_nan&mask),0.001)); % exclude DEM errors. 141 | clear dem_nan; 142 | end 143 | 144 | % NDVI NDSI NDBI 145 | ndvi = NDVI(data_toabt.BandRed, data_toabt.BandNIR); 146 | ndsi = NDSI(data_toabt.BandGreen, data_toabt.BandSWIR1); 147 | cdi = CDI(data_toabt.BandVRE3,data_toabt.BandNIR8,data_toabt.BandNIR);% band 7, 8, AND 8a 148 | 149 | data_toabt.BandVRE3 = []; 150 | data_toabt.BandNIR8 = []; 151 | 152 | % Statured Visible Bands 153 | satu_Bv = Saturate(data_toabt.SatuBlue, data_toabt.SatuGreen, data_toabt.SatuRed); 154 | data_toabt.SatuBlue = []; 155 | 156 | %% select potential cloud pixels (PCPs) 157 | % inputs: BandSWIR2 BandBT BandBlue BandGreen BandRed BandNIR BandSWIR1 158 | % BandCirrus 159 | % [idplcd,BandCirrusNormal,whiteness,HOT] = DetectPotentialPixels(mask,data_toabt,dem,ndvi,ndsi,satu_Bv); 160 | 161 | %% detect snow 162 | psnow = DetectSnow(data_meta.Dim, data_toabt.BandGreen, data_toabt.BandNIR, data_toabt.BandBT, ndsi); 163 | 164 | %% detect water 165 | [water, waterAll] = DetectWater(data_meta.Dim, mask, data_toabt.BandNIR, ndvi, psnow, slope, water_occur); 166 | clear water_occur; 167 | 168 | [idplcd,BandCirrusNormal,whiteness,HOT] = DetectPotentialPixels(mask,data_toabt,dem,ndvi,ndsi,satu_Bv); 169 | 170 | data_toabt.BandBlue = []; 171 | data_toabt.BandRed = []; 172 | data_toabt.BandSWIR2 = []; 173 | clear satu_Bv; 174 | data_toabt.BandCirrus = BandCirrusNormal; %refresh Cirrus band. 175 | clear BandCirrusNormal; 176 | 177 | %% select pure snow/ice pixels. 178 | abs_snow = DetectAbsSnow(data_toabt.BandGreen,data_toabt.SatuGreen,ndsi,psnow,data_meta.Resolution(1)); 179 | 180 | if ~isnan(abs_snow) 181 | idplcd(abs_snow==1)=0; clear abs_snow; % remove pure snow/ice pixels from all PCPs. 182 | end 183 | 184 | %% detect potential cloud 185 | ndbi = NDBI(data_toabt.BandNIR, data_toabt.BandSWIR1); 186 | 187 | % inputs: BandCirrus BandBT BandSWIR1 SatuGreen SatuRed 188 | [sum_clr,pcloud_all,idlnd,t_templ,t_temph]=DetectPotentialCloud(data_meta,mask,water,data_toabt, dem, ndvi,ndsi,ndbi,idplcd,whiteness,HOT,tpw,cldprob); 189 | clear ndsi idplcd whiteness HOT tpw cldprob; 190 | 191 | data_toabt.SatuGreen = []; 192 | data_toabt.SatuRed = []; 193 | data_toabt.BandCirrus = []; 194 | 195 | %% detect potential flase positive cloud layer, including urban, coastline, and snow/ice. 196 | pfpl = DetectPotentialFalsePositivePixels(mask, psnow, slope, ndbi, ndvi, data_toabt.BandBT,cdi, water,data_meta.Resolution(1)); 197 | 198 | clear ndbi ndvi; 199 | 200 | % buffer the potential false positive cloud layer. 201 | if expdpix>0 202 | PFPCEs=strel('square',2*expdpix+1); 203 | pfpl=imdilate(pfpl,PFPCEs); 204 | clear PFPCEs; 205 | end 206 | clear expdpix; 207 | 208 | %% remove most of commission errors from urban, bright rock, and coastline. 209 | pcloud = ErodeCommissons(data_meta,pcloud_all,pfpl,water,cdi,erdpix); 210 | clear cdi; 211 | 212 | %% detect cloud shadow 213 | cs_final = zeros(data_meta.Dim,'uint8'); % final masks, including cloud, cloud shadow, snow, and water. 214 | cs_final(water==1)=1; %water is fistly stacked because of its always lowest prioty. 215 | 216 | % note that 0.1% Landsat obersavtion is about 40,000 pixels, which will be used in the next statistic analyses. 217 | % when potential cloud cover less than 0.1%, directly screen all PCPs out. 218 | if sum_clr <= 40000 219 | fprintf('No clear pixel in this image (clear-sky pixels = %.0f%)\n',sum_clr); 220 | pcloud=pcloud>0; 221 | pshadow=~pcloud; 222 | clear data_toabt; 223 | else 224 | fprintf('Match cloud shadows with clouds.\n'); 225 | % detect potential cloud shadow 226 | pshadow = DetectPotentialCloudShadow(data_meta, data_toabt.BandNIR,data_toabt.BandSWIR1,idlnd,mask,... 227 | slope,aspect); 228 | 229 | data_toabt.BandNIR = []; 230 | data_toabt.BandSWIR1 = []; 231 | 232 | data_bt_c=data_toabt.BandBT; 233 | clear data_toabt; 234 | % match cloud shadow, and return clouds and cloud shadows. 235 | [ ~,pcloud, pshadow] = MatchCloudShadow(... 236 | mask,pcloud,pshadow,isShadowater,waterAll, dem ,data_bt_c,t_templ,t_temph,data_meta,sum_clr,14,angles_view); 237 | 238 | % make buffer for final masks. 239 | % the called cloud indicate those clouds are have highest piroity. 240 | % This is final cloud! 241 | [pcloud,pshadow,psnow] = BufferMasks(pcloud,cldpix,pshadow,sdpix,psnow,snpix); 242 | end 243 | %% stack results together. 244 | % step 1 snow or unknow 245 | cs_final(psnow==1)=3; % snow 246 | % step 2 shadow above snow and everyting 247 | cs_final(pshadow==1)=2; %shadow 248 | % step 3 cloud above all 249 | cs_final(pcloud==1)=4; % cloud 250 | % mask out no data. 251 | cs_final(mask==0)=255; % mask 252 | 253 | % clear pixels percentage 254 | clr_pct=100*(1-sum(pcloud(:))/sum(mask(:))); 255 | 256 | %% output as geotiff. 257 | trgt.Z=cs_final; 258 | fmask_name=[data_meta.Name,'_Fmask4']; 259 | trgt.name=fmask_name; 260 | 261 | fmask_output=fullfile(path_data,data_meta.Output,[fmask_name,'.tif']); 262 | GRIDobj2geotiff(trgt,fmask_output); 263 | time=toc; 264 | time=time/60; 265 | fprintf('%s finished (%.2f minutes)\nfor %s with %.2f%% clear pixels\n\n',... 266 | fmask_soft_name,time,data_meta.Name,clr_pct); 267 | end 268 | -------------------------------------------------------------------------------- /autoFmaskBatch.m: -------------------------------------------------------------------------------- 1 | function autoFmaskBatch() 2 | % This can automatically find all Landsats 4-8 and Sentinel-2 images 3 | % (folder) and to process them one by one. 4 | 5 | fprintf('Fmask 4.7 batch starts ...\n'); 6 | filepath_work = pwd; % CD to the path; or, type in the path of your working directory here. 7 | [~, ~, paths, ~] = CheckImagesPath(filepath_work); 8 | for i=1: length(paths) 9 | cd(paths{i}); 10 | fprintf('At %s.\n',paths{i}); 11 | autoFmask();% 4.0 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /findMatdetecFootprint.asv: -------------------------------------------------------------------------------- 1 | function Matdetec2 = findMatdetecFootprint(DetectFootPrint,XPsizeOut,YPsizeOut) 2 | % Fixed the calculation of footprint of Sentinel-2 detector based on old 3 | % version of Sentinel-2 data (17/12/2021 Shi) 4 | 5 | clear Matdetec k 6 | % sorted foot print from 1 to 4 7 | [DetectFootPrint.Ndect, idnews] = sort(DetectFootPrint.Ndect); 8 | DetectFootPrint.Nband = DetectFootPrint.Nband(idnews); 9 | DetectFootPrint.Data = DetectFootPrint.Data(idnews); 10 | 11 | for i = 1:length(DetectFootPrint.Ndect) 12 | 13 | IDX = knnsearch(XPsizeOut',DetectFootPrint.Data{i,1}(:,1)); 14 | IDY = knnsearch(YPsizeOut,DetectFootPrint.Data{i,1}(:,2)); 15 | 16 | dum2 = single(poly2mask(double(IDX), double(IDY),length(XPsizeOut),length(XPsizeOut))) ; 17 | clear IDX IDY; 18 | dum2 = conv2(dum2,ones(3),'same')>0; % to fill boundary 19 | Matdetec(:,:,i) = dum2; 20 | clear dum* 21 | 22 | % find orientation of detect + slope for computing perpendicular kernel 23 | I=nan(size(Matdetec,2),1); 24 | for ii=1:size(Matdetec,1) 25 | dum=find(Matdetec(ii,:,i)==1,1,'first'); 26 | if ~isempty(dum) 27 | I(ii,1)=dum; 28 | end 29 | end 30 | clear dum; 31 | J = [1:size(Matdetec,1)]' ; 32 | test = ~isnan(I) & I > 1 & I < size(Matdetec,2); 33 | warning off all % if warning => not enough point => slope=0 => good because tile boundary 34 | k{i,1} = polyfit(J(test),I(test),1); 35 | clear test; 36 | 37 | I=nan(size(Matdetec,2),1); 38 | for ii=1:size(Matdetec,1) 39 | dum=find(Matdetec(ii,:,i)==1,1,'last'); 40 | if ~isempty(dum) 41 | I(ii,1)=dum; 42 | end 43 | end 44 | J = [1:size(Matdetec,1)]' ; 45 | test = ~isnan(I) & I > 1 & I < size(Matdetec,2); 46 | k{i,2} = polyfit(J(test),I(test),1); 47 | clear test; 48 | warning on all 49 | 50 | end 51 | 52 | % mediane 53 | for i = 1:length(DetectFootPrint.Ndect)-1 54 | mediane = mean( [k{i,2} ; k{i+1,1}] ) ; 55 | 56 | k{i,2} = mediane ; 57 | k{i+1,1} = mediane ; 58 | clear mediane; 59 | end 60 | J = [1:size(Matdetec,1)]' ; 61 | I = [1:size(Matdetec,2)] ; 62 | 63 | [Jmat Imat] = meshgrid(I,J); 64 | clear I J; 65 | 66 | Matdetec2 = nan(size(Matdetec,1),size(Matdetec,2)); 67 | clear Matdetec; 68 | for i = 1:length(DetectFootPrint.Ndect) 69 | 70 | liminf = polyval(k{i,1},Jmat); 71 | limsup = polyval(k{i,2},Jmat); 72 | 73 | if sum(k{i,2}) == 0 % footprint at low-right corner 74 | Matdetec2(Imat>=liminf) = DetectFootPrint.Ndect(i) ; 75 | elseif sum(k{i,1}) == 0 % footprint at up-left corner 76 | Matdetec2(Imat<=limsup) = DetectFootPrint.Ndect(i) ; 77 | else 78 | Matdetec2(Imat>=liminf & Imat<=limsup) = DetectFootPrint.Ndect(i) ; 79 | end 80 | clear liminf limsup; 81 | end 82 | clear Imat ImatJ k; 83 | 84 | Matdetec2 = Matdetec2'; 85 | 86 | 87 | -------------------------------------------------------------------------------- /findMatdetecFootprint.m: -------------------------------------------------------------------------------- 1 | function Matdetec2 = findMatdetecFootprint(DetectFootPrint,XPsizeOut,YPsizeOut) 2 | % Fixed the calculation of footprint of Sentinel-2 detector based on old 3 | % version of Sentinel-2 data (17/12/2021 Shi) 4 | 5 | clear Matdetec k 6 | % sorted foot print from 1 to 4 7 | [DetectFootPrint.Ndect, idnews] = sort(DetectFootPrint.Ndect); 8 | DetectFootPrint.Nband = DetectFootPrint.Nband(idnews); 9 | DetectFootPrint.Data = DetectFootPrint.Data(idnews); 10 | 11 | for i = 1:length(DetectFootPrint.Ndect) 12 | 13 | IDX = knnsearch(XPsizeOut',DetectFootPrint.Data{i,1}(:,1)); 14 | IDY = knnsearch(YPsizeOut,DetectFootPrint.Data{i,1}(:,2)); 15 | 16 | dum2 = single(poly2mask(double(IDX), double(IDY),length(XPsizeOut),length(XPsizeOut))) ; 17 | clear IDX IDY; 18 | dum2 = conv2(dum2,ones(3),'same')>0; % to fill boundary 19 | Matdetec(:,:,i) = dum2; 20 | clear dum* 21 | 22 | % find orientation of detect + slope for computing perpendicular kernel 23 | I=nan(size(Matdetec,2),1); 24 | for ii=1:size(Matdetec,1) 25 | dum=find(Matdetec(ii,:,i)==1,1,'first'); 26 | if ~isempty(dum) 27 | I(ii,1)=dum; 28 | end 29 | end 30 | clear dum; 31 | J = [1:size(Matdetec,1)]' ; 32 | test = ~isnan(I) & I > 1 & I < size(Matdetec,2); 33 | warning off all % if warning => not enough point => slope=0 => good because tile boundary 34 | k{i,1} = polyfit(J(test),I(test),1); 35 | clear test; 36 | 37 | I=nan(size(Matdetec,2),1); 38 | for ii=1:size(Matdetec,1) 39 | dum=find(Matdetec(ii,:,i)==1,1,'last'); 40 | if ~isempty(dum) 41 | I(ii,1)=dum; 42 | end 43 | end 44 | J = [1:size(Matdetec,1)]' ; 45 | test = ~isnan(I) & I > 1 & I < size(Matdetec,2); 46 | k{i,2} = polyfit(J(test),I(test),1); 47 | clear test; 48 | warning on all 49 | 50 | end 51 | 52 | % mediane 53 | for i = 1:length(DetectFootPrint.Ndect)-1 54 | mediane = mean( [k{i,2} ; k{i+1,1}] ) ; 55 | 56 | k{i,2} = mediane ; 57 | k{i+1,1} = mediane ; 58 | clear mediane; 59 | end 60 | J = [1:size(Matdetec,1)]' ; 61 | I = [1:size(Matdetec,2)] ; 62 | 63 | [Jmat Imat] = meshgrid(I,J); 64 | clear I J; 65 | 66 | Matdetec2 = nan(size(Matdetec,1),size(Matdetec,2)); 67 | clear Matdetec; 68 | for i = 1:length(DetectFootPrint.Ndect) 69 | 70 | liminf = polyval(k{i,1},Jmat); 71 | limsup = polyval(k{i,2},Jmat); 72 | 73 | if sum(k{i,2}) == 0 % footprint at low-right corner 74 | Matdetec2(Imat>=liminf) = DetectFootPrint.Ndect(i) ; 75 | elseif sum(k{i,1}) == 0 % footprint at up-left corner 76 | Matdetec2(Imat<=limsup) = DetectFootPrint.Ndect(i) ; 77 | else 78 | Matdetec2(Imat>=liminf & Imat<=limsup) = DetectFootPrint.Ndect(i) ; 79 | end 80 | clear liminf limsup; 81 | end 82 | clear Imat ImatJ k; 83 | 84 | Matdetec2 = Matdetec2'; 85 | 86 | 87 | -------------------------------------------------------------------------------- /geoimread_lcog_matlab2020a.m: -------------------------------------------------------------------------------- 1 | function [A,x,y,I]=geoimread_lcg_matlab2020a(filename,varargin) 2 | %GEOIMREAD reads a sub region of a geotiff or geojp2 image. 3 | % 4 | % 5 | %% Syntax 6 | % 7 | % A = geoimread(filename) 8 | % A = geoimread(filename,xlim,ylim) 9 | % A = geoimread(filename,latlim,lonlim) 10 | % A = geoimread(...,buffer) 11 | % [A,x,y,I] = geoimread(...) 12 | % geoimread(...) 13 | % 14 | % 15 | %% Description 16 | % 17 | % A = geoimread(filename) returns the full image given by a filename. This 18 | % syntax is equivalent to A = geotiffread(filename). 19 | % 20 | % A = geoimread(filename,xlim,ylim) limits the region of the geotiff file to 21 | % the limits given by xlim and ylim, which are map units (usually meters) relative 22 | % to the data projection. For example, if the geotiff is projected in Texas Centric 23 | % Mapping System/Lambert Conformal coordinates, xlim and ylim will have units of 24 | % meters relative to the origin (100 W, 18 N). xlim and ylim can be multimensional, 25 | % in which case the limits of the map will be taken as the limits of the limits of 26 | % the distribution of all points in xlim, ylim. 27 | % 28 | % A = geoimread(filename,latlim,lonlim) if no values in xlim, ylim exceed 29 | % normal values of latitudes and longitudes, geoimread assumes you've entered 30 | % limits in geographic coordinates of degrees latitude and longitude. The first 31 | % input is latitude, the second input is longitude. 32 | % 33 | % A = geoimread(...,buffer) adds a buffer in map units (usually meters or feet) to the 34 | % limits of the region of interest. This may be useful if you want to load an image 35 | % surrounding scattered lat/lon data. If you'd like an extra 2 kilometers of image around 36 | % your data, enter 2000 as the buffer. If buffer is a two-element vector, the first 37 | % element is applied to the left and right extents of the image, and the second element 38 | % is applied to the top and bottom extents of the image. 39 | % 40 | % [A,x,y,I] = geoimread(...) also returns pixel center coordinates (x,y) of the 41 | % output image and a geotiff info structure I. I is a useful input for projfwd and projinv. 42 | % 43 | % geoimread(...) without any outputs shows the output image A without loading 44 | % any data into the workspace. 45 | % 46 | % 47 | %% Examples: 48 | % 49 | % % Show a whole geotiff: 50 | % geoimread('boston.tif'); 51 | % 52 | % % Compare results from above to a subset geotiff: 53 | % mapx = [765884 766035 766963]; % units are feet 54 | % mapy = [2959218 2957723 2958972]; 55 | % geoimread('boston.tif',mapx,mapy) 56 | % 57 | % % Or if you have coordinates in lat/lon and you want a 500 foot buffer: 58 | % lat = [42.3675288 42.3634246 42.3668397]; 59 | % lon = [-71.0940009 -71.0934685 -71.0900125]; 60 | % 61 | % geoimread('boston.tif',lat,lon,500); 62 | % 63 | %% Author Info: 64 | % 65 | % (c) Aslak Grinsted 2014- (http://www.glaciology.net/) 66 | % & Chad A. Greene (http://chadagreene.com/) 67 | % 68 | %% 69 | % 70 | % See also GEOTIFFREAD, GEOTIFFINFO, PIXCENTERS, and PROJFWD. 71 | 72 | %% CHAD'S CHANGES: 73 | % The following changes were made by Chad A. Greene (http://chadagreene.com/) 74 | % of the University of Texas Institute for Geophysics (UTIG) on Sept 17, 2014: 75 | % 76 | % * More input checking and error messages. 77 | % 78 | % * Clarified syntax and description in header. 79 | % 80 | % * The fileparts line now only writes the file extension because other outputs went unused. 81 | % 82 | % * If geographic coordinate limits are inferred, they are now ordered latlim,lonlim. <-- **FUNCTIONALITY CHANGE** 83 | % 84 | % * Limits xlim, ylim or latlim, lonlim can be scalar, vector, or matrix-- xlim is now taken 85 | % as xlim = [min(xlim(:)) max(xlim(:))]. This will save a small step if you have some data points 86 | % given by x,y. Now you can simply enter your data coordinates and geoimread will 87 | % figure out the limits. 88 | % 89 | % * Output variable I now has correct corner coordinates for the subset image instead of 90 | % the previous version which returned corner coordinates of the full original image. 91 | % 92 | % * A buffer can be specified around input limits. 93 | % 94 | % 95 | % Syntax before the changes: 96 | % geoimread('myimage.tif',[min(easting)-buffer_m max(easting)+buffer_m],... 97 | % [min(northing)-buffer_m max(northing)+buffer_m]); 98 | % 99 | % Syntax after the changes: 100 | % geoimread('myimage.tif',easting,northing,buffer_m) 101 | % 102 | % 103 | 104 | %TODO: support downsampling (ReductionLevel parameter in imread) 105 | %TODO: use map2pix and latlon2pix instead of projfwd and pixcenters. more robust if it is a rotational coordinate system. 106 | 107 | 108 | %% Set defaults: 109 | 110 | usegeocoordinates = false; 111 | returnNONsubsetImage = true; 112 | buffer_x = 0; 113 | buffer_y = 0; 114 | 115 | %% Input checks: 116 | 117 | % Check for mapping toolbox: 118 | % assert(license('test','map_toolbox')==1,'geoimread requires Matlab''s Mapping Toolbox.') 119 | 120 | % Check file type: 121 | assert(isnumeric(filename)==0,'Input filename must be a string.') 122 | [~,~,ext] = fileparts(filename); 123 | switch upper(ext) 124 | case {'.JP2' '.JPEG2000' '.GEOJP2'} 125 | I = jp2tiffinfo(filename); 126 | case {'.TIF' '.TIFF' '.GTIF' '.GTIFF'} 127 | I = robustgeotiffinfo(filename); 128 | otherwise 129 | error('Unrecognized image file type. Must be tif, tiff, gtif, gtiff, jp2, jpeg2000, or geojp2.') 130 | end 131 | 132 | 133 | % Parse optional inputs: 134 | if nargin>1 135 | returnNONsubsetImage = false; 136 | assert(nargin>2,'If you specify an xlim or latlim, you must specify a corresponding ylim or lonlim.') 137 | 138 | % Parse limits: 139 | xlimOrLatlim = varargin{1}(:); 140 | ylimOrLonlim = varargin{2}(:); 141 | 142 | assert(isnumeric(xlimOrLatlim)==1,'Input xlim or latlim must be numeric.') 143 | assert(isnumeric(ylimOrLonlim)==1,'Input ylim or lonlim must be numeric.') 144 | 145 | % Assume geo coordinates if no input limits exceed normal lat/lon values: 146 | if max(abs(xlimOrLatlim))<=90 && max(abs(ylimOrLonlim))<=360 147 | usegeocoordinates = true; 148 | end 149 | 150 | % Parse buffer: 151 | if nargin>3 152 | buffer_m = varargin{3}; 153 | assert(isnumeric(buffer_m)==1,'Buffer value must be either a scalar or a two-element array.') 154 | assert(numel(buffer_m)<3,'Buffer value must be either a scalar or a two-element array.') 155 | buffer_x = buffer_m(1); 156 | if isscalar(buffer_m) 157 | buffer_y = buffer_m(1); 158 | else 159 | buffer_y = buffer_m(2); 160 | end 161 | end 162 | 163 | if nargin>4 164 | error('Too many inputs in geoimread.') 165 | end 166 | 167 | end 168 | 169 | 170 | %% Begin work: 171 | 172 | % Get pixel coordinates of full (non-subset) image: 173 | [x,y]=robustpixcenters(I); 174 | 175 | if nargout==0 176 | fprintf('No outputs specified.\n') 177 | fprintf('X-range: %.1f:%.1f:%.1f\n',x(1),x(2)-x(1),x(end)) 178 | fprintf('Y-range: %.1f:%.1f:%.1f\n',y(1),y(2)-y(1),y(end)) 179 | clear A x y I 180 | return 181 | end 182 | 183 | % Set xlim and ylim depending on user inputs: 184 | if returnNONsubsetImage 185 | xlimOrLatlim = x(:); 186 | ylimOrLonlim = y(:); 187 | end 188 | 189 | if usegeocoordinates 190 | % lat/lon limits switch to x/y limits here: 191 | if ~strcmp(I.ModelType,'ModelTypeGeographic') 192 | assert(license('test','map_toolbox')==1,'Mapping toolbox needed to project between lat/lon limits and x,y limits. Specify limits in x,y coordinates.') 193 | [xlimOrLatlim,ylimOrLonlim]=projfwd(I,xlimOrLatlim,ylimOrLonlim); 194 | end 195 | end 196 | 197 | 198 | xlim = [min(xlimOrLatlim)-buffer_x max(xlimOrLatlim)+buffer_x]; 199 | ylim = [min(ylimOrLonlim)-buffer_y max(ylimOrLonlim)+buffer_y]; 200 | 201 | 202 | % Rows and columns of pixels to read: 203 | rows=find((y>=ylim(1))&(y<=ylim(2))); 204 | cols=find((x>=xlim(1))&(x<=xlim(2))); 205 | 206 | 207 | 208 | 209 | %% Display messages if region of interest is partly or wholly outside the image region: 210 | 211 | if xlim(1)max(x) 212 | disp('geoimread limits extend beyond the available image output in the x direction.') 213 | end 214 | 215 | if ylim(1)max(y) 216 | disp('geoimread limits extend beyond the available image output in the y direction.') 217 | end 218 | 219 | if isempty(rows)||isempty(cols) 220 | error('No image coordinates can be found inside the specified limits.') 221 | end 222 | 223 | %% Load region of interest: 224 | reductionlevel=0; 225 | if reductionlevel==0 226 | rows=sort(rows([1 end])); 227 | cols=sort(cols([1 end])); 228 | else 229 | %% Load region of interest: 230 | dpx=2^reductionlevel; 231 | rows=round(rows/dpx);cols=round(cols/dpx); 232 | 233 | rows=sort(rows([1 end])); 234 | cols=sort(cols([1 end])); 235 | end 236 | x=x(cols(1):cols(end)); 237 | y=y(rows(1):rows(end)); 238 | 239 | A=imread(filename,'PixelRegion',{rows cols}); 240 | 241 | 242 | %% Update info structure to more accurately reflect the new image: 243 | 244 | if nargout == 4 245 | I.FileSize = numel(A); 246 | I.Height = size(A,1); 247 | I.Width = size(A,2); 248 | try 249 | I.TiePoints.WorldPoints.X = x(1); 250 | I.TiePoints.WorldPoints.Y = y(1); 251 | I.SpatialRef.RasterSize = [size(A,1),size(A,2)]; 252 | I.RefMatrix(3,1) = x(1); 253 | I.RefMatrix(3,2) = y(1); 254 | I.BoundingBox = [min(x) min(y); max(x) max(y)]; 255 | I.CornerCoords.X = [min(x) max(x) max(x) min(x)]; 256 | I.CornerCoords.Y = [max(y) max(y) min(y) min(y)]; 257 | %TODO: check whether GTRasterTypeGeoKey is RasterPixelIsArea or RasterPixelIsPoint 258 | I.CornerCoords.Row = .5 + [0 0 size(A,1) size(A,1)]; %TODO: is this .5 always true? 259 | I.CornerCoords.Col = .5 + [0 size(A,2) size(A,2) 0]; 260 | [I.CornerCoords.Lat,I.CornerCoords.Lon] = projinv(I,I.CornerCoords.X,I.CornerCoords.Y); 261 | I.GeoTIFFTags.ModelTiepointTag(4) = x(1); 262 | I.GeoTIFFTags.ModelTiepointTag(5) = y(1); 263 | I.SpatialRef.XLimWorld = [min(x),max(x)]; 264 | I.SpatialRef.YLimWorld = [min(y),max(y)]; 265 | catch,end 266 | end 267 | 268 | %% Clean up: 269 | 270 | 271 | 272 | 273 | 274 | 275 | function I=jp2tiffinfo(fname) 276 | % 277 | % This is a function that attempts to extract the geo-info from a jp2 file. 278 | % There are two "standards" for how this can be stored: 279 | % * GeoJP2: an embedded geotiff is stored in a "uuid" box with a specific id. 280 | % * GMLJP2: an embedded gml file is in an asoc box. 281 | % (extremely annoying that there are multiple "standards".) 282 | % 283 | 284 | %Documents: 285 | % JP2 standard: http://www.jpeg.org/public/15444-1annexi.pdf 286 | % GEOJP2 standard: http://wiki.opf-labs.org/download/attachments/11337762/15444-1annexi.pdf 287 | % GMLJP2 standard: http://www.opengeospatial.org/standards/gmljp2 288 | 289 | fid=fopen(fname,'r','ieee-be'); 290 | lblcontents=''; 291 | while ~feof(fid) 292 | lbox=fread(fid,1,'uint32'); 293 | type=fread(fid,[1 4],'uint8=>char'); 294 | switch lbox 295 | case 1 296 | lbox=fread(fid,1,'uint64');lbox=lbox-16; 297 | case 2:7 %"reserved reserved for ISO use" (????) 298 | lbox=8;%No idea why ... there is no info on how to interpret values 2-7. 299 | %Setting this to 8 works for sentinel-2 images. 300 | otherwise 301 | lbox=lbox-8; 302 | end 303 | 304 | switch type 305 | case 'asoc' 306 | %the asoc box is a container with other boxes. Set lbox to zero 307 | %so that we also parse the containing boxes. 308 | lbox=0; 309 | case 'lbl ' 310 | %in gmljp2 the xml box is preceded by a lbl box. Read the 311 | %lbl box contents for later... 312 | lblcontents=fread(fid,[1 lbox],'uint8=>char'); 313 | lbox=0; 314 | case 'xml ' 315 | if (strfind(lblcontents,'gml')>0) %gmljp2: 316 | gmlcontents=fread(fid,[1 lbox],'uint8=>char'); 317 | fout=fopen('.temp.xml','w'); 318 | fwrite(fout,gmlcontents); 319 | fclose(fout); 320 | X=xmlread('.temp.xml'); 321 | delete('.temp.xml'); 322 | 323 | Elements=X.getElementsByTagName('gml:origin'); 324 | origin=str2num(Elements.item(0).getTextContent()); 325 | Elements=X.getElementsByTagName('gml:offsetVector'); 326 | R=[]; 327 | for ii=1:Elements.getLength 328 | R{ii}=str2num(Elements.item(ii-1).getTextContent()); 329 | end 330 | R=cell2mat(R'); 331 | %ModelPixelScaleTag: [15 15 0] 332 | %ModelTiepointTag: [0 0 0 4.695e+05 7240500 0] 333 | assert((R(1,2)==0)&(R(2,1)==0),'unexpected offsetvector in GML'); 334 | I=imfinfo(fname); 335 | I.GML=gmlcontents; 336 | I.ModelPixelScaleTag=[R(1,1) -R(2,2) 0]; %order and sign.. 337 | I.ModelTiepointTag=[0 0 0 origin 0]; 338 | fclose(fid); 339 | return %we have what we need - return 340 | end 341 | case 'uuid' % 342 | uuid=fread(fid,[1 16],'uint8=>char'); 343 | lbox=lbox-16; 344 | geo=[177 75 248 189 8 61 75 67 165 174 140 215 213 166 206 3]; 345 | if all(uuid==geo) 346 | fout=fopen('.temp.tif','w'); 347 | contents=fread(fid,lbox,'uint8');lbox=0; 348 | fwrite(fout,contents); 349 | fclose(fout); 350 | fclose(fid); 351 | I=geotiffinfo_lcog_matlab2020a('.temp.tif'); 352 | m=imfinfo(fname); % a little silly to use imfinfo when I already have a tag reader 353 | I.Height=m.Height; 354 | I.Width=m.Width; 355 | delete('.temp.tif'); 356 | 357 | fclose(fid); 358 | return %we have what we need - return 359 | end 360 | end 361 | 362 | fseek(fid,lbox,0); 363 | 364 | end 365 | fclose(fid); 366 | 367 | 368 | 369 | %--- BELOW is to make geoimread work even if no mapping toolbox (but with reduced functionality) --- 370 | 371 | function I = robustgeotiffinfo(fname) 372 | if license('test','map_toolbox') 373 | I=geotiffinfo_lcog_matlab2020a(fname); 374 | else 375 | I=imfinfo(fname); 376 | % I.ModelType='ModelTypeProjected'; %TODO: fix 377 | % %TODO: generate home-made refmatrix(?).... 378 | % if isfield(tags, 'ModelTransformationTag') && numel(tags.ModelTransformationTag) >= 8 379 | % geoimread does not work for rotated systems 380 | % 381 | % else %use ModelPixelScaleTag instead 382 | % dx = I.ModelPixelScaleTag(1); dy = -I.ModelPixelScaleTag(2); 383 | % x0 = I.ModelTiepointTag(4) - dx * I.ModelTiepointTag(1); 384 | % y0 = I.ModelTiepointTag(5) - dy * I.ModelTiepointTag(2); 385 | % J = [dx 0; 0 dy]; 386 | % end 387 | % I.RefMatrix=[flipud(J); x0-J(1,1)-J(1,2)]; 388 | end 389 | 390 | function [x,y]=robustpixcenters(I) 391 | if license('test','map_toolbox') && isfield(I,'GeoTIFFCodes') 392 | [x,y]=pixcenters(I); 393 | else 394 | %I have not read documentation... but this only works for rectilinear systems. 395 | assert(I.ModelPixelScaleTag(3)==0,'unexpected ModelPixelScaleTag format.'); 396 | assert(all(I.ModelTiepointTag(1:3)==0),'unexpected ModelTiepointTag format.'); 397 | x=((0:I.Width-1)-I.ModelTiepointTag(1))*I.ModelPixelScaleTag(1)+I.ModelTiepointTag(4); 398 | y=((0:I.Height-1)-I.ModelTiepointTag(2))*-I.ModelPixelScaleTag(2)+I.ModelTiepointTag(5); 399 | end -------------------------------------------------------------------------------- /geotiffread_lcog.m: -------------------------------------------------------------------------------- 1 | function varargout = geotiffread_lcog(filename, varargin) 2 | %GEOTIFFREAD Read GeoTIFF file 3 | % 4 | % GEOTIFFREAD is not recommended. Use READGEORASTER instead. 5 | % 6 | % [A, R] = GEOTIFFREAD(FILENAME) reads a georeferenced grayscale, RGB, or 7 | % multispectral image or data grid from the GeoTIFF file specified by 8 | % FILENAME into A and constructs a spatial referencing object, R. 9 | % 10 | % [X, CMAP, R] = GEOTIFFREAD(FILENAME) reads an indexed image into X and 11 | % the associated colormap into CMAP, and constructs a spatial referencing 12 | % object, R. Colormap values in the image file are rescaled into the 13 | % range [0,1]. 14 | % 15 | % FILENAME is a string scalar or character vector that specifies the name 16 | % of the GeoTIFF file. FILENAME can include the folder name. Otherwise, 17 | % the file must be in the current folder or in a folder on the MATLAB 18 | % path. If the named file includes the extension '.TIF' or '.TIFF' 19 | % (either upper or lower case), you can omit the extension from FILENAME. 20 | % 21 | % A is a two-dimensional array if the file contains a grayscale image or 22 | % data grid. A is an M-by-N-by-P array if the file contains a color 23 | % image, multispectral image, hyperspectral image, or data grid. The 24 | % class of A depends on the storage class of the pixel data in the file 25 | % which is related to the BitsPerSample property as returned by the 26 | % IMFINFO function. 27 | % 28 | % R is a geographic raster reference object if the image or data grid is 29 | % referenced to a geographic coordinate system, or a map raster reference 30 | % object if it is referenced to a projected coordinate system. 31 | % 32 | % [A, REFMAT, BBOX] = GEOTIFFREAD(FILENAME) reads a georeferenced 33 | % grayscale, RGB, or multispectral image or data grid into A; the 34 | % corresponding referencing matrix into REFMAT; and the bounding box into 35 | % BBOX. 36 | % 37 | % [X, CMAP, REFMAT, BBOX] = GEOTIFFREAD(FILENAME) reads an indexed image 38 | % into X, the associated colormap into CMAP, the referencing matrix into 39 | % REFMAT, and the bounding box into BBOX. The referencing matrix must 40 | % be unambiguously defined by the GeoTIFF file, otherwise it and the 41 | % bounding box are returned empty. 42 | % 43 | % [___] = GEOTIFFREAD(URL) reads the GeoTIFF image from a URL. The 44 | % URL must include the protocol type (e.g., "https://"). 45 | % 46 | % [___] = GEOTIFFREAD(___, IDX) reads one image from a multi-image 47 | % GeoTIFF file (or URL). IDX is an integer value that specifies the order 48 | % that the image appears in the file. For example, if IDX is 3, 49 | % GEOTIFFREAD reads the third image in the file. If you omit this 50 | % argument, GEOTIFFREAD reads the first image. 51 | % 52 | % Note 53 | % ---- 54 | % GEOTIFFREAD imports pixel data using the TIFF-reading capabilities of 55 | % the MATLAB function IMREAD and likewise shares any limitations of 56 | % IMREAD. Consult the IMREAD documentation for specific information on 57 | % TIFF image support. 58 | % 59 | % Example 60 | % ------- 61 | % % Read and display the Boston GeoTIFF image. 62 | % % Includes material (c) GeoEye, all rights reserved. 63 | % [boston, R] = geotiffread('boston.tif'); 64 | % figure 65 | % mapshow(boston, R) 66 | % axis image off 67 | % 68 | % See also GEOSHOW, GEOTIFFINFO, GEOTIFFWRITE, IMREAD, MAPSHOW, READGEORASTER 69 | 70 | % Copyright 1996-2020 The MathWorks, Inc. 71 | 72 | % Verify the input and output argument count. 73 | narginchk(1,2); 74 | nargoutchk(0,4); 75 | 76 | % Parse the inputs. 77 | filename = convertStringsToChars(filename); 78 | [filename, url, idx] = parseInputs(filename, varargin); 79 | 80 | % Read the info fields from the filename. 81 | info = geotiffinfo_lcog(filename); 82 | 83 | % Read the image from the filename. 84 | [A, cmap] = imread(filename,idx); 85 | 86 | % If the RefMatrix is empty, try to obtain the spatial information from a 87 | % corresponding worldfile. 88 | if isempty(info.RefMatrix) 89 | worldfilename = getworldfilename(filename); 90 | info = getSpatialInfoFromWorldfile(worldfilename, info, size(A)); 91 | end 92 | 93 | % Delete temporary file from Internet download. 94 | if (url) 95 | deleteDownload(filename); 96 | end 97 | 98 | % Assign output arguments. 99 | varargout = assignOutputArguments(A, cmap, info, nargout); 100 | 101 | %-------------------------------------------------------------------------- 102 | 103 | function [filename, url, idx] = parseInputs(filename, inputs) 104 | % Parse the inputs from the cell array, INPUTS. 105 | 106 | % Verify the filename and obtain the full pathname. 107 | extensions = {'tif', 'tiff'}; 108 | [filename, url] = internal.map.checkfilename(filename, extensions, mfilename, 1, true); 109 | 110 | % Check and set the image index number. 111 | if ~isempty(inputs) 112 | idx = inputs{1}; 113 | attributes = {'real' 'scalar' 'positive'}; 114 | validateattributes(idx, {'numeric'}, attributes, mfilename, 'IDX', 2); 115 | else 116 | idx = 1; 117 | end 118 | 119 | %-------------------------------------------------------------------------- 120 | 121 | function info = getSpatialInfoFromWorldfile(worldFileName, info, rasterSize) 122 | % Obtain the referencing matrix from a world file, if it exits. If so, 123 | % compute the bounding box and construct a spatial referencing object from 124 | % the referencing matrix and update the fields of the INFO structure. 125 | % WORLDFILENAME is a string denoting the name of the world file. 126 | 127 | if exist(worldFileName,'file') 128 | % Check that worldFileName is a file and that it can be opened. 129 | worldFileName = internal.map.checkfilename(worldFileName, {}, mfilename, 1, false); 130 | 131 | % Obtain the referencing matrix from the world file. 132 | W = load(worldFileName); 133 | map.internal.assert(isnumeric(W) && numel(W) == 6, 'map:fileio:expectedSixNumbers'); 134 | W = reshape(W,[2 3]); 135 | refmat = map.internal.referencingMatrix(W); 136 | 137 | % Calculate the spatial referencing object and bounding box from the 138 | % referencing matrix if it is not empty. 139 | if ~isempty(refmat) 140 | if strcmp(info.ModelType, 'ModelTypeGeographic') 141 | R = refmatToGeoRasterReference(refmat, rasterSize); 142 | elseif strcmp(info.ModelType, 'ModelTypeProjected') 143 | R = refmatToMapRasterReference(refmat, rasterSize); 144 | else 145 | R = []; 146 | end 147 | h = rasterSize(1); 148 | w = rasterSize(2); 149 | outline = [(0.5 + [0 0; 0 w; h w; h 0]), ones(4,1)] * refmat; 150 | info.BoundingBox = [min(outline); max(outline)]; 151 | info.RefMatrix = refmat; 152 | info.SpatialRef = R; 153 | end 154 | end 155 | 156 | %-------------------------------------------------------------------------- 157 | 158 | function outputs = assignOutputArguments(A, cmap, info, numOutputs) 159 | % Assign the output arguments based on the number of arguments requested. 160 | 161 | outputs{1} = A; 162 | switch numOutputs 163 | case 2 164 | if strcmp(info.ColorType, 'indexed') 165 | % [X, CMAP] = GEOTIFFREAD(...) 166 | outputs{2} = cmap; 167 | else 168 | % [A, R] = GEOTIFFREAD(...) 169 | outputs{2} = info.SpatialRef; 170 | end 171 | 172 | case 3 173 | if strcmp(info.ColorType, 'indexed') 174 | % [X, CMAP, R] = GEOTIFFREAD(...) 175 | outputs{2} = cmap; 176 | outputs{3} = info.SpatialRef; 177 | else 178 | % [A, REFMAT, BBOX] = GEOTIFFREAD(...) 179 | outputs{2} = info.RefMatrix; 180 | outputs{3} = info.BoundingBox; 181 | end 182 | 183 | case 4 184 | % [X, CMAP, REFMAT, BBOX] = GEOTIFFREAD(...) 185 | outputs{2} = cmap; 186 | outputs{3} = info.RefMatrix; 187 | outputs{4} = info.BoundingBox; 188 | end 189 | -------------------------------------------------------------------------------- /geotiffread_lcog_matlab2020a.m: -------------------------------------------------------------------------------- 1 | function varargout = geotiffread_lcog_matlab2020a(filename, varargin) 2 | %GEOTIFFREAD Read GeoTIFF file 3 | % 4 | % GEOTIFFREAD is not recommended. Use READGEORASTER instead. 5 | % 6 | % [A, R] = GEOTIFFREAD(FILENAME) reads a georeferenced grayscale, RGB, or 7 | % multispectral image or data grid from the GeoTIFF file specified by 8 | % FILENAME into A and constructs a spatial referencing object, R. 9 | % 10 | % [X, CMAP, R] = GEOTIFFREAD(FILENAME) reads an indexed image into X and 11 | % the associated colormap into CMAP, and constructs a spatial referencing 12 | % object, R. Colormap values in the image file are rescaled into the 13 | % range [0,1]. 14 | % 15 | % FILENAME is a string scalar or character vector that specifies the name 16 | % of the GeoTIFF file. FILENAME can include the folder name. Otherwise, 17 | % the file must be in the current folder or in a folder on the MATLAB 18 | % path. If the named file includes the extension '.TIF' or '.TIFF' 19 | % (either upper or lower case), you can omit the extension from FILENAME. 20 | % 21 | % A is a two-dimensional array if the file contains a grayscale image or 22 | % data grid. A is an M-by-N-by-P array if the file contains a color 23 | % image, multispectral image, hyperspectral image, or data grid. The 24 | % class of A depends on the storage class of the pixel data in the file 25 | % which is related to the BitsPerSample property as returned by the 26 | % IMFINFO function. 27 | % 28 | % R is a geographic raster reference object if the image or data grid is 29 | % referenced to a geographic coordinate system, or a map raster reference 30 | % object if it is referenced to a projected coordinate system. 31 | % 32 | % [A, REFMAT, BBOX] = GEOTIFFREAD(FILENAME) reads a georeferenced 33 | % grayscale, RGB, or multispectral image or data grid into A; the 34 | % corresponding referencing matrix into REFMAT; and the bounding box into 35 | % BBOX. 36 | % 37 | % [X, CMAP, REFMAT, BBOX] = GEOTIFFREAD(FILENAME) reads an indexed image 38 | % into X, the associated colormap into CMAP, the referencing matrix into 39 | % REFMAT, and the bounding box into BBOX. The referencing matrix must 40 | % be unambiguously defined by the GeoTIFF file, otherwise it and the 41 | % bounding box are returned empty. 42 | % 43 | % [___] = GEOTIFFREAD(URL) reads the GeoTIFF image from a URL. The 44 | % URL must include the protocol type (e.g., "https://"). 45 | % 46 | % [___] = GEOTIFFREAD(___, IDX) reads one image from a multi-image 47 | % GeoTIFF file (or URL). IDX is an integer value that specifies the order 48 | % that the image appears in the file. For example, if IDX is 3, 49 | % GEOTIFFREAD reads the third image in the file. If you omit this 50 | % argument, GEOTIFFREAD reads the first image. 51 | % 52 | % Note 53 | % ---- 54 | % GEOTIFFREAD imports pixel data using the TIFF-reading capabilities of 55 | % the MATLAB function IMREAD and likewise shares any limitations of 56 | % IMREAD. Consult the IMREAD documentation for specific information on 57 | % TIFF image support. 58 | % 59 | % Example 60 | % ------- 61 | % % Read and display the Boston GeoTIFF image. 62 | % % Includes material (c) GeoEye, all rights reserved. 63 | % [boston, R] = geotiffread('boston.tif'); 64 | % figure 65 | % mapshow(boston, R) 66 | % axis image off 67 | % 68 | % See also GEOSHOW, GEOTIFFINFO, GEOTIFFWRITE, IMREAD, MAPSHOW, READGEORASTER 69 | 70 | % Copyright 1996-2019 The MathWorks, Inc. 71 | 72 | % Verify the input and output argument count. 73 | narginchk(1,2); 74 | nargoutchk(0,4); 75 | 76 | % Parse the inputs. 77 | filename = convertStringsToChars(filename); 78 | [filename, url, idx] = parseInputs(filename, varargin); 79 | 80 | % Read the info fields from the filename. 81 | info = geotiffinfo_lcog_matlab2020a(filename); 82 | 83 | % Read the image from the filename. 84 | [A, cmap] = imread(filename,idx); 85 | 86 | % If the RefMatrix is empty, try to obtain the spatial information from a 87 | % corresponding worldfile. 88 | if isempty(info.RefMatrix) 89 | worldfilename = getworldfilename(filename); 90 | info = getSpatialInfoFromWorldfile(worldfilename, info, size(A)); 91 | end 92 | 93 | % Delete temporary file from Internet download. 94 | if (url) 95 | deleteDownload(filename); 96 | end 97 | 98 | % Assign output arguments. 99 | varargout = assignOutputArguments(A, cmap, info, nargout); 100 | 101 | %-------------------------------------------------------------------------- 102 | 103 | function [filename, url, idx] = parseInputs(filename, inputs) 104 | % Parse the inputs from the cell array, INPUTS. 105 | 106 | % Verify the filename and obtain the full pathname. 107 | extensions = {'tif', 'tiff'}; 108 | [filename, url] = internal.map.checkfilename(filename, extensions, mfilename, 1, true); 109 | 110 | % Check and set the image index number. 111 | if ~isempty(inputs) 112 | idx = inputs{1}; 113 | attributes = {'real' 'scalar' 'positive'}; 114 | validateattributes(idx, {'numeric'}, attributes, mfilename, 'IDX', 2); 115 | else 116 | idx = 1; 117 | end 118 | 119 | %-------------------------------------------------------------------------- 120 | 121 | function info = getSpatialInfoFromWorldfile(worldfilename, info, rasterSize) 122 | % Obtain the referencing matrix from a world file, if it exits. If so, 123 | % compute the bounding box and construct a spatial referencing object from 124 | % the referencing matrix and update the fields of the INFO structure. 125 | % WORLDFILENAME is a string denoting the name of the world file. 126 | 127 | if exist(worldfilename,'file') 128 | % Obtain the referencing matrix from the world file. 129 | refmat = worldfileread(worldfilename); 130 | 131 | % Calculate the spatial referencing object and bounding box from the 132 | % referencing matrix if it is not empty. 133 | if ~isempty(refmat) 134 | if strcmp(info.ModelType, 'ModelTypeGeographic') 135 | R = refmatToGeoRasterReference(refmat, rasterSize); 136 | elseif strcmp(info.ModelType, 'ModelTypeProjected') 137 | R = refmatToMapRasterReference(refmat, rasterSize); 138 | else 139 | R = []; 140 | end 141 | info.BoundingBox = mapbbox(refmat, rasterSize); 142 | info.RefMatrix = refmat; 143 | info.SpatialRef = R; 144 | end 145 | end 146 | 147 | %-------------------------------------------------------------------------- 148 | 149 | function outputs = assignOutputArguments(A, cmap, info, numOutputs) 150 | % Assign the output arguments based on the number of arguments requested. 151 | 152 | outputs{1} = A; 153 | switch numOutputs 154 | case 2 155 | if strcmp(info.ColorType, 'indexed') 156 | % [X, CMAP] = GEOTIFFREAD(...) 157 | outputs{2} = cmap; 158 | else 159 | % [A, R] = GEOTIFFREAD(...) 160 | outputs{2} = info.SpatialRef; 161 | end 162 | 163 | case 3 164 | if strcmp(info.ColorType, 'indexed') 165 | % [X, CMAP, R] = GEOTIFFREAD(...) 166 | outputs{2} = cmap; 167 | outputs{3} = info.SpatialRef; 168 | else 169 | % [A, REFMAT, BBOX] = GEOTIFFREAD(...) 170 | outputs{2} = info.RefMatrix; 171 | outputs{3} = info.BoundingBox; 172 | end 173 | 174 | case 4 175 | % [X, CMAP, REFMAT, BBOX] = GEOTIFFREAD(...) 176 | outputs{2} = cmap; 177 | outputs{3} = info.RefMatrix; 178 | outputs{4} = info.BoundingBox; 179 | end 180 | -------------------------------------------------------------------------------- /getRealCloudPosition.m: -------------------------------------------------------------------------------- 1 | function [x_new,y_new] = getRealCloudPosition(x,y,h,A,B,C,omiga_par,omiga_per,sensor_heigh_bias) 2 | % imput "x",j col 3 | % imput "y",i row 4 | % imput cloud height "h" 5 | H=705000-sensor_heigh_bias; % average Landsat 7 height (m) is 705000, but the height of cloud's surface should be excluded. 6 | dist=(A*x+B*y+C)/((A^2+B^2)^(0.5));% from the cetral perpendicular (unit: pixel) 7 | dist_par=dist/cos(omiga_per-omiga_par); 8 | dist_move=dist_par.*double(h)/double(H); % cloud move distance (m); 9 | delt_x=dist_move*cos(omiga_par); 10 | delt_y=dist_move*sin(omiga_par); 11 | 12 | x_new=x+delt_x; % new x, j 13 | y_new=y+delt_y; % new y, i 14 | end 15 | 16 | -------------------------------------------------------------------------------- /getRealCloudPositionS2.m: -------------------------------------------------------------------------------- 1 | function [x_new,y_new]=getRealCloudPositionS2(x,y,h,VZAxy,VAAxy,resolu) 2 | % imput "x",j col 3 | % imput "y",i row 4 | % imput cloud height "h" 5 | % H=786000; % average S2 height (m) 6 | 7 | % dist_move = h * tan(VZAxy) ./ resolu ; % but the height of cloud's surface should be excluded. 8 | % 9 | % % dist=(A*x+B*y+C)/((A^2+B^2)^(0.5));% from the cetral perpendicular (unit: pixel) 10 | % % dist_par=dist/cos(omiga_per-omiga_par); 11 | % % dist_move=dist_par.*h/H; % cloud move distance (m); 12 | % delt_x=dist_move(2)*cos(pi/2-VAAxy); 13 | % delt_y=dist_move(1)*-sin(pi/2-VAAxy); 14 | 15 | dist_move = h .* tan(VZAxy) ./ resolu ; % but the height of cloud's surface should be excluded. 16 | 17 | delt_x=dist_move(2).*cos(pi/2-VAAxy); 18 | delt_y=dist_move(1).*-sin(pi/2-VAAxy); 19 | 20 | x_new=x+delt_x; % new x, j 21 | y_new=y+delt_y; % new y, i 22 | end -------------------------------------------------------------------------------- /getSensorViewGeo.m: -------------------------------------------------------------------------------- 1 | function [A,B,C,omiga_par,omiga_per] = getSensorViewGeo( x_ul,y_ul,x_ur,y_ur,x_ll,y_ll,x_lr,y_lr ) 2 | % imput "x",j 3 | % imput "y",i 4 | % imput cloud height "h" 5 | 6 | x_u=(x_ul+x_ur)/2; 7 | x_l=(x_ll+x_lr)/2; 8 | y_u=(y_ul+y_ur)/2; 9 | y_l=(y_ll+y_lr)/2; 10 | 11 | K_ulr=(y_ul-y_ur)/(x_ul-x_ur); % get k of the upper left and right points 12 | K_llr=(y_ll-y_lr)/(x_ll-x_lr); % get k of the lower left and right points 13 | K_aver=(K_ulr+K_llr)/2; 14 | omiga_par=atan(K_aver); % get the angle of scan lines k (in pi) 15 | 16 | % AX(j)+BY(i)+C=0 17 | A=y_u-y_l; 18 | B=x_l-x_u; 19 | C=y_l*x_u-x_l*y_u; 20 | 21 | omiga_per=atan(B/A); % get the angle which is perpendicular to the track 22 | end 23 | 24 | -------------------------------------------------------------------------------- /getcoordinates.m: -------------------------------------------------------------------------------- 1 | function [x,y] = getcoordinates(DEM) 2 | 3 | %GETCOORDINATES get coordinate vectors of an instance of GRIDobj 4 | % 5 | % Syntax 6 | % 7 | % [x,y] = getcoordinates(DEM) 8 | % 9 | % Input arguments 10 | % 11 | % DEM grid (class: GRIDobj) 12 | % 13 | % Output arguments 14 | % 15 | % x coordinate vector in x direction (row vector) 16 | % y coordinate vector in y direction (column vector) 17 | % 18 | % Example 19 | % 20 | % DEM = GRIDobj('srtm_bigtujunga30m_utm11.tif'); 21 | % [x,y] = getcoordinates(DEM); 22 | % surf(x,y,double(DEM.Z)) 23 | % axis image; shading interp; camlight 24 | % 25 | % 26 | % 27 | % See also: GRIDobj2mat 28 | % 29 | % Author: Wolfgang Schwanghart (w.schwanghart[at]geo.uni-potsdam.de) 30 | % Date: 6. February, 2013 31 | 32 | [x,y] = refmat2XY(DEM.refmat,DEM.size); -------------------------------------------------------------------------------- /imcircle.m: -------------------------------------------------------------------------------- 1 | function y = imcircle(n) 2 | 3 | % y = imcircle(n); 4 | % 5 | % Draw a solid circle of ones with diameter n pixels 6 | % in a square of zero-valued pixels. 7 | % 8 | % Example: y = imcircle(50); 9 | % 10 | % For a hollow circle (line circle) of diameter n, 11 | % add the instruction bwmorph from the Image Processing 12 | % Toolbox. 13 | % 14 | % Example: y = bwmorph(imcircle(50),'remove'); 15 | 16 | if rem(n,1) > 0, 17 | disp(sprintf('n is not an integer and has been rounded to %1.0f',round(n))) 18 | n = round(n); 19 | end 20 | 21 | if n < 1 % invalid n 22 | error('n must be at least 1') 23 | 24 | elseif n < 4 % trivial n 25 | y = ones(n); 26 | 27 | elseif rem(n,2) == 0, % even n 28 | 29 | DIAMETER = n; 30 | diameter = n-1; 31 | RADIUS = DIAMETER/2; 32 | radius = diameter/2; 33 | height_45 = round(radius/sqrt(2)); 34 | width = zeros(1,RADIUS); 35 | semicircle = zeros(DIAMETER,RADIUS); 36 | 37 | for i = 1 : height_45 38 | upward = i - 0.5; 39 | sine = upward/radius; 40 | cosine = sqrt(1-sine^2); 41 | width(i) = ceil(cosine * radius); 42 | end 43 | 44 | array = width(1:height_45)-height_45; 45 | 46 | for j = max(array):-1:min(array) 47 | width(height_45 + j) = max(find(array == j)); 48 | end 49 | 50 | if min(width) == 0 51 | index = find(width == 0); 52 | width(index) = round(mean([width(index-1) width(index+1)])); 53 | end 54 | 55 | width = [fliplr(width) width]; 56 | 57 | for k = 1 : DIAMETER 58 | semicircle(k,1:width(k)) = ones(1,width(k)); 59 | end 60 | 61 | y = [fliplr(semicircle) semicircle]; 62 | 63 | else % odd n 64 | 65 | DIAMETER = n; 66 | diameter = n-1; 67 | RADIUS = DIAMETER/2; 68 | radius = diameter/2; 69 | semicircle = zeros(DIAMETER,radius); 70 | height_45 = round(radius/sqrt(2) - 0.5); 71 | width = zeros(1,radius); 72 | 73 | for i = 1 : height_45 74 | upward = i; 75 | sine = upward/radius; 76 | cosine = sqrt(1-sine^2); 77 | width(i) = ceil(cosine * radius - 0.5); 78 | end 79 | 80 | array = width(1:height_45) - height_45; 81 | 82 | for j = max(array):-1:min(array) 83 | width(height_45 + j) = max(find(array == j)); 84 | end 85 | 86 | if min(width) == 0 87 | index = find(width == 0); 88 | width(index) = round(mean([width(index-1) width(index+1)])); 89 | end 90 | 91 | width = [fliplr(width) max(width) width]; 92 | 93 | for k = 1 : DIAMETER 94 | semicircle(k,1:width(k)) = ones(1,width(k)); 95 | end 96 | 97 | y = [fliplr(semicircle) ones(DIAMETER,1) semicircle]; 98 | 99 | end 100 | -------------------------------------------------------------------------------- /nd2toarbt_msi.m: -------------------------------------------------------------------------------- 1 | function [im_th,TOAref,trgt,ijdim_ref,bbox,ul,zen,azi,zc,Angles2,B1Satu,B2Satu,B3Satu,resolu]=nd2toarbt_msi(im) 2 | % read TOA refs function derived from Fmask 3.3 for Sentinel 2. 3 | % Revisions: 4 | % Use REF vs. DN instead of RAD vs. DN (Zhe 06/20/2013) 5 | % Combined the Earth-Sun distance table into the function (Zhe 04/09/2013) 6 | % Process Landsat 8 DN values (Zhe 04/04/2013) 7 | % Proces the new metadata for Landsat TM/ETM+ images (Zhe 09/28/2012) 8 | % Fixed bugs caused by earth-sun distance table (Zhe 01/15/2012) 9 | % 10 | % [im_th,TOAref,ijdim_ref,ul,zen,azi,zc,B1Satu,B2Satu,B3Satu,resolu]=nd2toarbt(filename) 11 | % Where: 12 | % Inputs: 13 | % im= MSI filename structure including: 14 | % - im.Dmain (root directory of the SAFE directory) 15 | % - im.DataStrip directory (without ".SAFE") 16 | % - im.Granule directory; 17 | % im.Dmain = '/home/bernie/MSIdata/'; 18 | % im.DataStrip = 'S2A_OPER_PRD_MSIL1C_PDMC_20151229T234852_R139_V20151229T144823_20151229T144823'; 19 | % im.Granule = 'S2A_OPER_MSI_L1C_TL_SGS__20151229T201123_A002710_T20QPD_N02.01'; 20 | % Outputs: 21 | % 1) im_th = Brightness Temperature (BT) 22 | % 2) TOAref = Top Of Atmoshpere (TOA) reflectance 23 | % 3) ijdim = [nrows,ncols]; % dimension of optical bands 24 | % 4) ul = [upperleft_mapx upperleft_mapy]; 25 | % 5) zen = solar zenith angle (degrees); 26 | % 6) azi = solar azimuth angle (degrees); 27 | % 7) zc = Zone Number 28 | % 8,9,10) Saturation (true) in the Visible bands 29 | % 11) resolution of Fmask results 30 | % eg. 31 | 32 | FilesInfo.DirIn = im.Dmain; 33 | FilesInfo.DataStrip =im.DataStrip ; 34 | FilesInfo.Granule=im.Granule; 35 | 36 | FilesInfo.InspireXML=im.InspireXML; 37 | clear im; 38 | 39 | % obtain them from INSPIRE.xml 40 | bbox = ReadS2InspireXML(FilesInfo.InspireXML); 41 | 42 | 43 | %% Metadata read ReadSunViewGeometryMSI(DataStrip,Granule,BandSel,PsizeOut,Dmain) 44 | try 45 | [MSIinfo,Angles] = ReadSunViewGeometryMSI (FilesInfo.DataStrip,FilesInfo.Granule,4,10,FilesInfo.DirIn); 46 | catch 47 | [MSIinfo,Angles] = ReadSunViewGeometryMSIBaseline04 (FilesInfo.DataStrip,FilesInfo.Granule,4,10,FilesInfo.DirIn); 48 | end 49 | 50 | 51 | Angles2.VAA = Angles.VAA_B04 ; 52 | Angles2.VZA = Angles.VZA_B04 ; 53 | clear Angles; 54 | 55 | %% output resolution of Fmask 20meters for Sentinel 2 images 56 | resolu = [20 20] ; 57 | %% 58 | ijdim_ref = (MSIinfo.GeoInfo.Size.R10) *10 ./ resolu ; 59 | 60 | ul = [MSIinfo.GeoInfo.Xstart.R10 MSIinfo.GeoInfo.Ystart.R10] + [resolu(1)/2 0-resolu(2)/2]; % the center of the top-left pixel. 61 | zen = MSIinfo.Angles.Mean.SZA ; 62 | azi = MSIinfo.Angles.Mean.SAA ; 63 | 64 | zc_num=MSIinfo.GeoInfo.UTM_zone(1:end-1); 65 | 66 | % convert UTM zone to code by refering the bellow rule. 67 | % ref. http://geotiff.maptools.org/spec/geotiff6.html#6.3.3.1 68 | zc_ns=MSIinfo.GeoInfo.UTM_zone(end); 69 | clear MSIinfo; 70 | if zc_ns=='N' 71 | zc = abs(str2double(zc_num)); 72 | if zc>10 73 | geokey = ['326',zc_num]; 74 | else 75 | geokey = ['3260',zc_num]; 76 | end 77 | geokey=str2double(geokey); 78 | elseif zc_ns=='S' 79 | zc = abs(str2double(zc_num)); 80 | if zc>10 81 | geokey = ['327',zc_num]; 82 | else 83 | geokey = ['3270',zc_num]; 84 | end 85 | geokey=str2double(geokey); 86 | end 87 | 88 | 89 | %% open MSI data 90 | BandCorr = {'02','03','04','8A','11','12','10','07','08'}; 91 | Ratio = [10 10 10 20 20 20 60 20 10] /(resolu(1)); % resample all bands to the same resolu 92 | Dmain = fullfile(FilesInfo.DirIn, [FilesInfo.DataStrip '.SAFE'],'GRANULE', FilesInfo.Granule, 'IMG_DATA'); 93 | id_missing=zeros(ijdim_ref,'uint8'); 94 | for iB=1:length(BandCorr) 95 | % if FilesInfo.Granule(1) == 'L' 96 | % tempfn = dir(fullfile(Dmain, [FilesInfo.Granule(5:10) '*_B' BandCorr{iB} '.jp2'])); 97 | % % if isequal(iB,4) 98 | % % [dum,~,~,info_jp2]=geoimread([Dmain tempfn(1).name]); 99 | % % else 100 | % dum=imread(fullfile(Dmain, tempfn(1).name)); 101 | % % end 102 | % else 103 | % % if isequal(iB,4) 104 | % % % [dum,~,~,info_jp2]=geoimread('/Volumes/Transcend/Fmask3.3S2/Test data/S2A_OPER_PRD_MSIL1C_T31TCJ_2016292A.SAFE/GRANULE/S2A_OPER_MSI_L1C_TL_SGS__20161018T175658_A006912_T31TCJ_N02.04/IMG_DATA/S2A_OPER_MSI_L1C_TL_SGS__20161018T175658_A006912_T31TCJ_B05.jp2'); 105 | % % [dum,~,~,info_jp2]=geoimread([Dmain FilesInfo.Granule(1:end-7) 'B' BandCorr{iB} '.jp2']); 106 | % % else 107 | % % dum=imread([Dmain FilesInfo.Granule(1:end-6) 'B' BandCorr{iB} '.jp2']); 108 | % dum=imread(fullfile(Dmain, [FilesInfo.Granule(1:end-7) 'B' BandCorr{iB} '.jp2'])); 109 | % % end 110 | % end 111 | 112 | % support all versions for Sentinel 2 images. 113 | tempfn = dir(fullfile(Dmain, [FilesInfo.Granule(1) '*B' BandCorr{iB} '.jp2'])); 114 | if isempty(tempfn) 115 | tempfn = dir(fullfile(Dmain, [FilesInfo.Granule(5) '*B' BandCorr{iB} '.jp2'])); 116 | if isempty(tempfn) 117 | tempfn = dir(fullfile(Dmain, ['*B' BandCorr{iB} '.jp2'])); 118 | end 119 | end 120 | dum=imread(fullfile(Dmain, tempfn(1).name)); 121 | clear tempfn; 122 | 123 | dum=single(dum); 124 | dum(dum==0)=NaN; 125 | if Ratio(iB)<1 % box-agg pixel 126 | TOAref(:,:,iB) = imresize(dum,Ratio(iB),'box'); 127 | elseif Ratio(iB)>1 % split pixel 128 | TOAref(:,:,iB) = imresize(dum,Ratio(iB),'nearest'); 129 | elseif Ratio(iB)==1 130 | TOAref(:,:,iB) = dum; 131 | end 132 | clear dum; 133 | 134 | % % if isequal(BandCorr{iB},'08') 135 | % % % Gaussfilt for band 8 to better scall up. 136 | % % TOAref(:,:,iB) = imgaussfilt(TOAref(:,:,iB)); 137 | % % end 138 | 139 | % only processing pixesl where all bands have values (id_mssing) 140 | id_missing=id_missing|isnan(TOAref(:,:,iB)); 141 | end 142 | clear Dmain FilesInfo BandCorr Ratio iB; 143 | % trgt=CreateTargetGRIDobj(info_jp2.BoundingBox,resolu,ul,ijdim_ref,zc_num,zc_ns,geokey); 144 | trgt=CreateTargetGRIDobj(resolu,ul,ijdim_ref,zc_num,zc_ns,geokey); 145 | 146 | trgt.Z=[]; 147 | 148 | % trgt_test = GRIDobj('E:\New validation dataset\New accuracy assessment samples for Fmask 4_0\Sentinel2\S2A_MSIL1C_20171011T123151_N0205_R023_T17CNL_20171011T123149.SAFE\GRANULE\L1C_T17CNL_A012032_20171011T123149\Samples\assist data\Samples.tif'); 149 | 150 | %% 151 | %%%%% WARNING - what is the MSI fill value? what is the saturation flag ? 65535 152 | % TOAref(isnan(TOAref))=-9999; 153 | TOAref(id_missing)=-9999; 154 | clear id_missing; 155 | %%%%%%%%%%%% Not used but same format 156 | im_th=-9999; 157 | 158 | % B1Satu = zeros([size(TOAref,1) size(TOAref,2)],'uint8')==1; 159 | % B2Satu = zeros([size(TOAref,1) size(TOAref,2)],'uint8')==1; 160 | % B3Satu = zeros([size(TOAref,1) size(TOAref,2)],'uint8')==1; 161 | 162 | % find pixels that are saturated in the visible bands 163 | % SATURATED VALUE: 65535 set by Shi 10/18/2017 164 | B1Satu=TOAref(:,:,1)==65535; 165 | B2Satu=TOAref(:,:,2)==65535; 166 | B3Satu=TOAref(:,:,3)==65535; 167 | end 168 | 169 | function trgt=CreateTargetGRIDobj(resolu,ul,ijdim_ref,zc_num,zc_ns,geokey) 170 | 171 | trgt = GRIDobj([]); 172 | trgt.name='target'; 173 | trgt.cellsize=resolu(1); 174 | 175 | ul_tmp=ul - [resolu(1)/2 0-resolu(2)/2]; %back to limits. 176 | 177 | % location of the 'center' of the first (1,1) pixel in the image. 178 | trgt.refmat=makerefmat(ul(1),ul(2),resolu(1),0-resolu(2)); 179 | clear ul; 180 | trgt.size=ijdim_ref; 181 | 182 | % boundary. 183 | xWorldLimits=[ul_tmp(1),ul_tmp(1)+resolu(1)*(ijdim_ref(1))]; 184 | yWorldLimits=[ul_tmp(2)-resolu(2)*(ijdim_ref(2)),ul_tmp(2)]; 185 | clear ul_tmp resolu; 186 | 187 | spatialRef = maprefcells(xWorldLimits,yWorldLimits,ijdim_ref,... 188 | 'ColumnsStartFrom','north'); 189 | clear xWorldLimits yWorldLimits ijdim_ref; 190 | trgt.georef.SpatialRef=spatialRef; 191 | clear spatialRef; 192 | % % http://www.gdal.org/frmt_sentinel2.html 193 | trgt.georef.GeoKeyDirectoryTag.GTModelTypeGeoKey = 1; 194 | trgt.georef.GeoKeyDirectoryTag.GTRasterTypeGeoKey = 1; 195 | trgt.georef.GeoKeyDirectoryTag.GTCitationGeoKey = ['WGS 84 / UTM zone ',zc_num,zc_ns]; 196 | trgt.georef.GeoKeyDirectoryTag.ProjectedCSTypeGeoKey = geokey; clear geokey; 197 | trgt.georef.GeoKeyDirectoryTag.PCSCitationGeoKey = ['WGS 84 / UTM zone ',zc_num,zc_ns]; % same 198 | 199 | % ellipsoid = utmgeoid([zc_num,zc_ns]); 200 | 201 | % this can be used as follows because the Sentinel 2 titles's projection 202 | % will not be changable. 203 | % WGS84 UTM: A UTM zone is a 6?° segment of the Earth. 204 | E = wgs84Ellipsoid('meters'); 205 | utmstruct = defaultm('tranmerc'); 206 | utmstruct.geoid = [E.SemimajorAxis,E.Eccentricity]; 207 | clear E; 208 | % UTM false East (m) 209 | % the central meridian is assigned 500,000 meters in each zone. 210 | utmstruct.falseeasting=500000; 211 | % UTM false North (m) 212 | % If you are in the northern hemisphere, the equator has a northing 213 | % value of 0 meters. In the southern hemisphere, the equator starts 214 | % at 10,000,000 meters. This is because all values south of the 215 | % equator will be positive. 216 | if zc_ns=='N' 217 | utmstruct.falsenorthing=0; 218 | else % S 219 | utmstruct.falsenorthing=10000000;% 10,000,000 220 | end 221 | clear zc_ns; 222 | % UTM scale factor 223 | utmstruct.scalefactor=0.9996; 224 | % each UTM zone is 6 degrees. a total of 60 zones. 225 | origin=(str2double(zc_num)-31).*6+3; 226 | clear zc_num; 227 | utmstruct.origin=[0,origin,0]; 228 | clear origin; 229 | utmstruct = defaultm(utmstruct); 230 | trgt.georef.mstruct=utmstruct; 231 | clear utmstruct; 232 | end 233 | -------------------------------------------------------------------------------- /nd2toarbt_msi2.m: -------------------------------------------------------------------------------- 1 | function [im_th,TOAref,trgt,ijdim_ref,bbox,ul,zen,azi,zc,Angles2,B1Satu,B2Satu,B3Satu,resolu]=nd2toarbt_msi2(im, band_offsets, quantif) 2 | % read TOA refs function derived from Fmask 4.4 for Sentinel 2. 3 | % Revisions: 4 | % Designed for Sentinel-2 Baseline 4.00 (Shi /26/12/2021) 5 | % Use REF vs. DN instead of RAD vs. DN (Zhe 06/20/2013) 6 | % Combined the Earth-Sun distance table into the function (Zhe 04/09/2013) 7 | % Process Landsat 8 DN values (Zhe 04/04/2013) 8 | % Proces the new metadata for Landsat TM/ETM+ images (Zhe 09/28/2012) 9 | % Fixed bugs caused by earth-sun distance table (Zhe 01/15/2012) 10 | % 11 | % [im_th,TOAref,ijdim_ref,ul,zen,azi,zc,B1Satu,B2Satu,B3Satu,resolu]=nd2toarbt(filename) 12 | % Where: 13 | % Inputs: 14 | % im= MSI filename structure including: 15 | % - im.Dmain (root directory of the SAFE directory) 16 | % - im.DataStrip directory (without ".SAFE") 17 | % - im.Granule directory; 18 | % im.Dmain = '/home/bernie/MSIdata/'; 19 | % im.DataStrip = 'S2A_OPER_PRD_MSIL1C_PDMC_20151229T234852_R139_V20151229T144823_20151229T144823'; 20 | % im.Granule = 'S2A_OPER_MSI_L1C_TL_SGS__20151229T201123_A002710_T20QPD_N02.01'; 21 | % Outputs: 22 | % 1) im_th = Brightness Temperature (BT) 23 | % 2) TOAref = Top Of Atmoshpere (TOA) reflectance 24 | % 3) ijdim = [nrows,ncols]; % dimension of optical bands 25 | % 4) ul = [upperleft_mapx upperleft_mapy]; 26 | % 5) zen = solar zenith angle (degrees); 27 | % 6) azi = solar azimuth angle (degrees); 28 | % 7) zc = Zone Number 29 | % 8,9,10) Saturation (true) in the Visible bands 30 | % 11) resolution of Fmask results 31 | % eg. 32 | 33 | FilesInfo.DirIn = im.Dmain; 34 | FilesInfo.DataStrip =im.DataStrip ; 35 | FilesInfo.Granule=im.Granule; 36 | 37 | FilesInfo.InspireXML=im.InspireXML; 38 | clear im; 39 | 40 | % obtain them from INSPIRE.xml 41 | bbox = ReadS2InspireXML(FilesInfo.InspireXML); 42 | 43 | %% Metadata read ReadSunViewGeometryMSI(DataStrip,Granule,BandSel,PsizeOut,Dmain) 44 | [MSIinfo,Angles] = ReadSunViewGeometryMSIBaseline04 (FilesInfo.DataStrip,FilesInfo.Granule,4,10,FilesInfo.DirIn); 45 | 46 | Angles2.VAA = Angles.VAA_B04 ; 47 | Angles2.VZA = Angles.VZA_B04 ; 48 | clear Angles; 49 | 50 | %% output resolution of Fmask 20meters for Sentinel 2 images 51 | resolu = [20 20] ; 52 | %% 53 | ijdim_ref = (MSIinfo.GeoInfo.Size.R10) *10 ./ resolu ; 54 | 55 | ul = [MSIinfo.GeoInfo.Xstart.R10 MSIinfo.GeoInfo.Ystart.R10] + [resolu(1)/2 0-resolu(2)/2]; % the center of the top-left pixel. 56 | zen = MSIinfo.Angles.Mean.SZA ; 57 | azi = MSIinfo.Angles.Mean.SAA ; 58 | 59 | zc_num=MSIinfo.GeoInfo.UTM_zone(1:end-1); 60 | 61 | % convert UTM zone to code by refering the bellow rule. 62 | % ref. http://geotiff.maptools.org/spec/geotiff6.html#6.3.3.1 63 | zc_ns=MSIinfo.GeoInfo.UTM_zone(end); 64 | clear MSIinfo; 65 | if zc_ns=='N' 66 | zc = abs(str2double(zc_num)); 67 | if zc>10 68 | geokey = ['326',zc_num]; 69 | else 70 | geokey = ['3260',zc_num]; 71 | end 72 | geokey=str2double(geokey); 73 | elseif zc_ns=='S' 74 | zc = abs(str2double(zc_num)); 75 | if zc>10 76 | geokey = ['327',zc_num]; 77 | else 78 | geokey = ['3270',zc_num]; 79 | end 80 | geokey=str2double(geokey); 81 | end 82 | 83 | 84 | %% open MSI data 85 | BandCorr = {'02','03','04','8A','11','12','10','07','08'}; 86 | BandCorrId = [1, 2, 3, 8, 11, 12, 10, 6, 7]; 87 | Ratio = [10 10 10 20 20 20 60 20 10] /(resolu(1)); % resample all bands to the same resolu 88 | Dmain = fullfile(FilesInfo.DirIn, [FilesInfo.DataStrip '.SAFE'],'GRANULE', FilesInfo.Granule, 'IMG_DATA'); 89 | id_missing=zeros(ijdim_ref,'uint8'); 90 | for iB=1:length(BandCorr) 91 | % support all versions for Sentinel 2 images. 92 | tempfn = dir(fullfile(Dmain, [FilesInfo.Granule(1) '*B' BandCorr{iB} '.jp2'])); 93 | if isempty(tempfn) 94 | tempfn = dir(fullfile(Dmain, [FilesInfo.Granule(5) '*B' BandCorr{iB} '.jp2'])); 95 | if isempty(tempfn) 96 | tempfn = dir(fullfile(Dmain, ['*B' BandCorr{iB} '.jp2'])); 97 | end 98 | end 99 | dum=imread(fullfile(Dmain, tempfn(1).name)); 100 | mask_filled = dum == 0; 101 | dum = 10000.*(single(dum) + band_offsets(BandCorrId(iB) == band_offsets(:, 1), 2))./quantif; % OFFset 102 | clear tempfn; 103 | 104 | dum(mask_filled)=NaN; 105 | clear mask_filled; 106 | if Ratio(iB)<1 % box-agg pixel 107 | TOAref(:,:,iB) = imresize(dum,Ratio(iB),'box'); 108 | elseif Ratio(iB)>1 % split pixel 109 | TOAref(:,:,iB) = imresize(dum,Ratio(iB),'nearest'); 110 | elseif Ratio(iB)==1 111 | TOAref(:,:,iB) = dum; 112 | end 113 | clear dum; 114 | 115 | % % if isequal(BandCorr{iB},'08') 116 | % % % Gaussfilt for band 8 to better scall up. 117 | % % TOAref(:,:,iB) = imgaussfilt(TOAref(:,:,iB)); 118 | % % end 119 | 120 | % only processing pixesl where all bands have values (id_mssing) 121 | id_missing=id_missing|isnan(TOAref(:,:,iB)); 122 | end 123 | clear Dmain FilesInfo BandCorr Ratio iB; 124 | % trgt=CreateTargetGRIDobj(info_jp2.BoundingBox,resolu,ul,ijdim_ref,zc_num,zc_ns,geokey); 125 | trgt=CreateTargetGRIDobj(resolu,ul,ijdim_ref,zc_num,zc_ns,geokey); 126 | 127 | trgt.Z=[]; 128 | 129 | % trgt_test = GRIDobj('E:\New validation dataset\New accuracy assessment samples for Fmask 4_0\Sentinel2\S2A_MSIL1C_20171011T123151_N0205_R023_T17CNL_20171011T123149.SAFE\GRANULE\L1C_T17CNL_A012032_20171011T123149\Samples\assist data\Samples.tif'); 130 | 131 | %% 132 | %%%%% WARNING - what is the MSI fill value? what is the saturation flag ? 65535 133 | % TOAref(isnan(TOAref))=-9999; 134 | TOAref(id_missing)=-9999; 135 | clear id_missing; 136 | %%%%%%%%%%%% Not used but same format 137 | im_th=-9999; 138 | 139 | % B1Satu = zeros([size(TOAref,1) size(TOAref,2)],'uint8')==1; 140 | % B2Satu = zeros([size(TOAref,1) size(TOAref,2)],'uint8')==1; 141 | % B3Satu = zeros([size(TOAref,1) size(TOAref,2)],'uint8')==1; 142 | 143 | % find pixels that are saturated in the visible bands 144 | % SATURATED VALUE: 65535 set by Shi 10/18/2017 145 | B1Satu=TOAref(:,:,1)==65535; 146 | B2Satu=TOAref(:,:,2)==65535; 147 | B3Satu=TOAref(:,:,3)==65535; 148 | end 149 | 150 | function trgt=CreateTargetGRIDobj(resolu,ul,ijdim_ref,zc_num,zc_ns,geokey) 151 | 152 | trgt = GRIDobj([]); 153 | trgt.name='target'; 154 | trgt.cellsize=resolu(1); 155 | 156 | ul_tmp=ul - [resolu(1)/2 0-resolu(2)/2]; %back to limits. 157 | 158 | % location of the 'center' of the first (1,1) pixel in the image. 159 | trgt.refmat=makerefmat(ul(1),ul(2),resolu(1),0-resolu(2)); 160 | clear ul; 161 | trgt.size=ijdim_ref; 162 | 163 | % boundary. 164 | xWorldLimits=[ul_tmp(1),ul_tmp(1)+resolu(1)*(ijdim_ref(1))]; 165 | yWorldLimits=[ul_tmp(2)-resolu(2)*(ijdim_ref(2)),ul_tmp(2)]; 166 | clear ul_tmp resolu; 167 | 168 | spatialRef = maprefcells(xWorldLimits,yWorldLimits,ijdim_ref,... 169 | 'ColumnsStartFrom','north'); 170 | clear xWorldLimits yWorldLimits ijdim_ref; 171 | trgt.georef.SpatialRef=spatialRef; 172 | clear spatialRef; 173 | % % http://www.gdal.org/frmt_sentinel2.html 174 | trgt.georef.GeoKeyDirectoryTag.GTModelTypeGeoKey = 1; 175 | trgt.georef.GeoKeyDirectoryTag.GTRasterTypeGeoKey = 1; 176 | trgt.georef.GeoKeyDirectoryTag.GTCitationGeoKey = ['WGS 84 / UTM zone ',zc_num,zc_ns]; 177 | trgt.georef.GeoKeyDirectoryTag.ProjectedCSTypeGeoKey = geokey; clear geokey; 178 | trgt.georef.GeoKeyDirectoryTag.PCSCitationGeoKey = ['WGS 84 / UTM zone ',zc_num,zc_ns]; % same 179 | 180 | % ellipsoid = utmgeoid([zc_num,zc_ns]); 181 | 182 | % this can be used as follows because the Sentinel 2 titles's projection 183 | % will not be changable. 184 | % WGS84 UTM: A UTM zone is a 6?° segment of the Earth. 185 | E = wgs84Ellipsoid('meters'); 186 | utmstruct = defaultm('tranmerc'); 187 | utmstruct.geoid = [E.SemimajorAxis,E.Eccentricity]; 188 | clear E; 189 | % UTM false East (m) 190 | % the central meridian is assigned 500,000 meters in each zone. 191 | utmstruct.falseeasting=500000; 192 | % UTM false North (m) 193 | % If you are in the northern hemisphere, the equator has a northing 194 | % value of 0 meters. In the southern hemisphere, the equator starts 195 | % at 10,000,000 meters. This is because all values south of the 196 | % equator will be positive. 197 | if zc_ns=='N' 198 | utmstruct.falsenorthing=0; 199 | else % S 200 | utmstruct.falsenorthing=10000000;% 10,000,000 201 | end 202 | clear zc_ns; 203 | % UTM scale factor 204 | utmstruct.scalefactor=0.9996; 205 | % each UTM zone is 6 degrees. a total of 60 zones. 206 | origin=(str2double(zc_num)-31).*6+3; 207 | clear zc_num; 208 | utmstruct.origin=[0,origin,0]; 209 | clear origin; 210 | utmstruct = defaultm(utmstruct); 211 | trgt.georef.mstruct=utmstruct; 212 | clear utmstruct; 213 | end 214 | -------------------------------------------------------------------------------- /pixel2pixv.m: -------------------------------------------------------------------------------- 1 | function outim=pixel2pixv(jiul1,jiul2,resolu1,resolu2,im2,jidim1,jidim2) 2 | % from stack images of different resolution and different projection 3 | % Improved version of lndpixel2pixv 4 | % match image2 with image1 5 | % Input 1) jiul1 (upperleft corner of the pixel) of image 1 6 | % Input 2) jiul2 (upperleft corner of the pixel) of image 2 7 | % Input 3&4) j cols, i rows of image 1 8 | % Input 5&6) resolution of images 1&2 9 | % Input 7) image 2 data 10 | % Input 8&9) image 1 and 2 dimension 11 | % output matched data = outim(f(i),f(j))=>im1(i,j) 12 | % i.e. matchdata=pixel2pixv(ul1,ul2,res1,res2,data2,dim1,dim2); 13 | j=1:jidim1(1); 14 | i=1:jidim1(2); 15 | [x,y]=lndpixel2map(jiul1,j,i,resolu1); 16 | [j2,i2]=lndmap2pixel(jiul2,x,y,resolu2); 17 | 18 | % the first data is assume to be the filled value 19 | fill_v=im2(1,1); 20 | outim=fill_v*ones(jidim1(2),jidim1(1),class(fill_v)); % give filled data first 21 | 22 | 23 | jexist=(((j2 > 0)&(j2 <= jidim2(1)))); % matched data i,j exit in data2 24 | iexist=(((i2 > 0)&(i2 <= jidim2(2)))); 25 | 26 | %jexistv=j2(((j2 > 0)&(j2 <= jidim2(1)))); % exist i,j => data2 (f(i),f(j)) 27 | %iexistv=i2(((i2 > 0)&(i2 <= jidim2(2)))); 28 | 29 | jexistv=j2(jexist); % exist i,j => data2 (f(i),f(j)) 30 | iexistv=i2(iexist); 31 | 32 | outim(iexist,jexist)=im2(iexistv,jexistv); 33 | end 34 | 35 | % pixel to map 36 | function [x,y]=lndpixel2map(jiUL,j,i,resolu) 37 | % from pixel to map 38 | % Input jiUL (upperleft corner of the pixel) meters (x,y) 39 | % resolution [x, y] 40 | % output x, y of center of the pixel i,j 41 | % i.e. [x,y]=lndpixel2maplndpixel2map(jiUL,j,i,resolu) 42 | x=jiUL(1)+resolu(1)/2+(j-1)*resolu(1); 43 | y=jiUL(2)-resolu(2)/2+(1-i)*resolu(2); 44 | 45 | end 46 | 47 | % map to pixel 48 | function [j,i]=lndmap2pixel(jiUL,x,y,resolu) 49 | % from map to pixel 50 | % Input jiUL (upperleft corner of the pixel), and x meters, and y meters, and 51 | % resolution [x, y] 52 | % output j cols, i rows 53 | % i.e. [j,i]=lndmap2pixel(jiUL,x,y,resolu) 54 | j=ceil((x-jiUL(1))/resolu(1)); 55 | i=ceil((jiUL(2)-y)/resolu(2)); 56 | 57 | end 58 | 59 | 60 | -------------------------------------------------------------------------------- /probThin.m: -------------------------------------------------------------------------------- 1 | function prob_thin = probThin(BandCirrus) 2 | %PROBCIRRUS 3 | prob_thin = BandCirrus./400; 4 | clear BandCirrus; 5 | % prob_thin(prob_thin>1)=1; 6 | prob_thin(prob_thin<0)=0; 7 | end 8 | 9 | -------------------------------------------------------------------------------- /problBrightness.m: -------------------------------------------------------------------------------- 1 | function prob_brightness = problBrightness(HOT,idlnd,l_pt,h_pt) 2 | %PROBLBRIGHTNESS calculate brightness probability using HOT 3 | 4 | F_hot=HOT(idlnd); % get clear HOTs 5 | clear idlnd; 6 | % 0.175 percentile background HOT (low) 7 | t_hotl=prctile(F_hot,100*l_pt)-400; 8 | clear l_pt; 9 | % 0.825 percentile background HOT (high) 10 | t_hoth=prctile(F_hot,100*h_pt)+400; 11 | clear F_hot h_pt; 12 | 13 | prob_brightness=(HOT-t_hotl)./(t_hoth-t_hotl); 14 | clear HOT t_hotl t_hoth; 15 | prob_brightness(prob_brightness<0)=0; 16 | prob_brightness(prob_brightness>1)=1; % this cannot be higher 1 (maybe have commission errors from bright surfaces). 17 | end 18 | 19 | -------------------------------------------------------------------------------- /problSpectralVaribility.m: -------------------------------------------------------------------------------- 1 | function prob_vari = problSpectralVaribility(ndvi,ndsi,ndbi,whiteness,SatuGreen,SatuRed) 2 | %PROBLSPECTRALVARIBILITY 3 | % [varibility test over land] 4 | ndsi(SatuGreen==true&ndsi<0)=0; 5 | clear SatuGreen; 6 | ndvi(SatuRed==true&ndvi>0)=0; 7 | clear SatuRed; 8 | prob_vari=1-max(max(max(abs(ndsi),abs(ndvi)),abs(ndbi)),whiteness); 9 | clear ndsi ndvi ndbi whiteness; 10 | end 11 | 12 | -------------------------------------------------------------------------------- /problTemperature.m: -------------------------------------------------------------------------------- 1 | function [prob_temp, t_templ,t_temph] = problTemperature(BandBT,idclr,l_pt,h_pt) 2 | %PROBTEMPERATURE calculate temperature probability for land respectively. 3 | 4 | % [Temperature test (over land)] 5 | F_temp=BandBT(idclr); % get clear temperature 6 | clear idclr; 7 | t_buffer=4*100; 8 | % 0.175 percentile background temperature (low) 9 | t_templ=prctile(F_temp,100*l_pt); 10 | % 0.825 percentile background temperature (high) 11 | t_temph=prctile(F_temp,100*h_pt); 12 | clear F_temp l_pt h_pt; 13 | 14 | t_tempL=t_templ-t_buffer; 15 | t_tempH=t_temph+t_buffer; 16 | clear t_buffer; 17 | Temp_l=t_tempH-t_tempL; 18 | clear t_tempL; 19 | prob_temp=(t_tempH-BandBT)/Temp_l; 20 | clear BandBT t_tempH Temp_l; 21 | % Temperature can have prob > 1 22 | prob_temp(prob_temp<0)=0; 23 | % prob_temp(prob_temp>1)=1; 24 | 25 | end 26 | 27 | -------------------------------------------------------------------------------- /probwBrightness.m: -------------------------------------------------------------------------------- 1 | function wprob_brightness = probwBrightness(BandSWIR) 2 | %PROBWBRIGHTNESS calculate brightness probability 3 | % [Brightness test (over water)] 4 | t_bright=1100; 5 | wprob_brightness=BandSWIR./t_bright; 6 | clear BandSWIR t_bright; 7 | wprob_brightness(wprob_brightness>1)=1; 8 | wprob_brightness(wprob_brightness<0)=0; 9 | end 10 | 11 | -------------------------------------------------------------------------------- /probwTemperature.m: -------------------------------------------------------------------------------- 1 | function wprob_temp = probwTemperature(BandBT,idwt,h_pt) 2 | %PROBTEMPERATURE calculate temperature probability for water. 3 | 4 | % [temperature test (over water)] 5 | F_wtemp=BandBT(idwt); % get clear water temperature 6 | clear idwt; 7 | t_wtemp=prctile(F_wtemp,100*h_pt); 8 | clear F_wtemp h_pt; 9 | wprob_temp=(t_wtemp-BandBT)/400; 10 | clear t_wtemp BandBT; 11 | wprob_temp(wprob_temp<0)=0; 12 | % wTemp_prob(wTemp_prob > 1) = 1; 13 | end 14 | 15 | -------------------------------------------------------------------------------- /refmat2XY.m: -------------------------------------------------------------------------------- 1 | function [x,y] = refmat2XY(R,siz) 2 | 3 | % Convert referencing matrix (refmat) to coordinate vectors 4 | % 5 | % Syntax 6 | % 7 | % [x,y] = refmat2XY(R,siz) 8 | % 9 | % Input arguments 10 | % 11 | % R referencing matrix 12 | % siz size of the grid 13 | % 14 | % Output arguments 15 | % 16 | % x x-coordinates 17 | % y y-coordinates 18 | % 19 | % 20 | 21 | nrrows = siz(1); 22 | nrcols = siz(2); 23 | 24 | x = [ones(nrcols,1) (1:nrcols)' ones(nrcols,1)]*R; 25 | x = x(:,1)'; 26 | 27 | y = [(1:nrrows)' ones(nrrows,2)]*R; 28 | y = y(:,2); 29 | 30 | -------------------------------------------------------------------------------- /reproject2utm.m: -------------------------------------------------------------------------------- 1 | function [DEMr,zone] = reproject2utm(DEM,res,varargin) 2 | 3 | %REPROJECT2UTM Reproject DEM with WGS84 coordinate system to UTM-WGS84 4 | % 5 | % Syntax 6 | % 7 | % [GRIDr,zone] = reproject2utm(GRID,res) 8 | % [GRIDr,zone] = reproject2utm(GRID,res,pn,pv,...) 9 | % GRIDr = reproject2utm(GRID,GRID2) 10 | % GRIDr = reproject2utm(GRID,GRID2,'method',method) 11 | % 12 | % Description 13 | % 14 | % Reproject a grid (GRIDobj) with WGS84 geographic coordinates to UTM 15 | % WGS84 (requires the mapping toolbox and image processing toolbox). 16 | % 17 | % Input arguments 18 | % 19 | % GRID raster (GRIDobj) with WGS84 geographic coordinates. 20 | % res spatial resolution in x- and y-direction (scalar) 21 | % GRID2 raster (GRIDobj) with projected coordinate system to which 22 | % GRID shall be projected. The resulting grid will be 23 | % perfectly spatially aligned (same cellsize, same upper left 24 | % egde, same size) with GRID2. 25 | % 26 | % Parameter name/value pairs 27 | % 28 | % zone is automatically determined. If supplied, the value must 29 | % be a string, e.g., '32T'. Note that this function requires 30 | % the full grid zone reference that includes the uppercase 31 | % letter indicating the latitudinal band. 32 | % method interpolation method ('bilinear' (default), 'bicubic', 33 | % or 'nearest') 34 | % 35 | % Output arguments 36 | % 37 | % GRIDr raster (GRIDobj) with UTM-WGS84 projected coordinates 38 | % zone utm zone (string) 39 | % 40 | % 41 | % See also: GRIDobj, imtransform, maketform, mfwdtran, minvtran, utmzone 42 | % 43 | % Author: Wolfgang Schwanghart (w.schwanghart[at]geo.uni-potsdam.de) 44 | % Date: 12. January, 2017 45 | 46 | 47 | % get latitude and longitude vectors 48 | [lon,lat] = getcoordinates(DEM); 49 | 50 | if ~isa(res,'GRIDobj'); 51 | % and calculate centroid of DEM. The centroid is used to 52 | % get the utmzone 53 | lonc = sum(lon([1 end]))/2; 54 | latc = sum(lat([1 end]))/2; 55 | zone = utmzone(latc,lonc); 56 | Nhemisphere = double(upper(zone(end)))>=78; 57 | else 58 | zone = ''; 59 | 60 | end 61 | 62 | % parse input arguments 63 | p = inputParser; 64 | validmethods = {'bicubic','bilinear','nearest','linear'}; 65 | p.FunctionName = 'GRIDobj/reproject2UTM'; 66 | % required 67 | addRequired(p,'DEM',@(x) isa(x,'GRIDobj')); 68 | addRequired(p,'res',@(x) (~isa(x,'GRIDobj') && isscalar(x) && x > 0) || isa(x,'GRIDobj')); 69 | % optional 70 | addParameter(p,'zone',zone,@(x) ischar(x)); 71 | addParameter(p,'method','bilinear',@(x) ischar(validatestring(x,validmethods))); 72 | 73 | parse(p,DEM,res,varargin{:}); 74 | 75 | % get zone for output 76 | zone = p.Results.zone; 77 | 78 | 79 | % prepare mstruct (transformation structure) if only res supplied 80 | if ~isa(res,'GRIDobj'); 81 | mstruct = defaultm('utm'); 82 | mstruct.zone = p.Results.zone; 83 | mstruct.geoid = wgs84Ellipsoid; 84 | mstruct = defaultm(utm(mstruct)); 85 | 86 | % use forward transformation of the corner locations of the DEM 87 | % to calculate the bounds of the reprojected DEM 88 | xlims = zeros(4,1); 89 | ylims = xlims; 90 | [xlims(1:2),ylims(1:2)] = mfwdtran(mstruct,[min(lat) max(lat)],[min(lon) max(lon)]); 91 | [xlims(3:4),ylims(3:4)] = mfwdtran(mstruct,[min(lat) max(lat)],[max(lon) min(lon)]); 92 | lims = [min(xlims) max(xlims) min(ylims) max(ylims)]; 93 | 94 | else 95 | 96 | mstruct = res.georef.mstruct; 97 | [x,y] = getcoordinates(res); 98 | lims = [min(x) max(x) min(y) max(y)]; 99 | end 100 | 101 | 102 | % prepare tform for the image transform 103 | T = maketform('custom', 2, 2, ... 104 | @FWDTRANS, ... 105 | @INVTRANS, ... 106 | []); 107 | 108 | % calculate image transform 109 | if ~isa(res,'GRIDobj'); 110 | [Znew,xdata,ydata] = imtransform(flipud(DEM.Z),T,p.Results.method,... 111 | 'Xdata',lims([1 2]),... 112 | 'Ydata',lims([3 4]),... 113 | 'Udata',lon([1 end]),'Vdata',lat([end 1])',... 114 | 'XYScale',[res res],... 115 | 'Fillvalues',nan... 116 | ); 117 | % we have calculated the imtransform with 'ColumnsStartFrom' south. 118 | % GRIDobjs use 'ColumnsStartFrom' north 119 | Znew = flipud(Znew); 120 | xnew = cumsum([xdata(1) repmat(res,1,size(Znew,2)-1)]); 121 | ynew = flipud(cumsum([ydata(1) repmat(res,1,size(Znew,1)-1)])'); 122 | else 123 | Znew = imtransform(flipud(DEM.Z),T,p.Results.method,... 124 | 'Xdata',lims([1 2]),... 125 | 'Ydata',lims([3 4]),... 126 | 'Udata',lon([1 end]),'Vdata',lat([end 1])',... 127 | 'XYScale',[res.cellsize res.cellsize],... 128 | 'Fillvalues',nan... 129 | ); 130 | Znew = flipud(Znew); 131 | end 132 | 133 | 134 | 135 | if ~isa(res,'GRIDobj'); 136 | % Construct GRIDobj 137 | DEMr = GRIDobj(xnew,ynew,Znew); 138 | 139 | % and include geospatial information 140 | R = refmatToMapRasterReference(DEMr.refmat,DEMr.size); 141 | 142 | % write GeoKeyDirectoryTag so that DEMr can be exported 143 | % using GRIDobj2geotiff 144 | DEMr.georef.SpatialRef = R; 145 | GeoKeyDirectoryTag.GTModelTypeGeoKey = 1; % Projected coordinate system 146 | GeoKeyDirectoryTag.GTRasterTypeGeoKey = 1; % RasterPixelIsArea 147 | GeoKeyDirectoryTag.GTCitationGeoKey = ['PCS Name = WGS_84_UTM_zone_' mstruct.zone]; 148 | GeoKeyDirectoryTag.GeogCitationGeoKey = 'GCS_WGS_1984'; 149 | GeoKeyDirectoryTag.GeogAngularUnitsGeoKey = 9102; %Angular_Degree 150 | 151 | % get ProjectedCSTypeGeoKey 152 | if Nhemisphere 153 | hemisphere = '326'; 154 | else 155 | hemisphere = '327'; 156 | end 157 | 158 | % http://www.remotesensing.org/geotiff/spec/geotiff6.html#6.3.3.1 159 | % WGS84 / UTM northern hemisphere: 326zz where zz is UTM zone number 160 | % WGS84 / UTM southern hemisphere: 327zz where zz is UTM zone number 161 | % GeoKeyDirectoryTag.ProjectedCSTypeGeoKey = str2double([hemisphere zone(1:2)]); 162 | GeoKeyDirectoryTag.ProjectedCSTypeGeoKey = str2double([hemisphere sprintf('%02d',str2double(zone(regexp(zone,'[0-9]'))))]); 163 | GeoKeyDirectoryTag.ProjLinearUnitsGeoKey = 9001; % Linear_Meter 164 | 165 | DEMr.georef.GeoKeyDirectoryTag = GeoKeyDirectoryTag; 166 | DEMr.georef.mstruct = mstruct; 167 | DEMr.name = [DEM.name ' (utm)']; 168 | 169 | else 170 | DEMr = res; 171 | DEMr.Z = Znew; 172 | DEMr.name = [DEM.name ' (repr)']; 173 | zone = []; 174 | end 175 | 176 | % Transformation functions for imtransform 177 | % (may want to check projfwd and projinv instead mfwdtran and minvtran) 178 | function x = FWDTRANS(u,~) 179 | [x,y] = mfwdtran(mstruct,u(:,2),u(:,1)); 180 | x = [x y]; 181 | end 182 | 183 | function u = INVTRANS(x,~) 184 | [lati,long] = minvtran(mstruct,x(:,1),x(:,2)); 185 | u = [long lati]; 186 | end 187 | end 188 | 189 | -------------------------------------------------------------------------------- /stratiedSampleHanlder.m: -------------------------------------------------------------------------------- 1 | function samples_ids=stratiedSampleHanlder(data_dem_clear,dem_b,dem_t,dim,total_sample,ele_strata,distance,resolution) 2 | %STRATIEDSAMPLEHANLDER Further select clear sky (land) pixels to normalize 3 | %BT. 4 | % 5 | % Syntax 6 | % 7 | % samples_ids= 8 | % stratiedSampleHanlder(data_dem_clear,dem_b,dem_t,dim,total_sample,ele_strata,distance) 9 | % 10 | % Description 11 | % 12 | % Stratied sampling method is used by Qiu et al., (2017). 13 | % History: 14 | % 1. Create this function. (1. January, 2017) 15 | % 2. This stratied sampling method sometimes results in no enough 16 | % samples. (8. March, 2018) 17 | % 18 | % Input arguments 19 | % 20 | % data_dem_clear Clear sky pixels' DEM. 21 | % dem_b Basic elevation. 22 | % dem_t Highest elevation. 23 | % dim Dim for data. 24 | % total_sample All clear sky samples (40,000). 25 | % ele_strata Stratied sampling along elevation (300 meters). 26 | % distance Minmum distance among different samples(450 meters). 27 | % Distance rule will be removed if distance = 0. 28 | % resolution Spatial resolution (Landsat 30 meters; Sentinel-2 20 meters). 29 | % Output arguments 30 | % 31 | % Tempd Nomalized Temperature (BT). 32 | % 33 | % 34 | % Author: Shi Qiu (shi.qiu@uconn.edu) 35 | % Date: 27. Jan, 2022 36 | 37 | 38 | % set static seed for random generator. By Shi, at 27 Jan., 2022 39 | % 40 | % % num_clear_sky_pixels=numel(data_dem_clear); 41 | % % % no enough clear-sky pixels afther removing the pixels out of the upper 42 | % % % and lower levels.more than 1/4 total samples (10,000) can be used for 43 | % % % estimating lapse rate and c in topo correction. 44 | % % if num_clear_sky_pixels=i_dem&data_dem_clear0 56 | % num_strata=num_strata+1; 57 | strata_avail=[strata_avail;1]; 58 | else 59 | strata_avail=[strata_avail;0]; 60 | end 61 | clear dem_clear_index_tmp; 62 | end 63 | % equal samples in each stratum 64 | num_sam_each_strata=round(total_sample/sum(strata_avail)); 65 | clear total_sample; 66 | samples_ids=[];% to restore samples selected. 67 | % loop each strata and select samples 68 | loop_i=1; 69 | for i_dem=dem_b:ele_strata:dem_t 70 | if strata_avail(loop_i) 71 | % dem_clear_index_tmp=data_dem_clear>=i_dem&data_dem_clear=i_dem&data_dem_clearnum_max_tmp 82 | num_tmp=num_max_tmp; 83 | end 84 | clear num_max_tmp; 85 | samples_ids_rand_tmp=samples_ids_rand(1:num_tmp); 86 | clear num_tmp; 87 | % store data 88 | samples_ids=[samples_ids;samples_ids_rand_tmp]; 89 | clear samples_ids_rand samples_ids_rand_tmp; 90 | else 91 | num_max_tmp=num_sam_each_strata*samp_dis; 92 | if num_tmp>num_max_tmp 93 | num_tmp=num_max_tmp; 94 | end 95 | clear num_max_tmp; 96 | samples_ids_rand_tmp=samples_ids_rand(1:num_tmp); 97 | clear num_tmp; 98 | samples_ids_rand=samples_ids_rand_tmp; 99 | clear samples_ids_rand_tmp; 100 | all_num_tmp=size(samples_ids_rand); 101 | [i_tmp,j_tmp]=ind2sub(dim,samples_ids_rand); 102 | ij_tmp=[i_tmp,j_tmp]; 103 | clear i_tmp j_tmp; 104 | % removing the clear-sky pixels of which distance lt 15. 105 | idx_lt15 = rangesearch(ij_tmp,ij_tmp,samp_dis, 'distance','cityblock'); 106 | recorder_tmp=zeros(all_num_tmp,'uint8')+1; 107 | for i_idx=1:all_num_tmp 108 | if recorder_tmp(i_idx)>0 % when this label is available. 109 | recorder_tmp(cell2mat(idx_lt15(i_idx)))=0; 110 | recorder_tmp(i_idx)=1;% reback the current label as 1. 111 | end 112 | end 113 | clear all_num_tmp idx_lt15; 114 | idx_used=find(recorder_tmp==1); 115 | clear recorder_tmp; 116 | num_tmp=size(idx_used,1); 117 | num_max_tmp=num_sam_each_strata; 118 | if num_tmp>num_max_tmp 119 | num_tmp=num_max_tmp; 120 | end 121 | idx_used=idx_used(1:num_tmp); 122 | clear num_tmp num_max_tmp; 123 | % store data 124 | samples_ids=[samples_ids;samples_ids_rand(idx_used)]; 125 | clear samples_ids_rand idx_used; 126 | end 127 | end 128 | loop_i=loop_i+1; 129 | end 130 | clear samp_dis num_sam_each_strata; 131 | 132 | % % % no enough clear-sky pixels afther removing the pixels out of the upper 133 | % % % and lower levels.more than 1/4 total samples (10,000) can be used for 134 | % % % estimating lapse rate and c in topo correction. 135 | % % if numel(samples_ids) 7 | % Some text 8 | % Some more text 9 | % Even more text 10 | % 11 | % 12 | % Will produce: 13 | % s.XMLname.Attributes.attrib1 = "Some value"; 14 | % s.XMLname.Element.Text = "Some text"; 15 | % s.XMLname.DifferentElement{1}.Attributes.attrib2 = "2"; 16 | % s.XMLname.DifferentElement{1}.Text = "Some more text"; 17 | % s.XMLname.DifferentElement{2}.Attributes.attrib3 = "2"; 18 | % s.XMLname.DifferentElement{2}.Attributes.attrib4 = "1"; 19 | % s.XMLname.DifferentElement{2}.Text = "Even more text"; 20 | % 21 | % Please note that the following characters are substituted 22 | % '-' by '_dash_', ':' by '_colon_' and '.' by '_dot_' 23 | % 24 | % Written by W. Falkena, ASTI, TUDelft, 21-08-2010 25 | % Attribute parsing speed increased by 40% by A. Wanner, 14-6-2011 26 | % Added CDATA support by I. Smirnov, 20-3-2012 27 | % 28 | % Modified by X. Mo, University of Wisconsin, 12-5-2012 29 | 30 | if (nargin < 1) 31 | clc; 32 | help xml2struct 33 | return 34 | end 35 | 36 | if isa(file, 'org.apache.xerces.dom.DeferredDocumentImpl') || isa(file, 'org.apache.xerces.dom.DeferredElementImpl') 37 | % input is a java xml object 38 | xDoc = file; 39 | else 40 | %check for existance 41 | if (exist(file,'file') == 0) 42 | %Perhaps the xml extension was omitted from the file name. Add the 43 | %extension and try again. 44 | if (isempty(strfind(file,'.xml'))) 45 | file = [file '.xml']; 46 | end 47 | 48 | if (exist(file,'file') == 0) 49 | error(['The file ' file ' could not be found']); 50 | end 51 | end 52 | %read the xml file 53 | xDoc = xmlread(file); 54 | end 55 | 56 | %parse xDoc into a MATLAB structure 57 | s = parseChildNodes(xDoc); 58 | 59 | end 60 | 61 | % ----- Subfunction parseChildNodes ----- 62 | function [children,ptext,textflag] = parseChildNodes(theNode) 63 | % Recurse over node children. 64 | children = struct; 65 | ptext = struct; textflag = 'Text'; 66 | if hasChildNodes(theNode) 67 | childNodes = getChildNodes(theNode); 68 | numChildNodes = getLength(childNodes); 69 | 70 | for count = 1:numChildNodes 71 | theChild = item(childNodes,count-1); 72 | [text,name,attr,childs,textflag] = getNodeData(theChild); 73 | 74 | if (~strcmp(name,'#text') && ~strcmp(name,'#comment') && ~strcmp(name,'#cdata_dash_section')) 75 | %XML allows the same elements to be defined multiple times, 76 | %put each in a different cell 77 | if (isfield(children,name)) 78 | if (~iscell(children.(name))) 79 | %put existsing element into cell format 80 | children.(name) = {children.(name)}; 81 | end 82 | index = length(children.(name))+1; 83 | %add new element 84 | children.(name){index} = childs; 85 | if(~isempty(fieldnames(text))) 86 | children.(name){index} = text; 87 | end 88 | if(~isempty(attr)) 89 | children.(name){index}.('Attributes') = attr; 90 | end 91 | else 92 | %add previously unknown (new) element to the structure 93 | children.(name) = childs; 94 | if(~isempty(text) && ~isempty(fieldnames(text))) 95 | children.(name) = text; 96 | end 97 | if(~isempty(attr)) 98 | children.(name).('Attributes') = attr; 99 | end 100 | end 101 | else 102 | ptextflag = 'Text'; 103 | if (strcmp(name, '#cdata_dash_section')) 104 | ptextflag = 'CDATA'; 105 | elseif (strcmp(name, '#comment')) 106 | ptextflag = 'Comment'; 107 | end 108 | 109 | %this is the text in an element (i.e., the parentNode) 110 | if (~isempty(regexprep(text.(textflag),'[\s]*',''))) 111 | if (~isfield(ptext,ptextflag) || isempty(ptext.(ptextflag))) 112 | ptext.(ptextflag) = text.(textflag); 113 | else 114 | %what to do when element data is as follows: 115 | %Text More text 116 | 117 | %put the text in different cells: 118 | % if (~iscell(ptext)) ptext = {ptext}; end 119 | % ptext{length(ptext)+1} = text; 120 | 121 | %just append the text 122 | ptext.(ptextflag) = [ptext.(ptextflag) text.(textflag)]; 123 | end 124 | end 125 | end 126 | 127 | end 128 | end 129 | end 130 | 131 | % ----- Subfunction getNodeData ----- 132 | function [text,name,attr,childs,textflag] = getNodeData(theNode) 133 | % Create structure of node info. 134 | 135 | %make sure name is allowed as structure name 136 | name = toCharArray(getNodeName(theNode))'; 137 | name = strrep(name, '-', '_dash_'); 138 | name = strrep(name, ':', '_colon_'); 139 | name = strrep(name, '.', '_dot_'); 140 | 141 | attr = parseAttributes(theNode); 142 | if (isempty(fieldnames(attr))) 143 | attr = []; 144 | end 145 | 146 | %parse child nodes 147 | [childs,text,textflag] = parseChildNodes(theNode); 148 | 149 | if (isempty(fieldnames(childs)) && isempty(fieldnames(text))) 150 | %get the data of any childless nodes 151 | % faster than if any(strcmp(methods(theNode), 'getData')) 152 | % no need to try-catch (?) 153 | % faster than text = char(getData(theNode)); 154 | text.(textflag) = toCharArray(getTextContent(theNode))'; 155 | end 156 | 157 | end 158 | 159 | % ----- Subfunction parseAttributes ----- 160 | function attributes = parseAttributes(theNode) 161 | % Create attributes structure. 162 | 163 | attributes = struct; 164 | if hasAttributes(theNode) 165 | theAttributes = getAttributes(theNode); 166 | numAttributes = getLength(theAttributes); 167 | 168 | for count = 1:numAttributes 169 | %attrib = item(theAttributes,count-1); 170 | %attr_name = regexprep(char(getName(attrib)),'[-:.]','_'); 171 | %attributes.(attr_name) = char(getValue(attrib)); 172 | 173 | %Suggestion of Adrian Wanner 174 | str = toCharArray(toString(item(theAttributes,count-1)))'; 175 | k = strfind(str,'='); 176 | attr_name = str(1:(k(1)-1)); 177 | attr_name = strrep(attr_name, '-', '_dash_'); 178 | attr_name = strrep(attr_name, ':', '_colon_'); 179 | attr_name = strrep(attr_name, '.', '_dot_'); 180 | attributes.(attr_name) = str((k(1)+2):(end-1)); 181 | end 182 | end 183 | end --------------------------------------------------------------------------------