dreamlessx commited on
Commit
81d5fa1
·
verified ·
1 Parent(s): 0d32769

Upload landmarkdiff/manipulation.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. landmarkdiff/manipulation.py +63 -2
landmarkdiff/manipulation.py CHANGED
@@ -95,15 +95,39 @@ def apply_procedure_preset(
95
  intensity: float = 50.0,
96
  image_size: int = 512,
97
  clinical_flags: Optional["ClinicalFlags"] = None,
 
 
98
  ) -> FaceLandmarks:
99
- """Apply a named procedure preset at given intensity (0-100)."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
  if procedure not in PROCEDURE_LANDMARKS:
101
  raise ValueError(f"Unknown procedure: {procedure}. Choose from {list(PROCEDURE_LANDMARKS)}")
102
 
103
  landmarks = face.landmarks.copy()
 
 
 
 
 
 
 
 
104
  indices = PROCEDURE_LANDMARKS[procedure]
105
  radius = PROCEDURE_RADIUS[procedure]
106
- scale = intensity / 100.0
107
 
108
  # Ehlers-Danlos: wider influence radii for hypermobile tissue
109
  if clinical_flags and clinical_flags.ehlers_danlos:
@@ -143,6 +167,43 @@ def apply_procedure_preset(
143
  )
144
 
145
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
146
  def _get_procedure_handles(
147
  procedure: str,
148
  indices: list[int],
 
95
  intensity: float = 50.0,
96
  image_size: int = 512,
97
  clinical_flags: Optional["ClinicalFlags"] = None,
98
+ displacement_model_path: Optional[str] = None,
99
+ noise_scale: float = 0.0,
100
  ) -> FaceLandmarks:
101
+ """Apply a surgical procedure preset to landmarks.
102
+
103
+ Args:
104
+ face: Input face landmarks.
105
+ procedure: Procedure name (rhinoplasty, blepharoplasty, etc.).
106
+ intensity: Relative intensity 0-100 (mild=33, moderate=66, aggressive=100).
107
+ image_size: Reference image size for displacement scaling.
108
+ clinical_flags: Optional clinical condition flags.
109
+ displacement_model_path: Path to a fitted DisplacementModel (.npz).
110
+ When provided, uses data-driven displacements from real surgery pairs
111
+ instead of hand-tuned RBF vectors.
112
+ noise_scale: Variation noise scale for data-driven mode (0=deterministic).
113
+
114
+ Returns:
115
+ New FaceLandmarks with manipulated landmarks.
116
+ """
117
  if procedure not in PROCEDURE_LANDMARKS:
118
  raise ValueError(f"Unknown procedure: {procedure}. Choose from {list(PROCEDURE_LANDMARKS)}")
119
 
120
  landmarks = face.landmarks.copy()
121
+ scale = intensity / 100.0
122
+
123
+ # Data-driven displacement mode
124
+ if displacement_model_path is not None:
125
+ return _apply_data_driven(
126
+ face, procedure, scale, displacement_model_path, noise_scale,
127
+ )
128
+
129
  indices = PROCEDURE_LANDMARKS[procedure]
130
  radius = PROCEDURE_RADIUS[procedure]
 
131
 
132
  # Ehlers-Danlos: wider influence radii for hypermobile tissue
133
  if clinical_flags and clinical_flags.ehlers_danlos:
 
167
  )
168
 
169
 
170
+ def _apply_data_driven(
171
+ face: FaceLandmarks,
172
+ procedure: str,
173
+ scale: float,
174
+ model_path: str,
175
+ noise_scale: float = 0.0,
176
+ ) -> FaceLandmarks:
177
+ """Apply data-driven displacements from a fitted DisplacementModel.
178
+
179
+ The model provides mean displacement vectors learned from real surgery pairs,
180
+ applied directly to all 478 landmarks (not just procedure-specific subset).
181
+ """
182
+ from landmarkdiff.displacement_model import DisplacementModel
183
+
184
+ model = DisplacementModel.load(model_path)
185
+ field = model.get_displacement_field(
186
+ procedure=procedure,
187
+ intensity=scale,
188
+ noise_scale=noise_scale,
189
+ )
190
+
191
+ # field is (478, 2) in normalized coordinates
192
+ landmarks = face.landmarks.copy()
193
+ n_lm = min(landmarks.shape[0], field.shape[0])
194
+ landmarks[:n_lm, :2] += field[:n_lm]
195
+
196
+ # Clamp to [0, 1]
197
+ landmarks = np.clip(landmarks, 0.0, 1.0)
198
+
199
+ return FaceLandmarks(
200
+ landmarks=landmarks,
201
+ image_width=face.image_width,
202
+ image_height=face.image_height,
203
+ confidence=face.confidence,
204
+ )
205
+
206
+
207
  def _get_procedure_handles(
208
  procedure: str,
209
  indices: list[int],