Added visualisation for face alignment setting

This commit is contained in:
Arnaud_Cayrol 2026-02-08 12:42:51 +01:00
parent 5cbec95e74
commit b780dbad3e
4 changed files with 148 additions and 57 deletions

View file

@ -1,43 +0,0 @@
# name: Build and Publish image to Docker Hub
# on:
# push:
# branches:
# - master
# tags:
# - 'v*'
# jobs:
# publish_images:
# runs-on: ubuntu-latest
# steps:
# - name: checkout
# uses: actions/checkout@v4
# - name: Extract metadata (tags, labels) for Docker
# id: meta
# uses: docker/metadata-action@v5
# with:
# images: arnaudcayrol/immich-selfie-timelapse
# tags: |
# type=ref,event=branch
# type=ref,event=tag
# type=semver,pattern={{version}}
# type=semver,pattern={{major}}.{{minor}}
# type=raw,value=latest,enable={{is_default_branch}}
# - name: Set up Docker Buildx
# uses: docker/setup-buildx-action@v3
# - name: Login to Docker Hub
# uses: docker/login-action@v3
# with:
# username: arnaudcayrol
# password: ${{ secrets.DOCKERHUB_TOKEN }}
# - name: Build and push
# uses: docker/build-push-action@v5
# with:
# context: .
# push: true
# tags: ${{ steps.meta.outputs.tags }}
# labels: ${{ steps.meta.outputs.labels }}

View file

@ -3,6 +3,7 @@
import { DEFAULT_CONFIG, TIMING } from '../constants.js';
import { handleError } from '../errorHandler.js';
import EyeIndicator from './visual/EyeIndicator.svelte';
import AlignmentIndicator from './visual/AlignmentIndicator.svelte';
let { disabled = false } = $props();
@ -314,17 +315,12 @@
<!-- Alignment Section -->
<div class="setting-section">
<div class="section-header">
<span class="section-title">Face Alignment</span>
<span class="setting-hint" style="font-style: italic; color: var(--text-secondary);">Always enabled</span>
</div>
<div class="setting-row sub-setting">
<label for="eye-distance">
<span class="setting-label">Eye Distance</span>
<span class="setting-hint">Distance between eyes as % of image width (larger = zoom in)</span>
</label>
<div class="setting-control">
<div class="setting-control-with-visual">
<input
id="eye-distance"
type="range"
@ -334,6 +330,9 @@
step="0.01"
/>
<span class="value">{(config.processing.alignment.eye_distance * 100).toFixed(0)}%</span>
<div class="inline-visual-indicator">
<AlignmentIndicator eyeDistance={config.processing.alignment.eye_distance} />
</div>
</div>
</div>
</div>
@ -740,11 +739,11 @@
display: flex;
align-items: center;
justify-content: center;
padding: 0.25rem 0.5rem;
padding: 0.5rem;
background: #0f0f0f;
border: 1px solid #333;
border-radius: 6px;
min-width: 60px;
height: 32px;
width: 96px;
height: 96px;
}
</style>

View file

@ -0,0 +1,137 @@
<script>
/**
* Visual indicator for alignment settings showing head position and scale.
* Displays an anatomically proportioned face with eyes positioned according to eye_distance.
*
* The eye_distance parameter controls zoom level:
* - Smaller values (0.1): zoomed out, head appears smaller, shows more neck
* - Larger values (0.5): zoomed in, head appears larger, tighter crop
*
* Proportions match the processing pipeline (alignment.rs):
* - Eye positions calculated with FACE_CENTER_BELOW_EYES = 0.5
* - Face center (hairline-to-chin midpoint + neck) positioned at frame center
* - This creates a vertical offset showing more neck than forehead
* - Inter-pupillary distance (IPD) is ~37% of face width
*/
let { eyeDistance = 0.3 } = $props();
// Frame dimensions (square output image)
const frameSize = 120;
// Calculate eye positions using the same formulas as alignment.rs
const FACE_CENTER_BELOW_EYES = 0.5;
const leftEyeX = $derived((1.0 - eyeDistance) / 2.0);
const rightEyeX = $derived(1.0 - leftEyeX);
const eyeY = $derived(0.5 - eyeDistance * FACE_CENTER_BELOW_EYES);
// Convert to pixel coordinates
const leftEyePos = $derived({ x: leftEyeX * frameSize, y: eyeY * frameSize });
const rightEyePos = $derived({ x: rightEyeX * frameSize, y: eyeY * frameSize });
// Calculate eye distance in pixels
const eyeDistancePx = $derived(eyeDistance * frameSize);
// Calculate eye center point
const eyeCenterX = $derived((leftEyePos.x + rightEyePos.x) / 2);
const eyeCenterY = $derived(eyeY * frameSize);
// Face proportions based on inter-pupillary distance (IPD)
// Per crop_utils.rs, IPD is ~0.37 of face width
// Face center (hairline-to-chin midpoint + neck) is 0.5 * IPD below eyes
// Anatomical proportions relative to IPD:
// - Eyes to top of head (forehead): ~0.5 IPD
// - Eyes to face center: 0.5 IPD (by definition)
// - Face center to bottom (lower face + neck): ~0.5 IPD
// Total head height: ~1.5 IPD
const foreheadHeight = $derived(eyeDistancePx * 0.5);
const lowerFaceHeight = $derived(eyeDistancePx * 1.0);
// Head ellipse dimensions (head is taller than wide)
const headCenter = $derived({
x: eyeCenterX,
y: eyeCenterY + (lowerFaceHeight - foreheadHeight) / 2
});
const headWidth = $derived(eyeDistancePx * 1.35); // Width relative to IPD
const headHeight = $derived(eyeDistancePx * 1.85); // Height > width (ratio ~1.37)
// Eye rendering parameters (scale with IPD)
const eyeRadius = $derived(Math.max(2.5, eyeDistancePx * 0.12));
</script>
<svg
viewBox="0 0 {frameSize} {frameSize}"
class="alignment-indicator"
role="img"
aria-label="Face alignment indicator"
>
<!-- Background frame -->
<rect
x="0"
y="0"
width={frameSize}
height={frameSize}
fill="rgba(0, 0, 0, 0.8)"
stroke="none"
/>
<!-- Head ellipse (face shape) -->
<ellipse
cx={headCenter.x}
cy={headCenter.y}
rx={headWidth}
ry={headHeight}
fill="none"
stroke="white"
stroke-width="2"
class="head"
/>
<!-- Left eye -->
<circle
cx={leftEyePos.x}
cy={leftEyePos.y}
r={eyeRadius}
fill="none"
stroke="white"
stroke-width="2"
class="eye"
/>
<!-- Right eye -->
<circle
cx={rightEyePos.x}
cy={rightEyePos.y}
r={eyeRadius}
fill="none"
stroke="white"
stroke-width="2"
class="eye"
/>
<!-- Simple mouth curve (positioned between eyes and chin) -->
<path
d="M {eyeCenterX - eyeDistancePx * 0.35} {eyeCenterY + eyeDistancePx * 0.65}
Q {eyeCenterX} {eyeCenterY + eyeDistancePx * 0.75},
{eyeCenterX + eyeDistancePx * 0.35} {eyeCenterY + eyeDistancePx * 0.65}"
fill="none"
stroke="white"
stroke-width="2"
stroke-linecap="round"
class="mouth"
/>
</svg>
<style>
.alignment-indicator {
width: 80px;
height: 80px;
}
.head,
.eye,
.mouth {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
</style>

View file

@ -19,9 +19,7 @@
// Calculate eye height based on openness
const eyeHeight = $derived(minEyeHeight + normalizedOpenness * (maxEyeHeight - minEyeHeight));
const irisRadius = 12;
const pupilRadius = 3;
const pupilRadius = 2;
// Create almond-shaped eye outline using a path
// The path creates a pointed eye shape (yes, this is a Skyrim reference.)
@ -60,7 +58,7 @@
<circle
cx={centerX}
cy={centerY}
r={Math.min(irisRadius, eyeHeight / 2 - 3)}
r={eyeHeight / 2}
fill="none"
stroke="white"
stroke-width="2"
@ -71,7 +69,7 @@
<circle
cx={centerX}
cy={centerY}
r={Math.min(pupilRadius, eyeHeight / 2 - 8)}
r={Math.max(0, Math.min(pupilRadius, eyeHeight / 2 - 8))}
fill="white"
class="pupil"
/>