Skip to content
This repository was archived by the owner on Jun 18, 2023. It is now read-only.

Commit c782399

Browse files
committed
Add QAQC band for output segment type. Close #21
1 parent f4fabb5 commit c782399

File tree

1 file changed

+87
-49
lines changed

1 file changed

+87
-49
lines changed

scripts/yatsm_map.py

Lines changed: 87 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
Time segment map options:
2020
--after Use time segment after <date> if needed for map
2121
--before Use time segment before <date> if needed for map
22+
--qa Add QA band identifying segment type (3=intersect,
23+
2=after, 1=before)
2224
2325
Classification map options:
2426
--predict_proba Include prediction probability band (P x 10000)
@@ -76,6 +78,11 @@
7678
logging.basicConfig(format=FORMAT, level=logging.INFO, datefmt='%H:%M:%S')
7779
logger = logging.getLogger('yatsm')
7880

81+
# QA/QC values for segment types
82+
_intersect_qa = 3
83+
_after_qa = 2
84+
_before_qa = 1
85+
7986
# Filters for results
8087
_result_record = 'yatsm_*'
8188
# number of days in year
@@ -179,31 +186,33 @@ def find_indices(record, date, after=False, before=False):
179186
non-disturbed time segment
180187
181188
Yields:
182-
np.ndarray: The indices of `record` containing indices matching criteria.
183-
If `before` or `after` are specified, indices will be yielded in order
184-
of least desirability to allow overwriting -- `before` indices,
185-
`after` indices, and intersecting indices.
189+
tuple: (int, np.ndarray) the QA value and indices of `record` containing
190+
indices matching criteria. If `before` or `after` are specified,
191+
indices will be yielded in order of least desirability to allow
192+
overwriting -- `before` indices, `after` indices, and intersecting
193+
indices.
186194
187195
"""
188196
if before:
189197
# Model before, as long as it didn't change
190198
index = np.where((record['end'] <= date) & (record['break'] == 0))[0]
191-
yield index
199+
yield _before_qa, index
192200

193201
if after:
194202
# First model starting after date specified
195203
index = np.where(record['start'] >= date)[0]
196204
_, _index = np.unique(record['px'][index], return_index=True)
197205
index = index[_index]
198-
yield index
206+
yield _after_qa, index
199207

200208
# Model intersecting date
201209
index = np.where((record['start'] <= date) & (record['end'] >= date))[0]
202-
yield index
210+
yield _intersect_qa, index
203211

204212

205213
def get_classification(date, result_location, image_ds,
206-
after=False, before=False, pred_proba=False,
214+
after=False, before=False, qa=False,
215+
pred_proba=False,
207216
ndv=0, pattern=_result_record):
208217
""" Output raster with classification results
209218
@@ -215,6 +224,8 @@ def get_classification(date, result_location, image_ds,
215224
available time segment
216225
before (bool, optional): If date does not intersect a model, use previous
217226
non-disturbed time segment
227+
qa (bool, optional): Add QA flag specifying segment type (intersect,
228+
after, or before)
218229
pred_proba (bool, optional): Include additional band with classification
219230
value probabilities
220231
ndv (int, optional): NoDataValue
@@ -228,15 +239,19 @@ def get_classification(date, result_location, image_ds,
228239
# Find results
229240
records = find_results(result_location, pattern)
230241

231-
logger.debug('Allocating memory...')
232-
nband = 2 if pred_proba else 1
242+
n_bands = 2 if pred_proba else 1
233243
dtype = np.uint16 if pred_proba else np.uint8
234-
raster = np.ones((image_ds.RasterYSize, image_ds.RasterXSize, nband),
235-
dtype=dtype) * int(ndv)
236244

237245
band_names = ['Classification']
238246
if pred_proba:
239-
band_names = band_names + ['Pred Proba (x10,000)']
247+
band_names.append('Pred Proba (x10,000)')
248+
if qa:
249+
n_bands += 1
250+
band_names.append('SegmentQAQC')
251+
252+
logger.debug('Allocating memory...')
253+
raster = np.ones((image_ds.RasterYSize, image_ds.RasterXSize, n_bands),
254+
dtype=dtype) * int(ndv)
240255

241256
logger.debug('Processing results')
242257
for rec in iter_records(records, warn_on_empty=WARN_ON_EMPTY):
@@ -246,9 +261,7 @@ def get_classification(date, result_location, image_ds,
246261
raise ValueError('Results do not have classification prediction'
247262
' probability values')
248263

249-
# TODO: Add in QA/QC values for the type of index that was used per
250-
# pixel
251-
for index in find_indices(rec, date, after=after, before=before):
264+
for _qa, index in find_indices(rec, date, after=after, before=before):
252265
if index.shape[0] == 0:
253266
continue
254267

@@ -258,13 +271,16 @@ def get_classification(date, result_location, image_ds,
258271
raster[rec['py'][index],
259272
rec['px'][index], 1] = \
260273
rec['class_proba'][index].max(axis=1) * 10000
274+
if qa:
275+
raster[rec['py'][index], rec['px'][index], -1] = _qa
261276

262277
return raster, band_names
263278

264279

265280
def get_coefficients(date, result_location, image_ds,
266-
bands, coefs, no_scale_intercept=False,
267-
use_robust=False, after=False, before=False,
281+
bands, coefs,
282+
no_scale_intercept=False, use_robust=False,
283+
after=False, before=False, qa=False,
268284
ndv=-9999, pattern=_result_record):
269285
""" Output a raster with coefficients from CCDC
270286
@@ -282,6 +298,8 @@ def get_coefficients(date, result_location, image_ds,
282298
available time segment
283299
before (bool, optional): If date does not intersect a model, use previous
284300
non-disturbed time segment
301+
qa (bool, optional): Add QA flag specifying segment type (intersect,
302+
after, or before)
285303
ndv (int, optional): NoDataValue
286304
pattern (str, optional): filename pattern of saved record results
287305
@@ -302,31 +320,34 @@ def get_coefficients(date, result_location, image_ds,
302320
n_coefs = len(i_coefs)
303321
n_rmse = n_bands if use_rmse else 0
304322

305-
logger.debug('Allocating memory...')
306-
raster = np.ones((image_ds.RasterYSize, image_ds.RasterXSize,
307-
n_bands * n_coefs + n_rmse),
308-
dtype=np.float32) * ndv
309-
310323
# Setup output band names
311324
band_names = []
312325
for _c in coef_names:
313326
for _b in i_bands:
314327
band_names.append('B' + str(_b + 1) + '_' + _c.replace(' ', ''))
315-
for _b in i_bands:
316-
if use_rmse is True:
328+
if use_rmse is True:
329+
for _b in i_bands:
317330
band_names.append('B' + str(_b + 1) + '_RMSE')
331+
n_qa = 0
332+
if qa:
333+
n_qa += 1
334+
band_names.append('SegmentQAQC')
335+
n_out_bands = n_bands * n_coefs + n_rmse + n_qa
318336

319337
logger.debug('Band names:')
320338
logger.debug(band_names)
321339

322340
_coef = 'robust_coef' if use_robust else 'coef'
323341
_rmse = 'robust_rmse' if use_robust else 'rmse'
324342

343+
logger.debug('Allocating memory...')
344+
raster = np.ones((image_ds.RasterYSize, image_ds.RasterXSize,
345+
n_out_bands),
346+
dtype=np.float32) * ndv
347+
325348
logger.debug('Processing results')
326349
for rec in iter_records(records, warn_on_empty=WARN_ON_EMPTY):
327-
# TODO: Add in QA/QC values for the type of index that was used per
328-
# pixel
329-
for index in find_indices(rec, date, after=after, before=before):
350+
for _qa, index in find_indices(rec, date, after=after, before=before):
330351
if index.shape[0] == 0:
331352
continue
332353

@@ -345,14 +366,17 @@ def get_coefficients(date, result_location, image_ds,
345366

346367
if use_rmse:
347368
raster[rec['py'][index], rec['px'][index],
348-
n_coefs * n_bands:] =\
369+
n_coefs * n_bands:n_out_bands - n_qa] =\
349370
rec[_rmse][index][:, i_bands]
371+
if qa:
372+
raster[rec['py'][index], rec['px'][index], -1] = _qa
350373

351-
return (raster, band_names)
374+
return raster, band_names
352375

353376

354377
def get_prediction(date, result_location, image_ds,
355-
bands='all', use_robust=False, after=False, before=False,
378+
bands='all', use_robust=False,
379+
after=False, before=False, qa=False,
356380
ndv=-9999, pattern=_result_record):
357381
""" Output a raster with the predictions from model fit for a given date
358382
@@ -368,6 +392,8 @@ def get_prediction(date, result_location, image_ds,
368392
available time segment
369393
before (bool, optional): If date does not intersect a model, use previous
370394
non-disturbed time segment
395+
qa (bool, optional): Add QA flag specifying segment type (intersect,
396+
after, or before)
371397
ndv (int, optional): NoDataValue
372398
pattern (str, optional): filename pattern of saved record results
373399
@@ -384,6 +410,11 @@ def get_prediction(date, result_location, image_ds,
384410
records, bands, None, use_robust=use_robust)
385411

386412
n_bands = len(i_bands)
413+
band_names = ['Band_{0}'.format(b) for b in range(n_bands)]
414+
if qa:
415+
n_bands += 1
416+
band_names.append('SegmentQAQC')
417+
n_i_bands = len(i_bands)
387418

388419
# Create X matrix from date -- ignoring categorical variables
389420
if re.match(r'.*C\(.*\).*', design):
@@ -398,22 +429,22 @@ def get_prediction(date, result_location, image_ds,
398429

399430
logger.debug('Processing results')
400431
for rec in iter_records(records, warn_on_empty=WARN_ON_EMPTY):
401-
# TODO: Add in QA/QC values for the type of index that was used per
402-
# pixel
403-
for index in find_indices(rec, date, after=after, before=before):
432+
for _qa, index in find_indices(rec, date, after=after, before=before):
404433
if index.shape[0] == 0:
405434
continue
406435

407436
# Calculate prediction
408-
raster[rec['py'][index], rec['px'][index], :] = \
437+
raster[rec['py'][index], rec['px'][index], :n_i_bands] = \
409438
np.tensordot(rec['coef'][index, :][:, :, i_bands], X,
410439
axes=(1, 0))
440+
if qa:
441+
raster[rec['py'][index], rec['px'][index], -1] = _qa
411442

412-
return raster
443+
return raster, band_names
413444

414445

415446
def get_phenology(date, result_location, image_ds,
416-
after=False, before=False,
447+
after=False, before=False, qa=False,
417448
ndv=-9999, pattern=_result_record):
418449
""" Output a raster containing phenology information
419450
@@ -428,6 +459,8 @@ def get_phenology(date, result_location, image_ds,
428459
available time segment
429460
before (bool, optional): If date does not intersect a model, use previous
430461
non-disturbed time segment
462+
qa (bool, optional): Add QA flag specifying segment type (intersect,
463+
after, or before)
431464
ndv (int, optional): NoDataValue
432465
pattern (str, optional): filename pattern of saved record results
433466
@@ -440,23 +473,25 @@ def get_phenology(date, result_location, image_ds,
440473
# Find results
441474
records = find_results(result_location, pattern)
442475

443-
logger.debug('Allocating memory')
444-
raster = np.ones((image_ds.RasterYSize, image_ds.RasterXSize, 7),
445-
dtype=np.int32) * int(ndv)
446-
476+
n_bands = 7
447477
attributes = ['spring_doy', 'autumn_doy', 'pheno_cor', 'peak_evi',
448478
'peak_doy', 'pheno_nobs']
449479
band_names = ['SpringDOY', 'AutumnDOY', 'Pheno_R*10000', 'Peak_EVI*10000',
450480
'Peak_DOY', 'Pheno_NObs', 'GrowingDOY']
481+
if qa:
482+
n_bands += 1
483+
band_names.append('SegmentQAQC')
484+
485+
logger.debug('Allocating memory')
486+
raster = np.ones((image_ds.RasterYSize, image_ds.RasterXSize, n_bands),
487+
dtype=np.int32) * int(ndv)
451488

452489
logger.debug('Processing results')
453490
for rec in iter_records(records, warn_on_empty=WARN_ON_EMPTY):
454491
if not all([_attr in rec.dtype.names for _attr in attributes]):
455492
raise ValueError('Results do not have phenology metrics')
456493

457-
# TODO: Add in QA/QC values for the type of index that was used per
458-
# pixel
459-
for index in find_indices(rec, date, after=after, before=before):
494+
for _qa, index in find_indices(rec, date, after=after, before=before):
460495
if index.shape[0] == 0:
461496
continue
462497

@@ -470,6 +505,8 @@ def get_phenology(date, result_location, image_ds,
470505
raster[rec['py'][index],
471506
rec['px'][index], 6] = \
472507
rec['autumn_doy'][index] - rec['spring_doy'][index]
508+
if qa:
509+
raster[rec['py'][index], rec['px'][index], -1] = _qa
473510

474511
return raster, band_names
475512

@@ -595,6 +632,7 @@ def parse_args(args):
595632
# Go to next time segment option
596633
parsed['after'] = args['--after']
597634
parsed['before'] = args['--before']
635+
parsed['qa'] = args['--qa']
598636

599637
return parsed
600638

@@ -612,7 +650,7 @@ def main(args):
612650
if args['class']:
613651
raster, band_names = get_classification(
614652
args['date'], args['results'], image_ds,
615-
after=args['after'], before=args['before'],
653+
after=args['after'], before=args['before'], qa=args['qa'],
616654
pred_proba=args['pred_proba']
617655
)
618656
elif args['coef']:
@@ -621,21 +659,21 @@ def main(args):
621659
args['bands'], args['coefs'],
622660
no_scale_intercept=args['no_scale_intercept'],
623661
use_robust=args['use_robust'],
624-
after=args['after'], before=args['before'],
662+
after=args['after'], before=args['before'], qa=args['qa'],
625663
ndv=args['ndv']
626664
)
627665
elif args['predict']:
628-
raster = get_prediction(
666+
raster, band_names = get_prediction(
629667
args['date'], args['results'], image_ds,
630668
args['bands'],
631669
use_robust=args['use_robust'],
632-
after=args['after'], before=args['before'],
670+
after=args['after'], before=args['before'], qa=args['qa'],
633671
ndv=args['ndv']
634672
)
635673
elif args['pheno']:
636674
raster, band_names = get_phenology(
637675
args['date'], args['results'], image_ds,
638-
after=args['after'], before=args['before'],
676+
after=args['after'], before=args['before'], qa=args['qa'],
639677
ndv=args['ndv'])
640678

641679
write_output(raster, args['output'], image_ds,

0 commit comments

Comments
 (0)