TMF_005_Atlas_WebXR / basic_fastsurfer.html
Ajinkya
Deployment: Clean logic-only sync
130bee5
<!DOCTYPE html>
<html>
<head>
<title>TMF_005 | High-Res Tree Explorer</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/loaders/OBJLoader.js"></script>
<style>
body { margin: 0; background: #050505; color: #ccc; font-family: 'Segoe UI', sans-serif; overflow: hidden; display: flex; }
#sidebar { width: 350px; height: 100vh; background: #111; border-right: 1px solid #333; display: flex; flex-direction: column; z-index: 10; }
#header { padding: 15px; border-bottom: 1px solid #333; font-size: 14px; font-weight: bold; color: #00ffcc; text-align: center; }
#tree { flex-grow: 1; overflow-y: auto; padding: 10px; font-size: 11px; }
.group { margin-bottom: 8px; }
.group-header { cursor: pointer; background: #1a1a1a; padding: 6px 10px; border-radius: 4px; display: flex; justify-content: space-between; font-weight: bold; color: #fff; border: 1px solid #222; }
.group-content { padding-left: 5px; margin-top: 5px; border-left: 1px solid #222; }
.hidden { display: none; }
.item { display: flex; align-items: center; padding: 4px; border-radius: 3px; border-bottom: 1px solid #1a1a1a; }
.item:hover { background: #1a1a1a; }
.item input[type="checkbox"] { margin-right: 8px; }
.item input[type="range"] { width: 40px; margin-left: auto; height: 10px; }
.color-dot { width: 10px; height: 10px; border-radius: 2px; margin-right: 8px; flex-shrink: 0; }
.label-text { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 180px; }
#container { flex-grow: 1; position: relative; }
#loading { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: #00ffcc; font-weight: bold; }
.controls { padding: 10px; display: flex; gap: 5px; border-bottom: 1px solid #333; justify-content: center; }
button { background: #333; color: white; border: none; padding: 5px 10px; cursor: pointer; border-radius: 3px; font-size: 10px; }
button:hover { background: #444; }
::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-thumb { background: #333; border-radius: 10px; }
</style>
</head>
<body>
<div id="sidebar">
<div id="header">TMF_005 | ANATOMICAL ATLAS</div>
<div class="controls">
<button onclick="toggleGlobal(false)">Hide All</button>
<button onclick="toggleGlobal(true)">Show All</button>
</div>
<div id="tree"></div>
</div>
<div id="container"><div id="loading">CLEANING MESH SURFACES...</div></div>
<script>
// --- FULL ANATOMICAL LOOKUP TABLE ---
const lut_base = {
2: "White Matter", 3: "Gray Matter", 4: "Lateral Ventricle", 5: "Inf Lat Vent", 7: "Cerebellum WM", 8: "Cerebellum CX",
10: "Thalamus", 11: "Caudate", 12: "Putamen", 13: "Pallidum", 14: "3rd Ventricle", 15: "4th Ventricle",
16: "Brain Stem", 17: "Hippocampus", 18: "Amygdala", 24: "CSF", 26: "Accumbens", 28: "Ventral DC (Hypothalamus)", 31: "Choroid Plexus",
1002: "Caudal Ant Cingulate", 1003: "Caudal Mid Frontal", 1005: "Cuneus", 1006: "Entorhinal", 1007: "Fusiform", 1008: "Inferior Parietal",
1009: "Inferior Temporal", 1010: "Ismthmus Cingulate", 1011: "Lateral Occipital", 1012: "Lateral Orbitofrontal", 1013: "Lingual",
1014: "Medial Orbitofrontal", 1015: "Middle Temporal", 1016: "Parahippocampal", 1017: "Paracentral", 1018: "Pars Nasalis",
1019: "Pars Orbitalis", 1020: "Pars Triangularis", 1021: "Pericalcarine", 1022: "Postcentral", 1023: "Posterior Cingulate",
1024: "Precentral", 1025: "Precuneus", 1026: "Rostral Ant Cingulate", 1027: "Rostral Mid Frontal", 1028: "Superior Frontal",
1029: "Superior Parietal", 1030: "Superior Temporal", 1031: "Supramarginal", 1034: "Transverse Temporal", 1035: "Insula"
};
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, (window.innerWidth - 350) / window.innerHeight, 0.1, 2000);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth - 350, window.innerHeight);
document.getElementById('container').appendChild(renderer.domElement);
const controls = new THREE.OrbitControls(camera, renderer.domElement);
scene.add(new THREE.AmbientLight(0xffffff, 0.6));
const light = new THREE.PointLight(0xffffff, 1);
camera.add(light); scene.add(camera);
const meshes = {};
const groups = {
"Subcortical (Deep)": [],
"Cortex (Left)": [],
"Cortex (Right)": [],
"Ventricles & CSF": []
};
function getColor(name) {
let hash = 0;
for (let i = 0; i < name.length; i++) { hash = name.charCodeAt(i) + ((hash << 5) - hash); }
return "#" + (hash & 0x00FFFFFF).toString(16).padStart(6, '0');
}
const loader = new THREE.OBJLoader();
loader.load('highres_smooth_brain.obj', (object) => {
document.getElementById('loading').style.display = 'none';
object.traverse((child) => {
if (child.isMesh) {
const id = parseInt(child.name.replace('Segment_', ''));
let base_id = id > 2000 ? id - 1000 : id; // Normalize right cortex to left ID for name lookup
let name = (id >= 2000 ? "R " : id >= 1000 ? "L " : "") + (lut_base[base_id] || lut_base[id] || "Part " + id);
const color = getColor(child.name);
child.material = new THREE.MeshStandardMaterial({
color: color,
side: THREE.DoubleSide,
transparent: true,
opacity: 1.0
});
const isDefault = [17, 53, 28, 60].includes(id); // Hypothalamus/Hippocampus
child.visible = isDefault;
meshes[child.name] = child;
const item = { id, name, color, meshName: child.name, visible: isDefault };
if (id >= 2000) groups["Cortex (Right)"].push(item);
else if (id >= 1000) groups["Cortex (Left)"].push(item);
else if ([4, 5, 14, 15, 24, 31, 43, 44, 63].includes(id)) groups["Ventricles & CSF"].push(item);
else groups["Subcortical (Deep)"].push(item);
}
});
const tree = document.getElementById('tree');
for (let gName in groups) {
const gDiv = document.createElement('div');
gDiv.className = 'group';
// Categories are collapsed by default
gDiv.innerHTML = `<div class="group-header" onclick="this.nextSibling.classList.toggle('hidden')">
${gName} <span>▾</span></div><div class="group-content hidden"></div>`;
const content = gDiv.querySelector('.group-content');
groups[gName].sort((a, b) => a.id - b.id).forEach(item => {
const iDiv = document.createElement('div');
iDiv.className = 'item';
iDiv.innerHTML = `
<input type="checkbox" id="chk-${item.meshName}" ${item.visible ? 'checked' : ''}>
<div class="color-dot" style="background:${item.color}"></div>
<span class="label-text">${item.name}</span>
<input type="range" min="0" max="100" value="100" title="Opacity" id="alpha-${item.meshName}">
`;
// Visibility Click
iDiv.querySelector('input[type="checkbox"]').onchange = (e) => {
meshes[item.meshName].visible = e.target.checked;
};
// Opacity Slider
iDiv.querySelector('input[type="range"]').oninput = (e) => {
meshes[item.meshName].material.opacity = e.target.value / 100;
if (e.target.value < 100) meshes[item.meshName].visible = true; // Auto-show if sliding
};
content.appendChild(iDiv);
});
tree.appendChild(gDiv);
}
scene.add(object);
const box = new THREE.Box3().setFromObject(object);
const center = box.getCenter(new THREE.Vector3());
camera.position.set(center.x, center.y + 200, center.z + 280);
controls.target.copy(center);
controls.update();
});
function toggleGlobal(show) {
for (let id in meshes) {
meshes[id].visible = show;
const chk = document.getElementById(`chk-${id}`);
if (chk) chk.checked = show;
}
}
function animate() { requestAnimationFrame(animate); renderer.render(scene, camera); }
animate();
window.addEventListener('resize', () => {
renderer.setSize(window.innerWidth - 350, window.innerHeight);
camera.aspect = (window.innerWidth - 350) / window.innerHeight;
camera.updateProjectionMatrix();
});
</script>
</body>
</html>