diff --git a/BufferMasks.m b/BufferMasks.m new file mode 100644 index 0000000..3a65bd5 --- /dev/null +++ b/BufferMasks.m @@ -0,0 +1,66 @@ +function [cloud,shadow,snow] = BufferMasks(pcloud,cdpix,pshadow,csdpix,psnow,sdpix) +% BUFFERMASKS Dilate cloud/cloud shadow/snow with cdpix/csdpix/sdpix with +% default value of 3/3/3. +% +% Syntax +% +% [cloud,shadow,snow] = +% BufferMasks(pcloud,cdpix,pshadow,csdpix,psnow,sdpix) +% +% Description +% +% Dilate cloud/cloud shadow/snow with cdpix/csdpix/sdpix with +% default value of 3/3/3. +% +% Input arguments +% pcloud Cloud mask. +% cldpix Dilated number of pixels for cloud with default value of 3. +% pshadow Cloud shadow mask. +% sdpix Dilated number of pixels for cloud shadow value of 3. +% psnow Snow mask. +% snpix Dilated number of pixels for snow value of 3. +% +% Output arguments +% +% cloud Final dilated cloud mask. +% shadow Final dilated cloud shadow mask. +% snow Final dilated snow mask. +% +% Example +% +% [cloud,shadow,snow] = +% BufferMasks(pcloud,3,pshadow,3,psnow,3); +% +% +% Author: Shi Qiu (shi.qiu@ttu.edu) +% Date: 2. November, 2017 + + % buffer cloud + if cdpix>0 + CEs=strel('square',2*cdpix+1); + cloud=imdilate(pcloud,CEs); + clear pcloud CEs cdpix; + else + cloud=pcloud; + clear pcloud + end + % buffer cloud shadow + if csdpix>0 + CSEs=strel('square',2*csdpix+1); + shadow=imdilate(pshadow,CSEs); + clear pshadow CSEs csdpix; + else + shadow=pshadow; + clear pshadow + end + % buffer snow + if sdpix>0 + SEs=strel('square',2*sdpix+1); + snow=imdilate(psnow,SEs); + clear psnow SEs sdpix; + else + snow=psnow; + clear psnow + end +end + diff --git a/CDI.m b/CDI.m new file mode 100644 index 0000000..aa1c43d --- /dev/null +++ b/CDI.m @@ -0,0 +1,36 @@ +function cdi = CDI(S2band7,S2band8,S2band8A) +%CDI This is used seprate bright surface from cloud by following David, +%2018 RSE + if ~isempty(S2band7)&&~isempty(S2band8) + ratio_8A_8 = S2band8./S2band8A; + clear S2band8; + ratio_8A_7 = S2band7./S2band8A; + clear S2band7 S2band8A; + + std_ratio_8A_8 =stdfilt(ratio_8A_8, true(7)); + clear ratio_8A_8; + var_ratio_8A_8 = std_ratio_8A_8 .^2; + clear std_ratio_8A_8; + + std_ratio_8A_7 = stdfilt(ratio_8A_7, true(7)); + clear ratio_8A_7; + var_ratio_8A_7 = std_ratio_8A_7 .^2; + clear std_ratio_8A_7; + + cdi = (var_ratio_8A_7-var_ratio_8A_8)./(var_ratio_8A_8+var_ratio_8A_7); + +% dim = size(cdi); +% cdi = imresize(cdi,1/7,'box'); +% cdi = ordfilt2(cdi,1,ones(3,3)); +% cdi = imresize(cdi,dim,'nearest'); +% clear dim; + + +% h = fspecial('average', 21); +% cdi = filter2(h, cdi); + else + cdi = []; + end +end + + diff --git a/CheckImagesPath.m b/CheckImagesPath.m new file mode 100644 index 0000000..6b69ed7 --- /dev/null +++ b/CheckImagesPath.m @@ -0,0 +1,224 @@ +function [num_all_images, sensors, paths, info_count_text] = CheckImagesPath(path_data) +%CHECKIMAGEPATH Ensure the input path is right to find a Landsats 4-8, and +%Sentinel 2 image(s). +% path_data - the input path + + % searching deeps. 0 is default. + [image_types_paths] = CheckImagePath(path_data,0); + + % all count info foe searched images at current folder. + num_all_images = size(image_types_paths,1); + num_L4_tm = 0; + num_L5_tm = 0; + num_L6_tm = 0; + num_L7_tm_plus = 0; + num_L8_oli_tirs = 0; + num_S2A_msi = 0; + num_S2B_msi = 0; + + % key info: the senor and path are needed as outputs, the first one is + % to determine the default parameters for Fmask, and the second one is + % to determine the path loading all data. + sensors = []; + paths = []; + info_count_text = []; + + for i_all = 1:num_all_images + % c: current. + cimage_sensor = image_types_paths{i_all,1}; + cimage_num = image_types_paths{i_all,2}; + cimage_type = Convert2ImageType(cimage_sensor,cimage_num); + clear cimage_num; + cimage_path = image_types_paths{i_all,3}; + switch cimage_type + case {'Landsat 4 TM'} + num_L4_tm=num_L4_tm+1; + case {'Landsat 5 TM'} + num_L5_tm=num_L5_tm+1; + case {'Landsat 6 TM'} + num_L6_tm=num_L6_tm+1; + case {'Landsat 7 ETM+'} + num_L7_tm_plus=num_L7_tm_plus+1; + case {'Landsat 8 OLI/TIRS'} + num_L8_oli_tirs=num_L8_oli_tirs+1; + case {'Sentinel 2A MSI'} + % further check Sentinel 2 image folder, see there is + % .SAFE. + if contains(cimage_path,'.SAFE') + num_S2A_msi=num_S2A_msi+1; + else + cimage_sensor = [];% no available Sentinel 2 data. + end + case {'Sentinel 2B MSI'} + % see there is .SAFE. + if contains(cimage_path,'.SAFE') + num_S2B_msi=num_S2B_msi+1; + else + cimage_sensor = [];% no available Sentinel 2 data. + end + end + if ~isempty(cimage_sensor) + sensors = [sensors;{cimage_sensor}]; + paths = [paths;{cimage_path}]; + end + clear cimage_sensor; + end + % renew num_all_images + num_all_images = length(sensors); + % used to notice user. + text_line = 0; + % multide + if isequal(num_all_images,num_L4_tm)||... + isequal(num_all_images,num_L5_tm)||... + isequal(num_all_images,num_L6_tm)||... + isequal(num_all_images,num_L7_tm_plus)||... + isequal(num_all_images,num_L8_oli_tirs)||... + isequal(num_all_images,num_S2A_msi)||... + isequal(num_all_images,num_S2B_msi) + % only for 1 type image + if num_L4_tm > 0 + text_line = text_line+1; + info_count_text{text_line} = sprintf('%s Landsat 4 TM images are found at ''%s''\n',... + num2str(num_L4_tm),path_data); + end + if num_L5_tm > 0 + text_line = text_line+1; + info_count_text{text_line} = sprintf('%s Landsat 5 TM images are found at ''%s''\n',... + num2str(num_L5_tm),path_data); + end + if num_L6_tm > 0 + text_line = text_line+1; + info_count_text{text_line} = sprintf('%s Landsat 6 TM images are found at ''%s''\n',... + num2str(num_L6_tm),path_data); + end + if num_L7_tm_plus > 0 + text_line = text_line+1; + info_count_text{text_line} = sprintf('%s Landsat 7 ETM+ images are found at ''%s''\n',... + num2str(num_L7_tm_plus),path_data); + end + if num_L8_oli_tirs > 0 + text_line = text_line+1; + info_count_text{text_line} = sprintf('%s Landsat 8 OLI/TIRS images are found at ''%s''\n',... + num2str(num_L8_oli_tirs),path_data); + end + if num_S2A_msi > 0 + text_line = text_line+1; + info_count_text{text_line} = sprintf('%s Sentinel 2A MSI images are found at ''%s''\n',... + num2str(num_S2A_msi),path_data); + end + if num_S2B_msi > 0 + text_line = text_line+1; + info_count_text{text_line} = sprintf('%s Sentinel 2B MSI images are found at ''%s''\n',... + num2str(num_S2B_msi),path_data); + end + else + text_line = text_line+1; + info_count_text{text_line} = sprintf(... + 'A total of %s images (as follows) are found at ''%s''\n',... + num2str(num_all_images), path_data); + if num_L4_tm > 0 + text_line = text_line+1; + info_count_text{text_line} = sprintf('%s Landsat 4 TM images\n',... + num2str(num_L4_tm)); + end + if num_L5_tm > 0 + text_line = text_line+1; + info_count_text{text_line} = sprintf('%s Landsat 5 TM images\n',... + num2str(num_L5_tm)); + end + if num_L6_tm > 0 + text_line = text_line+1; + info_count_text{text_line} = sprintf('%s Landsat 6 TM images\n',... + num2str(num_L6_tm)); + end + if num_L7_tm_plus > 0 + text_line = text_line+1; + info_count_text{text_line} = sprintf('%s Landsat 7 ETM+ images\n',... + num2str(num_L7_tm_plus)); + end + if num_L8_oli_tirs > 0 + text_line = text_line+1; + info_count_text{text_line} = sprintf('%s Landsat 8 OLI/TIRS images\n',... + num2str(num_L8_oli_tirs)); + end + if num_S2A_msi > 0 + text_line = text_line+1; + info_count_text{text_line} = sprintf('%s Sentinel 2A MSI images\n',... + num2str(num_S2A_msi)); + end + if num_S2B_msi > 0 + text_line = text_line+1; + info_count_text{text_line} = sprintf('%s Sentinel 2B MSI images\n',... + num2str(num_S2B_msi)); + end + end + + % if no data is found, give mention info. + if isempty(sensors) + text_line = text_line+1; + info_count_text{text_line} = sprintf('%s available images are found at ''%s''\n',... + '0',path_data); + text_line = text_line+1; + info_count_text{text_line} = sprintf('Please ensure the path is correct\n'); + end +% fprintf(info_count); +end + +function [image_types_paths] = CheckImagePath(path_data,subfolder_level) +%CHECKIMAGEPATH Ensure the input path is right to find a Landsats 4-8, and +%Sentinel 2 image(s). +% path_data - the input path +% subfolder_level - the level of subfolders that can be used to limit +% searching deeps. 0 is default. + + % If the searching deeps are more than 5, stop and return; + if subfolder_level > 5 + image_types_paths = []; + return; + end + image_types_paths = []; + % first search the image(s) at current folder.image_types_paths + [sensor,num_Lst,~,~] = LoadSensorType(path_data); + if isempty(sensor) + % if no available image at current folder, + % and search its subfolders. + subfolders = dir(path_data); + for i_sub=1:length(subfolders) + % filter out the file names starting with '.', that is not + % right folder (system crashes). + if strcmp(subfolders(i_sub).name(1),'.') + continue; + end + % go to search the images at each subfolder + path_subfoler = fullfile(subfolders(i_sub).folder,... + subfolders(i_sub).name); + [image_types_paths_sub] = CheckImagePath(path_subfoler,subfolder_level+1); + if ~isempty(image_types_paths_sub) + image_types_paths =[image_types_paths;image_types_paths_sub]; + end + end + + else + % successfully searched a supported image. + % and, return the sensor and the image path + image_types_paths = [image_types_paths;{sensor,num_Lst,path_data}]; + end +end +function image_type = Convert2ImageType(sensor,num_Lst) +% CONVERT@IMAGETYPE Contruct image type from the sensor and num + + switch sensor + case 'L_TM' + image_type = ['Landsat ',num_Lst,' TM']; + case 'L_ETM_PLUS' + image_type = ['Landsat ',num_Lst,' ETM+']; + case 'L_OLI_TIRS' + image_type = ['Landsat ',num_Lst,' OLI/TIRS']; + case 'S_MSI' + image_type = ['Sentinel ',num_Lst,' MSI']; + otherwise + image_type=[]; +% error(['Errors occur when searching images at ''', path_data],'''.'); + end +end + diff --git a/DetectAbsSnow.m b/DetectAbsSnow.m new file mode 100644 index 0000000..857ae27 --- /dev/null +++ b/DetectAbsSnow.m @@ -0,0 +1,97 @@ +function abs_snow = DetectAbsSnow(band_green,band_green_statu,ndsi,psnow,resolution) +%DETECTABSSNOW Select absolute snow/ice pixels using spectral-contextual +% features. +% +% Syntax +% +% abs_snow = +% DetectAbsSnow(band_green,band_green_statu,ndsi,psnow,resolution) +% +% Description +% +% A spectral-contextual snow index (SCSI) is used to select 100% +% snow/ice pixels. SCSI10 is computed here, of which 10 indicates 10 +% kilometers-by-10 kilometers window size, that 333-by-333 pixels for +% Landsat 30 meters image and 501-by-501 pixels for Sentinel-2 20 +% meters image (Fmask runs at this 20 meters). That window size is +% large enough to capture the various contexts for clouds, but still +% give us soomth variations for pure snow/ice. +% +% Input arguments +% +% band_green Green band. +% band_green_statu Statured pixels at green band. +% psnow Potential snow/ice. +% resolution Spatial resolution of input image. +% +% Output arguments +% +% abs_snow Absolute snow/ice. +% +% Example +% +% abs_snow = +% DetectAbsSnow(band_green,band_green_statu,ndsi,psnow,resolution) +% +% +% Author: Shi Qiu (shi.qiu@uconn.edu) +% Date: 21. January, 2018 + + % which is equal to 51*51 % when have snow/ice pixels. + if sum(psnow(:))> 110889 + % sometimes Nan will appear in this green band. + band_green_filled = fillmissing(band_green,'constant',0); + clear band_green; + % window size is 10 km. + radius = fix(5000/resolution); + clear resolution; + win_size=2*radius+1; + % compute SCSI. + % clip to small arrays (16 smalls); + num_clips = 5; % 5 cippes + [size_row, size_colum] =size(band_green_filled); + num_per_row = round((size_row)/num_clips); + + row_nums = zeros([1,num_clips],'double'); + row_nums(1:num_clips-1)=num_per_row; + % the last one + row_nums(num_clips) = size_row - num_per_row*(num_clips-1); + + scsi = zeros([size_row, size_colum],'double'); + % the first clip + + + for i_row = 1:num_clips + row_ed = sum(row_nums(1:i_row)); + row_st = row_ed - row_nums(i_row) + 1; + + row_st_exp = row_st - radius; + row_ed_exp = row_ed + radius; + row_st_small = radius + 1; + row_ed_small = radius + row_nums(i_row); + % the first clip + if row_st_exp < 1 + row_st_exp =1; + row_st_small =1; + row_ed_small = row_nums(i_row);% no the first exp + end + % the last clip + if row_ed_exp > size_row + row_ed_exp = size_row; +% row_ed_small = radius + row_nums(i_row); + end + scsi_small= stdfilt(band_green_filled(row_st_exp:row_ed_exp,:),... + true(win_size)).*(1-ndsi(row_st_exp:row_ed_exp,:)); + scsi(row_st:row_ed,:) = scsi_small(row_st_small:row_ed_small,:); + clear scsi_small; + end + % scsi=stdfilt(band_green_filled,true(win_size)).*(1-ndsi); + clear band_green_filled ndsi win_size; + % only get snow/ice pixels from all potential snow/ice pixels, and + % do not select the saturated pixels which may be cloud! + abs_snow=scsi<9&psnow&(~band_green_statu); + clear scsi psnow band_green_statu; + else + abs_snow=[]; + end +end diff --git a/DetectPotentialCloud.m b/DetectPotentialCloud.m new file mode 100644 index 0000000..1bd8a6b --- /dev/null +++ b/DetectPotentialCloud.m @@ -0,0 +1,152 @@ +function [sum_clr,cloud,idused,t_templ,t_temph]=DetectPotentialCloud(... + data_meta,mask,water,data_toabt, dem, ndvi,ndsi,ndbi,idplcd,... + whiteness,HOT,wpt,cldprob) +%DETECTPOTENTIALCLOUD Detect potential clouds using scene-based method. +% +% Syntax +% +% [sum_clr,cloud,idlnd,t_templ,t_temph]= +% DetectPotentialCloud(data_meta,mask,water,data_toabt, dem, ndvi,ndsi, +% ndbi,idplcd,whiteness,HOT,wpt,cldprob) +% +% Description +% +% Several cloud probabilities are combinated together to capture cloud +% features of white, bright, cold, and/or high. +% +% Input arguments +% +% data_meta Metadata including [row size, column size]. +% mask Observation mask (outside). +% water Water mask. +% data_toabt TOA reflectance and BT. +% dem DEM data. +% ndvi NDVI. +% ndsi NDSI. +% ndbi NDBI. +% idplcd Absolute clear sky pixels. +% whiteness Whitness. +% HOT Data derived from the HOT transform. +% wpt Weight of thin probability (0.3 for Landsat 8 and +% 0.5 for Sentinel 2). +% cldprob Cloud probability threshold to segment clouds from +% surface. +% +% Output arguments +% +% sum_clr The total number of clear sky pixels. +% cloud Potential clouds. +% idlnd Clear sky land pixels. +% t_templ Low level temperature (78.5 percentile). +% t_temph High level temperature (81.5 percentile). +% +% +% +% Author: Shi Qiu (shi.qiu@uconn.edu) +% Date: 20. January, 2018 + + % inputs: BandCirrus BandBT BandSWIR1 SatuGreen SatuRed + cloud = zeros(data_meta.Dim,'uint8'); % cloud mask + %% Constants Parameters + l_pt=0.175; % low percent + h_pt=1-l_pt; % high percent + + %% Step 2: calcualte cloud probability + % select clear sky pixels for land and water, respectively. + idclr=idplcd==false&mask==1; + sum_clr=sum(idclr(:)); + idlnd = idclr&water==false; + idwt = idclr&water==true;%&data(:,:,6)<=300; + + t_templ = 0; + t_temph = 0; + idused = []; + % 99.9% TO 99.99% + if sum_clr <= 40000 % when potential cloud cover less than 0.1%, directly screen all PCPs out. + cloud(idplcd==true)=1; % all cld + cloud(mask==0)=0; + else + %%%%%%%%%%% thin cloud prob for both water and land%%%%%%%%%%% + prob_thin = 0; % there is no contribution from the new bands + if ~isempty(data_toabt.BandCirrus) % Landsat 4~7 + prob_thin = probThin(data_toabt.BandCirrus); + data_toabt.BandCirrus = []; + end + + %%%%%%%%%%%%%%%%%%%%%%cloud prob over water%%%%%%%%%%%%%%%%%%% + wprob_temp=1; + if ~isempty(data_toabt.BandBT) + wprob_temp = probwTemperature(data_toabt.BandBT,idwt,h_pt); + end + + wprob_brightness = probwBrightness(data_toabt.BandSWIR1); + data_toabt.BandSWIR1 = []; + + %%%%%%%%%%%%%%%%%%%%%%cloud prob over land%%%%%%%%%%%%%%%%%%%% + lndptm=100*sum(idlnd(:))/sum(mask(:)); + if lndptm >= 0.1 + idused=idlnd; + else % when having no enough clear land pixels, we used all PCPs to calculate clear land basics. + idused=idclr; + end + clear lndptm; + + lprob_temp=1; + lprob_brightness=1; + + + if ~isempty(data_toabt.BandBT) % if have BT. normalize it using DEMs and use it to calcualte temperature probability. + data_toabt.BandBT = NormalizeBT( data_meta.Dim,dem,mask,data_toabt.BandBT,idused,l_pt,h_pt,data_meta.Resolution(1)); + [lprob_temp, t_templ,t_temph]=problTemperature(data_toabt.BandBT,idused,l_pt,h_pt); + else % if have no BT, use HOT probability instead of temperature probability. + lprob_brightness = problBrightness(HOT,idused,l_pt,h_pt); + clear HOT l_pt; + end +% clear idused; + + lprob_vari = problSpectralVaribility(ndvi,ndsi,ndbi,whiteness,data_toabt.SatuGreen,data_toabt.SatuRed); + clear ndvi ndsi whiteness; + + %%%%%%%%%%%%%%%%%%%%%%%%%%final clouds%%%%%%%%%%%%%%%%%%%%%%% + % [Final prob mask (water)] + wprob_final=wprob_temp.*wprob_brightness + wpt.*prob_thin; % cloud over water probability + clear wprob_temp wprob_brightness; + wprob_final=100.*wprob_final; % convert percentage + wclr_h=prctile(wprob_final(idwt),100*h_pt); + clear idwt; + % Final prob mask (land) + lprob_final=lprob_temp.*lprob_vari.*lprob_brightness + wpt.*prob_thin; % cloud over land probability +% clear lprob_temp lprob_vari lprob_brightness prob_thin wpt; + clear lprob_temp lprob_vari prob_thin wpt; + lprob_final=100.*lprob_final; % convert percentage + clr_h=prctile(lprob_final(idlnd),100*h_pt); + clear h_pt; + + wclr_max=wclr_h+cldprob;% dynamic threshold (water) + clr_max=clr_h+cldprob;% dynamic threshold (land) + clear cldprob; + clear idclr; + + % all potential clouds +% % cloud(idclr==true)=-1; + + id_final_cld = SegementClouds(idplcd,water,data_toabt.BandBT,t_templ,lprob_final,wprob_final,clr_max,wclr_max); + clear clr_max wclr_max; + cloud(id_final_cld)=1; + clear id_final_cld; + + cloud(mask==0)=0; + clear mask; + end +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%final clouds%%%%%%%%%%%%%%%%%%%%%%% +function id_final_cld = SegementClouds(idplcd,water,bt,t_templ,lprob,wprob,clr_max,wclr_max) + % final clouds + id_final_cld=idplcd==true&((lprob>clr_max&water==0)|...% cloud over land + (wprob>wclr_max&water==1));% thin cloud over water + if ~isempty(bt) % if have BT. + id_final_cld=id_final_cld|(bt200)=1; + masker_shadow(masker_observation==0)=255; +end + +% CSC+C stratied on cos i with 0.1 increasement. a total of 50,000 pixels. +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 ) +% History: +% 1. Create this function. (1. January, 2017) +% 2. A total of samples are revised as 40,000 from 50,000. (8. March, 2018) +% 3. When c is calculated as Nan, this function will not make the topo +% correction. (8. March, 2018) + sun_zenith_rad=deg2rad(sun_zenith_deg); + sun_zenith_cos=cos(sun_zenith_rad); + sun_zenith_sin=sin(sun_zenith_rad); + clear sun_zenith_deg sun_zenith_rad sun_zenith_rad; + cos_sita=sun_zenith_cos.*cos(deg2rad(slope_data))+sun_zenith_sin.*sin(deg2rad(slope_data)).*cos(deg2rad(sun_azimuth_deg-aspect_data)); + clear aspect_data; + cos_sita_exclude_cloud=cos_sita(index_exclude_cloud_water); + % random stratied sampling + cos_sita_min=min(cos_sita_exclude_cloud); + cos_sita_max=max(cos_sita_exclude_cloud); +% total_sample=50000; + total_sample=40000; + cos_sita_interval=0.1; + samples_ids= stratiedSampleHanlder(cos_sita_exclude_cloud,cos_sita_min,cos_sita_max,dim,total_sample,cos_sita_interval,0,resl); + cos_sita_samples=cos_sita_exclude_cloud(samples_ids); + clear cos_sita_exclude_cloud cos_sita_min cos_sita_max total_sample cos_sita_interval; + % for NIR + data_nir_ori_tmp=data_nir_ori(index_exclude_cloud_water); + data_samples_nir=data_nir_ori_tmp(samples_ids); + clear data_nir_ori_tmp; + c_fitted=polyfit(double(cos_sita_samples),double(data_samples_nir),1); +% figure;plot(double(cos_sita_samples),double(data_samples_nir),'r.'); + c=c_fitted(1,2)/c_fitted(1,1); + + clear c_fitted; + if isnan(c) + data_nir=data_nir_ori; + clear data_nir_ori; + else + data_nir=double(data_nir_ori).*(cos(deg2rad(slope_data)).*sun_zenith_cos+c)./(cos_sita+c); + clear data_nir_ori; + end + % for SWIR + data_swir_ori_tmp=data_swir_ori(index_exclude_cloud_water); + data_samples_swir=data_swir_ori_tmp(samples_ids); + clear data_swir_ori_tmp; + c_fitted=polyfit(double(cos_sita_samples),double(data_samples_swir),1); + c=c_fitted(1,2)/c_fitted(1,1); + clear c_fitted samples_ids; + if isnan(c) + data_swir=data_swir_ori; + clear data_swir_ori; + else + data_swir=double(data_swir_ori).*(cos(deg2rad(slope_data)).*sun_zenith_cos+c)./(cos_sita+c); + clear data_swir_ori; + end +end diff --git a/DetectPotentialFalsePositivePixels.m b/DetectPotentialFalsePositivePixels.m new file mode 100644 index 0000000..e2cc5a8 --- /dev/null +++ b/DetectPotentialFalsePositivePixels.m @@ -0,0 +1,62 @@ +function pfpl = DetectPotentialFalsePositivePixels(mask, psnow,slope, ndbi,ndvi,bt,cdi,water,resolution) +%DETECTPOTENTIALFALSEPOSITIVEPIXELS +% remove the water pixels at the final layer. by Shi Mar.27, 2018. + %% urban + ndbi = EnhanceLine(ndbi); % enhance the ndbi to high urban/built-up area. + pfpl = (ndbi > 0)&(ndbi > ndvi)&mask&water==0;% urban + + %% remove cloudy pixels using ostu method for thermal band. + if sum(pfpl(:))>0 % have urban pixels + if ~isempty(bt) + % ostu's method to determine the temperature therhold to + % seperate clouds from urban/built-up area. + bt_pfpl = bt(pfpl==1); + t = graythresh(bt_pfpl); + % get the thershold + BW = imbinarize(bt_pfpl,t); + clear t; + cold_tmp = min(bt_pfpl(BW)); + clear bt_pfpl BW; + if ~isempty(cold_tmp) + clouds_tmp = bt < cold_tmp(1); + pfpl(clouds_tmp==1)=0; + end + clear bt cold_tmp +% figure; imshow(pfpl); + elseif ~isempty(cdi) +% pfpl(cdi < -1)=0; % by following David, 2018 RSE + pfpl(cdi < -0.8)=0; % by following David, 2018 RSE + end + end + + % remove the isolate urban/built-up pixels +% % pfpl = bwareaopen(pfpl,2); + + %% add poential snow/ice pixels in mountain. + if ~isempty(slope) + psnow_mountain = psnow==1& slope > 20; + % 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. + pfpl = pfpl|psnow_mountain; + end + + %% buffer urban pixels with 1 kilometer window. + witdh=250; + width_pixels=fix(witdh/resolution);% 1 km 33 Landsat pixel 500 meters 17 Landsat pixels. 200 meters 7 pixels. + SEw=strel('square',2*width_pixels+1); + pfpl=imdilate(pfpl,SEw); + + %% must over land this will be runned in commission process function. + % some coastline may be classified as water, that is easily detected as + % cloud when it is grouped into water layer. +% pfpl = pfpl&mask&water==0; % remove the water pixels at the final. also remove the absolute cloud pixels. + +% % % dilate user-defined buffers for erosion more commission errors. +% % if pfbpix > 0 +% % SEw=strel('square',2*pfbpix+1); +% % pfpl=imdilate(pfpl,SEw); +% % end + + pfpl = pfpl|psnow;%% add the snow/ice pixels located in normal areas. + + pfpl = pfpl&mask; % remove the water pixels at the final. also remove the absolute cloud pixels. +end \ No newline at end of file diff --git a/DetectPotentialPixels.m b/DetectPotentialPixels.m new file mode 100644 index 0000000..b40db8b --- /dev/null +++ b/DetectPotentialPixels.m @@ -0,0 +1,56 @@ +function [idplcd,BandCirrusNormal,whiteness,HOT] = DetectPotentialPixels(mask,data_toabt,dem,ndvi,ndsi,satu_Bv) +% DETECTPOTENTIALCLOUD detect potential cloud pixels (PCPs) + + % Cirrus Probability This is unavailable here because some high + % mountianus have high cirrus values. + + % inputs: BandSWIR2 BandBT BandBlue BandGreen BandRed BandNIR BandSWIR1 + % BandCirrus + %% Step 1: detect possible cloud pixels (PCPs) + + % [Basic cloud test] + idplcd=ndsi<0.8&ndvi<0.8&data_toabt.BandSWIR2>300; + clear ndsi ndvi; data_toabt.BandSWIR2 = []; % memory. + % when have BT data. + if ~isempty(data_toabt.BandBT) + idplcd=idplcd==true&data_toabt.BandBT<2700; + data_toabt.BandBT = []; + end + + % [Whiteness test] + % visible bands flatness (sum(abs)/mean < 0.6 => brigt and dark cloud ) + visimean=(data_toabt.BandBlue+data_toabt.BandGreen+data_toabt.BandRed)/3; + whiteness=(abs(data_toabt.BandBlue-visimean)+abs(data_toabt.BandGreen-visimean)+... + abs(data_toabt.BandRed-visimean))./visimean; + data_toabt.BandGreen = []; + clear visimean; + % update idplcd + whiteness(satu_Bv==1)=0;% If one visible is saturated whiteness == 0 + idplcd=idplcd==true&whiteness<0.7; + + % [Haze test] + HOT=data_toabt.BandBlue-0.5.*data_toabt.BandRed-800;% Haze test + data_toabt.BandBlue = []; + data_toabt.BandRed = []; + + idplcd=idplcd==true&(HOT>0|satu_Bv==1); + clear satu_Bv; % need to find thick warm cloud + + % [Ratio4/5>0.75 cloud test] + Ratio4_5=data_toabt.BandNIR./data_toabt.BandSWIR1; + data_toabt.BandNIR = []; + data_toabt.BandSWIR1 = []; + idplcd=idplcd==true&Ratio4_5>0.75; + clear Ratio4_5; + + BandCirrusNormal=[]; + % normalize Cirrus band [Cirrus test] from Landsat 8 and Sentinel 2 images + if ~isempty(data_toabt.BandCirrus) + BandCirrusNormal=NormalizaCirrusDEM( mask, idplcd, data_toabt.BandCirrus, dem ); +% BandCirrusNormal= data_toabt.BandCirrus; + clear data_toabt mask dem; + idplcd=idplcd==true|BandCirrusNormal > 100; % When TOA at Cirrus band is more than 0.01, it may be cloudy. + end + +end + diff --git a/DetectSnow.m b/DetectSnow.m new file mode 100644 index 0000000..b107891 --- /dev/null +++ b/DetectSnow.m @@ -0,0 +1,13 @@ +function snow = DetectSnow(dim,band_green,band_nir,band_bt,ndsi) +%DETECSNOW Summary of this function goes here +% Detailed explanation goes here + +% % snow=zeros(dim,'uint8'); % Snow mask + % It takes every snow pixels including snow pixel under thin clouds or icy clouds + snow=ndsi>0.15&band_nir>1100&band_green>1000; + if ~isempty(band_bt) + snow=snow&band_bt<1000; + end +% snow(ids_snow)=1; +end + diff --git a/DetectWater.m b/DetectWater.m new file mode 100644 index 0000000..d9085bc --- /dev/null +++ b/DetectWater.m @@ -0,0 +1,76 @@ +function water = DetectWater( dim, mask, nir, NDVI, psnow, slope, gswater) +%DETECTWATER Detect water by combining spectral-derived water and +%GSWO-derived water togeter. +% +% Syntax +% +% water = DetectWater( dim, mask, nir, NDVI, psnow, slope, gswater) +% +% Description +% +% History: +% 1. Create this function. (1. January, 2018) +% 2. The sepctral-derived water may be incorrect, resulting in a 100% +% absolutely wrong low level GSWO (equal to 0). The GWSO will be used +% only when the low level GSWO is larger than 0. (9. March, 2018) +% 3. Remove the coastline because of its frequent changes. (6. May, 2018) +% +% +% Input arguments +% +% dim Dim for data. +% mask Mask for observations. +% nir NIR. +% NDVI NDVI. +% psnow Potential snow. +% slope Slope. +% gswater GSWO. +% +% Output arguments +% +% water Water map. +% +% +% Author: Shi Qiu (shi.qiu@uconn.edu) +% Date: 9. March, 2018 + + water=zeros(dim,'uint8'); % Water msk + %% Zhe's water test (works over thin cloud) + water((NDVI<0.01&nir<1100)|(NDVI<0.1&NDVI>0&nir<500))=1; + clear resolution; + + % within observation. + water(~mask)=0; + + %% the GSWO data to enhance water map. + if sum(water(:))>0&&~isempty(gswater) + if sum(gswater(:))>0 % have water occurences. + % assume the water occurances are same in each whole scene. + % global surface water occurance (GSWO) + % low level to exclude the commssion errors as water. + % 5% tolerances. + gswater_occur=prctile(gswater(water==1),17.5)-5; + + if gswater_occur>0 % must be more than 0. + water_gs = gswater>gswater_occur; + clear gswater gswater_occur; + % sometimes ice may be over water. Snow covered sea ice is determined + % using the potential snow/ice. + water_gs(psnow == 1)=0; % remove out ice water. + clear psnow; + + water(water_gs==1)=1;% extend water regions based on GSWO. + % equal to the theshold because it may be 100%. + + % water(psnow)=0; % remove out ice water. I think this snow + % cannot be removed because there are sometimes ice clouds over + % water. + water(~mask)=0; + end + end + % note that 255 indicates no data in GSWO, that is ocean pixels or + % permenate snow/ice pixels (which can be identified as land pixels). + end + +end + diff --git a/EnhanceLine.m b/EnhanceLine.m new file mode 100644 index 0000000..e90b342 --- /dev/null +++ b/EnhanceLine.m @@ -0,0 +1,85 @@ +function line_enhanced = EnhanceLine(band) +%ENHANCELINE enhance NDBI to light urban/built-up areas and dark other +%bright surface, such as desert, rock. ref Guindon et. RSE 2004 + +% also can see the details at +% https://homepages.inf.ed.ac.uk/rbf/HIPR2/linedet.htm. +% band=data_toabt.BandGreen; +% +% band=ndbi; + +%% line with a length of three pixels +% template1=[-1 1 0; +% -1 1 0; +% -1 1 0;]; +% template2=[0 1 -1; +% 0 1 -1; +% 0 1 -1;]; +% line_enhanced1 = imfilter(band,template1); +% line_enhanced2 = imfilter(band,template2); +% line_enhanced=max(line_enhanced1,line_enhanced2); +% +% template1=[-1 -1 -1; +% 1 1 1; +% 0 0 0;]; +% template2=[0 0 0; +% 1 1 1; +% -1 -1 -1;]; +% line_enhanced1 = imfilter(band,template1); +% line_enhanced2 = imfilter(band,template2); +% line_enhanced=max(line_enhanced1,line_enhanced); +% line_enhanced=max(line_enhanced2,line_enhanced); +% +% template1=[1 -1 -1; +% 0 1 -1; +% 0 0 1;]; +% template2=[1 0 0; +% -1 1 0; +% -1 -1 1;]; +% line_enhanced1 = imfilter(band,template1); +% line_enhanced2 = imfilter(band,template2); +% line_enhanced=max(line_enhanced1,line_enhanced); +% line_enhanced=max(line_enhanced2,line_enhanced); +% +% template1=[-1 -1 1; +% -1 1 0; +% 1 0 0;]; +% template2=[0 0 1; +% 0 1 -1; +% 1 -1 -1;]; +% +% line_enhanced1 = imfilter(band,template1); +% line_enhanced2 = imfilter(band,template2); +% line_enhanced=max(line_enhanced1,line_enhanced); +% line_enhanced=max(line_enhanced2,line_enhanced); +% line_enhanced = line_enhanced./3; + + + template=[-1 2 -1; + -1 2 -1; + -1 2 -1;]; + template = template./6; + line_enhanced = imfilter(band,template); + + template=[-1 -1 -1; + 2 2 2; + -1 -1 -1;]; + template = template./6; + line_enhanced_new = imfilter(band,template); + line_enhanced=max(line_enhanced_new,line_enhanced); + + template =[2 -1 -1; + -1 2 -1; + -1 -1 2;]; + template = template./6; + line_enhanced_new = imfilter(band,template); + line_enhanced=max(line_enhanced_new,line_enhanced); + + template =[-1 -1 2; + -1 2 -1; + 2 -1 -1;]; + template = template./6; + line_enhanced_new = imfilter(band,template); + line_enhanced=max(line_enhanced_new,line_enhanced); +end + diff --git a/ErodeCommissons.m b/ErodeCommissons.m new file mode 100644 index 0000000..cf67aa6 --- /dev/null +++ b/ErodeCommissons.m @@ -0,0 +1,105 @@ + +% output: cloud 1:cloud; 2: potential cloud can be eroded, which will be +% identified using cloud shadow match method. +function cloud = ErodeCommissons(data_meta,pcloud,pfpl,water,cdi,erdpix) +%REMOVECOMMISSONS remove most of commission errors from bright and low +%tempareture features. +% +% use the optimal erosion size for Landsat and Sentinel 2 images. Shi +% 4/21/2018. +% remove the cloud objects with less than 3 pixels after eroding 1 pixel. +% Shi 4/10/2018 + % pcloud potential cloud layer + % pccl potential commissions as cloud layer + + % 2-by-2 pixels window for Landsat 8 + % 3-by-3 pixels window for Landsats 4-7 and Sentinel 2 + cipix = erdpix; + + cloud = zeros(data_meta.Dim,'uint8'); % the clouds needed to be eroded. + cloud(pcloud>0)=1; + clear pcloud; + + + %% erode and dilate back. +% CEs = strel('square',2*cipix+1); + CEs = strel('disk',cipix); + % erode to remove the potential false pixels + cloud_eroded = imerode(cloud,CEs); + clear CEs; + pixels_eroded = ~cloud_eroded & pfpl; + clear cloud_eroded; + % only remove the potential false positive pixels + cloud_eroded = cloud; + cloud_eroded(pixels_eroded) = 0; + clear pixels_eroded; + + %% dilate back to orginal cloud shape of which size is dual to the erosion. + CEs = strel('disk',2*cipix); + cloud_dilated = imdilate(cloud_eroded,CEs); + + %% remover the clouds gone forever. + % Segmentate each cloud to remove the small objs. + cloud_objs=bwlabeln(cloud,8); + clouds_remaining = cloud_objs; + clouds_remaining(~cloud_eroded)=0; % remove the clouds gone. + clear cloud_eroded; + idx_clouds_remaining = unique(clouds_remaining(:)); + clear clouds_remaining; + + cloud_remaining = zeros(data_meta.Dim,'uint8'); % the clouds needed to be eroded. + if ~isempty(idx_clouds_remaining) + if idx_clouds_remaining(1)==0 + idx_clouds_remaining(1) = []; + end + if ~isempty(idx_clouds_remaining) + cloud_remaining = ismember(cloud_objs,idx_clouds_remaining); + end + end + clear cloud_objs idx_clouds_remaining; + + % only for land + + clear cloud_eroded; + cloud = (cloud_dilated&cloud_remaining)|(water&cloud); % add clouds over water. + clear cloud_dilate cipix CEs water; + + %% remove small object with minum CDI < 0.5 only for Sentinel 2 + if ~isempty(cdi) + % exclude small objects of which minimum CDI is still larger than + % -0.5. + % get small clouds + large_obj = bwareaopen(cloud,10000); + small_obj = cloud==1&large_obj==0; + clear large_obj; + % segment small clouds + small_obj_init=bwlabeln(small_obj,8); + + % produce all non confident cloud pixels using CDI + confident_cloud = cdi < -0.5; + + clear cdi; + + % remove the non confident cloud pixels again + small_obj_exd_urban = small_obj_init; + small_obj_exd_urban(confident_cloud==0) = 0; + clear confident_cloud; + + % a true cloud can be determined when any confident pixels are + % remaining. + idx = unique(small_obj_exd_urban); + clear small_obj_exd_urban; + + true_cloud = ismember(small_obj_init,idx); + clear small_obj_init idx; + + % remove bright surfaces + bright_surfaces = true_cloud==0&small_obj==1; + clear small_obj true_cloud; + cloud(bright_surfaces) =0; + end + + %% remove very small object. + cloud = bwareaopen(cloud,3); + +end \ No newline at end of file diff --git a/FmaskParameters.m b/FmaskParameters.m new file mode 100644 index 0000000..0e66d55 --- /dev/null +++ b/FmaskParameters.m @@ -0,0 +1,45 @@ +classdef FmaskParameters + %FMASKPARAMETERS Save all fmask parameters here. + + properties + CloudBuffer % pixels + CloudShadowBuffer % pixels + SnowBuffer % pixels + ThinWeight + CloudProbabilityThershold + PFPCErosionRadius % meters + OutputResolution % meters + end + + methods + function obj = FmaskParameters(sensor) + %FMASKPARAMETERS Construct an instance of this class according + %to input image. + + % public and constant paras. + obj.CloudBuffer=3; + obj.CloudShadowBuffer=3; + obj.SnowBuffer=0; + + % different paras for different sensors. + switch sensor + case 'S_MSI' + obj.ThinWeight=0.5; + obj.CloudProbabilityThershold=20.00; + obj.OutputResolution=20; + obj.PFPCErosionRadius=90;% mirrored from Landsat 8. + case 'L_OLI_TIRS' + obj.ThinWeight=0.3; + obj.CloudProbabilityThershold=17.50; + obj.OutputResolution=30; + obj.PFPCErosionRadius=90; + case {'L_TM','L_ETM_PLUS'} + obj.ThinWeight=0.0; + obj.CloudProbabilityThershold=10.00; + obj.OutputResolution=30; + obj.PFPCErosionRadius=150; + end + end + end +end + diff --git a/GRIDobj.m b/GRIDobj.m new file mode 100644 index 0000000..c296347 --- /dev/null +++ b/GRIDobj.m @@ -0,0 +1,437 @@ +classdef GRIDobj + +%GRIDobj Create instance of a GRIDobj +% +% Syntax +% +% DEM = GRIDobj(X,Y,dem) +% DEM = GRIDobj('ESRIasciiGrid.txt') +% DEM = GRIDobj('GeoTiff.tif') +% DEM = GRIDobj(); +% DEM = GRIDobj([]); +% DEM = GRIDobj(FLOWobj or GRIDobj or STREAMobj,class) +% +% +% Description +% +% GRIDobj creates an instance of the grid class, which contains a +% numerical or logical matrix and information on georeferencing. When a +% GRIDobj is created from a file, the number format of the data in +% GRIDobj is either single or double. Unsigned and signed integers are +% converted to single. For unsigned integers, missing values are +% assumed to be denoted as intmax(class(input)). For signed integers, +% missing values are assumed to be intmin(class(input)). Please check, +% that missing values in your data have been identified correctly +% before further analysis. +% +% Note that while throughout this help text GRIDobj is associated with +% gridded digital elevation models, instances of GRIDobj can contain +% other gridded, single band, datasets such as flow accumulation grids, +% gradient grids etc. +% +% DEM = GRIDobj(X,Y,dem) creates a DEM object from the coordnate +% matrices or vectors X and Y and the matrix dem. The elements of dem +% refer to the elevation of each pixel. +% +% DEM = GRIDobj('ESRIasciiGrid.txt') creates a DEM object from an ESRI +% Ascii grid exported from other GI systems. +% +% DEM = GRIDobj('GeoTiff.tif') creates a DEM object from a Geotiff. +% +% DEM = GRIDobj() opens a dialog box to read either an ESRI Ascii Grid +% or a Geotiff. +% +% DEM = GRIDobj([]) creates an empty instance of GRIDobj +% +% DEM = GRIDobj(FLOWobj or GRIDobj or STREAMobj,class) creates an +% instance of GRIDobj with all common properties (e.g., spatial +% referencing) inherited from another instance of a FLOWobj, GRIDobj +% or STREAMobj class. DEM.Z is set to all zeros where class can be +% integer classes or double or single. By default, class is double. +% +% Example +% +% % Load DEM +% DEM = GRIDobj('srtm_bigtujunga30m_utm11.tif'); +% % Display DEM +% imageschs(DEM) +% +% See also: FLOWobj, STREAMobj, GRIDobj/info +% +% Author: Wolfgang Schwanghart (w.schwanghart[at]geo.uni-potsdam.de) +% Date: 17. August, 2017 + + + properties + %Public properties + +%Z matrix with elevation values +% The Z property contains the elevation values in a 2D matrix. +% +% See also GRIDobj + Z + +%CELLSIZE cellsize of the grid (scalar) +% The cellsize property specifies the spacing of the grid in x and y +% directions. Note that TopoToolbox requires grids to have square cells, +% e.g., dx and dy are the same. +% +% See also GRIDobj + cellsize + +%REFMAT 3-by-2 affine transformation matrix +% The refmat property specifies a 3-by-2 affine transformation matrix as +% used by the mapping toolbox. +% +% See also GRIDobj, makerefmat + refmat + +%SIZE size of the grid (two element vector) +% The cellsize property is a two element vector that contains the number +% of rows and columns of the Z matrix. +% +% See also GRIDobj, size + size + +%NAME optional name (string) +% The name property allows to specify a name of the grid. By default and +% if the constructor is called with a filename, the name property is set +% to the name of the file. +% +% See also GRIDobj + name + +%ZUNIT unit of grid values (string) +% The zunit is optional and is used to store the physical unit (e.g. m) +% of an instance of GRIDobj. This property is currently not fully +% supported and TopoToolbox functions usually assume that the unit is in +% meters and equals the xyunit property. +% +% See also GRIDobj + zunit + +%XYUNIT unit of the coordinates (string) +% The xyunit is optional and is used to store the physical unit (e.g. m) +% of the coordinates. This property is currently not fully +% supported and TopoToolbox functions usually assume that the unit is in +% meters and equals the zunit property. +% +% See also GRIDobj + xyunit + +%GEOREF additional information on spatial referencing (structure array) +% The georef property stores an instance of map.rasterref.MapCellsReference, +% a structure array GeoKeyDirectoryTag, and a mapping structure +% (mstruct). +% +% See also GRIDobj, geotiffinfo + georef + + end + + methods + function DEM = GRIDobj(varargin) + % GRIDobj constructor + + if nargin == 3 + %% GRIDobj is created from three matrices + % GRIDobj(X,Y,dem) + X = varargin{1}; + Y = varargin{2}; + + if min(size(X)) > 1 + X = X(1,:); + end + if min(size(Y)) > 1 + Y = Y(:,1); + end + + DEM.Z = varargin{3}; + DEM.size = size(DEM.Z); + + if numel(X) ~= DEM.size(2) || numel(Y) ~= DEM.size(1) + error('TopoToolbox:GRIDobj',... + ['Coordinate matrices/vectors don''t fit the size of the \n'... + 'the grid']); + end + + if (Y(2)-Y(1)) > 0 + % the y coordinate vector must be monotonically + % decreasing, so that the left upper edge of the DEM is + % north-west (on the northern hemisphere). + DEM.Z = flipud(DEM.Z); + Y = Y(end:-1:1); + end + + dy = Y(2)-Y(1); + dx = X(2)-X(1); + + if abs(abs(dx)-abs(dy))>1e-9 + error('TopoToolbox:GRIDobj',... + 'The resolution in x- and y-direction must be the same'); + end + + + DEM.refmat = double([0 dy;... + dx 0;... + X(1)-dx Y(1)-dy]); + + DEM.cellsize = dx; + DEM.georef = []; + DEM.name = []; + + + elseif nargin <= 2 + + + if nargin == 0 + %% No input arguments. File dialog box will open and ask + % for a txt or tiff file as input + FilterSpec = {'*.txt;*.asc;*.tif;*.tiff','supported file types (*.txt,*.asc,*.tif,*.tiff)';... + '*.txt', 'ESRI ASCII grid (*.txt)';... + '*.asc', 'ESRI ASCII grid (*.asc)';... + '*.tif', 'GeoTiff (*.tif)';... + '*.tiff', 'GeoTiff (*.tiff)';... + '*.*', 'all files (*.*)'}; + + DialogTitle = 'Select ESRI ASCII grid or GeoTiff'; + [FileName,PathName] = uigetfile(FilterSpec,DialogTitle); + + if FileName == 0 + error('TopoToolbox:incorrectinput',... + 'no file was selected') + end + + filename = fullfile(PathName, FileName); + + elseif nargin > 0 + % One input argument + if isempty(varargin{1}) + % if empty array than return empty GRIDobj + return + elseif isa(varargin{1},'GRIDobj') || ... + isa(varargin{1},'FLOWobj') || ... + isa(varargin{1},'STREAMobj') + % empty GRIDobj + DEM = GRIDobj([]); + % find common properties of F and G and from F to G + pg = properties(DEM); + pf = properties(varargin{1}); + p = intersect(pg,pf); + for r = 1:numel(p) + DEM.(p{r}) = varargin{1}.(p{r}); + end + if nargin == 1 + cl = 'double'; + else + cl = varargin{2}; + end + + if strcmp(cl,'logical') + DEM.Z = false(DEM.size); + else + DEM.Z = zeros(DEM.size,cl); + end + DEM.name = ''; + + return + end + + % GRIDobj is created from a file + filename = varargin{1}; + end + + % check if file exists + if exist(filename,'file')~=2 + error('File doesn''t exist') + end + + + % separate filename into path, name and extension + [pathstr,DEM.name,ext] = fileparts(filename); + + + if any(strcmpi(ext,{'.tif', '.tiff'})) + % it is a GeoTiff + try + % try to read using geotiffread (requires mapping + % toolbox) + [DEM.Z, DEM.refmat, ~] = geotiffread(filename); + gtiffinfo = geotiffinfo(filename); + DEM.georef.SpatialRef = gtiffinfo.SpatialRef; + DEM.georef.GeoKeyDirectoryTag = gtiffinfo.GeoTIFFTags.GeoKeyDirectoryTag; + georef_enabled = true; + + catch ME + + % mapping toolbox is not available. Will try to + % read the tif file together with the tfw file + georef_enabled = false; + + % the tfw file has the same filename but a tfw + % extension + tfwfile = fullfile(pathstr,[DEM.name '.tfw']); + + % check whether file exists. If it exists then read + % it using worldfileread or own function + tfwfile_exists = exist(tfwfile,'file'); + if tfwfile_exists + try + % prefer builtin worldfileread, if + % available + DEM.refmat = worldfileread(tfwfile); + catch ME + W = dlmread(tfwfile); + + DEM.refmat(2,1) = W(1,1); + DEM.refmat(1,2) = W(4,1); + DEM.refmat(3,1) = W(5,1)-W(1); + DEM.refmat(3,2) = W(6,1)-W(4); + end + + DEM.Z = imread(filename); + else + if ~tfwfile_exists + error('TopoToolbox:GRIDobj:read',... + 'GRIDobj cannot read the TIF-file because it does not have a tfw-file.'); + else + throw(ME) + end + + end + end + + + % Unless any error occurred, we now attempt to generate + % an mapping projection structure. This will not work + % if the DEM is in a geographic coordinate system or if + % the projection is not supported by mstruct. + if georef_enabled + try + DEM.georef.mstruct = geotiff2mstruct(gtiffinfo); + catch + DEM.georef.mstruct = []; + warning('TopoToolbox:GRIDobj:projection',... + ['GRIDobj cannot derive a map projection structure. This is either\n' ... + 'because the grid is in a geographic coordinate system or because\n' ... + 'geotiff2mstruct cannot identify the projected coordinate system used.\n' ... + 'TopoToolbox assumes that horizontal and vertical units of DEMs are \n'... + 'the same. It is recommended to use a projected coordinate system,\n' ... + 'preferably UTM WGS84. Use the function GRIDobj/reproject2utm\n' ... + 'to reproject your grid.']) + end + end + + % Finally, check whether no_data tag is available. This tag is + % not accessible using geotiffinfo (nice hack by Simon + % Riedl) + tiffinfo = imfinfo(filename); + if isfield(tiffinfo,'GDAL_NODATA') + nodata_val = str2double(tiffinfo.GDAL_NODATA); + end + + else + [DEM.Z,R] = rasterread(filename); + DEM.refmat = R; + DEM.georef = []; + end + + DEM.size = size(DEM.Z); + DEM.cellsize = abs(DEM.refmat(2)); + + % remove nans + demclass = class(DEM.Z); + nodata_val_exists = exist('nodata_val','var'); + + switch demclass + case {'uint8','uint16','uint32'} + % unsigned integer + DEM.Z = single(DEM.Z); + + if nodata_val_exists + nodata_val = single(nodata_val); + DEM.Z(DEM.Z == nodata_val) = nan; + else + DEM.Z(DEM.Z==intmax(demclass)) = nan; + end + + case {'int8','int16','int32'} + % signed integer + DEM.Z = single(DEM.Z); + if nodata_val_exists + nodata_val = single(nodata_val); + DEM.Z(DEM.Z == nodata_val) = nan; + else + DEM.Z(DEM.Z==intmin(demclass)) = nan; + end + + case {'double','single'} + if nodata_val_exists + DEM.Z(DEM.Z == cast(nodata_val,class(DEM.Z))) = nan; + end + case 'logical' + otherwise + error('TopoToolbox:GRIDobj','unrecognized class') + end + + + end + end + + end + +end + + + + +% Subfunction for ASCII GRID import +function [Z,refmat] = rasterread(file) + +fid=fopen(file,'r'); +% loop through header + +header = struct('ncols',[],... + 'nrows',[],... + 'xllcorner',[],... + 'yllcorner',[],... + 'cellsize',[],... + 'nodata',[]); + +names = fieldnames(header); +nrnames = numel(names); + +try + fseek(fid,0,'bof'); + for r = 1:nrnames ; + headertext = fgetl(fid); + [headertext, headernum] = strtok(headertext,' '); + I = cellfun(@(x,y) strcmpi(x(1:4),y(1:4)),names,repmat({headertext},nrnames,1)); + header.(names{I}) = str2double(headernum); + end +catch ME1 + error('header can not be read') +end + + +% read raster data +Z = fscanf(fid,'%lg',[header.ncols header.nrows]); +fclose(fid); +Z(Z==header.nodata) = NaN; +Z = Z'; +% create X and Y using meshgrid +refmat = [0 -header.cellsize;... + header.cellsize 0;... + header.xllcorner+(0.5*header.cellsize) - header.cellsize ... + (header.yllcorner+(0.5*header.cellsize))+((header.nrows)*header.cellsize)]; + +end + + + + + + + + + \ No newline at end of file diff --git a/GRIDobj2geotiff.m b/GRIDobj2geotiff.m new file mode 100644 index 0000000..0087e99 --- /dev/null +++ b/GRIDobj2geotiff.m @@ -0,0 +1,120 @@ +function GRIDobj2geotiff(A,file) + +%GRIDobj2geotiff Exports an instance of GRIDobj to a geotiff file +% +% Syntax +% +% GRIDobj2geotiff(DEM) +% GRIDobj2geotiff(DEM,filename) +% +% Description +% +% GeoTIFF is a common image file format that stores coordinates and +% projection information to be read by most GIS software. +% GRIDobj2geotiff writes an instance of GRIDobj to a GeoTIFF file. +% +% GRIDobj2geotiff requires the function geotiffwrite available with +% the Mapping Toolbox. If geotiffwrite does not exist on the search +% path, the function will write a standard tif together with a +% '.tfw'-file (worldfile, http://en.wikipedia.org/wiki/World_file ) to +% the disk. +% +% GRIDobj2geotiff(DEM) opens a dialogue box to save the GeoTIFF +% +% GRIDobj2geotiff(DEM,filename) saves the DEM to the specified +% filename +% +% Input arguments +% +% DEM instance of GRIDobj +% filename absolute or relative path and filename +% +% See also: GRIDobj +% +% Author: Wolfgang Schwanghart (w.schwanghart[at]geo.uni-potsdam.de) +% Date: 17. August, 2017 + +narginchk(1,2) + +% if only 1 argument, open file dialog box +if nargin == 1 + [FileName,PathName] = uiputfile({'*.tif'}); + if FileName == 0 + disp(' no output written to disk') + return + end + file = [PathName FileName]; +end + +% try to use geotiffwrite, which comes with the Mapping Toolbox +try + if isempty(A.georef); + geotiffwrite(file,A.Z,A.refmat); + else + geotiffwrite(file,A.Z,A.georef.SpatialRef,... + 'GeoKeyDirectoryTag',A.georef.GeoKeyDirectoryTag); + end +catch ME + warning('TopoToolbox:GRIDobj',... + ['GRIDobj2geotiff is unable to write a geotiff. Either you don''t \n'... + 'have the mapping toolbox, or there was another issue with geotiffwrite. \n'... + 'GRIDobj2geotiff instead writes a tif-image together with a world \n'... + 'file (*.tfw) which contains data on spatial referencing of the \n' ... + 'image, yet which lacks information on the type of projection used.']); + + + % if geotiffwrite is not available or any other error occurs + % a tif file will be written to the disk together with a worldfile + % .tfw-file. + [pathstr, name, ~] = fileparts(file); + k = refmat2worldfile(A.refmat); + dlmwrite(fullfile(pathstr,[name '.tfw']),k,'precision', '%.10f'); + A = A.Z; + + + siz = size(A); + cla = class(A); + + switch cla; + case 'double' + BpS = 64; + TSF = Tiff.SampleFormat.IEEEFP; + case 'single' + BpS = 32; + TSF = Tiff.SampleFormat.IEEEFP; + otherwise + if islogical(A); + A = uint32(A); + cla = 'uint32'; + end + BpS = round(log2(double(intmax(cla)))); + TSF = Tiff.SampleFormat.UInt; + + end + + t = Tiff(file,'w'); + tagstruct.ImageLength = siz(1); + tagstruct.ImageWidth = siz(2); + tagstruct.BitsPerSample = BpS; + tagstruct.SampleFormat = TSF; + tagstruct.SamplesPerPixel = 1; + tagstruct.RowsPerStrip = 16; + tagstruct.PlanarConfiguration = Tiff.PlanarConfiguration.Chunky; + tagstruct.Software = 'MATLAB'; + tagstruct.Photometric = 0; + + t.setTag(tagstruct); + t.write(A); + t.close; +end + +end + +function k = refmat2worldfile(r) +% does not support rotation + +k(1,1) = r(2,1); +k(4,1) = r(1,2); +k(5,1) = r(3,1)+k(1); +k(6,1) = r(3,2)+k(4); +end \ No newline at end of file diff --git a/LoadAuxiData.m b/LoadAuxiData.m new file mode 100644 index 0000000..cb299de --- /dev/null +++ b/LoadAuxiData.m @@ -0,0 +1,535 @@ +function [dem_out,slope_out,aspect_out,water_occur] = LoadAuxiData(doc_path,fname,bbox,trgt,saveas_local,varargin) +%LOADAXUIDATA Read DEM, GSWO, slope, and aspect. +% +% Syntax +% +% [dem_out,slope_out,aspect_out,water_occur] = +% LoadAuxiData(doc_path,fname,bbox,trgt,saveas_local) +% +% Description +% +% Read DEM and GSWO, and calculate Slope and Aspect data. +% It will spend about 1 min to load, mosaic DEM and GSWO, and calculate +% Slope and Aspect. If users use the online DEM, the time will depend +% on the Internet speed. +% Create a chache folder at scene directory, that will be affected by +% each others (especailly in parelel) +% User can define a DEM path. (by Shi. Jun 14, 2018) +% +% Input arguments +% +% doc_path Path of images. +% Landsat: the directory where you save the Landsat scene. +% Sentinel 2: the directory reaching to +% '~/S2*/GRANULE/S2*/IMG_DATA/'. +% fname Image name. +% bbox Boundary of image. [north, south, west, east] +% trgt The GRIDobj used as a targart, which is useful when +% projecting and mosaicing the auxi data. +% saveas_local Locally save as the auxi data or not. +% true: saveas false:do not saveas. +% +% Output arguments +% +% dem_out DEM responding to the scene. +% slope_out Slope, which will be generated if unvailable. +% aspect_out Aspect, which will be generated if unvailable. +% water_occur GSWO responding to the scene. +% +% +% +% Author: Shi Qiu (shi.qiu@ttu.edu) +% Date: 22. September, 2017 + + + % instant paras +% dem_path=strcat(pwd,'/AuxiData/GTOPO3010degZIP/'); % cliped by each +% 10 degrees -by- 10 degrees. + p = inputParser; + p.FunctionName = 'AuxiParameters'; + addParameter(p,'parallel',0); + addParameter(p,'userdem',''); + % request user's input + parse(p,varargin{:}); + isParallel=p.Results.parallel; + + userdem = p.Results.userdem; % dem path + + if isdeployed % Stand-alone mode. +% m_path = ctfroot; + if ismac||isunix + % Code to run on Mac plaform + % Code to run on Linux plaform + [~, result] = system('echo $PATH'); + m_path = char(regexpi(result, '(.*?):', 'tokens', 'once')); + elseif ispc + % Code to run on PC plaform + [~, result] = system('path'); + m_path = char(regexpi(result, 'Path=(.*?);', 'tokens', 'once')); + clear result; + end + else % MATLAB mode. + m_path=mfilename('fullpath'); + m_path(end-12:end)=[]; % remove the '/LoadAuxiData' + end + water_path=fullfile(m_path,'AuxiData','GSWO150ZIP'); + dem_path=fullfile(m_path,'AuxiData','GTOPO30ZIP'); + clear m_path; + +% water_path=fullfile('.','AuxiData','GSWO150ZIP'); +% dem_path=fullfile('.','AuxiData','GTOPO30ZIP'); +% cache_path=fullfile(m_path,'Cache'); + cache_path=fullfile(doc_path,'Cache'); + + % see the Cache is available + if ~exist(cache_path,'dir') + %have no the directory, create it here. + mkdir(cache_path); +% % cannot make it, give the results here directly. +% if ~status_mkdir +% cache_path = doc_path; +% end + end + + % online + + %% DEM + % Step 1. Read DEM data at the directory of scene. + % filename_dem=dir([doc_path,fname,'_DEM.tif']); + % userdem + dem_gridobj=[]; + if ~isempty(userdem)&&~isempty(trgt) + % read user's DEM + userdem_gridobj = GRIDobj(userdem); + % resample to same extent. + dem_gridobj = reproject2utm(userdem_gridobj,trgt); + clear userdem_gridobj userdem; + end + % local _DEM.tif + if isempty (dem_gridobj) + filename_dem=dir(fullfile(doc_path,'L*_DEM.tif')); % upper + if ~isempty(filename_dem) + dem_gridobj = GRIDobj(fullfile(doc_path,filename_dem(1).name)); + else + filename_dem=dir(fullfile(doc_path,'L*_dem.tif')); % lower + if ~isempty(filename_dem) + dem_gridobj = GRIDobj(fullfile(doc_path,filename_dem(1).name)); + else + filename_dem=dir(fullfile(doc_path,'S*_DEM.tif')); % upper + if ~isempty(filename_dem) + dem_gridobj = GRIDobj(fullfile(doc_path,filename_dem(1).name)); + else + filename_dem=dir(fullfile(doc_path,'S*_dem.tif')); % lower + if ~isempty(filename_dem) + dem_gridobj = GRIDobj(fullfile(doc_path,filename_dem(1).name)); + else + dem_gridobj=[]; + end + end + end + end + end +% dem_gridobj=[]; + clear filename_dem; + % Step 2. Locally load GTOPO30 DEM (1km) or Download SRTM DEM data according to users' requirements. + if isempty (dem_gridobj)&&~isempty(trgt) + islocal=true; + if islocal + dem_data=MosaicLocalDEM(dem_path,cache_path,trgt,bbox); +% dem_data = MosaicLocalAuxiData('DEM',dem_path,trgt,bbox); + if ~isempty(dem_data)&&sum(sum(dem_data(:)))>0 % all 0 will be empty. + % sometimes there may be over water totally. + dem_gridobj = GRIDobj(trgt); + dem_gridobj.name=[fname,'_DEM']; + dem_gridobj.Z = dem_data; + +% tic +% while sum(sum(isnan(dem_gridobj.Z )))>0 +% dem_gridobj = inpaintnans(dem_gridobj,'fill');% imfill nan +% end +% toc + if saveas_local + GRIDobj2geotiff(dem_gridobj,fullfile(doc_path,[dem_gridobj.name,'.tif'])); + end +% fprintf('GTOPO30 (1km DEM), '); + else +% fprintf('non-DEM, '); + end + clear dem_data; +% fprintf('Local GTOPO30 (1km DEM) will be used.\n'); + else + demtype = 'SRTMGL3'; + dem_gridobj = LoadOnlineDEM(fname,demtype,bbox,trgt); + if ~isempty(dem_gridobj) + if saveas_local + dem_gridobj.name=[fname,'_DEM']; + GRIDobj2geotiff(dem_gridobj,fullfile(doc_path,[dem_gridobj.name,'.tif'])); + end +% fprintf('Online DEM (',demtype,'),\n'); + else +% fprintf('non-DEM (',demtype,'), '); + end + + end + elseif ~isempty (dem_gridobj) +% fprintf('DEM in the scene directory,\n'); +% fprintf('The DEM in the directory of scene will be used.\n'); + end + + if ~isempty(dem_gridobj) + dem_out=single(dem_gridobj.Z); + else + dem_out=[]; + end + + % Step 3. Calculate Slope. + slope_out = LoadAuxiItem(doc_path,fname,'SLOPE'); + if isempty (slope_out) + if ~isempty (dem_gridobj) + slope_gridobj=arcslope(dem_gridobj,'deg'); + slope_out=single(slope_gridobj.Z); + if saveas_local + slope_gridobj.name=[fname,'_SLOPE']; + GRIDobj2geotiff(slope_gridobj,fullfile(doc_path,[slope_gridobj.name,'.tif'])); + end + clear slope_gridobj; +% fprintf('SLOPE, '); +% clear slope_gridobj; + else +% fprintf('non-SLOPE, '); + end + else +% fprintf('SLOPE in the scene directory,\n'); + end + % Step 4. Calculate Aspect. + aspect_out = LoadAuxiItem(doc_path,fname,'ASPECT'); + if isempty (aspect_out) + if ~isempty (dem_gridobj) + aspect_gridobj = aspect(dem_gridobj); + aspect_out = single(aspect_gridobj.Z); + if saveas_local + aspect_gridobj.name=[fname,'_ASPECT']; + GRIDobj2geotiff(aspect_gridobj,fullfile(doc_path,[aspect_gridobj.name,'.tif'])); + end +% fprintf('ASPECT, '); + clear aspect_gridobj; + else +% fprintf('non-ASPECT, '); + end + else +% fprintf('ASPECT in the scene directory,\n'); + end + clear dem_gridobj; + + %% WATER + water_occur = LoadAuxiItem(doc_path,fname,'WATER'); + + if isempty (water_occur)&& ~isempty(trgt) + water_occur = MosaicLocalAuxiData('WATER',water_path,cache_path,trgt,bbox); + if ~isempty(water_occur) +% fprintf('and GSWO (150m water map).\n'); +% fprintf('Local GSWO (150m water map) will be used.\n'); + if saveas_local + wt_gridobj = GRIDobj(trgt); + wt_gridobj.name=[fname,'_WATER']; + wt_gridobj.Z = water_occur; + GRIDobj2geotiff(wt_gridobj,fullfile(doc_path,[wt_gridobj.name,'.tif'])); + clear wt_gridobj; + end + else +% fprintf('and non-GSWO.\n'); + end + else +% fprintf('and GSWO in the scene directory.\n'); + end + clear trgt; + if ~isempty (water_occur) + water_occur(water_occur==255)=100;% 255 is 100% ocean. + end + if ~isParallel + % remove the cache folder in the scene directory. + if exist(cache_path,'dir') + rmdir(cache_path,'s'); + end + end +end + + +function auxi_data = LoadAuxiItem(doc_path,fname,name_match) +% LoadAuxiItem read local auxi data first. + +% filename=dir([doc_path,'x*_',name_match,'.tif']); + filename=dir(fullfile(doc_path,[fname,'_',name_match,'.tif'])); + if ~isempty(filename) + auxi_data=imread(fullfile(doc_path,filename(1).name)); +% fprintf('Successfully load %s at %s.\n',name_match,fname); + else + auxi_data=[]; +% fprintf('Fail to locally find out %s at %s.\n',name_match,fname); + end +end + +function dem = LoadOnlineDEM(fname,demtype,bbox,trgt) + try +% fprintf('DEM is downloading.\n'); +% cache_path=fullfile(pwd,'Cache'); + tempfile_path=fullfile(cache_path,[fname, '_DEM.tif']); + dem_temp=Readopentopo('filename',tempfile_path,... + 'north',bbox(1),... + 'south',bbox(2),... + 'west',bbox(3),... + 'east',bbox(4),... + 'demtype',demtype,... + 'deletefile',true); + if ~isequal(dem_temp.size,trgt.size) % when DEM is NOT same as Landsat, CLIP it. + dem = reproject2utm(dem_temp,trgt,'method','nearest'); + else + dem = dem_temp; + end +% fprintf('DEM was successfully downloaded at %s.\n',fname); + catch + dem=[]; +% fprintf('DEM was unsuccessfully downloaded at %s.\n',fname); + end +end + +function auxi_data=MosaicLocalDEM(auxi_path,cache_path,target_gridobj,bbox) + latlim=[bbox(2) bbox(1)]; + lonlim=[bbox(3) bbox(4)]; + clear bbox; +% bbox=[north,south,west,east]; +% [southern_limit northern_limit] +% [western_limit eastern_limit] + + tileNames = gtopo30s(latlim,lonlim); + clear latlim lonlim; + if isempty(tileNames) % no tiles, return empty. + auxi_data=[]; % set empty layer. + return; + end +% auxi_data = zeros(target_gridobj.size,'double'); % dem mask + auxi_data = NaN(target_gridobj.size,'double'); % dem mask +% mask_dem = createmask(target_gridobj); + + for i=1:length(tileNames) + tile_name=['gt30',lower(char(tileNames(i)))]; + if ~isempty(tile_name) + % unzip files. + filename = fullfile(auxi_path,[tile_name,'.zip']); + if exist(filename,'file') + unzip(filename,cache_path); + filename = fullfile(cache_path,[tile_name,'.tif']); + % reproject to UTM same with target image. + auxi_data_part_tmp = GRIDobj(filename); + clear filename; + + % In the gtopo30 DEM, ocean areas have been masked as "no data" +% ids_nan=auxi_data_part_tmp.Z==-9999; +% auxi_data_part_tmp.Z(ids_nan)=0;% give 0 to ocean. + % further give 0 to nan. + auxi_data_part_tmp.Z=fillmissing(auxi_data_part_tmp.Z,'constant',0); + +% % % recontruct the refmat to 3-by-2 matrix because of the orginal RGB. +% % LON11=min(auxi_data_part_tmp.georef.SpatialRef.LongitudeLimits); +% % LAT11=max(auxi_data_part_tmp.georef.SpatialRef.LatitudeLimits); +% % % Convert to a geographic raster reference object: +% % DLON=auxi_data_part_tmp.cellsize; +% % DLAT=0-auxi_data_part_tmp.cellsize; +% % auxi_data_part_tmp.refmat = makerefmat(LON11, LAT11, DLON, DLAT); + auxi_data_tmp = reproject2utm(auxi_data_part_tmp,target_gridobj); + clear auxi_data_part_tmp; + auxi_data=max(auxi_data,auxi_data_tmp.Z); + clear auxi_data_tmp; +% % delete(filename); % delete the files + end + clear tile_name; + end + end + clear tileNames; + % if using more than 2 tiles, the mosaic method maybe result in some + % Nan values at lines. fast fill then. + if sum(sum(isnan(auxi_data)))>0 + % see along-row or along-col + [row_nan,col_nan]=find(isnan(auxi_data)); + row_diff=max(row_nan)-min(row_nan); + col_diff=max(col_nan)-min(col_nan); + % along row and col. + if row_diff>col_diff + auxi_data=fillmissing(auxi_data,'linear',2);% by row. + else + auxi_data=fillmissing(auxi_data,'linear',1);% by column. + end + % https://www.mathworks.com/help/matlab/ref/fillmissing.html?searchHighlight=fillmissing&s_tid=doc_srchtitle + end + % when have nan values in DEM,this will be ocean. all as 0. + auxi_data=fillmissing(auxi_data,'constant',0); +end + +function auxi_data = MosaicLocalAuxiData(auxi_type,auxi_path,cache_path,target_gridobj,bbox) + + switch auxi_type + case 'DEM' + pname_1th='gt30'; + case 'WATER' + pname_1th='occurrence'; + otherwise + error('The input of auxiliary type is incorrect, which should be ''DEM'' or ''WATER''.'); + end + + % target_image + % get the up-left cornner's name for water title + [tiles_used, ul_name, ur_name, ll_name, lr_name] = ConvertBBox2TileName(bbox,pname_1th); + clear bbox pname_1th; + if sum(tiles_used(:))==0 % no tiles, return empty. + auxi_data=[]; % set empty water layer. + return; + end + + auxi_data = zeros(target_gridobj.size,'double'); % water mask + % loop all water titles covering the Landsat or Sentinel image. + for i=1:4 + tile_name=''; + switch i + case 1 + if tiles_used(i) + tile_name=ul_name; + end + case 2 + if tiles_used(i) + tile_name=ur_name; + end + case 3 + if tiles_used(i) + tile_name=ll_name; + end + case 4 + if tiles_used(i) + tile_name=lr_name; + end + end + if ~isempty(tile_name) + % unzip files. + filename = fullfile(auxi_path,[tile_name,'.zip']); +% cache_path=fullfile(pwd,'Cache'); + if exist(filename,'file') + unzip(filename,cache_path); + filename = fullfile(cache_path,[tile_name,'.tif']); +% filename = [water_path,tile_name,'.tif']; + % reproject to UTM same with target image. + auxi_data_part_tmp = GRIDobj(filename); + clear filename; + % recontruct the refmat to 3-by-2 matrix because of the orginal RGB. + +% LON11=min(auxi_data_part_tmp.georef.SpatialRef.LongitudeLimits); +% LAT11=max(auxi_data_part_tmp.georef.SpatialRef.LatitudeLimits); +% % Convert to a geographic raster reference object: +% % rasterSize = auxi_data_part_tmp.size; +% DLON=auxi_data_part_tmp.cellsize; +% DLAT=0-auxi_data_part_tmp.cellsize; +% % cell_resol=10/rasterSize(1); +% % DLON=cell_resol; +% % DLAT=0-cell_resol; +% auxi_data_part_tmp.refmat = makerefmat(LON11, LAT11, DLON, DLAT); + + auxi_data_part_tmp.Z=fillmissing(auxi_data_part_tmp.Z,'constant',255);% 255 or No data indicates ocean area. +% auxi_data_part_tmp.Z(isnan(auxi_data_part_tmp.Z))=255;% 255 or No data indicates ocean area. + auxi_data_tmp = reproject2utm(auxi_data_part_tmp,target_gridobj); + clear auxi_data_part_tmp; +% auxi_data_part_tmp.Z=fillmissing(auxi_data_part_tmp.Z,'constant',0);% no data indicates no sure whether there is water. + auxi_data=max(auxi_data,auxi_data_tmp.Z); + clear auxi_data_tmp; +% delete(filename); % delete the files + else + tiles_used(i)=0;% no data file! + end + end + end + % see it has or not again + if sum(tiles_used(:))==0 % no tiles, return empty. + auxi_data=[]; % set empty. + return; + end + + if sum(tiles_used(:))>1% masic will lead to none data. + auxi_data = medfilt2(auxi_data); % filter it. + end + + clear tiles_used; + + switch auxi_type + case 'DEM' + auxi_data=int16(auxi_data); % convert to intergr with 16 bit for DEM, can be negative and positive + case 'WATER' + auxi_data=uint8(auxi_data); % convert to intergr with 8 bit for water, only be positive. + end + clear auxi_type; +end + +function [tiles_used, ul_name, ur_name, ll_name, lr_name] = ConvertBBox2TileName(bbox,pname_1th) + tiles_used=zeros(1,4); +% bbox=[north,south,west,east]; +% e.g., 21.2993 19.1468 77.7836 80.0166 +% 30N 19N 70E 90E + size_tile=10;% unit: degree. +% bbox=[21.2993 19.1468 77.7836 80.0166]; + % to negative infinity + bbox_floor=size_tile.*floor(bbox./size_tile); %e.g., floor: floor(-1.3)=-2; floor(1.3)=1; + % 20 10 70 80 + % to positive infinity + bbox_ceil=size_tile.*ceil(bbox./size_tile); %e.g., ceil: ceil(-1.3)=-1; ceil(1.3)=2; + clear size_tile bbox; + % 30 20 80 90 + +% % % four corners for each image. +% % ul_loc=[bbox(1) bbox(3)]; +% % ur_loc=[bbox(1) bbox(4)]; +% % ll_loc=[bbox(2) bbox(3)]; +% % lr_loc=[bbox(2) bbox(4)]; + % find the Granule's top-left corner. + ul_loc=[bbox_ceil(1) bbox_floor(3)]; + ur_loc=[bbox_ceil(1) bbox_floor(4)]; + ll_loc=[bbox_ceil(2) bbox_floor(3)]; + lr_loc=[bbox_ceil(2) bbox_floor(4)]; + clear bbox_ceil bbox_floor; +% pname_1th='occurrence'; + ul_pname_2th=convertNumel2Char(ul_loc); + ur_pname_2th=convertNumel2Char(ur_loc); + ll_pname_2th=convertNumel2Char(ll_loc); + lr_pname_2th=convertNumel2Char(lr_loc); + clear ul_loc ur_loc ll_loc lr_loc; + + + ul_name=[pname_1th,ul_pname_2th]; + ur_name=''; + ll_name=''; + lr_name=''; + % remove repeat tiles. + tiles_used(1)=1; % always have 1 or more than 1 tile. + + if ~isequal(ul_pname_2th,ur_pname_2th) + ur_name=[pname_1th,ur_pname_2th]; + tiles_used(2)=1; + end + if ~isequal(ul_pname_2th,ll_pname_2th)&&~isequal(ur_pname_2th,ll_pname_2th) + ll_name=[pname_1th,ll_pname_2th]; + tiles_used(3)=1; + end + if ~isequal(ul_pname_2th,lr_pname_2th)&&~isequal(ur_pname_2th,lr_pname_2th)&&~isequal(ll_pname_2th,lr_pname_2th) + lr_name=[pname_1th,lr_pname_2th]; + tiles_used(4)=1; + end +end + +function pname_2th=convertNumel2Char(corner) + pname_2th='_'; + if corner(2)>=0 + pname_2th=[pname_2th,num2str(abs(corner(2))),'E_']; + else + pname_2th=[pname_2th,num2str(abs(corner(2))),'W_']; + end + if corner(1)>=0 + pname_2th=[pname_2th,num2str(abs(corner(1))),'N']; + else + pname_2th=[pname_2th,num2str(abs(corner(1))),'S']; + end +end diff --git a/LoadData.m b/LoadData.m new file mode 100644 index 0000000..e84f11c --- /dev/null +++ b/LoadData.m @@ -0,0 +1,152 @@ +function [data_meta,data_toabt,angles_view,trgt] = LoadData(path_data,sensor,InputFile,main_meta) +%LOADDATA Read all used bands and metadata for Landsats 4-8 and Sentinel 2. +% +% Syntax +% +% [data_meta,data_toabt,trgt] = LoadData(path_data) +% +% Description +% +% For Landsat Level-1 images, Digital Number (DN) values are converted +% to TOA reflectances and BT (Celsius degree) by using the LEDAPS +% atmosphere correction tool (Masek et al., 2006). This function is +% derived from Fmask 3.3 for Landsat. +% For Sentinel 2 Level-1C images, TOA reflectances are provided with a +% spatial resolution of 10, 20 and 60 meters of the different spectral +% bands, that will be resampled into 20 meters at Fmask 4.0 rountine. +% This read function is derived from Fmask 3.3 for Sentinel 2. +% +% History +% +% 1. Create this function. (17. May, 2017) +% +% Input arguments +% +% path_data Path of images. +% Landsat: the directory where you save the Landsat scene. +% Sentinel 2: the directory reaching to '~/S2*/GRANULE/S2*/IMG_DATA/' +% +% Output arguments +% +% data_meta Metadata +% data_toabt TOA reflectances (x10000) and BT(x100 Celsius degree) data. +% trgt The GRIDobj used as a targart, which is useful when +% projecting and mosaicing the auxi data. +% +% See also: nd2toarbt nd2toarbt_msi +% +% +% Author: Shi Qiu (shi.qiu@ttu.edu) +% Date: 17. May, 2017 + + trgt=[]; + angles_view=[]; + if strcmp(sensor , 'S_MSI' ) + +% fprintf('Load TOA reflectances from the Level-1C product.\n'); + % Load data for Sentinel 2 + [~,data,trgt,dim,bbox,ul,zen,azi,zc,Angles,satu_B1,satu_B2,satu_B3,resolu]=nd2toarbt_msi(InputFile); + %% Input data class defination + data_meta=ObjMeta; + data_toabt=ObjTOABT; + %% Parameters + + norln=strread(main_meta.name,'%s','delimiter','.'); + n_name=char(norln(1)); + data_meta.Name=n_name; + data_meta.Sensor=sensor; + data_meta.Dim=dim; + data_meta.UL=ul; + data_meta.Zen=zen; + data_meta.Azi=azi; + data_meta.ZC=zc; + data_meta.Resolution=resolu; + data_meta.BBox=bbox; + + fmask_dir='FMASK_DATA'; + % see the FMASK_DATA is available + if ~exist(fullfile(InputFile.pathh,fmask_dir),'dir') + %have no the Fmask directory, create it here. + status_mkdir = mkdir(fullfile(InputFile.pathh,fmask_dir)); + % cannot make it, give the results here directly. + if ~status_mkdir + fmask_dir=''; + end + end + data_meta.Output=fmask_dir; + clear fmask_dir; + + %% TOA + data_toabt.BandBlue=data(:,:,1); + data_toabt.BandGreen=data(:,:,2); + data_toabt.BandRed=data(:,:,3); + data_toabt.BandNIR=data(:,:,4); % band 8A + data_toabt.BandSWIR1=data(:,:,5); + data_toabt.BandSWIR2=data(:,:,6); + data_toabt.BandCirrus=data(:,:,7); + + data_toabt.BandVRE3 = data(:,:,8); % band 07 + data_toabt.BandNIR8 = data(:,:,9); % band 08 + + %% View angles + angles_view=Angles; + + %% Saturated at visible bands + data_toabt.SatuBlue=satu_B1; + data_toabt.SatuGreen=satu_B2; + data_toabt.SatuRed=satu_B3; + else +% fprintf('Calculate TOA reflectances and BT from the Collection 1 product.\n'); + % Load data for Landsat 4-8 + [Temp,data,trgt,dim,ul,bbox,zen,azi,zc,satu_B1,satu_B2,satu_B3,resolu]=nd2toarbt(path_data,main_meta.name); + + %% Input data class defination + data_meta=ObjMeta; + data_toabt=ObjTOABT; + %% Parameters + + % reedit dir_im + norln=strread(main_meta.name,'%s','delimiter','.'); + n_name=char(norln(1)); + data_meta.Name=n_name(1:end-4); + data_meta.Sensor=sensor; + data_meta.Dim=dim; + data_meta.UL=ul; + data_meta.Zen=zen; + data_meta.Azi=azi; + data_meta.ZC=zc; + data_meta.Resolution=resolu; + data_meta.BBox=bbox; + data_meta.Output=''; + + %% TOA + if strcmp(sensor,'L_ETM_PLUS')||strcmp(sensor,'L_TM') + data_toabt.BandBlue=data(:,:,1); + data_toabt.BandGreen=data(:,:,2); + data_toabt.BandRed=data(:,:,3); + data_toabt.BandNIR=data(:,:,4); + data_toabt.BandSWIR1=data(:,:,5); + data_toabt.BandSWIR2=data(:,:,6); + else + if strcmp(sensor,'L_OLI_TIRS') + data_toabt.BandBlue=data(:,:,1); + data_toabt.BandGreen=data(:,:,2); + data_toabt.BandRed=data(:,:,3); + data_toabt.BandNIR=data(:,:,4); + data_toabt.BandSWIR1=data(:,:,5); + data_toabt.BandSWIR2=data(:,:,6); + data_toabt.BandCirrus=data(:,:,7); + end + end + + %% BT + +% fprintf('BT, '); + data_toabt.BandBT=Temp; + %% Saturated at visible bands + data_toabt.SatuBlue=satu_B1; + data_toabt.SatuGreen=satu_B2; + data_toabt.SatuRed=satu_B3; + end +end + diff --git a/LoadSensorType.m b/LoadSensorType.m new file mode 100644 index 0000000..276b6f7 --- /dev/null +++ b/LoadSensorType.m @@ -0,0 +1,73 @@ +function [sensor,num_Lst,InputFile,main_meta] = LoadSensorType(path_data) +%LOADSENSORTYPE Basic metadata should be loaded first to see which sensor +%here. + +%% Search metadate file for Landsat 4-8 + main_meta=dir(fullfile(path_data,'L*MTL.txt')); + existMTL=size(main_meta); + InputFile=[]; + if existMTL(1)==0 + main_meta=dir(fullfile(path_data, 'S*MTD*TL*.xml')); + if isempty(main_meta) + main_meta=dir(fullfile(path_data, 'MTD*TL*.xml')); + end + if ~isempty(main_meta) + txtstart = strfind(path_data,'S2A') ; + num_Lst='2A'; + if isempty(txtstart) + txtstart = strfind(path_data,'S2B') ; % S2A or S2B by Shi 10/18/2017 + num_Lst='2B'; + end + txtend = strfind(path_data,'.SAFE')-1 ; + InputFile.DataStrip = path_data(txtstart:txtend) ; + + InputFile.Dmain = path_data(1:txtstart-1) ; + + txtstart = strfind(path_data,'GRANULE')+8 ; + + InputFile.InspireXML=path_data(1:txtstart-10); % extra add to read inspire xml. + + txtend = length(path_data); + InputFile.Granule = path_data(txtstart:txtend) ; + InputFile.pathh = path_data ; + main_meta.name=InputFile.Granule;% return file name fro Fmask results. + else +% fprintf('No available data in the current folder!\n'); + sensor=[]; % no supported image. + num_Lst=[]; + InputFile=[]; + main_meta=[]; + return; + end + else + % determine sensor type + % open and read hdr file + fid_in=fopen(fullfile(path_data,main_meta.name),'r'); + geo_char=fscanf(fid_in,'%c',inf); + fclose(fid_in); + geo_char=geo_char'; + geo_str=strread(geo_char,'%s'); + + % Identify Landsat Number (Lnum = 4, 5, 7, or 8) + LID=char(geo_str(strmatch('SPACECRAFT_ID',geo_str)+2)); +% num_Lst=str2double(LID(end-1)); + num_Lst=(LID(end-1)); + end + % define Landsat sensor. + sensor=''; + if strcmp(num_Lst,'8') + sensor='L_OLI_TIRS'; + else + if strcmp(num_Lst,'4')||strcmp(num_Lst,'5')||strcmp(num_Lst,'6') + sensor='L_TM'; + else + if strcmp(num_Lst,'7') + sensor='L_ETM_PLUS'; + end + end + end + if strcmp(num_Lst,'2A')||strcmp(num_Lst,'2B') + sensor='S_MSI'; + end +end + diff --git a/MatchCloudShadow.m b/MatchCloudShadow.m new file mode 100644 index 0000000..cc9c624 --- /dev/null +++ b/MatchCloudShadow.m @@ -0,0 +1,582 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% This functin is used to match cloud shadow with cloud. +% The similarity defineing cloud shadow is larger than 0.3 only for those +% clouds, of which all pixels are included in the Landsat observations. +% This minor modification was made because the match similarity may be +% wrong when some parts of cloud are out of the observations. +% +% +% fix the bug that cloud shadow would be projected on the other side in Sentinel-2 imagery when the azimuth angle > 180. By Shi, at 19, Jan., 2019 +% use new match similarity becasue we do not know the potential clouds +% excluding self cloud and outsides are shadow or not. by Shi, at 21, April, 2018 +% remove the overlap between final matched cloud shadow and the potential +% cloud shadow. by Shi, at 26, Mar., 2018. +% speed up the match of cloud shadow with cloud for large clouds using +% sampling projections. by Zhe and Shi. at 24, Mar., 2018. +% do not revisit for the big clouds (more than 10,000,000). by Shi. at 22, Mar., 2018 +% match cloud shadow by following the sort from the center because the +% clouds loacted boundary will be easily affected due to the unkown of the +% pixels out of obervations. by Shi. at 15, Mar., 2018 +% cloud's temperature may be warmer than surface when we wrongly give some +% surface to the cloud. This will result in no cloud shadow. by Shi. at 3, Mar., 2018 +% fix the bugger that revisit clouds when less than 14 cloud objects. by Shi. at 11, Dec., 2017 +% still improve the prediction of cloud shadow location when no DEMs. by Shi. at 13, Sept., 2017 +% revisit the first 14 cloud objects. by Shi. at 21,Feb.,2017 +% fix the bugger, struct2table for lt. struct2table. at 21,Feb.,2017 +% search neighboring clouds by distance rule. by Shi. at 13,Feb.,2017 +% fix the bugger that bias for the location of real cloud object. at 13,Oct.,2016 +% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +function [ similar_num,data_cloud_matched, data_shadow_matched] = MatchCloudShadow(... + mask,plcim,plsim,pfpl,water,data_dem,data_bt_c,t_templ,t_temph,data_meta,ptm,num_near,angles_view) + + dim=data_meta.Dim; + % get potential mask values + data_shadow_potential=zeros(dim,'uint8'); + data_cloud_potential=(plcim>0)&mask==1; + data_shadow_potential(plsim==1)=1;% plshadow layer + clear plsim; % empty memory + % matched cloud & shadow layer + data_cloud_matched=zeros(dim,'uint8'); + data_shadow_matched=zeros(dim,'double'); + % revised percent of cloud on the scene after plcloud + revised_ptm=sum(data_cloud_potential(:))/sum(mask(:)); + % When having too many clouds, we will not match cloud shadow. + if ptm <=40000||revised_ptm>=0.90 % 0.1% was changed to 40,000 pixels. + fprintf('Skip cloud shadow detection because high cloud cover\n'); + data_cloud_matched(data_cloud_potential==true)=1; + data_shadow_matched(data_shadow_potential==false)=1; + data_shadow_matched=uint8(data_shadow_matched); + similar_num=-1; + else + + clear pfpl; + %% parameters + clear plcim; % empty memory + % max similarity threshold + max_similar = 0.95; + % number of inward pixes (240m) for cloud base temperature + num_pix=3; + + % enviromental lapse rate 6.5 degrees/km + rate_elapse=6.5; + % dry adiabatic lapse rate 9.8 degrees/km + rate_dlapse=9.8; + + % max match pixels number + max_match_num =1000000; %more than 1 million pixels will result in ~2 mins runtime. + + % sun angles + sun_zenith_deg=data_meta.Zen; + sun_azimuth_deg=data_meta.Azi; + % sun angle geometry + sun_elevation_deg=90-sun_zenith_deg; + sun_elevation_rad=deg2rad(sun_elevation_deg); + % solar azimuth anngle + Sun_tazi=sun_azimuth_deg-90; + sun_tazi_rad=deg2rad(Sun_tazi); + clear sun_elevation_deg sun_elevation_deg; + + % view angles for Sentinel 2 images, which will be used compute the + % average values for each cloud. Note that the Landsat's view angles + % can be estimated by the obersations of the entire scene. + if strcmp(data_meta.Sensor,'S_MSI') + VAA = angles_view.VAA; + VZA = angles_view.VZA ; + clear angles_view; + % mini matched similarity + Tsimilar=0.425; +% Tsimilar=0.4; + % threshold for matching buffering + Tbuffer=0.90; + elseif strcmp(data_meta.Sensor,'L_OLI_TIRS')||... + strcmp(data_meta.Sensor,'L_ETM_PLUS')||... + strcmp(data_meta.Sensor,'L_TM') + % mini matched similarity +% Tsimilar=0.35; + Tsimilar=0.3; + % threshold for matching buffering + Tbuffer=0.95; + + % view angle geometry for Landsat + % get track derection + [rows,cols]=find(mask==1); + [cor_ul_y,num]=min(rows);cor_ul_x=cols(num); + [cor_lr_y,num]=max(rows);cor_lr_x=cols(num); + [cor_ll_x,num]=min(cols);cor_ll_y=rows(num); + [cor_ur_x,num]=max(cols);cor_ur_y=rows(num); + % get view angle geometry + [A,B,C,omiga_par,omiga_per]=getSensorViewGeo(cor_ul_x,cor_ul_y,cor_ur_x,cor_ur_y,cor_ll_x,cor_ll_y,cor_lr_x,cor_lr_y); + clear cor_ul_x cor_ul_y cor_ur_x cor_ur_y cor_ll_x cor_ll_y cor_lr_x cor_lr_y; + else + error('Only Landsats 4-7, Landsat 8 and Sentinel 2 data can be supported./n'); + end + + % the lowest elevation. + if ~isempty(data_dem) + dem_base_heigh=double(prctile(data_dem(mask),0.001)); + else + dem_base_heigh=0; + end + % expand 1,000 pixels for the potential cloud shadow layer. + dim_expd=2000; + + % spatial resolution of the image + sub_size=data_meta.Resolution(1); + win_height=dim(1);win_width=dim(2); + % intervals within each matching process. + step_interval=2*sub_size*tan(sun_elevation_rad); + + %% project all potential cloud shadow and cloud (can be matched) along sun light based on DEMs. + % cloud shadow may be overlap with another cloud, so we need to + % project the all potential cloud and potential cloud shadow pixels. + [recorderRow,recorderCol] = ProjectDEM2Plane(dim,... + mask,... + data_dem,dem_base_heigh,sun_elevation_rad,sun_tazi_rad,... + sun_azimuth_deg,dim_expd,... + data_meta.Resolution); + + % create cloud objtects using 8-by-8 pixels connection. + [segm_cloud,num]=bwlabeln(data_cloud_potential,8); + s = regionprops(segm_cloud,'area'); + area_final = [s.Area]; + obj_num=area_final; + clear segm_cloud_init L idx area_final s; + + % Get the x,y of each cloud + % Matrix used in recording the x,y + stats= regionprops(segm_cloud,'Centroid','PixelList'); + + + match_clds=zeros(1,num,'uint8'); % cloud shadow match similarity (m) + matched_clds_centroid=[]; % centers of cloud having shadow + height_clds_recorder=[]; % cloud shadow match heights (m) + % Use iteration to get the optimal move distance + % Calulate the moving cloud shadow + similar_num=zeros(1,num); % cloud shadow match similarity (m) + l_pt=0.175; h_pt=1-l_pt; + dim_expand=dim+2*dim_expd; + record_base_h_num=0; + num_revisited = 0; + if num > num_near + num_revisited=num_near; + end + num_all=num+num_revisited; + + % min moving distance (min high 200 meters) unit: pixels + i_xy_min=200/(sub_size*tan(sun_elevation_rad)); + + for cloud_type_cur= 1:num_all %num + % revisit the first 14 cloud objects. + cloud_type=cloud_type_cur; + if cloud_type>num && num_revisitedt_obj)=t_obj; + if ~(t_templ max_match_num + % renew the arrays + % randomly selection. + samples_rand_all=randperm(obj_num(cloud_type)); + samples_mov=samples_rand_all(1:max_match_num); + clear samples_rand_all; + % moving cloud xys + XY_type=XY_type_all(samples_mov,:); + % corrected for view angle xys + tmp_xys=tmp_xys_all(samples_mov,:); + % record this orinal ids + orin_xys = orin_xys_all(samples_mov,:); +% orin_xys = orin_xys_all(samples_mov,1); + if ~isempty(data_bt_c) % if have no temperature data. + % Temperature of the cloud object + temp_obj=temp_obj_all(samples_mov); + end + + else + % give all pixels + % moving cloud xys + XY_type=XY_type_all; + % corrected for view angle xys + tmp_xys=tmp_xys_all; + % record the original xys + orin_xys=orin_xys_all; + % record this orinal ids +% orin_cid=orin_cid_all; + + if ~isempty(data_bt_c) % if have no temperature data. + % temperature + temp_obj=temp_obj_all; + end + end + +% record_h=0; + record_thresh=0; + record_base_h=0; + record_base_h_near=0;% it is available only when >0 + center_cur=stats(cloud_type,:).Centroid; + + if strcmp(data_meta.Sensor,'S_MSI') + VZAxy = pi/180*mean(single(VZA(orin_cid_all))/100); + VAAxy = pi/180*mean(single(VAA(orin_cid_all))/100); + end + + % height estimated by neighboring clouds. + if record_base_h_num>=num_near + % the centers of already matched clouds + % current cloud's center + % the nearest cloud among all matched clouds. + + % remove the self cloud heigh + [nearest_cloud_centers,nearest_dis]=knnsearch(matched_clds_centroid,center_cur,'k',num_near, 'distance','cityblock');% less time-consuming method chebychev + if cloud_type_cur>num % remove its self for the first 14 clouds when coming back. + nearest_cloud_centers(nearest_dis==0)=[]; + end + + % get all matched clouds' height. + record_base_h_tmp=height_clds_recorder(nearest_cloud_centers); + record_base_h_near=prctile(record_base_h_tmp,100*h_pt); + h_pre_std=std(record_base_h_tmp); + clear record_base_h_tmp; + if h_pre_std>=1000||record_base_h_near <= Min_cl_height||record_base_h_near >= Max_cl_height + record_base_h_near=0; + end + clear h_pre_std; + end + + dist_pre=0; + dist_passed=false; + dist_first=true; + % all pixels of projected cloud object + if numel(orin_cid_all(:))==0 + dist_passed=true; + else + cpc_i=center_cur(2); + cpc_j=center_cur(1); + end + + % indicates the number of the matched cloud shadows for this + % current cloud. + num_matched=0; + + for base_h=Min_cl_height:step_interval:Max_cl_height % iterate in height (m) + % Get the true postion of the cloud + % calculate cloud DEM with initial base height + if strcmp(data_meta.Sensor,'S_MSI') + h=base_h; % have no temperature data. cannot serve as 3D object. + elseif strcmp(data_meta.Sensor,'L_OLI_TIRS')||... + strcmp(data_meta.Sensor,'L_ETM_PLUS')||... + strcmp(data_meta.Sensor,'L_TM') + h=double(10*(t_obj-temp_obj)/rate_elapse+base_h);% relative to the reference plane. Cloud top's height. + end + + % the height for the bias of the real cloud location should exclude the + % surface elevation below the cloud object. + h_bias=h-base_heigh_cloud;% hc-Ec the height between cloud object and its surface. + if strcmp(data_meta.Sensor,'S_MSI') + [tmp_xys(:,1),tmp_xys(:,2)]= getRealCloudPositionS2(orin_xys(:,1),... + orin_xys(:,2),h_bias,VZAxy,VAAxy,data_meta.Resolution); + elseif strcmp(data_meta.Sensor,'L_OLI_TIRS')||... + strcmp(data_meta.Sensor,'L_ETM_PLUS')||... + strcmp(data_meta.Sensor,'L_TM') + sensor_heigh_bias=base_heigh_cloud+dem_base_heigh; % used to exclude the elevation of cloud' surface. + [tmp_xys(:,1),tmp_xys(:,2)]=getRealCloudPosition(orin_xys(:,1),... + orin_xys(:,2),h_bias,A,B,C,omiga_par,omiga_per,sensor_heigh_bias); + else + error('Only Landsats 4-7, Landsat 8 and Sentinel 2 data can be supported./n'); + end + + % shadow moved distance (pixel) to calculate the cloud + % shadow locaiton. + % real cloud height relative to the plane. + i_xy=h/(sub_size*tan(sun_elevation_rad)); + XY_type(:,2)=round(tmp_xys(:,1)-i_xy*cos(sun_tazi_rad)); % X is for j,2 + XY_type(:,1)=round(tmp_xys(:,2)-i_xy*sin(sun_tazi_rad)); % Y is for i,1 + + clear i_xy; + % this location is relative to reference plane. + tmp_j_plane=XY_type(:,2);% col + tmp_i_plane=XY_type(:,1);% row + clear XY_type; + % back project +% dim_expd=1000;% 1000 pixels buffer + % some projected pixels out of observations. + tmp_i_plane_expd_tmp=tmp_i_plane+dim_expd; + tmp_j_plane_expd_tmp=tmp_j_plane+dim_expd; + + avail_pixels=find(tmp_i_plane_expd_tmp>0&tmp_j_plane_expd_tmp>0&... + tmp_i_plane_expd_tmp<=dim_expand(1)&tmp_j_plane_expd_tmp<=dim_expand(2)); + + tmp_i_plane_expd=tmp_i_plane_expd_tmp(avail_pixels); + tmp_j_plane_expd=tmp_j_plane_expd_tmp(avail_pixels); + clear tmp_i_plane_expd_tmp tmp_j_plane_expd_tmp avail_pixels; + tmp_id_plane_expd=sub2ind(dim_expand,tmp_i_plane_expd,tmp_j_plane_expd); % matched shadow locations + clear tmp_i_plane_expd tmp_j_plane_expd; + + % search the responding locations in real surface (derived + % from the relation-lookup table). + tmp_i_obs=recorderRow(tmp_id_plane_expd); + tmp_j_obs=recorderCol(tmp_id_plane_expd); + clear tmp_id_plane_expd tmp_id_plane_expd; + + % cloud shadow must be beyond the location of the orgianl cloud. + if ~dist_passed + % the center of cloud shadow in real image. + sum_cpmp_i=sum(tmp_i_obs(:)); + sum_cpmp_j=sum(tmp_j_obs(:)); + area_cpmp=numel(tmp_j_obs(:)); + ctmp_i=sum_cpmp_i/area_cpmp; + ctmp_j=sum_cpmp_j/area_cpmp; + clear sum_cpmp_i sum_cpmp_j area_cpmp; + + % distance between orginal cloud and its cloud shadow, + % Note we ignored the mini bias from view angles here. + dist_cur = pdist2([ctmp_j,ctmp_i],[cpc_j,cpc_i],'Euclidean'); +% dist_cur = floor(dist_cur); + clear ctmp_j ctmp_i; + if dist_first + dist_pre = dist_cur; + dist_first = false; + else + % the distance between the center of cloud and + % cloud shadow over plane decreases + if dist_pre >= dist_cur || dist_curwin_height|tmp_j_obs<1|tmp_j_obs>win_width); + out_all=sum(out_id(:)); + + + % exclude the pixels out of the entire image. + tmp_ii_obs=tmp_i_obs(out_id==0); + tmp_jj_obs=tmp_j_obs(out_id==0); + clear out_id tmp_i_obs tmp_j_obs; + tmp_id=sub2ind(dim,tmp_ii_obs,tmp_jj_obs); % matched shadow locations + clear tmp_ii_obs tmp_jj_obs; + + out_obs=mask(tmp_id)==0; + + id_ex_self = segm_cloud(tmp_id)~=cloud_type; + % 1st rule: out of obervations; 2nd rule: located in + % potential shadow or other clouds (exclude the self cloud). + + % Special case #1: + % for the cloud shadow previoudly matched, the new one + % cannot reach the boundary and other clouds, which easily + % result in the overestimation of silimarity. + match_id_unsure = out_obs | ... + (id_ex_self&(data_cloud_potential(tmp_id)==1)); + + match_id_sure = id_ex_self&data_shadow_potential(tmp_id)==1; + + % give half weight to the macthed pixels located in outside and other + % clouds. + matched_all=sum(match_id_sure(:))+0.5*sum(match_id_unsure(:))+out_all; + + total_all=sum(id_ex_self(:))+out_all; + + thresh_match=matched_all/total_all; + clear match_id total_all; + + + % used to determine whether the iteration continues or not. + iter_con=true; % continues as default. + clear id_ex_self; + + if num_matched > 0&&... % already have cloud shadow previously + (record_base_h_near > 0 && base_h >= record_base_h_near) % or more than the predicted cloud height. + iter_con=false; + end + clear pt_unsure; + + % check the matched cloud shadow or not? + % the following rules are used to decide to continue or not. + if iter_con + if (thresh_match >= Tbuffer*record_thresh)&&... + (base_h < Max_cl_height-step_interval)&&... + (record_thresh < max_similar) + if thresh_match > record_thresh + % record max similarity and the corresponding cloud base height. + record_thresh=thresh_match; + % record_h=h; + record_base_h=base_h; + end + continue; + else + if (record_thresh >= Tsimilar) + % successfully find a cloud shadow + num_matched=num_matched+1; % indicates one more cloud shadow was found out. + % only when expected height available. (record_base_h_near>0) + if base_h=record_thresh||thresh_match>=max_similar + record_thresh=thresh_match; + % record_h=h; + record_base_h=base_h; + end + continue; % much reach the predicted cloud height. + end + else + record_thresh=0; + continue; + end + end + end + % 1: continues; 0: not continue and get a cloud shadow + if num_matched<1 + break; + end + + if r_obj>num_pix&&... + cloud_type_cur<=num % cannot re-add for the first 14 clouds + matched_clds_centroid=[matched_clds_centroid;center_cur]; + match_clds(cloud_type)=1; + height_clds_recorder=[height_clds_recorder;record_base_h]; +% height_clds_recorder(cloud_type)=record_base_h; + record_base_h_num=record_base_h_num+1; + end + similar_num(cloud_type)=record_thresh; + + if isequal(data_meta.Sensor,'S_MSI') + record_h = record_base_h; + h_bias=record_h-base_heigh_cloud;% hc-Ec + [tmp_xys_all(:,1),tmp_xys_all(:,2)]= getRealCloudPositionS2(orin_xys_all(:,1),... + orin_xys_all(:,2),h_bias,VZAxy,VAAxy,data_meta.Resolution); + elseif isequal(data_meta.Sensor,'L_OLI_TIRS')||... + isequal(data_meta.Sensor,'L_ETM_PLUS')||... + isequal(data_meta.Sensor,'L_TM') + record_h = double(10*(t_obj-temp_obj_all)/rate_elapse+record_base_h); + h_bias=record_h-base_heigh_cloud;% hc-Ec + sensor_heigh_bias=base_heigh_cloud+dem_base_heigh; + [tmp_xys_all(:,1),tmp_xys_all(:,2)]=getRealCloudPosition(orin_xys_all(:,1),... + orin_xys_all(:,2),h_bias,A,B,C,omiga_par,omiga_per,sensor_heigh_bias); + else + error('Only Landsats 4-7, Landsat 8 and Sentinel 2 data can be supported./n'); + end + clear orin_xys_all; + + i_vir=record_h/(sub_size*tan(sun_elevation_rad)); + + tmp_XY_type_all(:,2)=round(tmp_xys_all(:,1)-i_vir*cos(sun_tazi_rad)); % X is for col j,2 + tmp_XY_type_all(:,1)=round(tmp_xys_all(:,2)-i_vir*sin(sun_tazi_rad)); % Y is for row i,1 + + clear tmp_xys_all i_vir; + + tmp_scol_plane=tmp_XY_type_all(:,2); + tmp_srow_plane=tmp_XY_type_all(:,1); + clear tmp_XY_type_all; + tmp_tmp_i_plane_expd=tmp_srow_plane+dim_expd; + tmp_tmp_j_plane_expd=tmp_scol_plane+dim_expd; + clear tmp_srow_plane tmp_scol_plane; + + avail_pixels=find(tmp_tmp_i_plane_expd>0&tmp_tmp_j_plane_expd>0&... + tmp_tmp_i_plane_expdwin_height|tmp_scol>win_width; + tmp_srow(out_ids)=[]; + tmp_scol(out_ids)=[]; + clear out_ids; + + tmp_sid=sub2ind(dim,tmp_srow,tmp_scol); + clear tmp_srow tmp_scol; + + % give shadow_cal=1 +% data_shadow_matched(tmp_sid)=1; + + if cloud_type_cur>num % re-visit the first 14 clouds. + % remove the matched before. + data_shadow_matched((data_shadow_matched==cloud_type))=0; + % and give new cloud shadow to this. + end + data_shadow_matched(tmp_sid)=cloud_type; + clear tmp_sid; + clear center_cur; + break; + end + + end + data_cloud_matched=data_cloud_potential; + data_shadow_matched=uint8(data_shadow_matched>0); + % remove the cloud. + data_shadow_matched(data_cloud_matched==1)=0; + end +end diff --git a/NDBI.m b/NDBI.m new file mode 100644 index 0000000..316640b --- /dev/null +++ b/NDBI.m @@ -0,0 +1,34 @@ +function ndbi = NDBI( nir, swir ) +%NDBI Calculate Normalized Difference Build-up Index (NDBI) using NIR and +% SWIR bands. +% Syntax +% +% ndbi = NDBI(swir,nir) +% +% Description +% +% This function calculates Normalized Difference Build-up Index (NDBI) +% using SWIR and NIR bands (as following equation). +% +% Input arguments +% +% swir Short-wave infrared band +% nir Near infrared band +% +% Output arguments +% +% ndbi Normalized Difference Build-up Index +% +% +% Author: Shi Qiu (shi.qiu@ttu.edu) +% Date: 19. Dec., 2017 + + % calculate NDBI + ndbi=(swir-nir)./(swir+nir); + + % fix unnormal pixels + % not 0.01 any more because we will identify urban pixel using ndbi more than 0. +% ndbi((nir+swir)==0)=0.0; + +end + diff --git a/NDSI.m b/NDSI.m new file mode 100644 index 0000000..0e6d3b6 --- /dev/null +++ b/NDSI.m @@ -0,0 +1,41 @@ +function ndsi = NDSI( green,swir ) +%NDSI calculate Normalized Difference Snow Index (NDSI) using Green and +% SWIR bands. +% Syntax +% +% ndsi = NDSI(green,swir) +% +% Description +% +% This function calculates Normalized Difference Snow Index (NDSI) +% using Green and SWIR bands (as following equation). This can be used +% to detect snow, as the atmosphere is transparent at both these +% wavelengths, while snow is very reflective at 0.66 mm and not +% reflective at 1.6mm. At visible wavelengths (e.g. 0.66 microns), snow +% cover is just as bright as clouds, and is therefore difficult to +% distinguish from cloud cover. However, at 1.6 microns, snow cover +% absorbs sunlight, and therefore appears much darker than clouds. +% +% NDSI=(Green-SWIR)/(Green+SWIR). +% +% Input arguments +% +% green Green band +% swir Short-wave infrared band +% +% Output arguments +% +% ndsi Normalized Difference Snow Index +% +% +% Author: Shi Qiu (shi.qiu@ttu.edu) +% Date: 19. October, 2017 + + % calculate NDSI + ndsi=(green-swir)./(green+swir); + + % fix unnormal pixels + ndsi((green+swir)==0)=0.01; + +end + diff --git a/NDVI.m b/NDVI.m new file mode 100644 index 0000000..b23b219 --- /dev/null +++ b/NDVI.m @@ -0,0 +1,36 @@ +function ndvi = NDVI( red,nir ) +%NDVI Calculate Normalized Difference Vegetation Index (NDVI) using NIR and +% Red bands. +% +% Syntax +% +% ndvi = NDVI(red,nir) +% +% Description +% +% This function calculates Normalized Difference Vegetation Index (NDVI) +% using NIR and Red bands (as following equation). This range is between +% -1 and 1. +% NDVI=(NIR-Red)/(NIR+Red). +% +% Input arguments +% +% red Red band +% nir Near-infrared band +% +% Output arguments +% +% ndvi Normalized Difference Vegetation Index +% +% +% Author: Shi Qiu (shi.qiu@ttu.edu) +% Date: 19. October, 2017 + + % calculate NDVI + ndvi=(nir-red)./(nir+red); + + % fix unnormal pixels + ndvi((nir+red)==0)=0.01; + +end + diff --git a/NormalizaCirrusDEM.m b/NormalizaCirrusDEM.m new file mode 100644 index 0000000..f7bf000 --- /dev/null +++ b/NormalizaCirrusDEM.m @@ -0,0 +1,54 @@ +function cirrus_noml = NormalizaCirrusDEM( mask, idplcd, cirrus, dem ) +%NORMALIZACIRRUSDEM normalize Cirrus band using most dark object at Cirrus +%band based on DEMs. + cirrus_noml=zeros(size(cirrus),'double'); + idclr=idplcd==false&mask; % clear sky pixels + l_pt=2; + if isempty(dem) % when no DEMs, we also adjust the cirrus TOA to be around 0; + cirrus_noml(mask)=cirrus(mask)-prctile(cirrus(idclr),l_pt); + clear mask cirrus idclr l_pt; + else + dem_start=prctile(dem(mask),0.001); dem_end=prctile(dem(mask),99.999); % further exclude errors in DEM. + clear mask; + dem_step=100; + dem_intvl=dem_start:dem_step:dem_end; + clear dem_start dem_end; + n_slice=length(dem_intvl); + dem_LUT=dem_intvl; + clear dem_intvl; + cirrus_lowest=0; + cirrus_lowest_al=0; + for i=1:n_slice + % the dem intervals. + if i==n_slice + ids_inter=dem>dem_LUT(i)-dem_step/2; + else + if i==1 + ids_inter=demdem_LUT(i)-dem_step/2)&(dem0 + cirrus_lowest=prctile(cirrus(ids_inter_clr),l_pt); + if cirrus_lowest_al==0 % the first local lowest cirrus value will be given + cirrus_lowest_al=cirrus_lowest; + end + end + clear ids_inter_clr; + + cirrus_noml(ids_inter)=cirrus(ids_inter)-cirrus_lowest; + clear ids_inter; + end + clear cirrus cirrus_lowest idclr dem_LUT dem_step n_slice; +% % % when no DEMs, we also adjust the cirrus TOA to be around 0, +% % % based on the full lowest cirrus value. +% % cirrus_noml(~dem_have)=cirrus(~dem_have)-cirrus_lowest_al; + end + % the normalized cirrus value will be set as 0 when it is negative. + cirrus_noml(cirrus_noml<0)=0; +end \ No newline at end of file diff --git a/NormalizeBT.m b/NormalizeBT.m new file mode 100644 index 0000000..05887ba --- /dev/null +++ b/NormalizeBT.m @@ -0,0 +1,96 @@ +function Tempd = NormalizeBT( dim, dem,mask,Temp,ratelaspe_cl,l_pt,h_pt,resl) +%NORMALIZEBT Normalize BT along elevation (DEM) by using a linear model. +% +% Syntax +% +% Tempd = NormalizeBT( dim, dem,mask,Temp,ratelaspe_cl,l_pt,h_pt ) +% +% Description +% +% A linear model is used to normalize BT along with DEM (Qiu et al., +% 2017). +% History: +% 1. Create this function. (1. January, 2017) +% 2. This stratied sampling method sometimes results in no enough +% samples. If the stratied samples are less than 40,000 (not 50,000), +% the stratied sampling method will not be used anymore. (8. March, +% 2018) +% 3. no normalization if no dem data. (20. March, 2018) +% +% Input arguments +% +% dim Dim for data. +% dem Digital Elevation Model (DEM). +% Temp Temperature (BT). +% ratelaspe_cl Clear sky (land) pixels, which are used for this +% normalization. +% l_pt Low level (17.5 percentile). +% h_pt High level (81.5 percentile). +% resl Spatial resolution (Landsat 30 meters; Sentinel-2 20 meters). +% +% Output arguments +% +% Tempd Nomalized Temperature (BT). +% +% +% Author: Shi Qiu (shi.qiu@ttu.edu) +% Date: 8. March, 2018 + + if isempty (dem) + Tempd = Temp; + else + mask(isnan(dem))=0;% exclude nan dem pixel. + dem_b=double(prctile(dem(mask),0.0001)); + temp_cl=Temp(ratelaspe_cl); + % clear ratelaspe_cl; + temp_min=prctile(temp_cl,l_pt*100); + temp_max=prctile(temp_cl,h_pt*100); + clear temp_cl l_pt h_pt; + cls=(Temp>temp_min&Temptemp_min&temp_cl-9999; +% +% obs_mask=band~=0; +% +% %% simulate Sentinel 2 scene by 100 km x 100 km. +% % spatial resolution is 30 m for each pixel. That means there will be +% % 3333 x 3333 30m pixels should be selected expanding from the centre. +% s2_pixels=[3333 3333]; +% s2_pixels_half=round(s2_pixels./2); +% obs_dim=size(obs_mask); +% pixel_centre=round(obs_dim./2); +% +% s2_mask=obs_mask.*0; +% r_start=pixel_centre(1)-s2_pixels_half(1); +% r_end=pixel_centre(1)+s2_pixels_half(1); +% c_start=pixel_centre(2)-s2_pixels_half(2); +% c_end=pixel_centre(2)+s2_pixels_half(2); +% s2_mask(r_start:r_end,c_start:c_end)=1; +% obs_mask=s2_mask&obs_mask; +end + diff --git a/ProjectDEM2Plane.m b/ProjectDEM2Plane.m new file mode 100644 index 0000000..404b15e --- /dev/null +++ b/ProjectDEM2Plane.m @@ -0,0 +1,108 @@ +% verison +% change to project the pixels from the potential shadow layer. Mar., 3, 2018 by Shi Qiu +% sometimes the projection will generate Nan values. fixed this bug by +% Feb., 13, 2018 by Shi Qiu +% fix the bug some pixels out of image. Shi 9/12/2017 +% +function [dem_plane_r,dem_plane_c]= ProjectDEM2Plane(dim,pshadow,dem,dem_base,... + sun_elevation_rad,sun_tazi_rad,sun_azimuth_deg,dim_expd,resolu) + % DEM is vertically obervated by scene. + % assume resolu.x=resolu.y + spatial_resol=resolu(1); + % get the location of orginal pixel + dem_loc=zeros(dim,'uint8')+1; + [loc_i,loc_j]=find(dem_loc); + +% must project all pixels. +% [loc_i,loc_j]=find(pshadow==1); +% loc_i=uint16(loc_i); +% loc_j=uint16(loc_j); +% if isequal(dem,0) % when no DEMs, no cloud shadow shape correction. + if isempty(dem) % when no DEMs, no cloud shadow shape correction. + loc_i_plane=loc_i; + loc_j_plane=loc_j; + else % when having DEMs +% dem_dif=dem(pshadow==1)-dem_base; + dem_dif=dem-dem_base; + dem_dif=dem_dif(:); + clear dem_loc dem dem_base; + % sun angle geometry +% sun_elevation_deg=90-sun_zenith_deg; +% sun_elevation_rad=deg2rad(sun_elevation_deg); + d_ij=dem_dif./(spatial_resol*tan(sun_elevation_rad)); +% Sun_tazi=sun_azimuth_deg-90; +% sun_tazi_rad=deg2rad(Sun_tazi); +% if sun_azimuth_deg< 180 + + if sun_azimuth_deg< 180 + di=0-d_ij.*sin(sun_tazi_rad); % i for row, Y + dj=0-d_ij.*cos(sun_tazi_rad); % j for col, X + else + di=d_ij.*sin(sun_tazi_rad); % i for row, Y + dj=d_ij.*cos(sun_tazi_rad); % j for col, X + end + di=double(di); + dj=double(dj); + clear dem_dif d_ij Sun_tazi sun_elevation_rad sun_zenith_deg Sun_tazi sun_tazi_rad; + + % original location adds the bias. + loc_i_plane=round(loc_i+di); + loc_j_plane=round(loc_j+dj); + clear di dj; + end +% dim_expd=1000;% 1000 buffer + dim_plane_expd=dim+(2*dim_expd); + % expand the range of observations + loc_i_plane_expd=loc_i_plane+dim_expd; + loc_j_plane_expd=loc_j_plane+dim_expd; + + % remove the pixels out of box. +% % out_ids=loc_i_plane_expd(:)<1|loc_j_plane_expd(:)<1|... +% % loc_i_plane_expd(:)>dim_plane_expd(1)|loc_j_plane_expd(:)>dim_plane_expd(2); +% % loc_i_plane_expd(out_ids)=[]; +% % loc_j_plane_expd(out_ids)=[]; +% % loc_i(out_ids)=[]; +% % loc_j(out_ids)=[]; + + loc_i_plane_expd(loc_i_plane_expd(:)<1)=1; + loc_j_plane_expd(loc_j_plane_expd(:)<1)=1; + loc_i_plane_expd(loc_i_plane_expd(:)>dim_plane_expd(1))=dim_plane_expd(1); + loc_j_plane_expd(loc_j_plane_expd(:)>dim_plane_expd(2))=dim_plane_expd(2); + + clear loc_i_plane loc_j_plane dj dim_expd; + dem_plane_r=zeros(dim_plane_expd); + dem_plane_c=zeros(dim_plane_expd); +% dem_plane_r = NaN(dim_plane_expd,'double'); +% dem_plane_c = NaN(dim_plane_expd,'double'); + % recorders. + tmp_id_plane=sub2ind(dim_plane_expd,double(loc_i_plane_expd),double(loc_j_plane_expd)); + clear loc_i_plane_expd loc_j_plane_expd; + + % sometimes there may be some Nan values! + nan_ids=isnan(tmp_id_plane); + if sum(nan_ids(:))>0 + tmp_id_plane(nan_ids)=[]; + loc_i(nan_ids)=[]; + loc_j(nan_ids)=[]; + end + dem_plane_r(tmp_id_plane)=loc_i; + dem_plane_c(tmp_id_plane)=loc_j; + + + + clear loc_i loc_j tmp_id_plane; + % filled the holes due to the dicretization projection. +% dem_plane_r=fillmissing(dem_plane_r,'nearest',1);% by column. +% dem_plane_c=fillmissing(dem_plane_c,'nearest',1);% by column. + % fill remainings as 0 value. +% dem_plane_r=fillmissing(dem_plane_r,'constant',0);% by column. +% dem_plane_c=fillmissing(dem_plane_c,'constant',0);% by column. + +% dem_plane_r=round(dem_plane_r); +% dem_plane_c=round(dem_plane_c); + + +% dem_plane_r=imfill(dem_plane_r,'holes'); +% dem_plane_c=imfill(dem_plane_c,'holes'); +end + diff --git a/ReadMetadataMSI.m b/ReadMetadataMSI.m new file mode 100644 index 0000000..c860ed9 --- /dev/null +++ b/ReadMetadataMSI.m @@ -0,0 +1,118 @@ +function MSIinfo=ReadMetadataMSI(FilesInfo) +% DataStrip='S2A_OPER_PRD_MSIL1C_PDMC_20150818T101237_R022_V20150813T102406_20150813T102406'; +% Granule='S2A_OPER_MSI_L1C_TL_MTI__20150813T201603_A000734_T32TPS_N01'; + +DirIn=FilesInfo.DirIn; +% Num=FilesInfo.Num; +DataStrip = FilesInfo.DataStrip; +Granule=FilesInfo.Granule; + +Dmain = DirIn ; + +if Granule(1) == 'L' + FileName = fullfile(Dmain, [DataStrip '.SAFE'],'GRANULE', Granule, 'MTD_TL.xml'); +% FileName = [Dmain DataStrip '.SAFE/GRANULE/' Granule '/' 'MTD_TL.xml']; +else +% FileName = [Dmain DataStrip '.SAFE/GRANULE/' Granule '/' strrep(Granule(1:55),'MSI','MTD') '.xml']; + FileName = fullfile(Dmain,[DataStrip '.SAFE'],'GRANULE', Granule,[strrep(Granule(1:55),'MSI','MTD') '.xml']); +end + +S = xml2struct(FileName); + +S = S.n1_colon_Level_dash_1C_Tile_ID; + +TileID = S.n1_colon_General_Info.TILE_ID.Text; + +ds = S.n1_colon_General_Info.SENSING_TIME.Text ; +DN = (datenum( ds ,'yyyy-mm-ddTHH:MM:SS')+str2double(ds(end-4:end-1))/3600/24); + +DS = datestr(DN); + +bandIdList = {'01' '02' '03' '04' '05' '06' '07' '08' '8A' '09' '10' '11' '12'} ; + +GeoInfo.UTM_zone = S.n1_colon_Geometric_Info.Tile_Geocoding.HORIZONTAL_CS_NAME.Text(end-2:end); + +%% +M = S.n1_colon_Quality_Indicators_Info.Pixel_Level_QI.MASK_FILENAME ; + +for i=1:length(M) + if isfield(M{i}.Attributes,'bandId') + Mask.(M{i}.Attributes.type).(['B' M{i}.Attributes.bandId]) = M{i}.Text ; + else + Mask.(M{i}.Attributes.type) = M{i}.Text ; + end +end +%% +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)] ; +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)] ; +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)] ; + +GeoInfo.Xstart.R10 = str2num(S.n1_colon_Geometric_Info.Tile_Geocoding.Geoposition{1}.ULX.Text); +GeoInfo.Xstart.R20 = str2num(S.n1_colon_Geometric_Info.Tile_Geocoding.Geoposition{2}.ULX.Text); +GeoInfo.Xstart.R60 = str2num(S.n1_colon_Geometric_Info.Tile_Geocoding.Geoposition{3}.ULX.Text); + +GeoInfo.Ystart.R10 = str2num(S.n1_colon_Geometric_Info.Tile_Geocoding.Geoposition{1}.ULY.Text); +GeoInfo.Ystart.R20 = str2num(S.n1_colon_Geometric_Info.Tile_Geocoding.Geoposition{2}.ULY.Text); +GeoInfo.Ystart.R60 = str2num(S.n1_colon_Geometric_Info.Tile_Geocoding.Geoposition{3}.ULY.Text); + +%% +clear dum +for i=1:23 + dum(i,:) = str2num(S.n1_colon_Geometric_Info.Tile_Angles.Sun_Angles_Grid.Zenith.Values_List.VALUES{i}.Text) ; +end +Angles.SZA = dum ; + +clear dum +for i=1:length(S.n1_colon_Geometric_Info.Tile_Angles.Sun_Angles_Grid.Azimuth.Values_List.VALUES) + dum(i,:) = str2num(S.n1_colon_Geometric_Info.Tile_Angles.Sun_Angles_Grid.Azimuth.Values_List.VALUES{i}.Text) ; +end +Angles.SAA = dum ; + +bandIdList = 0:12 ; +DeteIdList = 1:12 ; + +Angles.VZA = nan(23,23,length(bandIdList),length(DeteIdList)); +Angles.VAA = nan(23,23,length(bandIdList),length(DeteIdList)); + +Sdum = S.n1_colon_Geometric_Info.Tile_Angles.Viewing_Incidence_Angles_Grids; +for j=1:length(Sdum) + bandId = str2num(Sdum{j}.Attributes.bandId) ; + DeteId = str2num(Sdum{j}.Attributes.detectorId) ; + clear dum + for i=1:23 + dum(i,:) = str2num(Sdum{j}.Zenith.Values_List.VALUES{i}.Text) ; + end + Angles.VZA(:,:,bandIdList==bandId,DeteIdList==DeteId) = dum ; + clear dum + for i=1:23 + dum(i,:) = str2num(Sdum{j}.Azimuth.Values_List.VALUES{i}.Text) ; + end + Angles.VAA(:,:,bandIdList==bandId,DeteIdList==DeteId) = dum ; +end + +Angles.Mean.SZA = str2num(S.n1_colon_Geometric_Info.Tile_Angles.Mean_Sun_Angle.ZENITH_ANGLE.Text) ; +Angles.Mean.SAA = str2num(S.n1_colon_Geometric_Info.Tile_Angles.Mean_Sun_Angle.AZIMUTH_ANGLE.Text) ; + +Angles.Mean.VZA2 = nan(length(bandIdList),1); +Angles.Mean.VAA2 = nan(length(bandIdList),1); + +Sdum = S.n1_colon_Geometric_Info.Tile_Angles.Mean_Viewing_Incidence_Angle_List.Mean_Viewing_Incidence_Angle; +for j=1:length(Sdum) + bandId = str2num(Sdum{j}.Attributes.bandId) ; + Angles.Mean.VZA2(bandIdList==bandId) = str2num(Sdum{j}.ZENITH_ANGLE.Text) ; + Angles.Mean.VAA2(bandIdList==bandId) = str2num(Sdum{j}.AZIMUTH_ANGLE.Text) ; +end + +Angles.Mean.VZA = mean(Angles.Mean.VZA2); +Angles.Mean.VAA = mean(Angles.Mean.VAA2); + +Angles.bandIdList=bandIdList; +Angles.DeteIdList=DeteIdList; +%% +MSIinfo.TileID = TileID; +MSIinfo.DN = DN; +MSIinfo.DS = DS; +MSIinfo.bandIdList = bandIdList ; +MSIinfo.Angles = Angles; +MSIinfo.GeoInfo = GeoInfo; +MSIinfo.Mask = Mask ; diff --git a/ReadS2InspireXML.m b/ReadS2InspireXML.m new file mode 100644 index 0000000..d3de2da --- /dev/null +++ b/ReadS2InspireXML.m @@ -0,0 +1,16 @@ +function bbox = ReadS2InspireXML(xml_path) +%READS2INSPIREXML Load INSPIRE XML data at .SAFE directory, and can give us +%image extent. + FileName = fullfile(xml_path,'INSPIRE.xml'); + S = xml2struct(FileName); + clear FileName; + S = S.gmd_colon_MD_Metadata.gmd_colon_identificationInfo.gmd_colon_MD_DataIdentification; + extentS=S.gmd_colon_extent.gmd_colon_EX_Extent.gmd_colon_geographicElement.gmd_colon_EX_GeographicBoundingBox; + clear S; + % bbox=[north,south,west,east]; + bbox=[str2double(extentS.gmd_colon_northBoundLatitude.gco_colon_Decimal.Text),... + str2double(extentS.gmd_colon_southBoundLatitude.gco_colon_Decimal.Text),... + str2double(extentS.gmd_colon_westBoundLongitude.gco_colon_Decimal.Text),... + str2double(extentS.gmd_colon_eastBoundLongitude.gco_colon_Decimal.Text)]; +end + diff --git a/ReadSunViewGeometryMSI.m b/ReadSunViewGeometryMSI.m new file mode 100644 index 0000000..5580f04 --- /dev/null +++ b/ReadSunViewGeometryMSI.m @@ -0,0 +1,322 @@ +function [MSIinfo Angles CM] = ReadSunViewGeometryMSI (DataStrip,Granule,BandSel,PsizeOut,Dmain) +% DataStrip='S2A_OPER_PRD_MSIL1C_PDMC_20150818T101451_R080_V20150817T114755_20150817T114755'; +% Granule='S2A_OPER_MSI_L1C_TL_SGS__20150817T131818_A000792_T28QBG_N01'; +% this is derived from Fmask 3.3 for Sentinel 2. + +InterpMethod = 'linear';'nearest'; + +% Dmain = '/nobackupp6/jju/S2SAFE/'; +Pppplot = 0 ; +% PsizeOut = 20 ; +if nargin<5 + FilesInfo.DirIn = CodeStartingLinesSentinel(-1); +else + FilesInfo.DirIn = Dmain; +end +% FilesInfo.Num = '.04'; +FilesInfo.DataStrip = DataStrip; +FilesInfo.Granule=Granule; + +if Granule(1) == 'L' + mainpath = fullfile(Dmain,[DataStrip '.SAFE']); +else + mainpath = fullfile(Dmain, [DataStrip '.SAFE'],'GRANULE', Granule, 'QI_DATA'); +end + +try + MSIinfo=ReadMetadataMSI(FilesInfo); +catch + error('The metadata was wrongly loaded. Please input Sentinel-2 TOA data.'); +end + +if nargout==1 + return +end +bandIdList = {'01' '02' '03' '04' '05' '06' '07' '08' '8A' '09' '10' '11' '12'} ; +BandList = 0:12 ; +if exist('BandSel','var') + bandIdList = bandIdList(BandSel); + BandList = BandList(BandSel); +end +if ~exist('PsizeOut','var') + PsizeOut = 10 ; +end +% bandIdList = bandIdList (8:9); +% BandList = BandList (8:9); + + +% MethInterp100m10m = 'nearest'; + +% [BandList]=find(MSIinfo.Angles.Mean.VZA2==median(MSIinfo.Angles.Mean.VZA2)) ; + +%% +X5km = MSIinfo.GeoInfo.Xstart.R10 : 5000 : MSIinfo.GeoInfo.Xstart.R10 + 5000*22 ; +Y5km = MSIinfo.GeoInfo.Ystart.R10 :-5000 : MSIinfo.GeoInfo.Ystart.R10 - 5000*22 ; +% [X5kmat Y5kmat]=meshgrid(X5km,Y5km); +% Psize = 100 ; +% X100m = single(MSIinfo.GeoInfo.Xstart.R20+Psize/2 : Psize : MSIinfo.GeoInfo.Xstart.R20-Psize/2 + Psize*(MSIinfo.GeoInfo.Size.R10(2)/10))'; +% Y100m = single(MSIinfo.GeoInfo.Ystart.R20-Psize/2 :-Psize : MSIinfo.GeoInfo.Ystart.R20+Psize/2 - Psize*(MSIinfo.GeoInfo.Size.R10(1)/10))'; +% [X100mat Y100mat]=meshgrid(X100m,Y100m); + +Psize = 10 ; +X10m = single(MSIinfo.GeoInfo.Xstart.R20 : Psize : MSIinfo.GeoInfo.Xstart.R20+(MSIinfo.GeoInfo.Size.R10(2)-1)*Psize) + Psize/2 ; +Y10m = single(MSIinfo.GeoInfo.Ystart.R20 :-Psize : MSIinfo.GeoInfo.Ystart.R20-(MSIinfo.GeoInfo.Size.R10(1)-1)*Psize)' - Psize/2 ; + +% X100m = imresize(X10m,10/100,'box') ; +% Y100m = imresize(Y10m,10/100,'box') ; + +XPsizeOut = imresize(X10m,10/PsizeOut,'box') ; +YPsizeOut = imresize(Y10m,10/PsizeOut,'box') ; +clear PsizeOut; + +% Psize = PsizeOut ; + +% [X100mat Y100mat]=meshgrid(X100m,Y100m); +[XPsizeOutmat YPsizeOutmat]=meshgrid(XPsizeOut,YPsizeOut); + + +if Pppplot == 1 + % Psize = PsizeOut ; + % X10m = single(MSIinfo.GeoInfo.Xstart.R20+Psize/2 : Psize : MSIinfo.GeoInfo.Xstart.R20-Psize/2 + (MSIinfo.GeoInfo.Size.R10(2)*10/Psize))'; + % Y10m = single(MSIinfo.GeoInfo.Ystart.R20-Psize/2 :-Psize : MSIinfo.GeoInfo.Ystart.R20+Psize/2 - (MSIinfo.GeoInfo.Size.R10(1)*10/Psize))'; + % [X10mat Y100mat]=meshgrid(X100m,Y100m); + [X5kmat Y5kmat]=meshgrid(X5km,Y5km); +end +% % kern = ones(20);imcircle(20); + +%% cloud Mask + +if nargout==3 + + FileName = fullfile(mainpath, MSIinfo.Mask.MSK_CLOUDS); + S = xml2struct(FileName); + + if isfield(S.eop_colon_Mask,'eop_colon_maskMembers') + + S = S.eop_colon_Mask.eop_colon_maskMembers; + + + + + + if length(S)==1 + % DetectFootPrint.Ndect(1) = str2num(S(1).eop_colon_MaskFeature.Attributes.gml_colon_id(24:25)); + % disp('attention') + % DetectFootPrint.Nband(1) = find(strcmp(MSIinfo.bandIdList , S(1).eop_colon_MaskFeature.Attributes.gml_colon_id(21:22))); + Vect = str2num(S(1).eop_colon_MaskFeature.eop_colon_extentOf.gml_colon_Polygon.gml_colon_exterior.gml_colon_LinearRing.gml_colon_posList.Text); + Vect = reshape(Vect' , [2 length(Vect)/2 ])'; + CloudMask.Data{1,1} = Vect ; + % Z = MSIinfo.Angles.VZA(:,:,MSIinfo.Angles.bandIdList==DetectFootPrint.Nband(1),MSIinfo.Angles.DeteIdList==DetectFootPrint.Ndect(1)) ; + else + + for i = 1:length(S) + % DetectFootPrint.Ndect(i,1) = str2num(S{i}.eop_colon_MaskFeature.Attributes.gml_colon_id(24:25)); + % DetectFootPrint.Nband(i,1) = strcmp(MSIinfo.bandIdList , S{i}.eop_colon_MaskFeature.Attributes.gml_colon_id(21:22)); + Vect = str2num(S{i}.eop_colon_MaskFeature.eop_colon_extentOf.gml_colon_Polygon.gml_colon_exterior.gml_colon_LinearRing.gml_colon_posList.Text); + Vect = reshape(Vect' , [2 length(Vect)/2 ])'; + CloudMask.Data{i,1} = Vect ; + % if Pppplot == 1 + % IDX = knnsearch(X10m,DetectFootPrint.Data{i,1}(:,1)); + % Ximg = X10m(IDX); + % IDX = knnsearch(Y10m,DetectFootPrint.Data{i,1}(:,2)); + % Yimg = Y10m(IDX); + % plot(Ximg,Yimg,'k') + % Z = MSIinfo.Angles.VAA(:,:,MSIinfo.Angles.bandIdList==DetectFootPrint.Nband(i),MSIinfo.Angles.DeteIdList==DetectFootPrint.Ndect(i)) ; + % scatter(X5kmat(:),Y5kmat(:),30,Myreshape(Z),sym(i)) + % end + + end + end + + Vect = [NaN NaN]; + for i = 1:length(S) + Vect = cat(1,Vect ,CloudMask.Data{i},[NaN NaN]); + end + + % X10m = single(MSIinfo.GeoInfo.Xstart.R20+Psize/2 : Psize : MSIinfo.GeoInfo.Xstart.R10-Psize/2 + Psize*(MSIinfo.GeoInfo.Size.R10(2)))'; + % Y10m = single(MSIinfo.GeoInfo.Ystart.R20-Psize/2 :-Psize : MSIinfo.GeoInfo.Ystart.R10+Psize/2 - Psize*(MSIinfo.GeoInfo.Size.R10(1)))'; + + IDX = knnsearch(XPsizeOut',Vect(:,1)); + IDY = knnsearch(YPsizeOut,Vect(:,2)); + CM = uint8(poly2mask(double(IDX),double(IDY),length(YPsizeOut),length(XPsizeOut))); + clear IDX IDY; + else + CM = zeros( length(YPsizeOut),length(XPsizeOut) ,'uint8'); + end +end + + +%% +%% +%% +%% +%% Angles +for iB = 1:length(BandList) + % tic + FileName = fullfile(mainpath,... + MSIinfo.Mask.MSK_DETFOO.(['B' num2str(BandList(iB))])); + + S = xml2struct(FileName); + clear FileName; + + if isfield(S.eop_colon_Mask.eop_colon_maskMembers,'eop_colon_MaskFeature') + S = S.eop_colon_Mask.eop_colon_maskMembers.eop_colon_MaskFeature; + else + S = S.eop_colon_Mask.eop_colon_maskMembers; + end + % figure + + if length(S)==1 + + try + % the following codes are unworkable anymore. + if ~(isfield(S,'eop_colon_MaskFeature')) + S{1}.eop_colon_MaskFeature = S{1}; + end + DetectFootPrint.Ndect(1) = str2num(S{1}.eop_colon_MaskFeature.Attributes.gml_colon_id(24:25)); + disp('attention') + DetectFootPrint.Nband(1) = find(strcmp(MSIinfo.bandIdList , S(1).eop_colon_MaskFeature.Attributes.gml_colon_id(21:22))); + Vect = str2num(S(1).eop_colon_MaskFeature.eop_colon_extentOf.gml_colon_Polygon.gml_colon_exterior.gml_colon_LinearRing.gml_colon_posList.Text); + Vect = reshape(Vect' , [3 length(Vect)/3 ])'; + DetectFootPrint.Data{1,1} = Vect ; + clear Vect; + catch + if ~(isfield(S,'eop_colon_MaskFeature')) + S.eop_colon_MaskFeature = S; + end + DetectFootPrint.Ndect(1) = str2num(S.eop_colon_MaskFeature.Attributes.gml_colon_id(24:25)); + disp('attention') + DetectFootPrint.Nband(1) = find(strcmp(bandIdList , S.eop_colon_MaskFeature.Attributes.gml_colon_id(21:22))); + Vect = str2num(S.eop_colon_MaskFeature.eop_colon_extentOf.gml_colon_Polygon.gml_colon_exterior.gml_colon_LinearRing.gml_colon_posList.Text); + Vect = reshape(Vect' , [3 length(Vect)/3 ])'; + DetectFootPrint.Data{1,1} = Vect ; + clear Vect; + end + % Z = MSIinfo.Angles.VZA(:,:,MSIinfo.Angles.bandIdList==DetectFootPrint.Nband(1),MSIinfo.Angles.DeteIdList==DetectFootPrint.Ndect(1)) ; + else + if Pppplot == 1 + figure + hold on + sym = 'oxoxoxoxox'; + end + for i = 1:length(S) + if ~(isfield(S{i},'eop_colon_MaskFeature')) + S{i}.eop_colon_MaskFeature = S{i}; + end + DetectFootPrint.Ndect(i,1) = str2num(S{i}.eop_colon_MaskFeature.Attributes.gml_colon_id(24:25)); + % DetectFootPrint.Nband(i,1) = strcmp(MSIinfo.bandIdList , S{i}.eop_colon_MaskFeature.Attributes.gml_colon_id(21:22)); + DetectFootPrint.Nband(i,1) = strcmp(bandIdList , S{i}.eop_colon_MaskFeature.Attributes.gml_colon_id(21:22)); + + Vect = str2num(S{i}.eop_colon_MaskFeature.eop_colon_extentOf.gml_colon_Polygon.gml_colon_exterior.gml_colon_LinearRing.gml_colon_posList.Text); + Vect = reshape(Vect' , [3 length(Vect)/3 ])'; + DetectFootPrint.Data{i,1} = Vect ; + clear Vect; + if Pppplot == 1 + + IDX = knnsearch(X10m,DetectFootPrint.Data{i,1}(:,1)); + Ximg = X10m(IDX); + IDX = knnsearch(Y10m,DetectFootPrint.Data{i,1}(:,2)); + Yimg = Y10m(IDX); + clear IDX; + plot(Ximg,Yimg,'k') + clear Ximg Yimg; + Z = MSIinfo.Angles.VAA(:,:,MSIinfo.Angles.bandIdList==DetectFootPrint.Nband(i),MSIinfo.Angles.DeteIdList==DetectFootPrint.Ndect(i)) ; + scatter(X5kmat(:),Y5kmat(:),30,Myreshape(Z),sym(i)) + end + end + end + clear S; + % % axis image + + + %% 5km => 10m + Matdetec = findMatdetecFootprint(DetectFootPrint,XPsizeOut,YPsizeOut); + + % JJ.B04_detfoo = hdfread('D:\UMD\Data\HLS\Products\detfoo.T36JTT.2015350.v1.0.hdf', '/B04_detfoo', 'Index', {[1 1],[1 1],[1830 1830]}); + + Angles.Matdetec(:,:,iB) = uint8(Matdetec) ; + Angles.DetectFootPrint{iB} = DetectFootPrint ; + % return + clear dum* + + % figure + % hold on + % imagesc(X10m,Y10m,Matdetec) + % for i = 1:length(S) + % plot(DetectFootPrint.Data{i,1}(:,1),DetectFootPrint.Data{i,1}(:,2),'k') + % end + + Chp = {'VAA' 'VZA'} ; + + for iC=1:2 + Angles.([Chp{iC} '_B' bandIdList{iB}]) = nan(size(Matdetec),'single'); + for i = 1:length(DetectFootPrint.Ndect) + + Z = MSIinfo.Angles.(Chp{iC})(:,:,BandSel,MSIinfo.Angles.DeteIdList==DetectFootPrint.Ndect(i)) ; + Z = single(inpaint_nans(Z)); + + testMatdetec = Matdetec==i ; + [Ista dum] = find(max(testMatdetec,[],2),1,'first'); + [Iend dum] = find(max(testMatdetec,[],2),1,'last'); + [dum Jsta] = find(max(testMatdetec,[],1),1,'first'); + [dum Jend] = find(max(testMatdetec,[],1),1,'last'); + clear dum; + % Ista = floor(Ista/10)+1; + % Jsta = floor(Jsta/10)+1; + % Iend = ceil(Iend/10); + % Jend = ceil(Jend/10); + + ZI = zeros(size(Matdetec),'single'); + + ZI(Ista:Iend,Jsta:Jend) = interp2(X5km,Y5km,Z,XPsizeOutmat(Ista:Iend,Jsta:Jend),YPsizeOutmat(Ista:Iend,Jsta:Jend),InterpMethod); + clear Z Ista Iend Jsta Jend; + % ZI(Ista:Iend,Jsta:Jend) = interp2(X5km,Y5km,Z,X10mat(Ista:Iend,Jsta:Jend),Y100mat(Ista:Iend,Jsta:Jend),InterpMethod); + % ZI = imresize(ZI,10,MethInterp100m10m); + + Angles.([Chp{iC} '_B' bandIdList{iB}])(testMatdetec) = ZI(testMatdetec) ; + clear ZI testMatdetec; + end + end + clear Matdetec; + % toc +end + + +Z = MSIinfo.Angles.SZA ; +ZI = interp2(X5km,Y5km,Z,XPsizeOutmat,YPsizeOutmat,InterpMethod); +clear Z; +% ZI=imresize(ZI,10,MethInterp100m10m); +Angles.SZA = ZI; + +Z = MSIinfo.Angles.SAA ; +ZI = interp2(X5km,Y5km,Z,XPsizeOutmat,YPsizeOutmat,InterpMethod); +clear Z X5km Y5km XPsizeOutmat YPsizeOutmat InterpMethod; +% ZI=imresize(ZI,10,MethInterp100m10m); +Angles.SAA = ZI; +clear ZId; + +% for iB = BandList +% Angles.(['RAA_B' bandIdList{iB+1}]) = abs(Angles.(['VAA_B' num2str(iB)]) - Angles.SAA) ; +% Angles=rmfield(Angles,['VAA_B' num2str(iB)]); +% end +% Angles=rmfield(Angles,'SAA'); + +fn = fieldnames(Angles); +dum = cellfun(@(x) isempty(strfind(x,'A')),fn); +fn(dum) = [] ; +clear dum; +for i=1:length(fn)-1 + dum = Angles.(fn{i}) ; + dum(dum<0) = 655.36 ; + Angles.(fn{i}) = uint16(dum*100); + clear dum; +end +clear fn; + +Angles.BandList = BandList ; +Angles.bandIdList = bandIdList ; +clear BandList bandIdList; + +return + +y = structfun(@(x) max(x(:)), Angles) diff --git a/Readopentopo.m b/Readopentopo.m new file mode 100644 index 0000000..0edf996 --- /dev/null +++ b/Readopentopo.m @@ -0,0 +1,121 @@ +function DEM = Readopentopo(varargin) + +%READOPENTOPO read DEM using the opentopography.org API +% +% Syntax +% +% DEM = readopentopo(pn,pv,...) +% +% Description +% +% readopentopo reads DEMs from opentopography.org using the API +% described on: +% http://www.opentopography.org/developers +% The DEM comes in geographic coordinates (WGS84) and should be +% projected to a projected coordinate system (use reproject2utm) before +% analysis in TopoToolbox. +% +% Input arguments +% +% Parameter name values +% 'filename' provide filename. By default, the function will save +% the DEM to a temporary file in the system's temporary +% folder. +% 'north' northern boundary in geographic coordinates (WGS84) +% 'south' southern boundary +% 'west' western boundary +% 'east' eastern boundary +% 'demtype' The global raster dataset - SRTM GL3 (90m) is +% 'SRTMGL3', SRTM GL1 (30m) is 'SRTMGL1', SRTM GL1 +% (Ellipsoidal) is 'SRTMGL1_E', and ALOS World 3D 30m +% is 'AW3D30' +% 'deletefile' 'true' or false. True, if file should be deleted +% after it was downloaded and added to the workspace. +% +% Output arguments +% +% DEM Digital elevation model in geographic coordinates +% (GRIDobj) +% +% See also: GRIDobj, websave +% +% Reference: http://www.opentopography.org/developers +% +% Author: Wolfgang Schwanghart (w.schwanghart[at]geo.uni-potsdam.de) +% Date: 19. June, 2017 + + +p = inputParser; +addParameter(p,'filename',[tempname '.tif']); +% addParameter(p,'interactive',false); +addParameter(p,'north',37.091337); +addParameter(p,'south',36.738884); +addParameter(p,'west',-120.168457); +addParameter(p,'east',-119.465576); +addParameter(p,'demtype','SRTMGL3'); +addParameter(p,'deletefile',true); +parse(p,varargin{:}); + +demtype = validatestring(p.Results.demtype,{'SRTMGL3','SRTMGL1','SRTMGL1_E','AW3D30'},'readopentopo'); + +url = 'http://opentopo.sdsc.edu/otr/getdem'; + +% create output file +f = fullfile(p.Results.filename); + +% save to drive +options = weboptions('Timeout',inf); + +west = p.Results.west; +east = p.Results.east; +south = p.Results.south; +north = p.Results.north; + +% if any([isempty(west) isempty(east) isempty(south) isempty(north)]) || p.Results.interactive; +% wm = webmap; +% % get dialog box +% messagetext = ['Zoom and resize the webmap window to choose DEM extent. ' ... +% 'Click the close button when you''re done.']; +% d = waitdialog(messagetext); +% uiwait(d); +% [latlim,lonlim] = wmlimits(wm); +% west = lonlim(1); +% east = lonlim(2); +% south = latlim(1); +% north = latlim(2); +% end + + + +websave(f,url,'west',west,... + 'east',east,... + 'north',north,... + 'south',south,... + 'outputFormat', 'GTiff', ... + 'demtype', demtype, ... + options); + +DEM = GRIDobj(f); +DEM.name = demtype; + +if p.Results.deletefile + delete(f); +end +end + +% function d = waitdialog(messagetext) +% d = dialog('Position',[300 300 250 150],'Name','Choose rectangle region',... +% 'WindowStyle','normal'); +% +% txt = uicontrol('Parent',d,... +% 'Style','text',... +% 'Position',[20 80 210 40],... +% 'String',messagetext); +% +% btn = uicontrol('Parent',d,... +% 'Position',[85 20 70 25],... +% 'String','Close',... +% 'Callback','delete(gcf)'); +% end + + diff --git a/Saturate.m b/Saturate.m new file mode 100644 index 0000000..7792ce7 --- /dev/null +++ b/Saturate.m @@ -0,0 +1,7 @@ +function satu_Bv = Saturate( satu_blue,satu_green,satu_red ) +%SATURE Summary of this function goes here +% Detailed explanation goes here + satu_Bv=satu_blue+satu_green+satu_red>=1; % saturated pixes at any visible bands. + clear satu_blue satu_green satu_red; +end + diff --git a/arcslope.m b/arcslope.m new file mode 100644 index 0000000..f5a4857 --- /dev/null +++ b/arcslope.m @@ -0,0 +1,90 @@ +function SLP = arcslope(DEM,unit) + +%ARCSLOPE mean gradient from a digital elevation model sensu ArcGIS +% +% Syntax +% +% SLP = arcslope(DEM) +% SLP = arcslope(DEM,unit) +% +% Description +% +% ARCSLOPE returns the gradient as calculated by ArcGIS (mean slope of +% 8 connected neighborhood). Default unit is as tangent, but you can +% specify alternative units identical to gradient8 function. +% +% Input +% +% DEM digital elevation model (class: GRIDobj) +% unit 'tan' --> tangent (default) +% 'rad' --> radian +% 'deg' --> degree +% 'sin' --> sine +% 'per' --> percent +% +% Output +% +% SLP mean gradient (class: GRIDobj) +% +% Example +% +% DEM = GRIDobj('srtm_bigtujunga30m_utm11.tif'); +% SLP = arcslope(DEM); +% imageschs(DEM,SLP); +% +% See also: GRIDobj/gradient8 +% +% Author: Wolfgang Schwanghart (w.schwanghart[at]geo.uni-potsdam.de) and +% Adam M. Forte (aforte[a]asu.edu) +% Date: 6. February, 2017 + + +z=DEM.Z; + +% Pad array to avoid edge effects +zp=padarray(z,[1 1],'replicate'); + +% Handle nans +I = isnan(zp); +in = any(I(:)); +if in + [~,L] = bwdist(~I); + zp = zp(L); +end + +% Define anon function to calculate the mean gradient of 8 connected neighborhood +% by same algorithm as the ArcGIS slope function +kernel = [ -1 -2 -1; 0 0 0; 1 2 1]'; +rr = sqrt((conv2(zp,kernel','valid')./(8*DEM.cellsize)).^2 + ... + (conv2(zp,kernel,'valid')./(8*DEM.cellsize)).^2); + +% Package output +if nargin == 1 + unit = 'tangent'; +else + unit = validatestring(unit,{'tangent' 'degree' 'radian' 'percent' 'sine'},'gradient8','unit',2); +end + +if in + rr(I(2:end-1,2:end-1)) = nan; +end + +% create a copy of the DEM instance +SLP = DEM; +SLP.name = 'slope (arcgis)'; +SLP.zunit = unit; +SLP.Z=rr; + +switch unit + case 'tangent' + % do nothing + case 'degree' + SLP.Z = atand(SLP.Z); + case 'radian' + SLP.Z = atan(SLP.Z); + case 'sine' + SLP.Z = sin(atan(SLP.Z)); + case 'percent' + SLP.Z = SLP.Z*100; +end + diff --git a/aspect.m b/aspect.m new file mode 100644 index 0000000..8785620 --- /dev/null +++ b/aspect.m @@ -0,0 +1,99 @@ +function OUT = aspect(DEM,classify) + +%ASPECT angle of exposition from a digital elevation model (GRIDobj) +% +% Syntax +% +% ASP = aspect(DEM) +% ASP = aspect(DEM,classify) +% +% Description +% +% aspect returns the slope exposition of each cell in a digital +% elevation model in degrees. In contrast to the second output of +% gradient8 which returns the steepest slope direction, aspect +% returns the angle as calculated by surfnorm. +% +% Input +% +% DEM digital elevation model (class: GRIDobj) +% classify false (default) or true. If true, directions are +% classified according to the scheme proposed by +% Gomez-Plaza et al. (2001) +% +% Output +% +% ASP aspect in degrees (clockwise from top) or classified grid, +% if classify is set to true +% +% Example +% +% DEM = GRIDobj('srtm_bigtujunga30m_utm11.tif'); +% ASP = aspect(DEM); +% imageschs(DEM,ASP) +% +% References +% +% Gómez-Plaza, A.; Martínez-Mena, M.; Albaladejo, J. & Castillo, V. M. +% (2001): Factors regulating spatial distribution of soil water content +% in small semiarid catchments. Journal of Hydrology, 253, 211 - 226. +% +% +% See also: GRIDobj/gradient8, GRIDobj/reclassify +% +% Author: Wolfgang Schwanghart (w.schwanghart[at]geo.uni-potsdam.de) +% Date: 17. August, 2017 + +narginchk(1,2) + +if nargin == 1; + classify = false; +else + +end + +OUT = DEM; +OUT.Z = []; + +if classify + aspedges = (0:45:360)'; + aspclass = [1 3 5 7 8 6 4 2]; +end + +% Large matrix support. Break calculations in chunks using blockproc +if numel(DEM.Z)>(5001*5001); + fun = @(x) aspfun(x); + blksiz = bestblk(size(DEM.Z),5000); + OUT.Z = blockproc(DEM.Z,blksiz,fun,'BorderSize',[1 1]); +else + OUT.Z = aspfun(DEM.Z); +end + +OUT.name = 'aspect'; +OUT.zunit = 'degree'; + + + + +function ASP = aspfun(Z) +if isstruct(Z); + Z = Z.data; +end + +[Nx,Ny] = surfnorm(Z); +ASP = cart2pol(Nx,Ny); +ASP = mod(90+ASP/pi*180,360); +% ASP = 270 - ASP/pi*180 -90; + +if classify + + [~,bin] = histc(ASP(:),aspedges); + bin = reshape(bin,size(Z)); + ASPc = zeros(size(Z),'uint8'); + ASPc(bin>0) = aspclass(bin(bin>0)); + ASP = ASPc; + +end +end + +end \ No newline at end of file diff --git a/autoFmask.m b/autoFmask.m new file mode 100644 index 0000000..3d52c83 --- /dev/null +++ b/autoFmask.m @@ -0,0 +1,236 @@ +function clr_pct = autoFmask(varargin) +% AUTOFMASK Automatedly detect clouds, cloud shadows, snow, and water for +% Landsats 4-7 TM/EMT+, Landsat 8 OLI/TIRS, and Sentinel 2 MSI images. +% +% +% Description +% Shi Qiu, Zhe Zhu, Binbin He +% shi.qiu@uconn.com, zhe.zhu@uconn.edu, binbinhe@uestc.edu.cn +% Automatically detect clouds, cloud shadows, snow, and water for +% Landsats 4-7, Landsat 8, and Sentinel 2 images. +% +% This 4.0 version has better cloud, cloud shadow, and snow detection +% results for Sentinel-2 data and better results (compared to the 3.3 +% version that is being used by USGS as the Colection 1 QA Band) for +% Landsats 4-8 data as well. +% +% Input arguments +% +% cloud Dilated number of pixels for cloud with default value of 3. +% shadow Dilated number of pixels for cloud shadow with default value of 3. +% snow Dilated number of pixels for snow with default value of 0. +% p Cloud probability threshold with default values of 10.0 for +% Landsats 4~7, 17.5 for Landsat 8, and 20.0 for Sentinel 2. +% e Radius of erosion for Potential False Positive Cloud such as +% urban/built-up and (mountian) snow/ice. Default: 150 meters +% for Landsats 4-7 and 90 meters for Landsat 8 and +% Sentinel-2. +% udem The path of User's DEM data. (.tiff). If users provide +% local DEM data, Fmask 4.0 will process the image along with this DEM +% data; or, the default USGS GTOPO30 will be used. +% +% Output arguments +% +% fmask 0: clear land +% 1: clear water +% 2: cloud shadow +% 3: snow +% 4: cloud +% 255: filled (outside) +% +% Examples +% +% clr_pct = autoFmask('cloud',0, 'shadow', 0) will produce the mask without buffers. +% clr_pct = autoFmask('p',20) forces cloud probablity thershold as 20. +% clr_pct = autoFmask('e',500) forces erosion radius for Potential False Positive Cloud as 500 meters to remove the large commission errors. +% +% +% Author: Shi Qiu (shi.qiu@uconn.com) +% Last Date: May 15, 2019 + + tic + fmask_soft_name='Fmask 4.0'; + fprintf('%s start ...\n',fmask_soft_name); + path_data=pwd; + + %% get parameters from inputs + p = inputParser; + p.FunctionName = 'FmaskParas'; + % optional + % default values. + addParameter(p,'cloud',3); + addParameter(p,'shadow',3); + addParameter(p,'snow',0); + + %% read info from .xml. + [sensor,~,InputFile,main_meta] = LoadSensorType(path_data); + if isempty(sensor) + error('%s works only for Landsats 4-7, Landsat 8, and Sentinel 2 images.\n',fmask_soft_name); + end + + default_paras = FmaskParameters(sensor); + tpw = default_paras.ThinWeight; + addParameter(p,'e',default_paras.PFPCErosionRadius); + addParameter(p,'p',default_paras.CloudProbabilityThershold); + addParameter(p,'resolution',default_paras.OutputResolution); + + % user's path for DEM + addParameter(p,'udem',''); + + % request user's input + parse(p,varargin{:}); + resolution=p.Results.resolution; + cldpix=p.Results.cloud; + sdpix=p.Results.shadow; + snpix=p.Results.snow; + erdpix=round(p.Results.e/resolution); + cldprob=p.Results.p; + + % users can use the local dem. + userdem = p.Results.udem; + clear p; + + fprintf('Cloud/cloud shadow/snow dilated by %d/%d/%d pixels.\n',cldpix,sdpix,snpix); + fprintf('Cloud probability threshold of %.2f%%.\n',cldprob); + + fprintf('Load or calculate TOA reflectances.\n'); + + %% load data + [data_meta,data_toabt,angles_view,trgt] = LoadData(path_data,sensor,InputFile,main_meta); + clear InputFile norMTL; + + if isempty(userdem) + % default DEM + [dem,slope,aspect,water_occur] = LoadAuxiData(fullfile(path_data,data_meta.Output),data_meta.Name,data_meta.BBox,trgt,false); % true false + else + [dem,slope,aspect,water_occur] = LoadAuxiData(fullfile(path_data,data_meta.Output),data_meta.Name,data_meta.BBox,trgt,false,'userdem',userdem); % true false + end + + fprintf('Detect potential clouds, cloud shadows, snow, and water.\n'); + + %% public data + mask=ObservMask(data_toabt.BandBlue); + + % a pixel's DEM can be set as the lowest value derived from the all workable pixels. + if ~isempty(dem) + dem_nan=isnan(dem); + dem(dem_nan)=double(prctile(dem(~dem_nan&mask),0.001)); % exclude DEM errors. + clear dem_nan; + end + + % NDVI NDSI NDBI + ndvi = NDVI(data_toabt.BandRed, data_toabt.BandNIR); + ndsi = NDSI(data_toabt.BandGreen, data_toabt.BandSWIR1); + cdi = CDI(data_toabt.BandVRE3,data_toabt.BandNIR8,data_toabt.BandNIR);% band 7, 8, AND 8a + + data_toabt.BandVRE3 = []; + data_toabt.BandNIR8 = []; + + % Statured Visible Bands + satu_Bv = Saturate(data_toabt.SatuBlue, data_toabt.SatuGreen, data_toabt.SatuRed); + data_toabt.SatuBlue = []; + + %% select potential cloud pixels (PCPs) + % inputs: BandSWIR2 BandBT BandBlue BandGreen BandRed BandNIR BandSWIR1 + % BandCirrus +% [idplcd,BandCirrusNormal,whiteness,HOT] = DetectPotentialPixels(mask,data_toabt,dem,ndvi,ndsi,satu_Bv); + + %% detect snow + psnow = DetectSnow(data_meta.Dim, data_toabt.BandGreen, data_toabt.BandNIR, data_toabt.BandBT, ndsi); + + %% detect water + water = DetectWater(data_meta.Dim, mask, data_toabt.BandNIR, ndvi, psnow, slope, water_occur); + clear water_occur; + + [idplcd,BandCirrusNormal,whiteness,HOT] = DetectPotentialPixels(mask,data_toabt,dem,ndvi,ndsi,satu_Bv); + + data_toabt.BandBlue = []; + data_toabt.BandRed = []; + data_toabt.BandSWIR2 = []; + clear satu_Bv; + data_toabt.BandCirrus = BandCirrusNormal; %refresh Cirrus band. + clear BandCirrusNormal; + + %% select pure snow/ice pixels. + abs_snow = DetectAbsSnow(data_toabt.BandGreen,data_toabt.SatuGreen,ndsi,psnow,data_meta.Resolution(1)); + + if ~isnan(abs_snow) + idplcd(abs_snow==1)=0; clear abs_snow; % remove pure snow/ice pixels from all PCPs. + end + + %% detect potential cloud + ndbi = NDBI(data_toabt.BandNIR, data_toabt.BandSWIR1); + + % inputs: BandCirrus BandBT BandSWIR1 SatuGreen SatuRed + [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); + clear ndsi idplcd whiteness HOT tpw cldprob; + + data_toabt.SatuGreen = []; + data_toabt.SatuRed = []; + data_toabt.BandCirrus = []; + + %% detect potential flase positive cloud layer, including urban, coastline, and snow/ice. + pfpl = DetectPotentialFalsePositivePixels(mask, psnow, slope, ndbi, ndvi, data_toabt.BandBT,cdi, water,data_meta.Resolution(1)); + + clear ndbi ndvi; + %% remove most of commission errors from urban, bright rock, and coastline. + pcloud = ErodeCommissons(data_meta,pcloud_all,pfpl,water,cdi,erdpix); + clear cdi; + + %% detect cloud shadow + cs_final = zeros(data_meta.Dim,'uint8'); % final masks, including cloud, cloud shadow, snow, and water. + cs_final(water==1)=1; %water is fistly stacked because of its always lowest prioty. + + % note that 0.1% Landsat obersavtion is about 40,000 pixels, which will be used in the next statistic analyses. + % when potential cloud cover less than 0.1%, directly screen all PCPs out. + if sum_clr <= 40000 + fprintf('No clear pixel in this image (clear-sky pixels = %.0f%)\n',sum_clr); + pcloud=pcloud>0; + pshadow=~pcloud; + clear data_toabt; + else + fprintf('Match cloud shadows with clouds.\n'); + % detect potential cloud shadow + pshadow = DetectPotentialCloudShadow(data_meta, data_toabt.BandNIR,data_toabt.BandSWIR1,idlnd,mask,... + slope,aspect); + + data_toabt.BandNIR = []; + data_toabt.BandSWIR1 = []; + + data_bt_c=data_toabt.BandBT; + clear data_toabt; + % match cloud shadow, and return clouds and cloud shadows. + [ ~,pcloud, pshadow] = MatchCloudShadow(... + mask,pcloud,pshadow,pfpl,water, dem ,data_bt_c,t_templ,t_temph,data_meta,sum_clr,14,angles_view); + + % make buffer for final masks. + % the called cloud indicate those clouds are have highest piroity. + % This is final cloud! + [pcloud,pshadow,psnow] = BufferMasks(pcloud,cldpix,pshadow,sdpix,psnow,snpix); + end + %% stack results together. + % step 1 snow or unknow + cs_final(psnow==1)=3; % snow + % step 2 shadow above snow and everyting + cs_final(pshadow==1)=2; %shadow + % step 3 cloud above all + cs_final(pcloud==1)=4; % cloud + % mask out no data. + cs_final(mask==0)=255; % mask + + % clear pixels percentage + clr_pct=100*(1-sum(pcloud(:))/sum(mask(:))); + + %% output as geotiff. + trgt.Z=cs_final; + fmask_name=[data_meta.Name,'_Fmask4']; + trgt.name=fmask_name; + + fmask_output=fullfile(path_data,data_meta.Output,[fmask_name,'.tif']); + GRIDobj2geotiff(trgt,fmask_output); + time=toc; + time=time/60; + fprintf('%s finished (%.2f minutes)\nfor %s with %.2f%% clear pixels\n\n',... + fmask_soft_name,time,data_meta.Name,clr_pct); +% enviwrite([path_data,data_meta.Name,'_Fmask41'], cs_final, 'uint8', data_meta.Resolution, data_meta.UL, 'bsq', data_meta.ZC); +end diff --git a/autoFmaskBatch.m b/autoFmaskBatch.m new file mode 100644 index 0000000..7545e69 --- /dev/null +++ b/autoFmaskBatch.m @@ -0,0 +1,13 @@ +function autoFmaskBatch() +% This can automatically find all Landsats 4-8 and Sentinel-2 images +% (folder) and to process them one by one. + + fprintf('Fmask 4.0 batch starts ...\n'); + filepath_work = pwd; % CD to the path; or, type in the path of your working directory here. + [~, ~, paths, ~] = CheckImagesPath(filepath_work); + for i=1: length(paths) + cd(paths{i}); + fprintf('At %s.\n',paths{i}); + autoFmask('cloud',0);% 4.0 + end +end \ No newline at end of file diff --git a/findMatdetecFootprint.m b/findMatdetecFootprint.m new file mode 100644 index 0000000..be7697d --- /dev/null +++ b/findMatdetecFootprint.m @@ -0,0 +1,173 @@ +function Matdetec2 = findMatdetecFootprint(DetectFootPrint,XPsizeOut,YPsizeOut) +clear Matdetec k +for i = 1:length(DetectFootPrint.Ndect) + + IDX = knnsearch(XPsizeOut',DetectFootPrint.Data{i,1}(:,1)); + IDY = knnsearch(YPsizeOut,DetectFootPrint.Data{i,1}(:,2)); + + dum2 = single(poly2mask(double(IDX), double(IDY),length(XPsizeOut),length(XPsizeOut))) ; + clear IDX IDY; + dum2 = conv2(dum2,ones(3),'same')>0; % to fill boundary + Matdetec(:,:,i) = dum2; + clear dum* + + % find orientation of detect + slope for computing perpendicular kernel + I=nan(size(Matdetec,2),1); + for ii=1:size(Matdetec,1) + dum=find(Matdetec(ii,:,i)==1,1,'first'); + if ~isempty(dum) + I(ii,1)=dum; + end + end + clear dum; + J = [1:size(Matdetec,1)]' ; + test = ~isnan(I) & I > 1 & I < size(Matdetec,2); + warning off all % if warning => not enough point => slope=0 => good because tile boundary + k{i,1} = polyfit(J(test),I(test),1); + clear test; + + I=nan(size(Matdetec,2),1); + for ii=1:size(Matdetec,1) + dum=find(Matdetec(ii,:,i)==1,1,'last'); + if ~isempty(dum) + I(ii,1)=dum; + end + end + J = [1:size(Matdetec,1)]' ; + test = ~isnan(I) & I > 1 & I < size(Matdetec,2); + k{i,2} = polyfit(J(test),I(test),1); + clear test; + warning on all + +end + +% mediane +for i = 1:length(DetectFootPrint.Ndect)-1 + mediane = mean( [k{i,2} ; k{i+1,1}] ) ; + k{i,2} = mediane ; + k{i+1,1} = mediane ; + clear mediane; +end +J = [1:size(Matdetec,1)]' ; +I = [1:size(Matdetec,2)] ; + +[Jmat Imat] = meshgrid(I,J); +clear I J; + +Matdetec2 = nan(size(Matdetec,1),size(Matdetec,2)); +clear Matdetec; +for i = 1:length(DetectFootPrint.Ndect) + + liminf = polyval(k{i,1},Jmat); + limsup = polyval(k{i,2},Jmat); + + Matdetec2(Imat>=liminf & Imat<=limsup) = i ; + clear liminf limsup; +end +clear Imat ImatJ k; + +Matdetec2 = Matdetec2'; + +return + +%% +%% old codes +%% + + clear Matdetec k + for i = 1:length(DetectFootPrint.Ndect) + + IDX = knnsearch(XPsizeOut',DetectFootPrint.Data{i,1}(:,1)); + IDY = knnsearch(YPsizeOut,DetectFootPrint.Data{i,1}(:,2)); + + dum2 = single(poly2mask(double(IDX), double(IDY),length(XPsizeOut),length(XPsizeOut))) ; + dum2 = conv2(dum2,ones(3),'same')>0; % to fill boundary + Matdetec(:,:,i) = dum2; + clear dum* + + % find orientation of detect + slope for computing perpendicular kernel + I=nan(size(Matdetec,2),1); + for ii=1:size(Matdetec,1) + dum=find(Matdetec(ii,:,i)==1,1,'first'); + if ~isempty(dum) + I(ii,1)=dum; + end + end + J = [1:size(Matdetec,1)]' ; + test = ~isnan(I) & I > 1 & I < size(Matdetec,2); + k{i,1} = polyfit(J(test),I(test),1); + + I=nan(size(Matdetec,2),1); + for ii=1:size(Matdetec,1) + dum=find(Matdetec(ii,:,i)==1,1,'last'); + if ~isempty(dum) + I(ii,1)=dum; + end + end + J = [1:size(Matdetec,1)]' ; + test = ~isnan(I) & I > 1 & I < size(Matdetec,2); + k{i,2} = polyfit(J(test),I(test),1); + + + + % n = 100 ; + % x =1:n ; + % y = round(x*k(1)) ; + % y = y-min(y)+1 ; + % kern = zeros([max(y) n]) ; + % linearInd = sub2ind(size(kern), y, x); + % kern(linearInd)=1; + % kern = flipud(kern); + + end + + for i = 1:length(DetectFootPrint.Ndect)-1 + mediane = mean( [k{i,2} ; k{i+1,1}] ) ; + k{i,2} = mediane ; + k{i+1,1} = mediane ; + end + + figure + imagesc(Matdetec(:,:,1)) + hold on + for i = 1:length(DetectFootPrint.Ndect) + J = [1:size(Matdetec,1)]' ; +plot(polyval(k{i,2},J),J,'.g') + plot(polyval(k{i,1},J),J,'r') + end + % find orientation of detect + slope for computing perpendicular kernel + % [dum maxdetec]=max(sum(Myreshape(Matdetec,-1))); + % I=nan(size(Matdetec,2),1); + % for i=1:size(Matdetec,1) + % dum=find(Matdetec(i,:,maxdetec)==1,1,'first'); + % if ~isempty(dum) + % I(i,1)=dum; + % end + % end + % J = [1:size(Matdetec,1)]' ; + % k = polyfit(J(~isnan(I)),I(~isnan(I)),1); + % + % n = 100 ; + % x =1:n ; + % y = round(x*k(1)) ; + % y = y-min(y)+1 ; + % kern = zeros([max(y) n]) ; + % linearInd = sub2ind(size(kern), y, x); + % kern(linearInd)=1; + % kern = flipud(kern); + + + Matdetec = cat(1,Matdetec(1:100,:,:),Matdetec,Matdetec(end-99:end,:,:)); + Matdetec = cat(2,Matdetec(:,1:100,:),Matdetec,Matdetec(:,end-99:end,:)); + + % Matdetec=imresize(Matdetec,100/Psize,'bilinear'); %100m => 10m + % for i = 1:length(DetectFootPrint.Ndect) + % Matdetec2(:,:,i)=conv2(Matdetec(:,:,i),ones(100),'same'); %100m => 10m + % end +% for i = 1:length(DetectFootPrint.Ndect) +% Matdetec(:,:,i)=conv2(Matdetec(:,:,i),kern,'same'); %100m => 10m +% end +% Matdetec = Matdetec(101:end-100,101:end-100,:); + % [dum Matdetec2]=max(Matdetec2,[],3); + % Matdetec2(dum==0)=NaN; + [dum Matdetec]=max(Matdetec,[],3); diff --git a/geoimread.m b/geoimread.m new file mode 100644 index 0000000..43112f5 --- /dev/null +++ b/geoimread.m @@ -0,0 +1,399 @@ +function [A,x,y,I]=geoimread(filename,varargin) +%GEOIMREAD reads a sub region of a geotiff or geojp2 image. +% +% +%% Syntax +% +% A = geoimread(filename) +% A = geoimread(filename,xlim,ylim) +% A = geoimread(filename,latlim,lonlim) +% A = geoimread(...,buffer) +% [A,x,y,I] = geoimread(...) +% geoimread(...) +% +% +%% Description +% +% A = geoimread(filename) returns the full image given by a filename. This +% syntax is equivalent to A = geotiffread(filename). +% +% A = geoimread(filename,xlim,ylim) limits the region of the geotiff file to +% the limits given by xlim and ylim, which are map units (usually meters) relative +% to the data projection. For example, if the geotiff is projected in Texas Centric +% Mapping System/Lambert Conformal coordinates, xlim and ylim will have units of +% meters relative to the origin (100 W, 18 N). xlim and ylim can be multimensional, +% in which case the limits of the map will be taken as the limits of the limits of +% the distribution of all points in xlim, ylim. +% +% A = geoimread(filename,latlim,lonlim) if no values in xlim, ylim exceed +% normal values of latitudes and longitudes, geoimread assumes you've entered +% limits in geographic coordinates of degrees latitude and longitude. The first +% input is latitude, the second input is longitude. +% +% A = geoimread(...,buffer) adds a buffer in map units (usually meters or feet) to the +% limits of the region of interest. This may be useful if you want to load an image +% surrounding scattered lat/lon data. If you'd like an extra 2 kilometers of image around +% your data, enter 2000 as the buffer. If buffer is a two-element vector, the first +% element is applied to the left and right extents of the image, and the second element +% is applied to the top and bottom extents of the image. +% +% [A,x,y,I] = geoimread(...) also returns pixel center coordinates (x,y) of the +% output image and a geotiff info structure I. I is a useful input for projfwd and projinv. +% +% geoimread(...) without any outputs shows the output image A without loading +% any data into the workspace. +% +% +%% Examples: +% +% % Show a whole geotiff: +% geoimread('boston.tif'); +% +% % Compare results from above to a subset geotiff: +% mapx = [765884 766035 766963]; % units are feet +% mapy = [2959218 2957723 2958972]; +% geoimread('boston.tif',mapx,mapy) +% +% % Or if you have coordinates in lat/lon and you want a 500 foot buffer: +% lat = [42.3675288 42.3634246 42.3668397]; +% lon = [-71.0940009 -71.0934685 -71.0900125]; +% +% geoimread('boston.tif',lat,lon,500); +% +%% Author Info: +% +% (c) Aslak Grinsted 2014- (http://www.glaciology.net/) +% & Chad A. Greene (http://chadagreene.com/) +% +%% +% +% See also GEOTIFFREAD, GEOTIFFINFO, PIXCENTERS, and PROJFWD. + +%% CHAD'S CHANGES: +% The following changes were made by Chad A. Greene (http://chadagreene.com/) +% of the University of Texas Institute for Geophysics (UTIG) on Sept 17, 2014: +% +% * More input checking and error messages. +% +% * Clarified syntax and description in header. +% +% * The fileparts line now only writes the file extension because other outputs went unused. +% +% * If geographic coordinate limits are inferred, they are now ordered latlim,lonlim. <-- **FUNCTIONALITY CHANGE** +% +% * Limits xlim, ylim or latlim, lonlim can be scalar, vector, or matrix-- xlim is now taken +% as xlim = [min(xlim(:)) max(xlim(:))]. This will save a small step if you have some data points +% given by x,y. Now you can simply enter your data coordinates and geoimread will +% figure out the limits. +% +% * Output variable I now has correct corner coordinates for the subset image instead of +% the previous version which returned corner coordinates of the full original image. +% +% * A buffer can be specified around input limits. +% +% +% Syntax before the changes: +% geoimread('myimage.tif',[min(easting)-buffer_m max(easting)+buffer_m],... +% [min(northing)-buffer_m max(northing)+buffer_m]); +% +% Syntax after the changes: +% geoimread('myimage.tif',easting,northing,buffer_m) +% +% + +%TODO: support downsampling (ReductionLevel parameter in imread) +%TODO: use map2pix and latlon2pix instead of projfwd and pixcenters. more robust if it is a rotational coordinate system. + + +%% Set defaults: + +usegeocoordinates = false; +returnNONsubsetImage = true; +buffer_x = 0; +buffer_y = 0; + +%% Input checks: + +% Check for mapping toolbox: +% assert(license('test','map_toolbox')==1,'geoimread requires Matlab''s Mapping Toolbox.') + +% Check file type: +assert(isnumeric(filename)==0,'Input filename must be a string.') +[~,~,ext] = fileparts(filename); +switch upper(ext) + case {'.JP2' '.JPEG2000' '.GEOJP2'} + I = jp2tiffinfo(filename); + case {'.TIF' '.TIFF' '.GTIF' '.GTIFF'} + I = robustgeotiffinfo(filename); + otherwise + error('Unrecognized image file type. Must be tif, tiff, gtif, gtiff, jp2, jpeg2000, or geojp2.') +end + + +% Parse optional inputs: +if nargin>1 + returnNONsubsetImage = false; + assert(nargin>2,'If you specify an xlim or latlim, you must specify a corresponding ylim or lonlim.') + + % Parse limits: + xlimOrLatlim = varargin{1}(:); + ylimOrLonlim = varargin{2}(:); + + assert(isnumeric(xlimOrLatlim)==1,'Input xlim or latlim must be numeric.') + assert(isnumeric(ylimOrLonlim)==1,'Input ylim or lonlim must be numeric.') + + % Assume geo coordinates if no input limits exceed normal lat/lon values: + if max(abs(xlimOrLatlim))<=90 && max(abs(ylimOrLonlim))<=360 + usegeocoordinates = true; + end + + % Parse buffer: + if nargin>3 + buffer_m = varargin{3}; + assert(isnumeric(buffer_m)==1,'Buffer value must be either a scalar or a two-element array.') + assert(numel(buffer_m)<3,'Buffer value must be either a scalar or a two-element array.') + buffer_x = buffer_m(1); + if isscalar(buffer_m) + buffer_y = buffer_m(1); + else + buffer_y = buffer_m(2); + end + end + + if nargin>4 + error('Too many inputs in geoimread.') + end + +end + + +%% Begin work: + +% Get pixel coordinates of full (non-subset) image: +[x,y]=robustpixcenters(I); + +if nargout==0 + fprintf('No outputs specified.\n') + fprintf('X-range: %.1f:%.1f:%.1f\n',x(1),x(2)-x(1),x(end)) + fprintf('Y-range: %.1f:%.1f:%.1f\n',y(1),y(2)-y(1),y(end)) + clear A x y I + return +end + +% Set xlim and ylim depending on user inputs: +if returnNONsubsetImage + xlimOrLatlim = x(:); + ylimOrLonlim = y(:); +end + +if usegeocoordinates + % lat/lon limits switch to x/y limits here: + if ~strcmp(I.ModelType,'ModelTypeGeographic') + 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.') + [xlimOrLatlim,ylimOrLonlim]=projfwd(I,xlimOrLatlim,ylimOrLonlim); + end +end + + +xlim = [min(xlimOrLatlim)-buffer_x max(xlimOrLatlim)+buffer_x]; +ylim = [min(ylimOrLonlim)-buffer_y max(ylimOrLonlim)+buffer_y]; + + +% Rows and columns of pixels to read: +rows=find((y>=ylim(1))&(y<=ylim(2))); +cols=find((x>=xlim(1))&(x<=xlim(2))); + + + + +%% Display messages if region of interest is partly or wholly outside the image region: + +if xlim(1)max(x) + disp('geoimread limits extend beyond the available image output in the x direction.') +end + +if ylim(1)max(y) + disp('geoimread limits extend beyond the available image output in the y direction.') +end + +if isempty(rows)||isempty(cols) + error('No image coordinates can be found inside the specified limits.') +end + +%% Load region of interest: +reductionlevel=0; +if reductionlevel==0 + rows=sort(rows([1 end])); + cols=sort(cols([1 end])); +else + %% Load region of interest: + dpx=2^reductionlevel; + rows=round(rows/dpx);cols=round(cols/dpx); + + rows=sort(rows([1 end])); + cols=sort(cols([1 end])); +end +x=x(cols(1):cols(end)); +y=y(rows(1):rows(end)); + +A=imread(filename,'PixelRegion',{rows cols}); + + +%% Update info structure to more accurately reflect the new image: + +if nargout == 4 + I.FileSize = numel(A); + I.Height = size(A,1); + I.Width = size(A,2); + try + I.TiePoints.WorldPoints.X = x(1); + I.TiePoints.WorldPoints.Y = y(1); + I.SpatialRef.RasterSize = [size(A,1),size(A,2)]; + I.RefMatrix(3,1) = x(1); + I.RefMatrix(3,2) = y(1); + I.BoundingBox = [min(x) min(y); max(x) max(y)]; + I.CornerCoords.X = [min(x) max(x) max(x) min(x)]; + I.CornerCoords.Y = [max(y) max(y) min(y) min(y)]; + %TODO: check whether GTRasterTypeGeoKey is RasterPixelIsArea or RasterPixelIsPoint + I.CornerCoords.Row = .5 + [0 0 size(A,1) size(A,1)]; %TODO: is this .5 always true? + I.CornerCoords.Col = .5 + [0 size(A,2) size(A,2) 0]; + [I.CornerCoords.Lat,I.CornerCoords.Lon] = projinv(I,I.CornerCoords.X,I.CornerCoords.Y); + I.GeoTIFFTags.ModelTiepointTag(4) = x(1); + I.GeoTIFFTags.ModelTiepointTag(5) = y(1); + I.SpatialRef.XLimWorld = [min(x),max(x)]; + I.SpatialRef.YLimWorld = [min(y),max(y)]; + catch,end +end + +%% Clean up: + + + + + + +function I=jp2tiffinfo(fname) +% +% This is a function that attempts to extract the geo-info from a jp2 file. +% There are two "standards" for how this can be stored: +% * GeoJP2: an embedded geotiff is stored in a "uuid" box with a specific id. +% * GMLJP2: an embedded gml file is in an asoc box. +% (extremely annoying that there are multiple "standards".) +% + +%Documents: +% JP2 standard: http://www.jpeg.org/public/15444-1annexi.pdf +% GEOJP2 standard: http://wiki.opf-labs.org/download/attachments/11337762/15444-1annexi.pdf +% GMLJP2 standard: http://www.opengeospatial.org/standards/gmljp2 + +fid=fopen(fname,'r','ieee-be'); +lblcontents=''; +while ~feof(fid) + lbox=fread(fid,1,'uint32'); + type=fread(fid,[1 4],'uint8=>char'); + switch lbox + case 1 + lbox=fread(fid,1,'uint64');lbox=lbox-16; + case 2:7 %"reserved reserved for ISO use" (????) + lbox=8;%No idea why ... there is no info on how to interpret values 2-7. + %Setting this to 8 works for sentinel-2 images. + otherwise + lbox=lbox-8; + end + + switch type + case 'asoc' + %the asoc box is a container with other boxes. Set lbox to zero + %so that we also parse the containing boxes. + lbox=0; + case 'lbl ' + %in gmljp2 the xml box is preceded by a lbl box. Read the + %lbl box contents for later... + lblcontents=fread(fid,[1 lbox],'uint8=>char'); + lbox=0; + case 'xml ' + if (strfind(lblcontents,'gml')>0) %gmljp2: + gmlcontents=fread(fid,[1 lbox],'uint8=>char'); + fout=fopen('.temp.xml','w'); + fwrite(fout,gmlcontents); + fclose(fout); + X=xmlread('.temp.xml'); + delete('.temp.xml'); + + Elements=X.getElementsByTagName('gml:origin'); + origin=str2num(Elements.item(0).getTextContent()); + Elements=X.getElementsByTagName('gml:offsetVector'); + R=[]; + for ii=1:Elements.getLength + R{ii}=str2num(Elements.item(ii-1).getTextContent()); + end + R=cell2mat(R'); + %ModelPixelScaleTag: [15 15 0] + %ModelTiepointTag: [0 0 0 4.695e+05 7240500 0] + assert((R(1,2)==0)&(R(2,1)==0),'unexpected offsetvector in GML'); + I=imfinfo(fname); + I.GML=gmlcontents; + I.ModelPixelScaleTag=[R(1,1) -R(2,2) 0]; %order and sign.. + I.ModelTiepointTag=[0 0 0 origin 0]; + fclose(fid); + return %we have what we need - return + end + case 'uuid' % + uuid=fread(fid,[1 16],'uint8=>char'); + lbox=lbox-16; + geo=[177 75 248 189 8 61 75 67 165 174 140 215 213 166 206 3]; + if all(uuid==geo) + fout=fopen('.temp.tif','w'); + contents=fread(fid,lbox,'uint8');lbox=0; + fwrite(fout,contents); + fclose(fout); + fclose(fid); + I=geotiffinfo('.temp.tif'); + m=imfinfo(fname); % a little silly to use imfinfo when I already have a tag reader + I.Height=m.Height; + I.Width=m.Width; + delete('.temp.tif'); + + fclose(fid); + return %we have what we need - return + end + end + + fseek(fid,lbox,0); + +end +fclose(fid); + + + +%--- BELOW is to make geoimread work even if no mapping toolbox (but with reduced functionality) --- + +function I = robustgeotiffinfo(fname) +if license('test','map_toolbox') + I=geotiffinfo(fname); +else + I=imfinfo(fname); + % I.ModelType='ModelTypeProjected'; %TODO: fix + % %TODO: generate home-made refmatrix(?).... + % if isfield(tags, 'ModelTransformationTag') && numel(tags.ModelTransformationTag) >= 8 + % geoimread does not work for rotated systems + % + % else %use ModelPixelScaleTag instead + % dx = I.ModelPixelScaleTag(1); dy = -I.ModelPixelScaleTag(2); + % x0 = I.ModelTiepointTag(4) - dx * I.ModelTiepointTag(1); + % y0 = I.ModelTiepointTag(5) - dy * I.ModelTiepointTag(2); + % J = [dx 0; 0 dy]; + % end + % I.RefMatrix=[flipud(J); x0-J(1,1)-J(1,2)]; +end + +function [x,y]=robustpixcenters(I) +if license('test','map_toolbox') && isfield(I,'GeoTIFFCodes') + [x,y]=pixcenters(I); +else + %I have not read documentation... but this only works for rectilinear systems. + assert(I.ModelPixelScaleTag(3)==0,'unexpected ModelPixelScaleTag format.'); + assert(all(I.ModelTiepointTag(1:3)==0),'unexpected ModelTiepointTag format.'); + x=((0:I.Width-1)-I.ModelTiepointTag(1))*I.ModelPixelScaleTag(1)+I.ModelTiepointTag(4); + y=((0:I.Height-1)-I.ModelTiepointTag(2))*-I.ModelPixelScaleTag(2)+I.ModelTiepointTag(5); +end \ No newline at end of file diff --git a/getRealCloudPosition.m b/getRealCloudPosition.m new file mode 100644 index 0000000..c6d29d5 --- /dev/null +++ b/getRealCloudPosition.m @@ -0,0 +1,15 @@ +function [x_new,y_new] = getRealCloudPosition(x,y,h,A,B,C,omiga_par,omiga_per,sensor_heigh_bias) + % imput "x",j col + % imput "y",i row + % imput cloud height "h" + H=705000-sensor_heigh_bias; % average Landsat 7 height (m) is 705000, but the height of cloud's surface should be excluded. + dist=(A*x+B*y+C)/((A^2+B^2)^(0.5));% from the cetral perpendicular (unit: pixel) + dist_par=dist/cos(omiga_per-omiga_par); + dist_move=dist_par.*double(h)/double(H); % cloud move distance (m); + delt_x=dist_move*cos(omiga_par); + delt_y=dist_move*sin(omiga_par); + + x_new=x+delt_x; % new x, j + y_new=y+delt_y; % new y, i +end + diff --git a/getRealCloudPositionS2.m b/getRealCloudPositionS2.m new file mode 100644 index 0000000..e94b843 --- /dev/null +++ b/getRealCloudPositionS2.m @@ -0,0 +1,22 @@ +function [x_new,y_new]=getRealCloudPositionS2(x,y,h,VZAxy,VAAxy,resolu) + % imput "x",j col + % imput "y",i row + % imput cloud height "h" + % H=786000; % average S2 height (m) + +% dist_move = h * tan(VZAxy) ./ resolu ; % but the height of cloud's surface should be excluded. +% +% % dist=(A*x+B*y+C)/((A^2+B^2)^(0.5));% from the cetral perpendicular (unit: pixel) +% % dist_par=dist/cos(omiga_per-omiga_par); +% % dist_move=dist_par.*h/H; % cloud move distance (m); +% delt_x=dist_move(2)*cos(pi/2-VAAxy); +% delt_y=dist_move(1)*-sin(pi/2-VAAxy); + + dist_move = h .* tan(VZAxy) ./ resolu ; % but the height of cloud's surface should be excluded. + + delt_x=dist_move(2).*cos(pi/2-VAAxy); + delt_y=dist_move(1).*-sin(pi/2-VAAxy); + + x_new=x+delt_x; % new x, j + y_new=y+delt_y; % new y, i +end \ No newline at end of file diff --git a/getSensorViewGeo.m b/getSensorViewGeo.m new file mode 100644 index 0000000..d011876 --- /dev/null +++ b/getSensorViewGeo.m @@ -0,0 +1,23 @@ +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 ) +% imput "x",j +% imput "y",i +% imput cloud height "h" + + x_u=(x_ul+x_ur)/2; + x_l=(x_ll+x_lr)/2; + y_u=(y_ul+y_ur)/2; + y_l=(y_ll+y_lr)/2; + + K_ulr=(y_ul-y_ur)/(x_ul-x_ur); % get k of the upper left and right points + K_llr=(y_ll-y_lr)/(x_ll-x_lr); % get k of the lower left and right points + K_aver=(K_ulr+K_llr)/2; + omiga_par=atan(K_aver); % get the angle of scan lines k (in pi) + + % AX(j)+BY(i)+C=0 + A=y_u-y_l; + B=x_l-x_u; + C=y_l*x_u-x_l*y_u; + + omiga_per=atan(B/A); % get the angle which is perpendicular to the track +end + diff --git a/getcoordinates.m b/getcoordinates.m new file mode 100644 index 0000000..093c117 --- /dev/null +++ b/getcoordinates.m @@ -0,0 +1,32 @@ +function [x,y] = getcoordinates(DEM) + +%GETCOORDINATES get coordinate vectors of an instance of GRIDobj +% +% Syntax +% +% [x,y] = getcoordinates(DEM) +% +% Input arguments +% +% DEM grid (class: GRIDobj) +% +% Output arguments +% +% x coordinate vector in x direction (row vector) +% y coordinate vector in y direction (column vector) +% +% Example +% +% DEM = GRIDobj('srtm_bigtujunga30m_utm11.tif'); +% [x,y] = getcoordinates(DEM); +% surf(x,y,double(DEM.Z)) +% axis image; shading interp; camlight +% +% +% +% See also: GRIDobj2mat +% +% Author: Wolfgang Schwanghart (w.schwanghart[at]geo.uni-potsdam.de) +% Date: 6. February, 2013 + +[x,y] = refmat2XY(DEM.refmat,DEM.size); \ No newline at end of file diff --git a/imcircle.m b/imcircle.m new file mode 100644 index 0000000..ca5af78 --- /dev/null +++ b/imcircle.m @@ -0,0 +1,99 @@ +function y = imcircle(n) + +% y = imcircle(n); +% +% Draw a solid circle of ones with diameter n pixels +% in a square of zero-valued pixels. +% +% Example: y = imcircle(50); +% +% For a hollow circle (line circle) of diameter n, +% add the instruction bwmorph from the Image Processing +% Toolbox. +% +% Example: y = bwmorph(imcircle(50),'remove'); + +if rem(n,1) > 0, + disp(sprintf('n is not an integer and has been rounded to %1.0f',round(n))) + n = round(n); +end + +if n < 1 % invalid n + error('n must be at least 1') + +elseif n < 4 % trivial n + y = ones(n); + +elseif rem(n,2) == 0, % even n + + DIAMETER = n; + diameter = n-1; + RADIUS = DIAMETER/2; + radius = diameter/2; + height_45 = round(radius/sqrt(2)); + width = zeros(1,RADIUS); + semicircle = zeros(DIAMETER,RADIUS); + + for i = 1 : height_45 + upward = i - 0.5; + sine = upward/radius; + cosine = sqrt(1-sine^2); + width(i) = ceil(cosine * radius); + end + + array = width(1:height_45)-height_45; + + for j = max(array):-1:min(array) + width(height_45 + j) = max(find(array == j)); + end + + if min(width) == 0 + index = find(width == 0); + width(index) = round(mean([width(index-1) width(index+1)])); + end + + width = [fliplr(width) width]; + + for k = 1 : DIAMETER + semicircle(k,1:width(k)) = ones(1,width(k)); + end + + y = [fliplr(semicircle) semicircle]; + +else % odd n + + DIAMETER = n; + diameter = n-1; + RADIUS = DIAMETER/2; + radius = diameter/2; + semicircle = zeros(DIAMETER,radius); + height_45 = round(radius/sqrt(2) - 0.5); + width = zeros(1,radius); + + for i = 1 : height_45 + upward = i; + sine = upward/radius; + cosine = sqrt(1-sine^2); + width(i) = ceil(cosine * radius - 0.5); + end + + array = width(1:height_45) - height_45; + + for j = max(array):-1:min(array) + width(height_45 + j) = max(find(array == j)); + end + + if min(width) == 0 + index = find(width == 0); + width(index) = round(mean([width(index-1) width(index+1)])); + end + + width = [fliplr(width) max(width) width]; + + for k = 1 : DIAMETER + semicircle(k,1:width(k)) = ones(1,width(k)); + end + + y = [fliplr(semicircle) ones(DIAMETER,1) semicircle]; + +end diff --git a/inpaint_nans.m b/inpaint_nans.m new file mode 100644 index 0000000..2460b51 --- /dev/null +++ b/inpaint_nans.m @@ -0,0 +1 @@ +function B=inpaint_nans(A,method) % INPAINT_NANS: in-paints over nans in an array % usage: B=INPAINT_NANS(A) % default method % usage: B=INPAINT_NANS(A,method) % specify method used % % Solves approximation to one of several pdes to % interpolate and extrapolate holes in an array % % arguments (input): % A - nxm array with some NaNs to be filled in % % method - (OPTIONAL) scalar numeric flag - specifies % which approach (or physical metaphor to use % for the interpolation.) All methods are capable % of extrapolation, some are better than others. % There are also speed differences, as well as % accuracy differences for smooth surfaces. % % methods {0,1,2} use a simple plate metaphor. % method 3 uses a better plate equation, % but may be much slower and uses % more memory. % method 4 uses a spring metaphor. % method 5 is an 8 neighbor average, with no % rationale behind it compared to the % other methods. I do not recommend % its use. % % method == 0 --> (DEFAULT) see method 1, but % this method does not build as large of a % linear system in the case of only a few % NaNs in a large array. % Extrapolation behavior is linear. % % method == 1 --> simple approach, applies del^2 % over the entire array, then drops those parts % of the array which do not have any contact with % NaNs. Uses a least squares approach, but it % does not modify known values. % In the case of small arrays, this method is % quite fast as it does very little extra work. % Extrapolation behavior is linear. % % method == 2 --> uses del^2, but solving a direct % linear system of equations for nan elements. % This method will be the fastest possible for % large systems since it uses the sparsest % possible system of equations. Not a least % squares approach, so it may be least robust % to noise on the boundaries of any holes. % This method will also be least able to % interpolate accurately for smooth surfaces. % Extrapolation behavior is linear. % % Note: method 2 has problems in 1-d, so this % method is disabled for vector inputs. % % method == 3 --+ See method 0, but uses del^4 for % the interpolating operator. This may result % in more accurate interpolations, at some cost % in speed. % % method == 4 --+ Uses a spring metaphor. Assumes % springs (with a nominal length of zero) % connect each node with every neighbor % (horizontally, vertically and diagonally) % Since each node tries to be like its neighbors, % extrapolation is as a constant function where % this is consistent with the neighboring nodes. % % method == 5 --+ See method 2, but use an average % of the 8 nearest neighbors to any element. % This method is NOT recommended for use. % % % arguments (output): % B - nxm array with NaNs replaced % % % Example: % [x,y] = meshgrid(0:.01:1); % z0 = exp(x+y); % znan = z0; % znan(20:50,40:70) = NaN; % znan(30:90,5:10) = NaN; % znan(70:75,40:90) = NaN; % % z = inpaint_nans(znan); % % % See also: griddata, interp1 % % Author: John D'Errico % e-mail address: woodchips@rochester.rr.com % Release: 2 % Release date: 4/15/06 % I always need to know which elements are NaN, % and what size the array is for any method [n,m]=size(A); A=A(:); nm=n*m; k=isnan(A(:)); % list the nodes which are known, and which will % be interpolated nan_list=find(k); known_list=find(~k); % how many nans overall nan_count=length(nan_list); % convert NaN indices to (r,c) form % nan_list==find(k) are the unrolled (linear) indices % (row,column) form [nr,nc]=ind2sub([n,m],nan_list); % both forms of index in one array: % column 1 == unrolled index % column 2 == row index % column 3 == column index nan_list=[nan_list,nr,nc]; % supply default method if (nargin<2) || isempty(method) method = 0; elseif ~ismember(method,0:5) error 'If supplied, method must be one of: {0,1,2,3,4,5}.' end % for different methods switch method case 0 % The same as method == 1, except only work on those % elements which are NaN, or at least touch a NaN. % is it 1-d or 2-d? if (m == 1) || (n == 1) % really a 1-d case work_list = nan_list(:,1); work_list = unique([work_list;work_list - 1;work_list + 1]); work_list(work_list <= 1) = []; work_list(work_list >= nm) = []; nw = numel(work_list); u = (1:nw)'; fda = sparse(repmat(u,1,3),bsxfun(@plus,work_list,-1:1), ... repmat([1 -2 1],nw,1),nw,nm); else % a 2-d case % horizontal and vertical neighbors only talks_to = [-1 0;0 -1;1 0;0 1]; neighbors_list=identify_neighbors(n,m,nan_list,talks_to); % list of all nodes we have identified all_list=[nan_list;neighbors_list]; % generate sparse array with second partials on row % variable for each element in either list, but only % for those nodes which have a row index > 1 or < n L = find((all_list(:,2) > 1) & (all_list(:,2) < n)); nl=length(L); if nl>0 fda=sparse(repmat(all_list(L,1),1,3), ... repmat(all_list(L,1),1,3)+repmat([-1 0 1],nl,1), ... repmat([1 -2 1],nl,1),nm,nm); else fda=spalloc(n*m,n*m,size(all_list,1)*5); end % 2nd partials on column index L = find((all_list(:,3) > 1) & (all_list(:,3) < m)); nl=length(L); if nl>0 fda=fda+sparse(repmat(all_list(L,1),1,3), ... repmat(all_list(L,1),1,3)+repmat([-n 0 n],nl,1), ... repmat([1 -2 1],nl,1),nm,nm); end end % eliminate knowns rhs=-fda(:,known_list)*A(known_list); k=find(any(fda(:,nan_list(:,1)),2)); % and solve... B=A; B(nan_list(:,1))=fda(k,nan_list(:,1))\rhs(k); case 1 % least squares approach with del^2. Build system % for every array element as an unknown, and then % eliminate those which are knowns. % Build sparse matrix approximating del^2 for % every element in A. % is it 1-d or 2-d? if (m == 1) || (n == 1) % a 1-d case u = (1:(nm-2))'; fda = sparse(repmat(u,1,3),bsxfun(@plus,u,0:2), ... repmat([1 -2 1],nm-2,1),nm-2,nm); else % a 2-d case % Compute finite difference for second partials % on row variable first [i,j]=ndgrid(2:(n-1),1:m); ind=i(:)+(j(:)-1)*n; np=(n-2)*m; fda=sparse(repmat(ind,1,3),[ind-1,ind,ind+1], ... repmat([1 -2 1],np,1),n*m,n*m); % now second partials on column variable [i,j]=ndgrid(1:n,2:(m-1)); ind=i(:)+(j(:)-1)*n; np=n*(m-2); fda=fda+sparse(repmat(ind,1,3),[ind-n,ind,ind+n], ... repmat([1 -2 1],np,1),nm,nm); end % eliminate knowns rhs=-fda(:,known_list)*A(known_list); k=find(any(fda(:,nan_list),2)); % and solve... B=A; B(nan_list(:,1))=fda(k,nan_list(:,1))\rhs(k); case 2 % Direct solve for del^2 BVP across holes % generate sparse array with second partials on row % variable for each nan element, only for those nodes % which have a row index > 1 or < n % is it 1-d or 2-d? if (m == 1) || (n == 1) % really just a 1-d case error('Method 2 has problems for vector input. Please use another method.') else % a 2-d case L = find((nan_list(:,2) > 1) & (nan_list(:,2) < n)); nl=length(L); if nl>0 fda=sparse(repmat(nan_list(L,1),1,3), ... repmat(nan_list(L,1),1,3)+repmat([-1 0 1],nl,1), ... repmat([1 -2 1],nl,1),n*m,n*m); else fda=spalloc(n*m,n*m,size(nan_list,1)*5); end % 2nd partials on column index L = find((nan_list(:,3) > 1) & (nan_list(:,3) < m)); nl=length(L); if nl>0 fda=fda+sparse(repmat(nan_list(L,1),1,3), ... repmat(nan_list(L,1),1,3)+repmat([-n 0 n],nl,1), ... repmat([1 -2 1],nl,1),n*m,n*m); end % fix boundary conditions at extreme corners % of the array in case there were nans there if ismember(1,nan_list(:,1)) fda(1,[1 2 n+1])=[-2 1 1]; end if ismember(n,nan_list(:,1)) fda(n,[n, n-1,n+n])=[-2 1 1]; end if ismember(nm-n+1,nan_list(:,1)) fda(nm-n+1,[nm-n+1,nm-n+2,nm-n])=[-2 1 1]; end if ismember(nm,nan_list(:,1)) fda(nm,[nm,nm-1,nm-n])=[-2 1 1]; end % eliminate knowns rhs=-fda(:,known_list)*A(known_list); % and solve... B=A; k=nan_list(:,1); B(k)=fda(k,k)\rhs(k); end case 3 % The same as method == 0, except uses del^4 as the % interpolating operator. % del^4 template of neighbors talks_to = [-2 0;-1 -1;-1 0;-1 1;0 -2;0 -1; ... 0 1;0 2;1 -1;1 0;1 1;2 0]; neighbors_list=identify_neighbors(n,m,nan_list,talks_to); % list of all nodes we have identified all_list=[nan_list;neighbors_list]; % generate sparse array with del^4, but only % for those nodes which have a row & column index % >= 3 or <= n-2 L = find( (all_list(:,2) >= 3) & ... (all_list(:,2) <= (n-2)) & ... (all_list(:,3) >= 3) & ... (all_list(:,3) <= (m-2))); nl=length(L); if nl>0 % do the entire template at once fda=sparse(repmat(all_list(L,1),1,13), ... repmat(all_list(L,1),1,13) + ... repmat([-2*n,-n-1,-n,-n+1,-2,-1,0,1,2,n-1,n,n+1,2*n],nl,1), ... repmat([1 2 -8 2 1 -8 20 -8 1 2 -8 2 1],nl,1),nm,nm); else fda=spalloc(n*m,n*m,size(all_list,1)*5); end % on the boundaries, reduce the order around the edges L = find((((all_list(:,2) == 2) | ... (all_list(:,2) == (n-1))) & ... (all_list(:,3) >= 2) & ... (all_list(:,3) <= (m-1))) | ... (((all_list(:,3) == 2) | ... (all_list(:,3) == (m-1))) & ... (all_list(:,2) >= 2) & ... (all_list(:,2) <= (n-1)))); nl=length(L); if nl>0 fda=fda+sparse(repmat(all_list(L,1),1,5), ... repmat(all_list(L,1),1,5) + ... repmat([-n,-1,0,+1,n],nl,1), ... repmat([1 1 -4 1 1],nl,1),nm,nm); end L = find( ((all_list(:,2) == 1) | ... (all_list(:,2) == n)) & ... (all_list(:,3) >= 2) & ... (all_list(:,3) <= (m-1))); nl=length(L); if nl>0 fda=fda+sparse(repmat(all_list(L,1),1,3), ... repmat(all_list(L,1),1,3) + ... repmat([-n,0,n],nl,1), ... repmat([1 -2 1],nl,1),nm,nm); end L = find( ((all_list(:,3) == 1) | ... (all_list(:,3) == m)) & ... (all_list(:,2) >= 2) & ... (all_list(:,2) <= (n-1))); nl=length(L); if nl>0 fda=fda+sparse(repmat(all_list(L,1),1,3), ... repmat(all_list(L,1),1,3) + ... repmat([-1,0,1],nl,1), ... repmat([1 -2 1],nl,1),nm,nm); end % eliminate knowns rhs=-fda(:,known_list)*A(known_list); k=find(any(fda(:,nan_list(:,1)),2)); % and solve... B=A; B(nan_list(:,1))=fda(k,nan_list(:,1))\rhs(k); case 4 % Spring analogy % interpolating operator. % list of all springs between a node and a horizontal % or vertical neighbor hv_list=[-1 -1 0;1 1 0;-n 0 -1;n 0 1]; hv_springs=[]; for i=1:4 hvs=nan_list+repmat(hv_list(i,:),nan_count,1); k=(hvs(:,2)>=1) & (hvs(:,2)<=n) & (hvs(:,3)>=1) & (hvs(:,3)<=m); hv_springs=[hv_springs;[nan_list(k,1),hvs(k,1)]]; end % delete replicate springs hv_springs=unique(sort(hv_springs,2),'rows'); % build sparse matrix of connections, springs % connecting diagonal neighbors are weaker than % the horizontal and vertical springs nhv=size(hv_springs,1); springs=sparse(repmat((1:nhv)',1,2),hv_springs, ... repmat([1 -1],nhv,1),nhv,nm); % eliminate knowns rhs=-springs(:,known_list)*A(known_list); % and solve... B=A; B(nan_list(:,1))=springs(:,nan_list(:,1))\rhs; case 5 % Average of 8 nearest neighbors % generate sparse array to average 8 nearest neighbors % for each nan element, be careful around edges fda=spalloc(n*m,n*m,size(nan_list,1)*9); % -1,-1 L = find((nan_list(:,2) > 1) & (nan_list(:,3) > 1)); nl=length(L); if nl>0 fda=fda+sparse(repmat(nan_list(L,1),1,2), ... repmat(nan_list(L,1),1,2)+repmat([-n-1, 0],nl,1), ... repmat([1 -1],nl,1),n*m,n*m); end % 0,-1 L = find(nan_list(:,3) > 1); nl=length(L); if nl>0 fda=fda+sparse(repmat(nan_list(L,1),1,2), ... repmat(nan_list(L,1),1,2)+repmat([-n, 0],nl,1), ... repmat([1 -1],nl,1),n*m,n*m); end % +1,-1 L = find((nan_list(:,2) < n) & (nan_list(:,3) > 1)); nl=length(L); if nl>0 fda=fda+sparse(repmat(nan_list(L,1),1,2), ... repmat(nan_list(L,1),1,2)+repmat([-n+1, 0],nl,1), ... repmat([1 -1],nl,1),n*m,n*m); end % -1,0 L = find(nan_list(:,2) > 1); nl=length(L); if nl>0 fda=fda+sparse(repmat(nan_list(L,1),1,2), ... repmat(nan_list(L,1),1,2)+repmat([-1, 0],nl,1), ... repmat([1 -1],nl,1),n*m,n*m); end % +1,0 L = find(nan_list(:,2) < n); nl=length(L); if nl>0 fda=fda+sparse(repmat(nan_list(L,1),1,2), ... repmat(nan_list(L,1),1,2)+repmat([1, 0],nl,1), ... repmat([1 -1],nl,1),n*m,n*m); end % -1,+1 L = find((nan_list(:,2) > 1) & (nan_list(:,3) < m)); nl=length(L); if nl>0 fda=fda+sparse(repmat(nan_list(L,1),1,2), ... repmat(nan_list(L,1),1,2)+repmat([n-1, 0],nl,1), ... repmat([1 -1],nl,1),n*m,n*m); end % 0,+1 L = find(nan_list(:,3) < m); nl=length(L); if nl>0 fda=fda+sparse(repmat(nan_list(L,1),1,2), ... repmat(nan_list(L,1),1,2)+repmat([n, 0],nl,1), ... repmat([1 -1],nl,1),n*m,n*m); end % +1,+1 L = find((nan_list(:,2) < n) & (nan_list(:,3) < m)); nl=length(L); if nl>0 fda=fda+sparse(repmat(nan_list(L,1),1,2), ... repmat(nan_list(L,1),1,2)+repmat([n+1, 0],nl,1), ... repmat([1 -1],nl,1),n*m,n*m); end % eliminate knowns rhs=-fda(:,known_list)*A(known_list); % and solve... B=A; k=nan_list(:,1); B(k)=fda(k,k)\rhs(k); end % all done, make sure that B is the same shape as % A was when we came in. B=reshape(B,n,m); % ==================================================== % end of main function % ==================================================== % ==================================================== % begin subfunctions % ==================================================== function neighbors_list=identify_neighbors(n,m,nan_list,talks_to) % identify_neighbors: identifies all the neighbors of % those nodes in nan_list, not including the nans % themselves % % arguments (input): % n,m - scalar - [n,m]=size(A), where A is the % array to be interpolated % nan_list - array - list of every nan element in A % nan_list(i,1) == linear index of i'th nan element % nan_list(i,2) == row index of i'th nan element % nan_list(i,3) == column index of i'th nan element % talks_to - px2 array - defines which nodes communicate % with each other, i.e., which nodes are neighbors. % % talks_to(i,1) - defines the offset in the row % dimension of a neighbor % talks_to(i,2) - defines the offset in the column % dimension of a neighbor % % For example, talks_to = [-1 0;0 -1;1 0;0 1] % means that each node talks only to its immediate % neighbors horizontally and vertically. % % arguments(output): % neighbors_list - array - list of all neighbors of % all the nodes in nan_list if ~isempty(nan_list) % use the definition of a neighbor in talks_to nan_count=size(nan_list,1); talk_count=size(talks_to,1); nn=zeros(nan_count*talk_count,2); j=[1,nan_count]; for i=1:talk_count nn(j(1):j(2),:)=nan_list(:,2:3) + ... repmat(talks_to(i,:),nan_count,1); j=j+nan_count; end % drop those nodes which fall outside the bounds of the % original array L = (nn(:,1)<1)|(nn(:,1)>n)|(nn(:,2)<1)|(nn(:,2)>m); nn(L,:)=[]; % form the same format 3 column array as nan_list neighbors_list=[sub2ind([n,m],nn(:,1),nn(:,2)),nn]; % delete replicates in the neighbors list neighbors_list=unique(neighbors_list,'rows'); % and delete those which are also in the list of NaNs. neighbors_list=setdiff(neighbors_list,nan_list,'rows'); else neighbors_list=[]; end \ No newline at end of file diff --git a/lndhdrread.m b/lndhdrread.m new file mode 100644 index 0000000..95e8c93 --- /dev/null +++ b/lndhdrread.m @@ -0,0 +1,371 @@ +function [Lmax,Lmin,Qcalmax,Qcalmin,Refmax,Refmin,ijdim_ref,ijdim_thm,reso_ref,... + reso_thm,ul,bbox,zen,azi,zc,Lnum,doy]=lndhdrread(fullfile_path,filename) +% Revisions: +% Read in the latitude and longtitude for automate downloading DEM (Shi 7/9/2017) +% Read in the product level for Landsat data (Shi 28/03/2016) +% Read in the metadata for Landsat 8 (Zhe 04/04/2013) +% Read in the old or new metadata for Landsat TM/ETM+ images (Zhe 10/18/2012) +% [Lmax,Lmin,Qcalmax,Qcalmin,Refmax,Refmin,ijdim_ref,ijdim_thm,reso_ref,... +% reso_thm,ul,zen,azi,zc,Lnum,doy]=lndhdrread(filename) +% Where: +% Inputs: +% filename='L*MTL.txt'; +% Outputs: +% 1) Lmax = Max radiances; +% 2) Lmin = Min radiances; +% 3) Qcalmax = Max calibrated DNs; +% 4) Qcalmin = Min calibrated DNs; +% 5) ijdim_ref = [nrows,ncols]; % dimension of optical bands +% 6) ijdim_ref = [nrows,ncols]; % dimension of thermal band +% 7) reo_ref = 28/30; % resolution of optical bands +% 8) reo_thm = 60/120; % resolution of thermal band +% 9) ul = [upperleft_mapx upperleft_mapy]; +% 10) zen = solar zenith angle (degrees); +% 11) azi = solar azimuth angle (degrees); +% 12) zc = Zone Number +% 13) Lnum = 4,5,or 7; Landsat sensor number +% 14) doy = day of year (1,2,3,...,356); +% 15) pro_type=PRODUCT_TYPE (L1G: DEM bias; others: no DEM bias). +% +%% +% open and read hdr file +fid_in=fopen(fullfile(fullfile_path,filename),'r'); +geo_char=fscanf(fid_in,'%c',inf); +fclose(fid_in); +geo_char=geo_char'; +geo_str=strread(geo_char,'%s'); + +% initialize Refmax & Refmin +Refmax = -1; +Refmin = -1; + +% Identify Landsat Number (Lnum = 4, 5, 7, or 8) +LID=char(geo_str(strmatch('SPACECRAFT_ID',geo_str)+2)); +Lnum=str2double(LID(end-1)); + +if Lnum >= 4 && Lnum <=7 + % determine the metadata type "old" or "new" + if isempty(strmatch('LANDSAT_SCENE_ID',geo_str)) == false % "new" MTL.txt + % read in LMAX + Lmax_B1 = str2double(geo_str(strmatch('RADIANCE_MAXIMUM_BAND_1',geo_str)+2)); + Lmax_B2 = str2double(geo_str(strmatch('RADIANCE_MAXIMUM_BAND_2',geo_str)+2)); + Lmax_B3 = str2double(geo_str(strmatch('RADIANCE_MAXIMUM_BAND_3',geo_str)+2)); + Lmax_B4 = str2double(geo_str(strmatch('RADIANCE_MAXIMUM_BAND_4',geo_str)+2)); + Lmax_B5 = str2double(geo_str(strmatch('RADIANCE_MAXIMUM_BAND_5',geo_str)+2)); + if Lnum==7 + Lmax_B6 = str2double(geo_str(strmatch('RADIANCE_MAXIMUM_BAND_6_VCID_1',geo_str)+2)); + else + Lmax_B6 = str2double(geo_str(strmatch('RADIANCE_MAXIMUM_BAND_6',geo_str)+2)); + end + Lmax_B7 = str2double(geo_str(strmatch('RADIANCE_MAXIMUM_BAND_7',geo_str)+2)); + Lmax=[Lmax_B1,Lmax_B2,Lmax_B3,Lmax_B4,Lmax_B5,Lmax_B6,Lmax_B7]; + + % Read in LMIN + Lmin_B1 = str2double(geo_str(strmatch('RADIANCE_MINIMUM_BAND_1',geo_str)+2)); + Lmin_B2 = str2double(geo_str(strmatch('RADIANCE_MINIMUM_BAND_2',geo_str)+2)); + Lmin_B3 = str2double(geo_str(strmatch('RADIANCE_MINIMUM_BAND_3',geo_str)+2)); + Lmin_B4 = str2double(geo_str(strmatch('RADIANCE_MINIMUM_BAND_4',geo_str)+2)); + Lmin_B5 = str2double(geo_str(strmatch('RADIANCE_MINIMUM_BAND_5',geo_str)+2)); + if Lnum==7 + Lmin_B6 = str2double(geo_str(strmatch('RADIANCE_MINIMUM_BAND_6_VCID_1',geo_str)+2)); + else + Lmin_B6 = str2double(geo_str(strmatch('RADIANCE_MINIMUM_BAND_6',geo_str)+2)); + end + Lmin_B7 = str2double(geo_str(strmatch('RADIANCE_MINIMUM_BAND_7',geo_str)+2)); + Lmin=[Lmin_B1,Lmin_B2,Lmin_B3,Lmin_B4,Lmin_B5,Lmin_B6,Lmin_B7]; + + % Read in QCALMAX + Qcalmax_B1 = str2double(geo_str(strmatch('QUANTIZE_CAL_MAX_BAND_1',geo_str)+2)); + Qcalmax_B2 = str2double(geo_str(strmatch('QUANTIZE_CAL_MAX_BAND_2',geo_str)+2)); + Qcalmax_B3 = str2double(geo_str(strmatch('QUANTIZE_CAL_MAX_BAND_3',geo_str)+2)); + Qcalmax_B4 = str2double(geo_str(strmatch('QUANTIZE_CAL_MAX_BAND_4',geo_str)+2)); + Qcalmax_B5 = str2double(geo_str(strmatch('QUANTIZE_CAL_MAX_BAND_5',geo_str)+2)); + if Lnum==7 + Qcalmax_B6 = str2double(geo_str(strmatch('QUANTIZE_CAL_MAX_BAND_6_VCID_1',geo_str)+2)); + else + Qcalmax_B6 = str2double(geo_str(strmatch('QUANTIZE_CAL_MAX_BAND_6',geo_str)+2)); + end + Qcalmax_B7 = str2double(geo_str(strmatch('QUANTIZE_CAL_MAX_BAND_7',geo_str)+2)); + Qcalmax=[Qcalmax_B1,Qcalmax_B2,Qcalmax_B3,Qcalmax_B4,Qcalmax_B5,Qcalmax_B6,Qcalmax_B7]; + + % Read in QCALMIN + Qcalmin_B1 = str2double(geo_str(strmatch('QUANTIZE_CAL_MIN_BAND_1',geo_str)+2)); + Qcalmin_B2 = str2double(geo_str(strmatch('QUANTIZE_CAL_MIN_BAND_2',geo_str)+2)); + Qcalmin_B3 = str2double(geo_str(strmatch('QUANTIZE_CAL_MIN_BAND_3',geo_str)+2)); + Qcalmin_B4 = str2double(geo_str(strmatch('QUANTIZE_CAL_MIN_BAND_4',geo_str)+2)); + Qcalmin_B5 = str2double(geo_str(strmatch('QUANTIZE_CAL_MIN_BAND_5',geo_str)+2)); + if Lnum==7 + Qcalmin_B6 = str2double(geo_str(strmatch('QUANTIZE_CAL_MIN_BAND_6_VCID_1',geo_str)+2)); + else + Qcalmin_B6 = str2double(geo_str(strmatch('QUANTIZE_CAL_MIN_BAND_6',geo_str)+2)); + end + Qcalmin_B7 = str2double(geo_str(strmatch('QUANTIZE_CAL_MIN_BAND_7',geo_str)+2)); + Qcalmin=[Qcalmin_B1,Qcalmin_B2,Qcalmin_B3,Qcalmin_B4,Qcalmin_B5,Qcalmin_B6,Qcalmin_B7]; + + % Read in nrows & ncols of optical bands + Sample_ref = str2double(geo_str(strmatch('REFLECTIVE_SAMPLES',geo_str)+2)); + Line_ref = str2double(geo_str(strmatch('REFLECTIVE_LINES',geo_str)+2)); + % record ijdimension of optical bands + ijdim_ref=[Line_ref,Sample_ref]; + + Sample_thm = str2double(geo_str(strmatch('THERMAL_SAMPLES',geo_str)+2)); + Line_thm = str2double(geo_str(strmatch('THERMAL_LINES',geo_str)+2)); + % record thermal band dimensions (i,j) + ijdim_thm=[Line_thm,Sample_thm]; + + % Read in resolution of optical and thermal bands + reso_ref = str2double(geo_str(strmatch('GRID_CELL_SIZE_REFLECTIVE',geo_str)+2)); + reso_thm = str2double(geo_str(strmatch('GRID_CELL_SIZE_THERMAL',geo_str)+2)); + + % Read in UTM Zone Number + zc=str2double(geo_str(strmatch('UTM_ZONE',geo_str)+2)); + % Read in Solar Azimuth & Elevation angle (degrees) + azi=str2double(geo_str(strmatch('SUN_AZIMUTH',geo_str)+2)); + zen=90-str2double(geo_str(strmatch('SUN_ELEVATION',geo_str)+2)); + % Read in upperleft mapx,y + ulx=str2double(geo_str(strmatch('CORNER_UL_PROJECTION_X_PRODUCT',geo_str)+2)); + uly=str2double(geo_str(strmatch('CORNER_UL_PROJECTION_Y_PRODUCT',geo_str)+2)); + ul=[ulx,uly]; + % Read in date of year + char_doy=char(geo_str(strmatch('LANDSAT_SCENE_ID',geo_str)+2)); + doy=str2double(char_doy(15:17)); + + % Read in latidtude and longtitude. + ul_lat=str2double(geo_str(strmatch('CORNER_UL_LAT_PRODUCT',geo_str)+2)); + ul_lon=str2double(geo_str(strmatch('CORNER_UL_LON_PRODUCT',geo_str)+2)); + ur_lat=str2double(geo_str(strmatch('CORNER_UR_LAT_PRODUCT',geo_str)+2)); + ur_lon=str2double(geo_str(strmatch('CORNER_UR_LON_PRODUCT',geo_str)+2)); + ll_lat=str2double(geo_str(strmatch('CORNER_LL_LAT_PRODUCT',geo_str)+2)); + ll_lon=str2double(geo_str(strmatch('CORNER_LL_LON_PRODUCT',geo_str)+2)); + lr_lat=str2double(geo_str(strmatch('CORNER_LR_LAT_PRODUCT',geo_str)+2)); + lr_lon=str2double(geo_str(strmatch('CORNER_LR_LON_PRODUCT',geo_str)+2)); + + north=max(ul_lat,ur_lat); + south=min(ll_lat,lr_lat); + west=min(ul_lon,ll_lon); + east=max(ur_lon,lr_lon); + bbox=[north,south,west,east]; + + + else % "old" MTL.txt + % read in LMAX + Lmax_B1 = str2double(geo_str(strmatch('LMAX_BAND1',geo_str)+2)); + Lmax_B2 = str2double(geo_str(strmatch('LMAX_BAND2',geo_str)+2)); + Lmax_B3 = str2double(geo_str(strmatch('LMAX_BAND3',geo_str)+2)); + Lmax_B4 = str2double(geo_str(strmatch('LMAX_BAND4',geo_str)+2)); + Lmax_B5 = str2double(geo_str(strmatch('LMAX_BAND5',geo_str)+2)); + if Lnum==7 + Lmax_B6 = str2double(geo_str(strmatch('LMAX_BAND61',geo_str)+2)); + else + Lmax_B6 = str2double(geo_str(strmatch('LMAX_BAND6',geo_str)+2)); + end + Lmax_B7 = str2double(geo_str(strmatch('LMAX_BAND7',geo_str)+2)); + Lmax=[Lmax_B1,Lmax_B2,Lmax_B3,Lmax_B4,Lmax_B5,Lmax_B6,Lmax_B7]; + + % Read in LMIN + Lmin_B1 = str2double(geo_str(strmatch('LMIN_BAND1',geo_str)+2)); + Lmin_B2 = str2double(geo_str(strmatch('LMIN_BAND2',geo_str)+2)); + Lmin_B3 = str2double(geo_str(strmatch('LMIN_BAND3',geo_str)+2)); + Lmin_B4 = str2double(geo_str(strmatch('LMIN_BAND4',geo_str)+2)); + Lmin_B5 = str2double(geo_str(strmatch('LMIN_BAND5',geo_str)+2)); + if Lnum==7 + Lmin_B6 = str2double(geo_str(strmatch('LMIN_BAND61',geo_str)+2)); + else + Lmin_B6 = str2double(geo_str(strmatch('LMIN_BAND6',geo_str)+2)); + end + Lmin_B7 = str2double(geo_str(strmatch('LMIN_BAND7',geo_str)+2)); + Lmin=[Lmin_B1,Lmin_B2,Lmin_B3,Lmin_B4,Lmin_B5,Lmin_B6,Lmin_B7]; + + % Read in QCALMAX + Qcalmax_B1 = str2double(geo_str(strmatch('QCALMAX_BAND1',geo_str)+2)); + Qcalmax_B2 = str2double(geo_str(strmatch('QCALMAX_BAND2',geo_str)+2)); + Qcalmax_B3 = str2double(geo_str(strmatch('QCALMAX_BAND3',geo_str)+2)); + Qcalmax_B4 = str2double(geo_str(strmatch('QCALMAX_BAND4',geo_str)+2)); + Qcalmax_B5 = str2double(geo_str(strmatch('QCALMAX_BAND5',geo_str)+2)); + if Lnum==7 + Qcalmax_B6 = str2double(geo_str(strmatch('QCALMAX_BAND61',geo_str)+2)); + else + Qcalmax_B6 = str2double(geo_str(strmatch('QCALMAX_BAND6',geo_str)+2)); + end + Qcalmax_B7 = str2double(geo_str(strmatch('QCALMAX_BAND7',geo_str)+2)); + Qcalmax=[Qcalmax_B1,Qcalmax_B2,Qcalmax_B3,Qcalmax_B4,Qcalmax_B5,Qcalmax_B6,Qcalmax_B7]; + + % Read in QCALMIN + Qcalmin_B1 = str2double(geo_str(strmatch('QCALMIN_BAND1',geo_str)+2)); + Qcalmin_B2 = str2double(geo_str(strmatch('QCALMIN_BAND2',geo_str)+2)); + Qcalmin_B3 = str2double(geo_str(strmatch('QCALMIN_BAND3',geo_str)+2)); + Qcalmin_B4 = str2double(geo_str(strmatch('QCALMIN_BAND4',geo_str)+2)); + Qcalmin_B5 = str2double(geo_str(strmatch('QCALMIN_BAND5',geo_str)+2)); + if Lnum==7 + Qcalmin_B6 = str2double(geo_str(strmatch('QCALMIN_BAND61',geo_str)+2)); + else + Qcalmin_B6 = str2double(geo_str(strmatch('QCALMIN_BAND6',geo_str)+2)); + end + Qcalmin_B7 = str2double(geo_str(strmatch('QCALMIN_BAND7',geo_str)+2)); + Qcalmin=[Qcalmin_B1,Qcalmin_B2,Qcalmin_B3,Qcalmin_B4,Qcalmin_B5,Qcalmin_B6,Qcalmin_B7]; + + % Read in nrows & ncols of optical bands + Sample_ref = str2double(geo_str(strmatch('PRODUCT_SAMPLES_REF',geo_str)+2)); + Line_ref = str2double(geo_str(strmatch('PRODUCT_LINES_REF',geo_str)+2)); + % record ijdimension of optical bands + ijdim_ref=[Line_ref,Sample_ref]; + + Sample_thm = str2double(geo_str(strmatch('PRODUCT_SAMPLES_THM',geo_str)+2)); + Line_thm = str2double(geo_str(strmatch('PRODUCT_LINES_THM',geo_str)+2)); + % record thermal band dimensions (i,j) + ijdim_thm=[Line_thm,Sample_thm]; + + % Read in resolution of optical and thermal bands + reso_ref = str2double(geo_str(strmatch('GRID_CELL_SIZE_REF',geo_str)+2)); + reso_thm = str2double(geo_str(strmatch('GRID_CELL_SIZE_THM',geo_str)+2)); + + % Read in UTM Zone Number + zc=str2double(geo_str(strmatch('ZONE_NUMBER',geo_str)+2)); + % Read in Solar Azimuth & Elevation angle (degrees) + azi=str2double(geo_str(strmatch('SUN_AZIMUTH',geo_str)+2)); + zen=90-str2double(geo_str(strmatch('SUN_ELEVATION',geo_str)+2)); + % Read in upperleft mapx,y + ulx=str2double(geo_str(strmatch('PRODUCT_UL_CORNER_MAPX',geo_str)+2)); + uly=str2double(geo_str(strmatch('PRODUCT_UL_CORNER_MAPY',geo_str)+2)); + ul=[ulx,uly]; + % Read in date of year + char_doy=char(geo_str(strmatch('DATEHOUR_CONTACT_PERIOD',geo_str)+2)); + doy=str2double(char_doy(4:6)); + + % Read in latidtude and longtitude. + ul_lat=str2double(geo_str(strmatch('PRODUCT_UL_CORNER_LAT',geo_str)+2)); + ul_lon=str2double(geo_str(strmatch('PRODUCT_UL_CORNER_LON',geo_str)+2)); + ur_lat=str2double(geo_str(strmatch('PRODUCT_UR_CORNER_LAT',geo_str)+2)); + ur_lon=str2double(geo_str(strmatch('PRODUCT_UR_CORNER_LON',geo_str)+2)); + ll_lat=str2double(geo_str(strmatch('PRODUCT_LL_CORNER_LAT',geo_str)+2)); + ll_lon=str2double(geo_str(strmatch('PRODUCT_LL_CORNER_LON',geo_str)+2)); + lr_lat=str2double(geo_str(strmatch('PRODUCT_LR_CORNER_LAT',geo_str)+2)); + lr_lon=str2double(geo_str(strmatch('PRODUCT_LR_CORNER_LON',geo_str)+2)); + + north=max(ul_lat,ur_lat); + south=min(ll_lat,lr_lat); + west=min(ul_lon,ll_lon); + east=max(ur_lon,lr_lon); + bbox=[north,south,west,east]; + + end +elseif Lnum == 8 + % read in LMAX + Lmax_B2 = str2double(geo_str(strmatch('RADIANCE_MAXIMUM_BAND_2',geo_str)+2)); + Lmax_B3 = str2double(geo_str(strmatch('RADIANCE_MAXIMUM_BAND_3',geo_str)+2)); + Lmax_B4 = str2double(geo_str(strmatch('RADIANCE_MAXIMUM_BAND_4',geo_str)+2)); + Lmax_B5 = str2double(geo_str(strmatch('RADIANCE_MAXIMUM_BAND_5',geo_str)+2)); + Lmax_B6 = str2double(geo_str(strmatch('RADIANCE_MAXIMUM_BAND_6',geo_str)+2)); + Lmax_B7 = str2double(geo_str(strmatch('RADIANCE_MAXIMUM_BAND_7',geo_str)+2)); + Lmax_B9 = str2double(geo_str(strmatch('RADIANCE_MAXIMUM_BAND_9',geo_str)+2)); + Lmax_B10 = str2double(geo_str(strmatch('RADIANCE_MAXIMUM_BAND_10',geo_str)+2)); + + Lmax=[Lmax_B2,Lmax_B3,Lmax_B4,Lmax_B5,Lmax_B6,Lmax_B7,Lmax_B9,Lmax_B10]; + + % read in LMIN + Lmin_B2 = str2double(geo_str(strmatch('RADIANCE_MINIMUM_BAND_2',geo_str)+2)); + Lmin_B3 = str2double(geo_str(strmatch('RADIANCE_MINIMUM_BAND_3',geo_str)+2)); + Lmin_B4 = str2double(geo_str(strmatch('RADIANCE_MINIMUM_BAND_4',geo_str)+2)); + Lmin_B5 = str2double(geo_str(strmatch('RADIANCE_MINIMUM_BAND_5',geo_str)+2)); + Lmin_B6 = str2double(geo_str(strmatch('RADIANCE_MINIMUM_BAND_6',geo_str)+2)); + Lmin_B7 = str2double(geo_str(strmatch('RADIANCE_MINIMUM_BAND_7',geo_str)+2)); + Lmin_B9 = str2double(geo_str(strmatch('RADIANCE_MINIMUM_BAND_9',geo_str)+2)); + Lmin_B10 = str2double(geo_str(strmatch('RADIANCE_MINIMUM_BAND_10',geo_str)+2)); + + Lmin=[Lmin_B2,Lmin_B3,Lmin_B4,Lmin_B5,Lmin_B6,Lmin_B7,Lmin_B9,Lmin_B10]; + + % Read in QCALMAX + Qcalmax_B2 = str2double(geo_str(strmatch('QUANTIZE_CAL_MAX_BAND_2',geo_str)+2)); + Qcalmax_B3 = str2double(geo_str(strmatch('QUANTIZE_CAL_MAX_BAND_3',geo_str)+2)); + Qcalmax_B4 = str2double(geo_str(strmatch('QUANTIZE_CAL_MAX_BAND_4',geo_str)+2)); + Qcalmax_B5 = str2double(geo_str(strmatch('QUANTIZE_CAL_MAX_BAND_5',geo_str)+2)); + Qcalmax_B6 = str2double(geo_str(strmatch('QUANTIZE_CAL_MAX_BAND_6',geo_str)+2)); + Qcalmax_B7 = str2double(geo_str(strmatch('QUANTIZE_CAL_MAX_BAND_7',geo_str)+2)); + Qcalmax_B9 = str2double(geo_str(strmatch('QUANTIZE_CAL_MAX_BAND_9',geo_str)+2)); + Qcalmax_B10 = str2double(geo_str(strmatch('QUANTIZE_CAL_MAX_BAND_10',geo_str)+2)); + Qcalmax=[Qcalmax_B2,Qcalmax_B3,Qcalmax_B4,Qcalmax_B5,Qcalmax_B6,Qcalmax_B7,Qcalmax_B9,Qcalmax_B10]; + + % Read in QCALMIN + Qcalmin_B2 = str2double(geo_str(strmatch('QUANTIZE_CAL_MIN_BAND_2',geo_str)+2)); + Qcalmin_B3 = str2double(geo_str(strmatch('QUANTIZE_CAL_MIN_BAND_3',geo_str)+2)); + Qcalmin_B4 = str2double(geo_str(strmatch('QUANTIZE_CAL_MIN_BAND_4',geo_str)+2)); + Qcalmin_B5 = str2double(geo_str(strmatch('QUANTIZE_CAL_MIN_BAND_5',geo_str)+2)); + Qcalmin_B6 = str2double(geo_str(strmatch('QUANTIZE_CAL_MIN_BAND_6',geo_str)+2)); + Qcalmin_B7 = str2double(geo_str(strmatch('QUANTIZE_CAL_MIN_BAND_7',geo_str)+2)); + Qcalmin_B9 = str2double(geo_str(strmatch('QUANTIZE_CAL_MIN_BAND_9',geo_str)+2)); + Qcalmin_B10 = str2double(geo_str(strmatch('QUANTIZE_CAL_MIN_BAND_10',geo_str)+2)); + Qcalmin=[Qcalmin_B2,Qcalmin_B3,Qcalmin_B4,Qcalmin_B5,Qcalmin_B6,Qcalmin_B7,Qcalmin_B9,Qcalmin_B10]; + + % read in Refmax + Refmax_B2 = str2double(geo_str(strmatch('REFLECTANCE_MAXIMUM_BAND_2',geo_str)+2)); + Refmax_B3 = str2double(geo_str(strmatch('REFLECTANCE_MAXIMUM_BAND_3',geo_str)+2)); + Refmax_B4 = str2double(geo_str(strmatch('REFLECTANCE_MAXIMUM_BAND_4',geo_str)+2)); + Refmax_B5 = str2double(geo_str(strmatch('REFLECTANCE_MAXIMUM_BAND_5',geo_str)+2)); + Refmax_B6 = str2double(geo_str(strmatch('REFLECTANCE_MAXIMUM_BAND_6',geo_str)+2)); + Refmax_B7 = str2double(geo_str(strmatch('REFLECTANCE_MAXIMUM_BAND_7',geo_str)+2)); + Refmax_B9 = str2double(geo_str(strmatch('REFLECTANCE_MAXIMUM_BAND_9',geo_str)+2)); + + Refmax=[Refmax_B2,Refmax_B3,Refmax_B4,Refmax_B5,Refmax_B6,Refmax_B7,Refmax_B9]; + + % read in Refmin + Refmin_B2 = str2double(geo_str(strmatch('REFLECTANCE_MINIMUM_BAND_2',geo_str)+2)); + Refmin_B3 = str2double(geo_str(strmatch('REFLECTANCE_MINIMUM_BAND_3',geo_str)+2)); + Refmin_B4 = str2double(geo_str(strmatch('REFLECTANCE_MINIMUM_BAND_4',geo_str)+2)); + Refmin_B5 = str2double(geo_str(strmatch('REFLECTANCE_MINIMUM_BAND_5',geo_str)+2)); + Refmin_B6 = str2double(geo_str(strmatch('REFLECTANCE_MINIMUM_BAND_6',geo_str)+2)); + Refmin_B7 = str2double(geo_str(strmatch('REFLECTANCE_MINIMUM_BAND_7',geo_str)+2)); + Refmin_B9 = str2double(geo_str(strmatch('REFLECTANCE_MINIMUM_BAND_9',geo_str)+2)); + + Refmin=[Refmin_B2,Refmin_B3,Refmin_B4,Refmin_B5,Refmin_B6,Refmin_B7,Refmin_B9]; + + % Read in nrows & ncols of optical bands + Sample_ref = str2double(geo_str(strmatch('REFLECTIVE_SAMPLES',geo_str)+2)); + Line_ref = str2double(geo_str(strmatch('REFLECTIVE_LINES',geo_str)+2)); + % record ijdimension of optical bands + ijdim_ref=[Line_ref,Sample_ref]; + + Sample_thm = str2double(geo_str(strmatch('THERMAL_SAMPLES',geo_str)+2)); + Line_thm = str2double(geo_str(strmatch('THERMAL_LINES',geo_str)+2)); + % record thermal band dimensions (i,j) + ijdim_thm=[Line_thm,Sample_thm]; + + % Read in resolution of optical and thermal bands + reso_ref = str2double(geo_str(strmatch('GRID_CELL_SIZE_REFLECTIVE',geo_str)+2)); + reso_thm = str2double(geo_str(strmatch('GRID_CELL_SIZE_THERMAL',geo_str)+2)); + + % Read in UTM Zone Number + zc=str2double(geo_str(strmatch('UTM_ZONE',geo_str)+2)); + % Read in Solar Azimuth & Elevation angle (degrees) + azi=str2double(geo_str(strmatch('SUN_AZIMUTH',geo_str)+2)); + zen=90-str2double(geo_str(strmatch('SUN_ELEVATION',geo_str)+2)); + % Read in upperleft mapx,y + ulx=str2double(geo_str(strmatch('CORNER_UL_PROJECTION_X_PRODUCT',geo_str)+2)); + uly=str2double(geo_str(strmatch('CORNER_UL_PROJECTION_Y_PRODUCT',geo_str)+2)); + ul=[ulx,uly]; + % Read in latidtude and longtitude. + ul_lat=str2double(geo_str(strmatch('CORNER_UL_LAT_PRODUCT',geo_str)+2)); + ul_lon=str2double(geo_str(strmatch('CORNER_UL_LON_PRODUCT',geo_str)+2)); + ur_lat=str2double(geo_str(strmatch('CORNER_UR_LAT_PRODUCT',geo_str)+2)); + ur_lon=str2double(geo_str(strmatch('CORNER_UR_LON_PRODUCT',geo_str)+2)); + ll_lat=str2double(geo_str(strmatch('CORNER_LL_LAT_PRODUCT',geo_str)+2)); + ll_lon=str2double(geo_str(strmatch('CORNER_LL_LON_PRODUCT',geo_str)+2)); + lr_lat=str2double(geo_str(strmatch('CORNER_LR_LAT_PRODUCT',geo_str)+2)); + lr_lon=str2double(geo_str(strmatch('CORNER_LR_LON_PRODUCT',geo_str)+2)); + + north=max(ul_lat,ur_lat); + south=min(ll_lat,lr_lat); + west=min(ul_lon,ll_lon); + east=max(ur_lon,lr_lon); + bbox=[north,south,west,east]; + + + % Read in date of year + char_doy=char(geo_str(strmatch('LANDSAT_SCENE_ID',geo_str)+2)); + doy=str2double(char_doy(15:17)); +else + fprintf('This sensor is not Landsat 4, 5, 7, or 8!\n'); + return; +end + +end + diff --git a/nd2toarbt.m b/nd2toarbt.m new file mode 100644 index 0000000..2c5e66a --- /dev/null +++ b/nd2toarbt.m @@ -0,0 +1,691 @@ +function [im_th,TOAref,trgt,ijdim_ref,ul,bbox,zen,azi,zc,B1Satu,B2Satu,B3Satu,resolu]=nd2toarbt(path_top,filename) +% convert DNs to TOA ref and BT +% Revisions: +% Cannot use the GRIDobj to read our band, because it may result in Nan data. +% (Shi 2/26/2018) +% Added a target image based on blue band, which will be helpfull to +% resample and reproject the auxiliary data. (Shi 2/16/2018) +% Use REF vs. DN instead of RAD vs. DN (Zhe 06/20/2013) +% Combined the Earth-Sun distance table into the function (Zhe 04/09/2013) +% Process Landsat 8 DN values (Zhe 04/04/2013) +% Proces the new metadata for Landsat TM/ETM+ images (Zhe 09/28/2012) +% Fixed bugs caused by earth-sun distance table (Zhe 01/15/2012) +% +% [im_th,TOAref,ijdim_ref,ul,zen,azi,zc,B1Satu,B2Satu,B3Satu,resolu]=nd2toarbt(filename) +% Where: +% Inputs: +% filename='L*MTL.txt'; +% Outputs: +% 1) im_th = Brightness Temperature (BT) +% 2) TOAref = Top Of Atmoshpere (TOA) reflectance +% 3) trgt = Target Grid Object +% 4) ijdim = [nrows,ncols]; % dimension of optical bands +% 5) ul = [upperleft_mapx upperleft_mapy]; +% 6) zen = solar zenith angle (degrees); +% 7) azi = solar azimuth angle (degrees); +% 8) zc = Zone Number +% 9,10,11) Saturation (true) in the Visible bands +% 12) resolution of Fmask results + +% % % fprintf('Read in header information & TIF images\n'); +[Lmax,Lmin,Qcalmax,Qcalmin,Refmax,Refmin,ijdim_ref,ijdim_thm,reso_ref,... + reso_thm,ul,bbox,zen,azi,zc,Lnum,doy]=lndhdrread(path_top,filename); + + +% earth-sun distance see G. Chander et al. RSE 113 (2009) 893-903 +Tab_ES_Dist = [ + 1 0.98331 + 2 0.98330 + 3 0.98330 + 4 0.98330 + 5 0.98330 + 6 0.98332 + 7 0.98333 + 8 0.98335 + 9 0.98338 + 10 0.98341 + 11 0.98345 + 12 0.98349 + 13 0.98354 + 14 0.98359 + 15 0.98365 + 16 0.98371 + 17 0.98378 + 18 0.98385 + 19 0.98393 + 20 0.98401 + 21 0.98410 + 22 0.98419 + 23 0.98428 + 24 0.98439 + 25 0.98449 + 26 0.98460 + 27 0.98472 + 28 0.98484 + 29 0.98496 + 30 0.98509 + 31 0.98523 + 32 0.98536 + 33 0.98551 + 34 0.98565 + 35 0.98580 + 36 0.98596 + 37 0.98612 + 38 0.98628 + 39 0.98645 + 40 0.98662 + 41 0.98680 + 42 0.98698 + 43 0.98717 + 44 0.98735 + 45 0.98755 + 46 0.98774 + 47 0.98794 + 48 0.98814 + 49 0.98835 + 50 0.98856 + 51 0.98877 + 52 0.98899 + 53 0.98921 + 54 0.98944 + 55 0.98966 + 56 0.98989 + 57 0.99012 + 58 0.99036 + 59 0.99060 + 60 0.99084 + 61 0.99108 + 62 0.99133 + 63 0.99158 + 64 0.99183 + 65 0.99208 + 66 0.99234 + 67 0.99260 + 68 0.99286 + 69 0.99312 + 70 0.99339 + 71 0.99365 + 72 0.99392 + 73 0.99419 + 74 0.99446 + 75 0.99474 + 76 0.99501 + 77 0.99529 + 78 0.99556 + 79 0.99584 + 80 0.99612 + 81 0.99640 + 82 0.99669 + 83 0.99697 + 84 0.99725 + 85 0.99754 + 86 0.99782 + 87 0.99811 + 88 0.99840 + 89 0.99868 + 90 0.99897 + 91 0.99926 + 92 0.99954 + 93 0.99983 + 94 1.00012 + 95 1.00041 + 96 1.00069 + 97 1.00098 + 98 1.00127 + 99 1.00155 + 100 1.00184 + 101 1.00212 + 102 1.00240 + 103 1.00269 + 104 1.00297 + 105 1.00325 + 106 1.00353 + 107 1.00381 + 108 1.00409 + 109 1.00437 + 110 1.00464 + 111 1.00492 + 112 1.00519 + 113 1.00546 + 114 1.00573 + 115 1.00600 + 116 1.00626 + 117 1.00653 + 118 1.00679 + 119 1.00705 + 120 1.00731 + 121 1.00756 + 122 1.00781 + 123 1.00806 + 124 1.00831 + 125 1.00856 + 126 1.00880 + 127 1.00904 + 128 1.00928 + 129 1.00952 + 130 1.00975 + 131 1.00998 + 132 1.01020 + 133 1.01043 + 134 1.01065 + 135 1.01087 + 136 1.01108 + 137 1.01129 + 138 1.01150 + 139 1.01170 + 140 1.01191 + 141 1.01210 + 142 1.01230 + 143 1.01249 + 144 1.01267 + 145 1.01286 + 146 1.01304 + 147 1.01321 + 148 1.01338 + 149 1.01355 + 150 1.01371 + 151 1.01387 + 152 1.01403 + 153 1.01418 + 154 1.01433 + 155 1.01447 + 156 1.01461 + 157 1.01475 + 158 1.01488 + 159 1.01500 + 160 1.01513 + 161 1.01524 + 162 1.01536 + 163 1.01547 + 164 1.01557 + 165 1.01567 + 166 1.01577 + 167 1.01586 + 168 1.01595 + 169 1.01603 + 170 1.01610 + 171 1.01618 + 172 1.01625 + 173 1.01631 + 174 1.01637 + 175 1.01642 + 176 1.01647 + 177 1.01652 + 178 1.01656 + 179 1.01659 + 180 1.01662 + 181 1.01665 + 182 1.01667 + 183 1.01668 + 184 1.01670 + 185 1.01670 + 186 1.01670 + 187 1.01670 + 188 1.01669 + 189 1.01668 + 190 1.01666 + 191 1.01664 + 192 1.01661 + 193 1.01658 + 194 1.01655 + 195 1.01650 + 196 1.01646 + 197 1.01641 + 198 1.01635 + 199 1.01629 + 200 1.01623 + 201 1.01616 + 202 1.01609 + 203 1.01601 + 204 1.01592 + 205 1.01584 + 206 1.01575 + 207 1.01565 + 208 1.01555 + 209 1.01544 + 210 1.01533 + 211 1.01522 + 212 1.01510 + 213 1.01497 + 214 1.01485 + 215 1.01471 + 216 1.01458 + 217 1.01444 + 218 1.01429 + 219 1.01414 + 220 1.01399 + 221 1.01383 + 222 1.01367 + 223 1.01351 + 224 1.01334 + 225 1.01317 + 226 1.01299 + 227 1.01281 + 228 1.01263 + 229 1.01244 + 230 1.01225 + 231 1.01205 + 232 1.01186 + 233 1.01165 + 234 1.01145 + 235 1.01124 + 236 1.01103 + 237 1.01081 + 238 1.01060 + 239 1.01037 + 240 1.01015 + 241 1.00992 + 242 1.00969 + 243 1.00946 + 244 1.00922 + 245 1.00898 + 246 1.00874 + 247 1.00850 + 248 1.00825 + 249 1.00800 + 250 1.00775 + 251 1.00750 + 252 1.00724 + 253 1.00698 + 254 1.00672 + 255 1.00646 + 256 1.00620 + 257 1.00593 + 258 1.00566 + 259 1.00539 + 260 1.00512 + 261 1.00485 + 262 1.00457 + 263 1.00430 + 264 1.00402 + 265 1.00374 + 266 1.00346 + 267 1.00318 + 268 1.00290 + 269 1.00262 + 270 1.00234 + 271 1.00205 + 272 1.00177 + 273 1.00148 + 274 1.00119 + 275 1.00091 + 276 1.00062 + 277 1.00033 + 278 1.00005 + 279 0.99976 + 280 0.99947 + 281 0.99918 + 282 0.99890 + 283 0.99861 + 284 0.99832 + 285 0.99804 + 286 0.99775 + 287 0.99747 + 288 0.99718 + 289 0.99690 + 290 0.99662 + 291 0.99634 + 292 0.99605 + 293 0.99577 + 294 0.99550 + 295 0.99522 + 296 0.99494 + 297 0.99467 + 298 0.99440 + 299 0.99412 + 300 0.99385 + 301 0.99359 + 302 0.99332 + 303 0.99306 + 304 0.99279 + 305 0.99253 + 306 0.99228 + 307 0.99202 + 308 0.99177 + 309 0.99152 + 310 0.99127 + 311 0.99102 + 312 0.99078 + 313 0.99054 + 314 0.99030 + 315 0.99007 + 316 0.98983 + 317 0.98961 + 318 0.98938 + 319 0.98916 + 320 0.98894 + 321 0.98872 + 322 0.98851 + 323 0.98830 + 324 0.98809 + 325 0.98789 + 326 0.98769 + 327 0.98750 + 328 0.98731 + 329 0.98712 + 330 0.98694 + 331 0.98676 + 332 0.98658 + 333 0.98641 + 334 0.98624 + 335 0.98608 + 336 0.98592 + 337 0.98577 + 338 0.98562 + 339 0.98547 + 340 0.98533 + 341 0.98519 + 342 0.98506 + 343 0.98493 + 344 0.98481 + 345 0.98469 + 346 0.98457 + 347 0.98446 + 348 0.98436 + 349 0.98426 + 350 0.98416 + 351 0.98407 + 352 0.98399 + 353 0.98391 + 354 0.98383 + 355 0.98376 + 356 0.98370 + 357 0.98363 + 358 0.98358 + 359 0.98353 + 360 0.98348 + 361 0.98344 + 362 0.98340 + 363 0.98337 + 364 0.98335 + 365 0.98333 + 366 0.98331]; + +if Lnum >= 4 && Lnum <= 7 + % LPGS Upper lef corner alignment (see Landsat handbook for detail) + ul(1)=ul(1)-15; + ul(2)=ul(2)+15; + resolu=[reso_ref,reso_ref]; + % Read in all bands + % Band1 + n_B1=dir(fullfile(path_top,'L*B1*')); + if isempty(n_B1) + n_B1=dir(fullfile(path_top,'L*b1*')); + end + im_B1=single(imread(fullfile(path_top,n_B1.name))); + % Band2 + % Also used to be target or referenced image when resampling and reprojecting to the image extent and resolution. + n_B2=dir(fullfile(path_top,'L*B2*')); + if isempty(n_B2) + n_B2=dir(fullfile(path_top,'L*b2*')); + end + im_B2=single(imread(fullfile(path_top,n_B2.name))); + trgt = GRIDobj(fullfile(path_top,n_B2.name)); +% im_B2=single(trgt.Z); + trgt.Z=[];% remove non-used values to save memory. + + % Band3 + n_B3=dir(fullfile(path_top,'L*B3*')); + if isempty(n_B3) + n_B3=dir(fullfile(path_top,'L*b3*')); + end + im_B3=single(imread(fullfile(path_top,n_B3.name))); + % Band4 + n_B4=dir(fullfile(path_top,'L*B4*')); + if isempty(n_B4) + n_B4=dir(fullfile(path_top,'L*b4*')); + end + im_B4=single(imread(fullfile(path_top,n_B4.name))); + % Band5 + n_B5=dir(fullfile(path_top,'L*B5*')); + if isempty(n_B5) + n_B5=dir(fullfile(path_top,'L*b5*')); + end + im_B5=single(imread(fullfile(path_top,n_B5.name))); + % Band6 + if Lnum==7 + n_B6=dir(fullfile(path_top,'L*B6*1*')); + if isempty(n_B6) + n_B6=dir(fullfile(path_top,'L*b6*1*')); + end + else + n_B6=dir(fullfile(path_top,'L*B6*')); + if isempty(n_B6) + n_B6=dir(fullfile(path_top,'L*b6*')); + end + end + im_th=single(imread(fullfile(path_top,n_B6.name))); + % check to see whether need to resample thermal band + if reso_ref~=reso_thm + % resmaple thermal band + im_th=pixel2pixv([ul(2),ul(1)],[ul(2),ul(1)],... + resolu,[reso_thm,reso_thm],... + im_th,[ijdim_ref(2),ijdim_ref(1)],[ijdim_thm(2),ijdim_thm(1)]); + end + % Band7 + n_B7=dir(fullfile(path_top,'L*B7*')); + if isempty(n_B7) + n_B7=dir(fullfile(path_top,'L*b7*')); + end + im_B7=single(imread(fullfile(path_top,n_B7.name))); + % only processing pixesl where all bands have values (id_mssing) + id_missing=im_B1==0|im_B2==0|im_B3==0|im_B4==0|im_B5==0|im_th==0|im_B7==0; + % find pixels that are saturated in the visible bands + B1Satu=im_B1==255; + B2Satu=im_B2==255; + B3Satu=im_B3==255; + + % ND to radiance first +% % % fprintf('From DNs to TOA ref & BT\n'); + im_B1=((Lmax(1)-Lmin(1))/(Qcalmax(1)-Qcalmin(1)))*(im_B1-Qcalmin(1))+Lmin(1); + im_B2=((Lmax(2)-Lmin(2))/(Qcalmax(2)-Qcalmin(2)))*(im_B2-Qcalmin(2))+Lmin(2); + im_B3=((Lmax(3)-Lmin(3))/(Qcalmax(3)-Qcalmin(3)))*(im_B3-Qcalmin(3))+Lmin(3); + im_B4=((Lmax(4)-Lmin(4))/(Qcalmax(4)-Qcalmin(4)))*(im_B4-Qcalmin(4))+Lmin(4); + im_B5=((Lmax(5)-Lmin(5))/(Qcalmax(5)-Qcalmin(5)))*(im_B5-Qcalmin(5))+Lmin(5); + im_th=((Lmax(6)-Lmin(6))/(Qcalmax(6)-Qcalmin(6)))*(im_th-Qcalmin(6))+Lmin(6); + im_B7=((Lmax(7)-Lmin(7))/(Qcalmax(7)-Qcalmin(7)))*(im_B7-Qcalmin(7))+Lmin(7); + + % Landsat 4, 5, and 7 solar spectral irradiance + % see G. Chander et al. RSE 113 (2009) 893-903 + esun_L7=[1997.000, 1812.000, 1533.000, 1039.000, 230.800, -1.0, 84.90]; + esun_L5=[1983.0, 1796.0, 1536.0, 1031.0, 220.0, -1.0, 83.44]; + esun_L4=[1983.0, 1795.0, 1539.0, 1028.0, 219.8, -1.0, 83.49]; + + if Lnum==7 + ESUN=esun_L7; + elseif Lnum==5 + ESUN=esun_L5; + elseif Lnum==4 + ESUN=esun_L4; + end + + % earth-sun distance see G. Chander et al. RSE 113 (2009) 893-903 + dsun_doy = Tab_ES_Dist(doy,2); + + % compute TOA reflectances + % converted from degrees to radiance + s_zen=deg2rad(zen); + im_B1=10^4*pi*im_B1*dsun_doy^2/(ESUN(1)*cos(s_zen)); + im_B2=10^4*pi*im_B2*dsun_doy^2/(ESUN(2)*cos(s_zen)); + im_B3=10^4*pi*im_B3*dsun_doy^2/(ESUN(3)*cos(s_zen)); + im_B4=10^4*pi*im_B4*dsun_doy^2/(ESUN(4)*cos(s_zen)); + im_B5=10^4*pi*im_B5*dsun_doy^2/(ESUN(5)*cos(s_zen)); + im_B7=10^4*pi*im_B7*dsun_doy^2/(ESUN(7)*cos(s_zen)); + + % convert Band6 from radiance to BT + % fprintf('From Band 6 Radiance to Brightness Temperature\n'); + % see G. Chander et al. RSE 113 (2009) 893-903 + K1_L4= 671.62; + K2_L4= 1284.30; + K1_L5= 607.76; + K2_L5= 1260.56; + K1_L7 = 666.09; + K2_L7 = 1282.71; + + if Lnum==7 + K1=K1_L7; + K2=K2_L7; + elseif Lnum==5 + K1=K1_L5; + K2=K2_L5; + elseif Lnum==4 + K1=K1_L4; + K2=K2_L4; + end + + im_th=K2./log((K1./im_th)+1); + % convert from Kelvin to Celcius with 0.01 scale_facor + im_th=100*(im_th-273.15); + % get data ready for Fmask + im_B1(id_missing)=-9999; + im_B2(id_missing)=-9999; + im_B3(id_missing)=-9999; + im_B4(id_missing)=-9999; + im_B5(id_missing)=-9999; + im_th(id_missing)=-9999; + im_B7(id_missing)=-9999; + clear id_missing; + TOAref=zeros(ijdim_ref(1),ijdim_ref(2),6,'single');% Band1,2,3,4,5,&7 + TOAref(:,:,1)=im_B1;clear im_B1; + TOAref(:,:,2)=im_B2;clear im_B2; + TOAref(:,:,3)=im_B3;clear im_B3; + TOAref(:,:,4)=im_B4;clear im_B4; + TOAref(:,:,5)=im_B5;clear im_B5; + TOAref(:,:,6)=im_B7;clear im_B7; + +elseif Lnum == 8 + % LPGS Upper lef corner alignment (see Landsat handbook for detail) + ul(1)=ul(1)-15; + ul(2)=ul(2)+15; + resolu=[reso_ref,reso_ref]; + % Read in all bands + % Band2 + n_B2=dir(fullfile(path_top,'L*B2*')); + if isempty(n_B2) + n_B2=dir(fullfile(path_top,'L*b2*')); + end + im_B2=single(imread(fullfile(path_top,n_B2(1).name))); clear n_B2; + % Band3 + % Also used to be target or referenced image when resampling and reprojecting to the image extent and resolution. + n_B3=dir(fullfile(path_top,'L*B3*')); + if isempty(n_B3) + n_B3=dir(fullfile(path_top,'L*b3*')); + end + im_B3=single(imread(fullfile(path_top,n_B3(1).name))); + trgt = GRIDobj(fullfile(path_top,n_B3.name)); clear n_B3; +% im_B3=single(trgt.Z); + trgt.Z=[];% remove non-used values to save memory. + + % Band4 + n_B4=dir(fullfile(path_top,'L*B4*')); + if isempty(n_B4) + n_B4=dir(fullfile(path_top,'L*b4*')); + end + im_B4=single(imread(fullfile(path_top,n_B4(1).name))); clear n_B4; + % Band5 + n_B5=dir(fullfile(path_top,'L*B5*')); + if isempty(n_B5) + n_B5=dir(fullfile(path_top,'L*b5*')); + end + im_B5=single(imread(fullfile(path_top,n_B5(1).name))); clear n_B5; + % Band6 + n_B6=dir(fullfile(path_top,'L*B6*')); + if isempty(n_B6) + n_B6=dir(fullfile(path_top,'L*b6*')); + end + im_B6=single(imread(fullfile(path_top,n_B6(1).name))); clear n_B6; + % Band7 + n_B7=dir(fullfile(path_top,'L*B7*')); + if isempty(n_B7) + n_B7=dir(fullfile(path_top,'L*b7*')); + end + im_B7=single(imread(fullfile(path_top,n_B7(1).name))); clear n_B7; + % Band9 + n_B9=dir(fullfile(path_top,'L*B9.*')); + if isempty(n_B9) + n_B9=dir(fullfile(path_top,'L*b9.*')); + end + im_B9=single(imread(fullfile(path_top,n_B9(1).name))); clear n_B9; + % Band10 + n_B10=dir(fullfile(path_top,'L*B10*')); + if isempty(n_B10) + n_B10=dir(fullfile(path_top,'L*b10*')); + end + im_B10=single(imread(fullfile(path_top,n_B10(1).name))); clear n_B10 path_top; + + % check to see whether need to resample thermal band + if reso_ref~=reso_thm + % resmaple thermal band + im_B10=pixel2pixv([ul(2),ul(1)],[ul(2),ul(1)],... + resolu,[reso_thm,reso_thm],... + im_B10,[ijdim_ref(2),ijdim_ref(1)],[ijdim_thm(2),ijdim_thm(1)]); + end + + % only processing pixesl where all bands have values (id_mssing) + id_missing=im_B2==0|im_B3==0|im_B4==0|im_B5==0|im_B6==0|im_B7==0|im_B9==0|im_B10==0; + % find pixels that are saturated in the visible bands + B1Satu=im_B2==65535; + B2Satu=im_B3==65535; + B3Satu=im_B4==65535; + + % ND to TOA reflectance with 0.0001 scale_facor +% % % fprintf('From DNs to TOA ref & BT\n'); + im_B2=((Refmax(1)-Refmin(1))/(Qcalmax(1)-Qcalmin(1)))*(im_B2-Qcalmin(1))+Refmin(1); + im_B3=((Refmax(2)-Refmin(2))/(Qcalmax(2)-Qcalmin(2)))*(im_B3-Qcalmin(2))+Refmin(2); + im_B4=((Refmax(3)-Refmin(3))/(Qcalmax(3)-Qcalmin(3)))*(im_B4-Qcalmin(3))+Refmin(3); + im_B5=((Refmax(4)-Refmin(4))/(Qcalmax(4)-Qcalmin(4)))*(im_B5-Qcalmin(4))+Refmin(4); + im_B6=((Refmax(5)-Refmin(5))/(Qcalmax(5)-Qcalmin(5)))*(im_B6-Qcalmin(5))+Refmin(5); + im_B7=((Refmax(6)-Refmin(6))/(Qcalmax(6)-Qcalmin(6)))*(im_B7-Qcalmin(6))+Refmin(6); + im_B9=((Refmax(7)-Refmin(7))/(Qcalmax(7)-Qcalmin(7)))*(im_B9-Qcalmin(7))+Refmin(7); + im_B10=((Lmax(8)-Lmin(8))/(Qcalmax(8)-Qcalmin(8)))*(im_B10-Qcalmin(8))+Lmin(8); + + % compute TOA reflectances + % with a correction for the sun angle + s_zen=deg2rad(zen); + im_B2=10^4*im_B2/cos(s_zen); + im_B3=10^4*im_B3/cos(s_zen); + im_B4=10^4*im_B4/cos(s_zen); + im_B5=10^4*im_B5/cos(s_zen); + im_B6=10^4*im_B6/cos(s_zen); + im_B7=10^4*im_B7/cos(s_zen); + im_B9=10^4*im_B9/cos(s_zen); + + % convert Band6 from radiance to BT + % fprintf('From Band 6 Radiance to Brightness Temperature\n'); + K1_B10 = 774.89; + K2_B10 = 1321.08; + + im_B10=K2_B10./log((K1_B10./im_B10)+1); + + % convert from Kelvin to Celcius with 0.01 scale_facor + im_B10=100*(im_B10-273.15); + + % get data ready for Fmask + im_B2(id_missing)=-9999; + im_B3(id_missing)=-9999; + im_B4(id_missing)=-9999; + im_B5(id_missing)=-9999; + im_B6(id_missing)=-9999; + im_B7(id_missing)=-9999; + im_B9(id_missing)=-9999; + im_B10(id_missing) = -9999; + clear id_missing; % empty memory + + TOAref=zeros(ijdim_ref(1),ijdim_ref(2),7,'single');% Band 2,3,4,5,6,7,& 9 + TOAref(:,:,1)=im_B2; clear im_B2; + TOAref(:,:,2)=im_B3; clear im_B3; + TOAref(:,:,3)=im_B4; clear im_B4; + TOAref(:,:,4)=im_B5; clear im_B5; + TOAref(:,:,5)=im_B6; clear im_B6; + TOAref(:,:,6)=im_B7; clear im_B7; + TOAref(:,:,7)=im_B9; clear im_B9; + + im_th=zeros(ijdim_ref(1),ijdim_ref(2),'single');% Band 10 + im_th(:,:) = im_B10; clear im_B10; +else + fprintf('This sensor is not Landsat 4, 5, 7, or 8!\n'); + return; +end + +end + diff --git a/nd2toarbt_msi.m b/nd2toarbt_msi.m new file mode 100644 index 0000000..7808c11 --- /dev/null +++ b/nd2toarbt_msi.m @@ -0,0 +1,227 @@ +function [im_th,TOAref,trgt,ijdim_ref,bbox,ul,zen,azi,zc,Angles2,B1Satu,B2Satu,B3Satu,resolu]=nd2toarbt_msi2(im) +% read TOA refs function derived from Fmask 3.3 for Sentinel 2. +% Revisions: +% Use REF vs. DN instead of RAD vs. DN (Zhe 06/20/2013) +% Combined the Earth-Sun distance table into the function (Zhe 04/09/2013) +% Process Landsat 8 DN values (Zhe 04/04/2013) +% Proces the new metadata for Landsat TM/ETM+ images (Zhe 09/28/2012) +% Fixed bugs caused by earth-sun distance table (Zhe 01/15/2012) +% +% [im_th,TOAref,ijdim_ref,ul,zen,azi,zc,B1Satu,B2Satu,B3Satu,resolu]=nd2toarbt(filename) +% Where: +% Inputs: +% im= MSI filename structure including: +% - im.Dmain (root directory of the SAFE directory) +% - im.DataStrip directory (without ".SAFE") +% - im.Granule directory; +% im.Dmain = '/home/bernie/MSIdata/'; +% im.DataStrip = 'S2A_OPER_PRD_MSIL1C_PDMC_20151229T234852_R139_V20151229T144823_20151229T144823'; +% im.Granule = 'S2A_OPER_MSI_L1C_TL_SGS__20151229T201123_A002710_T20QPD_N02.01'; +% Outputs: +% 1) im_th = Brightness Temperature (BT) +% 2) TOAref = Top Of Atmoshpere (TOA) reflectance +% 3) ijdim = [nrows,ncols]; % dimension of optical bands +% 4) ul = [upperleft_mapx upperleft_mapy]; +% 5) zen = solar zenith angle (degrees); +% 6) azi = solar azimuth angle (degrees); +% 7) zc = Zone Number +% 8,9,10) Saturation (true) in the Visible bands +% 11) resolution of Fmask results +% eg. + + FilesInfo.DirIn = im.Dmain; + FilesInfo.DataStrip =im.DataStrip ; + FilesInfo.Granule=im.Granule; + + FilesInfo.InspireXML=im.InspireXML; + clear im; + + % obtain them from INSPIRE.xml + bbox = ReadS2InspireXML(FilesInfo.InspireXML); + + + %% Metadata read ReadSunViewGeometryMSI(DataStrip,Granule,BandSel,PsizeOut,Dmain) + [MSIinfo,Angles] = ReadSunViewGeometryMSI (FilesInfo.DataStrip,FilesInfo.Granule,4,10,FilesInfo.DirIn); + + Angles2.VAA = Angles.VAA_B04 ; + Angles2.VZA = Angles.VZA_B04 ; + clear Angles; + + %% output resolution of Fmask 20meters for Sentinel 2 images + resolu = [20 20] ; + %% + ijdim_ref = (MSIinfo.GeoInfo.Size.R10) *10 ./ resolu ; + + ul = [MSIinfo.GeoInfo.Xstart.R10 MSIinfo.GeoInfo.Ystart.R10] + [resolu(1)/2 0-resolu(2)/2]; % the center of the top-left pixel. + zen = MSIinfo.Angles.Mean.SZA ; + azi = MSIinfo.Angles.Mean.SAA ; + + zc_num=MSIinfo.GeoInfo.UTM_zone(1:end-1); + + % convert UTM zone to code by refering the bellow rule. + % ref. http://geotiff.maptools.org/spec/geotiff6.html#6.3.3.1 + zc_ns=MSIinfo.GeoInfo.UTM_zone(end); + clear MSIinfo; + if zc_ns=='N' + zc = abs(str2double(zc_num)); + if zc>10 + geokey = ['326',zc_num]; + else + geokey = ['3260',zc_num]; + end + geokey=str2double(geokey); + elseif zc_ns=='S' + zc = abs(str2double(zc_num)); + if zc>10 + geokey = ['327',zc_num]; + else + geokey = ['3270',zc_num]; + end + geokey=str2double(geokey); + end + + + %% open MSI data + BandCorr = {'02','03','04','8A','11','12','10','07','08'}; + Ratio = [10 10 10 20 20 20 60 20 10] /(resolu(1)); % resample all bands to the same resolu + Dmain = fullfile(FilesInfo.DirIn, [FilesInfo.DataStrip '.SAFE'],'GRANULE', FilesInfo.Granule, 'IMG_DATA'); + id_missing=zeros(ijdim_ref,'uint8'); + for iB=1:length(BandCorr) +% if FilesInfo.Granule(1) == 'L' +% tempfn = dir(fullfile(Dmain, [FilesInfo.Granule(5:10) '*_B' BandCorr{iB} '.jp2'])); +% % if isequal(iB,4) +% % [dum,~,~,info_jp2]=geoimread([Dmain tempfn(1).name]); +% % else +% dum=imread(fullfile(Dmain, tempfn(1).name)); +% % end +% else +% % if isequal(iB,4) +% % % [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'); +% % [dum,~,~,info_jp2]=geoimread([Dmain FilesInfo.Granule(1:end-7) 'B' BandCorr{iB} '.jp2']); +% % else +% % dum=imread([Dmain FilesInfo.Granule(1:end-6) 'B' BandCorr{iB} '.jp2']); +% dum=imread(fullfile(Dmain, [FilesInfo.Granule(1:end-7) 'B' BandCorr{iB} '.jp2'])); +% % end +% end + +% support all versions for Sentinel 2 images. + tempfn = dir(fullfile(Dmain, [FilesInfo.Granule(1) '*B' BandCorr{iB} '.jp2'])); + if isempty(tempfn) + tempfn = dir(fullfile(Dmain, [FilesInfo.Granule(5) '*B' BandCorr{iB} '.jp2'])); + if isempty(tempfn) + tempfn = dir(fullfile(Dmain, ['*B' BandCorr{iB} '.jp2'])); + end + end + dum=imread(fullfile(Dmain, tempfn(1).name)); + clear tempfn; + + dum=single(dum); + dum(dum==0)=NaN; + if Ratio(iB)<1 % box-agg pixel + TOAref(:,:,iB) = imresize(dum,Ratio(iB),'box'); + elseif Ratio(iB)>1 % split pixel + TOAref(:,:,iB) = imresize(dum,Ratio(iB),'nearest'); + elseif Ratio(iB)==1 + TOAref(:,:,iB) = dum; + end + clear dum; + +% % if isequal(BandCorr{iB},'08') +% % % Gaussfilt for band 8 to better scall up. +% % TOAref(:,:,iB) = imgaussfilt(TOAref(:,:,iB)); +% % end + + % only processing pixesl where all bands have values (id_mssing) + id_missing=id_missing|isnan(TOAref(:,:,iB)); + end + clear Dmain FilesInfo BandCorr Ratio iB; +% trgt=CreateTargetGRIDobj(info_jp2.BoundingBox,resolu,ul,ijdim_ref,zc_num,zc_ns,geokey); + trgt=CreateTargetGRIDobj(resolu,ul,ijdim_ref,zc_num,zc_ns,geokey); + + trgt.Z=[]; + +% 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'); + + %% + %%%%% WARNING - what is the MSI fill value? what is the saturation flag ? 65535 +% TOAref(isnan(TOAref))=-9999; + TOAref(id_missing)=-9999; + clear id_missing; + %%%%%%%%%%%% Not used but same format + im_th=-9999; + + % B1Satu = zeros([size(TOAref,1) size(TOAref,2)],'uint8')==1; + % B2Satu = zeros([size(TOAref,1) size(TOAref,2)],'uint8')==1; + % B3Satu = zeros([size(TOAref,1) size(TOAref,2)],'uint8')==1; + + % find pixels that are saturated in the visible bands + % SATURATED VALUE: 65535 set by Shi 10/18/2017 + B1Satu=TOAref(:,:,1)==65535; + B2Satu=TOAref(:,:,2)==65535; + B3Satu=TOAref(:,:,3)==65535; +end + +function trgt=CreateTargetGRIDobj(resolu,ul,ijdim_ref,zc_num,zc_ns,geokey) + + trgt = GRIDobj([]); + trgt.name='target'; + trgt.cellsize=resolu(1); + + ul_tmp=ul - [resolu(1)/2 0-resolu(2)/2]; %back to limits. + + % location of the 'center' of the first (1,1) pixel in the image. + trgt.refmat=makerefmat(ul(1),ul(2),resolu(1),0-resolu(2)); + clear ul; + trgt.size=ijdim_ref; + + % boundary. + xWorldLimits=[ul_tmp(1),ul_tmp(1)+resolu(1)*(ijdim_ref(1))]; + yWorldLimits=[ul_tmp(2)-resolu(2)*(ijdim_ref(2)),ul_tmp(2)]; + clear ul_tmp resolu; + + spatialRef = maprefcells(xWorldLimits,yWorldLimits,ijdim_ref,... + 'ColumnsStartFrom','north'); + clear xWorldLimits yWorldLimits ijdim_ref; + trgt.georef.SpatialRef=spatialRef; + clear spatialRef; +% % http://www.gdal.org/frmt_sentinel2.html + trgt.georef.GeoKeyDirectoryTag.GTModelTypeGeoKey = 1; + trgt.georef.GeoKeyDirectoryTag.GTRasterTypeGeoKey = 1; + trgt.georef.GeoKeyDirectoryTag.GTCitationGeoKey = ['WGS 84 / UTM zone ',zc_num,zc_ns]; + trgt.georef.GeoKeyDirectoryTag.ProjectedCSTypeGeoKey = geokey; clear geokey; + trgt.georef.GeoKeyDirectoryTag.PCSCitationGeoKey = ['WGS 84 / UTM zone ',zc_num,zc_ns]; % same + +% ellipsoid = utmgeoid([zc_num,zc_ns]); + + % this can be used as follows because the Sentinel 2 titles's projection + % will not be changable. + % WGS84 UTM: A UTM zone is a 6?° segment of the Earth. + E = wgs84Ellipsoid('meters'); + utmstruct = defaultm('tranmerc'); + utmstruct.geoid = [E.SemimajorAxis,E.Eccentricity]; + clear E; + % UTM false East (m) + % the central meridian is assigned 500,000 meters in each zone. + utmstruct.falseeasting=500000; + % UTM false North (m) + % If you are in the northern hemisphere, the equator has a northing + % value of 0 meters. In the southern hemisphere, the equator starts + % at 10,000,000 meters. This is because all values south of the + % equator will be positive. + if zc_ns=='N' + utmstruct.falsenorthing=0; + else % S + utmstruct.falsenorthing=10000000;% 10,000,000 + end + clear zc_ns; + % UTM scale factor + utmstruct.scalefactor=0.9996; + % each UTM zone is 6 degrees. a total of 60 zones. + origin=(str2double(zc_num)-31).*6+3; + clear zc_num; + utmstruct.origin=[0,origin,0]; + clear origin; + utmstruct = defaultm(utmstruct); + trgt.georef.mstruct=utmstruct; + clear utmstruct; +end \ No newline at end of file diff --git a/pixel2pixv.m b/pixel2pixv.m new file mode 100644 index 0000000..1c34d55 --- /dev/null +++ b/pixel2pixv.m @@ -0,0 +1,59 @@ +function outim=pixel2pixv(jiul1,jiul2,resolu1,resolu2,im2,jidim1,jidim2) +% from stack images of different resolution and different projection +% Improved version of lndpixel2pixv +% match image2 with image1 +% Input 1) jiul1 (upperleft corner of the pixel) of image 1 +% Input 2) jiul2 (upperleft corner of the pixel) of image 2 +% Input 3&4) j cols, i rows of image 1 +% Input 5&6) resolution of images 1&2 +% Input 7) image 2 data +% Input 8&9) image 1 and 2 dimension +% output matched data = outim(f(i),f(j))=>im1(i,j) +% i.e. matchdata=pixel2pixv(ul1,ul2,res1,res2,data2,dim1,dim2); +j=1:jidim1(1); +i=1:jidim1(2); +[x,y]=lndpixel2map(jiul1,j,i,resolu1); +[j2,i2]=lndmap2pixel(jiul2,x,y,resolu2); + +% the first data is assume to be the filled value +fill_v=im2(1,1); +outim=fill_v*ones(jidim1(2),jidim1(1),class(fill_v)); % give filled data first + + +jexist=(((j2 > 0)&(j2 <= jidim2(1)))); % matched data i,j exit in data2 +iexist=(((i2 > 0)&(i2 <= jidim2(2)))); + +%jexistv=j2(((j2 > 0)&(j2 <= jidim2(1)))); % exist i,j => data2 (f(i),f(j)) +%iexistv=i2(((i2 > 0)&(i2 <= jidim2(2)))); + +jexistv=j2(jexist); % exist i,j => data2 (f(i),f(j)) +iexistv=i2(iexist); + +outim(iexist,jexist)=im2(iexistv,jexistv); +end + +% pixel to map +function [x,y]=lndpixel2map(jiUL,j,i,resolu) +% from pixel to map +% Input jiUL (upperleft corner of the pixel) meters (x,y) +% resolution [x, y] +% output x, y of center of the pixel i,j +% i.e. [x,y]=lndpixel2maplndpixel2map(jiUL,j,i,resolu) +x=jiUL(1)+resolu(1)/2+(j-1)*resolu(1); +y=jiUL(2)-resolu(2)/2+(1-i)*resolu(2); + +end + +% map to pixel +function [j,i]=lndmap2pixel(jiUL,x,y,resolu) +% from map to pixel +% Input jiUL (upperleft corner of the pixel), and x meters, and y meters, and +% resolution [x, y] +% output j cols, i rows +% i.e. [j,i]=lndmap2pixel(jiUL,x,y,resolu) +j=ceil((x-jiUL(1))/resolu(1)); +i=ceil((jiUL(2)-y)/resolu(2)); + +end + + diff --git a/probThin.m b/probThin.m new file mode 100644 index 0000000..e0eb6be --- /dev/null +++ b/probThin.m @@ -0,0 +1,8 @@ +function prob_thin = probThin(BandCirrus) +%PROBCIRRUS + prob_thin = BandCirrus./400; + clear BandCirrus; +% prob_thin(prob_thin>1)=1; + prob_thin(prob_thin<0)=0; +end + diff --git a/problBrightness.m b/problBrightness.m new file mode 100644 index 0000000..e90656b --- /dev/null +++ b/problBrightness.m @@ -0,0 +1,18 @@ +function prob_brightness = problBrightness(HOT,idlnd,l_pt,h_pt) +%PROBLBRIGHTNESS calculate brightness probability using HOT + + F_hot=HOT(idlnd); % get clear HOTs + clear idlnd; + % 0.175 percentile background HOT (low) + t_hotl=prctile(F_hot,100*l_pt)-400; + clear l_pt; + % 0.825 percentile background HOT (high) + t_hoth=prctile(F_hot,100*h_pt)+400; + clear F_hot h_pt; + + prob_brightness=(HOT-t_hotl)./(t_hoth-t_hotl); + clear HOT t_hotl t_hoth; + prob_brightness(prob_brightness<0)=0; + prob_brightness(prob_brightness>1)=1; % this cannot be higher 1 (maybe have commission errors from bright surfaces). +end + diff --git a/problSpectralVaribility.m b/problSpectralVaribility.m new file mode 100644 index 0000000..efee9b5 --- /dev/null +++ b/problSpectralVaribility.m @@ -0,0 +1,11 @@ +function prob_vari = problSpectralVaribility(ndvi,ndsi,ndbi,whiteness,SatuGreen,SatuRed) +%PROBLSPECTRALVARIBILITY + % [varibility test over land] + ndsi(SatuGreen==true&ndsi<0)=0; + clear SatuGreen; + ndvi(SatuRed==true&ndvi>0)=0; + clear SatuRed; + prob_vari=1-max(max(max(abs(ndsi),abs(ndvi)),abs(ndbi)),whiteness); + clear ndsi ndvi ndbi whiteness; +end + diff --git a/problTemperature.m b/problTemperature.m new file mode 100644 index 0000000..29b7b23 --- /dev/null +++ b/problTemperature.m @@ -0,0 +1,26 @@ +function [prob_temp, t_templ,t_temph] = problTemperature(BandBT,idclr,l_pt,h_pt) +%PROBTEMPERATURE calculate temperature probability for land respectively. + + % [Temperature test (over land)] + F_temp=BandBT(idclr); % get clear temperature + clear idclr; + t_buffer=4*100; + % 0.175 percentile background temperature (low) + t_templ=prctile(F_temp,100*l_pt); + % 0.825 percentile background temperature (high) + t_temph=prctile(F_temp,100*h_pt); + clear F_temp l_pt h_pt; + + t_tempL=t_templ-t_buffer; + t_tempH=t_temph+t_buffer; + clear t_buffer; + Temp_l=t_tempH-t_tempL; + clear t_tempL; + prob_temp=(t_tempH-BandBT)/Temp_l; + clear BandBT t_tempH Temp_l; + % Temperature can have prob > 1 + prob_temp(prob_temp<0)=0; +% prob_temp(prob_temp>1)=1; + +end + diff --git a/probwBrightness.m b/probwBrightness.m new file mode 100644 index 0000000..fe0156e --- /dev/null +++ b/probwBrightness.m @@ -0,0 +1,10 @@ +function wprob_brightness = probwBrightness(BandSWIR) +%PROBWBRIGHTNESS calculate brightness probability + % [Brightness test (over water)] + t_bright=1100; + wprob_brightness=BandSWIR./t_bright; + clear BandSWIR t_bright; + wprob_brightness(wprob_brightness>1)=1; + wprob_brightness(wprob_brightness<0)=0; +end + diff --git a/probwTemperature.m b/probwTemperature.m new file mode 100644 index 0000000..4934c60 --- /dev/null +++ b/probwTemperature.m @@ -0,0 +1,14 @@ +function wprob_temp = probwTemperature(BandBT,idwt,h_pt) +%PROBTEMPERATURE calculate temperature probability for water. + + % [temperature test (over water)] + F_wtemp=BandBT(idwt); % get clear water temperature + clear idwt; + t_wtemp=prctile(F_wtemp,100*h_pt); + clear F_wtemp h_pt; + wprob_temp=(t_wtemp-BandBT)/400; + clear t_wtemp BandBT; + wprob_temp(wprob_temp<0)=0; + % wTemp_prob(wTemp_prob > 1) = 1; +end + diff --git a/readme.txt b/readme.txt new file mode 100644 index 0000000..da178cd --- /dev/null +++ b/readme.txt @@ -0,0 +1,13 @@ +The software called Fmask (Function of mask) is used for automated clouds, cloud shadows, snow, and water masking for Landsats 4-8 and Sentinel 2 images. + +This 4.0 version has substantial better cloud, cloud shadow, and snow detection results for Sentinel 2 and better results (compared to the 3.3 version that is currently being used by USGS as the Collection 1 QA Band) for Landsats 4-8 . This one software can be used for **Landsats 4-8 Collection 1 Level 1 product (Digital Numbers) and Sentinel 2 Level-1C product (Top Of Atmosphere reflectance) at the same time. + +If you have any questions, please contact +Shi Qiu (shi.qiu@uconn.edu) +Zhe Zhu (zhe@uconn.edu) + + +Note: +autoFmask is the main function. +autoFmaskBacth function can search all Landsats 4-8 and Sentinel-2 images into a certain folder. + diff --git a/refmat2XY.m b/refmat2XY.m new file mode 100644 index 0000000..71d7384 --- /dev/null +++ b/refmat2XY.m @@ -0,0 +1,29 @@ +function [x,y] = refmat2XY(R,siz) + +% Convert referencing matrix (refmat) to coordinate vectors +% +% Syntax +% +% [x,y] = refmat2XY(R,siz) +% +% Input arguments +% +% R referencing matrix +% siz size of the grid +% +% Output arguments +% +% x x-coordinates +% y y-coordinates +% +% + +nrrows = siz(1); +nrcols = siz(2); + +x = [ones(nrcols,1) (1:nrcols)' ones(nrcols,1)]*R; +x = x(:,1)'; + +y = [(1:nrrows)' ones(nrrows,2)]*R; +y = y(:,2); + diff --git a/reproject2utm.m b/reproject2utm.m new file mode 100644 index 0000000..4a09369 --- /dev/null +++ b/reproject2utm.m @@ -0,0 +1,188 @@ +function [DEMr,zone] = reproject2utm(DEM,res,varargin) + +%REPROJECT2UTM Reproject DEM with WGS84 coordinate system to UTM-WGS84 +% +% Syntax +% +% [GRIDr,zone] = reproject2utm(GRID,res) +% [GRIDr,zone] = reproject2utm(GRID,res,pn,pv,...) +% GRIDr = reproject2utm(GRID,GRID2) +% GRIDr = reproject2utm(GRID,GRID2,'method',method) +% +% Description +% +% Reproject a grid (GRIDobj) with WGS84 geographic coordinates to UTM +% WGS84 (requires the mapping toolbox and image processing toolbox). +% +% Input arguments +% +% GRID raster (GRIDobj) with WGS84 geographic coordinates. +% res spatial resolution in x- and y-direction (scalar) +% GRID2 raster (GRIDobj) with projected coordinate system to which +% GRID shall be projected. The resulting grid will be +% perfectly spatially aligned (same cellsize, same upper left +% egde, same size) with GRID2. +% +% Parameter name/value pairs +% +% zone is automatically determined. If supplied, the value must +% be a string, e.g., '32T'. Note that this function requires +% the full grid zone reference that includes the uppercase +% letter indicating the latitudinal band. +% method interpolation method ('bilinear' (default), 'bicubic', +% or 'nearest') +% +% Output arguments +% +% GRIDr raster (GRIDobj) with UTM-WGS84 projected coordinates +% zone utm zone (string) +% +% +% See also: GRIDobj, imtransform, maketform, mfwdtran, minvtran, utmzone +% +% Author: Wolfgang Schwanghart (w.schwanghart[at]geo.uni-potsdam.de) +% Date: 12. January, 2017 + + +% get latitude and longitude vectors +[lon,lat] = getcoordinates(DEM); + +if ~isa(res,'GRIDobj'); + % and calculate centroid of DEM. The centroid is used to + % get the utmzone + lonc = sum(lon([1 end]))/2; + latc = sum(lat([1 end]))/2; + zone = utmzone(latc,lonc); + Nhemisphere = double(upper(zone(end)))>=78; +else + zone = ''; + +end + +% parse input arguments +p = inputParser; +validmethods = {'bicubic','bilinear','nearest','linear'}; +p.FunctionName = 'GRIDobj/reproject2UTM'; +% required +addRequired(p,'DEM',@(x) isa(x,'GRIDobj')); +addRequired(p,'res',@(x) (~isa(x,'GRIDobj') && isscalar(x) && x > 0) || isa(x,'GRIDobj')); +% optional +addParameter(p,'zone',zone,@(x) ischar(x)); +addParameter(p,'method','bilinear',@(x) ischar(validatestring(x,validmethods))); + +parse(p,DEM,res,varargin{:}); + +% get zone for output +zone = p.Results.zone; + + +% prepare mstruct (transformation structure) if only res supplied +if ~isa(res,'GRIDobj'); + mstruct = defaultm('utm'); + mstruct.zone = p.Results.zone; + mstruct.geoid = wgs84Ellipsoid; + mstruct = defaultm(utm(mstruct)); + + % use forward transformation of the corner locations of the DEM + % to calculate the bounds of the reprojected DEM + xlims = zeros(4,1); + ylims = xlims; + [xlims(1:2),ylims(1:2)] = mfwdtran(mstruct,[min(lat) max(lat)],[min(lon) max(lon)]); + [xlims(3:4),ylims(3:4)] = mfwdtran(mstruct,[min(lat) max(lat)],[max(lon) min(lon)]); + lims = [min(xlims) max(xlims) min(ylims) max(ylims)]; + +else + + mstruct = res.georef.mstruct; + [x,y] = getcoordinates(res); + lims = [min(x) max(x) min(y) max(y)]; +end + + +% prepare tform for the image transform +T = maketform('custom', 2, 2, ... + @FWDTRANS, ... + @INVTRANS, ... + []); + +% calculate image transform +if ~isa(res,'GRIDobj'); + [Znew,xdata,ydata] = imtransform(flipud(DEM.Z),T,p.Results.method,... + 'Xdata',lims([1 2]),... + 'Ydata',lims([3 4]),... + 'Udata',lon([1 end]),'Vdata',lat([end 1])',... + 'XYScale',[res res],... + 'Fillvalues',nan... + ); + % we have calculated the imtransform with 'ColumnsStartFrom' south. + % GRIDobjs use 'ColumnsStartFrom' north + Znew = flipud(Znew); + xnew = cumsum([xdata(1) repmat(res,1,size(Znew,2)-1)]); + ynew = flipud(cumsum([ydata(1) repmat(res,1,size(Znew,1)-1)])'); +else + Znew = imtransform(flipud(DEM.Z),T,p.Results.method,... + 'Xdata',lims([1 2]),... + 'Ydata',lims([3 4]),... + 'Udata',lon([1 end]),'Vdata',lat([end 1])',... + 'XYScale',[res.cellsize res.cellsize],... + 'Fillvalues',nan... + ); + Znew = flipud(Znew); +end + + + +if ~isa(res,'GRIDobj'); +% Construct GRIDobj +DEMr = GRIDobj(xnew,ynew,Znew); + +% and include geospatial information +R = refmatToMapRasterReference(DEMr.refmat,DEMr.size); + +% write GeoKeyDirectoryTag so that DEMr can be exported +% using GRIDobj2geotiff +DEMr.georef.SpatialRef = R; +GeoKeyDirectoryTag.GTModelTypeGeoKey = 1; % Projected coordinate system +GeoKeyDirectoryTag.GTRasterTypeGeoKey = 1; % RasterPixelIsArea +GeoKeyDirectoryTag.GTCitationGeoKey = ['PCS Name = WGS_84_UTM_zone_' mstruct.zone]; +GeoKeyDirectoryTag.GeogCitationGeoKey = 'GCS_WGS_1984'; +GeoKeyDirectoryTag.GeogAngularUnitsGeoKey = 9102; %Angular_Degree + +% get ProjectedCSTypeGeoKey +if Nhemisphere + hemisphere = '326'; +else + hemisphere = '327'; +end + +% http://www.remotesensing.org/geotiff/spec/geotiff6.html#6.3.3.1 +% WGS84 / UTM northern hemisphere: 326zz where zz is UTM zone number +% WGS84 / UTM southern hemisphere: 327zz where zz is UTM zone number +% GeoKeyDirectoryTag.ProjectedCSTypeGeoKey = str2double([hemisphere zone(1:2)]); +GeoKeyDirectoryTag.ProjectedCSTypeGeoKey = str2double([hemisphere sprintf('%02d',str2double(zone(regexp(zone,'[0-9]'))))]); +GeoKeyDirectoryTag.ProjLinearUnitsGeoKey = 9001; % Linear_Meter + +DEMr.georef.GeoKeyDirectoryTag = GeoKeyDirectoryTag; +DEMr.georef.mstruct = mstruct; +DEMr.name = [DEM.name ' (utm)']; + +else + DEMr = res; + DEMr.Z = Znew; + DEMr.name = [DEM.name ' (repr)']; + zone = []; +end + +% Transformation functions for imtransform +% (may want to check projfwd and projinv instead mfwdtran and minvtran) + function x = FWDTRANS(u,~) + [x,y] = mfwdtran(mstruct,u(:,2),u(:,1)); + x = [x y]; + end + + function u = INVTRANS(x,~) + [lati,long] = minvtran(mstruct,x(:,1),x(:,2)); + u = [long lati]; + end +end + diff --git a/stratiedSampleHanlder.m b/stratiedSampleHanlder.m new file mode 100644 index 0000000..9f55ae3 --- /dev/null +++ b/stratiedSampleHanlder.m @@ -0,0 +1,137 @@ +function samples_ids=stratiedSampleHanlder(data_dem_clear,dem_b,dem_t,dim,total_sample,ele_strata,distance,resolution) +%STRATIEDSAMPLEHANLDER Further select clear sky (land) pixels to normalize +%BT. +% +% Syntax +% +% samples_ids= +% stratiedSampleHanlder(data_dem_clear,dem_b,dem_t,dim,total_sample,ele_strata,distance) +% +% Description +% +% Stratied sampling method is used by Qiu et al., (2017). +% History: +% 1. Create this function. (1. January, 2017) +% 2. This stratied sampling method sometimes results in no enough +% samples. (8. March, 2018) +% +% Input arguments +% +% data_dem_clear Clear sky pixels' DEM. +% dem_b Basic elevation. +% dem_t Highest elevation. +% dim Dim for data. +% total_sample All clear sky samples (40,000). +% ele_strata Stratied sampling along elevation (300 meters). +% distance Minmum distance among different samples(450 meters). +% Distance rule will be removed if distance = 0. +% resolution Spatial resolution (Landsat 30 meters; Sentinel-2 20 meters). +% Output arguments +% +% Tempd Nomalized Temperature (BT). +% +% +% Author: Shi Qiu (shi.qiu@ttu.edu) +% Date: 8. March, 2018 + + +% % num_clear_sky_pixels=numel(data_dem_clear); +% % % no enough clear-sky pixels afther removing the pixels out of the upper +% % % and lower levels.more than 1/4 total samples (10,000) can be used for +% % % estimating lapse rate and c in topo correction. +% % if num_clear_sky_pixels=i_dem&data_dem_clear0 +% num_strata=num_strata+1; + strata_avail=[strata_avail;1]; + else + strata_avail=[strata_avail;0]; + end + clear dem_clear_index_tmp; + end + % equal samples in each stratum + num_sam_each_strata=round(total_sample/sum(strata_avail)); + clear total_sample; + samples_ids=[];% to restore samples selected. + % loop each strata and select samples + loop_i=1; + for i_dem=dem_b:ele_strata:dem_t + if strata_avail(loop_i) +% dem_clear_index_tmp=data_dem_clear>=i_dem&data_dem_clear=i_dem&data_dem_clearnum_max_tmp + num_tmp=num_max_tmp; + end + clear num_max_tmp; + samples_ids_rand_tmp=samples_ids_rand(1:num_tmp); + clear num_tmp; + % store data + samples_ids=[samples_ids;samples_ids_rand_tmp]; + clear samples_ids_rand samples_ids_rand_tmp; + else + num_max_tmp=num_sam_each_strata*samp_dis; + if num_tmp>num_max_tmp + num_tmp=num_max_tmp; + end + clear num_max_tmp; + samples_ids_rand_tmp=samples_ids_rand(1:num_tmp); + clear num_tmp; + samples_ids_rand=samples_ids_rand_tmp; + clear samples_ids_rand_tmp; + all_num_tmp=size(samples_ids_rand); + [i_tmp,j_tmp]=ind2sub(dim,samples_ids_rand); + ij_tmp=[i_tmp,j_tmp]; + clear i_tmp j_tmp; + % removing the clear-sky pixels of which distance lt 15. + idx_lt15 = rangesearch(ij_tmp,ij_tmp,samp_dis, 'distance','cityblock'); + recorder_tmp=zeros(all_num_tmp,'uint8')+1; + for i_idx=1:all_num_tmp + if recorder_tmp(i_idx)>0 % when this label is available. + recorder_tmp(cell2mat(idx_lt15(i_idx)))=0; + recorder_tmp(i_idx)=1;% reback the current label as 1. + end + end + clear all_num_tmp idx_lt15; + idx_used=find(recorder_tmp==1); + clear recorder_tmp; + num_tmp=size(idx_used,1); + num_max_tmp=num_sam_each_strata; + if num_tmp>num_max_tmp + num_tmp=num_max_tmp; + end + idx_used=idx_used(1:num_tmp); + clear num_tmp num_max_tmp; + % store data + samples_ids=[samples_ids;samples_ids_rand(idx_used)]; + clear samples_ids_rand idx_used; + end + end + loop_i=loop_i+1; + end + clear samp_dis num_sam_each_strata; + +% % % no enough clear-sky pixels afther removing the pixels out of the upper +% % % and lower levels.more than 1/4 total samples (10,000) can be used for +% % % estimating lapse rate and c in topo correction. +% % if numel(samples_ids) +% Some text +% Some more text +% Even more text +% +% +% Will produce: +% s.XMLname.Attributes.attrib1 = "Some value"; +% s.XMLname.Element.Text = "Some text"; +% s.XMLname.DifferentElement{1}.Attributes.attrib2 = "2"; +% s.XMLname.DifferentElement{1}.Text = "Some more text"; +% s.XMLname.DifferentElement{2}.Attributes.attrib3 = "2"; +% s.XMLname.DifferentElement{2}.Attributes.attrib4 = "1"; +% s.XMLname.DifferentElement{2}.Text = "Even more text"; +% +% Please note that the following characters are substituted +% '-' by '_dash_', ':' by '_colon_' and '.' by '_dot_' +% +% Written by W. Falkena, ASTI, TUDelft, 21-08-2010 +% Attribute parsing speed increased by 40% by A. Wanner, 14-6-2011 +% Added CDATA support by I. Smirnov, 20-3-2012 +% +% Modified by X. Mo, University of Wisconsin, 12-5-2012 + + if (nargin < 1) + clc; + help xml2struct + return + end + + if isa(file, 'org.apache.xerces.dom.DeferredDocumentImpl') || isa(file, 'org.apache.xerces.dom.DeferredElementImpl') + % input is a java xml object + xDoc = file; + else + %check for existance + if (exist(file,'file') == 0) + %Perhaps the xml extension was omitted from the file name. Add the + %extension and try again. + if (isempty(strfind(file,'.xml'))) + file = [file '.xml']; + end + + if (exist(file,'file') == 0) + error(['The file ' file ' could not be found']); + end + end + %read the xml file + xDoc = xmlread(file); + end + + %parse xDoc into a MATLAB structure + s = parseChildNodes(xDoc); + +end + +% ----- Subfunction parseChildNodes ----- +function [children,ptext,textflag] = parseChildNodes(theNode) + % Recurse over node children. + children = struct; + ptext = struct; textflag = 'Text'; + if hasChildNodes(theNode) + childNodes = getChildNodes(theNode); + numChildNodes = getLength(childNodes); + + for count = 1:numChildNodes + theChild = item(childNodes,count-1); + [text,name,attr,childs,textflag] = getNodeData(theChild); + + if (~strcmp(name,'#text') && ~strcmp(name,'#comment') && ~strcmp(name,'#cdata_dash_section')) + %XML allows the same elements to be defined multiple times, + %put each in a different cell + if (isfield(children,name)) + if (~iscell(children.(name))) + %put existsing element into cell format + children.(name) = {children.(name)}; + end + index = length(children.(name))+1; + %add new element + children.(name){index} = childs; + if(~isempty(fieldnames(text))) + children.(name){index} = text; + end + if(~isempty(attr)) + children.(name){index}.('Attributes') = attr; + end + else + %add previously unknown (new) element to the structure + children.(name) = childs; + if(~isempty(text) && ~isempty(fieldnames(text))) + children.(name) = text; + end + if(~isempty(attr)) + children.(name).('Attributes') = attr; + end + end + else + ptextflag = 'Text'; + if (strcmp(name, '#cdata_dash_section')) + ptextflag = 'CDATA'; + elseif (strcmp(name, '#comment')) + ptextflag = 'Comment'; + end + + %this is the text in an element (i.e., the parentNode) + if (~isempty(regexprep(text.(textflag),'[\s]*',''))) + if (~isfield(ptext,ptextflag) || isempty(ptext.(ptextflag))) + ptext.(ptextflag) = text.(textflag); + else + %what to do when element data is as follows: + %Text More text + + %put the text in different cells: + % if (~iscell(ptext)) ptext = {ptext}; end + % ptext{length(ptext)+1} = text; + + %just append the text + ptext.(ptextflag) = [ptext.(ptextflag) text.(textflag)]; + end + end + end + + end + end +end + +% ----- Subfunction getNodeData ----- +function [text,name,attr,childs,textflag] = getNodeData(theNode) + % Create structure of node info. + + %make sure name is allowed as structure name + name = toCharArray(getNodeName(theNode))'; + name = strrep(name, '-', '_dash_'); + name = strrep(name, ':', '_colon_'); + name = strrep(name, '.', '_dot_'); + + attr = parseAttributes(theNode); + if (isempty(fieldnames(attr))) + attr = []; + end + + %parse child nodes + [childs,text,textflag] = parseChildNodes(theNode); + + if (isempty(fieldnames(childs)) && isempty(fieldnames(text))) + %get the data of any childless nodes + % faster than if any(strcmp(methods(theNode), 'getData')) + % no need to try-catch (?) + % faster than text = char(getData(theNode)); + text.(textflag) = toCharArray(getTextContent(theNode))'; + end + +end + +% ----- Subfunction parseAttributes ----- +function attributes = parseAttributes(theNode) + % Create attributes structure. + + attributes = struct; + if hasAttributes(theNode) + theAttributes = getAttributes(theNode); + numAttributes = getLength(theAttributes); + + for count = 1:numAttributes + %attrib = item(theAttributes,count-1); + %attr_name = regexprep(char(getName(attrib)),'[-:.]','_'); + %attributes.(attr_name) = char(getValue(attrib)); + + %Suggestion of Adrian Wanner + str = toCharArray(toString(item(theAttributes,count-1)))'; + k = strfind(str,'='); + attr_name = str(1:(k(1)-1)); + attr_name = strrep(attr_name, '-', '_dash_'); + attr_name = strrep(attr_name, ':', '_colon_'); + attr_name = strrep(attr_name, '.', '_dot_'); + attributes.(attr_name) = str((k(1)+2):(end-1)); + end + end +end \ No newline at end of file