Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions PanTS-Demo/src/components/ScanReadout/ScanReadout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { organDescriptions } from "../../helpers/organInfo";

const pretty = (s: string) => s.replaceAll("_", " ");
const fmtVolume = (cm3: number) => (cm3 >= 1000 ? `${(cm3 / 1000).toFixed(1)} L` : `${Math.round(cm3)} cm³`);

type ReadoutStat = { organ_name: string; volume_cm3: number; mean_hu: number };

type Props = {
stats: ReadoutStat[] | null;
loading: boolean;
error: boolean;
onLoad: () => void;
};

export default function ScanReadout({ stats, loading, error, onLoad }: Props) {
return (
<div className="w-full flex flex-col gap-2 border-2 rounded-sm bg-gray-900 shadow-md">
<div className="bg-gray-600 w-full h-8 flex items-center justify-center text-center rounded-t-sm text-white">
Scan readout
</div>
<div className="pb-2 pl-4 pr-4 flex flex-col gap-2 max-h-80 overflow-y-auto">
{loading ? (
<p className="text-gray-300 text-sm">Reading the scan…</p>
) : error ? (
<p className="text-red-400 text-sm">Couldn't load measurements for this scan.</p>
) : !stats ? (
<>
<p className="text-gray-300 text-sm leading-snug">
A plain-language summary of this scan: each structure's measured size and density, explained. For learning, not diagnosis.
</p>
<button
type="button"
className="text-white relative !p-1 text-2xs !bg-gray-700 hover:!border-white"
onClick={onLoad}
>
Explain this scan
</button>
</>
) : stats.length === 0 ? (
<p className="text-gray-300 text-sm">No measurable structures found in this scan.</p>
) : (
<>
<p className="text-gray-400 text-2xs leading-snug">
Density is in Hounsfield Units (HU): water is 0, fat is negative, soft tissue is mildly positive, bone is high. Volumes are measured from the segmentation. For learning, not diagnosis.
</p>
{stats.map((s) => (
<div key={s.organ_name} className="border-t border-gray-700 pt-2">
<div className="text-white text-sm capitalize">
{pretty(s.organ_name)}
<span className="text-gray-400"> — {fmtVolume(s.volume_cm3)} · {Math.round(s.mean_hu)} HU</span>
</div>
<p className="text-gray-300 text-xs leading-snug">
{organDescriptions[s.organ_name] ?? "A structure identified in this scan."}
</p>
</div>
))}
</>
)}
</div>
</div>
);
}
34 changes: 34 additions & 0 deletions PanTS-Demo/src/helpers/organInfo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
export const organDescriptions: Record<string, string> = {
adrenal_gland_left: "A small hormone-making gland on top of the left kidney. It helps control stress response, blood pressure, and metabolism.",
adrenal_gland_right: "A small hormone-making gland on top of the right kidney. It helps control stress response, blood pressure, and metabolism.",
aorta: "The body's main artery, carrying oxygen-rich blood from the heart down through the chest and abdomen.",
bladder: "The hollow, stretchy organ that stores urine until you empty it.",
celiac_artery: "A short, major artery off the aorta that feeds the liver, stomach, and spleen.",
colon: "The large intestine, which pulls water back out of digested food and forms stool.",
common_bile_duct: "The tube carrying bile from the liver and gallbladder into the small intestine to help digest fat.",
duodenum: "The first stretch of the small intestine, right after the stomach.",
femur_left: "The thigh bone of the left leg, the longest, strongest bone in the body.",
femur_right: "The thigh bone of the right leg, the longest, strongest bone in the body.",
gall_bladder: "A small sac under the liver that stores bile and releases it into the gut to break down fats.",
kidney_left: "The left kidney, which filters waste out of the blood and makes urine.",
kidney_right: "The right kidney, which filters waste out of the blood and makes urine.",
liver: "A large organ that filters the blood, processes nutrients, and makes bile.",
lung_left: "The left lung, moving oxygen into the blood and carbon dioxide out.",
lung_right: "The right lung, moving oxygen into the blood and carbon dioxide out.",
pancreas: "An organ behind the stomach that makes digestive enzymes and insulin, which controls blood sugar.",
pancreas_body: "The middle section of the pancreas.",
pancreas_head: "The wide end of the pancreas, set into the curve of the duodenum.",
pancreas_tail: "The narrow end of the pancreas, reaching toward the spleen.",
pancreatic_duct: "The channel carrying digestive juices from the pancreas into the small intestine.",
pancreatic_lesion: "An abnormal spot in the pancreas that a doctor would want to look at closely.",
postcava: "The inferior vena cava, the large vein returning blood from the lower body to the heart.",
prostate: "A small gland below the bladder in men that adds fluid to semen.",
spleen: "An organ near the stomach that filters blood and helps fight infection.",
stomach: "The muscular pouch that mixes food with acid and starts breaking it down.",
superior_mesenteric_artery: "A major artery off the aorta that supplies most of the small intestine and part of the colon.",
veins: "Blood vessels that carry blood back toward the heart.",
intestine: "The long, coiled tube where most digestion and nutrient absorption happen.",
renal_vein_left: "The vein draining filtered blood from the left kidney back toward the heart.",
renal_vein_right: "The vein draining filtered blood from the right kidney back toward the heart.",
cbd_stent: "A small tube a doctor places to hold the bile duct open so bile can drain.",
};
7 changes: 7 additions & 0 deletions PanTS-Demo/src/routes/VisualizationPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import ReportScreen from "../components/ReportScreen/ReportScreen";
import SnakeGame from "../components/SnakeGame/SnakeGame";
import WindowingSlider from "../components/WindowingSlider/WindowingSlider";
import ZoomHandle from "../components/zoomHandle";
import ScanReadout from "../components/ScanReadout/ScanReadout";
import {
API_BASE,
APP_CONSTANTS,
Expand Down Expand Up @@ -805,6 +806,12 @@ function VisualizationPage() {
setSubmitted={setZoomLevel}
setZoomMode={setZoomMode}
/>
<ScanReadout
stats={organStats}
loading={statsLoading}
error={statsError}
onLoad={loadOrganStats}
/>
</>

{/* Report Download Zoom Buttons */}
Expand Down
Loading