deadhand3d commited on
Commit
3809460
·
verified ·
1 Parent(s): 1b446bf

make a tool where i can upload all images of woman body anatomy photos of different angles lenses and perspectives and crops and have a auto breast aligner that scales and aligns the breast scale for 3d reference

Browse files
Files changed (2) hide show
  1. README.md +7 -4
  2. index.html +472 -18
README.md CHANGED
@@ -1,10 +1,13 @@
1
  ---
2
- title: Boobalign Pro
3
- emoji: 🏃
4
- colorFrom: indigo
5
  colorTo: red
 
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
1
  ---
2
+ title: BoobAlign Pro 🎯
3
+ colorFrom: gray
 
4
  colorTo: red
5
+ emoji: 🐳
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite-v3
10
  ---
11
 
12
+ # Welcome to your new DeepSite project!
13
+ This project was created with [DeepSite](https://deepsite.hf.co).
index.html CHANGED
@@ -1,19 +1,473 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  </html>
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>BoobAlign Pro - 3D Breast Reference Tool</title>
7
+ <link rel="icon" type="image/x-icon" href="/static/favicon.ico">
8
+ <script src="https://cdn.tailwindcss.com"></script>
9
+ <script src="https://unpkg.com/feather-icons"></script>
10
+ <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
11
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/5.3.1/fabric.min.js"></script>
12
+ <style>
13
+ .dropzone {
14
+ border: 3px dashed #9CA3AF;
15
+ transition: all 0.3s;
16
+ }
17
+ .dropzone-active {
18
+ border-color: #6366F1;
19
+ background-color: #EEF2FF;
20
+ }
21
+ #canvas-container {
22
+ position: relative;
23
+ }
24
+ .landmark {
25
+ position: absolute;
26
+ width: 12px;
27
+ height: 12px;
28
+ background: #EC4899;
29
+ border-radius: 50%;
30
+ transform: translate(-50%, -50%);
31
+ cursor: move;
32
+ z-index: 10;
33
+ }
34
+ .tools-panel {
35
+ transition: all 0.3s ease;
36
+ }
37
+ </style>
38
+ </head>
39
+ <body class="bg-gray-50 min-h-screen">
40
+ <div class="container mx-auto px-4 py-8">
41
+ <header class="mb-10 text-center">
42
+ <h1 class="text-4xl font-bold text-gray-800 mb-2">BoobAlign Pro</h1>
43
+ <p class="text-lg text-gray-600">Precision breast alignment for 3D modeling reference</p>
44
+ </header>
45
+
46
+ <div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
47
+ <!-- Upload Panel -->
48
+ <div class="lg:col-span-1 bg-white rounded-xl shadow-lg overflow-hidden">
49
+ <div class="p-6 border-b border-gray-200">
50
+ <h2 class="text-xl font-semibold text-gray-800">Upload Images</h2>
51
+ </div>
52
+ <div class="p-6">
53
+ <div id="dropzone" class="dropzone rounded-lg p-8 text-center cursor-pointer mb-6">
54
+ <i data-feather="upload-cloud" class="w-12 h-12 mx-auto text-gray-400 mb-3"></i>
55
+ <p class="text-gray-600 mb-1">Drag & drop images here</p>
56
+ <p class="text-sm text-gray-500">or click to browse</p>
57
+ <input type="file" id="file-input" class="hidden" multiple accept="image/*">
58
+ </div>
59
+ <button id="process-btn" class="w-full bg-indigo-600 hover:bg-indigo-700 text-white font-medium py-3 px-4 rounded-lg transition disabled:opacity-50" disabled>
60
+ <span class="flex items-center justify-center">
61
+ <i data-feather="cpu" class="w-5 h-5 mr-2"></i>
62
+ Process Images
63
+ </span>
64
+ </button>
65
+ </div>
66
+ </div>
67
+
68
+ <!-- Main Canvas -->
69
+ <div class="lg:col-span-2">
70
+ <div id="canvas-container" class="bg-white rounded-xl shadow-lg overflow-hidden">
71
+ <div class="p-4 border-b border-gray-200 flex justify-between items-center">
72
+ <h2 class="text-xl font-semibold text-gray-800">Alignment Canvas</h2>
73
+ <div class="flex space-x-2">
74
+ <button id="zoom-in" class="p-2 rounded hover:bg-gray-100">
75
+ <i data-feather="zoom-in"></i>
76
+ </button>
77
+ <button id="zoom-out" class="p-2 rounded hover:bg-gray-100">
78
+ <i data-feather="zoom-out"></i>
79
+ </button>
80
+ <button id="reset-view" class="p-2 rounded hover:bg-gray-100">
81
+ <i data-feather="refresh-cw"></i>
82
+ </button>
83
+ </div>
84
+ </div>
85
+ <div class="relative" style="height: 500px;">
86
+ <canvas id="main-canvas" class="absolute inset-0 w-full h-full"></canvas>
87
+ <div id="landmark-container" class="absolute inset-0 pointer-events-none"></div>
88
+ </div>
89
+ </div>
90
+ </div>
91
+ </div>
92
+
93
+ <!-- Tools Panel (hidden by default) -->
94
+ <div id="tools-panel" class="mt-8 bg-white rounded-xl shadow-lg overflow-hidden hidden">
95
+ <div class="p-6 border-b border-gray-200">
96
+ <h2 class="text-xl font-semibold text-gray-800">Alignment Tools</h2>
97
+ </div>
98
+ <div class="p-6 grid grid-cols-1 md:grid-cols-3 gap-6">
99
+ <div>
100
+ <h3 class="font-medium text-gray-700 mb-3">Landmarks</h3>
101
+ <div class="space-y-2">
102
+ <button id="add-nipple" class="w-full flex items-center justify-between px-4 py-2 bg-gray-100 hover:bg-gray-200 rounded-lg">
103
+ <span>Add Nipple Point</span>
104
+ <i data-feather="circle" class="w-4 h-4 text-pink-500"></i>
105
+ </button>
106
+ <button id="add-underbust" class="w-full flex items-center justify-between px-4 py-2 bg-gray-100 hover:bg-gray-200 rounded-lg">
107
+ <span>Add Underbust Point</span>
108
+ <i data-feather="circle" class="w-4 h-4 text-purple-500"></i>
109
+ </button>
110
+ <button id="add-side" class="w-full flex items-center justify-between px-4 py-2 bg-gray-100 hover:bg-gray-200 rounded-lg">
111
+ <span>Add Side Point</span>
112
+ <i data-feather="circle" class="w-4 h-4 text-blue-500"></i>
113
+ </button>
114
+ </div>
115
+ </div>
116
+
117
+ <div>
118
+ <h3 class="font-medium text-gray-700 mb-3">Alignment</h3>
119
+ <div class="space-y-2">
120
+ <button id="auto-align" class="w-full flex items-center justify-between px-4 py-2 bg-indigo-100 hover:bg-indigo-200 rounded-lg">
121
+ <span>Auto-Align Breasts</span>
122
+ <i data-feather="move" class="w-4 h-4 text-indigo-600"></i>
123
+ </button>
124
+ <button id="symmetry-check" class="w-full flex items-center justify-between px-4 py-2 bg-indigo-100 hover:bg-indigo-200 rounded-lg">
125
+ <span>Check Symmetry</span>
126
+ <i data-feather="mirror" class="w-4 h-4 text-indigo-600"></i>
127
+ </button>
128
+ </div>
129
+ </div>
130
+
131
+ <div>
132
+ <h3 class="font-medium text-gray-700 mb-3">Export</h3>
133
+ <div class="space-y-2">
134
+ <button id="export-json" class="w-full flex items-center justify-between px-4 py-2 bg-green-100 hover:bg-green-200 rounded-lg">
135
+ <span>Export as JSON</span>
136
+ <i data-feather="download" class="w-4 h-4 text-green-600"></i>
137
+ </button>
138
+ <button id="export-image" class="w-full flex items-center justify-between px-4 py-2 bg-green-100 hover:bg-green-200 rounded-lg">
139
+ <span>Export as Image</span>
140
+ <i data-feather="image" class="w-4 h-4 text-green-600"></i>
141
+ </button>
142
+ </div>
143
+ </div>
144
+ </div>
145
+ </div>
146
+
147
+ <!-- Gallery (hidden by default) -->
148
+ <div id="gallery" class="mt-8 hidden">
149
+ <div class="bg-white rounded-xl shadow-lg overflow-hidden">
150
+ <div class="p-6 border-b border-gray-200">
151
+ <h2 class="text-xl font-semibold text-gray-800">Image Gallery</h2>
152
+ </div>
153
+ <div class="p-6">
154
+ <div id="thumbnails" class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-4">
155
+ <!-- Thumbnails will be added here -->
156
+ </div>
157
+ </div>
158
+ </div>
159
+ </div>
160
+ </div>
161
+
162
+ <script>
163
+ document.addEventListener('DOMContentLoaded', function() {
164
+ feather.replace();
165
+
166
+ // Initialize Fabric.js canvas
167
+ const canvas = new fabric.Canvas('main-canvas', {
168
+ backgroundColor: '#f8fafc',
169
+ preserveObjectStacking: true
170
+ });
171
+
172
+ let currentImage = null;
173
+ let uploadedImages = [];
174
+ let landmarks = [];
175
+
176
+ // Dropzone functionality
177
+ const dropzone = document.getElementById('dropzone');
178
+ const fileInput = document.getElementById('file-input');
179
+
180
+ dropzone.addEventListener('click', () => fileInput.click());
181
+
182
+ dropzone.addEventListener('dragover', (e) => {
183
+ e.preventDefault();
184
+ dropzone.classList.add('dropzone-active');
185
+ });
186
+
187
+ dropzone.addEventListener('dragleave', () => {
188
+ dropzone.classList.remove('dropzone-active');
189
+ });
190
+
191
+ dropzone.addEventListener('drop', (e) => {
192
+ e.preventDefault();
193
+ dropzone.classList.remove('dropzone-active');
194
+ handleFiles(e.dataTransfer.files);
195
+ });
196
+
197
+ fileInput.addEventListener('change', () => {
198
+ if (fileInput.files.length > 0) {
199
+ handleFiles(fileInput.files);
200
+ }
201
+ });
202
+
203
+ function handleFiles(files) {
204
+ const processBtn = document.getElementById('process-btn');
205
+
206
+ for (let i = 0; i < files.length; i++) {
207
+ const file = files[i];
208
+ if (!file.type.match('image.*')) continue;
209
+
210
+ const reader = new FileReader();
211
+ reader.onload = function(e) {
212
+ uploadedImages.push({
213
+ name: file.name,
214
+ src: e.target.result
215
+ });
216
+
217
+ // Update UI
218
+ processBtn.disabled = false;
219
+ document.getElementById('gallery').classList.remove('hidden');
220
+
221
+ // Add thumbnail to gallery
222
+ const thumbnails = document.getElementById('thumbnails');
223
+ const thumbnail = document.createElement('div');
224
+ thumbnail.className = 'cursor-pointer group';
225
+ thumbnail.innerHTML = `
226
+ <div class="relative overflow-hidden rounded-lg aspect-square">
227
+ <img src="${e.target.result}" class="w-full h-full object-cover group-hover:opacity-75 transition">
228
+ <div class="absolute inset-0 bg-black bg-opacity-0 group-hover:bg-opacity-20 transition"></div>
229
+ </div>
230
+ <p class="text-sm text-gray-600 truncate mt-1">${file.name}</p>
231
+ `;
232
+ thumbnail.addEventListener('click', () => loadImageToCanvas(e.target.result, file.name));
233
+ thumbnails.appendChild(thumbnail);
234
+ };
235
+ reader.readAsDataURL(file);
236
+ }
237
+ }
238
+
239
+ function loadImageToCanvas(src, name) {
240
+ const img = new Image();
241
+ img.src = src;
242
+
243
+ img.onload = function() {
244
+ // Clear previous image and landmarks
245
+ canvas.clear();
246
+ document.getElementById('landmark-container').innerHTML = '';
247
+ landmarks = [];
248
+
249
+ // Add new image
250
+ const fabricImg = new fabric.Image(img, {
251
+ left: canvas.width / 2,
252
+ top: canvas.height / 2,
253
+ originX: 'center',
254
+ originY: 'center',
255
+ selectable: false
256
+ });
257
+
258
+ // Scale image to fit canvas
259
+ const scale = Math.min(
260
+ (canvas.width * 0.9) / img.width,
261
+ (canvas.height * 0.9) / img.height
262
+ );
263
+ fabricImg.scale(scale);
264
+
265
+ canvas.add(fabricImg);
266
+ currentImage = fabricImg;
267
+
268
+ // Show tools panel
269
+ document.getElementById('tools-panel').classList.remove('hidden');
270
+ };
271
+ }
272
+
273
+ // Add landmark functionality
274
+ document.getElementById('add-nipple').addEventListener('click', () => addLandmark('nipple', 'bg-pink-500'));
275
+ document.getElementById('add-underbust').addEventListener('click', () => addLandmark('underbust', 'bg-purple-500'));
276
+ document.getElementById('add-side').addEventListener('click', () => addLandmark('side', 'bg-blue-500'));
277
+
278
+ function addLandmark(type, colorClass) {
279
+ if (!currentImage) return;
280
+
281
+ // Center position (temporary, user will drag it)
282
+ const container = document.getElementById('landmark-container');
283
+ const landmark = document.createElement('div');
284
+ landmark.className = `landmark ${colorClass} cursor-move`;
285
+ landmark.dataset.type = type;
286
+ landmark.style.left = '50%';
287
+ landmark.style.top = '50%';
288
+
289
+ // Make draggable
290
+ let isDragging = false;
291
+ let offsetX, offsetY;
292
+
293
+ landmark.addEventListener('mousedown', function(e) {
294
+ isDragging = true;
295
+ offsetX = e.clientX - landmark.getBoundingClientRect().left;
296
+ offsetY = e.clientY - landmark.getBoundingClientRect().top;
297
+ e.preventDefault(); // Prevent text selection
298
+ });
299
+
300
+ document.addEventListener('mousemove', function(e) {
301
+ if (!isDragging) return;
302
+
303
+ const containerRect = container.getBoundingClientRect();
304
+ let x = e.clientX - containerRect.left - offsetX;
305
+ let y = e.clientY - containerRect.top - offsetY;
306
+
307
+ // Constrain to container
308
+ x = Math.max(0, Math.min(x, containerRect.width));
309
+ y = Math.max(0, Math.min(y, containerRect.height));
310
+
311
+ landmark.style.left = x + 'px';
312
+ landmark.style.top = y + 'px';
313
+ });
314
+
315
+ document.addEventListener('mouseup', function() {
316
+ isDragging = false;
317
+ updateLandmarkPositions();
318
+ });
319
+
320
+ container.appendChild(landmark);
321
+ updateLandmarkPositions();
322
+ }
323
+
324
+ function updateLandmarkPositions() {
325
+ const container = document.getElementById('landmark-container');
326
+ const containerRect = container.getBoundingClientRect();
327
+ const canvasRect = canvas.getSelectionElement().getBoundingClientRect();
328
+ const scaleX = canvas.width / canvasRect.width;
329
+ const scaleY = canvas.height / canvasRect.height;
330
+
331
+ landmarks = [];
332
+
333
+ Array.from(container.children).forEach(landmark => {
334
+ const rect = landmark.getBoundingClientRect();
335
+ const x = (rect.left + rect.width/2 - canvasRect.left) * scaleX;
336
+ const y = (rect.top + rect.height/2 - canvasRect.top) * scaleY;
337
+
338
+ landmarks.push({
339
+ type: landmark.dataset.type,
340
+ x: x,
341
+ y: y
342
+ });
343
+ });
344
+ }
345
+
346
+ // Process button functionality
347
+ document.getElementById('process-btn').addEventListener('click', function() {
348
+ if (uploadedImages.length === 0) return;
349
+
350
+ // For demo purposes, just load the first image
351
+ loadImageToCanvas(uploadedImages[0].src, uploadedImages[0].name);
352
+ });
353
+
354
+ // Zoom controls
355
+ document.getElementById('zoom-in').addEventListener('click', function() {
356
+ if (currentImage) {
357
+ currentImage.scaleX *= 1.1;
358
+ currentImage.scaleY *= 1.1;
359
+ canvas.renderAll();
360
+ }
361
+ });
362
+
363
+ document.getElementById('zoom-out').addEventListener('click', function() {
364
+ if (currentImage) {
365
+ currentImage.scaleX *= 0.9;
366
+ currentImage.scaleY *= 0.9;
367
+ canvas.renderAll();
368
+ }
369
+ });
370
+
371
+ document.getElementById('reset-view').addEventListener('click', function() {
372
+ if (currentImage) {
373
+ const img = currentImage.getElement();
374
+ const scale = Math.min(
375
+ (canvas.width * 0.9) / img.width,
376
+ (canvas.height * 0.9) / img.height
377
+ );
378
+ currentImage.scaleX = scale;
379
+ currentImage.scaleY = scale;
380
+ currentImage.left = canvas.width / 2;
381
+ currentImage.top = canvas.height / 2;
382
+ currentImage.setCoords();
383
+ canvas.renderAll();
384
+ }
385
+ });
386
+
387
+ // Auto-align functionality (simplified for demo)
388
+ document.getElementById('auto-align').addEventListener('click', function() {
389
+ if (landmarks.length < 2) return;
390
+
391
+ // Simple demo: align nipples horizontally
392
+ const nipples = landmarks.filter(l => l.type === 'nipple');
393
+ if (nipples.length >= 2) {
394
+ const avgY = nipples.reduce((sum, l) => sum + l.y, 0) / nipples.length;
395
+
396
+ nipples.forEach(nipple => {
397
+ const element = Array.from(document.getElementById('landmark-container').children)
398
+ .find(el => el.dataset.type === 'nipple' && Math.abs(parseFloat(el.style.left) - nipple.x) < 10);
399
+ if (element) {
400
+ const containerRect = document.getElementById('landmark-container').getBoundingClientRect();
401
+ const yPos = (avgY * (containerRect.height / canvas.height)) + 'px';
402
+ element.style.top = yPos;
403
+ }
404
+ });
405
+
406
+ updateLandmarkPositions();
407
+ alert('Breasts aligned horizontally based on nipple positions!');
408
+ }
409
+ });
410
+
411
+ // Export functionality
412
+ document.getElementById('export-json').addEventListener('click', function() {
413
+ const data = {
414
+ image: currentImage ? currentImage.getSrc() : null,
415
+ landmarks: landmarks,
416
+ timestamp: new Date().toISOString()
417
+ };
418
+
419
+ const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
420
+ const url = URL.createObjectURL(blob);
421
+
422
+ const a = document.createElement('a');
423
+ a.href = url;
424
+ a.download = 'breast-alignment-' + new Date().getTime() + '.json';
425
+ document.body.appendChild(a);
426
+ a.click();
427
+ document.body.removeChild(a);
428
+ URL.revokeObjectURL(url);
429
+ });
430
+
431
+ document.getElementById('export-image').addEventListener('click', function() {
432
+ if (!currentImage) return;
433
+
434
+ // Create a temporary canvas with landmarks
435
+ const tempCanvas = document.createElement('canvas');
436
+ tempCanvas.width = canvas.width;
437
+ tempCanvas.height = canvas.height;
438
+ const ctx = tempCanvas.getContext('2d');
439
+
440
+ // Draw original image
441
+ ctx.drawImage(currentImage.getElement(), 0, 0, canvas.width, canvas.height);
442
+
443
+ // Draw landmarks
444
+ landmarks.forEach(landmark => {
445
+ ctx.beginPath();
446
+ ctx.arc(landmark.x, landmark.y, 10, 0, Math.PI * 2);
447
+
448
+ switch(landmark.type) {
449
+ case 'nipple': ctx.fillStyle = 'rgba(236, 72, 153, 0.7)'; break;
450
+ case 'underbust': ctx.fillStyle = 'rgba(168, 85, 247, 0.7)'; break;
451
+ case 'side': ctx.fillStyle = 'rgba(99, 102, 241, 0.7)'; break;
452
+ }
453
+
454
+ ctx.fill();
455
+ ctx.strokeStyle = 'white';
456
+ ctx.lineWidth = 2;
457
+ ctx.stroke();
458
+ });
459
+
460
+ // Export
461
+ const url = tempCanvas.toDataURL('image/png');
462
+ const a = document.createElement('a');
463
+ a.href = url;
464
+ a.download = 'breast-alignment-' + new Date().getTime() + '.png';
465
+ document.body.appendChild(a);
466
+ a.click();
467
+ document.body.removeChild(a);
468
+ });
469
+ });
470
+ </script>
471
+ <script>feather.replace();</script>
472
+ </body>
473
  </html>