Skip to content

Commit

Permalink
bugfix: Manual tool. Allow working with image folders at EEN values > 1
Browse files Browse the repository at this point in the history
  • Loading branch information
torzdf committed Jul 5, 2024
1 parent ea63f1e commit b6ac7b8
Show file tree
Hide file tree
Showing 14 changed files with 657 additions and 477 deletions.
25 changes: 22 additions & 3 deletions docs/full/tools/manual.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,10 @@ The Manual Module is the main entry point into the Manual Editor Tool.

.. autosummary::
:nosignatures:

~tools.manual.manual.Aligner
~tools.manual.manual.FrameLoader
~tools.manual.manual.Manual
~tools.manual.manual.TkGlobals

.. rubric:: Module

Expand All @@ -43,7 +42,7 @@ detected_faces module

.. autosummary::
:nosignatures:

~tools.manual.detected_faces.DetectedFaces
~tools.manual.detected_faces.FaceUpdate
~tools.manual.detected_faces.Filter
Expand All @@ -55,6 +54,26 @@ detected_faces module
:undoc-members:
:show-inheritance:

globals module
==============

.. rubric:: Module Summary

.. autosummary::
:nosignatures:

~tools.manual.globals.CurrentFrame
~tools.manual.globals.TkGlobals
~tools.manual.globals.TKVars

.. rubric:: Module

.. automodule:: tools.manual.globals
:members:
:undoc-members:
:show-inheritance:


thumbnails module
==================

Expand Down
5 changes: 4 additions & 1 deletion lib/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -1466,7 +1466,10 @@ def image_from_index(self, index):
image = self._reader.get_data(index)[..., ::-1]
filename = self._dummy_video_framename(index)
else:
filename = self.file_list[index]
file_list = [f for idx, f in enumerate(self._file_list)
if idx not in self._skip_list] if self._skip_list else self._file_list

filename = file_list[index]
image = read_image(filename, raise_error=True)
filename = os.path.basename(filename)
logger.trace("index: %s, filename: %s image shape: %s", index, filename, image.shape)
Expand Down
56 changes: 30 additions & 26 deletions tools/manual/detected_faces.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ def __init__(self,
logger.debug("Initialized %s", self.__class__.__name__)

# <<<< PUBLIC PROPERTIES >>>> #
# << SUBCLASSES >> #
@property
def extractor(self) -> manual.Aligner:
""" :class:`~tools.manual.manual.Aligner`: The pipeline for passing faces through the
Expand Down Expand Up @@ -108,6 +107,11 @@ def tk_face_count_changed(self) -> tk.BooleanVar:
return self._tk_vars["face_count_changed"]

# << STATISTICS >> #
@property
def frame_list(self) -> list[str]:
""" list[str]: The list of all frame names that appear in the alignments file """
return list(self._alignments.data)

@property
def available_masks(self) -> dict[str, int]:
""" dict[str, int]: The mask type names stored in the alignments; type as key with the
Expand Down Expand Up @@ -343,7 +347,7 @@ def revert_to_saved(self, frame_index: int) -> None:
self._tk_face_count_changed.set(True)
else:
self._tk_edited.set(True)
self._globals.tk_update.set(True)
self._globals.var_full_update.set(True)

@classmethod
def _add_remove_faces(cls,
Expand Down Expand Up @@ -485,7 +489,7 @@ def __init__(self, detected_faces: DetectedFaces) -> None:
def frame_meets_criteria(self) -> bool:
""" bool: ``True`` if the current frame meets the selected filter criteria otherwise
``False`` """
filter_mode = self._globals.filter_mode
filter_mode = self._globals.var_filter_mode.get()
frame_faces = self._detected_faces.current_faces[self._globals.frame_index]
distance = self._filter_distance

Expand All @@ -505,7 +509,7 @@ def frame_meets_criteria(self) -> bool:
def _filter_distance(self) -> float:
""" float: The currently selected distance when Misaligned Faces filter is selected. """
try:
retval = self._globals.tk_filter_distance.get()
retval = self._globals.var_filter_distance.get()
except tk.TclError:
# Suppress error when distance box is empty
retval = 0
Expand All @@ -514,22 +518,22 @@ def _filter_distance(self) -> float:
@property
def count(self) -> int:
""" int: The number of frames that meet the filter criteria returned by
:attr:`~tools.manual.manual.TkGlobals.filter_mode`. """
:attr:`~tools.manual.manual.TkGlobals.var_filter_mode.get()`. """
face_count_per_index = self._detected_faces.face_count_per_index
if self._globals.filter_mode == "No Faces":
if self._globals.var_filter_mode.get() == "No Faces":
retval = sum(1 for fcount in face_count_per_index if fcount == 0)
elif self._globals.filter_mode == "Has Face(s)":
elif self._globals.var_filter_mode.get() == "Has Face(s)":
retval = sum(1 for fcount in face_count_per_index if fcount != 0)
elif self._globals.filter_mode == "Multiple Faces":
elif self._globals.var_filter_mode.get() == "Multiple Faces":
retval = sum(1 for fcount in face_count_per_index if fcount > 1)
elif self._globals.filter_mode == "Misaligned Faces":
elif self._globals.var_filter_mode.get() == "Misaligned Faces":
distance = self._filter_distance
retval = sum(1 for frame in self._detected_faces.current_faces
if any(face.aligned.average_distance > distance for face in frame))
else:
retval = len(face_count_per_index)
logger.trace("filter mode: %s, frame count: %s", # type:ignore[attr-defined]
self._globals.filter_mode, retval)
self._globals.var_filter_mode.get(), retval)
return retval

@property
Expand All @@ -554,22 +558,22 @@ def raw_indices(self) -> dict[T.Literal["frame", "face"], list[int]]:
@property
def frames_list(self) -> list[int]:
""" list[int]: The list of frame indices that meet the filter criteria returned by
:attr:`~tools.manual.manual.TkGlobals.filter_mode`. """
:attr:`~tools.manual.manual.TkGlobals.var_filter_mode.get()`. """
face_count_per_index = self._detected_faces.face_count_per_index
if self._globals.filter_mode == "No Faces":
if self._globals.var_filter_mode.get() == "No Faces":
retval = [idx for idx, count in enumerate(face_count_per_index) if count == 0]
elif self._globals.filter_mode == "Multiple Faces":
elif self._globals.var_filter_mode.get() == "Multiple Faces":
retval = [idx for idx, count in enumerate(face_count_per_index) if count > 1]
elif self._globals.filter_mode == "Has Face(s)":
elif self._globals.var_filter_mode.get() == "Has Face(s)":
retval = [idx for idx, count in enumerate(face_count_per_index) if count != 0]
elif self._globals.filter_mode == "Misaligned Faces":
elif self._globals.var_filter_mode.get() == "Misaligned Faces":
distance = self._filter_distance
retval = [idx for idx, frame in enumerate(self._detected_faces.current_faces)
if any(face.aligned.average_distance > distance for face in frame)]
else:
retval = list(range(len(face_count_per_index)))
logger.trace("filter mode: %s, number_frames: %s", # type:ignore[attr-defined]
self._globals.filter_mode, len(retval))
self._globals.var_filter_mode.get(), len(retval))
return retval


Expand Down Expand Up @@ -677,7 +681,7 @@ def delete(self, frame_index: int, face_index: int) -> None:
faces = self._faces_at_frame_index(frame_index)
del faces[face_index]
self._tk_face_count_changed.set(True)
self._globals.tk_update.set(True)
self._globals.var_full_update.set(True)

def bounding_box(self,
frame_index: int,
Expand Down Expand Up @@ -717,7 +721,7 @@ def bounding_box(self,
face.top = pnt_y
face.height = height
face.add_landmarks_xy(self._extractor.get_landmarks(frame_index, face_index, aligner))
self._globals.tk_update.set(True)
self._globals.var_full_update.set(True)

def landmark(self,
frame_index: int, face_index: int,
Expand Down Expand Up @@ -764,7 +768,7 @@ def landmark(self,
face.landmarks_xy[idx] = lmk
else:
face.landmarks_xy[landmark_index] += (shift_x, shift_y)
self._globals.tk_update.set(True)
self._globals.var_full_update.set(True)

def landmarks(self, frame_index: int, face_index: int, shift_x: int, shift_y: int) -> None:
""" Shift all of the landmarks and bounding box for the
Expand Down Expand Up @@ -792,7 +796,7 @@ def landmarks(self, frame_index: int, face_index: int, shift_x: int, shift_y: in
face.left += shift_x
face.top += shift_y
face.add_landmarks_xy(face.landmarks_xy + (shift_x, shift_y))
self._globals.tk_update.set(True)
self._globals.var_full_update.set(True)

def landmarks_rotate(self,
frame_index: int,
Expand All @@ -818,7 +822,7 @@ def landmarks_rotate(self,
rot_mat = cv2.getRotationMatrix2D(tuple(center.astype("float32")), angle, 1.)
face.add_landmarks_xy(cv2.transform(np.expand_dims(face.landmarks_xy, axis=0),
rot_mat).squeeze())
self._globals.tk_update.set(True)
self._globals.var_full_update.set(True)

def landmarks_scale(self,
frame_index: int,
Expand All @@ -842,7 +846,7 @@ def landmarks_scale(self,
"""
face = self._faces_at_frame_index(frame_index)[face_index]
face.add_landmarks_xy(((face.landmarks_xy - center) * scale) + center)
self._globals.tk_update.set(True)
self._globals.var_full_update.set(True)

def mask(self, frame_index: int, face_index: int, mask: np.ndarray, mask_type: str) -> None:
""" Update the mask on an edit for the :class:`~lib.align.DetectedFace` object at
Expand All @@ -862,7 +866,7 @@ def mask(self, frame_index: int, face_index: int, mask: np.ndarray, mask_type: s
face = self._faces_at_frame_index(frame_index)[face_index]
face.mask[mask_type].replace_mask(mask)
self._tk_edited.set(True)
self._globals.tk_update.set(True)
self._globals.var_full_update.set(True)

def copy(self, frame_index: int, direction: T.Literal["prev", "next"]) -> None:
""" Copy the alignments from the previous or next frame that has alignments
Expand Down Expand Up @@ -903,7 +907,7 @@ def copy(self, frame_index: int, direction: T.Literal["prev", "next"]) -> None:

faces.extend(copied)
self._tk_face_count_changed.set(True)
self._globals.tk_update.set(True)
self._globals.var_full_update.set(True)

def post_edit_trigger(self, frame_index: int, face_index: int) -> None:
""" Update the jpg thumbnail, the viewport thumbnail, the landmark masks and the aligned
Expand All @@ -922,11 +926,11 @@ def post_edit_trigger(self, frame_index: int, face_index: int) -> None:
face.clear_all_identities()

aligned = AlignedFace(face.landmarks_xy,
image=self._globals.current_frame["image"],
image=self._globals.current_frame.image,
centering="head",
size=96)
assert aligned.face is not None
face.thumbnail = generate_thumbnail(aligned.face, size=96)
if self._globals.filter_mode == "Misaligned Faces":
if self._globals.var_filter_mode.get() == "Misaligned Faces":
self._detected_faces.tk_face_count_changed.set(True)
self._tk_edited.set(True)
15 changes: 8 additions & 7 deletions tools/manual/faceviewer/frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class FacesFrame(ttk.Frame): # pylint:disable=too-many-ancestors
Parameters
----------
parent: :class:`ttk.PanedWindow`
parent: :class:`ttk.Frame`
The paned window that the faces frame resides in
tk_globals: :class:`~tools.manual.manual.TkGlobals`
The tkinter variables that apply to the whole of the GUI
Expand All @@ -48,7 +48,7 @@ class FacesFrame(ttk.Frame): # pylint:disable=too-many-ancestors
The section of the Manual Tool that holds the frames viewer
"""
def __init__(self,
parent: ttk.PanedWindow,
parent: ttk.Frame,
tk_globals: TkGlobals,
detected_faces: DetectedFaces,
display_frame: DisplayFrame) -> None:
Expand Down Expand Up @@ -282,7 +282,7 @@ def __init__(self, parent: ttk.Frame,
def face_size(self) -> int:
""" int: The currently selected thumbnail size in pixels """
scaling = get_config().scaling_factor
size = self._sizes[self._globals.tk_faces_size.get().lower().replace(" ", "")]
size = self._sizes[self._globals.var_faces_size.get().lower().replace(" ", "")]
scaled = size * scaling
return int(round(scaled / 2) * 2)

Expand Down Expand Up @@ -328,10 +328,11 @@ def _set_tk_callbacks(self, detected_faces: DetectedFaces):
Updates the mask type when the user changes the selected mask types
Toggles the face viewer annotations on an optional annotation button press.
"""
for var in (self._globals.tk_faces_size, self._globals.tk_filter_mode):
var.trace_add("write", lambda *e, v=var: self.refresh_grid(v))
var = detected_faces.tk_face_count_changed
var.trace_add("write", lambda *e, v=var: self.refresh_grid(v, retain_position=True))
for strvar in (self._globals.var_faces_size, self._globals.var_filter_mode):
strvar.trace_add("write", lambda *e, v=strvar: self.refresh_grid(v))
boolvar = detected_faces.tk_face_count_changed
boolvar.trace_add("write",
lambda *e, v=boolvar: self.refresh_grid(v, retain_position=True))

self._display_frame.tk_control_colors["Mesh"].trace_add(
"write", lambda *e: self._update_mesh_color())
Expand Down
16 changes: 8 additions & 8 deletions tools/manual/faceviewer/interact.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ def on_hover(self, event: tk.Event | None) -> None:
is_zoomed = self._globals.is_zoomed
if (-1 in face or (frame_idx == self._globals.frame_index
and (not is_zoomed or
(is_zoomed and face_idx == self._globals.tk_face_index.get())))):
(is_zoomed and face_idx == self._globals.face_index)))):
self._clear()
self._canvas.config(cursor="")
self._current_frame_index = None
Expand Down Expand Up @@ -125,14 +125,14 @@ def _select_frame(self) -> None:
if frame_id is None or (frame_id == self._globals.frame_index and not is_zoomed):
return
face_idx = self._current_face_index if is_zoomed else 0
self._globals.tk_face_index.set(face_idx)
self._globals.set_face_index(face_idx)
transport_id = self._grid.transport_index_from_frame(frame_id)
logger.trace("frame_index: %s, transport_id: %s, face_idx: %s",
frame_id, transport_id, face_idx)
if transport_id is None:
return
self._navigation.stop_playback()
self._globals.tk_transport_index.set(transport_id)
self._globals.var_transport_index.set(transport_id)
self._viewport.move_active_to_top()
self.on_hover(None)

Expand Down Expand Up @@ -192,8 +192,8 @@ def __init__(self, viewport: Viewport, tk_edited_variable: tk.BooleanVar) -> Non
"edited": tk_edited_variable}
self._assets: Asset = Asset([], [], [], [])

self._globals.tk_update_active_viewport.trace_add("write",
lambda *e: self._reload_callback())
self._globals.var_update_active_viewport.trace_add("write",
lambda *e: self._reload_callback())
tk_edited_variable.trace_add("write", lambda *e: self._update_on_edit())
logger.debug("Initialized: %s", self.__class__.__name__)

Expand All @@ -205,7 +205,7 @@ def frame_index(self) -> int:
@property
def current_frame(self) -> np.ndarray:
""" :class:`numpy.ndarray`: A BGR version of the frame currently being displayed. """
return self._globals.current_frame["image"]
return self._globals.current_frame.image

@property
def _size(self) -> int:
Expand All @@ -221,7 +221,7 @@ def _optional_annotations(self) -> dict[T.Literal["mesh", "mask"], bool]:
def _reload_callback(self) -> None:
""" If a frame has changed, triggering the variable, then update the active frame. Return
having done nothing if the variable is resetting. """
if self._globals.tk_update_active_viewport.get():
if self._globals.var_update_active_viewport.get():
self.reload_annotations()

def reload_annotations(self) -> None:
Expand Down Expand Up @@ -249,7 +249,7 @@ def reload_annotations(self) -> None:

self._update_face()
self._canvas.tag_raise("active_highlighter")
self._globals.tk_update_active_viewport.set(False)
self._globals.var_update_active_viewport.set(False)
self._last_execution["frame_index"] = self.frame_index

def _clear_previous(self) -> None:
Expand Down
Loading

0 comments on commit b6ac7b8

Please sign in to comment.