Skip to content

Commit 6c25358

Browse files
authored
Merge pull request #474 from cameron-a-johnson/dev/fixes-to-gsp
Functionality restored for GSP training.
2 parents 0814edf + bd7c6f4 commit 6c25358

File tree

3 files changed

+90
-55
lines changed

3 files changed

+90
-55
lines changed

TRAIN_AND_RUN_README.md

+69
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,75 @@ train_command \
338338
task_name=my_m2_training
339339
```
340340

341+
## The Global Step Predictor (GSP)
342+
### How the GSP relates to the TCN Activity Classifier
343+
344+
The above TCN activity classifier in its current configuration takes in a second or
345+
two of video artifacts (e.g. for the "Locs&Confs" version, pose joint pixel coordinates
346+
and confidences, the user's hand detection locations and confidences, and other procedure-relevant
347+
object pixel coordinates and confidences), and outputs confidences for each of a vector
348+
of activities (examples: "labels" in config/activity_labels/medical), assuming up to
349+
one activity is ocurring "presently."
350+
351+
Now, the GSP takes as input the confidence vector per frame window, and keeps track over time of which
352+
activities ("steps" in the GSP context) have occurred, and which step a user is on.
353+
354+
Basically, if the "next step" at any point has been activated long enough, and with enough confidence,
355+
the GSP progresses to that step as the latest "completed" step.
356+
357+
Assumptions:
358+
- One activity or "background" (none of the listed activities) happens at a time.
359+
- The activities must happen in a specific linear order.
360+
- So, if an activity is detected with strong confidence way out of order (e.g. we're on step 3
361+
and we detect step 8), the GSP does not mark step 8 as completed.
362+
- A single "skipped step" is possible given some criteria. Skipping one step can be allowed unconditionally.
363+
Skipping one step can also be allowed if the "skipped step" has been activated with some "easier" criteria
364+
(a lower confidence threshold and/or fewer frames above that threshold). We can also configure to skip one step
365+
simply given that a threshold number of frames have passed since we completed the most recent step.
366+
367+
### Training the GSP
368+
369+
To "train" the GSP, we simply compute the average true positive output scores per class- that is, the
370+
average confidence of each TCN activity classification in its output vector,
371+
only when ground truth states that activity is happening. This includes the background class.
372+
373+
To do this, we must run inference on videos for which the TCN has never seen ground truth (and are hopefully
374+
quite independent from the training videos). The validation or test splits of your dataset may suffice.
375+
*Note:* If you have run training, test set prediction outputs should have been produced, in a
376+
file named `tcn_activity_predictions.kwcoco.json`.
377+
378+
If you don't have that file, he TCN's training harness can be run with `train=false` to only run
379+
inference and save the test data's output in the needed KWCOCO output format. Example:
380+
381+
```
382+
python train_command \
383+
experiment=r18/feat_locsconfs \
384+
paths.root_dir=/path/to/my/data/splits/ \ # See above TCN docs for training data structure
385+
task_name=r18_my_TCN_i_just_trained \
386+
train=false \
387+
ckpt_path=model_files/activity_classifier/r18_tcn.ckpt \
388+
```
389+
390+
This should create a new predictions output file, e.g. `tcn_activity_predictions.kwcoco.json`.
391+
392+
Then, for each class, we filter the video outputs by those which the ground truth indicates that class
393+
activity is occurring. Then we simply average the TCN output for that activity, for those frames.
394+
395+
Given the test dataset split ground truth you just gave as input to the `train_command`, and the predictions
396+
output file your `train_command` produced, create the average TP activation numpy file as follows:
397+
398+
```
399+
python angel_system/global_step_prediction/run_expirement.py r18 \
400+
path/to/TEST-activity_truth.coco.json \
401+
path/to/tcn_activity_predictions.kwcoco.json \
402+
path/to/tcn_activity_predictions.kwcoco.json
403+
404+
```
405+
406+
That numpy file then can be provisioned to load to the default GSP `model_files` filepath, e.g. in the case
407+
of the R18 task, `model_files/task_monitor/global_step_predictor_act_avgs_r18.npy`.
408+
409+
341410
## Docker local testing
342411

343412
***to start the service run:***

angel_system/global_step_prediction/global_step_predictor.py

+17-8
Original file line numberDiff line numberDiff line change
@@ -143,20 +143,21 @@ def get_activity_order_from_config(self, config_fn):
143143

144144
def compute_average_TP_activations(
145145
self,
146-
coco_train: kwcoco.CocoDataset,
146+
coco_preds: kwcoco.CocoDataset,
147+
coco_truth: kwcoco.CocoDataset,
147148
):
148149
# For each activity, given the Ground Truth-specified
149150
# frame subset where that activity is happening, get the
150151
# average activation of that class.
151152

152-
all_activity_ids = coco_train.categories().get("id")
153+
all_activity_ids = coco_preds.categories().get("id")
153154
print(f"all_activity_ids: {all_activity_ids}")
154155

155156
# create a mapping of all the annots ids to image ids in the training set
156-
tr_aid_to_gid = coco_train.annots().get("image_id", keepid=True)
157+
tr_aid_to_gid = coco_preds.annots().get("image_id", keepid=True)
157158
print(f"training set annotations: {len(tr_aid_to_gid)}")
158159

159-
all_vid_ids = coco_train.videos().get("id")
160+
all_vid_ids = coco_preds.videos().get("id")
160161
print(
161162
f"Computing average true positive activations for {len(all_vid_ids)} video(s)."
162163
)
@@ -165,10 +166,18 @@ def compute_average_TP_activations(
165166
avg_probs = np.zeros(max(all_activity_ids) + 1)
166167

167168
for activity_id in all_activity_ids:
168-
sub_dset = coco_train.subset(gids=tr_aid_to_gid.keys(), copy=True)
169-
probs_for_true_inds = np.asarray(sub_dset.annots().get("prob"))[
170-
:, activity_id
171-
]
169+
probs_for_true_inds = np.array([])
170+
171+
for ann_id in coco_truth.index.anns:
172+
ann = coco_truth.index.anns[ann_id]
173+
if ann["category_id"] == activity_id:
174+
referenced_img_id = ann["image_id"]
175+
pred_ann = coco_preds.annots(image_id=referenced_img_id).objs
176+
assert len(pred_ann) == 1
177+
pred_ann = pred_ann[0]
178+
TP_prob = pred_ann["prob"][activity_id]
179+
probs_for_true_inds = np.append(probs_for_true_inds, TP_prob)
180+
172181
avg_prob = np.mean(probs_for_true_inds)
173182
avg_probs[activity_id] = avg_prob
174183

angel_system/global_step_prediction/run_expirement.py

+4-47
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,9 @@ def run_inference_all_vids(
5353
if avg_probs is not None:
5454
step_predictor.get_average_TP_activations_from_array(avg_probs)
5555
else:
56-
avg_probs = step_predictor.compute_average_TP_activations(coco_train)
56+
avg_probs = step_predictor.compute_average_TP_activations(
57+
coco_train, coco_test
58+
)
5759
save_file = (
5860
code_dir
5961
/ "model_files"
@@ -104,57 +106,12 @@ def get_unique(activity_ids):
104106

105107
print(f"unique broad steps: {get_unique(broad_step_gts)}")
106108

107-
_, granular_preds, granular_gt = step_predictor.plot_gt_vs_predicted_one_recipe(
109+
_ = step_predictor.plot_gt_vs_predicted_one_recipe(
108110
granular_step_gts,
109111
recipe_type,
110112
fname_suffix=f"{str(vid_id)}_granular_{extra_output_suffix}",
111113
granular_or_broad="granular",
112114
)
113-
# _, broad_preds, broad_gt = step_predictor.plot_gt_vs_predicted_one_recipe(
114-
# broad_step_gts,
115-
# recipe_type,
116-
# fname_suffix=f"{str(vid_id)}_broad_{extra_output_suffix}",
117-
# granular_or_broad="broad",
118-
# )
119-
120-
# print(f"broad_gt len: {len(broad_gt)}")
121-
# print(f"broad_preds len: {len(broad_preds)}")
122-
# print(f"granular_gt len: {len(granular_gt)}")
123-
# print(f"granular_preds len: {len(granular_preds)}")
124-
125-
min_length = min(len(granular_preds), len(granular_gt))
126-
127-
preds.extend(granular_preds[:min_length])
128-
gt.extend(granular_gt[:min_length])
129-
130-
num_act_classes = len(step_predictor.activity_config["labels"])
131-
fig, ax = plt.subplots(figsize=(num_act_classes, num_act_classes))
132-
133-
print(f"gt len: {len(gt)}")
134-
print(f"preds len: {len(preds)}")
135-
print(f"labels: {step_predictor.activity_config['labels']}")
136-
label_ids = [item["id"] for item in step_predictor.activity_config["labels"]]
137-
labels = [item["full_str"] for item in step_predictor.activity_config["labels"]]
138-
139-
broad_cm = confusion_matrix(gt, preds, labels=label_ids, normalize="true")
140-
141-
# granular_cm = confusion_matrix(
142-
# granular_step_gts,
143-
# granular_preds,
144-
# labels=step_predictor.activity_config["labels"],
145-
# normalize="true"
146-
# )
147-
148-
sns.heatmap(broad_cm, annot=True, ax=ax, fmt=".2f", linewidth=0.5, vmin=0, vmax=1)
149-
150-
ax.set_xlabel("Predicted labels")
151-
ax.set_ylabel("True labels")
152-
# ax.set_title(f'CM GSP Accuracy: {acc:.4f}')
153-
ax.xaxis.set_ticklabels(labels, rotation=25)
154-
ax.yaxis.set_ticklabels(labels, rotation=0)
155-
# fig.savefig(f"{self.hparams.output_dir}/confusion_mat_val_acc_{acc:.4f}.png", pad_inches=5)
156-
print(f"Saving confusion matrix to {out_file}")
157-
fig.savefig(out_file.as_posix(), pad_inches=5)
158115

159116

160117
@click.command(context_settings={"help_option_names": ["-h", "--help"]})

0 commit comments

Comments
 (0)