Skip to content

Commit dbce3ac

Browse files
authored
Merge pull request #63 from yoterel/master
adds mirror_annotation, and improves command line options consistency.
2 parents 1cf0d06 + 70411cd commit dbce3ac

7 files changed

Lines changed: 162 additions & 114 deletions

File tree

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "icatcher"
7-
version = "0.1.2"
7+
version = "0.2.0"
88
description = "iCatcher+: Robust and automated annotation of infant gaze from videos collected in laboratory, field, and online studies."
99
readme = "README.md"
1010
authors = [{ name = "Yotam Erel", email = "erelyotam@gmail.com" }]
@@ -39,7 +39,7 @@ dev = ["pytest"]
3939
Homepage = "https://github.com/yoterel/icatcher_plus"
4040

4141
[tool.bumpver]
42-
current_version = "0.1.2"
42+
current_version = "0.2.0"
4343
version_pattern = "MAJOR.MINOR.PATCH"
4444
commit_message = "bump version {old_version} -> {new_version}"
4545
commit = false

src/icatcher/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
### define version
2-
__version__ = "0.1.2"
2+
__version__ = "0.2.0"
33
version = __version__
44
### define classes
55
classes = {"noface": -2, "nobabyface": -1, "away": 0, "left": 1, "right": 2}

src/icatcher/cli.py

Lines changed: 23 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
parallelize_face_detection,
2222
detect_face_opencv_dnn,
2323
)
24-
from pathos.helpers import cpu_count
2524
from batch_face import RetinaFace
2625

2726

@@ -278,11 +277,12 @@ def create_output_streams(video_path, framerate, resolution, opt):
278277
prediction_output_file = Path(
279278
opt.output_annotation, video_path.stem + opt.output_file_suffix
280279
)
281-
if opt.output_format == "PrefLookTimestamp":
282-
with open(prediction_output_file, "w", newline="") as f: # Write header
283-
f.write(
284-
"Tracks: left, right, away, codingactive, outofframe\nTime,Duration,TrackName,comment\n\n"
285-
)
280+
if prediction_output_file.exists():
281+
if opt.overwrite:
282+
prediction_output_file.unlink()
283+
else:
284+
raise FileExistsError("Annotation output file already exists. Use --overwrite flag to overwrite.")
285+
286286
return video_output_file, prediction_output_file, skip
287287

288288

@@ -367,11 +367,7 @@ def predict_from_video(opt):
367367
last_class_text = "" # Initialize so that we see the first class assignment as an event to record
368368

369369
# if going to use cpu parallelization, don't allow for live stream video
370-
if use_cpu and opt.fd_model == "retinaface" and not opt.dont_buffer:
371-
# figure out how many cpus can be used
372-
num_cpus = cpu_count() - opt.num_cpus_saved
373-
assert num_cpus > 0
374-
370+
if use_cpu and opt.fd_model == "retinaface" and opt.fd_parallel_processing:
375371
# send all frames in to be preprocessed and have faces detected prior to running gaze detection
376372
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
377373
vid_frames = range(
@@ -384,9 +380,9 @@ def predict_from_video(opt):
384380
processed_frames[0].shape[0],
385381
processed_frames[0].shape[1],
386382
)
387-
logging.debug("face detection on buffered frames ...")
383+
logging.info("performing face detection on buffered frames...")
388384
faces = parallelize_face_detection(
389-
processed_frames, face_detector_model, num_cpus, opt
385+
processed_frames, face_detector_model, opt.fd_num_cpus, opt
390386
)
391387
del processed_frames
392388

@@ -413,7 +409,7 @@ def predict_from_video(opt):
413409
frames.append(frame)
414410

415411
if (
416-
use_cpu and opt.fd_model == "retinaface" and not opt.dont_buffer
412+
use_cpu and opt.fd_model == "retinaface" and opt.fd_parallel_processing
417413
): # if using cpu, just pull from master
418414
bboxes = master_bboxes[frame_count]
419415
elif opt.fd_model == "opencv_dnn":
@@ -528,6 +524,11 @@ def predict_from_video(opt):
528524
corrected_transitions,
529525
)
530526
class_text = reverse_classes[answers[cursor]]
527+
if opt.mirror_annotation:
528+
if class_text == "left":
529+
class_text = "right"
530+
elif class_text == "right":
531+
class_text = "left"
531532
if opt.on_off:
532533
class_text = "off" if class_text == "away" else "on"
533534
if opt.output_video_path:
@@ -576,16 +577,6 @@ def predict_from_video(opt):
576577
confidences[cursor],
577578
)
578579
)
579-
elif opt.output_format == "PrefLookTimestamp":
580-
if (
581-
class_text != last_class_text
582-
): # Record "event" for change of direction if code has changed
583-
frame_ms = int(
584-
(frame_count + cursor + 1) * (1000.0 / framerate)
585-
)
586-
with open(prediction_output_file, "a", newline="") as f:
587-
f.write("{},0,{}\n".format(frame_ms, class_text))
588-
last_class_text = class_text
589580
logging.info(
590581
"frame: {}, class: {}, confidence: {:.02f}, cur_fps: {:.02f}".format(
591582
str(frame_count + cursor + 1),
@@ -624,12 +615,14 @@ def cleanup(
624615
if opt.output_video_path:
625616
video_output_file.release()
626617
if opt.output_annotation: # write footer to file
627-
if opt.output_format == "PrefLookTimestamp":
628-
start_ms = int((1000.0 / framerate) * (opt.sliding_window_size // 2))
629-
end_ms = int((1000.0 / framerate) * frame_count)
630-
with open(prediction_output_file, "a", newline="") as f:
631-
f.write("{},{},codingactive\n".format(start_ms, end_ms))
632-
elif opt.output_format == "compressed":
618+
if opt.output_format == "compressed":
619+
answers = np.array(answers)
620+
confidences = np.array(confidences)
621+
if opt.mirror_annotation:
622+
lefts = answers == classes["left"]
623+
rights = answers == classes["right"]
624+
answers[lefts] = classes["right"]
625+
answers[rights] = classes["left"]
633626
np.savez(prediction_output_file, answers, confidences)
634627
cap.release()
635628

src/icatcher/face_detector.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from pathos.pools import ProcessPool
55
from icatcher import draw
66
import logging
7+
from tqdm import tqdm
78

89

910
def threshold_faces(all_faces: list, confidence_threshold: float):
@@ -60,12 +61,10 @@ def process_frames(cap, frames, h_start_at, h_end_at, w_start_at, w_end_at):
6061
:param h_end_at: optional crop coordinate
6162
:param w_start_at: optional crop coordinate
6263
:param w_end_at: optional crop coordinate
63-
:param v
6464
:return: list of images corresponding to video frames
6565
"""
6666
processed_frames = []
67-
for frame in frames:
68-
logging.debug("buffering frames {}/{}".format(frame, len(frames)))
67+
for frame in tqdm(frames, desc="buffering frames"):
6968
cap.set(cv2.CAP_PROP_POS_FRAMES, frame)
7069
ret, image = cap.read()
7170
if ret:

0 commit comments

Comments
 (0)