Spaces:
Running
Running
File size: 9,172 Bytes
130bee5 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 | <!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> |