1
1
# -*- coding: utf-8 -*-
2
2
"""Special models for surface meteorological data."""
3
+ import os
4
+ import json
3
5
import logging
6
+ from inspect import signature
4
7
from fnmatch import fnmatch
5
8
import numpy as np
6
9
from PIL import Image
7
10
from sklearn import linear_model
8
11
from warnings import warn
9
12
10
- from sup3r .models .abstract import AbstractInterface
13
+ from sup3r .models .linear import LinearInterp
11
14
from sup3r .utilities .utilities import spatial_coarsening
12
15
13
16
logger = logging .getLogger (__name__ )
14
17
15
18
16
- class SurfaceSpatialMetModel (AbstractInterface ):
19
+ class SurfaceSpatialMetModel (LinearInterp ):
17
20
"""Model to spatially downscale daily-average near-surface temperature,
18
21
relative humidity, and pressure
19
22
@@ -43,7 +46,8 @@ class SurfaceSpatialMetModel(AbstractInterface):
43
46
44
47
def __init__ (self , features , s_enhance , noise_adders = None ,
45
48
temp_lapse = None , w_delta_temp = None , w_delta_topo = None ,
46
- pres_div = None , pres_exp = None ):
49
+ pres_div = None , pres_exp = None , interp_method = 'LANCZOS' ,
50
+ fix_bias = True ):
47
51
"""
48
52
Parameters
49
53
----------
@@ -85,6 +89,15 @@ def __init__(self, features, s_enhance, noise_adders=None,
85
89
pres_div : None | float
86
90
Exponential factor in the pressure scale height equation. Defaults
87
91
to the cls.PRES_EXP attribute.
92
+ interp_method : str
93
+ Name of the interpolation method to use from PIL.Image.Resampling
94
+ (NEAREST, BILINEAR, BICUBIC, LANCZOS)
95
+ LANCZOS is default and has been tested to work best for
96
+ SurfaceSpatialMetModel.
97
+ fix_bias : bool
98
+ Some local bias can be introduced by the bilinear interp + lapse
99
+ rate, this flag will attempt to correct that bias by using the
100
+ low-resolution deviation from the input data
88
101
"""
89
102
90
103
self ._features = features
@@ -95,6 +108,8 @@ def __init__(self, features, s_enhance, noise_adders=None,
95
108
self ._w_delta_topo = w_delta_topo or self .W_DELTA_TOPO
96
109
self ._pres_div = pres_div or self .PRES_DIV
97
110
self ._pres_exp = pres_exp or self .PRES_EXP
111
+ self ._fix_bias = fix_bias
112
+ self ._interp_method = getattr (Image .Resampling , interp_method )
98
113
99
114
if isinstance (self ._noise_adders , (int , float )):
100
115
self ._noise_adders = [self ._noise_adders ] * len (self ._features )
@@ -103,42 +118,6 @@ def __len__(self):
103
118
"""Get number of model steps (match interface of MultiStepGan)"""
104
119
return 1
105
120
106
- @classmethod
107
- def load (cls , features , s_enhance , verbose = False , ** kwargs ):
108
- """Load the GAN with its sub-networks from a previously saved-to output
109
- directory.
110
-
111
- Parameters
112
- ----------
113
- features : list
114
- List of feature names that this model will operate on for both
115
- input and output. This must match the feature axis ordering in the
116
- array input to generate(). Typically this is a list containing:
117
- temperature_*m, relativehumidity_*m, and pressure_*m. The list can
118
- contain multiple instances of each variable at different heights.
119
- relativehumidity_*m entries must have corresponding temperature_*m
120
- entires at the same hub height.
121
- s_enhance : int
122
- Integer factor by which the spatial axes are to be enhanced.
123
- verbose : bool
124
- Flag to log information about the loaded model.
125
- kwargs : None | dict
126
- Optional kwargs to initialize SurfaceSpatialMetModel
127
-
128
- Returns
129
- -------
130
- out : SurfaceSpatialMetModel
131
- Returns an initialized SurfaceSpatialMetModel
132
- """
133
-
134
- model = cls (features , s_enhance , ** kwargs )
135
-
136
- if verbose :
137
- logger .info ('Loading SurfaceSpatialMetModel with meta data: {}'
138
- .format (model .meta ))
139
-
140
- return model
141
-
142
121
@staticmethod
143
122
def _get_s_enhance (topo_lr , topo_hr ):
144
123
"""Get the spatial enhancement factor given low-res and high-res
@@ -227,8 +206,39 @@ def _get_temp_rh_ind(self, idf_rh):
227
206
228
207
return idf_temp
229
208
209
+ def _fix_downscaled_bias (self , single_lr , single_hr ,
210
+ method = Image .Resampling .LANCZOS ):
211
+ """Fix any bias introduced by the spatial downscaling with lapse rate.
212
+
213
+ Parameters
214
+ ----------
215
+ single_lr : np.ndarray
216
+ Single timestep raster data with shape
217
+ (lat, lon) matching the low-resolution input data.
218
+ single_hr : np.ndarray
219
+ Single timestep downscaled raster data with shape
220
+ (lat, lon) matching the high-resolution input data.
221
+ method : Image.Resampling.LANCZOS
222
+ An Image.Resampling method (NEAREST, BILINEAR, BICUBIC, LANCZOS).
223
+ NEAREST enforces zero bias but makes slightly more spatial seams.
224
+
225
+ Returns
226
+ -------
227
+ single_hr : np.ndarray
228
+ Single timestep downscaled raster data with shape
229
+ (lat, lon) matching the high-resolution input data.
230
+ """
231
+
232
+ re_coarse = spatial_coarsening (np .expand_dims (single_hr , axis = - 1 ),
233
+ s_enhance = self ._s_enhance ,
234
+ obs_axis = False )[..., 0 ]
235
+ bias = re_coarse - single_lr
236
+ bc = self .downscale_arr (bias , s_enhance = self ._s_enhance , method = method )
237
+ single_hr -= bc
238
+ return single_hr
239
+
230
240
@staticmethod
231
- def downscale_arr (arr , s_enhance , method = Image .Resampling .BILINEAR ):
241
+ def downscale_arr (arr , s_enhance , method = Image .Resampling .LANCZOS ):
232
242
"""Downscale a 2D array of data Image.resize() method
233
243
234
244
Parameters
@@ -238,9 +248,9 @@ def downscale_arr(arr, s_enhance, method=Image.Resampling.BILINEAR):
238
248
(lat, lon)
239
249
s_enhance : int
240
250
Integer factor by which the spatial axes are to be enhanced.
241
- method : Image.Resampling.BILINEAR
251
+ method : Image.Resampling.LANCZOS
242
252
An Image.Resampling method (NEAREST, BILINEAR, BICUBIC, LANCZOS).
243
- BILINEAR is default and has been tested to work best for
253
+ LANCZOS is default and has been tested to work best for
244
254
SurfaceSpatialMetModel.
245
255
"""
246
256
im = Image .fromarray (arr )
@@ -284,9 +294,15 @@ def downscale_temp(self, single_lr_temp, topo_lr, topo_hr):
284
294
assert len (topo_hr .shape ) == 2 , 'Bad shape for topo_hr'
285
295
286
296
lower_data = single_lr_temp .copy () + topo_lr * self ._temp_lapse
287
- hi_res_temp = self .downscale_arr (lower_data , self ._s_enhance )
297
+ hi_res_temp = self .downscale_arr (lower_data , self ._s_enhance ,
298
+ method = self ._interp_method )
288
299
hi_res_temp -= topo_hr * self ._temp_lapse
289
300
301
+ if self ._fix_bias :
302
+ hi_res_temp = self ._fix_downscaled_bias (single_lr_temp ,
303
+ hi_res_temp ,
304
+ method = self ._interp_method )
305
+
290
306
return hi_res_temp
291
307
292
308
def downscale_rh (self , single_lr_rh , single_lr_temp , single_hr_temp ,
@@ -336,9 +352,12 @@ def downscale_rh(self, single_lr_rh, single_lr_temp, single_hr_temp,
336
352
assert len (topo_lr .shape ) == 2 , 'Bad shape for topo_lr'
337
353
assert len (topo_hr .shape ) == 2 , 'Bad shape for topo_hr'
338
354
339
- interp_rh = self .downscale_arr (single_lr_rh , self ._s_enhance )
340
- interp_temp = self .downscale_arr (single_lr_temp , self ._s_enhance )
341
- interp_topo = self .downscale_arr (topo_lr , self ._s_enhance )
355
+ interp_rh = self .downscale_arr (single_lr_rh , self ._s_enhance ,
356
+ method = self ._interp_method )
357
+ interp_temp = self .downscale_arr (single_lr_temp , self ._s_enhance ,
358
+ method = self ._interp_method )
359
+ interp_topo = self .downscale_arr (topo_lr , self ._s_enhance ,
360
+ method = self ._interp_method )
342
361
343
362
delta_temp = single_hr_temp - interp_temp
344
363
delta_topo = topo_hr - interp_topo
@@ -347,6 +366,10 @@ def downscale_rh(self, single_lr_rh, single_lr_temp, single_hr_temp,
347
366
+ self ._w_delta_temp * delta_temp
348
367
+ self ._w_delta_topo * delta_topo )
349
368
369
+ if self ._fix_bias :
370
+ hi_res_rh = self ._fix_downscaled_bias (single_lr_rh , hi_res_rh ,
371
+ method = self ._interp_method )
372
+
350
373
return hi_res_rh
351
374
352
375
def downscale_pres (self , single_lr_pres , topo_lr , topo_hr ):
@@ -388,21 +411,28 @@ def downscale_pres(self, single_lr_pres, topo_lr, topo_hr):
388
411
warn (msg )
389
412
390
413
const = 101325 * (1 - (1 - topo_lr / self ._pres_div )** self ._pres_exp )
391
- single_lr_pres = single_lr_pres .copy () + const
414
+ lr_pres_adj = single_lr_pres .copy () + const
392
415
393
- if np .min (single_lr_pres ) < 0.0 :
416
+ if np .min (lr_pres_adj ) < 0.0 :
394
417
msg = ('Spatial interpolation of surface pressure '
395
418
'resulted in negative values. Incorrectly '
396
419
'scaled/unscaled values or incorrect units are '
397
- 'the most likely causes.' )
420
+ 'the most likely causes. All pressure data should be '
421
+ 'in Pascals.' )
398
422
logger .error (msg )
399
423
raise ValueError (msg )
400
424
401
- hi_res_pres = self .downscale_arr (single_lr_pres , self ._s_enhance )
425
+ hi_res_pres = self .downscale_arr (lr_pres_adj , self ._s_enhance ,
426
+ method = self ._interp_method )
402
427
403
428
const = 101325 * (1 - (1 - topo_hr / self ._pres_div )** self ._pres_exp )
404
429
hi_res_pres -= const
405
430
431
+ if self ._fix_bias :
432
+ hi_res_pres = self ._fix_downscaled_bias (single_lr_pres ,
433
+ hi_res_pres ,
434
+ method = self ._interp_method )
435
+
406
436
if np .min (hi_res_pres ) < 0.0 :
407
437
msg = ('Spatial interpolation of surface pressure '
408
438
'resulted in negative values. Incorrectly '
@@ -524,25 +554,11 @@ def meta(self):
524
554
'pressure_exponent' : self ._pres_exp ,
525
555
'training_features' : self .training_features ,
526
556
'output_features' : self .output_features ,
557
+ 'interp_method' : str (self ._interp_method ),
558
+ 'fix_bias' : self ._fix_bias ,
527
559
'class' : self .__class__ .__name__ ,
528
560
}
529
561
530
- @property
531
- def training_features (self ):
532
- """Get the list of input feature names that the generative model was
533
- trained on.
534
-
535
- Note that topography needs to be passed into generate() as an exogenous
536
- data input.
537
- """
538
- return self ._features
539
-
540
- @property
541
- def output_features (self ):
542
- """Get the list of output feature names that the generative model
543
- outputs"""
544
- return self ._features
545
-
546
562
def train (self , true_hr_temp , true_hr_rh , true_hr_topo ):
547
563
"""This method trains the relative humidity linear model. The
548
564
temperature and surface lapse rate models are parameterizations taken
0 commit comments