-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathscore_events.cxx
More file actions
2999 lines (2635 loc) · 98.6 KB
/
Copy pathscore_events.cxx
File metadata and controls
2999 lines (2635 loc) · 98.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*ckwg +5
* Copyright 2011-2015 by Kitware, Inc. All Rights Reserved. Please refer to
* KITWARE_LICENSE.TXT for licensing information, or contact General Counsel,
* Kitware, Inc., 28 Corporate Drive, Clifton Park, NY 12065.
*/
/*
In order to generate an event ROC on two sets, G and C, of tracks, the following conditions
must be met:
1) The two sets must be scorable by phase 1, i.e. have boxes and
commensurate timestamps.
2) The two sets must either have been externally filtered to contain only
the activity of interest, or must contain a track-level <int>("activity") containing
an activity index which is used to filter the set.
3) Computed set C must also also contain some track field<double> to sort and use for
sweeping an ROC. For KSTs, this is "relevancy" on the assumption that all tracks in
C were selected to be the ground-truth activity by e.g. IQR; for kwxmls, the probability of
the activity index is used.
*/
#include <algorithm>
#include <iostream>
#include <iterator>
#include <fstream>
#include <string>
#include <sstream>
#include <cstdlib>
#include <limits>
#include <vul/vul_arg.h>
#include <vul/vul_file.h>
#include <vul/vul_sprintf.h>
#include <vul/vul_awk.h>
#include <vul/vul_reg_exp.h>
#include <vul/vul_timer.h>
#include <vgl/vgl_area.h>
#include <arrows/kpf/yaml/kpf_reader.h>
#include <arrows/kpf/yaml/kpf_yaml_parser.h>
#include <arrows/kpf/yaml/kpf_packet_header.h>
#include <arrows/kpf/yaml/kpf_parse_utils.h>
#include <kwiversys/RegularExpression.hxx>
#include <track_oracle/core/track_oracle_core.h>
#include <track_oracle/core/track_base.h>
#include <track_oracle/core/track_field.h>
#include <track_oracle/file_formats/kpf_utils/kpf_utils.h>
#include <track_oracle/file_formats/track_kst/track_kst.h>
#include <track_oracle/file_formats/track_xgtf/track_xgtf.h>
#include <track_oracle/file_formats/track_kwxml/track_kwxml.h>
#include <track_oracle/file_formats/track_kw18/track_kw18.h>
#if KWANT_ENABLE_KWE
#include <track_oracle/file_formats/track_filter_kwe/track_filter_kwe.h>
#endif
#include <track_oracle/file_formats/file_format_schema.h>
#include <track_oracle/file_formats/file_format_manager.h>
#include <track_oracle/utils/logging_map.h>
#include <track_oracle/data_terms/data_terms.h>
#include <track_oracle/aries_interface/aries_interface.h>
#include <track_oracle/file_formats/track_filter_kpf_activity/track_filter_kpf_activity.h>
#include <scoring_framework/score_phase1.h>
#include <scoring_framework/score_tracks_hadwav.h>
#include <scoring_framework/score_tracks_loader.h>
#include <scoring_framework/matching_args_type.h>
#include <scoring_framework/timestamp_utilities.h>
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/split.hpp>
#include <vital/config/config_block.h>
#include <vital/logger/logger.h>
static kwiver::vital::logger_handle_t main_logger( kwiver::vital::get_logger( __FILE__ ) );
using std::back_inserter;
using std::cout;
using std::endl;
using std::fstream;
using std::getline;
using std::ifstream;
using std::istringstream;
using std::map;
using std::max;
using std::numeric_limits;
using std::ofstream;
using std::ostream;
using std::ostringstream;
using std::pair;
using std::make_pair;
using std::replace;
using std::runtime_error;
using std::string;
using std::stringstream;
using std::transform;
using std::vector;
using kwiver::track_oracle::aries_interface;
using kwiver::track_oracle::frame_handle_list_type;
using kwiver::track_oracle::field_handle_type;
using kwiver::track_oracle::oracle_entry_handle_type;
using kwiver::track_oracle::track_field;
using kwiver::track_oracle::track_handle_list_type;
using kwiver::track_oracle::track_handle_type;
using kwiver::track_oracle::file_format_schema_type;
using kwiver::track_oracle::file_format_enum;
using kwiver::track_oracle::file_format_manager;
using kwiver::track_oracle::track_oracle_core;
using kwiver::track_oracle::track_kwxml_type;
using kwiver::track_oracle::track_kst_type;
using kwiver::track_oracle::aries_interface_exception;
using kwiver::track_oracle::track_filter_kpf_activity;
using kwiver::vital::kpf::packet_header_t;
namespace { // anon
using namespace kwiver::kwant;
enum class activity_style { INVALID, VIRAT, VIRAT_AND_EXIT, PVO, PREFILTERED, KPF_ACTIVITY, KPF_OBJECT, KPF_ADHOC };
struct pvo_request_type
{
bool valid;
string pvo_filename;
size_t pvo_index;
pvo_request_type():
valid( false ), pvo_filename(""), pvo_index(0)
{}
bool set( const string& arg );
track_handle_list_type process( const track_handle_list_type& tracks, bool is_gt ) const;
};
struct activity_selector_type
{
activity_style style;
int activity_index; // for VIRAT style
string activity_name; // for KPF style
string kpf_adhoc_field; // for KPF adhoc
int activity_domain; // for KPF style
packet_header_t kpf_conf_src; // for KPF style
pvo_request_type pvo_req; // for PVO style
activity_selector_type()
: style( activity_style::INVALID ), activity_index(0), activity_name("invalid")
{}
};
//
// Abstract base class for various sources of relevancy
//
class relevancy_extractor_type
{
public:
relevancy_extractor_type() {}
virtual bool valid() const { return true; }
virtual ~relevancy_extractor_type() {}
virtual pair<bool, double> get( oracle_entry_handle_type row ) = 0;
};
//
// returns a fixed value (typically 1.0 for ground truth)
//
class fixed_extractor_type: public relevancy_extractor_type
{
public:
explicit fixed_extractor_type( double v ): val(v) {}
virtual pair<bool, double> get( oracle_entry_handle_type ) { return make_pair( true, this->val ); }
private:
double val;
};
//
// obtains the value from a single column (which stores a double)
//
class simple_field_extractor_type: public relevancy_extractor_type
{
public:
explicit simple_field_extractor_type( field_handle_type fh): f(fh) {}
virtual pair<bool, double> get( oracle_entry_handle_type row )
{
return track_oracle_core::get<double>( row, this->f );
}
private:
field_handle_type f;
};
//
// obtains the value via a KPF cset
//
// KPF csets implemented (per track_oracle kpf_utils.cxx) as TWO fields:
// each row has a map of size_t -> double, and a single instance
// of a map resolving user-side strings to the size_t key of the per-row field.
//
class kpf_cset_extractor_type: public relevancy_extractor_type
{
public:
kpf_cset_extractor_type( const string& kpf_header_string, const string& target, bool zero_if_absent );
virtual bool valid() const { return this->valid_flag; }
virtual pair<bool, double> get( oracle_entry_handle_type row );
private:
field_handle_type f; // field for the size_t -> double map
pair< bool, size_t> target_index; // index of the target string in the map@f (if true)
bool zero_flag; // if true, return <true, 0.0> if the string isn't there
bool valid_flag;
};
//
// obtains the value via a KPF activity
//
// KPF activities are stored directly as a map of <string, double>
//
class kpf_activity_extractor_type: public relevancy_extractor_type
{
public:
kpf_activity_extractor_type( field_handle_type fh, const string& t, bool zero_if_absent );
virtual pair< bool, double> get( oracle_entry_handle_type row );
private:
field_handle_type f; // field for the actual string -> double map
string target; // string we're looking for
bool zero_flag; // if true, return <true, 0.0> if string isn't there
};
//
// obtains the value via a track_field directly holding a cset
//
class kpf_adhoc_extractor_type: public relevancy_extractor_type
{
public:
kpf_adhoc_extractor_type( const string& field_name, const string& t, bool zero_if_absent );
virtual bool valid() const { return this->valid_flag; }
virtual pair< bool, double > get( oracle_entry_handle_type row );
private:
field_handle_type f; // field for the field_name
string target; // string we're looking for
bool zero_flag; // if true, return <true, 0.0> if the string isn't there
bool valid_flag;
};
//
// kpf_adhoc implementation
//
kpf_adhoc_extractor_type
::kpf_adhoc_extractor_type( const string& field_name,
const string& t,
bool zero_if_absent )
: target( t ), zero_flag( zero_if_absent ), valid_flag( false )
{
// lookup the field for the cset; return if not found
this->f = track_oracle_core::lookup_by_name( field_name );
if ( this->f == kwiver::track_oracle::INVALID_FIELD_HANDLE) return;
this->valid_flag = true;
}
pair< bool, double >
kpf_adhoc_extractor_type
::get( oracle_entry_handle_type row )
{
if ( ! this->valid_flag ) return make_pair( false, 0.0 );
const auto& m = track_oracle_core::get< kwiver::track_oracle::kpf_cset_type >( row, this->f );
if ( ! m.first ) return make_pair( false, 0.0 );
const auto& p = m.second.find( this->target );
return
p != m.second.end()
? make_pair( true, p->second )
: make_pair( this->zero_flag, 0.0 );
}
//
// kpf_cset implementation
//
kpf_cset_extractor_type
::kpf_cset_extractor_type( const string& kpf_header_string,
const string& target,
bool zero_if_absent)
: zero_flag( zero_if_absent ), valid_flag( false )
{
// lookup the field for per-row index->double table, return if not found
this->f = track_oracle_core::lookup_by_name( kpf_header_string );
if (this->f == kwiver::track_oracle::INVALID_FIELD_HANDLE) return;
// lookup the single instance of string->index, return if not found
auto s2i_fh = track_oracle_core::lookup_by_name( kpf_header_string+"_s2i" );
if (s2i_fh == kwiver::track_oracle::INVALID_FIELD_HANDLE) return;
// obtain the string->index map; return if not found
const auto& s2i_map =
track_oracle_core::get< map<string, size_t > >(
kwiver::track_oracle::SYSTEM_ROW_HANDLE,
s2i_fh );
if ( ! s2i_map.first ) return;
// lookup and cache the index of the target string
// target_index is a pair; set the first to false when we have a valid
// string which nobody ever reported and therefore isn't in the map
auto p = s2i_map.second.find( target );
this->target_index =
p == s2i_map.second.end()
? make_pair( false, static_cast<size_t>(0) )
: make_pair( true, p->second );
// all set!
this->valid_flag = true;
}
pair< bool, double >
kpf_cset_extractor_type
::get( oracle_entry_handle_type row )
{
if ( ! this->valid_flag ) return make_pair( false, 0.0 );
// get the per-row size_t -> double map
const auto& m = track_oracle_core::get< map< size_t, double > >( row, this->f );
if ( ! m.first ) return make_pair( false, 0.0 );
// if the target index is false (i.e. it's a string we NEVER saw...)
if (! this->target_index.first )
{
// ... return (true, 0.0) if zero-if-absent flag is set, (false, 0.0) otherwise
return make_pair( this->zero_flag, 0.0 );
}
// lookup the index in the map, return value if found,
// otherwise return value conditioned on the zero-if-absent-flag
auto p = m.second.find( this->target_index.second );
return
p != m.second.end()
? make_pair( true, p->second )
: make_pair( this->zero_flag, 0.0 );
}
//
// kpf_activity implementation
//
kpf_activity_extractor_type
::kpf_activity_extractor_type( field_handle_type fh, const string& t, bool zero_if_absent ):
f(fh), target(t), zero_flag( zero_if_absent )
{
}
pair< bool, double >
kpf_activity_extractor_type
::get( oracle_entry_handle_type row )
{
// get the per-row string->double map
const auto& m = track_oracle_core::get< kwiver::track_oracle::kpf_cset_type >( row, this->f );
if ( ! m.first ) return make_pair( false, 0.0 );
// look for the string; return val if found, else according to zero_flag
auto p = m.second.find( this->target );
return
p == m.second.end()
? make_pair( true, p->second )
: make_pair( this->zero_flag, 0.0 );
}
ostream& operator<<( ostream& os, const activity_selector_type& a )
{
switch (a.style)
{
case activity_style::INVALID:
os << "invalid";
break;
case activity_style::VIRAT:
os << "VIRAT: index " << a.activity_index;
break;
case activity_style::VIRAT_AND_EXIT:
os << "VIRAT (listing activities and exit)";
break;
case activity_style::PVO:
os << "PVO (index " << a.pvo_req.pvo_index << ")";
break;
case activity_style::PREFILTERED:
os << "prefiltered";
break;
case activity_style::KPF_ACTIVITY:
os << "KPF " << a.activity_name << " / " << a.activity_domain << " via activity";
break;
case activity_style::KPF_OBJECT:
os << "KPF " << a.activity_name << " / " << a.activity_domain << " via object";
break;
case activity_style::KPF_ADHOC:
os << "KPF adhoc: " << a.activity_name << " via field " << a.kpf_adhoc_field;
default:
os << "UNKNOWN?";
break;
}
return os;
}
bool
pvo_request_type
::set( const string& arg )
{
if (arg == "" ) return true;
size_t colon_idx = arg.find( ':' );
if ((colon_idx == string::npos) || (colon_idx != arg.size()-2))
{
LOG_ERROR( main_logger, "Couldn't parse pvo request type '" << arg << "'" );
return false;
}
this->pvo_filename = arg.substr( 0, colon_idx );
string pvo_flag = arg.substr( colon_idx+1, 1 );
string tr_src = "PpVvOo";
size_t tr_dst[] = {0, 0, 1, 1, 2, 2};
size_t pvo_pos = tr_src.find( pvo_flag[0] );
if (pvo_pos == string::npos)
{
LOG_ERROR( main_logger, "Bad PVO flag '" << pvo_flag[0] << "'; must be one of '" << tr_src << "'" );
return false;
}
else
{
this->pvo_index = tr_dst[ pvo_pos ];
}
LOG_INFO( main_logger, "Will read PVO values from '" << pvo_filename << "' using " << pvo_flag << " value" );
this->valid = true;
return true;
}
track_handle_list_type
pvo_request_type
::process( const track_handle_list_type& tracks,
bool is_gt ) const
{
track_field< double > relevancy( "relevancy" );
if (is_gt)
{
for (size_t i=0; i<tracks.size(); ++i)
{
relevancy( tracks[i].row ) = 1.0;
}
}
else
{
// build up our own little ad-hoc domain for the track IDs
track_field< unsigned > external_id( "external_id" );
map< unsigned, oracle_entry_handle_type > id_map;
for (size_t i=0; i<tracks.size(); ++i)
{
oracle_entry_handle_type h = tracks[i].row;
pair< bool, unsigned > probe = external_id.get( h );
if ( ! probe.first )
{
throw runtime_error( "Cannot set PVO values on computed tracks without external IDs" );
}
if (id_map.find( probe.second ) != id_map.end())
{
ostringstream oss;
oss << "PVO: computed track set has duplicate track ID " << probe.second;
throw runtime_error( oss.str().c_str() );
}
id_map[ probe.second ] = h;
}
// load up the pvo map
ifstream is( this->pvo_filename.c_str() );
if ( ! is )
{
throw runtime_error( "Couldn't open PVO filename '" + this->pvo_filename + "'" );
}
string tmp;
while (getline( is, tmp ))
{
replace( tmp.begin(), tmp.end(), ',', ' ');
istringstream iss( tmp );
unsigned id;
double pvo[3];
if (!( iss >> id >> pvo[0] >> pvo[1] >> pvo[2] ))
{
LOG_WARN( main_logger, "Couldn't parse PVO value from string '" << tmp << "'" );
}
map< unsigned, oracle_entry_handle_type >::iterator probe = id_map.find( id );
if ( probe == id_map.end())
{
LOG_WARN( main_logger, "PVO map refers to non-existent track " << id << "; skipping" );
continue;
}
// set the value
relevancy( probe->second ) = pvo[ this->pvo_index ];
// remove
id_map.erase( probe );
}
if (! id_map.empty() )
{
LOG_WARN( main_logger, "Warning: " << id_map.size() << " input tracks were not referenced in the PVO map" );
}
}
return tracks;
}
} // anon
using namespace kwiver::kwant;
struct output_args_type
{
vul_arg< string > track_stats_fn;
vul_arg< string > target_stats_fn;
vul_arg< string > matches_dump_fn;
vul_arg< string > ts_dump_fn;
vul_arg< string > roc_dump_fn;
vul_arg< string > roc_csv_dump_fn;
vul_arg< string > pr_dump_fn;
vul_arg< string > thresholds_arg;
vul_arg< bool > console_dump_arg;
output_args_type()
: track_stats_fn( "--track-stats", "write track purity / continuity to file" ),
target_stats_fn( "--target-stats", "write target purity / continuity to file" ),
matches_dump_fn( "--matches", "write track match info to file" ),
ts_dump_fn( "--ts-dump", "write ground-truth frame -> timestamps to file" ),
roc_dump_fn( "--roc-dump", "write the roc chart information to file" ),
roc_csv_dump_fn( "--roc-csv-dump", "write the roc chart information to file (CSV format)" ),
pr_dump_fn( "--pr-dump", "write the P/R curve information to file (if not set, dump to cout" ),
thresholds_arg( "--thresholds", "Manually specified thresholds to score on (min[:max[:step]])" ),
console_dump_arg( "--console", "Write ROC lines to the console" )
{}
};
void
compute_roc( const track2track_phase1& p1,
const track_handle_list_type& truth_tracks,
const track_handle_list_type& computed_tracks,
double fa_norm,
int max_n_roc_points,
output_args_type& output_args );
void
compute_pr( const track2track_phase1& p1,
track_handle_list_type truth_tracks,
track_handle_list_type computed_tracks,
output_args_type& output_args );
//
// There are two types of filtering we need to support.
//
// Track-style filtering typically only applies to kwxml files;
// it allows tracks of (say) "trackKWE" to be selected from a
// set of larger files.
//
// Activity filtering is used to select tracks which contain a given
// activity. Currently, the downstream scoring code will be looking
// for a single value (1.0 for ground truth, [0..1] for computed
// activity measures) in the "relevancy" slot introduced by the KST
// format. There are three paths to populating the relevancy slot:
//
// 1) It's already there (i.e. a KST file.) Do nothing.
//
// 2) If the track contains both activity() and activity_probability()
// fields (i.e. xgtf), discard the track if activity() doesn't match
// the --a parameter. If it matches, keep the track and copy the
// probability over to the relevancy slot.
//
// 3) If the track contains a descriptor_classifier, keep the track
// and copy the activity probability over to relevancy.
//
// If both (2) and (3) are true, it's an error.
//
// If none of (1), (2), or (3) is true, it's an error.
//
// If track_style is present in the track but no track-style filtering
// is selected, that's an error. (Allow ':all:' to bypass the filter
// and, therefore, accept all track styles.)
//
//
// utility routine to return the source of a track
//
string
get_track_source( const track_handle_type& h )
{
file_format_schema_type fst;
string source_file = "undefined";
if ( fst.source_file_id.exists( h.row ))
{
source_file = file_format_schema_type::source_id_to_filename(
fst( h ).source_file_id() );
}
return source_file;
}
//
// Filter on track_style. If the track doesn't define track_style,
// copy. If it defines it but no argument is specified, throw.
// Otherwise, copy if the style matches or the argument is ':all:'.
//
track_handle_list_type
filter_on_track_style( const track_handle_list_type& input_tracks,
vul_arg<string>& track_style_arg )
{
track_field< kwiver::track_oracle::dt::tracking::track_style > track_style;
const string any_style = ":all:";
track_handle_list_type ret;
for (size_t i=0; i<input_tracks.size(); ++i)
{
const oracle_entry_handle_type& row = input_tracks[i].row;
// if the track does not define track style, copy it and continue.
if ( ! track_style.exists( row ))
{
ret.push_back( input_tracks[i] );
continue;
}
// otherwise, if the track style argument isn't set, throw an error.
if ( ! track_style_arg.set() )
{
string source_file = get_track_source( input_tracks[i] );
throw runtime_error( "At least one track in source '" + source_file + "' defines" +
" track_style but the '" + track_style_arg.option() +
"' option is not set.\nPlease select a track style or use '" +
any_style +"' to select all tracks regardless of style.\n" );
}
// If we get here, the track defines a style and the user has selected
// a style. If they match, copy it over.
string selected_style = track_style_arg();
if ( (selected_style == any_style) || (track_style(row) == selected_style))
{
ret.push_back( input_tracks[i] );
}
}
return ret;
}
//
// Filter the input tracks such that every output track:
// (a) has a probability / likelihood / whatever defined for the
// user-selected activity
// (b) that probability is copied into the relevance() slot.
//
// For ground truth tracks, we only want to keep tracks whose
// value in (a) is 1.0. For computed tracks, we'll take them
// all (assuming appropriate descriptor-level filtering was handled
// by filter_on_track_style.)
//
// The p2r flag is only used if the file is KST. Previously,
// it was used to convert kwxml descriptor probs to KST relevancy,
// but now that always happens implicitly. p2r is only when a KST
// file contains relevancy unrelated to the descriptor classifier,
// i.e. some sort of IQR return ranking.
//
// pvo requests are special-cased by loading the pvo file and plugging
// 1.0 as relevancy for ground-truth or the appropriate p/v/o value as
// relevancy for computed.
//
// If the input is prefiltered, we assume it has relevancy externally
// set as well. (If we're ground-truth, we'll set it to 1.0.)
//
track_handle_list_type
normalize_activity_tracks( const track_handle_list_type& input_tracks,
bool input_is_gt,
bool input_is_prefiltered,
bool probability_to_relevancy,
const activity_selector_type& what_act )
{
//
// KPF activity tracks have already been normalized
//
if (what_act.style == activity_style::KPF_ACTIVITY)
{
return input_tracks;
}
if ( what_act.pvo_req.valid )
{
return what_act.pvo_req.process( input_tracks, input_is_gt );
}
kwiver::logging_map_type wmap( main_logger, KWIVER_LOGGER_SITE );
wmap.set_output_prefix( "normalize_activity_tracks: " );
// (1) from xgtf
track_field< kwiver::track_oracle::dt::events::event_type > activity;
track_field< kwiver::track_oracle::dt::events::event_probability > activity_probability;
// (2) from kwxml
track_field< vector<double> > descriptor_classifier( "descriptor_classifier" );
// (3) from kst
track_field< double > relevancy( "relevancy" );
enum {NONE = 0, HAS_XGTF = 1, HAS_KWXML = 2, HAS_KST = 4};
track_handle_list_type ret;
//
// KPF objects
//
if (what_act.style == activity_style::KPF_OBJECT)
{
ostringstream oss;
oss << kwiver::vital::kpf::style2str( kwiver::vital::kpf::packet_style::CSET )
<< "_"
<< what_act.activity_domain;
// for ground-truth, absence means exclude
// for computed, absence means assume 0.0
bool zero_if_absent = (! input_is_gt );
relevancy_extractor_type* r =
new kpf_cset_extractor_type( oss.str(), what_act.activity_name, zero_if_absent );
LOG_DEBUG( main_logger, "KPF cset extractor from " << oss.str() << " for " << what_act );
for (const auto& i: input_tracks )
{
size_t match_count = 0;
double last_r = 0.0;
for (const auto &f: track_oracle_core::get_frames( i ))
{
auto p = r->get( f.row );
if (input_is_prefiltered || p.first)
{
relevancy( f.row ) = p.second;
last_r = p.second;
++match_count;
}
}
if (match_count == track_oracle_core::get_n_frames( i ))
{
// LOG_DEBUG( main_logger, "all frames match; setting track to " << last_r );
relevancy( i.row ) = last_r;
ret.push_back( i );
}
else if (match_count > 0)
{
LOG_WARN( main_logger, "Inconsistent detection flags for multi-detection track: matched "
<< match_count << " detections out of " << track_oracle_core::get_n_frames( i )
<< "; not counting as match" );
}
else if (match_count == 0)
{
// do nothing, it's just a straight miss
}
}
delete r;
return ret;
}
//
// KPF adhoc
//
if (what_act.style == activity_style::KPF_ADHOC)
{
// for ground-truth, absence means exclude
// for computed, absence means assume 0.0
bool zero_if_absent = (! input_is_gt );
relevancy_extractor_type* r =
new kpf_adhoc_extractor_type( what_act.kpf_adhoc_field, what_act.activity_name, zero_if_absent );
LOG_INFO( main_logger, "KPF adhoc extractor for " << what_act.activity_name << " from " << what_act.kpf_adhoc_field << ": valid? " << r->valid() );
if (! r->valid())
{
delete r;
return ret;
}
for (const auto& i: input_tracks )
{
size_t match_count = 0;
double last_r = 0.0;
for (const auto& f: track_oracle_core::get_frames( i ))
{
auto p = r->get( f.row );
if (input_is_prefiltered || p.first )
{
relevancy( f.row ) = p.second;
last_r = p.second;
++match_count;
}
}
if (match_count == track_oracle_core::get_n_frames( i ))
{
// LOG_DEBUG( main_logger, "all frames match; setting track to " << last_r );
relevancy( i.row ) = last_r;
ret.push_back( i );
}
else if (match_count > 0)
{
LOG_WARN( main_logger, "Inconsistent detection flags for multi-detection track: matched "
<< match_count << " detections out of " << track_oracle_core::get_n_frames( i )
<< "; not counting as match" );
}
else if (match_count == 0)
{
// do nothing; it's just a straight miss
}
}
delete r;
return ret;
}
for (size_t i=0; i<input_tracks.size(); ++i)
{
const oracle_entry_handle_type& row = input_tracks[i].row;
// quick exit if prefiltered set and we have relevancy (
if (input_is_prefiltered)
{
ret.push_back( input_tracks[i] );
if (input_is_gt)
{
relevancy( row ) = 1.0;
}
else
{
if (! relevancy.exists(row ))
{
wmap.add_msg( "Prefiltered computed track has no relevancy" );
}
}
continue;
}
// Must have one of (1) or (2) or (3)
int flag = NONE;
if (activity.exists( row ) && activity_probability.exists( row )) flag += HAS_XGTF;
if ((! relevancy.exists( row ) ) && descriptor_classifier.exists( row )) flag += HAS_KWXML;
if (relevancy.exists( row ) && descriptor_classifier.exists( row )) flag += HAS_KST;
bool keep_this_track = false;
switch (flag)
{
case HAS_XGTF:
{
if (what_act.style == activity_style::VIRAT)
{
wmap.add_msg( "track activity style XGTF" );
if (activity( row ) == what_act.activity_index )
{
double p = activity_probability( row );
if ((input_is_gt) && (p != 1.0))
{
LOG_WARN( main_logger, "XGTF-style probability for activity " << what_act.activity_index << " is " << p << "?" );
}
relevancy( row ) = p;
keep_this_track = true;
}
}
else
{
ostringstream oss;
oss << "XGTF input detected but activity selector is " << what_act;
wmap.add_msg( oss.str() );
}
}
break;
case HAS_KWXML:
{
if (what_act.style == activity_style::VIRAT)
{
wmap.add_msg( "track activity style kwxml" );
vector< double > probs = descriptor_classifier( row );
double p = probs [ what_act.activity_index ];
if (input_is_gt)
{
// If we're claiming this is ground truth, only keep tracks of the
// desired style whose probability is 1.0
keep_this_track = ( p == 1.0 );
}
else
{
// If it's not ground truth, always keep
keep_this_track = true;
}
if (keep_this_track)
{
relevancy( row ) = p;
}
}
else
{
ostringstream oss;
oss << "KWXML input detected but activity selector is " << what_act;
wmap.add_msg( oss.str() );
}
}
break;
case HAS_KST:
{
if (what_act.style == activity_style::VIRAT)
{
wmap.add_msg( "track activity style kst" );
if (probability_to_relevancy)
{
vector< double > probs = descriptor_classifier( row );
double p = probs [ what_act.activity_index ];
if ((input_is_gt) && (p != 1.0))
{
LOG_WARN( main_logger, "KWXML-style probability for activity " << what_act.activity_index << " is " << p << "?" );
}
relevancy( row ) = p;
}
keep_this_track = true;
}
else
{
ostringstream oss;
oss << "KST input detected but activity selector is " << what_act;
wmap.add_msg( oss.str() );
}
}
break;
case NONE:
{
wmap.add_msg( "track activity style none" );
string source_file = get_track_source( input_tracks[i] );
file_format_schema_type fst;
bool add_empty_classifier = false;
if (fst.format.exists( input_tracks[i].row ))
{
add_empty_classifier = (fst.format( input_tracks[i].row ) == kwiver::track_oracle::TF_KWXML);
}
if (add_empty_classifier)
{
vector< double > activity_probabilities;
activity_probabilities.resize( aries_interface::index_to_activity_map().size(), 0.0 );
descriptor_classifier( row ) = activity_probabilities;
ostringstream oss;
oss << "Added empty classifier to track from " << source_file;
wmap.add_msg( oss.str() );
}
else
{
// I don't think this is a problem?
/*
throw runtime_error( "At least one track in '" + source_file + "' does not define " +
"either xgtf or kwxml/kst activities; can't be used in score_events.");
*/
}
}
break;
default:
{
string source_file = get_track_source( input_tracks[i] );
throw runtime_error( "At least one track in '" + source_file + "' defines " +
"both xgtf-acitivty and kwxml/kst descriptor classifier fields;\n" +
"no workaround defined yet.");
}
} // switch
if (keep_this_track)
{
ret.push_back( input_tracks[i] );
}
} // ...for all tracks
wmap.dump_msgs();
return ret;
}
// Link (kwxml) tracks "K" via their source track ID.
// Assume all K tracks are the same track style; the intent is to
// create a single e.g. PVMoving track from all the PVMoving segments