diff --git a/.gitignore b/.gitignore index 7f56ac5..854b814 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,6 @@ build/ *.log .DS_Store data-pipeline/seed/manifest.json +data-pipeline/seed/manifest-*.json data-pipeline/seed/models/ +data-pipeline/converted/ diff --git a/data-pipeline/Dockerfile b/data-pipeline/Dockerfile index b3b2175..3b2ac29 100644 --- a/data-pipeline/Dockerfile +++ b/data-pipeline/Dockerfile @@ -3,8 +3,17 @@ # Alpine essa dependência é bem mais dolorosa de resolver. FROM node:20-bookworm RUN apt-get update \ - && apt-get install -y --no-install-recommends assimp-utils ca-certificates \ + && apt-get install -y --no-install-recommends assimp-utils ca-certificates python3 python3-venv \ && rm -rf /var/lib/apt/lists/* + +# Ambiente Python isolado só pra buscar as organelas reais da célula +# (fetch-cell-organelles.py) — cloud-volume/trimesh não têm nada a ver com +# o resto do pipeline (100% Node/glTF-transform), então ficam num venv +# próprio em vez de poluir o Python do sistema. +RUN python3 -m venv /opt/venv-cell \ + && /opt/venv-cell/bin/pip install --no-cache-dir --upgrade pip \ + && /opt/venv-cell/bin/pip install --no-cache-dir cloud-volume trimesh + WORKDIR /app COPY package.json ./ RUN npm install diff --git a/data-pipeline/package.json b/data-pipeline/package.json index 3c61d78..971ad45 100644 --- a/data-pipeline/package.json +++ b/data-pipeline/package.json @@ -9,9 +9,12 @@ "extract:manifest": "node scripts/extract-manifest.mjs", "build:tegumentar": "node seed/build-manifest.mjs", "build:microscopy": "node seed/build-manifest-microscopy.mjs", + "cell:fetch": "/opt/venv-cell/bin/python scripts/fetch-cell-organelles.py", + "cell:model": "node scripts/build-cell-model.mjs", + "cell:manifest": "node seed/build-manifest-cell.mjs", "merge:manifest": "node scripts/merge-manifests.mjs", "seed:db": "node scripts/seed-db.mjs", - "run:all": "npm run fetch:models && npm run convert:models && npm run extract:manifest && npm run build:tegumentar && npm run build:microscopy && npm run merge:manifest && npm run seed:db" + "run:all": "npm run fetch:models && npm run convert:models && npm run extract:manifest && npm run build:tegumentar && npm run build:microscopy && npm run cell:fetch && npm run cell:model && npm run cell:manifest && npm run merge:manifest && npm run seed:db" }, "dependencies": { "@gltf-transform/core": "^4.0.4", diff --git a/data-pipeline/scripts/build-cell-model.mjs b/data-pipeline/scripts/build-cell-model.mjs new file mode 100644 index 0000000..040b26e --- /dev/null +++ b/data-pipeline/scripts/build-cell-model.mjs @@ -0,0 +1,56 @@ +// Lê citologia-raw.glb (gerado por fetch-cell-organelles.py a partir de +// dados REAIS de FIB-SEM do Janelia OpenOrganelle/CellMap — ver aquele +// script para a curadoria completa e a licença CC BY 4.0), simplifica e +// comprime com o MESMO recipe já usado pras malhas do Z-Anatomy em +// extract-manifest.mjs, e salva o resultado final em seed/models/citologia.glb. +// +// Diferente do Z-Anatomy, aqui não precisamos marcar nós excluídos nem +// resolver nomes duplicados — o .glb de entrada já tem exatamente 5 nós, +// um por organela, com o nome final (ex: "celula-nucleo") definido no +// próprio script Python. weld/simplify/dedup/meshopt só afetam a +// geometria, não o nome dos nós, então o "meshName" no manifesto +// (build-manifest-cell.mjs) pode confiar que bate exatamente com isso. +import { NodeIO } from "@gltf-transform/core"; +import { ALL_EXTENSIONS } from "@gltf-transform/extensions"; +import { prune, dedup, weld, simplify, meshopt } from "@gltf-transform/functions"; +import { MeshoptSimplifier, MeshoptEncoder } from "meshoptimizer"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { mkdirSync } from "node:fs"; + +await MeshoptSimplifier.ready; +await MeshoptEncoder.ready; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const IN_PATH = path.resolve(__dirname, "../converted/citologia/citologia-raw.glb"); +const OUT_DIR = path.resolve(__dirname, "../seed/models"); +mkdirSync(OUT_DIR, { recursive: true }); + +const io = new NodeIO() + .registerExtensions(ALL_EXTENSIONS) + .registerDependencies({ "meshopt.encoder": MeshoptEncoder }); + +console.log(`Lendo ${IN_PATH}...`); +const doc = await io.read(IN_PATH); + +const meshNames = doc + .getRoot() + .listNodes() + .map((n) => n.getName()) + .filter(Boolean); +console.log(`Organelas encontradas: ${meshNames.join(", ")}`); + +await doc.transform( + weld(), + // As organelas maiores (membrana, RE) chegam com bem mais geometria do + // que o necessário pra estudo interativo — mesmo ratio conservador já + // usado no resto do atlas. + simplify({ simplifier: MeshoptSimplifier, ratio: 0.5, error: 0.01 }), + prune(), + dedup(), + meshopt({ encoder: MeshoptEncoder, level: "medium" }), +); + +const outPath = path.join(OUT_DIR, "citologia.glb"); +await io.write(outPath, doc); +console.log(`✓ ${outPath} salvo.`); diff --git a/data-pipeline/scripts/fetch-cell-organelles.py b/data-pipeline/scripts/fetch-cell-organelles.py new file mode 100644 index 0000000..74c1a48 --- /dev/null +++ b/data-pipeline/scripts/fetch-cell-organelles.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python3 +# Busca organelas REAIS de uma célula HeLa inteira, reconstruída em 3D a +# partir de FIB-SEM (microscopia eletrônica volumétrica) e segmentada por +# machine learning pelo projeto CellMap/COSEM do Janelia Research Campus +# (HHMI) — publicado como OpenOrganelle, CC BY 4.0. +# https://openorganelle.janelia.org/ | dataset "jrc_hela-2" +# DOI da coleção: https://doi.org/10.25378/janelia.c.7456966 +# +# POR QUE ISSO E NÃO GEOMETRIA PROCEDURAL: é o mesmo princípio já usado pro +# resto do atlas (malhas reais do Z-Anatomy) — aqui não existe um "Z-Anatomy +# da célula" pronto pra converter de FBX, mas o Janelia publica a segmentação +# 3D real como malhas no formato Neuroglancer "precomputed" (multi-resolução, +# comprimido com Draco). Este script usa a biblioteca `cloud-volume` (Seung +# Lab) pra baixar e decodificar essas malhas direto do bucket público, sem +# fabricar nenhuma forma. +# +# Escolhas de curadoria (documentadas pra poder ajustar depois): +# - Cada organela é uma "camada" de segmentação própria no dataset +# (nucleus_seg, mito_seg, er_seg, golgi_seg, pm_seg), com um ID por +# instância. Pegamos a maior instância de núcleo/membrana (a célula +# mais completa no campo de visão) e TODAS as instâncias de +# mitocôndria/RE/Golgi (são realmente múltiplas organelas de verdade, +# não fragmentos de ruído — juntar todas reproduz a distribuição real). +# - LOD (nível de detalhe) escolhido por organela: mais fino pra +# estruturas pequenas/poucas (núcleo, Golgi), mais grosso pras que têm +# MUITA geometria agregada (mitocôndrias, RE) — o pipeline Node.js +# (build-cell-model.mjs) simplifica/comprime tudo de qualquer forma. +# - Todas as peças são recentralizadas usando o MESMO deslocamento (o +# centro da membrana plasmática) — cada organela NÃO pode ser +# recentralizada individualmente, isso destruiria a posição relativa +# real dentro da célula. +# - Escala: nanômetros (unidade nativa do dataset) → micrômetros (÷1000), +# só por conveniência numérica — a câmera do app se ajusta ao tamanho +# real do conteúdo de qualquer forma (ver Scene.tsx/CameraController). +# +# Vesículas (~40 mil instâncias) e endossomos (~5.600) ficaram de fora +# desta primeira versão por volume de dados — dá pra adicionar depois como +# mais uma organela seguindo exatamente o mesmo padrão abaixo. +import os +import time + +import numpy as np +import cloudvolume as cv +import trimesh + +DATASET = "jrc_hela-2" +BASE = f"precomputed://https://janelia-cosem-datasets.s3.amazonaws.com/{DATASET}/neuroglancer/mesh" +OUT_DIR = os.path.join(os.path.dirname(__file__), "..", "converted", "citologia") + +# (nome do nó no glb, camada de segmentação, IDs de instância, LOD) +GROUPS = [ + ("celula-membrana-plasmatica", "pm_seg", [2], 4), + ("celula-nucleo", "nucleus_seg", [1], 3), + ("celula-reticulo-endoplasmatico", "er_seg", list(range(1, 25)), 4), + ("celula-complexo-de-golgi", "golgi_seg", list(range(1, 9)), 3), + ("celula-mitocondrias", "mito_seg", list(range(1, 422)), 4), +] + +SCALE_NM_TO_UM = 1.0 / 1000.0 + + +def make_volume(seg_layer): + # CloudVolume espera o "info" completo de uma camada de imagem/ + # segmentação (com "scales"), mas só precisamos da parte de malha — + # os valores de "scales" abaixo são um placeholder mínimo válido só + # pra passar da checagem interna da biblioteca; a resolução REAL usada + # pra posicionar os vértices vem do "transform" dentro do info.json de + # cada camada de malha (lido automaticamente pelo cloud-volume). + info = { + "type": "segmentation", + "data_type": "uint64", + "num_channels": 1, + "scales": [{ + "chunk_sizes": [[64, 64, 64]], + "encoding": "raw", + "key": "1_1_1", + "resolution": [4, 4, 5.24], + "size": [1, 1, 1], + "voxel_offset": [0, 0, 0], + }], + "mesh": seg_layer, + } + return cv.CloudVolume(BASE, info=info, use_https=True) + + +def fetch_group(name, seg_layer, ids, lod): + t0 = time.time() + vol = make_volume(seg_layer) + meshes = vol.mesh.get(ids, lod=lod) + parts = [] + for m in meshes.values(): + if m is None or len(m.vertices) == 0 or len(m.faces) == 0: + continue + # A decodificação Draco em algumas LODs mais grosseiras às vezes + # produz vértices NaN/Inf pra fragmentos degenerados — zera-los + # em vez de excluir a organela inteira mantém a peça utilizável. + vertices = np.nan_to_num(m.vertices, nan=0.0, posinf=0.0, neginf=0.0) + parts.append(trimesh.Trimesh(vertices=vertices, faces=m.faces, process=False)) + if not parts: + raise RuntimeError(f"{name}: nenhuma geometria válida encontrada (verifique os IDs/LOD)") + combined = trimesh.util.concatenate(parts) + print(f" {name}: {len(combined.vertices)} vértices, {len(combined.faces)} faces ({time.time() - t0:.1f}s)", flush=True) + return combined + + +def main(): + print(f"Baixando organelas reais de {DATASET} (Janelia OpenOrganelle/CellMap, CC BY 4.0)...") + results = {} + for name, seg_layer, ids, lod in GROUPS: + results[name] = fetch_group(name, seg_layer, ids, lod) + + center = results["celula-membrana-plasmatica"].bounds.mean(axis=0) + + scene = trimesh.Scene() + for name, mesh in results.items(): + mesh.vertices = (mesh.vertices - center) * SCALE_NM_TO_UM + scene.add_geometry(mesh, node_name=name, geom_name=name) + + os.makedirs(OUT_DIR, exist_ok=True) + out_path = os.path.join(OUT_DIR, "citologia-raw.glb") + scene.export(out_path) + print(f"✓ {out_path} salvo — pronto para o build-cell-model.mjs simplificar/comprimir.") + + +if __name__ == "__main__": + main() diff --git a/data-pipeline/scripts/merge-manifests.mjs b/data-pipeline/scripts/merge-manifests.mjs index 037c430..9c21306 100644 --- a/data-pipeline/scripts/merge-manifests.mjs +++ b/data-pipeline/scripts/merge-manifests.mjs @@ -8,12 +8,13 @@ const SEED_DIR = path.resolve(__dirname, "../seed"); const real = JSON.parse(readFileSync(path.join(SEED_DIR, "manifest-real.json"), "utf-8")); const tegumentar = JSON.parse(readFileSync(path.join(SEED_DIR, "manifest-tegumentar.json"), "utf-8")); const microscopy = JSON.parse(readFileSync(path.join(SEED_DIR, "manifest-microscopy.json"), "utf-8")); +const cell = JSON.parse(readFileSync(path.join(SEED_DIR, "manifest-cell.json"), "utf-8")); -const structures = [...real.structures, ...tegumentar.structures, ...microscopy.structures]; +const structures = [...real.structures, ...tegumentar.structures, ...microscopy.structures, ...cell.structures]; writeFileSync( path.join(SEED_DIR, "manifest.json"), JSON.stringify({ structures }, null, 2), ); -console.log(`manifest.json final: ${structures.length} estruturas (${real.structures.length} reais + ${tegumentar.structures.length} tegumentar + ${microscopy.structures.length} citologia/histologia).`); +console.log(`manifest.json final: ${structures.length} estruturas (${real.structures.length} reais + ${tegumentar.structures.length} tegumentar + ${microscopy.structures.length} histologia + ${cell.structures.length} citologia 3D).`); diff --git a/data-pipeline/seed/build-manifest-cell.mjs b/data-pipeline/seed/build-manifest-cell.mjs new file mode 100644 index 0000000..5bad753 --- /dev/null +++ b/data-pipeline/seed/build-manifest-cell.mjs @@ -0,0 +1,78 @@ +// Gera manifest-cell.json — as 5 organelas do sistema "citologia", com +// malha 3D REAL (ao contrário de "histologia", que continua sendo +// fotomicrografias 2D — ver build-manifest-microscopy.mjs). +// +// Fonte: célula HeLa (jrc_hela-2) reconstruída em 3D a partir de FIB-SEM +// real e segmentada por machine learning pelo projeto CellMap/COSEM do +// Janelia Research Campus (HHMI) — OpenOrganelle, CC BY 4.0. +// https://openorganelle.janelia.org/ — DOI da coleção: +// https://doi.org/10.25378/janelia.c.7456966 +// Extração das malhas: data-pipeline/scripts/fetch-cell-organelles.py +// Simplificação/compressão final: data-pipeline/scripts/build-cell-model.mjs +// +// O nome de cada nó do citologia.glb (definido no script Python) é usado +// aqui como "meshName" — sem simplificação de nome/exclusão como no +// esquelético, já que são só 5 peças, cada uma com nome final único. +import { writeFileSync } from "node:fs"; +import { fileURLToPath } from "node:url"; +import path from "node:path"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const MODEL_FILE = "citologia.glb"; + +const CREDIT = + "Malha 3D real, segmentada por FIB-SEM (célula HeLa, jrc_hela-2) pelo projeto CellMap/Janelia Research Campus — OpenOrganelle, CC BY 4.0."; + +function structure(slug, namePt, nameEn, description) { + return { + system: "citologia", + slug, + namePt, + nameEn, + synonyms: [], + description: `${description} ${CREDIT}`, + regionPath: ["Citologia", "Célula (HeLa) — FIB-SEM real"], + modelFile: MODEL_FILE, + meshName: slug, + }; +} + +const structures = [ + structure( + "celula-membrana-plasmatica", + "Membrana Plasmática", + "Plasma Membrane", + "Envoltório que delimita a célula, controlando a entrada e saída de substâncias; formada por uma bicamada fosfolipídica com proteínas de membrana.", + ), + structure( + "celula-nucleo", + "Núcleo", + "Nucleus", + "Organela que guarda o DNA da célula e comanda a síntese proteica; a forma irregular e volumosa vista aqui é real e típica de células HeLa (uma linhagem tumoral, com núcleo frequentemente atípico).", + ), + structure( + "celula-reticulo-endoplasmatico", + "Retículo Endoplasmático", + "Endoplasmic Reticulum", + "Rede de membranas contínua com o envoltório nuclear, espalhada por todo o citoplasma; participa da síntese de proteínas (porção rugosa) e lipídios, além do armazenamento de cálcio.", + ), + structure( + "celula-complexo-de-golgi", + "Complexo de Golgi", + "Golgi Apparatus", + "Conjunto de sáculos membranosos empilhados, tipicamente posicionado perto do núcleo (posição confirmada nesta reconstrução real); processa, modifica e empacota proteínas vindas do retículo endoplasmático.", + ), + structure( + "celula-mitocondrias", + "Mitocôndrias", + "Mitochondria", + "População completa de mitocôndrias segmentadas nesta célula — organelas responsáveis pela produção de ATP por respiração celular; note a variação real de tamanho e forma entre elas, incluindo fragmentação, comum em linhagens tumorais.", + ), +]; + +writeFileSync( + path.resolve(__dirname, "manifest-cell.json"), + JSON.stringify({ structures }, null, 2), +); + +console.log(`manifest-cell.json gerado com ${structures.length} organelas.`); diff --git a/data-pipeline/seed/build-manifest-microscopy.mjs b/data-pipeline/seed/build-manifest-microscopy.mjs index b59e140..a14ce20 100644 --- a/data-pipeline/seed/build-manifest-microscopy.mjs +++ b/data-pipeline/seed/build-manifest-microscopy.mjs @@ -1,24 +1,20 @@ -// Gera manifest-microscopy.json (sistemas "citologia" e "histologia"). +// Gera manifest-microscopy.json (sistema "histologia" — tecidos). // -// MESMA DECISÃO já tomada pro tegumentar (ver build-manifest.mjs): não -// existe malha 3D real e aberta derivada de microscopia (nem de célula, -// nem de tecido histológico — confirmado pesquisando: Allen Institute for -// Cell Science só distribui dado volumétrico bruto (OME-Zarr/TIFF), não -// malha pronta; não existe um "Z-Anatomy da histologia" de código aberto). -// Fabricar formas geométricas (esferas pra núcleo, cilindros pra -// mitocôndria etc.) seria exatamente o "método não realístico" que se -// queria evitar. Em vez disso: FOTOMICROGRAFIAS REAIS (não ilustrações), -// de domínio público ou CC, com hotspots — mesmo padrão do tegumentar. +// Citologia (a célula) NÃO usa mais este padrão: em vez de fotomicrografia +// 2D, virou um modelo 3D real de verdade (segmentação FIB-SEM de uma +// célula HeLa completa) — ver build-manifest-cell.mjs. Pra tecidos +// histológicos propriamente ditos, porém, a mesma limitação do tegumentar +// (ver build-manifest.mjs) continua valendo: não existe malha 3D real e +// aberta de tecido histológico. Fabricar formas geométricas seria +// exatamente o "método não realístico" a evitar — por isso aqui é +// FOTOMICROGRAFIA REAL (não ilustração), de domínio público ou CC, com +// hotspots, mesmo padrão do tegumentar. // // Curadoria das imagens (baixadas de commons.wikimedia.org, verificadas // visualmente uma a uma pra confirmar que são fotos reais e não diagramas/ // ilustrações — vários candidatos populares como "Blausen ..." ou // "Typical human cell" foram DESCARTADOS por serem ilustrações, não fotos): // -// citologia/nucleo-celular-met.png -// Micrografia eletrônica de transmissão (MET) real de um núcleo -// celular. Domínio público. https://commons.wikimedia.org/wiki/File:Micrograph_of_a_cell_nucleus.png -// // histologia/epitelio-simples-colunar.jpg // Mucosa gástrica normal, H&E. Autor: Nephron. CC BY-SA 3.0. // https://commons.wikimedia.org/wiki/File:Normal_gastric_mucosa_intermed_mag.jpg @@ -62,29 +58,16 @@ function add(system, image, slug, namePt, nameEn, description, region = []) { nameEn, synonyms: [], description, - regionPath: [system === "citologia" ? "Citologia" : "Histologia", ...region], + regionPath: ["Histologia", ...region], // Sem malha 3D — o caminho da fotomicrografia (servida como asset // estático do frontend, igual à imagem do tegumentar) fica direto no // campo modelFile; MicroscopyDiagram.tsx usa isso pra saber em qual - // imagem desenhar o hotspot de cada estrutura. + // imagem desenhar o hotspot de cada estrutura. modelFile: image, meshName: slug, }); } -// --------------------------------------------------------------------- -// CITOLOGIA — núcleo celular (MET real) -// --------------------------------------------------------------------- -const CEL = "/citologia/nucleo-celular-met.png"; -add("citologia", CEL, "nucleo-citologia", "Núcleo Celular", "Cell Nucleus", - "Maior organela da célula eucariótica; guarda o DNA organizado em cromatina e comanda toda a síntese proteica ao transcrever RNA mensageiro."); -add("citologia", CEL, "envoltorio-nuclear", "Envoltório Nuclear", "Nuclear Envelope", - "Dupla membrana que separa o núcleo do citoplasma, perfurada por poros nucleares que controlam a troca de moléculas (RNA sai, proteínas regulatórias entram) entre os dois compartimentos."); -add("citologia", CEL, "cromatina", "Cromatina", "Chromatin", - "DNA associado a proteínas histonas, disperso no núcleo entre divisões celulares; as regiões mais condensadas (heterocromatina) aparecem mais escuras na micrografia."); -add("citologia", CEL, "citoplasma-citologia", "Citoplasma", "Cytoplasm", - "Conteúdo celular entre a membrana plasmática e o envoltório nuclear — citosol mais as organelas (mitocôndrias, retículo, ribossomos) que sustentam o metabolismo da célula."); - // --------------------------------------------------------------------- // HISTOLOGIA — Epitélio simples colunar (mucosa gástrica) // --------------------------------------------------------------------- diff --git a/data-pipeline/seed/systems.mjs b/data-pipeline/seed/systems.mjs index 7e8a685..bf9de57 100644 --- a/data-pipeline/seed/systems.mjs +++ b/data-pipeline/seed/systems.mjs @@ -13,10 +13,11 @@ export const SYSTEMS = [ { slug: "tegumentar", namePt: "Sistema Tegumentar", nameEn: "Integumentary System", colorHex: "#e0a96d", order: 9 }, { slug: "regioes", namePt: "Regiões Anatômicas do Corpo", nameEn: "Body Regions", colorHex: "#f472b6", order: 10 }, { slug: "referencias", namePt: "Planos e Linhas de Referência", nameEn: "Reference Planes and Lines", colorHex: "#a3e635", order: 11 }, - // Citologia/Histologia: sem malha 3D (mesma situação do tegumentar — não - // existe dataset aberto de malha 3D real derivada de microscopia pra - // célula/tecidos), então usam o mesmo padrão de diagrama 2D com hotspots - // sobre fotomicrografias REAIS (não ilustrações) — ver MicroscopyDiagram.tsx. + // Citologia: malha 3D REAL (segmentação FIB-SEM de uma célula HeLa + // inteira, Janelia OpenOrganelle/CellMap) — ver build-manifest-cell.mjs. + // Histologia: sem malha 3D (não existe dataset aberto de malha 3D real + // de tecido histológico), usa fotomicrografias REAIS 2D com hotspots + // — ver build-manifest-microscopy.mjs e MicroscopyDiagram.tsx. { slug: "citologia", namePt: "Citologia (A Célula)", nameEn: "Cytology (The Cell)", colorHex: "#22d3ee", order: 12 }, { slug: "histologia", namePt: "Histologia (Tecidos)", nameEn: "Histology (Tissues)", colorHex: "#fb923c", order: 13 }, ]; diff --git a/frontend/public/citologia/nucleo-celular-met.png b/frontend/public/citologia/nucleo-celular-met.png deleted file mode 100644 index c4c59cb..0000000 Binary files a/frontend/public/citologia/nucleo-celular-met.png and /dev/null differ diff --git a/frontend/src/components/MicroscopyDiagram.tsx b/frontend/src/components/MicroscopyDiagram.tsx index 745a38b..6dbba32 100644 --- a/frontend/src/components/MicroscopyDiagram.tsx +++ b/frontend/src/components/MicroscopyDiagram.tsx @@ -5,9 +5,9 @@ import { MICROSCOPY_CREDITS } from "../data/microscopyCredits.js"; import type { Structure } from "../lib/api.js"; // Generalização do SkinDiagram.tsx pra sistemas sem malha 3D que precisam -// de MAIS de uma imagem (citologia/histologia têm uma fotomicrografia real -// por célula/tecido, ao contrário do tegumentar, que é uma imagem só) — ver -// a decisão de usar fotos reais em vez de geometria procedural em +// de MAIS de uma imagem (histologia tem uma fotomicrografia real por +// tecido, ao contrário do tegumentar, que é uma imagem só) — ver a decisão +// de usar fotos reais em vez de geometria procedural em // data-pipeline/seed/build-manifest-microscopy.mjs. export function MicroscopyDiagram({ bySlug, diff --git a/frontend/src/components/viewer/Scene.tsx b/frontend/src/components/viewer/Scene.tsx index 3ac011a..d8f6773 100644 --- a/frontend/src/components/viewer/Scene.tsx +++ b/frontend/src/components/viewer/Scene.tsx @@ -8,8 +8,9 @@ import type { Structure } from "../../lib/api.js"; // Sistemas apresentados como diagrama 2D (fotomicrografia real com // hotspots) em vez de malha 3D — ver SkinDiagram.tsx e -// MicroscopyDiagram.tsx. -const NON_3D_SYSTEMS = new Set(["tegumentar", "citologia", "histologia"]); +// MicroscopyDiagram.tsx. Citologia usa malha 3D real (ver +// build-manifest-cell.mjs), então não entra aqui. +const NON_3D_SYSTEMS = new Set(["tegumentar", "histologia"]); // Normais de referência em anatomia: sagital separa esq./dir., coronal // (= frontal) separa ant./post., transversal separa sup./inf. diff --git a/frontend/src/data/microscopyCredits.ts b/frontend/src/data/microscopyCredits.ts index 6589d8d..0319e4e 100644 --- a/frontend/src/data/microscopyCredits.ts +++ b/frontend/src/data/microscopyCredits.ts @@ -4,8 +4,6 @@ // completa (com link de origem) em // data-pipeline/seed/build-manifest-microscopy.mjs. export const MICROSCOPY_CREDITS: Record = { - "/citologia/nucleo-celular-met.png": - "Micrografia eletrônica de transmissão real. Domínio público (Wikimedia Commons).", "/histologia/epitelio-simples-colunar.jpg": "Mucosa gástrica normal, H&E. Nephron, CC BY-SA 3.0 (Wikimedia Commons).", "/histologia/epitelio-estratificado-pavimentoso.jpg": diff --git a/frontend/src/data/microscopyHotspots.ts b/frontend/src/data/microscopyHotspots.ts index 33a9416..05eb65b 100644 --- a/frontend/src/data/microscopyHotspots.ts +++ b/frontend/src/data/microscopyHotspots.ts @@ -1,16 +1,11 @@ // Posição (em % da largura/altura de CADA imagem) de cada marcador -// clicável dos sistemas "citologia" e "histologia" — mesmo esquema do -// tegumentar (ver skinHotspots.ts), mas aqui um sistema pode ter VÁRIAS -// imagens (uma por lâmina/tecido), então a imagem de cada estrutura é o -// próprio structure.modelFile (ver data-pipeline/seed/build-manifest-microscopy.mjs) -// — este arquivo só guarda x/y dentro daquela imagem. +// clicável do sistema "histologia" — mesmo esquema do tegumentar (ver +// skinHotspots.ts), mas aqui o sistema tem VÁRIAS imagens (uma por lâmina/ +// tecido), então a imagem de cada estrutura é o próprio structure.modelFile +// (ver data-pipeline/seed/build-manifest-microscopy.mjs) — este arquivo só +// guarda x/y dentro daquela imagem. Citologia (a célula) não usa mais isso +// — virou um modelo 3D real, ver build-manifest-cell.mjs. export const MICROSCOPY_HOTSPOTS: Record = { - // Citologia — /citologia/nucleo-celular-met.png - "nucleo-citologia": { x: 46, y: 52 }, - "envoltorio-nuclear": { x: 70, y: 48 }, - cromatina: { x: 42, y: 60 }, - "citoplasma-citologia": { x: 14, y: 18 }, - // Histologia — /histologia/epitelio-simples-colunar.jpg "epitelio-superficie-gastrico": { x: 28, y: 9 }, "fovéola-gastrica": { x: 47, y: 16 }, diff --git a/frontend/src/routes/AtlasPage.tsx b/frontend/src/routes/AtlasPage.tsx index af44f47..a97686c 100644 --- a/frontend/src/routes/AtlasPage.tsx +++ b/frontend/src/routes/AtlasPage.tsx @@ -73,7 +73,6 @@ export default function AtlasPage() { -