From c3e965b535a98ae0fede8fd4232da45cdd5daf78 Mon Sep 17 00:00:00 2001 From: wjsjwr Date: Mon, 11 Aug 2025 22:08:19 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E.hintrc=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E6=96=87=E4=BB=B6=EF=BC=8C=E6=9B=B4=E6=96=B0=E5=A4=9A=E4=B8=AA?= =?UTF-8?q?h5ad=E6=96=87=E4=BB=B6=E7=9A=84=E5=A4=A7=E5=B0=8F=E5=92=8C?= =?UTF-8?q?=E5=93=88=E5=B8=8C=E5=80=BC=EF=BC=8C=E4=BF=AE=E6=94=B9FakeEmbry?= =?UTF-8?q?o.py=E4=B8=AD=E7=9A=84=E7=BB=86=E8=83=9E=E5=B1=82=E5=8F=82?= =?UTF-8?q?=E6=95=B0=EF=BC=8C=E8=B0=83=E6=95=B4=E5=89=8D=E7=AB=AF=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2=E4=B8=AD=E7=BB=86=E8=83=9E=E4=BD=8D=E7=BD=AE=E5=92=8C?= =?UTF-8?q?=E5=A4=A7=E5=B0=8F=E7=9A=84=E8=AE=A1=E7=AE=97=EF=BC=8C=E4=BC=98?= =?UTF-8?q?=E5=8C=96TemporalAnalysis.tsx=E4=B8=AD=E7=9A=84=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E8=BF=9E=E6=8E=A5=E9=80=BB=E8=BE=91=E4=BB=A5=E7=A1=AE?= =?UTF-8?q?=E4=BF=9D=E7=BB=86=E8=83=9E=E7=B1=BB=E5=9E=8B=E6=B5=81=E5=8A=A8?= =?UTF-8?q?=E7=9A=84=E5=B9=B3=E8=A1=A1=E6=80=A7=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .hintrc | 8 ++ embryo-backend/Data/CS7.5.h5ad | 4 +- embryo-backend/Data/CS7.h5ad | 4 +- embryo-backend/Data/CS8.h5ad | 4 +- embryo-backend/Data/CS9.h5ad | 2 +- embryo-backend/Data/FakeEmbryo.py | 26 ++-- .../src/pages/EmbryoGeneration.tsx | 20 +-- .../src/pages/TemporalAnalysis.tsx | 116 ++++++++++++------ embryo-frontend/tsconfig.json | 1 + 9 files changed, 120 insertions(+), 65 deletions(-) create mode 100644 .hintrc diff --git a/.hintrc b/.hintrc new file mode 100644 index 0000000..cb34607 --- /dev/null +++ b/.hintrc @@ -0,0 +1,8 @@ +{ + "extends": [ + "development" + ], + "hints": { + "no-inline-styles": "off" + } +} \ No newline at end of file diff --git a/embryo-backend/Data/CS7.5.h5ad b/embryo-backend/Data/CS7.5.h5ad index 7a5e248..59f7f20 100644 --- a/embryo-backend/Data/CS7.5.h5ad +++ b/embryo-backend/Data/CS7.5.h5ad @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fe1c37e14ee94488e44f5a0786f9e30a321d87d93ad695da69fe16694bf96a48 -size 177616 +oid sha256:18f1b4be18f32b7079d0cc2bbc90fc61d83eb1d9fd1ddf954a41f01b225afc90 +size 177856 diff --git a/embryo-backend/Data/CS7.h5ad b/embryo-backend/Data/CS7.h5ad index 3181616..6c7f446 100644 --- a/embryo-backend/Data/CS7.h5ad +++ b/embryo-backend/Data/CS7.h5ad @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:101670117924d6905c233fb8b95ad418c0988e6566be9eeceb516dc9dfc3d380 -size 114968 +oid sha256:e9d1c779d33b455021b07427137350cfb5400c287543a416fd6aff70f87041a8 +size 115088 diff --git a/embryo-backend/Data/CS8.h5ad b/embryo-backend/Data/CS8.h5ad index 6552cca..5671af7 100644 --- a/embryo-backend/Data/CS8.h5ad +++ b/embryo-backend/Data/CS8.h5ad @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:41911fccae1fccd64d417b143eddfc156ab3a0aefecefa7b318d4d379815b80b -size 209672 +oid sha256:ba0cea567f5d9058dd3e8ff267ca6362d08e5cdfd7f7242bddc372f8ac18b83d +size 209912 diff --git a/embryo-backend/Data/CS9.h5ad b/embryo-backend/Data/CS9.h5ad index 4ec30f7..416d174 100644 --- a/embryo-backend/Data/CS9.h5ad +++ b/embryo-backend/Data/CS9.h5ad @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7fb3cd18873e9aad28f25f0e08a739f1eac4ff4a5c2157567bebdff5880b0df7 +oid sha256:b843baea0d40ef7d90cfa0abf5d732efa4347c4ef8be3598dac4154f78f7e3b6 size 335448 diff --git a/embryo-backend/Data/FakeEmbryo.py b/embryo-backend/Data/FakeEmbryo.py index 0aaa749..f61d76c 100644 --- a/embryo-backend/Data/FakeEmbryo.py +++ b/embryo-backend/Data/FakeEmbryo.py @@ -6,12 +6,12 @@ import os np.random.seed(42) # 参数配置 -stages = [("CS7", 500), ("CS8", 1000), ("CS9", 1500)] +stages = [("CS7", 500), ("CS7.5", 750), ("CS8", 1000), ("CS9", 1500)] genes = ["SOX2", "NANOG", "T", "POU5F1", "OTX2", "ZIC2", "FOXA2", "LEFTY1"] layers = { - "Ectoderm": 1.0, # 外层 - "Mesoderm": 0.85, # 中层 - "Endoderm": 0.7, # 内层 + "Ectoderm": 0.1, # 外层 + "Mesoderm": 0.085, # 中层 + "Endoderm": 0.07, # 内层 } notochord_ratio = 0.05 # 脊索/原条占总细胞数的比例 @@ -21,17 +21,23 @@ script_path = os.path.dirname(os.path.realpath(__file__)) for stage, total_cells in stages: n_noto = int(total_cells * notochord_ratio) n_layer = total_cells - n_noto - n_per_layer = n_layer // len(layers) + num_layers = len(layers) + # 使用 Dirichlet 生成随机且不相等的概率,再用多项分布采样;若计数仍相等则重采样 + for _ in range(100): + probs = np.random.dirichlet(np.ones(num_layers)) + n_per_layer = np.random.multinomial(n_layer, probs) + if len(set(n_per_layer)) == num_layers: + break positions = [] cell_types = [] ids = [] # 椭球比例参数 - a, b, c = 10, 8, 5 + a, b, c = 1, 0.8, 0.5 for i, (layer_name, scale) in enumerate(layers.items()): - N = n_per_layer + N = n_per_layer[i] phi = np.random.uniform(0, np.pi, N) theta = np.random.uniform(0, 2*np.pi, N) r = scale # 层的半径比例 @@ -46,9 +52,9 @@ for stage, total_cells in stages: ids.append(f"{stage}_{layer_name}_{len(ids)}") # 添加脊索/原条样结构(Z轴中间偏下的细胞) - x = np.random.normal(0, 0.5, n_noto) - y = np.random.normal(0, 0.5, n_noto) - z = np.linspace(-3, 3, n_noto) + x = np.random.normal(0, 0.01, n_noto) + y = np.random.normal(0, 0.01, n_noto) + z = np.linspace(-0.1, 0.1, n_noto) for xi, yi, zi in zip(x, y, z): positions.append([xi, yi, zi]) diff --git a/embryo-frontend/src/pages/EmbryoGeneration.tsx b/embryo-frontend/src/pages/EmbryoGeneration.tsx index 327007a..770855a 100644 --- a/embryo-frontend/src/pages/EmbryoGeneration.tsx +++ b/embryo-frontend/src/pages/EmbryoGeneration.tsx @@ -258,10 +258,10 @@ const UnifiedEmbryoAnimation: React.FC = ({ cs7Data // 计算所有点的包围盒,自动居中和缩放 let allPositions: number[] = []; - cs7Data.forEach(p => { allPositions.push(p.x - 40, p.y, p.z); }); - cs8Data.forEach(p => { allPositions.push(p.x + 40, p.y, p.z); }); + cs7Data.forEach(p => { allPositions.push(p.x - 0.5, p.y, p.z); }); + cs8Data.forEach(p => { allPositions.push(p.x + 0.5,p.y, p.z); }); cs75Data.forEach((p, i) => { - const fromX = i % 2 === 0 ? p.x - 40 : p.x + 40; + const fromX = i % 2 === 0 ? p.x - 0.5 : p.x + 0.5; allPositions.push(fromX, p.y, p.z); allPositions.push(p.x, p.y, p.z); }); @@ -285,7 +285,7 @@ const UnifiedEmbryoAnimation: React.FC = ({ cs7Data const positions: number[] = []; const colors: number[] = []; cs7Data.forEach((p) => { - positions.push(p.x - 40, p.y, p.z); + positions.push(p.x - 0.5, p.y, p.z); const c = colorMap.get(p.value as string) || new THREE.Color('#CCCCCC'); colors.push(c.r, c.g, c.b); }); @@ -293,7 +293,7 @@ const UnifiedEmbryoAnimation: React.FC = ({ cs7Data geometry.setAttribute("color", new THREE.Float32BufferAttribute(colors, 3)); const material = new THREE.PointsMaterial({ vertexColors: true, - size: 0.8, + size: 0.4, sizeAttenuation: true, transparent: true, alphaTest: 0.5, @@ -327,7 +327,7 @@ const UnifiedEmbryoAnimation: React.FC = ({ cs7Data const positions: number[] = []; const colors: number[] = []; cs8Data.forEach((p) => { - positions.push(p.x + 40, p.y, p.z); + positions.push(p.x + 0.5, p.y, p.z); const c = colorMap.get(p.value as string) || new THREE.Color('#CCCCCC'); colors.push(c.r, c.g, c.b); }); @@ -335,7 +335,7 @@ const UnifiedEmbryoAnimation: React.FC = ({ cs7Data geometry.setAttribute("color", new THREE.Float32BufferAttribute(colors, 3)); const material = new THREE.PointsMaterial({ vertexColors: true, - size: 0.8, + size: 0.4, sizeAttenuation: true, transparent: true, alphaTest: 0.5, @@ -408,7 +408,7 @@ const UnifiedEmbryoAnimation: React.FC = ({ cs7Data const sourcePoint = cs7PointsOfType.length > 0 ? cs7PointsOfType[sourceIndex] : cs7Data[i % cs7Data.length]; - fromX = sourcePoint.x - 40; + fromX = sourcePoint.x - 0.5; fromY = sourcePoint.y; fromZ = sourcePoint.z; } else { @@ -418,7 +418,7 @@ const UnifiedEmbryoAnimation: React.FC = ({ cs7Data const sourcePoint = cs8PointsOfType.length > 0 ? cs8PointsOfType[sourceIndex] : cs8Data[i % cs8Data.length]; - fromX = sourcePoint.x + 40; + fromX = sourcePoint.x + 0.5; fromY = sourcePoint.y; fromZ = sourcePoint.z; } @@ -466,7 +466,7 @@ const UnifiedEmbryoAnimation: React.FC = ({ cs7Data vAlpha = alpha; vColor = color; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); - gl_PointSize = 10.0; + gl_PointSize = 5.0; } `; diff --git a/embryo-frontend/src/pages/TemporalAnalysis.tsx b/embryo-frontend/src/pages/TemporalAnalysis.tsx index 0d47434..54c966e 100644 --- a/embryo-frontend/src/pages/TemporalAnalysis.tsx +++ b/embryo-frontend/src/pages/TemporalAnalysis.tsx @@ -68,7 +68,7 @@ const TemporalAnalysis: React.FC = () => { // 转换数据为线图格式 const stagesData = response.data.stages_data; const allCellTypes = new Set(); - + // 收集所有细胞类型 stagesData.forEach((stageData: any) => { Object.keys(stageData.cell_types).forEach(cellType => { @@ -78,7 +78,7 @@ const TemporalAnalysis: React.FC = () => { const cellTypesArray = Array.from(allCellTypes).sort(); const colorMap = generateCellTypeColors(cellTypesArray); - + // 构建线图数据 - 每个细胞类型一条线 const lineData = cellTypesArray.map(cellType => { const linePoints = stagesData.map((stageData: any) => { @@ -112,7 +112,7 @@ const TemporalAnalysis: React.FC = () => { // 构建桑基图数据 const buildSankeyData = (dataCS7: any[], dataCS8: any[], dataCS9: any[]) => { setSankeyLoading(true); - + try { // 统计各阶段的细胞类型数量 const countCellTypes = (data: any[]) => { @@ -152,7 +152,7 @@ const TemporalAnalysis: React.FC = () => { const nodes: any[] = []; cellTypeArray.forEach(cellType => { const color = colorMap.get(cellType) || '#CCCCCC'; - + // CS7 节点 if (countsCS7[cellType]) { const percentage = ((countsCS7[cellType] / totalCS7) * 100).toFixed(1); @@ -184,39 +184,79 @@ const TemporalAnalysis: React.FC = () => { } }); - // 构建连接 - const links: any[] = []; - cellTypeArray.forEach(cellType => { - const color = colorMap.get(cellType) || '#CCCCCC'; - - // CS7 -> CS8 连接 - if (countsCS7[cellType] && countsCS8[cellType]) { - const cs7Percentage = (countsCS7[cellType] / totalCS7) * 100; - const cs8Percentage = (countsCS8[cellType] / totalCS8) * 100; - const avgPercentage = (cs7Percentage + cs8Percentage) / 2; - - links.push({ - source: `CS7_${cellType}`, - target: `CS8_${cellType}`, - value: Math.max(avgPercentage, 0.1), // 确保有最小值以显示连接 - color: color + '80' // 添加透明度 - }); - } + // 构建连接:使用最小百分比作为同类连线,并将剩余部分匹配到有缺口的目标细胞类型,保证各阶段总量守恒 + const buildBalancedLinks = ( + sourceCounts: Record, + sourceTotal: number, + targetCounts: Record, + targetTotal: number, + sourceStage: string, + targetStage: string + ) => { + const types = Array.from( + new Set([ + ...Object.keys(sourceCounts), + ...Object.keys(targetCounts) + ]) + ); - // CS8 -> CS9 连接 - if (countsCS8[cellType] && countsCS9[cellType]) { - const cs8Percentage = (countsCS8[cellType] / totalCS8) * 100; - const cs9Percentage = (countsCS9[cellType] / totalCS9) * 100; - const avgPercentage = (cs8Percentage + cs9Percentage) / 2; - - links.push({ - source: `CS8_${cellType}`, - target: `CS9_${cellType}`, - value: Math.max(avgPercentage, 0.1), // 确保有最小值以显示连接 - color: color + '80' // 添加透明度 + const sourcePct: Record = {}; + const targetPct: Record = {}; + types.forEach((t) => { + sourcePct[t] = ((sourceCounts[t] || 0) / sourceTotal) * 100; + targetPct[t] = ((targetCounts[t] || 0) / targetTotal) * 100; + }); + + const flows: any[] = []; + + // 同类对齐:取最小值 + const sourceResidual: Record = {}; + const targetResidual: Record = {}; + types.forEach((t) => { + const direct = Math.min(sourcePct[t], targetPct[t]); + if (direct > 0) { + const color = (colorMap.get(t) || '#CCCCCC') + '80'; + flows.push({ + source: `${sourceStage}_${t}`, + target: `${targetStage}_${t}`, + value: direct, + color + }); + } + sourceResidual[t] = sourcePct[t] - direct; // >= 0 + targetResidual[t] = targetPct[t] - direct; // >= 0 + }); + + // 将源阶段的剩余按目标阶段的缺口比例分配 + types.forEach((sType) => { + let remaining = sourceResidual[sType]; + if (remaining <= 0) return; + // 计算当前目标缺口总和 + let totalDeficit = types.reduce((sum, t) => sum + Math.max(targetResidual[t], 0), 0); + if (totalDeficit <= 0) return; + types.forEach((dType) => { + if (targetResidual[dType] <= 0) return; + const share = (remaining * targetResidual[dType]) / totalDeficit; + if (share > 1e-6) { + const color = (colorMap.get(sType) || '#CCCCCC') + '80'; + flows.push({ + source: `${sourceStage}_${sType}`, + target: `${targetStage}_${dType}`, + value: share, + color + }); + targetResidual[dType] -= share; + } }); - } - }); + }); + + return flows; + }; + + const links: any[] = [ + ...buildBalancedLinks(countsCS7, totalCS7, countsCS8, totalCS8, 'CS7', 'CS8'), + ...buildBalancedLinks(countsCS8, totalCS8, countsCS9, totalCS9, 'CS8', 'CS9') + ]; setSankeyData({ nodes, links }); } catch (error) { @@ -326,7 +366,7 @@ const TemporalAnalysis: React.FC = () => { The three 3D views below show cell type distributions for CS7, CS8, and CS9. Compare spatial distributions to observe changes over development.

- +
{ The Sankey diagram (left) shows cell type flows across stages; the line chart (right) shows the proportion changes of the selected gene across cell types.

- +
{ ); }; -export default TemporalAnalysis; \ No newline at end of file +export default TemporalAnalysis; \ No newline at end of file diff --git a/embryo-frontend/tsconfig.json b/embryo-frontend/tsconfig.json index 9cc289d..c32769e 100644 --- a/embryo-frontend/tsconfig.json +++ b/embryo-frontend/tsconfig.json @@ -6,6 +6,7 @@ "module": "ESNext", "lib": ["ES2022", "DOM", "DOM.Iterable"], "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, /* Bundler mode */ "moduleResolution": "bundler",