From 369638bf982da3c635d381938342008b1653e498 Mon Sep 17 00:00:00 2001 From: Rodrigo Bertoldo de Sousa Martins Date: Fri, 11 Sep 2026 15:29:03 -0300 Subject: [PATCH] modelo 3d citologia --- .gitignore | 2 + data-pipeline/Dockerfile | 11 +- data-pipeline/package.json | 5 +- data-pipeline/scripts/build-cell-model.mjs | 56 ++++++++ .../scripts/fetch-cell-organelles.py | 126 ++++++++++++++++++ data-pipeline/scripts/merge-manifests.mjs | 5 +- data-pipeline/seed/build-manifest-cell.mjs | 78 +++++++++++ .../seed/build-manifest-microscopy.mjs | 41 ++---- data-pipeline/seed/systems.mjs | 9 +- .../public/citologia/nucleo-celular-met.png | Bin 33550 -> 0 bytes frontend/src/components/MicroscopyDiagram.tsx | 6 +- frontend/src/components/viewer/Scene.tsx | 5 +- frontend/src/data/microscopyCredits.ts | 2 - frontend/src/data/microscopyHotspots.ts | 17 +-- frontend/src/routes/AtlasPage.tsx | 1 - 15 files changed, 308 insertions(+), 56 deletions(-) create mode 100644 data-pipeline/scripts/build-cell-model.mjs create mode 100644 data-pipeline/scripts/fetch-cell-organelles.py create mode 100644 data-pipeline/seed/build-manifest-cell.mjs delete mode 100644 frontend/public/citologia/nucleo-celular-met.png 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 c4c59cb7deb3c7f83fcd796306464cc9b2a036eb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 33550 zcmV(@K-RyBP)Px#1ZP1_K>z@;j|==^1poj5AY({UO#lFTCIA3{ga82g0001h=l}q9FaQARU;qF* zm;eA5aGbhPJOBWKj7da6R2Uf~!Pk#v`C%aD-#NdNbGSLxy|;2#RabXU_w-B;XGji7 zks?J&7AdbRuMPOce%c2=_;DZn2N(+jey{=i!GK{HSYRQpD2j209CBh$$EvOzZ%*gj zlXI}_{XXRX39z}1{%JY0tK0K$ccc8@bIoI>*u9zl`G+8o76;1kL(o5nTf|xACG}_E zb%}U(+^uZY|MW#X`sST4=IyVl-*Ew37bh3|{#rbt4k`&V%Yb48Lsyfw(c!6H%gG;z z!_VtCU))q1x_NYf~AFRmcd8Z&;+~9;Ge#WVfFuWXE4xmKr=))x*7C_{Ojxq`E0OR zmiyY$yAW|rytyoJ(V$XmfTQOhr07pXMj!4>Kl-qS(0Xt0@#2%_y-(|!)}NUrZuogv zk>O3AhT20|DAgggb5sIzXT*p-jl=oC$GE_WlK|uSkxx-=X-pTmIaV8$*Z+A?+x=Hx zyf~h!PO3jDemu7J!~^W|E|G|+wKZGdU0aDVMbImQH%$RVLUd7D4ZL}VBJjUYooQ$h zdLrQ4JQ!Rf=xFtRZ{q&=T}Q%BKYsj|U*?ti;+JImqq{`BKG!d~GIve6w=KW)Q(_qP zh5M@k1x}xO+ESLHSFh%O6y8x5zdA)ttnLL!ar8OH(2Ru6Cc`&&={!f>oC*94$D+u- zqUd7j#|3+1C5$~E6K*cv-SNNr%U!z5ul&az|nV+2e-&&L+j2 z+nxN=`REym-sG}ySn}b{FVG`BotE$2->0{ipE|2>Sl^Yfw-m9Pola2fib}rzQ-H4x z>cLGa#b`(hqaK?ZEz7H&&%l!Keakxw@B2(qD>LAl)e4%0tIY zSU2Bn^O${M-z|d4$h&>%&MgMagGcnm`D_jOx9%WWKgZs|k+aAm6g@SYURWGsOgHaI z_ivviO&Q`gQ9CU3k)k#&HXFfJa#49ui?T@jlrRc^=`mk?_LG@&cbff3OTfajqP}aj zZ2|RGw+^RGam4WYJQ38dbzuwrVhGKu(scSRnQ@J>xJ&%b^DUAf%=79ht3b_2e84gg z>*xVqJ{dCj146^9-lSc>j3Q9Om<0!iBd={~-+ceKnS3i7)#$8NTWXB`3T%C# z#^?M&a#xP`)@n+rQGQrNIv*Cz?TT6qdv%mvXlpw6@Xh)9ntm%V67QpF7$IDD17p~s z`#LM!?@gQ}HDz&jd2cP~VlN$U>yu#aDHWC->df*1sw|n>+-C=f65#Je7wD|-Ki&P? zEp2{YIDIhs4B$Tn@ST%&`9-=`Qh#~PmCs*{wu@g`oZKd&3gI8EWHlM95`OTJiR?6c z*W<{mcWsSZILX3FN0zrD(KG$kz!g4xn9PUg%d^4F;_CeM7jNDi{`_D;rpGU}lXG-j zZzBh7`Iu}4Nb5VRdKq`JI(_GEceszRox-w}-Dmv=zx>@dHbs7Rz&KAF?NZX^SPehY<2!1nkS0zrJnbcz z;1OOp9MdFr5@draF*xg|Ung%%(u8fMbg|s3Xl|ZVI{B)4=4SWKf(a9K8A7!otrhgA zRh3Lzl?OM08${FMChnV-6HV2AXoik@#?<&{J<6QSmF8+s;rA||{m1W$gD71>_+iI5 z`_6J#oRVBzXzp5T*W5QQpUrkV-cnLZ5UwA}6@jc4wk3xyZLP0QFV=5gM2xg|r!dMB zW9lr_>(0A;>Gc>Xc%xXumH2uTEh1=Hbc;ckV;vF&o7KO5VPy~%8feg5*!w)6^@^a`^eRe+iZX8qiONs)d3o%3J%-tnv7oGzDaHeL*Mtibw`a;FZ?Jl9#8PL|Wl zVbYsZ`s`X*=#$EFF$d;_Z*cVGLau|!{37Nww3W^lX@S?^1dkoxi?StMNYW(UTyt?C zhs@gX^!SA@G`7Su5mqv5w2}6T)63mmDS=N2d-u0<@!3r=%jxOo-n1J0E<%;XKW14s zI?*;7@7dwU?uG3v*QJvaq4x^veR{veUZ)zR3nu^x4b?ZD>@X42T?`z?`?r@625KHD!=79{2 z950Syh2qIL@&W}zaC>#Z0%u$H9^dcBQ z6ae5PsGm4NqKzXT1sNK#I1oXYW-0VzNX21Dt9e8M%Cq5L9+6)n-!l)%aku__*${)Y z{wQtw*7t}UrI{+me*AnE+r&CAQU<$!Ni+nfOt@67#y&H;+ae-zmFK@-ps@CcuI3o! zf@lbts3&4LZ>8o9cw@{OK&i;8%?%-;EN`0=>>I5cyNzcY1VeNLHw z9sQP?zt?$<+ksF&Q{Ce1>LKdx?!Q<263oBa?qA#^_3Flh{0+Q%>j&uo$O=(t7WDPD z&C5?eO+r)eqiZdgt*)aPZs({I6hsv*h8~3_TmeZSf=5M^RS-0pI3Au{94)TitV`I@ z*GSevSLn<6>e}&HY;uG)U{7iymg0Akv76DD*=I??&EI?2DJxgCt06iqE-uxr^Hbyp zwdD9#Sfh;;)9;Q>JFwe&zOnTSjlsVAbzcABs(aFGSY>d?qS%mE#FL0q2(H-%S&O4Cs+zGJU^jJWCc(UI8?Pl?98ZUqrwxl ze>5>Lq--SYV*ckVuflQ7@=iga*$Zb-emplj@XrHxm%U;xA0K6$eTPDOz%_n-M@Jrp zA3goKaEg5Y+1ZD~{{V__esyxP+2OsjDcl!Ix-}oLPygK?mer+qMmKRnWW8!Z8W?v= zVm)3LV$Gt>Qo>^70cVG)12Y@RvJ_Kd52FFY`Jyl3{_v&oPARPm7KH}o zO2^1T51dz;r|L?&zJpT+{`Cv76pk*PR_zx~LF`X%9~He5wJYSm55P~@1JQwx2H&ep zZudxJSrWbxkJCID96~I3rydT+N-y}}W3BOWB;b5rCyAS7J1wdY+_3>Xll$cL+6H{H zuxON?t6NG*BU&2|xHr`I7n58E*!*!D~t?L9uc=HNH|+uX0E z#6`MZv#b&YSCl_P&woye1Rz%*eEtaoznDLYhJU?%z@VnVtem_OK2KC6@Q^~}=vU2e z4I4r9LN9EEee7=fPcPVp=vJ01-|T?Ma!TfHyP8obNUs2^$W6*q)aC%yjPjr`gv75Alz*ENg}$1 z&JwMIxq@T!5GdDJa-HeH5+UPKmhlCel?an$ATc*%VLT%xLb%_!*#4V?3$Wq8s+88o zuiTpJ-7{|eTRyyU`}p^{gF#2(yRUA8IwX7K&bE=E-bQ)9_qus6{Sy_h!2j9*>P0B> z7ek_b`r^s(A7*uSeT^N(O!Ax2wmwUGQ3513u{qg}d=hnq>ii$6zfgZYz)o-3Y7yCN?I<;%Vi2|;e|%n)|dxhl>dw5I=1 z;66C|O)*%<5^!|jui4Bxn(}upydTh+O1I8cJV%{(t52H;fVKl|8q;><5k)j>Cx-S; z;nyP~9*((2G}CP`pn#uXFkq`1e4OFW*RFJjLSl4q?S-%3+OL=(u(k;T%Bo*}H_jC#T@6@)ydE%xWQ^^+-tXwjuH2iO zl(`q)OwrKRFQPklCZE24L7qn6UzR!e!`E3U8y(E0u_v#tmYK$So#fwKljBSD&qinY zkhXoCb~8yu9ZRYb%G%`m!K;rR=+C1f7shil>Eg4r=$yoIE~nng+;++`Pv{;2)LK1H zkk!V`n-|h{XLhl^Z4g#k@29L9xLKu^lG4ErGO z6#MHtAn>w-Nz;wy`$x2vq5#jNJaPRzL^CI^r@gB$;ZG00lnX)NTC4z56S=p2%GFnz z`L(E^hJy*|J&8(2XuL$=jZNjaT2i$?+V8UyB_+mq7$T`EAyN+OB{IfwZW(d7J4tym zh%48)LY;vcA3|_*Go$MD*vB!Em);r;4wJGr9eC_FU&i#i#77MBu6?uj`bWeubJImK zxIKaBvzb?wZ2xUs>Y`iMUuohU_4KoCu>J;(J(E;0J`3z5jBUJ$h7 zN=yiels8JP2Awn+2Vr+z-0+N*#5~P^)E7< z3SXbAkeeUH-wbXY#{*PIsv%99^pktN_?2?{8@iBtA|I)ksK`6?_U`3j=TRw1uoz~9 zKa1y=0(-+dUL~f2dPwp%h)#!9?`{DQXp)Am>!hjFnmCFaW+j@>)hc^CSWpe)Ji{Xs zyJ>9i?F9&i_|-@G^AGObSe#*?f6ru3jMqIeYMq^=k5ihso?S_i^E$3-I4= z;HT}J$DUjb-+5=8d?;l3ZIFWduQm%K`7L$ON3q261OY{^q#wTfR1mJyk9fkdhyu(p zFIPLF2(Glk8evZc)+~wVtXNw|UaDbi$8?C&Z3vjmNT?BcPbIOFRJbx_dWV2@TrrOu z8-VM&{zmLyMGeV~Uyf0C0q>y;F`s!}y>cI4-^XbGoonidapDeSzgb=+1-J&Rgd=zR}&J+mbr@BdSyo- z)Z3?IKac}JWollhcoDJ!2BK*cgGdh)WPJ4}bU4oWoa{nU@NN*?Om{;HrCm9vb1p^! zKbdb;Qx-vSpIN#>WTn!vTv!?e%=DQmUJ2{IWe>*JePbSN*cU>4dMl2S)Ho!863$bC zpG~tGyqVsY&kwhiY60^Iu0F{2AaXRW2h5Hem&->-hM#)XHjnw^6)RQz)MBV}=q3>q zt0dcYg1Ye;auwC^kc6~Nk}&d}H5*SOtB)r73^AFI-$L*#WdwkFl&>XH*R9=u(_3ae z(*Q+d3XDFa7%pCzz`H4?Bz`0LXoFY0sX$33T?@Ih*Mb`}5(sT11On2FJn+wH0Zm#+ zv^J!H(!3fG^y_JLk3QuyiZ6azD0bVrj6w326$3ihW}l8Cz57i?@?H~Ng1Jez-<{8( zcMDiPMR6 zx5TSYKG$<|!jHWy3>G{d;3^urVvdJ5Cb}LEmbhoOmj>C1bd3-%^n3PAu_R9C3*1+D z6Ov5RV>GV#)?gY|83)~~9gHep3z*#+y`iGNugt&vFx^;whA!aa?t5H$aQ-GU`Igg( z&-1awow3{>xI%V#;a-0Xb|3F=vf1xOGLrRXVVlL`0{ED)eVkJ^T!$uvcOioNwiEeYky)tN-L@TUv!)$j%kE0YqR!PEJE=DUxn>{H8T(En|3&cU}~ zn%qX2nAvl#jd;~XSNZVt(1l&@Vax=Abg1hiGkco{a&dOP~hmVYqqrD>wp=P$|P`IFsk_dLD9lZ;e+ z=to=?8*H{zufMdJ$X|}j;^c;cqUET=}5zPv1FRRjC@!B8}jb!5y#8Y$XBx46!NNOm#)#k6IT{NIA zl!)t#Q3PfPHJlN_^_(mSi`DH*n68SCXdzlU7;{Ps9JU)i91f8%r&Ms~U;xbV;8+%+KP?NH4@rf|B2 zJ;CQusdsWxN1eN(`(}bR<8N&q-JGAyH&;HDC z%rI5Xs}!Hk3T4Qt+meqbL|?-k!Xjg)=`V~+lI!n4T&@k7)IT5DWx5ctd_%^9;t66O$*r>=Ya zY8sXc0zEyXRTM>ARIn%_1AldVv|lC9EBlk-W^Dm6Zyq6`flW>l)nJ?AJU;701sB<0 zoYR{|F2mrY+_^6tq@g&<6b;83!P(9A!M&oTHXLqj#7;mhchU2sv#oiyM;5CMnee-! z;mquE6yggc5bg*iD>I^mC^4u>B_eR;I5HS$T-j1cA?}fvkr5vl8G;9vg;2}+Fmxjp zL^D1XNbHkdALX*HV$L%q^bsFM6HKDbp8NQs|9&0~N#!D64ZJNZVBEO9L?AEwCFRwK z74Kd2)_C7{-`3c83IChTmhV{;Lku0$D}8VJi+SO4&RjFhAZs=n#fQ5!f4+X@B|CLJ}je{aqgUg%J!>3=96tGEf9JOK_Q~It= zcO{US7ZrQ1?ovfD2cKWaLHa6hw=oH!uHij+8(tc8B7hm12F%uu_T_ATgHfbxOeSjM zVht~?mHm<;kOoF&G{4MKksw)wsiL<@5V9yy_qAdG`UW%aHHrpFr$Lf3Y?3bH{hu#r zv^E48qR_@{0!@^)q*?AEglX1Pby_3ZT6<(L8TsWpMF03=6VZ`i{0gp7pFc&yxizKy z)o*`Jn^)+}Qde!EG`=#Eu?)6B0Xa8tg!Z}-2`PMP&Q80HLwwcr7)$Wu*e>F6bgc<} zjiG3NN^~y$4q+fsRpjtl$zMi@2=a5L5wJ|W_BL_Rau;)~HJRvoY0tB*sm1ta>gJ`? zhBF!|Y0bK8V_c?w%F$u~H!L|0Jf=DM;JL#v2VTr=Y*5lJjDJ>v}D^Tj< zLc3IcbPu_@ysM#Wfo-euiuOy;M2{M^ekH;Z-7kP^y6kM|n zK@{ClagkuCXc7s%w#qCi9B;a%B2^YCSXVAw+ZIIAiXvL3XbX})g8RJk`K>TQrsD|9 zTb-5Y`eRgGxhv%ig@Fu^5tErd?a#CJP2ND|6dxBhQn3ual5p8|lSjRNb(&QhSE>qo ziutYRjEDc--#y^h#~b>qs}XK-_ha4UhVeV%rHapAm4?|{X(4@MVwhZ}1aE}i&hm0L zAoJle=})-GG#O^4bm4WqCKIdpGUr!of$wBC#iEX9K$~VTIOTJ%Rx;)QVQ`CzD|tGv z7t_c=v1l+I1D=-Zyy58}!pr$|iOez76+=8pnFut^>(*@p1?s3VX^<#1{5x$cpL<2L zziL8p^OTETH^<(i>%$`aA4`*i^Y&zc2l`UwS((j8M9x-*Zjk1&5OP6^MnTI(R-O4} zcEe8dOK+0m`$zfWz1$_18W7zzsQ*5MVsn0bp|BSSbbXvJYh>-R*W~g}Pw_GIz;GL{erETTr~TSWB!oRD~&a%ZnFmxdym8O%Xn^ z6wt&kIgaJ+M8yf|U^so-ovjv^HfeX(&sg5OyxX!0%@IGFt;ulrd0}Oxe>ea9!NK<1 z{ihsm?i-6o@GJNo*B71AYHU`|1A?HFx1AfD@b_o~?c*i#=w5o@jZZpCSRKuqq8*Y# zuFEa@QPBv=4`fvKta}urfPrbJ=#xszr%uZ8+l&0H)Vlkc^$D@YbKe>a3zGkyu_(~k zdkU;le=q-R+4{9K0^hwHYpC|OKb+l9n1o%w0B`MesIQK$U9p0PO0M0y>JLYT!-@gnmLjE5 zqLVY~J!a)zBaieOhnkBlJDl-sXoLwl@l|*wJ0rp8!O3-TbX!YYVtRy@XV!2zK#+9k598@@#mjV1$sdilBh_g{d==1TSE)YzQ9EC2gq|7%WJk!WJ=>}kVii1l`1R)oK&(Z zRUWd5w>oqrYRSx2Brc=oRU}!Kty&X~DfrOWG^LuiR-oSoe_OKZFJ=$I@Q}THbl*=l zY0!@OzmmoN!NYS5vXatCOHXptLBr@t@1~O+Ya(Zrn>UU|!Np=!#&l$)Nx~|nE$;iz z3l;gSfhS7mPvfl(!AgUS4ic`$P#IxdWOzb=P~KaW*%K_92Ane2rYub9vVkW$HfTkc zY}F&AEZVmQ8VeS>19R$f@wTfm z^D-&nN{wC-7{?~$Xk`OQ66=~MmD1VBn6f0*M9L+1U?h;XOeZvFBei_LDou_|J0*w^ z<e~@W zO(rw|S)}TFDY}6Gi=1|Qp!ccBMpL8v>a+AQecTEc^#fb)(3rftRYd+*w*^dYa){bN23C;+1gnR%Pf( z?7eODvuZNjErujpjF_Q_CTFRzT ze@(=lffKL=1W9r$NGT4&r8%mlna@XVnGPV2P**XB$75)-yDM@&jhrb$al%OF&6KBB zCU*0M2zse9eaO68E^#ZY^f9`P`@LOk&`{_`39fM81~oW3c$tzSU4`beE9#E{-z0M={s$VODHrXC%KHyd?0$uir$11{(dW}>y) z3siPu66BR7TM4}m1y>phEnp_3ihRL77y=H%HHxf0Ne+dSOkuq_)ba#xzuyV8iJ!xa zW+K~cfnM0e*CxtMokYF?W7kq~>AsL3z%uE`x4$pk|?t&7C`~AwZm&sOv1Q@SyAK zth7xA3KCDjx9IT;ubZNk;$7KOv@^0mO8GS{^Q%lpOrq%O;Kp9q6}{(l={Od%Nl;bm z=i`Lg72|xdeQy>ax<2KLhZ7Og-L$AUE1!&MoZ9-5cso0i?h50)UZTH~<^YTihf>jdEv}3of%T;T_q?a)J*ax{XsQnHeHXlHM~esRXlxVGQwZTpQuS61jHCnB2%iB zt~T4)Pw(1bc1k?UG3}`JcTEJn2^bX?={9>r01ZNq$QQckGuFVpjmnWgq1|n4y0(pnW#vMo(~n^<*1t!3btn?3;>RK8}fK11^Os}`4L7q ztPSr5HU}BVmk|4Mks6Q`&``@H4B_nXECpbFmMLjz1DRk>6mQ*!CK<-fx^cr)fmq&K ziE%C`nmm7tOjyW7!sd4SjHWNE5BkFyA_E5AJuLFS6W0unVWM5|-XOx#)MpskY-;!^(*J*%@BG3AnrI&o8n@BRHh=O3tz zO8W8-cTSFsToQY~w;NxVV8u`MNG@}ZKv~ZvPxAeejgch} zWf5YF%5ZuQS!8eBlX1R!1V!2}mk@7b?5Bs;M(g(VNG~vlc#OFhnp>RNR20D_yOJBx zl}ovRj-X|#Yzn6I&<2_458Yd{Kc;=JMnV5eIXS-6(ghTtBjv*tBxoTojJ-0wPdLyT zWn!^5fOL@fYUi^TMWrEX!*w$=0ZpI-NRbxOD2a4X0LZM@><( zc7{})u3W{;RHhM{AQj9%bWXq?TG1o8ZYHB^aMZN_OonLFx6 zNfL&~vvUhc*7xE6a{t@aC?qbdcu-u8l45!J8TLG>=Qze=MKLJ&q~FIav#StZ-{sjz ziAmF@jB{5kD`IhKLd9Y{Ik8a%oC zH}^{7N|Et;um42(slvXa!heUT+Trz^=k`AP=F^|;Pt8vT*5o_toKj4Slw|^KpPvb= zro!0j3yPDA6CKZV0*8hShP^9-wVefnu58qe)T!gD{bDpHFTUWA)QVSbFQ|YQuM-9J z!d}nxqNL!*uB)3?gk>4X(8v!^CCen1DOF_FgO*o|UK>%Rv)avX3-u(euZ>J;-5jKf zfLveHiJUIxH#M>P9xnI?OyErnRTP9O`w)dc`2Ej+c4B!NIHF4SJ1ac*Y4aQq=EJ{e zAUHAh$jLDA5~rf|k0i#OAc*LytuDHB!nDgRb;ZlarYi{H3A#j-A!y~d8KyX4uHr9( za!B|cAIPD{mtBZYP$|wWhv3{O$&v&GXvW|qiQ#n^%_l|0GE$i&KnceS+ZD6uFJGY@ zR#maNA0->HXE2TGA}$c^%|h5=ZvU?nob|+0>K(-u-EbX)=r6uf*D8YNn}=A-zPe)^ zu}>pQC@MIb+#chl7dLV;PDLL>9WFC6Rv=U;`dBd?VQ7X114cDCgr~oBuJY!P)unqX zBU}#Vi`a&Hi@zbi_!#3O}K*2;$XZa5s@{P zv+${3V+UBV%uqS-oHFBkC=;h}PP=rI%n1JaGi74%9MwtvP^Run1~gh_?#-W`&Tnpk z*KNW6N6w)oN7(i3;^+@!;z{}QWp{sBFcK#vR6L0A7>CH*xl3qLW_Ia<-&voV#W@#Y zGl@)ETtwh$1_ac+E2;v1ftWEqCj zQqIi!Q1ni`82T%Mdux#SJR~>CQ=7+CW|;tr*e(Z3e*1sJ4}ASmz1|j;hfxhY>%)YW zlax^x`Bw3w2a;kBJxz*nydqr9Np2m!xox!R>s+6Q2_P+88OkYyBNN~Dht2!MMw~jO zRVADyd_m5WjM*xiCM~K;8p~#!km7*0hheJo)9VOxy(F0}0RS0W;X}EkTLH>Po(DLd zF8Q&hGfGyXpdazInGtu}pr3Ft*I(ds)38;w5CSJy+oITJJkJ?0&?J0Xi_+EJb-o*! z6ugTmI7wru^r3M0@~!TRkKeka3BoS$M^T+i^c4K$PC8SJ9o{zuVXlA_DyL!Di>H3O z_;hb4zq*PF<+Rns0=J4{$h>$_)j9w#Bnyfm=lIC*8D~nhwFw9nB8ejKFG!xHX=r&N zf@VpG7)%WX3aV>MhfcgOsjMT*DG4UGbD_i;US=-v<6viL+s~9CqX&!c!V9FEO@RZ@Al7Nk0T>J8dt+Cd%?%HG;xIqT~MeZyZ z+ROt4BxoiUNN2KiVBm3w0~Xciz48a?(CRk}I(kM7#i$D)m672;;(8(4%mrgyTjw znHQweJ?2a(k`(Th*q0`*-Ua$;fIGx?!RXPFH?{3vA1HM^_oiv?X(_7tmJQJuvw;`m z8NgwTQzSA92oz*thLd<2L14>yv0tZwyf#J>fV0it*SMVN9Q$>GH-u1 zSlEw}hwL@yRa%w918L*>_z1kW4)ff(rkQ$ucKmV>+5C0ay#VbQ%HHYnrS`q#`23zW z0>RCL#!M2O9wQT5)${-r+nheT49Bmy%{6C~=UCw~nb4e=5)6HLVq$A$#5^4drC2PA zd4)-XJVwGKOIV)rDgCzC=z_0)m;$mhlXPXS;E8AB9y7WOQEVruxM-HYyFVs*PblAl*@Ppwa8qefi*!IdO@0Aco8Y~ zuzYTcfLv$on6QhLkT+T4_;Qrlso`T@NXiv>8zmV?>>PqH$}l1iA&KJ{mIPVs`c4Sg z%qO9y8Ot#iCv`-Xz`_d$73OfY~=j%!V@mnuN67C52qm zQ9Qb;b;|Sd9_$lXs4LCn1j1Cz7IWV=19!BT;b{zw3_+JOl1(BUW*JKxM?LgP`7Nr2RK>0P;jk=*akO^TXQAtv2oySOncX z^x%)n8=rZgi|Dg8R!Q4Q7! zmA7{NuF#|y5OlFf`%A2M#OH+H?^`5H=v2@33}RT~p84i7f zPV5Y#Nk&(YRN>47U|519Q(OuOiCgE> z%usP!YYHBS}{gc@gWI30s1Fw!LQ4j5cZGfe(p+UqubZ*XFe9fh>;#%+Hb_$ASc8 z0FY>cut)^6jHEFVfrvcIFcRV!5QMd_b0JPj6Z$@;C~u6eEaYK-*bez6q5?b(qoh!@ z=Kg|gvyyEu*KYu2mtfxN!KT`uYF6jgIg766fX$)2d>+O2_4#T5#>GM2dL(MXWkNuB|K zgD}f706>y}%yzSXfdCB4C>%@3+y_~W;c2p5<04LU%KenaczicUa8H&n0<~f^3}8wZ zks=Xiv(aR;c$_AU<#pa=V9mBQ+XuE41o8Uxi^RbJE-U*fBA%sVBt9G16N>xjS9&83 zba|EkG`+NNuUFg|9;E=~*6>p!w)4x1l=ii#hTQzYomU8IXl{~?d8e>2eT23H3#Ul{ zvkZVN1OGof0|3v!|0hdurJSbFz+sY*QjluJ2c&HX>-Mor4dQp@@#Ld?Hh!y0@He>_ zl6ppy56nd?ouY05@kYp#wNbFb!JkSZLS$1XHu?|uP0OFr<^3e3A)5~AD#?IAS&?+t z-K}XkAdBin+|*cliPev8BZ(`k;Uo&uWWMe_HJ5bz@)cHxl}eI#n`m}wg2L2NqW*GoD$iRP+mk}LHc#@=&*u{6GDh7EFPljnZ2UTUTD08zqT821JlA%7g zsEiU4Z80HeX2W!`UTv3tjch^pva}OSy$D3i-p;e(NMgDiaix_lzI@Sm~Vi5o!gW3Pf6Pz?jk{t$Oe*T{CyL))-8IgNtWo2b`Rd;oD7toE3AOR8_ zf}&q}1*xmdaJx`|z8x%24qZ|9%8QS~II(rO z$&Wg|9g?G;TkAndZ=(ENRBrBlnDsb7#P9EUm!Zcrq_Jls=(q1GfOQYzR6aA6GJ2>O zh3a!Ko9{g6jZ65&g3fD`qiA-#8;{!sHsW1JF|#oV2`i{%eI2XyS#%{9a*DDf^f3iU z&@>KUP_P9BoB=UF0z(1dK!ROj)&wug9Ro4(>Z&&-;C3b6W;?TzMT5D#jB~5Hn1#rm zh{9g&)%jM1o*&YP?cdvP@l8HPUw`9+=`zRRgYPTCs5CZX$me^abz^@8}c+wc}H0wQ@kTgToZO; zqf+H3mHYfh1h{?v_Q)f*%j`{@uSaMupR~v4DShvUx3))*%Ga8u-DQ%Ozc>HFudmlf zJ6uJLAKfEEf;Y@bn?_L30vt0(xg>Dgw;*^LHn2vdHMQmTmj0X@=YsU|l^i7MF=usy zgPV(`&d?zO^ee1HBLDydqJ*PNf&c&(1401-B#a1~KoI9JljQoXO7t|BWqt}~TUNEk zGu0a)uTkx!@GlK}HIUin{)g$zUKRQMdf?GH+I5uK)4ueU?RvGW#>w;SiZ zjcCN0XHpdHJzgSOVuEW1%Y9CZqWkl`>v% ztG&nL$v#}9Nx~*;o`r-^nhJIZ0H(HprXtr!V-@j(f8W*zuQ`pMZ&EHQZ;t#)bnxCmkHT_tc+Myb4r@>wHfb*MJ2} z1~5w?g^i7$u}?~_M6+y_0Tz%9{C_wASU@sBz*j`D0ze}%0GZ{i{ThgES&(n?9Q8^` zP}>5$p<9tqQpPNleNes(JLe_U38Qn%GEd)d#zGF{FD$+mpue&=%DrDWo7x}U-~Hm; zE$&wM+t_k~meaYOPu;5?%CEvkKTQ>Di+ma?H=w9`TGfc*JF^v30uT-Bg!uOJbn3;+>~;rm+yqp1-}lnJv+u*}3pk&%EEh z&0qYv;0xC1QM6@z_N-9r;QHr7A*q?ZFzL=>kFkT0U)9Cek>YdOls!WC;96W=YFwi? zr@4=d7Mm(_JnF8II0;^znP0D|Uaq96BT_^&K@wO6!Vn<}IF==phg9Y;!&1QKv{At_ zVR#`=Qd2UQQB?~Wb}s8_%J*l25WEh}YPkrT3Nt9%jQFWscdM7Om^9$Q9c7)!>>@}4 z-F`)^(=bzq#hh!L<=T zNAC#cFFqCbj>b=t7gzky;slc~YV5exA4;1Bo^7S3DvL5rl-s zgir>Om>Ld@ys)Y)T_jaK;PG|l2S%|$Evo<A`H(T;znq0Ijy>4`0J?JoZ+Y1w=6RkO2a|_E$!=PXmDz_B9c@-=SiV2lC z2r)}h!g2&~fM$SDIq(532Pp?=5s}1KrF6z7F2R*$zV@t{)&!@WUh(S1c%_nqm3Uaa z+(e&B5+jqd_?_)G`fcui9~&R+{b+E9>s}V?4KumfGZg5al2Xuwc*EWt3s25~tJvvlyhn76)<(j#jC>`gNyp&rgFO zQARs-*60UM!M= zhm6uzdsNRx6dU_)R%g6-t6FK9GiEJcr?)f|wEaOfe>n;^_<8GXZA?pe2@@}qFu^OA zgGfXvUXhqZBZdM@K}Hav3&pd5 z!k(fDD~`Fp?QOXQvgSXmE7@kS-5CcalU><^WVu<5-0rSf}CG)l%}gFg?~Iz@}PbY<>F%;Jjk zN4zZJXhqD(2L;Aa7zDl|EEiae38fSOLILF{_zI;600L}A;R`KyBhFxpa3XD)?HU76 zIpEwrc~{8oK7%WC8D>Izvm~tip7!#ONB`-=KiMl)Xn_9Pzi_vYLptS78t!t8w&2Fi z05?F$zhE@JxwXfJ&l6pH%x)&{n)Om^HnStqdsYlamFh>2Z_!L38dnRM>UdI%bUprr zyAhpd7`;MBw^;F}CDzyMbS@#=OE{LoB;+Wi#4i9K;QuEuNMMj4SvG~hrHT}h7yvE? zlWakY(Tjximg1LRUu$-t=28aY1ZCOvY z__~==RbzIKkpm{p+DH!rq(6|dQNoBkhCJrvJVhMjDd8yiC!DNU*is}!+tg$yJdJVY z6CAmMub0D1vajD9`17*6wQtRviBeiDGZ`-MUh}Rny?Svmey|?H=JWZRUxf<#-YcV` zFF%8JEq$H(DKT0jw5T#LyFCPz&hw)>SGfpB|8Pb*zP!GU2M4$HcC-5QBjRMV-#76~ zVjL~!{ZA^b$*U@RAT&zmIAa%!VCox~t~@s~6ht8=0;@odCq$HB3?vDIuLznH1CBr* zxMamClwiJU$P$;WlH z8NSscXFa|mANVRc-2N6Q^ur}lwt~M+^^^1(qVh7JgS+k5@n+*(``Y0<4hbe-zN#@+s{^a%F*mr{}l5o z8)4N5(@NxCp2o>4-WA7~MB~k4eP!l`!eN`A=&~Y>#Ts`WOMq%|%`#&H z_sTl=qXT}>YRpLECi-7y>iOnp-x7}FL6(lw#>`kJXT#bjLr#~DK=+5%=KLRf--G|i zcKg>^-8S7+une+#d0w%@1tTc!Y71Pfol4j*DW%!ov?r_={1QI-^@7QoR28wR!5H#Z za3!pRFy??Nz)nKftDFJ=B#BaN*4M+5NMlDz7(K!g=Luz?qFPN?h8$1;ELRd=^y3*L_-gTzo?f!0@|!Ec)>L#nC&bZ~OV=)wn&o8aLt2Za5BErs6L&&?hC{hg*l#uj}!No%mH6 z6*ii!lVq!t35i+8)L(m15oSK8fE=|HLGfOjoi5CZK_x5xJpxJzOB2T_sIKGDdt1ry^wPa3kJ70s(>{50)6M5VFWJah0EN?{C0!+kMxp?tER;$F$>|DF@D(m&S(>k~Xc>z|0w)WY1Q27v=CeX_ zL~hYt7VP3cARBi3E#oEEdVyFHjkj(+-%`8ldHw$9^6gG?q>NiEH4_0nQD%8ke{~*b zuFTHp-b`>g93)a-0j3&!C_kSoS}lN4q2?oc_~E0EiT)m$n@9Sm{EhRDaFow))KSP% z4UCM^fUmDsOPz3Xl%{d)SV0V8z$!5j0m&o~Q31#z$-r-{Vio`rJ8VZyGZy>?D-f}) z6_Y@joO3PzdR;EF>lO)Hg3Kp_k5*b#f^pc~H?YWy2~iJr^j;}rHTy=`=9J25H!g*f z<+*Ne@^LQkYfhNknoQL!NC+U9U_vOOK&f)k%%c>1g#r}k6eO^?z)TXyI7|>5GE&5i zB;YQ)FV3yKO5Tr@5DVE@Wd|%dN{?%gAeQ39Ci^_h{ThxtC9+hnLAL}=e$M?0-jgp}0 ztNPB_H88&!?#Ly9+U^^(neZu3uvB0K3A7;2P^O&JgX=M+;Ga-HB_L`{K!b^&1Eg3q z&nRV3xu2wu0QWWK5=nS%6#iKNsH59>3OUo+S7N@tBPm{8o zlII@P*f?ioS5j9^eOOmLm7(lF5c$3vRW#&``S0FVAN3>2$nCP#ebiEOrM0O0BPJ0) znG4S8#E!KrT_;ylSM_v#KVI{AK~-KsSR)Zj2?d;$&Bp}%6OIB75yGPd#}bBNc>ob& zIUq^5xS1&n&PLS_#4@xuysI(b@Q6)W5)`m86mNd1zdiF>QdMBv^Z!Rf9}1;E`si%5 zNhe!jK967{bpzNWm(#jGC8b1FwuJJlzDU(qFInb_evtiRmlxmHDmyBE@_G1f1`pR8 zJGb@Cc+yC(pbaaT@AzibWAmXdF?_6I!n;f^Fru_bIKrXY?z0YL5TXD8O=Ji~8KN9X zR=KP|3P}W@t+wg}m?&jwIZo^GnI|nf;W`}WI%lK-BC_1O#XS#qCSCn~Vxd6uef0f{ zf7Sf>{duIs*6Vd($+^Rr>UzA-^_gu|PnayS@-Wa#VaztY!|07X%Qx-t!ex77vbnpc ztD5#^wfr<}H`D0$_On^5*x4r%XNmovmk-)OI9g?yz#B>mxP+mUB4IM)VT{zA0#4NI zGUXG>fP@M>q$DA{NL|^eIxa6Gwp-=T<+g!CPH~v>&W7kt5a(-*uv#6|jniJzD%ZB& zTy58dA^PJ-f8c+1=L~5#rcaccOLFcF_WP%!dR?T>}*sHU&ttI z1nc-Z+mY-wc=Pe_Dvy<~FZ(qlh`PM0@|T|%=#lcyp`AtJR^;b|aO@%`d<*~*0H6RX z4h$vUwxLXL0uafl1Tc&F7)!LEYoo%Mkx+)x1RvL5dzV=w%gw%k=1S^IG}Sz&g-rBm zaQkLD|6HoOV+J|zpFDi?olotl4?=LdZRgoKyKuB%1Mc0>yc?maj}Ke%RLdBye;}-7 zcX`>90j?iBnM3+!$f8Si!HScbKhruNTe(+D8XMuDGb!(w>1%xJ)%&Ec`wZ^|82T7! z3M7c)P~!cB6NO?v#B79^3{g-J9s;095~WH)rqX7{Tt7kXNjv>TrA}3FGdNdy*>II2 z>+BWU`_(hKF&Eos%k{VE!|?lT@LOuEr$Ex)*rZjzHBm*vCuOCxY7X5>)~#geX8I_w zSaB}y@(6Zg%c{0^ja}SgLr>tw^Qp2|T#5NvLJj(p_FA@!ByXUXs+8M8{wxUs$lJ>> zPFMl}&`c+~AOysK05%ZW002J$83zCdM2AfQn#X1cG)Xq0y*^Lso7(Hb6k3eTGTe>W zWX7UB1ELMQ>vBTne<4PtVt5{HSpejVkSIPOrV#U?O5`XOhjSG zdkbBikJ3HijYy}k6y82I2}BF_WuckT{^n%Er2?;Rjrwn?p?)N(9oNwg{Ixv%BH>nR ziDJ1@j3|?Gf+QGulv5}OI72DL38fT}Op+-BfXLWpP^cV?sKTA>3`ij~&03~1XHOX> z^$fudvEV+PQJo3?u3ibfAF`?mly5o=}GwV=o49=XbE=jAGOuP z<%{n)c{E1W3Q3VvX$2QIeLjmeAuLskY473VMhLTwRx>&3(Ee+kM=@^>`LD%FmO&ZOu(=h+ zR=;X+HF)Y*^t{hW%j=e&mfcQ*M3I*`z>%CuC?O2x5CuTua4cvCr7%jFFoZOi7d#iP zBDuNEL_V{eV%9t38tJfP>{(GJ+Iz<91rZOQ*S@tKFmHLm-~VXpwKhXxpS|YrPR~-G zT&!7nR_iT>Vx%cJJ;I}TlsP&Y3!31c-_=H|e3#-YF{-n@yi)3pkM{f$uUb|IbWKsE zWwZ5C&CR991=S_oTC9%}-!iItkS^FTA%JB8=VY9=^kYH*02(12$YL%7z_!)Ik2Ao< zv1Y3%O<0b@U#8J$SDwOP8maPjq#9iEy0j5Ns7^9+0d6-a62$rJ8J{0+u1as=kW zkFAOCF7_sE(<>M1lcfG4eLV`??UOfBKhJq_M=#dT;dV|zLgf^t5PAf#@GBri0udN0 znG8flz#tG8m_=!_^r_sB%<*gqwS8$;B;^u|s&U`6l#SC_t5_Mu#_!aAGTivP|C_ky zAAWZQm^ej$cIk@ky+_*sz6UT2=UXD%BnRQ(zJ|ejqEQ8E$7WyF30M6&MK$s$h7OWcqz(WiS zjRHUj1w|n+By>~6GOVQ}VMGYw*m8rD>TS=DJ+09)0EPUTS314k2t&7_euqsZw)#2mYn?AiBCD@VGnX)SSV&Sp0RR-UXg*H_N<~A zZ=>o_`b%f{;`V*|>*WvWa~plX+wNEseD4_gvo`xKy91k#%aZC$8p#Wu4gJJO^WOx>`9F(A-p(xL%D}u~C0P;|! zlV$3S0yj2mZjWDdIagpgsb(i zZLSwlXZ=hj>o191_1P*|5m90#@)ZCG0U!e$3jo{Z6JrDbNLWh(mzNx05gK@8a^Ud$Uh+^k2@ty#*0Azp>HP-K05> zA3+=*HPj}Iy9-~YSGvJ$=DDA(j0mPBZamG$vf91{*;ZGsZBP4`d)p-QMauOS<+CnR zzSDd&tfWd3Zj3usGSB_S2Q}}-Lcoxf*cB!ur2?A(L;;`-g`y%i`NL;t4@zUcn>W>4TTr7c;y|s0}JRyN|%`_2MOU~4fq5A0$7rPuVesl$cj_I5(0n^ zDj4TNn#Zi3rc)PfD1e&ws^EYUNhm>)x*m&eAhV`B?Xp&P#*Z$3>u~g_A66cq|KU4p z=H-av|Ed=5_J;TVQ(jS!@cCfxGuhTP({n&Rk4A$bZL=LKm(;pPdUhrC<|W3iC{uq0 zSzN8A{Srs2az z`5KUvDoEr|9*7e+2qGn(Gf_?G)VGG-m0j~@S(;nILcG%F)xG66MAm^n>zowb?|!fI z)1Qs?JrVs+i=0QB3=M{a6Rx+G=Q=8;C3>A#EI1GMYhq7R_f_O@s+pvI&PpzLPo9gn z0gVm5ZSt2*wb`3htXQ=z2iAkXfWj(=yqp2Vl`1n~Gl|!-0_*LJR_k>Rv@ta_ zN(8{>fM!So2}Oz+0tpb2%pr$42vIn8Z3ZiXpmEd0Nj7-iELXpmGrp^Dsz^G}*2``7ibdmG6uEuk2|yanH`({Bh%Lj%N0a_e;n6(u=Ikb9^cthF z({PS~mH47k=olQR2UNk>Rqp7u_LV5tt~Osc~sUGrNikwQK0?1%TeyUp}X$X-Wjs1FkCa**^9^%dcP)%g+R7GJH5%l z;^R^MpgauLOJ6TsyuH_`7`8i<{ml<=X1SDR3%n4r5W!T!CDVr#u!H~rC8-toPfm6 zQly{E6yj_xxAlMi>Rf%twA6*#pE4wmPEJ2qy zBrhd61t^sylwt@Wp#&f#NLdnN7Vvvgk+655B)Fs%Wv!<12- zd8Ufocs>WoHiOK8b7=o6cmMU1<8^Oq<4rT}v_?=! zAtp16+4^yf&uDkJJ zY?o`C{?}bql&s|~tMSxf|GqM9$gi$WHJzasWvA_He{ie1!)b#JUM*(%t8{tFY>BVA z>PeY;-A|LEal7_(eeZdk^MkFp|75(H;N?E^+%Zb=3Ns6qgy1(gswiyYc|;(D@>WgG zQbkJ~sU)SI$x}rH(PPX9{6Xf$#O8`eCwKBeaJbd^Si+Uf&!N>scW=B|4sW7cs9Y5j z-Tk(1F6Q%YaU`PKyw5FO^YwgQDi%bb&$)Tq5(`~a9t5iHx4d|(4C2O%mH8~+E=)hE zv+ddQH4{H8whvB|(10`0B1F<2u18K5?TubWL>%&y=W9IY$ zY6Di4$FK27Tz1cYKA9F37KN{$e@+*Enao669=_Tt(rw9NYW_ii0y)W8pss@MT3e|T zpSHnam1hz$(b8B`yg9FTiskxx@OXL7?+7rqfOmPFGErKVv4;>7%~T*L7;ylIG^Lay zB$RYS2&NR`m=|Q8$-P(*_y`HqPou~PROE)zN~?lfSloN2y!-yf&t5Il-3sXO+O(%S zb>4E&X1{qvNd* z7BvRvZXB`9d@=A|O2hEBxafZEAeYK6ywT$G?H=hajzN1!pmeM?;MGztFdO*Znq*2< zv>~H}76dFx77-%HUTHKR4x9&@(Eo*kLz&zs;U!GC_3!NDOt(9XE9#{Jdy6H?xK zms@B#_XaX7Kv-unPC}-{sx4O{kS#=w5=sHXMl@fq6UZ}ugcVH>PT8`&;U{|J@j?0f z#qgi)Tt7@}!AGS(8F!AEODT_P!n&y2zl+Q$&vBwf_uet8y&M1jcJ&`hOknB{wV5Kh zRvw5kXnBAGvbxg%qCu@ zqtq`p&qt-UX> zk~c%6A_>=eIa6>()qS$Ay!1(>Y{tsB)X>UzH#=x<)Mvh|Up`dyW-KnRt6R+4gBlr& zi@}NgmIdy8T%?-c0dd!#sIOBNHAboNC5#A zfi3BsbW^-U=X##6>gw%!-H`w8)$w;r)7F=p$CsB|+0xd7cR#93hm3{pf-KFr>Fo!7 zhB=)6_&|KEIDo2UHzEFxF69ylIFaY(VeZpyy^v==eV`8lgE)yWj3#i0kD-s=LgCZMSlW zD#;`Tta$9O?kqI-1voH+c$CxfWas7belJw#fk6_U`r!y}O}!8J+-RIw;fpc!s!nH8 zoj#@dD86&g63d)2f1TXsYB6bcv(lnf9lbU-@a2pf?w8+X#C9|7+Wy|Xe0jQ7DtTC# z=~S&<7n&gzc3xu=9zu#CYcNC*B-ysg&6XO7LRvC~(GK{ngYzFHV3FUfy$CqQzLAd_ zKNT%B}K*?@5n(bf4@EDJsNAZHd-vC88R;GRdA``0 zN7pewWqq!k13{r19P+WaYr4FeQAvsbgottq&ovzwg&9sHSX9YMtw& zE~}och|R_=vIx|6hKgaWtSB#Cr|b=1y@*)1B#b!Bag)t3I1kGHYjV8B$vbdNA16Xk zK&dfWhyc%Lx2lDcuK=qRuDBd0EJMSoFAE^=QcuHPTt~MUYDw?wqe=ckAD_OSU8;W=242jV2hGxt z_omZKzy*pMY6VQp#Z(jGByUOQcW+c5y6u`5Ny`Gt8}Q9Q6V|Th2f%&wgg?>N&!y#} z;g)h``NpNxx~smJ$*Qy}5G016TKqIds(KI#ly!2QAkR&l0uUjBJfHwzvE0eZl19Z6 z5Ja5g>hNw*-1$EQooUXWvr>_i7t^+gqnE0EoIyw zIao{Cxg=!)(M2hpTY-AhvXaq@&&7Q^Ml({9Qbm|5nIsEnw9ehGCC$s@3my?5D!h-7 zUPLTdc{N8!5|P7fHVnClH%M-`!?9j<+sikR@O!|}7uigW=NY4*vv&?JUsYNe*kC8y z8|dHFHt{tpHoWot#=f4)rC#Of_P0#0IIlV{`1O;M>{Np}KMbYprB(3?P=R0CUHXl! zPo9*{om*DefAyp=EEvxl!YfkVHBd&6ulk!G_MsG2ytXpV{@w(-Tct#6zf1*m}wd5mH*PdYM;h_+!TfKUFUXq7m9?$c1 z4p{CI9I&Z%@#rEI5>=+P=AC&(BfCKUJnZ<5qEXI=aklb}`7U z3RSMYG-tDL9uY3{XD;>BJWe^n00AW9WyxTebsTv_Qs!(0vkFL|()Sm-jDKK%TxiwE zv~+P6rd+Yinl5B+8kyA)Mrnop!a`Q{W9^$C_jcYF3q_(?hu>TK+4ji4a z>(d7xU*c9AX!{{1OMRbNPe!xBQp>J3zl~?B)j3+R^&ozA#fOn`@{P)+KR>zQEjL$# zT6GPR)o}l|r3GFY%_OZYL#Z#9mP-gepY+!25~sj>+yqwMB+kIk2{x#k;p2W2ubq+m*Urj!x0ne5xB zdPAp^xOwy(0K-`wRbtUibHPDsrVTDRZ`fL_RLOrbibUR8%>yi>xG=L6Yjv^0n(`V^ zMy!O~{OcRD?xyQx`MPNqzdF0s&_h^KGHmyL!RucPsv@Y%r1Lr53VXYPsAJZm zoQXI!WwCJ_^4V2^(`cUYIsu~aMqaZ7rAlD|=L8sR|z z>La7(6DQa1SU|c)fmdOH32NoknV-?uNkl(F)5kaH7`!-*3x+MGTDPOo zYh3fNwtd$9Q>iRFzm^htLm1%{M=eAFUW%;*6_$^ege(mKiKG;~y^nVAz`PFyZ ziZYI5C!A_uLsttuT{q`WxZV8UR{Ix>iV;}XSQbKKp6@It&u8t3FZZuULCG2AY3NFJ z*5udfZpnwzs`qsx1Ko2MliU8yQin`?&_|puKTXadk92pP1%n7gH+wcK;BAOIiaJ-A|a~*1W0Ciyg>7z7#wH- zFbJ*+t2swfvx0fm+*0%i;LAM$M9Fd&JYJ^wBG6oZ>xa!@6oqH2z{dGyvJ+M4fImgA z6aZ3rmewLXGNGN(wP}dqGVN5J{4(#>)VyTCS}y2kA&ZAal=H2m&sS~8MAeZEgvEYr zSVs*y98DG*4hEG}pn4We79wFfK!8|>xA913(o=bi_PbuW14*Wg`B9|_#{rEXc?S&%YEC(8f;=3DG0~C&?uiAY6ofUy7aPt;i)kTb}I2$vxuC< zNp_NJC}SlPw)1CToYcwnkevmsIkrtI5N)D%(kPbA5j`fHnXuFk5YF@pwIQ2@IT9a0 zo}~|nU|<7|E1F>xa}hHWhyHk8n$VFY0s`3xA;GxX!*xD*vlaiok3Q^9KOIir)!Q9< zlrX_pT<_|Y&l}@|D~`MT>*seTZ|yVU#_RBUB#|LxWfWVwh38j;Cn(@B19~F+rNPE8 zrfqF$-_Bx4Z?v9Jm#QV?(8DAz!!ko3h2%~&2nbneYjM;daXF%GAF&Bi9Usdo7L6gc zV<~}6CdL#6%>s6jG}Qv3WY7*WvY!IE!qc2pL1+WxHmgsmadDH|gK(XI3 ztnUK4xf`$2-%o8+=U6{g4o48Y;Ji491+(VSgO2d8c3V<4fBIe&E{tc1R$3i27U?Z1 zI~(Wr?jP5GI;C$~E3M(x3oZvvUjC?x79dE2Z@vJ%E}x}!q)(xK{48Gr3V1I5WP=z3 zLb`A<3_D^rmdC~;4jPj3xlw0INJY5On!2CD@gzZuC9E3v8Yxs^Mx0-yJ9rZDMjn#2 zuy8Uqrlw*gSquY%W>gZrqIvkk?4w6}T4r{YYCof4W)&D`u$s7Y(8{2~{24U))nCLv z`ay$yN#0f973g*<9|p&%_1Hyl*qMGdL45f=8s6Q@XNTDOCO8}93NMoj>`?f2o4TiT z#^ajp5Sf8EL-TFNRsX1CLFRoIN>rcoI*StfGNJEn}$~n7SmariArJX za0IU)4N_ON3`44}7x6j#TuPy>^g`&c8lo|^AwfejfE7C|zO^L(F^^6C^-7=}O^@yQ z>!gU)z7XQGEHpp89jbhtpZn#)rCpACA5r0n^lHzwh7pz#r9r-If4KY$sGv4lz6hj4 z(~sVJg~fk$J{#ra^hNK;devPz)fCP!toseTNIuiPH~)qFBcOHX(b&=fe>r!jy$i4QJ}BLd zs$Zs4JGIfD&hOi&=KYKKSEnrbj(+BDt&529sZhG)G2C>K+fH1HjnMH^o}9>{K9N0_ z88WBAJ^=Kk3)DHu5fCx&N07mM>1dCKQWp&sqzDJLnQ;du+$swY=_t_A24+4K9=Uor z+E;E&(UeQZni`ExVhN++U_E`m59Ep0ib8|E|38ki-O=VT)t|jxAh(`ACsU`bmG^>Q zi$4i{{==U61Nyg%@>ont!T-LPc8iyCg7e7^3CDN;1*ujNW^Ff%Esh@_K3f?z1GJ(@d8=N<1kL2^VcQ>eL>F^`euUJAX-2;$J)3Z$SDgMjhynrEO(J># zI}=EX z|FGU=iDcSuZ#*5L23P*`mA#MA_p%$dfAUW3w3$ACwfdBT-T0t<)zNphjGR1cKa($u z6`R=%YyO+9cGFv)u1G^_{L2V+78F9D*=R80OlZJC@rIQOj07#W2#2|-hO8FZQ_6EV z2q^4wBoN@ZqzHqTXCx|P-T+QAyfllVNe&UqfI~$TJ*`JY7#gX54EJ# z!cBPY6a5EeRCvjMugi?pw|*QPfPGBPe}WZ0RpcA9qpbPW&SFhdgeg;gxew73l~Hyv zrZbg}W3cd9m&5~^pv;&mA|cfDGX^O76V5|-{Op(&Y{t_S&i0wC<6HxRkPNJn}KIBYA5Y+I4>G*3=ChDqn4`a$1uyIrgRA~#oj zwN4#)omJOpz(8+?Qa&UHba?SK^Ye#im)~!FO#R#Oe?D6Ds*QWkmoB>f<}G-&5wNzr z)&0v)wCnQI@18ta7atd*8}E;OyF$=J;{37Y$MR2(XR=S93) z+sQ+aV##}=&*Hr)D$C)dDR%Lhl7ooD;o5EcH&M@di_5Kbz5?$qklUHRZT@|K z>Bqo}h}6B^r;+bh;QtVLj0^fdyDq@X2M4&u_w>j@ao9< zfQ#KNiJw~I+usqrh2zg>USFV}@4oi6Cw_~gO58-1V>YAOcLd4SfUp}5LCQ&w zQytFrE?Q+wy7TV1p()ZIl_)iB)Lz2jm5B0CTdUuBI|82WBbD&QW;fh#UtcGqsx*Wr zms^15Plpf_fpc;3XWXan6MEW}zdN;I0Nr(K5Shw@1WooM`N)y#le? za#b#y#f4gPtweuYU1)SYD-o5EmGl=06M0u22Jn1u)1Bx{)O%Ad7V;wJfH?w~Elah8OCgu(CS^O|qQe$Jaj;Upp+` zykNF2rY~XtM_5Eu_ja-G{bJkznfpBj2*$lE{2zCAXXTB`!5deNQ{{Q~YUu(-F}5sx z_WGavTq_T%(WD?$N}lt0l(C-O?B3^bhExpQ6ChMXEb4ZqvpaD~jj$#ypwSiY6F{$M;~X;5ZP-uJOjRqKr;QNsdgQ9t1h{p)CVI3vEeds9O)seUa+ zt6eb}LO1JN?w(rLd(F5Lis+|JpA_7rJkTRfNo{E|SH$fQ(DvxnsG2=_d>pEvuQ_%c2XqIbKnw8#5qKls5Ytp#1iAyz-9g zZS4@jkMPtBbaw9OdCpVbnIwklZ!3S&dUPul0%}K{`PT=16b9~2)Ia5~z9J5Gnb0{y zzr;5k%ISN=(T~1M?(9`71AB_-&r$9MIuA4-A5P!tn|uPzCpkM_W!^E}nMJf2MVkCz zEF-kl5N9uq$x{>3v+cRJXNS)ftqlzy*8(SqMf8C-bWiFgNrMmox-j8lOazl$OY_M$ z?eo=J*~5z>w#n`0XuJF!{p)QyGy?z^kMP5L*B0wv+&0_i_yrS!+XX+y=t_ zOM@*vVAw_t{@c*@$7J$GihnfJ+H=_fNX-V2f8})Z_MO3t1|S=Pg!O#T5F-M;JQaa! zC;AAZL30pB<0$s>DCg9RS7$*P07s%2K(>h?U?yk}bLdR&+kxQs`5NXLfX6!FO(Ee} zyzf&iZYOXQuyN49UEKxrLZa7w^ETjH6Xe;x93VW>&Zx%@gQbp3n@!>Vdj;fcZ`Sx+ zF(i)-^UB$C4h94-qLSEb0aDmnzx@cM7A{MI)hq< zJwQ5zSj7n-0Sm=eQhjs&a>iMoM6|bNX3jGSs6V;IuiOfCYu6VQKR#5@rlpRbJvj)L zFZZ6eOxahHa2qx(NtZMQK(!%@+PVuv9!mCAB@k^p1eHB7x3&r!eVW|hJUL@Cb7twh z4#?}15$>@G<(9{{zkDMlK=RB=c#wfAeIHZ&TOLHJM^Uo!UjIrygZA{g(xtMQLcfRE{XaO=tm=I6J4la zNdI?-c}7$Uan1uizPteO<_ck87=i$Xc>(0DGf~h`iqI`y3mgD(Jq@N{G-B&$ z)^%+eM|=c}=T*D(28cj`rYQxm9=kceV@?{UszAEw7k^yOI2eKW( z2XPv;Y{XW408x~{(m284-AHmV0*8~8<@rc#sJNt%FGfyAx8aYEim4PN6Ny+UkRCC~ z_0aHtZmw%T&k^aM$)dR^m6VuUnRv*B(z=|QX!U}WR)F7pHd?qQKYD}w;O)Yby)DX! z!?3Q8f<)N5-2yWUCgl>U?uPtMSc|imkEV5uw!yZW%`VA%r3id%2b|IEx#Un)nG92k z?mV6hhcI}lZ@xR# z#PXj3^YTXkw-|Wt$Wprsyfh)XWheo346LUZG_}h()s<+Ib6aA93Y3V91F04JaFvD-3th2#Y`kVT9ehU0!>|3_OrbMI#AEGnd^UxN<8&BQ5vyz3 z12t;Ij)!AnXYt)D{)W(w`uupf59KTlB@A>DyNDHX5gfyO3lX9Ixg()wtgT{h+?7Pb z)&`P-yQVC7HC_V?1Zq#+pB{-M10!Z9ev@pjM-fF6P5^(%tmQe$nh#$EC*jgEX@f%=ynV&wVfxZkt zgsXzn*wN{)rr=7LJ6FO8@ui>aN)kJg?Rk|P)};e=zL2q=_9IFs@uA6k}q;y`2!aAqhrfGJp zdG}~{q!jMxf1ut~<+H&!4Ga@E^QhK&TT{oplQ4WRB8Yoyzt+bZsiRXLDP=}>e-DWG z^3Eh&&n@k`%>2~Dacg3J*ybkS|2*{~_u$F>EB$}G0mVi|s?%~ap^$)i)-IfbzrYH0 zaV+9R4AK^da?bb$0j>g3{c3mQ0@(y|Fz3v_dH%;k;O(&&iUZeb>d;v$MPuW)3luMP z-Su||-NVC9%{CEB6ye85gJ1r!3ouyg7@)N$yVZ}LwOkyl3_h@amLtA78`m24(mUz6 z{{#yCZv7Q%e{|M;Ram~;+GM6x`B}8wX^rqU!XD^hwprl4nbs$`%i7!5K~?(X_iH~v z?rgPw_AprD|KZii{o#u1rX4HI43Pv;rh2CU469SZ%L@eXkHPTzqT>~IQPQvsCZa-f zOL#v6RfU7`^iXd&aL8#l$~immIA?{Z4^KO8BG!sgz4n%Ak+c&MbdrB@j0qOXiuRP> z_fbexM)+dU>_LeIf1WZcS>$YwkiUNQ+Sbm1rIvMf4bbChe{zFJ)aAFCvnVi}JKidh zpItWlZW!lyKQ?SkW5gA52=KpsXY!n0RS&9D6kzV<(vkS_!F=TRJuC?3uT7oSZK3M- zJa`1q(b7T=7)cNZKj#6{NyI2sPl$+&-zY?``wyL{CP%_BX$&xsV?u9_|DqSd6Zq-j z4=No#ooBWEqE#b^TQO*eo0000 = { - "/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() { -