Spaces:
Sleeping
Sleeping
Commit ·
7d9495f
1
Parent(s): 690d566
✨ Upgrade Odontogram to a realistic Curved Parabolic Dental Arch
Browse files- toothmap_frontend/src/App.jsx +29 -15
toothmap_frontend/src/App.jsx
CHANGED
|
@@ -19,6 +19,18 @@ const QUADRANT_COLOURS = ['#3b82f6', '#22c55e', '#f59e0b', '#ef4444'];
|
|
| 19 |
const UPPER_TEETH = [18, 17, 16, 15, 14, 13, 12, 11, 21, 22, 23, 24, 25, 26, 27, 28];
|
| 20 |
const LOWER_TEETH = [48, 47, 46, 45, 44, 43, 42, 41, 31, 32, 33, 34, 35, 36, 37, 38];
|
| 21 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
function computeIoU(b1, b2) {
|
| 23 |
const ix = Math.max(b1[0], b2[0]), iy = Math.max(b1[1], b2[1]);
|
| 24 |
const iw = Math.min(b1[2], b2[2]) - ix, ih = Math.min(b1[3], b2[3]) - iy;
|
|
@@ -280,18 +292,19 @@ export default function App() {
|
|
| 280 |
|
| 281 |
{/* Dental FDI Chart (Odontogram) moved to Main Section */}
|
| 282 |
{(uniquePipelineResults && mode.includes('pipeline')) && (
|
| 283 |
-
<div className="w-full max-w-4xl bg-zinc-900/60 rounded-2xl p-
|
| 284 |
-
<p className="text-sm font-bold text-slate-300 uppercase tracking-widest mb-
|
| 285 |
-
<div className="flex flex-col gap-
|
| 286 |
-
{/* Upper Row */}
|
| 287 |
-
<div className="flex w-full justify-between gap-1.5">
|
| 288 |
{UPPER_TEETH.map((t, idx) => {
|
| 289 |
const isLeftQuad = idx < 8;
|
| 290 |
return (
|
| 291 |
<div key={t}
|
| 292 |
-
|
|
|
|
| 293 |
uniquePipelineResults.some(r => r.fdi === t)
|
| 294 |
-
? 'bg-green-500/
|
| 295 |
: 'bg-zinc-800/80 text-zinc-500 border border-zinc-700/50 opacity-60'
|
| 296 |
} ${isLeftQuad && idx === 7 ? 'mr-4' : ''}`}>
|
| 297 |
{t}
|
|
@@ -300,21 +313,22 @@ export default function App() {
|
|
| 300 |
</div>
|
| 301 |
|
| 302 |
{/* Middle Divider */}
|
| 303 |
-
<div className="flex items-center w-full my-
|
| 304 |
-
<div className="flex-1 h-px bg-zinc-700" />
|
| 305 |
-
<div className="px-
|
| 306 |
-
<div className="flex-1 h-px bg-zinc-700" />
|
| 307 |
</div>
|
| 308 |
|
| 309 |
-
{/* Lower Row */}
|
| 310 |
-
<div className="flex w-full justify-between gap-1.5">
|
| 311 |
{LOWER_TEETH.map((t, idx) => {
|
| 312 |
const isLeftQuad = idx < 8;
|
| 313 |
return (
|
| 314 |
<div key={t}
|
| 315 |
-
|
|
|
|
| 316 |
uniquePipelineResults.some(r => r.fdi === t)
|
| 317 |
-
? 'bg-green-500/
|
| 318 |
: 'bg-zinc-800/80 text-zinc-500 border border-zinc-700/50 opacity-60'
|
| 319 |
} ${isLeftQuad && idx === 7 ? 'mr-4' : ''}`}>
|
| 320 |
{t}
|
|
|
|
| 19 |
const UPPER_TEETH = [18, 17, 16, 15, 14, 13, 12, 11, 21, 22, 23, 24, 25, 26, 27, 28];
|
| 20 |
const LOWER_TEETH = [48, 47, 46, 45, 44, 43, 42, 41, 31, 32, 33, 34, 35, 36, 37, 38];
|
| 21 |
|
| 22 |
+
// Calculates a quadratic offset to form a realistic dental arch (horseshoe layout)
|
| 23 |
+
function getArchStyle(index, isUpper) {
|
| 24 |
+
const max = 56.25; // 7.5 squared
|
| 25 |
+
const val = max - Math.pow(index - 7.5, 2);
|
| 26 |
+
const offset = val * 0.45; // Scale the depth of the curve (approx 25px max)
|
| 27 |
+
const rotation = (index - 7.5) * (isUpper ? -1.5 : 1.5); // Slight tilt to face the center
|
| 28 |
+
|
| 29 |
+
return {
|
| 30 |
+
transform: `translateY(${isUpper ? offset : -offset}px) rotate(${rotation}deg)`
|
| 31 |
+
};
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
function computeIoU(b1, b2) {
|
| 35 |
const ix = Math.max(b1[0], b2[0]), iy = Math.max(b1[1], b2[1]);
|
| 36 |
const iw = Math.min(b1[2], b2[2]) - ix, ih = Math.min(b1[3], b2[3]) - iy;
|
|
|
|
| 292 |
|
| 293 |
{/* Dental FDI Chart (Odontogram) moved to Main Section */}
|
| 294 |
{(uniquePipelineResults && mode.includes('pipeline')) && (
|
| 295 |
+
<div className="w-full max-w-4xl bg-zinc-900/60 rounded-2xl p-8 border border-zinc-800/80 shadow-2xl mt-4">
|
| 296 |
+
<p className="text-sm font-bold text-slate-300 uppercase tracking-widest mb-10 text-center">Comprehensive Dental Arch</p>
|
| 297 |
+
<div className="flex flex-col gap-6">
|
| 298 |
+
{/* Upper Row (Maxillary) */}
|
| 299 |
+
<div className="flex w-full justify-between gap-1.5 pb-6 relative">
|
| 300 |
{UPPER_TEETH.map((t, idx) => {
|
| 301 |
const isLeftQuad = idx < 8;
|
| 302 |
return (
|
| 303 |
<div key={t}
|
| 304 |
+
style={getArchStyle(idx, true)}
|
| 305 |
+
className={`flex-1 aspect-[3/4.5] rounded-t-sm rounded-b-2xl flex items-center justify-center text-sm font-bold transition-all transform hover:scale-110 duration-200 cursor-default ${
|
| 306 |
uniquePipelineResults.some(r => r.fdi === t)
|
| 307 |
+
? 'bg-gradient-to-b from-green-500/10 to-green-500/30 text-green-400 border border-green-500/50 shadow-[0_4px_12px_rgba(34,197,94,0.25)]'
|
| 308 |
: 'bg-zinc-800/80 text-zinc-500 border border-zinc-700/50 opacity-60'
|
| 309 |
} ${isLeftQuad && idx === 7 ? 'mr-4' : ''}`}>
|
| 310 |
{t}
|
|
|
|
| 313 |
</div>
|
| 314 |
|
| 315 |
{/* Middle Divider */}
|
| 316 |
+
<div className="flex items-center w-full my-4 opacity-70 z-10">
|
| 317 |
+
<div className="flex-1 h-px bg-zinc-700/50" />
|
| 318 |
+
<div className="px-6 text-[10px] text-zinc-400 font-mono tracking-widest">MAXILLARY / MANDIBULAR</div>
|
| 319 |
+
<div className="flex-1 h-px bg-zinc-700/50" />
|
| 320 |
</div>
|
| 321 |
|
| 322 |
+
{/* Lower Row (Mandibular) */}
|
| 323 |
+
<div className="flex w-full justify-between gap-1.5 pt-6 relative">
|
| 324 |
{LOWER_TEETH.map((t, idx) => {
|
| 325 |
const isLeftQuad = idx < 8;
|
| 326 |
return (
|
| 327 |
<div key={t}
|
| 328 |
+
style={getArchStyle(idx, false)}
|
| 329 |
+
className={`flex-1 aspect-[3/4.5] rounded-b-sm rounded-t-2xl flex items-center justify-center text-sm font-bold transition-all transform hover:scale-110 duration-200 cursor-default ${
|
| 330 |
uniquePipelineResults.some(r => r.fdi === t)
|
| 331 |
+
? 'bg-gradient-to-t from-green-500/10 to-green-500/30 text-green-400 border border-green-500/50 shadow-[0_-4px_12px_rgba(34,197,94,0.25)]'
|
| 332 |
: 'bg-zinc-800/80 text-zinc-500 border border-zinc-700/50 opacity-60'
|
| 333 |
} ${isLeftQuad && idx === 7 ? 'mr-4' : ''}`}>
|
| 334 |
{t}
|