python实现时间序列谐波分析(HANTS)
NDVI 时间序列谐波分析法 (Harmonic Analysis of NDVI Time-Series)(简称 Hants )对时间序列数据进行平滑。该方法是一种新的物候分析方法,可用于定量化的监测植被动态变化。
其核心算法是傅里叶变换和最小二乘法拟合, 即把时间波谱数据分解成许多不同相位、频率和幅度的正弦曲线和余弦曲线,从中选取若干个能够反映时间序列特征的曲线进行叠加,以达到时间序列数据的重建目的。
NDVI 时间序列谐波分析法是对快速傅立叶变换的改进,它不仅可以去除云污染点(高频噪声),而且对时序图像的要求不象快速傅立叶变换 (FFT) 那么严格,它可以是不等时间间隔的影像。
该算法的具体实现过程如下:
首先针对每个像元点的时间序列进行傅立叶变换;
然后选择几个低频分量进行反傅立叶变换得到一个新的序列(每个正弦或余弦函数在重建原始数据集中的贡献系数通过最小二乘曲线拟合获得);
计算原始时间序列和新序列的差值。如果差值大于设定的阈值那么该点将被认为是受到了污染,便从原始序列中去掉,用新序列中对应的值来填充;
对改变的原始序列重复上述过程,直到没有受云污染的点被找到或者达到设定的迭代结束条件为止。
该过程的输出结果为一平滑曲线,如下图所示:
Hants 算法在进行 NDVI 时间序列处理时,需要设置频率个数、误差阈值、最大删除点个数及有效数据范围等参数,这些参数的设置没有客观标准,只能根据经验或多次试验来确定。
Hants 除了平滑数据、删除异常值的作用还可用于填补空白或获取缺失的数据以及数据压缩。
modis 数据平滑与填充:
步骤:
首先根据时间范围构建时序面数据,对于缺失时间的空间数据用指定值代表,如 -9999.0
接着逐像素提取散点序列
然后对散点序列运用算法 Hants
最后逐像元保存,形成时空序列
注意:填充的 -9999 在算法中会被过滤掉,不参与计算。
# ref:https://github.com/gespinoza/hants/tree/master
import pandas as pd
import math
from copy import deepcopy
import numpy as np
def HANTS(ni, nb, nf, y, ts, HiLo, low, high, fet, dod, delta, fill_val):
'''
输入:
ni:样本个数,散点时间序列长度
nb:拟合样本的周期长度
nf:非0频率的频率个数
y:散点时间序列,真实值
ts:拟合样本的数量(相对于虚拟周期长度)
HiLo:{'Hi','Lo'},用于指示拒绝低的异常值还是高的异常值
low:有效范围的最小值
high:有效范围的最大值,范围外的值被拒绝
fet:拟合曲线的误差容错,偏离大于fet的点将从曲线中删除
dod:过度确定程度(迭代停止,如果迭代次数达到曲线拟合所需的最小值)
delta:小正数(如0.1)抑制高振幅
fill_val:填充值
输出:
yr:重构后的时间序列
outlier:异常点
'''
"""定义正余弦序列"""
# [
# [1, 1, ..., 1 ],
# [cosx1, cosx2, ..., cosxn],
# [sinx1, sinx2, ..., sinxn],
# [cosx1, cosx2, ..., cosxn],
# ...
# ]
mat = pd.np.zeros((min(2*nf+1, ni), ni)) # [nr,ni]
# amp = np.zeros((nf + 1, 1))
# phi = np.zeros((nf+1, 1))
yr = pd.np.zeros((ni, 1)) # 重构曲线
outliers = pd.np.zeros((1, len(y))) # 异常值,[1,ni]
"""Filter"""
sHiLo = 0
if HiLo == 'Hi': #
sHiLo = -1
elif HiLo == 'Lo':
sHiLo = 1
nr = min(2*nf+1, ni) # number of 2*+1 frequencies, or number of input images
noutmax = ni - nr - dod # 异常点的最大个数
# dg = 180.0/math.pi
mat[0, :] = 1.0 # 用于求偏最小二乘中的b
"""定义一个周期内的正余弦曲线"""
ang = 2*math.pi*pd.np.arange(nb)/nb
cs = pd.np.cos(ang)
sn = pd.np.sin(ang)
"""将时序样本点转成cos、sin"""
i = pd.np.arange(1, nf+1)
for j in pd.np.arange(ni):
index = pd.np.mod(i*ts[j], nb) # 取余
mat[2 * i-1, j] = cs.take(index)
mat[2 * i, j] = sn.take(index)
"""根据数值有效范围将范围外的值设为0"""
p = pd.np.ones_like(y) # (ni,)
bool_out = (y < low) | (y > high)
p[bool_out] = 0
outliers[bool_out.reshape(1, y.shape[0])] = 1
nout = pd.np.sum(p == 0) # 统计异常值的数量
"""异常值个数判断"""
if nout > noutmax:
if pd.np.isclose(y, fill_val).any():
# 当填充值是序列中的一员时,直接填充整个序列,并将整个序列设为异常值
ready = pd.np.array([True])
yr = y
outliers = pd.np.zeros((y.shape[0]), dtype=int)
outliers[:] = fill_val
else:
# 数据点太少
raise Exception('Not enough data points.')
else:
ready = pd.np.zeros((y.shape[0]), dtype=bool) # 设置迭代的初值
"""PLS,迭代优化"""
nloop = 0
nloopmax = ni
while ((not ready.all()) & (nloop < nloopmax)):
nloop += 1
za = pd.np.matmul(mat, p*y) # [nr,ni]*[ni,1],转换的傅里叶序列(去除异常点)
A = pd.np.matmul(pd.np.matmul(mat, pd.np.diag(p)), # 将正常的列过滤出来
pd.np.transpose(mat)) # [nr, ni]*[ni,ni]*[ni,nr]
# add delta to suppress high amplitudes but not for [0,0]
A = A + pd.np.identity(nr)*delta # [nr,nr]+[nr,nr]
A[0, 0] = A[0, 0] - delta
zr = pd.np.linalg.solve(A, za) # 对傅里叶序列进行PLS [nr,nr],[nr,1]->[nr,1]
# solve linear matrix equation and define reconstructed timeseries
yr = pd.np.matmul(pd.np.transpose(mat), zr) # [ni,nr]*[nr,1] -> [ni,1]
# calculate error and sort err by index
diffVec = sHiLo*(yr-y)
err = p*diffVec
err_ls = list(err)
err_sort = deepcopy(err)
err_sort.sort()
rankVec = [err_ls.index(f) for f in err_sort]
# select maximum error and compute new ready status
maxerr = diffVec[rankVec[-1]]
ready = (maxerr <= fet) | (nout == noutmax)
# if ready is still false
if (not ready):
i = ni - 1
j = rankVec[i]
# 迭代的本质就是p在不停的变,从而导致其他相应值的改变
while ((p[j]*diffVec[j] > 0.5*maxerr) & (nout < noutmax)):
p[j] = 0
outliers[0, j] = 1
nout += 1
i -= 1
if i == 0:
j = 0
else:
j = 1
return [yr, outliers]
from hants.wa_gdal import * # from hants.wa_arcpy import *
# Data parameters
rasters_path = r'C:\example\data'
name_format = 'PROBAV_S1_TOC_{0}_100M_V001.tif'
start_date = '2015-08-01'
end_date = '2016-07-28'
latlim = [11.4505, 11.4753]
lonlim = [108.8605, 108.8902]
cellsize = 0.00099162627
nc_path = r'C:\example\ndvi_probav.nc'
rasters_path_out = r'C:\example\output_rasters'
# HANTS parameters
nb = 365
nf = 3
low = -1
high = 1
HiLo = 'Lo'
fet = 0.05
delta = 0.1
dod = 1
# Run
run_HANTS(rasters_path, name_format,
start_date, end_date, latlim, lonlim, cellsize, nc_path,
nb, nf, HiLo, low, high, fet, dod, delta,
4326, -9999.0, rasters_path_out)
# Check fit
point = [108.87, 11.47]
ylim = [-1, 1]
plot_point(nc_path, point, ylim)
from hants.wa_arcpy import * # from hants.wa_gdal import *
# Create netcdf file
rasters_path = r'C:\example\data'
name_format = 'PROBAV_S1_TOC_{0}_100M_V001.tif'
start_date = '2015-08-01'
end_date = '2016-07-28'
latlim = [11.4505, 11.4753]
lonlim = [108.8605, 108.8902]
cellsize = 0.00099162627
nc_path = r'C:\example\ndvi_probav.nc'
create_netcdf(rasters_path, name_format, start_date, end_date,
latlim, lonlim, cellsize, nc_path)
# Run HANTS for a single point
nb = 365
nf = 3
low = -1
high = 1
HiLo = 'Lo'
fet = 0.05
delta = 0.1
dod = 1
point = [108.87, 11.47]
df = HANTS_singlepoint(nc_path, point, nb, nf, HiLo, low, high, fet,
dod, delta)
print df
# Run HANTS
HANTS_netcdf(nc_path, nb, nf, HiLo, low, high, fet, dod, delta)
# Check fit
ylim = [-1, 1]
plot_point(nc_path, point, ylim)
# Export rasters
rasters_path_out = r'C:\example\output_rasters'
export_tiffs(rasters_path_out, nc_path, name_format)
散点拟合效果:
面拟合效果:
import matplotlib.pyplot as plt
%matplotlib inline
import xarray as xr
p = r'./example/ndvi_probav.nc'
ds = xr.open_dataset(p, engine='netcdf4')
plt.figure(figsize=(20,5))
ax = plt.subplot(1,3,1)
ds['original_values'][:,:,0].plot(ax=ax,vmin=0,vmax=1)
ax2 = plt.subplot(1,3,2)
ds['hants_values'][:,:,0].plot(ax=ax2,vmin=0,vmax=1)
ax3 = plt.subplot(1,3,3)
ds['outliers'][:,:,0].plot(ax=ax3,vmin=0,vmax=1)
plt.figure(figsize=(20,5))
ax = plt.subplot(1,3,1)
ds['original_values'][:,:,1].plot(ax=ax,vmin=0,vmax=1)
ax2 = plt.subplot(1,3,2)
ds['hants_values'][:,:,1].plot(ax=ax2,vmin=0,vmax=1)
ax3 = plt.subplot(1,3,3)
ds['outliers'][:,:,1].plot(ax=ax3,vmin=0,vmax=1)
参考:
https://mabouali.wordpress.com/projects/harmonic-analysis-of-time-series-hants/
http://zhihu.geoscene.cn/article/1997
MODIS NDVI 时间序列数据的去云算法比较-梁守真。