19
19
Time segment map options:
20
20
--after Use time segment after <date> if needed for map
21
21
--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)
22
24
23
25
Classification map options:
24
26
--predict_proba Include prediction probability band (P x 10000)
76
78
logging .basicConfig (format = FORMAT , level = logging .INFO , datefmt = '%H:%M:%S' )
77
79
logger = logging .getLogger ('yatsm' )
78
80
81
+ # QA/QC values for segment types
82
+ _intersect_qa = 3
83
+ _after_qa = 2
84
+ _before_qa = 1
85
+
79
86
# Filters for results
80
87
_result_record = 'yatsm_*'
81
88
# number of days in year
@@ -179,31 +186,33 @@ def find_indices(record, date, after=False, before=False):
179
186
non-disturbed time segment
180
187
181
188
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.
186
194
187
195
"""
188
196
if before :
189
197
# Model before, as long as it didn't change
190
198
index = np .where ((record ['end' ] <= date ) & (record ['break' ] == 0 ))[0 ]
191
- yield index
199
+ yield _before_qa , index
192
200
193
201
if after :
194
202
# First model starting after date specified
195
203
index = np .where (record ['start' ] >= date )[0 ]
196
204
_ , _index = np .unique (record ['px' ][index ], return_index = True )
197
205
index = index [_index ]
198
- yield index
206
+ yield _after_qa , index
199
207
200
208
# Model intersecting date
201
209
index = np .where ((record ['start' ] <= date ) & (record ['end' ] >= date ))[0 ]
202
- yield index
210
+ yield _intersect_qa , index
203
211
204
212
205
213
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 ,
207
216
ndv = 0 , pattern = _result_record ):
208
217
""" Output raster with classification results
209
218
@@ -215,6 +224,8 @@ def get_classification(date, result_location, image_ds,
215
224
available time segment
216
225
before (bool, optional): If date does not intersect a model, use previous
217
226
non-disturbed time segment
227
+ qa (bool, optional): Add QA flag specifying segment type (intersect,
228
+ after, or before)
218
229
pred_proba (bool, optional): Include additional band with classification
219
230
value probabilities
220
231
ndv (int, optional): NoDataValue
@@ -228,15 +239,19 @@ def get_classification(date, result_location, image_ds,
228
239
# Find results
229
240
records = find_results (result_location , pattern )
230
241
231
- logger .debug ('Allocating memory...' )
232
- nband = 2 if pred_proba else 1
242
+ n_bands = 2 if pred_proba else 1
233
243
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 )
236
244
237
245
band_names = ['Classification' ]
238
246
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 )
240
255
241
256
logger .debug ('Processing results' )
242
257
for rec in iter_records (records , warn_on_empty = WARN_ON_EMPTY ):
@@ -246,9 +261,7 @@ def get_classification(date, result_location, image_ds,
246
261
raise ValueError ('Results do not have classification prediction'
247
262
' probability values' )
248
263
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 ):
252
265
if index .shape [0 ] == 0 :
253
266
continue
254
267
@@ -258,13 +271,16 @@ def get_classification(date, result_location, image_ds,
258
271
raster [rec ['py' ][index ],
259
272
rec ['px' ][index ], 1 ] = \
260
273
rec ['class_proba' ][index ].max (axis = 1 ) * 10000
274
+ if qa :
275
+ raster [rec ['py' ][index ], rec ['px' ][index ], - 1 ] = _qa
261
276
262
277
return raster , band_names
263
278
264
279
265
280
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 ,
268
284
ndv = - 9999 , pattern = _result_record ):
269
285
""" Output a raster with coefficients from CCDC
270
286
@@ -282,6 +298,8 @@ def get_coefficients(date, result_location, image_ds,
282
298
available time segment
283
299
before (bool, optional): If date does not intersect a model, use previous
284
300
non-disturbed time segment
301
+ qa (bool, optional): Add QA flag specifying segment type (intersect,
302
+ after, or before)
285
303
ndv (int, optional): NoDataValue
286
304
pattern (str, optional): filename pattern of saved record results
287
305
@@ -302,31 +320,34 @@ def get_coefficients(date, result_location, image_ds,
302
320
n_coefs = len (i_coefs )
303
321
n_rmse = n_bands if use_rmse else 0
304
322
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
-
310
323
# Setup output band names
311
324
band_names = []
312
325
for _c in coef_names :
313
326
for _b in i_bands :
314
327
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 :
317
330
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
318
336
319
337
logger .debug ('Band names:' )
320
338
logger .debug (band_names )
321
339
322
340
_coef = 'robust_coef' if use_robust else 'coef'
323
341
_rmse = 'robust_rmse' if use_robust else 'rmse'
324
342
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
+
325
348
logger .debug ('Processing results' )
326
349
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 ):
330
351
if index .shape [0 ] == 0 :
331
352
continue
332
353
@@ -345,14 +366,17 @@ def get_coefficients(date, result_location, image_ds,
345
366
346
367
if use_rmse :
347
368
raster [rec ['py' ][index ], rec ['px' ][index ],
348
- n_coefs * n_bands :] = \
369
+ n_coefs * n_bands :n_out_bands - n_qa ] = \
349
370
rec [_rmse ][index ][:, i_bands ]
371
+ if qa :
372
+ raster [rec ['py' ][index ], rec ['px' ][index ], - 1 ] = _qa
350
373
351
- return ( raster , band_names )
374
+ return raster , band_names
352
375
353
376
354
377
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 ,
356
380
ndv = - 9999 , pattern = _result_record ):
357
381
""" Output a raster with the predictions from model fit for a given date
358
382
@@ -368,6 +392,8 @@ def get_prediction(date, result_location, image_ds,
368
392
available time segment
369
393
before (bool, optional): If date does not intersect a model, use previous
370
394
non-disturbed time segment
395
+ qa (bool, optional): Add QA flag specifying segment type (intersect,
396
+ after, or before)
371
397
ndv (int, optional): NoDataValue
372
398
pattern (str, optional): filename pattern of saved record results
373
399
@@ -384,6 +410,11 @@ def get_prediction(date, result_location, image_ds,
384
410
records , bands , None , use_robust = use_robust )
385
411
386
412
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 )
387
418
388
419
# Create X matrix from date -- ignoring categorical variables
389
420
if re .match (r'.*C\(.*\).*' , design ):
@@ -398,22 +429,22 @@ def get_prediction(date, result_location, image_ds,
398
429
399
430
logger .debug ('Processing results' )
400
431
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 ):
404
433
if index .shape [0 ] == 0 :
405
434
continue
406
435
407
436
# Calculate prediction
408
- raster [rec ['py' ][index ], rec ['px' ][index ], :] = \
437
+ raster [rec ['py' ][index ], rec ['px' ][index ], :n_i_bands ] = \
409
438
np .tensordot (rec ['coef' ][index , :][:, :, i_bands ], X ,
410
439
axes = (1 , 0 ))
440
+ if qa :
441
+ raster [rec ['py' ][index ], rec ['px' ][index ], - 1 ] = _qa
411
442
412
- return raster
443
+ return raster , band_names
413
444
414
445
415
446
def get_phenology (date , result_location , image_ds ,
416
- after = False , before = False ,
447
+ after = False , before = False , qa = False ,
417
448
ndv = - 9999 , pattern = _result_record ):
418
449
""" Output a raster containing phenology information
419
450
@@ -428,6 +459,8 @@ def get_phenology(date, result_location, image_ds,
428
459
available time segment
429
460
before (bool, optional): If date does not intersect a model, use previous
430
461
non-disturbed time segment
462
+ qa (bool, optional): Add QA flag specifying segment type (intersect,
463
+ after, or before)
431
464
ndv (int, optional): NoDataValue
432
465
pattern (str, optional): filename pattern of saved record results
433
466
@@ -440,23 +473,25 @@ def get_phenology(date, result_location, image_ds,
440
473
# Find results
441
474
records = find_results (result_location , pattern )
442
475
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
447
477
attributes = ['spring_doy' , 'autumn_doy' , 'pheno_cor' , 'peak_evi' ,
448
478
'peak_doy' , 'pheno_nobs' ]
449
479
band_names = ['SpringDOY' , 'AutumnDOY' , 'Pheno_R*10000' , 'Peak_EVI*10000' ,
450
480
'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 )
451
488
452
489
logger .debug ('Processing results' )
453
490
for rec in iter_records (records , warn_on_empty = WARN_ON_EMPTY ):
454
491
if not all ([_attr in rec .dtype .names for _attr in attributes ]):
455
492
raise ValueError ('Results do not have phenology metrics' )
456
493
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 ):
460
495
if index .shape [0 ] == 0 :
461
496
continue
462
497
@@ -470,6 +505,8 @@ def get_phenology(date, result_location, image_ds,
470
505
raster [rec ['py' ][index ],
471
506
rec ['px' ][index ], 6 ] = \
472
507
rec ['autumn_doy' ][index ] - rec ['spring_doy' ][index ]
508
+ if qa :
509
+ raster [rec ['py' ][index ], rec ['px' ][index ], - 1 ] = _qa
473
510
474
511
return raster , band_names
475
512
@@ -595,6 +632,7 @@ def parse_args(args):
595
632
# Go to next time segment option
596
633
parsed ['after' ] = args ['--after' ]
597
634
parsed ['before' ] = args ['--before' ]
635
+ parsed ['qa' ] = args ['--qa' ]
598
636
599
637
return parsed
600
638
@@ -612,7 +650,7 @@ def main(args):
612
650
if args ['class' ]:
613
651
raster , band_names = get_classification (
614
652
args ['date' ], args ['results' ], image_ds ,
615
- after = args ['after' ], before = args ['before' ],
653
+ after = args ['after' ], before = args ['before' ], qa = args [ 'qa' ],
616
654
pred_proba = args ['pred_proba' ]
617
655
)
618
656
elif args ['coef' ]:
@@ -621,21 +659,21 @@ def main(args):
621
659
args ['bands' ], args ['coefs' ],
622
660
no_scale_intercept = args ['no_scale_intercept' ],
623
661
use_robust = args ['use_robust' ],
624
- after = args ['after' ], before = args ['before' ],
662
+ after = args ['after' ], before = args ['before' ], qa = args [ 'qa' ],
625
663
ndv = args ['ndv' ]
626
664
)
627
665
elif args ['predict' ]:
628
- raster = get_prediction (
666
+ raster , band_names = get_prediction (
629
667
args ['date' ], args ['results' ], image_ds ,
630
668
args ['bands' ],
631
669
use_robust = args ['use_robust' ],
632
- after = args ['after' ], before = args ['before' ],
670
+ after = args ['after' ], before = args ['before' ], qa = args [ 'qa' ],
633
671
ndv = args ['ndv' ]
634
672
)
635
673
elif args ['pheno' ]:
636
674
raster , band_names = get_phenology (
637
675
args ['date' ], args ['results' ], image_ds ,
638
- after = args ['after' ], before = args ['before' ],
676
+ after = args ['after' ], before = args ['before' ], qa = args [ 'qa' ],
639
677
ndv = args ['ndv' ])
640
678
641
679
write_output (raster , args ['output' ], image_ds ,
0 commit comments