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",