Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .gitattributes

This file was deleted.

12 changes: 6 additions & 6 deletions .github/workflows/cpu-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,17 @@ jobs:
runs-on: ${{ matrix.os }}

steps:
- name: Checkout Repository
- name: Checkout repository
uses: actions/checkout@v4
with:
lfs: true

- name: Install Prerequisites for Linux
- name: Install prerequisites for Linux
if: ${{ matrix.os == 'ubuntu-latest' || matrix.os == 'ubuntu-24.04-arm' }}
run:
sudo apt update && sudo apt install -y libturbojpeg exiftool ffmpeg libheif-dev poppler-utils

- name: Install Prerequisites for MacOS
- name: Install prerequisites for MacOS
if: ${{ matrix.os == 'macos-13' || matrix.os == 'macos-latest' }}
run:
brew install libjpeg exiftool ffmpeg libheif poppler
Expand All @@ -40,18 +40,18 @@ jobs:
python-version: ${{ matrix.python-version }}
check-latest: true

- name: Install Dependencies
- name: Install dependencies
run: |
python -m pip install -U pip wheel "numpy>=2" "cython>=3.0.12" "setuptools>=69"
python setup.py build_ext --inplace
python -m pip install .

- name: Lint with Pylint
- name: Lint with pylint
run: |
python -m pip install pylint
python -m pylint pyface --rcfile=.github/workflows/.pylintrc

- name: Run Tests with Pytest
- name: Run tests with pytest
run: |
mkdir -p tests/coverage
python -m pip install pytest pytest-cov typeguard
Expand Down
48 changes: 44 additions & 4 deletions .github/workflows/cuda-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,55 @@ on:
- "doc/**"
- "**.md"

env:
LOCAL_REGISTRY: localhost:5000
DOCKERFILE: docker/pr.dockerfile

jobs:
get_runner_and_uid:
name: Get Runner
runs-on: [self-hosted, unicorn]
steps:
- name: Get UID and GID
id: uid_gid
run: |
echo "uid_gid=$(id -u):$(id -g)" >> "$GITHUB_OUTPUT"
outputs:
runner: ${{ runner.name }}
uid: ${{ steps.uid_gid.outputs.uid_gid }}

build_docker_image:
name: Build Docker Image
needs: [get_runner_and_uid]
runs-on: ${{ needs.get_runner_and_uid.outputs.runner }}

steps:
- name: Checkout Repository
uses: actions/checkout@v4

- name: Build Docker Image
id: docker_build
uses: docker/build-push-action@v3
with:
file: ${{ env.DOCKERFILE }}
no-cache: false
push: true
tags: ${{ env.LOCAL_REGISTRY }}/pyface-container:ci

outputs:
image: ${{ env.LOCAL_REGISTRY }}/pyface-container:ci

ci:
name: CI
needs: [get_runner_and_uid, build_docker_image]
runs-on: ${{ needs.get_runner_and_uid.outputs.runner }}
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.12"]

runs-on: ["self-hosted", "unicorn"]
python-version:
- "3.10"
container:
image: ${{ needs.build_docker_image.outputs.image }}
options: --user ${{ needs.get_runner_and_uid.outputs.uid }} --gpus all

steps:
- name: Checkout repository
Expand Down
6 changes: 3 additions & 3 deletions demo/face_depth.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@

import pyface.components as pf

cur_folder = cb.get_curdir(__file__)
RESOURCE_DIR = cb.get_curdir(__file__).parent / "tests" / "resources"


def main(img_path: str = str(cur_folder / "data" / "EmmaWatson1.jpg")):
def main(img_path: str = str(RESOURCE_DIR / "EmmaWatson1.jpg")):
face_detect = pf.build_face_detection()
face_depth = pf.build_face_depth()

Expand All @@ -16,7 +16,7 @@ def main(img_path: str = str(cur_folder / "data" / "EmmaWatson1.jpg")):
results = face_depth([img], [face_box], return_depth=True)
plotted = face_depth.draw_results(img, [face_box], results)

out_folder = cur_folder / "output"
out_folder = cb.get_curdir(__file__) / "output"
out_folder.mkdir(exist_ok=True, parents=True)
cb.imwrite(plotted, out_folder / "face_depth1.png")
cb.imwrite(results[0]["depth_img"], out_folder / "face_depth2.png")
Expand Down
6 changes: 3 additions & 3 deletions demo/face_detection.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@

import pyface.components as pf

cur_folder = cb.get_curdir(__file__)
RESOURCE_DIR = cb.get_curdir(__file__).parent / "tests" / "resources"


def main(
img_path: str = str(cur_folder / "data" / "EmmaWatson1.jpg"),
img_path: str = str(RESOURCE_DIR / "EmmaWatson1.jpg"),
score_th: float = 0.5,
inp_size: Tuple[int, int] = (480, 640),
):
Expand All @@ -22,7 +22,7 @@ def main(
proposals_list = face_detection([img] * 7)
plotted = face_detection.draw_proposals(img, proposals_list[0])

out_folder = cur_folder / "output"
out_folder = cb.get_curdir(__file__) / "output"
out_folder.mkdir(exist_ok=True, parents=True)
cb.imwrite(plotted, out_folder / "face_detection.png")

Expand Down
6 changes: 3 additions & 3 deletions demo/face_gender.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@

import pyface.components as pf

cur_folder = cb.get_curdir(__file__)
RESOURCE_DIR = cb.get_curdir(__file__).parent / "tests" / "resources"


def main(img_path: str = str(cur_folder / "data" / "EmmaWatson1.jpg")):
def main(img_path: str = str(RESOURCE_DIR / "EmmaWatson1.jpg")):
face_detect = pf.build_face_detection()
face_gender = pf.build_gender_detection()

Expand All @@ -16,7 +16,7 @@ def main(img_path: str = str(cur_folder / "data" / "EmmaWatson1.jpg")):
results = face_gender([img], [face_box])
plotted = face_gender.draw_results(img, [face_box], results)

out_folder = cur_folder / "output"
out_folder = cb.get_curdir(__file__) / "output"
out_folder.mkdir(exist_ok=True, parents=True)
cb.imwrite(plotted, out_folder / "face_gender.png")

Expand Down
6 changes: 3 additions & 3 deletions demo/face_landmark.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@

import pyface as pf

cur_folder = cb.get_curdir(__file__)
RESOURCE_DIR = cb.get_curdir(__file__).parent / "tests" / "resources"


def main(
img_path: str = str(cur_folder / "data" / "EmmaWatson1.jpg"),
img_path: str = str(RESOURCE_DIR / "EmmaWatson1.jpg"),
score_th: float = 0.5,
inp_size: Tuple[int, int] = (480, 640),
):
Expand All @@ -25,7 +25,7 @@ def main(
result = face_landmark([img], [box])[0]
plotted = face_landmark.draw_result(img, box, result, plot_details=True)

out_folder = cur_folder / "output"
out_folder = cb.get_curdir(__file__) / "output"
out_folder.mkdir(exist_ok=True, parents=True)
cb.imwrite(plotted, out_folder / "face_landmark.jpg")

Expand Down
14 changes: 5 additions & 9 deletions demo/face_normalization.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@

import pyface.components as pf

cur_folder = cb.get_curdir(__file__)
RESOURCE_DIR = cb.get_curdir(__file__).parent / "tests" / "resources"


def main(
img_path: str = str(cur_folder / "data" / "EmmaWatson1.jpg"),
img_path: str = str(RESOURCE_DIR / "EmmaWatson1.jpg"),
scale: float = 1,
dst_size: Tuple[int, int] = (224, 224),
):
face_detection = pf.SCRFD()
face_detection = pf.build_face_detection()
face_normaliation = pf.FaceNormalize(
dst_size=dst_size,
interpolation=cb.INTER.BILINEAR,
Expand All @@ -22,13 +22,9 @@ def main(
img = cb.imread(img_path)
proposals = face_detection([img])[0]
norm_img = face_normaliation([img], [cb.Keypoints(proposals["lmk5pts"][0])])[0]
norm_img = cb.draw_keypoints(
norm_img,
face_normaliation.destination_pts,
scale=1,
)
norm_img = cb.draw_keypoints(norm_img, face_normaliation.destination_pts, scale=1)

out_folder = cur_folder / "output"
out_folder = cb.get_curdir(__file__) / "output"
out_folder.mkdir(exist_ok=True, parents=True)
cb.imwrite(norm_img, out_folder / f"face_normalize_s={scale}.png")

Expand Down
8 changes: 4 additions & 4 deletions demo/face_recognition.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@

import pyface as pf

cur_folder = cb.get_curdir(__file__)
RESOURCE_DIR = cb.get_curdir(__file__).parent / "tests" / "resources"


def main(
src_path: str = str(cur_folder / "data" / "EmmaWatson1.jpg"),
tgt_path: str = str(cur_folder / "data" / "face_bank" / "EmmaWatson.jpg"),
src_path: str = str(RESOURCE_DIR / "EmmaWatson1.jpg"),
tgt_path: str = str(RESOURCE_DIR / "face_bank" / "EmmaWatson.jpg"),
):
face_detection = pf.build_face_detection()
face_recognition = pf.build_face_recognition()
Expand All @@ -17,7 +17,7 @@ def main(
tgt_img = cb.imread(tgt_path)
lmks = [p["lmk5pts"][0] for p in face_detection([src_img, tgt_img])]

out_folder = cur_folder / "output"
out_folder = cb.get_curdir(__file__) / "output"
out_folder.mkdir(exist_ok=True, parents=True)
results = face_recognition([src_img, tgt_img], lmks)
src_emb = results[0]["embeddings"]
Expand Down
10 changes: 6 additions & 4 deletions demo/face_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@

import pyface as pf

cur_folder = cb.get_curdir(__file__)
RESOURCE_DIR = cb.get_curdir(__file__).parent / "tests" / "resources"


def main(
img_path: str = str(cur_folder / "data" / "EmmaWatson1.jpg"),
face_bank: str = str(cur_folder / "data" / "face_bank"),
img_path: str = str(RESOURCE_DIR / "EmmaWatson1.jpg"),
face_bank: str = str(RESOURCE_DIR / "face_bank"),
):
face_service = pf.FaceService(
enable_gender=True,
Expand All @@ -19,7 +19,9 @@ def main(
)
img = cb.imread(img_path)
faces_on_img = face_service([img], do_1n=True)[0]
cb.imwrite(faces_on_img.gen_info_img(), str(cur_folder / "output" / "face_service.jpg"))
out_folder = cb.get_curdir(__file__) / "output"
out_folder.mkdir(exist_ok=True, parents=True)
cb.imwrite(faces_on_img.gen_info_img(), str(out_folder / "face_service.jpg"))


if __name__ == "__main__":
Expand Down
2 changes: 1 addition & 1 deletion docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# syntax=docker/dockerfile:experimental
FROM nvidia/cuda:12.8.1-cudnn-runtime-ubuntu22.04 as builder
FROM nvidia/cuda:12.8.1-cudnn-runtime-ubuntu24.04 as builder

ENV PYTHONDONTWRITEBYTECODE=1 \
DEBIAN_FRONTEND=noninteractive \
Expand Down
17 changes: 17 additions & 0 deletions docker/pr.dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# syntax=docker/dockerfile:experimental
FROM nvidia/cuda:12.8.1-cudnn-runtime-ubuntu24.04 as builder

ENV PYTHONDONTWRITEBYTECODE=1 \
DEBIAN_FRONTEND=noninteractive \
TZ=Asia/Taipei

RUN apt-get update -y && apt-get upgrade -y && \
apt-get install -y --no-install-recommends \
tzdata wget git git-lfs libturbojpeg exiftool ffmpeg poppler-utils libpng-dev \
libtiff5-dev libjpeg8-dev libopenjp2-7-dev zlib1g-dev gcc \
libfreetype6-dev liblcms2-dev libwebp-dev tcl8.6-dev tk8.6-dev python3-tk \
python3-pip libharfbuzz-dev libfribidi-dev libxcb1-dev libfftw3-dev gosu \
libpq-dev python3-dev build-essential && \
ln -sf /usr/share/zoneinfo/$TZ /etc/localtime && \
dpkg-reconfigure -f noninteractive tzdata && \
apt-get clean && rm -rf /var/lib/apt/lists/*
33 changes: 15 additions & 18 deletions pyface/components/face_depth/tddfav2.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from ..enums import FacePose
from ..utils import append_to_batch, detach_from_batch, download_model_and_return_model_fpath
from .Sim3DR import sim3dr_cython
from .Sim3DR import sim3dr_cython # pylint: disable=E0611


def rasterize(
Expand Down Expand Up @@ -168,9 +168,7 @@ def _prepare_bfm_npz(
return u_base, w_shp_base, w_exp_base


def _parse_tffda_param(
param: np.ndarray,
) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
def _parse_tffda_param(param: np.ndarray) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
"""
matrix pose form
param: shape = (trans_dim + shape_dim + exp_dim) i.e., 62 = 12 + 40 + 10
Expand Down Expand Up @@ -261,16 +259,12 @@ def initialize(self) -> None:
dummy_inputs = {k: np.zeros(input_dims[k], dtype=v["dtype"]) for k, v in self.bfm_engine.input_infos.items()}
self.bfm_engine(**dummy_inputs)

def _transform(
self,
img: np.ndarray,
box: cb.Box,
) -> Tuple[np.ndarray, Tuple[float, float], np.ndarray]:
def _transform(self, img: np.ndarray, box: cb.Box) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
inp_w, inp_h = self.engine.input_infos["input"]["shape"][2:]

expanded_box = box.square().scale(fx=0.98, fy=0.98)
scale = np.array([expanded_box.width / inp_w, expanded_box.height / inp_h])
shift = expanded_box.left_top
scale = expanded_box.width / inp_w, expanded_box.height / inp_h
blob = cb.imcropbox(img, expanded_box)
blob = cb.imresize(blob, (inp_w, inp_h))
blob = blob.transpose(2, 0, 1)[None]
Expand All @@ -280,7 +274,7 @@ def preprocess(
self,
imgs: List[np.ndarray],
boxes: List[cb.Box],
):
) -> Tuple[List[np.ndarray], List[np.ndarray], List[np.ndarray]]:
if len(imgs) != len(boxes):
raise ValueError(f"imgs and boxes should have same length, but got {len(imgs)} and {len(boxes)}")

Expand All @@ -294,11 +288,7 @@ def preprocess(
shifts.append(shift)
return blobs, scales, shifts

def _reconstruct_vertices(
self,
param: np.ndarray,
dense_flag: bool = False,
) -> np.ndarray:
def _reconstruct_vertices(self, param: np.ndarray, dense_flag: bool = False) -> np.ndarray:
def _similar_transform(pts3d, scales):
x_s, y_s = scales[..., 1], scales[..., 0]
z_s = (x_s + y_s) / 2
Expand Down Expand Up @@ -374,15 +364,22 @@ def __call__(
blobs, scales, shifts = self.preprocess(imgs, boxes)
preds = {k: [] for k in self.engine.output_infos.keys()}
for batch in cb.make_batch(blobs, self.batch_size):
current_batch_size = len(batch)
inputs = {
name: np.concatenate(append_to_batch(batch, self.batch_size)).astype(v["dtype"]) # E1123
for name, v in self.engine.input_infos.items()
}
tmp_preds = self.engine(**inputs)
for k, v in tmp_preds.items():
preds[k].append(detach_from_batch(v, len(batch)))
preds[k].append(detach_from_batch(v, current_batch_size))
preds = {k: np.concatenate(v, 0) for k, v in preds.items()}
params = np.concatenate((preds["params"], scales, shifts), -1)
scales = np.stack(scales, 0) # n x 2
shifts = np.stack(shifts, 0) # n x 2
# Ensure shapes are compatible for concatenation
n = preds["params"].shape[0]
if scales.shape[0] != n or shifts.shape[0] != n:
raise ValueError(f"Shape mismatch: preds['params'] has {n} rows, scales has {scales.shape[0]}, shifts has {shifts.shape[0]}")
params = np.concatenate((preds["params"], scales, shifts), axis=-1)
lmk3d68pts = self._gen_3d_landmarks(params)
pose_degrees = self._get_pose_degrees(params)

Expand Down
Loading
Loading