Spaces:
Sleeping
Sleeping
| "use client" | |
| import React, { useRef, useMemo } from "react" | |
| import { Canvas, useFrame } from "@react-three/fiber" | |
| import { OrbitControls, Sphere, Html, Stars, Float } from "@react-three/drei" | |
| import * as THREE from "three" | |
| interface PredictionOutput { | |
| sepsis: number | |
| respiration: number | |
| coagulation: number | |
| liver: number | |
| cardiovascular: number | |
| cns: number | |
| renal: number | |
| hours_beforesepsis: number | |
| fod: number | |
| hours_beforedeath: number | |
| } | |
| interface OrganProps { | |
| position: [number, number, number] | |
| baseColor: string | |
| riskScore?: number // 0-4 SOFA score or 0-1 probability | |
| label: string | |
| size?: number | |
| } | |
| function getColorByRisk(baseColor: string, riskScore: number = 0, maxScore: number = 4): string { | |
| const normalizedRisk = Math.min(riskScore / maxScore, 1) | |
| if (normalizedRisk >= 0.75) return "#ef4444" // Critical - Red | |
| if (normalizedRisk >= 0.5) return "#f59e0b" // Warning - Orange | |
| if (normalizedRisk >= 0.25) return "#eab308" // Elevated - Yellow | |
| return baseColor // Normal - Original color | |
| } | |
| function Organ({ position, baseColor, riskScore = 0, label, size = 0.3 }: OrganProps) { | |
| const meshRef = useRef<THREE.Mesh>(null) | |
| const color = getColorByRisk(baseColor, riskScore) | |
| const isHighRisk = riskScore >= 2 | |
| useFrame((state) => { | |
| if (meshRef.current) { | |
| const t = state.clock.getElapsedTime() | |
| // More intense pulse for high-risk organs | |
| const pulseIntensity = isHighRisk ? 0.15 : 0.05 | |
| const pulseSpeed = isHighRisk ? 3 : 2 | |
| meshRef.current.scale.setScalar(1 + Math.sin(t * pulseSpeed) * pulseIntensity) | |
| } | |
| }) | |
| return ( | |
| <Float speed={isHighRisk ? 2 : 1} rotationIntensity={0.2} floatIntensity={0.3}> | |
| <mesh position={position} ref={meshRef}> | |
| <sphereGeometry args={[size, 32, 32]} /> | |
| <meshStandardMaterial | |
| color={color} | |
| emissive={color} | |
| emissiveIntensity={isHighRisk ? 0.8 : 0.4} | |
| opacity={0.9} | |
| transparent | |
| roughness={0.3} | |
| metalness={0.2} | |
| /> | |
| <Html distanceFactor={10} style={{ pointerEvents: 'none' }}> | |
| <div className={` | |
| px-2 py-1 rounded-lg text-xs font-medium whitespace-nowrap shadow-xl border | |
| ${isHighRisk | |
| ? 'bg-red-950/90 text-red-200 border-red-500/50 animate-pulse' | |
| : 'bg-slate-900/90 text-white border-slate-700' | |
| } | |
| `}> | |
| {label} | |
| {riskScore > 0 && ( | |
| <span className={`ml-1.5 ${isHighRisk ? 'text-red-400' : 'text-slate-400'}`}> | |
| ({riskScore.toFixed(1)}) | |
| </span> | |
| )} | |
| </div> | |
| </Html> | |
| </mesh> | |
| </Float> | |
| ) | |
| } | |
| interface BodyProps { | |
| prediction: PredictionOutput | null | |
| } | |
| function RotatingBody({ prediction }: BodyProps) { | |
| const groupRef = useRef<THREE.Group>(null) | |
| useFrame((state, delta) => { | |
| if (groupRef.current) { | |
| groupRef.current.rotation.y += delta * 0.15 | |
| } | |
| }) | |
| // Calculate overall risk for body wireframe color | |
| const overallRisk = useMemo(() => { | |
| if (!prediction) return 0 | |
| return prediction.sepsis | |
| }, [prediction]) | |
| const bodyColor = overallRisk >= 0.7 ? "#ef4444" : overallRisk >= 0.4 ? "#f59e0b" : "#64748b" | |
| return ( | |
| <group ref={groupRef}> | |
| {/* Human Body Outline - Capsule torso */} | |
| <mesh position={[0, 0, 0]}> | |
| <capsuleGeometry args={[0.7, 2.2, 4, 16]} /> | |
| <meshStandardMaterial | |
| color={bodyColor} | |
| wireframe | |
| opacity={0.15} | |
| transparent | |
| emissive={bodyColor} | |
| emissiveIntensity={overallRisk >= 0.4 ? 0.2 : 0} | |
| /> | |
| </mesh> | |
| {/* Head */} | |
| <mesh position={[0, 1.8, 0]}> | |
| <sphereGeometry args={[0.4, 16, 16]} /> | |
| <meshStandardMaterial | |
| color={bodyColor} | |
| wireframe | |
| opacity={0.1} | |
| transparent | |
| /> | |
| </mesh> | |
| {/* Organ Highlights with prediction data */} | |
| <Organ | |
| position={[0, 0.4, 0.45]} | |
| baseColor="#ef4444" | |
| riskScore={prediction?.cardiovascular || 0} | |
| label="Heart (CVS)" | |
| size={0.28} | |
| /> | |
| <Organ | |
| position={[-0.35, -0.1, 0.35]} | |
| baseColor="#eab308" | |
| riskScore={prediction?.liver || 0} | |
| label="Liver" | |
| size={0.25} | |
| /> | |
| <Organ | |
| position={[0, 0.7, 0.1]} | |
| baseColor="#3b82f6" | |
| riskScore={prediction?.respiration || 0} | |
| label="Lungs" | |
| size={0.35} | |
| /> | |
| <Organ | |
| position={[0, -0.7, 0.3]} | |
| baseColor="#a855f7" | |
| riskScore={prediction?.renal || 0} | |
| label="Kidneys" | |
| size={0.22} | |
| /> | |
| <Organ | |
| position={[0, 1.5, 0.1]} | |
| baseColor="#10b981" | |
| riskScore={prediction?.cns || 0} | |
| label="CNS (Brain)" | |
| size={0.3} | |
| /> | |
| <Organ | |
| position={[0.35, -0.1, 0.35]} | |
| baseColor="#f59e0b" | |
| riskScore={prediction?.coagulation || 0} | |
| label="Coagulation" | |
| size={0.2} | |
| /> | |
| </group> | |
| ) | |
| } | |
| interface BodyVisualizerProps { | |
| prediction?: PredictionOutput | null | |
| } | |
| export default function BodyVisualizer({ prediction = null }: BodyVisualizerProps) { | |
| return ( | |
| <div className="w-full h-full min-h-[400px] relative"> | |
| <Canvas | |
| camera={{ position: [0, 0, 5.5], fov: 50 }} | |
| gl={{ antialias: true, alpha: true }} | |
| > | |
| {/* Lighting */} | |
| <ambientLight intensity={0.4} /> | |
| <pointLight position={[10, 10, 10]} intensity={0.8} color="#ffffff" /> | |
| <pointLight position={[-10, -10, -10]} intensity={0.3} color="#06b6d4" /> | |
| <spotLight | |
| position={[0, 10, 5]} | |
| intensity={0.5} | |
| angle={0.3} | |
| penumbra={0.5} | |
| color="#3b82f6" | |
| /> | |
| {/* Star field background */} | |
| <Stars | |
| radius={100} | |
| depth={50} | |
| count={3000} | |
| factor={4} | |
| saturation={0.5} | |
| fade | |
| speed={0.5} | |
| /> | |
| {/* Controls */} | |
| <OrbitControls | |
| enableZoom={true} | |
| enablePan={false} | |
| autoRotate | |
| autoRotateSpeed={0.3} | |
| minDistance={3} | |
| maxDistance={8} | |
| /> | |
| {/* Body Visualization */} | |
| <RotatingBody prediction={prediction} /> | |
| </Canvas> | |
| </div> | |
| ) | |
| } | |