新增.hintrc配置文件,更新多个h5ad文件的大小和哈希值,修改FakeEmbryo.py中的细胞层参数,调整前端页面中细胞位置和大小的计算,优化TemporalAnalysis.tsx中的数据连接逻辑以确保细胞类型流动的平衡性。
This commit is contained in:
parent
801700cd3d
commit
c3e965b535
8
.hintrc
Normal file
8
.hintrc
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"extends": [
|
||||||
|
"development"
|
||||||
|
],
|
||||||
|
"hints": {
|
||||||
|
"no-inline-styles": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
embryo-backend/Data/CS7.5.h5ad
(Stored with Git LFS)
BIN
embryo-backend/Data/CS7.5.h5ad
(Stored with Git LFS)
Binary file not shown.
BIN
embryo-backend/Data/CS7.h5ad
(Stored with Git LFS)
BIN
embryo-backend/Data/CS7.h5ad
(Stored with Git LFS)
Binary file not shown.
BIN
embryo-backend/Data/CS8.h5ad
(Stored with Git LFS)
BIN
embryo-backend/Data/CS8.h5ad
(Stored with Git LFS)
Binary file not shown.
BIN
embryo-backend/Data/CS9.h5ad
(Stored with Git LFS)
BIN
embryo-backend/Data/CS9.h5ad
(Stored with Git LFS)
Binary file not shown.
@ -6,12 +6,12 @@ import os
|
|||||||
np.random.seed(42)
|
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"]
|
genes = ["SOX2", "NANOG", "T", "POU5F1", "OTX2", "ZIC2", "FOXA2", "LEFTY1"]
|
||||||
layers = {
|
layers = {
|
||||||
"Ectoderm": 1.0, # 外层
|
"Ectoderm": 0.1, # 外层
|
||||||
"Mesoderm": 0.85, # 中层
|
"Mesoderm": 0.085, # 中层
|
||||||
"Endoderm": 0.7, # 内层
|
"Endoderm": 0.07, # 内层
|
||||||
}
|
}
|
||||||
notochord_ratio = 0.05 # 脊索/原条占总细胞数的比例
|
notochord_ratio = 0.05 # 脊索/原条占总细胞数的比例
|
||||||
|
|
||||||
@ -21,17 +21,23 @@ script_path = os.path.dirname(os.path.realpath(__file__))
|
|||||||
for stage, total_cells in stages:
|
for stage, total_cells in stages:
|
||||||
n_noto = int(total_cells * notochord_ratio)
|
n_noto = int(total_cells * notochord_ratio)
|
||||||
n_layer = total_cells - n_noto
|
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 = []
|
positions = []
|
||||||
cell_types = []
|
cell_types = []
|
||||||
ids = []
|
ids = []
|
||||||
|
|
||||||
# 椭球比例参数
|
# 椭球比例参数
|
||||||
a, b, c = 10, 8, 5
|
a, b, c = 1, 0.8, 0.5
|
||||||
|
|
||||||
for i, (layer_name, scale) in enumerate(layers.items()):
|
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)
|
phi = np.random.uniform(0, np.pi, N)
|
||||||
theta = np.random.uniform(0, 2*np.pi, N)
|
theta = np.random.uniform(0, 2*np.pi, N)
|
||||||
r = scale # 层的半径比例
|
r = scale # 层的半径比例
|
||||||
@ -46,9 +52,9 @@ for stage, total_cells in stages:
|
|||||||
ids.append(f"{stage}_{layer_name}_{len(ids)}")
|
ids.append(f"{stage}_{layer_name}_{len(ids)}")
|
||||||
|
|
||||||
# 添加脊索/原条样结构(Z轴中间偏下的细胞)
|
# 添加脊索/原条样结构(Z轴中间偏下的细胞)
|
||||||
x = np.random.normal(0, 0.5, n_noto)
|
x = np.random.normal(0, 0.01, n_noto)
|
||||||
y = np.random.normal(0, 0.5, n_noto)
|
y = np.random.normal(0, 0.01, n_noto)
|
||||||
z = np.linspace(-3, 3, n_noto)
|
z = np.linspace(-0.1, 0.1, n_noto)
|
||||||
|
|
||||||
for xi, yi, zi in zip(x, y, z):
|
for xi, yi, zi in zip(x, y, z):
|
||||||
positions.append([xi, yi, zi])
|
positions.append([xi, yi, zi])
|
||||||
|
|||||||
@ -258,10 +258,10 @@ const UnifiedEmbryoAnimation: React.FC<UnifiedEmbryoAnimationProps> = ({ cs7Data
|
|||||||
|
|
||||||
// 计算所有点的包围盒,自动居中和缩放
|
// 计算所有点的包围盒,自动居中和缩放
|
||||||
let allPositions: number[] = [];
|
let allPositions: number[] = [];
|
||||||
cs7Data.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 + 40, p.y, p.z); });
|
cs8Data.forEach(p => { allPositions.push(p.x + 0.5,p.y, p.z); });
|
||||||
cs75Data.forEach((p, i) => {
|
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(fromX, p.y, p.z);
|
||||||
allPositions.push(p.x, p.y, p.z);
|
allPositions.push(p.x, p.y, p.z);
|
||||||
});
|
});
|
||||||
@ -285,7 +285,7 @@ const UnifiedEmbryoAnimation: React.FC<UnifiedEmbryoAnimationProps> = ({ cs7Data
|
|||||||
const positions: number[] = [];
|
const positions: number[] = [];
|
||||||
const colors: number[] = [];
|
const colors: number[] = [];
|
||||||
cs7Data.forEach((p) => {
|
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');
|
const c = colorMap.get(p.value as string) || new THREE.Color('#CCCCCC');
|
||||||
colors.push(c.r, c.g, c.b);
|
colors.push(c.r, c.g, c.b);
|
||||||
});
|
});
|
||||||
@ -293,7 +293,7 @@ const UnifiedEmbryoAnimation: React.FC<UnifiedEmbryoAnimationProps> = ({ cs7Data
|
|||||||
geometry.setAttribute("color", new THREE.Float32BufferAttribute(colors, 3));
|
geometry.setAttribute("color", new THREE.Float32BufferAttribute(colors, 3));
|
||||||
const material = new THREE.PointsMaterial({
|
const material = new THREE.PointsMaterial({
|
||||||
vertexColors: true,
|
vertexColors: true,
|
||||||
size: 0.8,
|
size: 0.4,
|
||||||
sizeAttenuation: true,
|
sizeAttenuation: true,
|
||||||
transparent: true,
|
transparent: true,
|
||||||
alphaTest: 0.5,
|
alphaTest: 0.5,
|
||||||
@ -327,7 +327,7 @@ const UnifiedEmbryoAnimation: React.FC<UnifiedEmbryoAnimationProps> = ({ cs7Data
|
|||||||
const positions: number[] = [];
|
const positions: number[] = [];
|
||||||
const colors: number[] = [];
|
const colors: number[] = [];
|
||||||
cs8Data.forEach((p) => {
|
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');
|
const c = colorMap.get(p.value as string) || new THREE.Color('#CCCCCC');
|
||||||
colors.push(c.r, c.g, c.b);
|
colors.push(c.r, c.g, c.b);
|
||||||
});
|
});
|
||||||
@ -335,7 +335,7 @@ const UnifiedEmbryoAnimation: React.FC<UnifiedEmbryoAnimationProps> = ({ cs7Data
|
|||||||
geometry.setAttribute("color", new THREE.Float32BufferAttribute(colors, 3));
|
geometry.setAttribute("color", new THREE.Float32BufferAttribute(colors, 3));
|
||||||
const material = new THREE.PointsMaterial({
|
const material = new THREE.PointsMaterial({
|
||||||
vertexColors: true,
|
vertexColors: true,
|
||||||
size: 0.8,
|
size: 0.4,
|
||||||
sizeAttenuation: true,
|
sizeAttenuation: true,
|
||||||
transparent: true,
|
transparent: true,
|
||||||
alphaTest: 0.5,
|
alphaTest: 0.5,
|
||||||
@ -408,7 +408,7 @@ const UnifiedEmbryoAnimation: React.FC<UnifiedEmbryoAnimationProps> = ({ cs7Data
|
|||||||
const sourcePoint = cs7PointsOfType.length > 0
|
const sourcePoint = cs7PointsOfType.length > 0
|
||||||
? cs7PointsOfType[sourceIndex]
|
? cs7PointsOfType[sourceIndex]
|
||||||
: cs7Data[i % cs7Data.length];
|
: cs7Data[i % cs7Data.length];
|
||||||
fromX = sourcePoint.x - 40;
|
fromX = sourcePoint.x - 0.5;
|
||||||
fromY = sourcePoint.y;
|
fromY = sourcePoint.y;
|
||||||
fromZ = sourcePoint.z;
|
fromZ = sourcePoint.z;
|
||||||
} else {
|
} else {
|
||||||
@ -418,7 +418,7 @@ const UnifiedEmbryoAnimation: React.FC<UnifiedEmbryoAnimationProps> = ({ cs7Data
|
|||||||
const sourcePoint = cs8PointsOfType.length > 0
|
const sourcePoint = cs8PointsOfType.length > 0
|
||||||
? cs8PointsOfType[sourceIndex]
|
? cs8PointsOfType[sourceIndex]
|
||||||
: cs8Data[i % cs8Data.length];
|
: cs8Data[i % cs8Data.length];
|
||||||
fromX = sourcePoint.x + 40;
|
fromX = sourcePoint.x + 0.5;
|
||||||
fromY = sourcePoint.y;
|
fromY = sourcePoint.y;
|
||||||
fromZ = sourcePoint.z;
|
fromZ = sourcePoint.z;
|
||||||
}
|
}
|
||||||
@ -466,7 +466,7 @@ const UnifiedEmbryoAnimation: React.FC<UnifiedEmbryoAnimationProps> = ({ cs7Data
|
|||||||
vAlpha = alpha;
|
vAlpha = alpha;
|
||||||
vColor = color;
|
vColor = color;
|
||||||
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
||||||
gl_PointSize = 10.0;
|
gl_PointSize = 5.0;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|||||||
@ -68,7 +68,7 @@ const TemporalAnalysis: React.FC = () => {
|
|||||||
// 转换数据为线图格式
|
// 转换数据为线图格式
|
||||||
const stagesData = response.data.stages_data;
|
const stagesData = response.data.stages_data;
|
||||||
const allCellTypes = new Set<string>();
|
const allCellTypes = new Set<string>();
|
||||||
|
|
||||||
// 收集所有细胞类型
|
// 收集所有细胞类型
|
||||||
stagesData.forEach((stageData: any) => {
|
stagesData.forEach((stageData: any) => {
|
||||||
Object.keys(stageData.cell_types).forEach(cellType => {
|
Object.keys(stageData.cell_types).forEach(cellType => {
|
||||||
@ -78,7 +78,7 @@ const TemporalAnalysis: React.FC = () => {
|
|||||||
|
|
||||||
const cellTypesArray = Array.from(allCellTypes).sort();
|
const cellTypesArray = Array.from(allCellTypes).sort();
|
||||||
const colorMap = generateCellTypeColors(cellTypesArray);
|
const colorMap = generateCellTypeColors(cellTypesArray);
|
||||||
|
|
||||||
// 构建线图数据 - 每个细胞类型一条线
|
// 构建线图数据 - 每个细胞类型一条线
|
||||||
const lineData = cellTypesArray.map(cellType => {
|
const lineData = cellTypesArray.map(cellType => {
|
||||||
const linePoints = stagesData.map((stageData: any) => {
|
const linePoints = stagesData.map((stageData: any) => {
|
||||||
@ -112,7 +112,7 @@ const TemporalAnalysis: React.FC = () => {
|
|||||||
// 构建桑基图数据
|
// 构建桑基图数据
|
||||||
const buildSankeyData = (dataCS7: any[], dataCS8: any[], dataCS9: any[]) => {
|
const buildSankeyData = (dataCS7: any[], dataCS8: any[], dataCS9: any[]) => {
|
||||||
setSankeyLoading(true);
|
setSankeyLoading(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 统计各阶段的细胞类型数量
|
// 统计各阶段的细胞类型数量
|
||||||
const countCellTypes = (data: any[]) => {
|
const countCellTypes = (data: any[]) => {
|
||||||
@ -152,7 +152,7 @@ const TemporalAnalysis: React.FC = () => {
|
|||||||
const nodes: any[] = [];
|
const nodes: any[] = [];
|
||||||
cellTypeArray.forEach(cellType => {
|
cellTypeArray.forEach(cellType => {
|
||||||
const color = colorMap.get(cellType) || '#CCCCCC';
|
const color = colorMap.get(cellType) || '#CCCCCC';
|
||||||
|
|
||||||
// CS7 节点
|
// CS7 节点
|
||||||
if (countsCS7[cellType]) {
|
if (countsCS7[cellType]) {
|
||||||
const percentage = ((countsCS7[cellType] / totalCS7) * 100).toFixed(1);
|
const percentage = ((countsCS7[cellType] / totalCS7) * 100).toFixed(1);
|
||||||
@ -184,39 +184,79 @@ const TemporalAnalysis: React.FC = () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 构建连接
|
// 构建连接:使用最小百分比作为同类连线,并将剩余部分匹配到有缺口的目标细胞类型,保证各阶段总量守恒
|
||||||
const links: any[] = [];
|
const buildBalancedLinks = (
|
||||||
cellTypeArray.forEach(cellType => {
|
sourceCounts: Record<string, number>,
|
||||||
const color = colorMap.get(cellType) || '#CCCCCC';
|
sourceTotal: number,
|
||||||
|
targetCounts: Record<string, number>,
|
||||||
// CS7 -> CS8 连接
|
targetTotal: number,
|
||||||
if (countsCS7[cellType] && countsCS8[cellType]) {
|
sourceStage: string,
|
||||||
const cs7Percentage = (countsCS7[cellType] / totalCS7) * 100;
|
targetStage: string
|
||||||
const cs8Percentage = (countsCS8[cellType] / totalCS8) * 100;
|
) => {
|
||||||
const avgPercentage = (cs7Percentage + cs8Percentage) / 2;
|
const types = Array.from(
|
||||||
|
new Set([
|
||||||
links.push({
|
...Object.keys(sourceCounts),
|
||||||
source: `CS7_${cellType}`,
|
...Object.keys(targetCounts)
|
||||||
target: `CS8_${cellType}`,
|
])
|
||||||
value: Math.max(avgPercentage, 0.1), // 确保有最小值以显示连接
|
);
|
||||||
color: color + '80' // 添加透明度
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// CS8 -> CS9 连接
|
const sourcePct: Record<string, number> = {};
|
||||||
if (countsCS8[cellType] && countsCS9[cellType]) {
|
const targetPct: Record<string, number> = {};
|
||||||
const cs8Percentage = (countsCS8[cellType] / totalCS8) * 100;
|
types.forEach((t) => {
|
||||||
const cs9Percentage = (countsCS9[cellType] / totalCS9) * 100;
|
sourcePct[t] = ((sourceCounts[t] || 0) / sourceTotal) * 100;
|
||||||
const avgPercentage = (cs8Percentage + cs9Percentage) / 2;
|
targetPct[t] = ((targetCounts[t] || 0) / targetTotal) * 100;
|
||||||
|
});
|
||||||
links.push({
|
|
||||||
source: `CS8_${cellType}`,
|
const flows: any[] = [];
|
||||||
target: `CS9_${cellType}`,
|
|
||||||
value: Math.max(avgPercentage, 0.1), // 确保有最小值以显示连接
|
// 同类对齐:取最小值
|
||||||
color: color + '80' // 添加透明度
|
const sourceResidual: Record<string, number> = {};
|
||||||
|
const targetResidual: Record<string, number> = {};
|
||||||
|
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 });
|
setSankeyData({ nodes, links });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -326,7 +366,7 @@ const TemporalAnalysis: React.FC = () => {
|
|||||||
The three 3D views below show cell type distributions for CS7, CS8, and CS9.
|
The three 3D views below show cell type distributions for CS7, CS8, and CS9.
|
||||||
Compare spatial distributions to observe changes over development.
|
Compare spatial distributions to observe changes over development.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className="three-column-grid visualization-grid"
|
className="three-column-grid visualization-grid"
|
||||||
style={{
|
style={{
|
||||||
@ -425,7 +465,7 @@ const TemporalAnalysis: React.FC = () => {
|
|||||||
The Sankey diagram (left) shows cell type flows across stages; the line chart (right)
|
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.
|
shows the proportion changes of the selected gene across cell types.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "grid",
|
display: "grid",
|
||||||
@ -663,4 +703,4 @@ const TemporalAnalysis: React.FC = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default TemporalAnalysis;
|
export default TemporalAnalysis;
|
||||||
@ -6,6 +6,7 @@
|
|||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
|
||||||
/* Bundler mode */
|
/* Bundler mode */
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user