novo modelo 3d
This commit is contained in:
1 parent
bc7e77b007
commit
08cb8c426f
11 files changed
+301
-256
No files matched your search
@@ -1,56 +0,0 @@
|
||||
// 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.`);
|
||||
@@ -1,126 +0,0 @@
|
||||
#!/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()
|
||||
@@ -17,4 +17,4 @@ writeFileSync(
|
||||
JSON.stringify({ structures }, null, 2),
|
||||
);
|
||||
|
||||
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).`);
|
||||
console.log(`manifest.json final: ${structures.length} estruturas (${real.structures.length} reais + ${tegumentar.structures.length} tegumentar + ${microscopy.structures.length} histologia + ${cell.structures.length} citologia).`);
|
||||
Reference in new issue
Block a user