| import os |
| |
| os.environ['R_DEFAULT_DEVICE'] = 'png' |
| import time |
| import rpy2.robjects as robjects |
| from rpy2.robjects import pandas2ri |
| from rpy2.robjects.packages import importr |
| from rpy2.robjects.conversion import localconverter |
| from django.conf import settings |
| import logging |
| import zipfile |
| import shutil |
| import re |
| from rpy2 import robjects as ro |
| from rpy2.robjects.conversion import localconverter |
|
|
| |
| logger = logging.getLogger(__name__) |
|
|
| class RExecutor: |
| """R代码执行器,用于通过rpy2执行R分析代码""" |
|
|
| PREPROCESSING_KEYS = ( |
| 'quality_control', |
| 'spike_removal', |
| 'smoothing', |
| 'baseline', |
| 'truncation', |
| 'normalization', |
| ) |
| META_FREE_KEYS = ( |
| 'dim_reduction', |
| 'phenotype', |
| 'spectral_decomposition', |
| 'intra_ramanome', |
| ) |
| META_BASED_KEYS = ( |
| 'classification', |
| 'quantification', |
| 'raman_markers', |
| ) |
| |
| @staticmethod |
| def initialize(): |
| """初始化R环境""" |
| try: |
| with robjects.default_converter.context(): |
| try: |
| base = importr('base') |
| utils = importr('utils') |
| try: |
| ramex = importr('RamEx') |
| except Exception as e: |
| logger.warning(f"Failed to import RamEx package: {e}") |
| return True |
| except Exception as e: |
| logger.error(f"Failed to initialize R environment: {e}") |
| return False |
| except Exception as e: |
| logger.error(f"Failed to initialize R runtime: {e}") |
| return False |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| @staticmethod |
| def _normalize_params(params): |
| if isinstance(params, (str, bytes)): |
| import json |
| return json.loads(params) |
| return params or {} |
|
|
| @staticmethod |
| def _has_methods(section): |
| return bool((section or {}).get('methods')) |
|
|
| @staticmethod |
| def has_preprocessing_params(params): |
| params = RExecutor._normalize_params(params) |
| return any([ |
| RExecutor._has_methods(params.get('quality_control')), |
| (params.get('spike_removal') or {}).get('enabled', False), |
| RExecutor._has_methods(params.get('smoothing')), |
| RExecutor._has_methods(params.get('baseline')), |
| (params.get('truncation') or {}).get('enabled', False), |
| bool((params.get('normalization') or {}).get('method')), |
| ]) |
|
|
| @staticmethod |
| def has_meta_free_params(params): |
| params = RExecutor._normalize_params(params) |
| return any(RExecutor._has_methods(params.get(key)) for key in RExecutor.META_FREE_KEYS) |
|
|
| @staticmethod |
| def has_meta_based_params(params): |
| params = RExecutor._normalize_params(params) |
| return any(RExecutor._has_methods(params.get(key)) for key in RExecutor.META_BASED_KEYS) |
|
|
| @staticmethod |
| def has_selected_work(params): |
| params = RExecutor._normalize_params(params) |
| return any([ |
| RExecutor.has_preprocessing_params(params), |
| RExecutor.has_meta_free_params(params), |
| RExecutor.has_meta_based_params(params), |
| ]) |
|
|
| @staticmethod |
| def infer_analysis_type(params): |
| params = RExecutor._normalize_params(params) |
| if RExecutor.has_meta_based_params(params): |
| return 'meta_based' |
| return 'meta_free' |
|
|
| @staticmethod |
| def _extract_preprocessing_params(params): |
| params = RExecutor._normalize_params(params) |
| return {key: params[key] for key in RExecutor.PREPROCESSING_KEYS if key in params} |
|
|
| @staticmethod |
| def _get_processed_data_path(project_id): |
| from api.models import Project |
| project = Project.objects.get(id=project_id) |
| user_id = project.user.id |
| project_dir = os.path.join(settings.MEDIA_ROOT, f'users/{user_id}/projects/{project_id}/data') |
| return os.path.join(project_dir, 'processed_RamEx_data.rds') |
|
|
| @staticmethod |
| def execute_analysis(project_id, params): |
| params = RExecutor._normalize_params(params) |
| if not RExecutor.has_selected_work(params): |
| return {"status": "error", "message": "未检测到可执行的处理或分析参数"} |
|
|
| processed_data_path = RExecutor._get_processed_data_path(project_id) |
| preprocessing_requested = RExecutor.has_preprocessing_params(params) |
| meta_free_requested = RExecutor.has_meta_free_params(params) |
| meta_based_requested = RExecutor.has_meta_based_params(params) |
| analysis_requested = meta_free_requested or meta_based_requested |
|
|
| result = { |
| "status": "success", |
| "analyses": {}, |
| "plots": {}, |
| "artifacts": {}, |
| "executed_steps": [], |
| } |
| errors = [] |
|
|
| if preprocessing_requested or (analysis_requested and not os.path.exists(processed_data_path)): |
| preprocessing_params = RExecutor._extract_preprocessing_params(params) |
| preprocessing_result = RExecutor.execute_preprocessing(project_id, preprocessing_params) |
| if preprocessing_result.get("status") == "error": |
| return preprocessing_result |
| result["executed_steps"].append("preprocessing") |
| result["analyses"]["preprocessing"] = { |
| "status": preprocessing_result.get("status", "success"), |
| "message": preprocessing_result.get("message", ""), |
| } |
| if preprocessing_result.get("plot_url"): |
| result["plots"]["mean_spec_plot"] = preprocessing_result["plot_url"] |
| for key in ["plot_url"]: |
| if preprocessing_result.get(key): |
| result["artifacts"][key] = preprocessing_result[key] |
|
|
| if meta_free_requested: |
| meta_free_result = RExecutor.execute_meta_free_analysis(project_id, params) |
| if meta_free_result.get("status") == "error": |
| errors.append(meta_free_result.get("message", "meta_free analysis failed")) |
| else: |
| result["executed_steps"].append("algorithm_analysis") |
| if isinstance(meta_free_result.get("analyses"), dict): |
| result["analyses"].update(meta_free_result["analyses"]) |
| if isinstance(meta_free_result.get("plots"), dict): |
| result["plots"].update(meta_free_result["plots"]) |
| for key, value in meta_free_result.items(): |
| if key not in {"status", "analyses", "plots"} and key not in result["artifacts"]: |
| result["artifacts"][key] = value |
|
|
| if meta_based_requested: |
| meta_based_result = RExecutor.execute_meta_based_analysis(project_id, params) |
| if meta_based_result.get("status") == "error": |
| errors.append(meta_based_result.get("message", "meta_based analysis failed")) |
| else: |
| result["executed_steps"].append("metadata_algorithm_analysis") |
| if isinstance(meta_based_result.get("analyses"), dict): |
| result["analyses"].update(meta_based_result["analyses"]) |
| if isinstance(meta_based_result.get("plots"), dict): |
| result["plots"].update(meta_based_result["plots"]) |
| for key, value in meta_based_result.items(): |
| if key not in {"status", "analyses", "plots"} and key not in result["artifacts"]: |
| result["artifacts"][key] = value |
|
|
| if errors and not result["executed_steps"]: |
| return {"status": "error", "message": "; ".join(errors)} |
| if errors: |
| result["status"] = "partial_success" |
| result["message"] = "; ".join(errors) |
|
|
| return result |
|
|
| @staticmethod |
| def execute_r_code(r_code, capture_output=False, working_dir=None): |
| """执行R代码并返回结果""" |
| try: |
| if working_dir: |
| working_dir = working_dir.replace('\\', '/') |
| r_code = f""" |
| # 设置工作目录 |
| setwd("{working_dir}") |
| |
| {r_code} |
| """ |
|
|
| print("正在执行R代码:\n%s" % r_code) |
|
|
| with robjects.default_converter.context(): |
| if capture_output: |
| capture = robjects.r('capture.output') |
| output = capture(robjects.r(r_code)) |
| return [str(x) for x in output] |
| else: |
| result = robjects.r(r_code) |
| return result |
|
|
| except Exception as e: |
| logger.error(f"Failed to execute R code: {str(e)}") |
| raise |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| |
| @staticmethod |
| def process_upload(project_id, file_path, wavenumber_range=None, group_index=2, is_zip=False): |
| """处理上传的数据文件,支持zip压缩包的解压缩和处理""" |
| |
| try: |
| from api.models import Project |
| project = Project.objects.get(id=project_id) |
| user_id = project.user.id |
| |
| |
| project_dir = os.path.join(settings.MEDIA_ROOT, f'users/{user_id}/projects/{project_id}/data') |
| os.makedirs(project_dir, exist_ok=True) |
| |
| |
| if is_zip: |
| |
| extract_dir = os.path.join(project_dir, 'txt_files') |
| os.makedirs(extract_dir, exist_ok=True) |
| |
| |
| for item in os.listdir(extract_dir): |
| item_path = os.path.join(extract_dir, item) |
| if os.path.isfile(item_path): |
| os.remove(item_path) |
| |
| |
| with zipfile.ZipFile(file_path, 'r') as zip_ref: |
| |
| for file_info in zip_ref.infolist(): |
| if file_info.filename.lower().endswith('.txt'): |
| |
| file_name = os.path.basename(file_info.filename) |
| |
| zip_ref.extract(file_info, extract_dir) |
| |
| |
| default_min = 500 |
| default_max = 3150 |
| |
| |
| if wavenumber_range: |
| cutoff_range = f"c({wavenumber_range[0]}, {wavenumber_range[1]})" |
| else: |
| cutoff_range = f"c({default_min}, {default_max})" |
| |
| |
| r_code = f""" |
| # 加载必要的包 |
| library(RamEx) |
| |
| # 设置数据路径 |
| datas_path <- "{extract_dir}" |
| |
| # 读取数据 |
| RamEx_data <- read.spec( |
| data_path = datas_path, |
| group.index = {group_index}, # 使用传入的group_index |
| cutoff = {cutoff_range}, # 波数范围 |
| interpolation = TRUE # 进行插值 |
| ) |
| |
| # 获取分组信息 |
| group_names <- levels(RamEx_data@meta.data$group) |
| |
| # 将分组信息转换为字符向量 |
| group_names_vector <- as.character(group_names) |
| |
| # 保存RamEx数据对象 |
| saveRDS(RamEx_data, file = "{os.path.join(project_dir, 'ramex_data.rds')}") |
| """ |
| |
| |
| robjects.r('if(exists("r_error")) rm(r_error)') |
| |
| |
| result = RExecutor.execute_r_code(r_code, working_dir=project_dir) |
| |
| |
| |
| try: |
| r_code_get_groups = """ |
| if (exists("group_names_vector")) { |
| as.character(group_names_vector) |
| } else { |
| character(0) |
| } |
| """ |
|
|
| r_groups = RExecutor.execute_r_code(r_code_get_groups, working_dir=project_dir) |
| group_names = [str(x) for x in r_groups] |
|
|
| print(f"最终解析的分组列表: {group_names}") |
|
|
| if not group_names: |
| return {"status": "error", "message": "未能获取有效的分组名称,请尝试其他索引值"} |
|
|
| return {"status": "success", "group_names": group_names} |
|
|
| except Exception as e: |
| import traceback |
| error_details = traceback.format_exc() |
| logger.error(f"获取分组名称失败: {str(e)}\n{error_details}") |
| return {"status": "error", "message": f"获取分组名称失败: {str(e)}"} |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| if not group_names or len(group_names) == 0: |
| return {"status": "error", "message": "未能获取有效的分组名称,请尝试其他索引值"} |
| |
| return {"status": "success", "group_names": group_names} |
| except Exception as e: |
| import traceback |
| error_details = traceback.format_exc() |
| logger.error(f"获取分组名称失败: {str(e)}\n{error_details}") |
| return {"status": "error", "message": f"获取分组名称失败: {str(e)}"} |
| else: |
| |
| r_code = f""" |
| # 加载数据文件 |
| raw_data <- readRDS("{file_path}") |
| |
| # 保存处理后的数据 |
| saveRDS(raw_data, file = "{os.path.join(project_dir, 'processed_data.rds')}") |
| """ |
| |
| |
| result = RExecutor.execute_r_code(r_code, working_dir=project_dir) |
| |
| return {"status": "success"} |
| |
| except Exception as e: |
| logger.error(f"Failed to process upload: {e}") |
| return {"status": "error", "message": str(e)} |
|
|
| @staticmethod |
| def get_group_info(project_id): |
| """获取指定项目的分组信息""" |
| try: |
| from api.models import Project |
| project = Project.objects.get(id=project_id) |
| user_id = project.user.id |
| |
| |
| project_dir = os.path.join(settings.MEDIA_ROOT, f'users/{user_id}/projects/{project_id}/data') |
| ramex_data_path = os.path.join(project_dir, 'ramex_data.rds') |
| |
| |
| if not os.path.exists(ramex_data_path): |
| return {"status": "error", "message": "RamEx数据文件不存在,请先上传数据"} |
| |
| |
| r_code = f""" |
| # 加载必要的包 |
| library(RamEx) |
| |
| # 加载RamEx数据对象 |
| RamEx_data <- readRDS("{ramex_data_path}") |
| |
| # 获取分组信息并转换为字符串 |
| groups <- as.character(levels(RamEx_data@meta.data$group)) |
| |
| # 将结果转换为更容易处理的格式 |
| paste(groups, collapse="|") |
| """ |
| |
| |
| result = RExecutor.execute_r_code(r_code, working_dir=project_dir) |
| |
| |
| if result is not None: |
| result_str = str(result[0]) if hasattr(result, '__iter__') else str(result) |
| |
| result_str = result_str.strip("[]'\"") |
| |
| if result_str: |
| |
| group_names = [name.strip() for name in result_str.split("|") if name.strip()] |
| if group_names: |
| return {"status": "success", "group_names": group_names} |
| |
| |
| return {"status": "error", "message": "未能获取有效的分组名称"} |
| |
| except Exception as e: |
| logger.error(f"Failed to get group info: {e}") |
| import traceback |
| logger.error(traceback.format_exc()) |
| return {"status": "error", "message": str(e)} |
|
|
| @staticmethod |
| def update_wavenumber_range(project_id, file_id, wavenumber_range): |
| """更新波数范围并重新生成ramex_data.rds文件""" |
| try: |
| from api.models import Project, DataFile |
| project = Project.objects.get(id=project_id) |
| file = DataFile.objects.get(id=file_id) |
| user_id = project.user.id |
| |
| |
| project_dir = os.path.join(settings.MEDIA_ROOT, f'users/{user_id}/projects/{project_id}/data') |
| extract_dir = os.path.join(project_dir, 'txt_files') |
| |
| |
| min_wavenumber, max_wavenumber = wavenumber_range |
| cutoff_range = f"c({min_wavenumber}, {max_wavenumber})" |
| |
| |
| group_index_value = 2 |
| if hasattr(file, 'group_index') and file.group_index is not None: |
| group_index_value = file.group_index |
| |
| |
| r_code = f""" |
| # 加载必要的包 |
| library(RamEx) |
| |
| # 设置数据路径 |
| datas_path <- "{extract_dir}" |
| |
| # 读取数据 |
| RamEx_data <- read.spec( |
| data_path = datas_path, |
| group.index = {group_index_value}, # 使用正确的group_index值 |
| cutoff = {cutoff_range}, # 更新的波数范围 |
| interpolation = TRUE # 进行插值 |
| ) |
| |
| # 保存RamEx数据对象 |
| saveRDS(RamEx_data, file = "{os.path.join(project_dir, 'ramex_data.rds')}") |
| """ |
| |
| |
| result = RExecutor.execute_r_code(r_code, working_dir=project_dir) |
| |
| return {"status": "success"} |
| |
| except Exception as e: |
| logger.error(f"Failed to update wavenumber range: {e}") |
| import traceback |
| logger.error(traceback.format_exc()) |
| return {"status": "error", "message": str(e)} |
|
|
| @staticmethod |
| def execute_preprocessing(project_id, params): |
| """执行预处理任务""" |
| try: |
| from api.models import Project |
| project = Project.objects.get(id=project_id) |
| user_id = project.user.id |
| |
| |
| json_params = params |
| if isinstance(json_params, str) or isinstance(json_params, bytes): |
| import json |
| json_params = json.loads(json_params) |
| |
| |
| project_dir = os.path.join(settings.MEDIA_ROOT, f'users/{user_id}/projects/{project_id}/data') |
| ramex_data_path = os.path.join(project_dir, 'ramex_data.rds') |
| processed_data_path = os.path.join(project_dir, 'processed_RamEx_data.rds') |
| |
| |
| imgs_dir = os.path.join(settings.MEDIA_ROOT, f'users/{user_id}/projects/{project_id}/imgs') |
| os.makedirs(imgs_dir, exist_ok=True) |
| mean_spec_plot_path = os.path.join(imgs_dir, 'mean_spec_plot.png') |
| |
| |
| if not os.path.exists(ramex_data_path): |
| return {"status": "error", "message": "RamEx数据文件不存在,请先上传数据"} |
| |
|
|
| |
| r_code = f""" |
| # 加载所有必要的包 |
| library(RamEx) |
| library(ggplot2) |
| |
| # 读取数据 |
| RamEx_data <- readRDS("{ramex_data_path}") |
| # 查看数据分组 |
| print(levels(RamEx_data@meta.data$group)) |
| |
| # 质量控制前先做一步预处理Preprocessing.OneStep |
| data_preprocessed <- Preprocessing.OneStep(RamEx_data) |
| """ |
| |
| |
| qc_params = json_params.get('quality_control', {}) |
| qc_methods = qc_params.get('methods', []) |
| qc_parameters = qc_params.get('parameters', {}) |
| |
| |
| if 'euclidean' in qc_methods: |
| max_distance = qc_parameters.get('max_distance', '1') |
| r_code += f""" |
| # 欧氏距离质量控制 |
| qc_dis <- Qualitycontrol.Dis( |
| data_preprocessed, |
| max.dis = {max_distance} |
| ) |
| high_quality_samples <- qc_dis$quality |
| data_preprocessed <- data_preprocessed[high_quality_samples, ] |
| |
| # 检查数据是否为空 |
| if(nrow(data_preprocessed@datasets$raw.data) == 0) {{ |
| stop("数据集在欧氏距离过滤后为空,请调整参数后重试。") |
| }} |
| """ |
| |
| |
| if 'snr' in qc_methods: |
| snr_level = qc_parameters.get('snr_level', 'medium') |
| r_code += f""" |
| # 信噪比质量控制 |
| qc_snr <- Qualitycontrol.Snr(data_preprocessed, level = "{snr_level}") |
| |
| high_quality_samples_snr <- as.logical(qc_snr$quality) |
| data_preprocessed <- data_preprocessed[high_quality_samples_snr, ] |
| |
| # 检查数据是否为空 |
| if(nrow(data_preprocessed@datasets$raw.data) == 0) {{ |
| stop("数据集在SNR过滤后为空,请调整参数后重试。") |
| }} |
| """ |
| |
| |
| if 'icod' in qc_methods: |
| var_tol = qc_parameters.get('var_tol', '0.5') |
| max_iterations = qc_parameters.get('max_iterations', '100') |
| r_code += f""" |
| # ICOD质量控制 |
| qc_icod <- Qualitycontrol.ICOD( |
| data_preprocessed, |
| var_tol = {var_tol}, |
| max_iterations = {max_iterations}, |
| kernel = c(1, 1, 1) |
| ) |
| data_preprocessed <- data_preprocessed[qc_icod$quality, ] |
| |
| # 检查数据是否为空 |
| if(nrow(data_preprocessed@datasets$raw.data) == 0) {{ |
| stop("数据集在ICOD过滤后为空,请调整参数后重试。") |
| }} |
| """ |
| |
| |
| if 'hotelling' in qc_methods: |
| signif = qc_parameters.get('signif', '0.05') |
| r_code += f""" |
| # Hotelling T2质量控制 |
| qc_t2 <- Qualitycontrol.T2(data_preprocessed, signif = {signif}) |
| data_preprocessed <- data_preprocessed[qc_t2$quality, ] |
| |
| # 检查数据是否为空 |
| if(nrow(data_preprocessed@datasets$raw.data) == 0) {{ |
| stop("数据集在T2过滤后为空,请调整参数后重试。") |
| }} |
| """ |
| |
| |
| if 'pcmcd' in qc_methods: |
| fraction_mcd = qc_parameters.get('fraction_mcd', '0.5') |
| alpha_mcd = qc_parameters.get('alpha_mcd', '0.01') |
| r_code += f""" |
| # PC-MCD质量控制 |
| qc_mcd <- Qualitycontrol.Mcd(data_preprocessed, h = {fraction_mcd}, alpha = {alpha_mcd}) |
| data_preprocessed <- data_preprocessed[qc_mcd$quality, ] |
| |
| # 检查数据是否为空 |
| if(nrow(data_preprocessed@datasets$raw.data) == 0) {{ |
| stop("数据集在MCD过滤后为空,请调整参数后重试。") |
| }} |
| """ |
| |
| |
| spike_params = json_params.get('spike_removal', {}) |
| if spike_params.get('enabled', False): |
| device = spike_params.get('parameters', {}).get('device', 'CPU') |
| r_code += f""" |
| # 峰值去除 |
| data_preprocessed <- Preprocessing.Spike( |
| data_preprocessed, |
| device = "{device}" |
| ) |
| """ |
| |
| |
| smooth_params = json_params.get('smoothing', {}) |
| smooth_methods = smooth_params.get('methods', []) |
| smooth_parameters = smooth_params.get('parameters', {}) |
| |
| |
| for smooth_method in smooth_methods: |
| if smooth_method == 'savitzky-golay': |
| m_order = smooth_parameters.get('differentiation_order', '0') |
| p_order = smooth_parameters.get('polynomial_order', '5') |
| w_size = smooth_parameters.get('window_size', '11') |
| r_code += f""" |
| # Savitzky-Golay平滑 |
| data_preprocessed <- Preprocessing.Smooth.Sg( |
| data_preprocessed, |
| m = {m_order}, # 微分阶数 |
| p = {p_order}, # 多项式阶数 |
| w = {w_size} # 窗口大小 |
| ) |
| """ |
| elif smooth_method == 'snv': |
| r_code += f""" |
| # 标准正态变量变换(SNV)平滑 |
| data_preprocessed <- Preprocessing.Smooth.Snv(data_preprocessed) |
| """ |
| |
| |
| baseline_params = json_params.get('baseline', {}) |
| baseline_methods = baseline_params.get('methods', []) |
| baseline_parameters = baseline_params.get('parameters', {}) |
| |
| |
| for baseline_method in baseline_methods: |
| if baseline_method == 'polyfit': |
| order_fingerprint = baseline_parameters.get('poly_order_fingerprint', '1') |
| order_others = baseline_parameters.get('poly_order_others', '6') |
| |
| r_code += f""" |
| # 多项式拟合基线校正 |
| data_preprocessed <- Preprocessing.Baseline.Polyfit( |
| data_preprocessed, |
| order_1 = {order_fingerprint}, # 指纹区域多项式阶数 |
| order_2 = {order_others} # 其它区域多项式阶数 |
| ) |
| """ |
| elif baseline_method == 'bubble': |
| bubble_width = baseline_parameters.get('min_bubble_width', '50') |
| r_code += f""" |
| # 气泡法基线校正 |
| data_preprocessed <- Preprocessing.Baseline.Bubble( |
| data_preprocessed, |
| min_bubble_widths = {bubble_width} |
| ) |
| """ |
| |
| |
| truncation_params = json_params.get('truncation', {}) |
| if truncation_params.get('enabled', False): |
| truncation_parameters = truncation_params.get('parameters', {}) |
| min_wave = truncation_parameters.get('wavenumber_min', '550') |
| max_wave = truncation_parameters.get('wavenumber_max', '1800') |
| r_code += f""" |
| # 波数范围截断 |
| data_preprocessed <- Preprocessing.Cutoff( |
| data_preprocessed, |
| from = {min_wave}, # 起始波数 |
| to = {max_wave} # 结束波数 |
| ) |
| """ |
| |
| |
| normalization_params = json_params.get('normalization', {}) |
| normalize_method = normalization_params.get('method') |
| |
| if normalize_method == 'specific': |
| peak_value = normalization_params.get('parameters', {}).get('peak_value') |
| if peak_value: |
| r_code += f""" |
| # 特定峰值归一化 |
| data_preprocessed <- Preprocessing.Normalize( |
| data_preprocessed, |
| method = "specific", |
| wave = {peak_value} |
| ) |
| """ |
| elif normalize_method and normalize_method != 'specific': |
| r_code += f""" |
| # {normalize_method}归一化 |
| data_preprocessed <- Preprocessing.Normalize( |
| data_preprocessed, |
| method = "{normalize_method}", |
| wave = NULL |
| ) |
| """ |
| else: |
| |
| logger.info("用户选择不使用任何归一化方法") |
| |
| |
| r_code += f""" |
| # 保存最终处理后的数据对象 |
| saveRDS(data_preprocessed, file = "{processed_data_path}") |
| |
| # 最后生成平均光谱图 |
| # 确保归一化数据存在,如果不存在则使用处理后的数据 |
| if ("normalized.data" %in% names(data_preprocessed@datasets)) {{ |
| plot_data <- data_preprocessed@datasets$normalized.data |
| }} else if ("processed.data" %in% names(data_preprocessed@datasets)) {{ |
| plot_data <- data_preprocessed@datasets$processed.data |
| }} else {{ |
| plot_data <- data_preprocessed@datasets$raw.data |
| }} |
| |
| # 生成平均光谱图 |
| plot_object <- mean.spec(plot_data, data_preprocessed@meta.data$group) |
| # 保存图像到指定路径 |
| ggsave("{mean_spec_plot_path}", plot = plot_object, width = 10, height = 6, dpi = 300) |
| |
| # 输出处理完成信息 |
| print("光谱数据预处理流程已完成!") |
| |
| # 返回固定的成功标志 |
| "SUCCESS" |
| """ |
| |
| |
| try: |
| result = RExecutor.execute_r_code(r_code, working_dir=project_dir) |
| print(f"R代码执行结果: {result}") |
| |
| |
| img_rel_path = f'/media/users/{user_id}/projects/{project_id}/imgs/mean_spec_plot.png' |
| |
| |
| if os.path.exists(mean_spec_plot_path): |
| |
| return { |
| "status": "success", |
| "message": "预处理完成", |
| "plot_url": img_rel_path |
| } |
| else: |
| |
| return { |
| "status": "success", |
| "message": "预处理已完成,但未能生成平均光谱图", |
| } |
| except Exception as e: |
| logger.error(f"预处理失败: {str(e)}") |
| import traceback |
| logger.error(traceback.format_exc()) |
| return {"status": "error", "message": f"预处理失败: {str(e)}"} |
| |
| except Exception as e: |
| logger.error(f"预处理失败: {str(e)}") |
| import traceback |
| logger.error(traceback.format_exc()) |
| return {"status": "error", "message": f"预处理失败: {str(e)}"} |
| |
| @staticmethod |
| def execute_meta_free_analysis(project_id, params): |
| """执行无元数据分析任务""" |
| try: |
| from api.models import Project |
| project = Project.objects.get(id=project_id) |
| user_id = project.user.id |
| |
| |
| project_dir = os.path.join(settings.MEDIA_ROOT, f'users/{user_id}/projects/{project_id}/data') |
| processed_data_path = os.path.join(project_dir, 'processed_RamEx_data.rds') |
| |
| |
| imgs_dir = os.path.join(settings.MEDIA_ROOT, f'users/{user_id}/projects/{project_id}/imgs/meta_free') |
| os.makedirs(imgs_dir, exist_ok=True) |
| |
| |
| if not os.path.exists(processed_data_path): |
| return {"status": "error", "message": "处理后的数据文件不存在,请先完成预处理"} |
| |
| |
| dim_reduction = params.get('dim_reduction', {}) |
| dim_reduction_methods = dim_reduction.get('methods', []) |
| dim_reduction_params = dim_reduction.get('parameters', {}) |
| |
| phenotype = params.get('phenotype', {}) |
| phenotype_methods = phenotype.get('methods', []) |
| phenotype_params = phenotype.get('parameters', {}) |
| |
| spectral_decomposition = params.get('spectral_decomposition', {}) |
| spectral_decomposition_methods = spectral_decomposition.get('methods', []) |
| spectral_decomposition_params = spectral_decomposition.get('parameters', {}) |
| |
| intra_ramanome = params.get('intra_ramanome', {}) |
| intra_ramanome_methods = intra_ramanome.get('methods', []) |
| intra_ramanome_params = intra_ramanome.get('parameters', {}) |
| |
| |
| |
| bands_csv_file = None |
| if params.get('bands_csv_path', None): |
| |
| bands_csv_file = os.path.join(project_dir, 'bands_ann.csv') |
| print(bands_csv_file) |
| if not os.path.exists(bands_csv_file): |
| bands_csv_file = None |
| |
| |
| r_code = f""" |
| # 加载必要的包 |
| library(RamEx) |
| library(ggplot2) |
| |
| ######################################################################## |
| # 1. 数据加载与分组设置 |
| ######################################################################## |
| # 读取数据 |
| RamEx_data <- tryCatch({{ |
| readRDS("{processed_data_path}") |
| }}, error = function(e) {{ |
| print(paste("数据加载失败:", e$message)) |
| print("尝试使用示例数据...") |
| return(RamEx_data) |
| }}) |
| # 使用示例数据 |
| # data(RamEx_data) |
| |
| # 数据预处理 |
| data_processed <- tryCatch({{ |
| Preprocessing.OneStep(RamEx_data) |
| }}, error = function(e) {{ |
| print(paste("数据预处理失败:", e$message)) |
| return(NULL) |
| }}) |
| |
| # 创建结果列表 |
| result_list <- list() |
| result_plots <- list() |
| pca_result <- NULL |
| tsne_result <- NULL |
| umap_result <- NULL |
| pcoa_result <- NULL |
| louvain_clusters <- NULL |
| kmeans_result <- NULL |
| |
| # 检查数据是否成功加载和处理 |
| if (!is.null(data_processed)) {{ |
| # 查看数据分组 |
| tryCatch({{ |
| group_levels <- levels(data_processed@meta.data$group) |
| result_list$groups <- as.character(group_levels) |
| print("数据分组:") |
| print(group_levels) |
| }}, error = function(e) {{ |
| print(paste("查看数据分组失败:", e$message)) |
| }}) |
| """ |
| |
| |
| if 'pca' in dim_reduction_methods: |
| pca_params = dim_reduction_params.get('pca', {}) |
| n_pc = pca_params.get('n_pc', 2) |
| |
| r_code += f""" |
| ######################################################################## |
| # 2. 特征降维方法 - PCA |
| ######################################################################## |
| print("执行PCA降维...") |
| pca_result <- tryCatch({{ |
| Feature.Reduction.Pca( |
| data_processed, |
| show = FALSE, # 不显示PCA图 |
| save = TRUE, # 保存图像 |
| n_pc = {n_pc}) # 保存主成分数量 |
| }}, error = function(e) {{ |
| print(paste("PCA降维失败:", e$message)) |
| return(NULL) |
| }}) |
| if (!is.null(pca_result)) {{ |
| print("PCA降维完成") |
| # 保存PCA结果 |
| result_list$pca <- list(status = "success") |
| # 将生成的图保存 |
| if(file.exists("Reduction.pca.png")) {{ |
| file.copy("Reduction.pca.png", "{imgs_dir}/Reduction.pca.png", overwrite = TRUE) |
| result_plots$pca <- "/media/users/{user_id}/projects/{project_id}/imgs/meta_free/Reduction.pca.png" |
| }} |
| }} else {{ |
| result_list$pca <- list(status = "error", message = "PCA降维失败") |
| }} |
| """ |
| |
| if 'tsne' in dim_reduction_methods: |
| tsne_params = dim_reduction_params.get('tsne', {}) |
| pca = tsne_params.get('pca', 20) |
| n_pc = tsne_params.get('n_pc', 2) |
| perplexity = tsne_params.get('perplexity', 5) |
| theta = tsne_params.get('theta', 0.5) |
| max_iter = tsne_params.get('max_iter', 1000) |
| |
| r_code += f""" |
| ######################################################################## |
| # 2. 特征降维方法 - t-SNE |
| ######################################################################## |
| print("执行t-SNE降维...") |
| tsne_result <- tryCatch({{ |
| Feature.Reduction.Tsne( |
| data_processed, |
| PCA = {pca}, # 使用主成分数量进行预降维 |
| n_pc = {n_pc}, # 保存t-SNE组分数量 |
| perplexity = {perplexity}, # 困惑度参数 |
| theta = {theta}, # Barnes-Hut算法精度参数 |
| max_iter = {max_iter}, # 最大迭代次数 |
| show = FALSE, # 不显示t-SNE图 |
| save = TRUE, # 保存图像 |
| seed = 42 # 随机种子 |
| ) |
| }}, error = function(e) {{ |
| print(paste("t-SNE降维失败:", e$message)) |
| return(NULL) |
| }}) |
| if (!is.null(tsne_result)) {{ |
| print("t-SNE降维完成") |
| # 保存t-SNE结果 |
| result_list$tsne <- list(status = "success") |
| # 将生成的图保存 |
| if(file.exists("Reduction.tsne.png")) {{ |
| file.copy("Reduction.tsne.png", "{imgs_dir}/Reduction.tsne.png", overwrite = TRUE) |
| result_plots$tsne <- "/media/users/{user_id}/projects/{project_id}/imgs/meta_free/Reduction.tsne.png" |
| }} |
| }} else {{ |
| result_list$tsne <- list(status = "error", message = "t-SNE降维失败") |
| }} |
| """ |
| |
| if 'umap' in dim_reduction_methods: |
| umap_params = dim_reduction_params.get('umap', {}) |
| pca = umap_params.get('pca', 20) |
| n_pc = umap_params.get('n_pc', 2) |
| n_neighbors = umap_params.get('n_neighbors', 30) |
| min_dist = umap_params.get('min_dist', 0.01) |
| spread = umap_params.get('spread', 1) |
| |
| r_code += f""" |
| ######################################################################## |
| # 2. 特征降维方法 - UMAP |
| ######################################################################## |
| print("执行UMAP降维...") |
| umap_result <- tryCatch({{ |
| Feature.Reduction.Umap( |
| data_processed, |
| PCA = {pca}, # 使用主成分数量进行预降维 |
| n_pc = {n_pc}, # 保存UMAP组分数量 |
| n_neighbors = {n_neighbors}, # 局部邻居数量 |
| min.dist = {min_dist}, # 最小距离 |
| spread = {spread}, # 扩散参数 |
| show = FALSE, # 不显示UMAP图 |
| save = TRUE, # 保存图像 |
| seed = 42 # 随机种子 |
| ) |
| }}, error = function(e) {{ |
| print(paste("UMAP降维失败:", e$message)) |
| return(NULL) |
| }}) |
| if (!is.null(umap_result)) {{ |
| print("UMAP降维完成") |
| # 保存UMAP结果 |
| result_list$umap <- list(status = "success") |
| # 将生成的图保存 |
| if(file.exists("Reduction.umap.png")) {{ |
| file.copy("Reduction.umap.png", "{imgs_dir}/Reduction.umap.png", overwrite = TRUE) |
| result_plots$umap <- "/media/users/{user_id}/projects/{project_id}/imgs/meta_free/Reduction.umap.png" |
| }} |
| }} else {{ |
| result_list$umap <- list(status = "error", message = "UMAP降维失败") |
| }} |
| """ |
| |
| if 'pcoa' in dim_reduction_methods: |
| pcoa_params = dim_reduction_params.get('pcoa', {}) |
| pca = pcoa_params.get('pca', 20) |
| n_pc = pcoa_params.get('n_pc', 2) |
| distance = pcoa_params.get('distance', 'euclidean') |
| |
| r_code += f""" |
| ######################################################################## |
| # 2. 特征降维方法 - PCoA |
| ######################################################################## |
| print("执行PCoA降维...") |
| pcoa_result <- tryCatch({{ |
| Feature.Reduction.Pcoa( |
| data_processed, |
| PCA = {pca}, # 使用主成分数量进行预降维 |
| n_pc = {n_pc}, # 保存PCoA组分数量 |
| distance = "{distance}", # 距离计算方法 |
| show = FALSE, # 不显示PCoA图 |
| save = TRUE # 保存图像 |
| ) |
| }}, error = function(e) {{ |
| print(paste("PCoA降维失败:", e$message)) |
| return(NULL) |
| }}) |
| if (!is.null(pcoa_result)) {{ |
| print("PCoA降维完成") |
| # 保存PCoA结果 |
| result_list$pcoa <- list(status = "success") |
| # 将生成的图保存 |
| if(file.exists("Reduction.pcoa.png")) {{ |
| file.copy("Reduction.pcoa.png", "{imgs_dir}/Reduction.pcoa.png", overwrite = TRUE) |
| result_plots$pcoa <- "/media/users/{user_id}/projects/{project_id}/imgs/meta_free/Reduction.pcoa.png" |
| }} |
| }} else {{ |
| result_list$pcoa <- list(status = "error", message = "PCoA降维失败") |
| }} |
| """ |
| |
| |
| if 'louvain' in phenotype_methods: |
| louvain_params = phenotype_params.get('louvain', {}) |
| resolution = louvain_params.get('resolution', 0.4) |
| n_pc = louvain_params.get('n_pc', 10) |
| threshold = louvain_params.get('threshold', 0.001) |
| k = louvain_params.get('k', 30) |
| n_tree = louvain_params.get('n_tree', 50) |
| |
| r_code += f""" |
| ######################################################################## |
| # 3. 聚类分析 - Louvain聚类 |
| ######################################################################## |
| print("执行Louvain聚类...") |
| louvain_clusters <- tryCatch({{ |
| Phenotype.Analysis.Louvaincluster( |
| data_processed, |
| resolutions = {resolution}, # 聚类分辨率 |
| n_pc = {n_pc}, # 使用主成分数量 |
| threshold = {threshold}, # 聚类阈值 |
| k = {k}, # 构建图时的邻居数量 |
| n_tree = {n_tree}, # 随机投影树的数量 |
| seed = 42 # 随机种子 |
| ) |
| }}, error = function(e) {{ |
| print(paste("Louvain聚类失败:", e$message)) |
| return(NULL) |
| }}) |
| |
| if (!is.null(louvain_clusters)) {{ |
| print("Louvain聚类完成") |
| # 保存Louvain聚类结果 |
| result_list$louvain <- list(status = "success") |
| |
| # 使用降维结果可视化聚类结果 |
| if (exists("umap_result") && !is.null(umap_result)) {{ |
| tryCatch({{ |
| # 绘制UMAP+Louvain聚类图 |
| p <- Plot.reductions( |
| umap_result, |
| reduction = "UMAP", |
| dims = c(1, 2), |
| color = louvain_clusters[,1], |
| cols = NULL, |
| point_size = 1, |
| point_alpha = 0.5, |
| quantile_range = c(0.05, 0.95) |
| ) |
| ggsave("{imgs_dir}/umap_louvain_clusters.png", p, width = 6, height = 5, dpi = 300) |
| result_plots$louvain <- "/media/users/{user_id}/projects/{project_id}/imgs/meta_free/umap_louvain_clusters.png" |
| }}, error = function(e) {{ |
| print(paste("Louvain聚类可视化失败:", e$message)) |
| }}) |
| }} |
| }} else {{ |
| result_list$louvain <- list(status = "error", message = "Louvain聚类失败") |
| }} |
| """ |
| |
| if 'hca' in phenotype_methods: |
| r_code += f""" |
| ######################################################################## |
| # 3. 聚类分析 - 层次聚类分析(HCA) |
| ######################################################################## |
| print("执行层次聚类分析...") |
| hca_result <- tryCatch({{ |
| Phenotype.Analysis.Hca( |
| data_processed, |
| show = FALSE # 不显示聚类树状图 |
| ) |
| }}, error = function(e) {{ |
| print(paste("层次聚类分析失败:", e$message)) |
| return(NULL) |
| }}) |
| |
| if (!is.null(hca_result)) {{ |
| print("层次聚类分析完成") |
| result_list$hca <- list(status = "success") |
| |
| # 保存聚类树状图 |
| tryCatch({{ |
| png("{imgs_dir}/hca_result.png", width = 1200, height = 900) |
| plot(hca_result) # 直接绘制hclust对象 |
| dev.off() |
| result_plots$hca <- "/media/users/{user_id}/projects/{project_id}/imgs/meta_free/hca_result.png" |
| }}, error = function(e) {{ |
| print(paste("保存HCA树状图失败:", e$message)) |
| }}) |
| }} else {{ |
| result_list$hca <- list(status = "error", message = "层次聚类分析失败") |
| }} |
| """ |
| |
| if 'kmeans' in phenotype_methods: |
| kmeans_params = phenotype_params.get('kmeans', {}) |
| k = kmeans_params.get('k', 4) |
| n_pc = kmeans_params.get('n_pc', 10) |
| |
| r_code += f""" |
| ######################################################################## |
| # 3. 聚类分析 - K-means聚类 |
| ######################################################################## |
| print("执行K-means聚类...") |
| kmeans_result <- tryCatch({{ |
| Phenotype.Analysis.Kmeans( |
| data_processed, |
| k = {k}, # 聚类数量 |
| n_pc = {n_pc} # 使用主成分数量 |
| ) |
| }}, error = function(e) {{ |
| print(paste("K-means聚类失败:", e$message)) |
| return(NULL) |
| }}) |
| |
| if (!is.null(kmeans_result)) {{ |
| print("K-means聚类完成") |
| result_list$kmeans <- list(status = "success") |
| |
| # 使用降维结果可视化聚类结果 |
| if (exists("umap_result") && !is.null(umap_result)) {{ |
| tryCatch({{ |
| # 绘制UMAP+K-means聚类图 |
| p <- Plot.reductions( |
| umap_result, |
| reduction = "UMAP", |
| dims = c(1, 2), |
| color = as.factor(kmeans_result$clusters), |
| cols = NULL, |
| point_size = 1, |
| point_alpha = 0.5, |
| quantile_range = c(0.05, 0.95) |
| ) |
| ggsave("{imgs_dir}/umap_kmeans_clusters.png", p, width = 6, height = 5, dpi = 300) |
| result_plots$kmeans <- "/media/users/{user_id}/projects/{project_id}/imgs/meta_free/umap_kmeans_clusters.png" |
| }}, error = function(e) {{ |
| print(paste("K-means聚类可视化失败:", e$message)) |
| }}) |
| }} |
| }} else {{ |
| result_list$kmeans <- list(status = "error", message = "K-means聚类失败") |
| }} |
| """ |
| |
| |
| if 'mcrals' in spectral_decomposition_methods: |
| mcrals_params = spectral_decomposition_params.get('mcrals', {}) |
| n_comp = mcrals_params.get('n_comp', 3) |
| |
| r_code += f""" |
| ######################################################################## |
| # 4. 谱图分解 - MCR-ALS分解 |
| ######################################################################## |
| print("执行MCR-ALS分解...") |
| mcrals_result <- tryCatch({{ |
| Spectral.Decomposition.Mcrals( |
| data_processed, |
| n_comp = {n_comp}, # 提取组分数量 |
| seed = 42 # 随机种子 |
| ) |
| }}, error = function(e) {{ |
| print(paste("MCR-ALS分解失败:", e$message)) |
| return(NULL) |
| }}) |
| |
| if (!is.null(mcrals_result)) {{ |
| print("MCR-ALS分解完成") |
| result_list$mcrals <- list(status = "success") |
| |
| # 使用结果绘制小提琴图和均值光谱图 |
| tryCatch({{ |
| # 小提琴图 |
| p1 <- Plot.ViolinBox(mcrals_result$concentration, data_processed$group) |
| ggsave("{imgs_dir}/MCR-ALS_ViolinBox.png", p1, dpi = 300) |
| |
| # 均值光谱图 |
| p2 <- mean.spec(t(mcrals_result$components), as.factor(colnames(mcrals_result$components)), gap=0) |
| ggsave("{imgs_dir}/MCR-ALS_mean.spec.png", p2, dpi = 300) |
| |
| # 添加到结果图像列表 |
| result_plots$mcrals_violin <- "/media/users/{user_id}/projects/{project_id}/imgs/meta_free/MCR-ALS_ViolinBox.png" |
| result_plots$mcrals_spec <- "/media/users/{user_id}/projects/{project_id}/imgs/meta_free/MCR-ALS_mean.spec.png" |
| }}, error = function(e) {{ |
| print(paste("MCR-ALS结果可视化失败:", e$message)) |
| }}) |
| }} else {{ |
| result_list$mcrals <- list(status = "error", message = "MCR-ALS分解失败") |
| }} |
| """ |
| |
| if 'ica' in spectral_decomposition_methods: |
| ica_params = spectral_decomposition_params.get('ica', {}) |
| n_comp = ica_params.get('n_comp', 3) |
| |
| r_code += f""" |
| ######################################################################## |
| # 4. 谱图分解 - ICA分解 |
| ######################################################################## |
| print("执行ICA分解...") |
| ica_result <- tryCatch({{ |
| Spectral.Decomposition.Ica( |
| data_processed, |
| n_comp = {n_comp}, # 提取独立组分数量 |
| seed = 42 # 随机种子 |
| ) |
| }}, error = function(e) {{ |
| print(paste("ICA分解失败:", e$message)) |
| return(NULL) |
| }}) |
| |
| if (!is.null(ica_result)) {{ |
| print("ICA分解完成") |
| result_list$ica <- list(status = "success") |
| |
| # 使用结果绘制小提琴图和均值光谱图 |
| tryCatch({{ |
| # 小提琴图 |
| p1 <- Plot.ViolinBox(ica_result$Y, data_processed$group) |
| ggsave("{imgs_dir}/ICA_ViolinBox.png", p1, dpi = 300) |
| |
| # 均值光谱图 |
| p2 <- mean.spec( |
| t(ica_result$M), |
| as.factor(seq_len(ncol(ica_result$M))), |
| gap = 0 |
| ) |
| ggsave("{imgs_dir}/ICA_mean.spec.png", p2, dpi = 300) |
| |
| # 添加到结果图像列表 |
| result_plots$ica_violin <- "/media/users/{user_id}/projects/{project_id}/imgs/meta_free/ICA_ViolinBox.png" |
| result_plots$ica_spec <- "/media/users/{user_id}/projects/{project_id}/imgs/meta_free/ICA_mean.spec.png" |
| }}, error = function(e) {{ |
| print(paste("ICA结果可视化失败:", e$message)) |
| }}) |
| }} else {{ |
| result_list$ica <- list(status = "error", message = "ICA分解失败") |
| }} |
| """ |
| |
| if 'nmf' in spectral_decomposition_methods: |
| nmf_params = spectral_decomposition_params.get('nmf', {}) |
| n_comp = nmf_params.get('n_comp', 3) |
| max_iter = nmf_params.get('max_iter', 100) |
| tol = nmf_params.get('tol', 1e-04) |
| |
| r_code += f""" |
| ######################################################################## |
| # 4. 谱图分解 - NMF分解 |
| ######################################################################## |
| print("执行NMF分解...") |
| nmf_result <- tryCatch({{ |
| Spectral.Decomposition.Nmf( |
| data_processed, |
| n_comp = {n_comp}, # 提取非负组分数量 |
| seed = 42, # 随机种子 |
| max_iter = {max_iter}, # 最大迭代次数 |
| tol = {tol}, # 收敛容差 |
| verbose = FALSE # 不显示迭代过程 |
| ) |
| }}, error = function(e) {{ |
| print(paste("NMF分解失败:", e$message)) |
| return(NULL) |
| }}) |
| |
| if (!is.null(nmf_result)) {{ |
| print("NMF分解完成") |
| result_list$nmf <- list(status = "success") |
| |
| # 使用结果绘制小提琴图和均值光谱图 |
| tryCatch({{ |
| # 小提琴图 |
| p1 <- Plot.ViolinBox(nmf_result$basis, data_processed$group) |
| ggsave("{imgs_dir}/NMF_ViolinBox.png", p1, dpi = 300) |
| |
| # 均值光谱图 |
| p2 <- mean.spec( |
| nmf_result$coef, |
| as.factor(seq_len(nrow(nmf_result$coef))), |
| gap = 0 |
| ) |
| ggsave("{imgs_dir}/NMF_mean.spec.png", p2, dpi = 300) |
| |
| # 添加到结果图像列表 |
| result_plots$nmf_violin <- "/media/users/{user_id}/projects/{project_id}/imgs/meta_free/NMF_ViolinBox.png" |
| result_plots$nmf_spec <- "/media/users/{user_id}/projects/{project_id}/imgs/meta_free/NMF_mean.spec.png" |
| }}, error = function(e) {{ |
| print(paste("NMF结果可视化失败:", e$message)) |
| }}) |
| }} else {{ |
| result_list$nmf <- list(status = "error", message = "NMF分解失败") |
| }} |
| """ |
| |
| |
| if 'irca' in intra_ramanome_methods: |
| irca_params = intra_ramanome_params.get('irca', {}) |
| threshold = irca_params.get('threshold', 0.6) |
| |
| r_code += f""" |
| ######################################################################## |
| # 5. Ramanome内部分析 - IRCA全局分析 |
| ######################################################################## |
| print("执行IRCA全局分析...") |
| irca_global_result <- tryCatch({{ |
| Intraramanome.Analysis.Irca.Global( |
| data_processed, |
| threshold = {threshold}, # 相关性阈值 |
| show = FALSE, # 不显示IRCA图 |
| save = TRUE # 保存图像 |
| ) |
| }}, error = function(e) {{ |
| print(paste("IRCA全局分析失败:", e$message)) |
| return(NULL) |
| }}) |
| |
| if (!is.null(irca_global_result)) {{ |
| print("IRCA全局分析完成") |
| result_list$irca_global <- list(status = "success") |
| |
| # 寻找并复制生成的IRCA文件 |
| irca_files <- list.files(pattern = "IRCA_global_.*\\\\.(jpeg|png)") |
| if(length(irca_files) > 0) {{ |
| result_plots$irca_global <- list() |
| for(i in 1:length(irca_files)) {{ |
| file.copy(irca_files[i], paste0("{imgs_dir}/", irca_files[i]), overwrite = TRUE) |
| result_plots$irca_global[[i]] <- paste0("/media/users/{user_id}/projects/{project_id}/imgs/meta_free/", irca_files[i]) |
| }} |
| }} |
| }} else {{ |
| result_list$irca_global <- list(status = "error", message = "IRCA全局分析失败") |
| }} |
| """ |
| |
| if 'irca_local' in intra_ramanome_methods: |
| irca_local_params = intra_ramanome_params.get('irca_local', {}) |
| threshold = irca_local_params.get('threshold', 0.6) |
| |
| r_code += f""" |
| ######################################################################## |
| # 5. Ramanome内部分析 - IRCA局部分析 |
| ######################################################################## |
| print("执行IRCA局部分析...") |
| # 定义感兴趣的波段分组 |
| bands_ann <- data.frame( |
| Wave_num = c( |
| # 核酸相关波段 |
| 742, 850, 872, 971, 997, 1098, 1293, 1328, 1426, 1576, |
| # 蛋白质相关波段 |
| 824, 883, 1005, 1033, 1051, 1237, 1559, 1651, |
| # 脂质相关波段 |
| 1076, 1119, 1370, 2834, 2866, 2912 |
| ), |
| Group = c( |
| rep("Nucleic acid", 10), |
| rep("Protein", 8), |
| rep("Lipids", 6) |
| ) |
| ) |
| """ |
| |
| |
| if bands_csv_file: |
| r_code += f""" |
| # 使用用户上传的波段分组 |
| if(file.exists("{bands_csv_file}")) {{ |
| |
| bands_ann <- read.csv("{bands_csv_file}") |
| |
| }} |
| """ |
| |
| r_code += f""" |
| # 执行局部IRCA分析 |
| irca_local_result <- tryCatch({{ |
| Intraramanome.Analysis.Irca.Local( |
| data_processed, |
| bands_ann = bands_ann, # 波段注释 |
| threshold = {threshold}, # 相关性阈值 |
| show = FALSE, # 不显示IRCA图 |
| save = TRUE # 保存图像 |
| ) |
| }}, error = function(e) {{ |
| print(paste("IRCA局部分析失败:", e$message)) |
| return(NULL) |
| }}) |
| |
| if (!is.null(irca_local_result)) {{ |
| print("IRCA局部分析完成") |
| result_list$irca_local <- list(status = "success") |
| |
| # 寻找并复制生成的IRCA局部文件 |
| irca_local_files <- list.files(pattern = "IRCA_local_.*\\\\.(jpeg|png)") |
| if(length(irca_local_files) > 0) {{ |
| result_plots$irca_local <- list() |
| for(i in 1:length(irca_local_files)) {{ |
| file.copy(irca_local_files[i], paste0("{imgs_dir}/", irca_local_files[i]), overwrite = TRUE) |
| result_plots$irca_local[[i]] <- paste0("/media/users/{user_id}/projects/{project_id}/imgs/meta_free/", irca_local_files[i]) |
| }} |
| }} |
| }} else {{ |
| result_list$irca_local <- list(status = "error", message = "IRCA局部分析失败") |
| }} |
| """ |
| |
| if '2dcos' in intra_ramanome_methods: |
| r_code += f""" |
| ######################################################################## |
| # 5. Ramanome内部分析 - 二维相关光谱分析 (2D-COS) |
| ######################################################################## |
| print("执行2D相关分析...") |
| png("{imgs_dir}/cos_2d_plot.png", res=150) |
| cos_2d_result <- tryCatch({{ |
| Intraramanome.Analysis.2Dcos(data_processed) |
| }}, error = function(e) {{ |
| print(paste("2D相关分析失败:", e$message)) |
| dev.off() |
| return(NULL) |
| }}) |
| dev.off() |
| |
| if (!is.null(cos_2d_result)) {{ |
| print("2D相关分析完成") |
| result_list$cos_2d <- list(status = "success") |
| result_plots$cos_2d <- "/media/users/{user_id}/projects/{project_id}/imgs/meta_free/cos_2d_plot.png" |
| }} else {{ |
| result_list$cos_2d <- list(status = "error", message = "2D相关分析失败") |
| }} |
| """ |
| |
| |
| r_code += """ |
| print("Meta-free分析流程全部完成!") |
| } else { |
| print("由于数据加载或预处理失败,无法执行后续分析。") |
| result_list$overall <- list(status = "error", message = "数据加载或预处理失败") |
| } |
| |
| # 将所有结果整合 |
| final_result <- list( |
| status = "success", |
| analyses = result_list, |
| plots = result_plots |
| ) |
| |
| # 转换为JSON格式返回 |
| jsonlite::toJSON(final_result, auto_unbox = TRUE) |
| """ |
| |
| |
| print("正在执行R代码:\n%s" % r_code) |
| |
| result_json = RExecutor.execute_r_code(r_code, working_dir=project_dir) |
| |
| try: |
| import json |
| import re |
| try: |
| |
| result_str = str(result_json[0] if hasattr(result_json, '__iter__') else result_json) |
| logger.info(f"原始返回字符串: {result_str}") |
| |
| |
| result_str = re.sub(r'^\[\d+\]\s*', '', result_str) |
| |
| |
| if result_str.startswith('"') and result_str.endswith('"'): |
| |
| result_str = result_str[1:-1] |
| |
| result_str = result_str.replace('\\"', '"') |
| |
| |
| result_str = result_str.replace("\\\\", "\\") |
| |
| logger.info(f"处理后的JSON字符串: {result_str}") |
| |
| |
| try: |
| result_dict = json.loads(result_str) |
| logger.info("JSON解析成功!") |
| |
| |
| if 'plots' in result_dict: |
| for analysis_type, plots in result_dict['plots'].items(): |
| if isinstance(plots, list): |
| |
| for i, plot in enumerate(plots): |
| if not plot.startswith("/media"): |
| plots[i] = f'/media/users/{user_id}/projects/{project_id}/imgs/meta_free/{plot}' |
| |
| return result_dict |
| except json.JSONDecodeError: |
| |
| match = re.search(r'(\{.*\})', result_str) |
| if match: |
| json_str = match.group(1) |
| logger.info(f"提取的JSON: {json_str}") |
| result_dict = json.loads(json_str) |
| logger.info("JSON提取并解析成功!") |
| return result_dict |
| else: |
| raise ValueError("无法从字符串中提取JSON对象") |
| |
| except Exception as e: |
| logger.error(f"解析R分析结果失败: {e}") |
| |
| try: |
| |
| match = re.search(r'(\{.*\})', str(result_json[0])) |
| if match: |
| json_str = match.group(1) |
| logger.info(f"简单提取的JSON: {json_str}") |
| |
| json_str = json_str.replace('\\\\', '\\').replace('\\"', '"') |
| result_dict = json.loads(json_str) |
| logger.info("简单方法解析成功!") |
| return result_dict |
| else: |
| |
| original_str = str(result_json[0]) |
| |
| start = original_str.find('{') |
| end = original_str.rfind('}') + 1 |
| if start >= 0 and end > start: |
| json_str = original_str[start:end] |
| result_dict = json.loads(json_str) |
| return result_dict |
| else: |
| return { |
| "status": "error", |
| "message": "无法提取JSON,所有方法均失败", |
| "raw_result": str(result_json) |
| } |
| except Exception as ex: |
| logger.error(f"所有JSON解析方法均失败: {ex}") |
| return { |
| "status": "error", |
| "message": f"解析分析结果失败: {str(e)}", |
| "raw_result": str(result_json).replace('"', '\\"') |
| } |
| except Exception as e: |
| logger.error(f"执行基于元数据分析失败: {e}") |
| import traceback |
| logger.error(traceback.format_exc()) |
| return {"status": "error", "message": f"执行基于元数据分析失败: {str(e)}"} |
| |
| except Exception as e: |
| logger.error(f"基于元数据分析失败: {e}") |
| import traceback |
| logger.error(traceback.format_exc()) |
| return {"status": "error", "message": f"基于元数据分析失败: {str(e)}"} |
|
|
| @staticmethod |
| def execute_meta_based_analysis(project_id, params): |
| """执行基于元数据的分析任务""" |
| try: |
| from api.models import Project |
| project = Project.objects.get(id=project_id) |
| user_id = project.user.id |
| |
| |
| project_dir = os.path.join(settings.MEDIA_ROOT, f'users/{user_id}/projects/{project_id}/data') |
| processed_data_path = os.path.join(project_dir, 'processed_RamEx_data.rds') |
| |
| |
| imgs_dir = os.path.join(settings.MEDIA_ROOT, f'users/{user_id}/projects/{project_id}/imgs/meta_based') |
| os.makedirs(imgs_dir, exist_ok=True) |
| |
| |
| if not os.path.exists(processed_data_path): |
| return {"status": "error", "message": "处理后的数据文件不存在,请先完成预处理"} |
| |
| |
| classification = params.get('classification', {}) |
| classification_methods = classification.get('methods', []) |
| classification_params = classification.get('parameters', {}) |
| |
| quantification = params.get('quantification', {}) |
| quantification_methods = quantification.get('methods', []) |
| quantification_params = quantification.get('parameters', {}) |
| |
| raman_markers = params.get('raman_markers', {}) |
| raman_markers_methods = raman_markers.get('methods', []) |
| raman_markers_params = raman_markers.get('parameters', {}) |
| |
| |
| r_code = f""" |
| ################################################################################ |
| # RamEx Meta_base 分析流程 |
| ################################################################################ |
| |
| # 加载RamEx包 |
| library(RamEx) |
| library(ggplot2) |
| ################################################################################ |
| # 1. 数据加载 |
| ################################################################################ |
| |
| # 读取数据 |
| tryCatch({{ |
| RamEx_data <- readRDS("{processed_data_path}") |
| }}, error = function(e) {{ |
| message("无法读取RDS文件: ", e$message) |
| # 使用示例数据作为备选 |
| message("尝试使用示例数据...") |
| }}) |
| |
| # tryCatch({{ |
| # data(RamEx_data) |
| # data_processed <- Preprocessing.OneStep(RamEx_data) |
| # }}, error = function(e) {{ |
| # message("数据预处理失败: ", e$message) |
| # stop("数据预处理是必要步骤,无法继续执行") |
| # }}) |
| |
| # 查看数据分组 |
| group_levels <- tryCatch({{ |
| levels(data_processed@meta.data$group) |
| }}, error = function(e) {{ |
| message("无法获取数据分组信息: ", e$message) |
| NULL |
| }}) |
| |
| # 创建结果列表 |
| result_list <- list() |
| result_plots <- list() |
| |
| # 存储分组信息 |
| if (!is.null(group_levels)) {{ |
| result_list$groups <- as.character(group_levels) |
| }} |
| """ |
| |
| |
| if 'lda' in classification_methods: |
| lda_params = classification_params.get('lda', {}) |
| n_pc = lda_params.get('n_pc', 20) |
| |
| r_code += f""" |
| ################################################################################ |
| # 2. 分类分析 - LDA |
| ################################################################################ |
| |
| # LDA分类 |
| lda_model <- tryCatch({{ |
| Classification.Lda( |
| train = data_processed, # 训练数据 |
| show = FALSE, # 显示混淆矩阵 |
| save = TRUE, # 保存图片 |
| seed = 42, # 随机种子 |
| n_pc = {n_pc} # 使用的主成分数 |
| ) |
| }}, error = function(e) {{ |
| message("LDA分类失败: ", e$message) |
| return(NULL) |
| }}) |
| |
| if (!is.null(lda_model)) {{ |
| result_list$lda <- list(status = "success") |
| # 将生成的图保存到项目目录 |
| if(file.exists("Classification_PC-LDA_Train.png")) {{ |
| file.copy("Classification_PC-LDA_Train.png", "{imgs_dir}/Classification_PC-LDA_Train.png", overwrite = TRUE) |
| result_plots$lda_train <- "/media/users/{user_id}/projects/{project_id}/imgs/meta_based/Classification_PC-LDA_Train.png" |
| }} |
| if(file.exists("Classification_PC-LDA_Test.png")) {{ |
| file.copy("Classification_PC-LDA_Test.png", "{imgs_dir}/Classification_PC-LDA_Test.png", overwrite = TRUE) |
| result_plots$lda_test <- "/media/users/{user_id}/projects/{project_id}/imgs/meta_based/Classification_PC-LDA_Test.png" |
| }} |
| }} else {{ |
| result_list$lda <- list(status = "error", message = "LDA分类失败") |
| }} |
| """ |
| |
| if 'rf' in classification_methods: |
| rf_params = classification_params.get('rf', {}) |
| ntree = rf_params.get('n_estimators', 500) |
| mtry = rf_params.get('mtry', 2) |
| |
| r_code += f""" |
| ################################################################################ |
| # 2. 分类分析 - 随机森林 |
| ################################################################################ |
| |
| # 随机森林分类 |
| rf_model <- tryCatch({{ |
| Classification.Rf( |
| train = data_processed, # 训练数据 |
| ntree = {ntree}, # 森林中的树数量 |
| mtry = {mtry}, # 每个节点随机抽样的变量数 |
| show = FALSE, # 显示混淆矩阵 |
| save = TRUE, # 保存图片 |
| seed = 42 # 随机种子 |
| ) |
| }}, error = function(e) {{ |
| message("随机森林分类失败: ", e$message) |
| return(NULL) |
| }}) |
| |
| if (!is.null(rf_model)) {{ |
| result_list$rf <- list(status = "success") |
| # 将生成的图保存到项目目录 |
| if(file.exists("Classification_RF_Train.png")) {{ |
| file.copy("Classification_RF_Train.png", "{imgs_dir}/Classification_RF_Train.png", overwrite = TRUE) |
| result_plots$rf_train <- "/media/users/{user_id}/projects/{project_id}/imgs/meta_based/Classification_RF_Train.png" |
| }} |
| if(file.exists("Classification_RF_Test.png")) {{ |
| file.copy("Classification_RF_Test.png", "{imgs_dir}/Classification_RF_Test.png", overwrite = TRUE) |
| result_plots$rf_test <- "/media/users/{user_id}/projects/{project_id}/imgs/meta_based/Classification_RF_Test.png" |
| }} |
| }} else {{ |
| result_list$rf <- list(status = "error", message = "随机森林分类失败") |
| }} |
| """ |
| |
| if 'svm' in classification_methods: |
| r_code += f""" |
| ################################################################################ |
| # 2. 分类分析 - SVM |
| ################################################################################ |
| |
| # SVM分类 |
| svm_model <- tryCatch({{ |
| Classification.Svm( |
| train = data_processed, # 训练数据 |
| show = FALSE, # 显示混淆矩阵 |
| save = TRUE, # 保存图片 |
| seed = 42 # 随机种子 |
| ) |
| }}, error = function(e) {{ |
| message("SVM分类失败: ", e$message) |
| return(NULL) |
| }}) |
| |
| if (!is.null(svm_model)) {{ |
| result_list$svm <- list(status = "success") |
| # 将生成的图保存到项目目录 |
| if(file.exists("Classification_SVM_Train.png")) {{ |
| file.copy("Classification_SVM_Train.png", "{imgs_dir}/Classification_SVM_Train.png", overwrite = TRUE) |
| result_plots$svm_train <- "/media/users/{user_id}/projects/{project_id}/imgs/meta_based/Classification_SVM_Train.png" |
| }} |
| if(file.exists("Classification_SVM_Test.png")) {{ |
| file.copy("Classification_SVM_Test.png", "{imgs_dir}/Classification_SVM_Test.png", overwrite = TRUE) |
| result_plots$svm_test <- "/media/users/{user_id}/projects/{project_id}/imgs/meta_based/Classification_SVM_Test.png" |
| }} |
| }} else {{ |
| result_list$svm <- list(status = "error", message = "SVM分类失败") |
| }} |
| """ |
| |
| |
| if 'pls' in quantification_methods: |
| pls_params = quantification_params.get('pls', {}) |
| n_comp = pls_params.get('n_comp', 10) |
| |
| r_code += f""" |
| ################################################################################ |
| # 3. 回归与量化分析 - PLS |
| ################################################################################ |
| |
| # 偏最小二乘回归 (PLS) |
| pls_model <- tryCatch({{ |
| Quantification.Pls( |
| train = data_processed, # 训练数据 |
| test = NULL, # 目标变量 |
| n_comp = {n_comp}, # 组分数量 |
| save = TRUE, # 保存图片 |
| show = FALSE, # 不显示 |
| seed = 42 # 随机种子 |
| ) |
| }}, error = function(e) {{ |
| message("PLS回归分析失败: ", e$message) |
| return(NULL) |
| }}) |
| |
| if (!is.null(pls_model)) {{ |
| result_list$pls <- list(status = "success") |
| # PLS分析生成的图片 |
| if(file.exists("Quantification_Pls_Train.png")) {{ |
| file.copy("Quantification_Pls_Train.png", "{imgs_dir}/Quantification_Pls_Train.png", overwrite = TRUE) |
| result_plots$pls_train <- "/media/users/{user_id}/projects/{project_id}/imgs/meta_based/Quantification_Pls_Train.png" |
| }} |
| if(file.exists("Quantification_Pls_Test.png")) {{ |
| file.copy("Quantification_Pls_Test.png", "{imgs_dir}/Quantification_Pls_Test.png", overwrite = TRUE) |
| result_plots$pls_test <- "/media/users/{user_id}/projects/{project_id}/imgs/meta_based/Quantification_Pls_Test.png" |
| }} |
| if(file.exists("Quantification_PLS_plot.png")) {{ |
| file.copy("Quantification_PLS_plot.png", "{imgs_dir}/Quantification_PLS_plot.png", overwrite = TRUE) |
| result_plots$pls_plot <- "/media/users/{user_id}/projects/{project_id}/imgs/meta_based/Quantification_PLS_plot.png" |
| }} |
| }} else {{ |
| result_list$pls <- list(status = "error", message = "PLS回归分析失败") |
| }} |
| """ |
| |
| if 'mlr' in quantification_methods: |
| mlr_params = quantification_params.get('mlr', {}) |
| n_pc = mlr_params.get('n_pc', 10) |
| |
| r_code += f""" |
| ################################################################################ |
| # 3. 回归与量化分析 - MLR |
| ################################################################################ |
| |
| # 多元线性回归 (MLR) |
| mlr_model <- tryCatch({{ |
| Quantification.Mlr( |
| train = data_processed, # 训练数据 |
| test = NULL, # 目标变量 |
| n_pc = {n_pc}, # 组分数量 |
| save = TRUE, # 保存图片 |
| show = FALSE, # 不显示 |
| seed = 42 # 随机种子 |
| ) |
| }}, error = function(e) {{ |
| message("MLR回归分析失败: ", e$message) |
| return(NULL) |
| }}) |
| |
| if (!is.null(mlr_model)) {{ |
| result_list$mlr <- list(status = "success") |
| # MLR分析生成的图片 |
| if(file.exists("Quantification_Mlr_Train.png")) {{ |
| file.copy("Quantification_Mlr_Train.png", "{imgs_dir}/Quantification_Mlr_Train.png", overwrite = TRUE) |
| result_plots$mlr_train <- "/media/users/{user_id}/projects/{project_id}/imgs/meta_based/Quantification_Mlr_Train.png" |
| }} |
| if(file.exists("Quantification_Mlr_Test.png")) {{ |
| file.copy("Quantification_Mlr_Test.png", "{imgs_dir}/Quantification_Mlr_Test.png", overwrite = TRUE) |
| result_plots$mlr_test <- "/media/users/{user_id}/projects/{project_id}/imgs/meta_based/Quantification_Mlr_Test.png" |
| }} |
| if(file.exists("Quantification_MLR_plot.png")) {{ |
| file.copy("Quantification_MLR_plot.png", "{imgs_dir}/Quantification_MLR_plot.png", overwrite = TRUE) |
| result_plots$mlr_plot <- "/media/users/{user_id}/projects/{project_id}/imgs/meta_based/Quantification_MLR_plot.png" |
| }} |
| }} else {{ |
| result_list$mlr <- list(status = "error", message = "MLR回归分析失败") |
| }} |
| """ |
| |
| if 'glm' in quantification_methods: |
| glm_params = quantification_params.get('glm', {}) |
| n_pc = glm_params.get('n_pc', 10) |
| |
| r_code += f""" |
| ################################################################################ |
| # 3. 回归与量化分析 - GLM |
| ################################################################################ |
| |
| # 广义线性模型 (GLM) |
| glm_model <- tryCatch({{ |
| Quantification.Glm( |
| train = data_processed, # 训练数据 |
| test = NULL, # 目标变量 |
| n_pc = {n_pc}, # 主成分数量 |
| save = TRUE, # 保存图片 |
| show = FALSE, # 不显示 |
| seed = 42 # 随机种子 |
| ) |
| }}, error = function(e) {{ |
| message("GLM回归分析失败: ", e$message) |
| return(NULL) |
| }}) |
| |
| if (!is.null(glm_model)) {{ |
| result_list$glm <- list(status = "success") |
| # GLM分析生成的图片 |
| if(file.exists("Quantification_Glm_Train.png")) {{ |
| file.copy("Quantification_Glm_Train.png", "{imgs_dir}/Quantification_Glm_Train.png", overwrite = TRUE) |
| result_plots$glm_train <- "/media/users/{user_id}/projects/{project_id}/imgs/meta_based/Quantification_Glm_Train.png" |
| }} |
| if(file.exists("Quantification_Glm_Test.png")) {{ |
| file.copy("Quantification_Glm_Test.png", "{imgs_dir}/Quantification_Glm_Test.png", overwrite = TRUE) |
| result_plots$glm_test <- "/media/users/{user_id}/projects/{project_id}/imgs/meta_based/Quantification_Glm_Test.png" |
| }} |
| if(file.exists("Quantification_GLM_plot.png")) {{ |
| file.copy("Quantification_GLM_plot.png", "{imgs_dir}/Quantification_GLM_plot.png", overwrite = TRUE) |
| result_plots$glm_plot <- "/media/users/{user_id}/projects/{project_id}/imgs/meta_based/Quantification_GLM_plot.png" |
| }} |
| }} else {{ |
| result_list$glm <- list(status = "error", message = "GLM回归分析失败") |
| }} |
| """ |
| |
| |
| if 'rbcs' in raman_markers_methods: |
| rbcs_params = raman_markers_params.get('rbcs', {}) |
| threshold = rbcs_params.get('threshold', 0.002) |
| ntree = rbcs_params.get('n_estimators', 1000) |
| nfolds = rbcs_params.get('nfolds', 3) |
| |
| r_code += f""" |
| ################################################################################ |
| # 4. Raman标记分析 - RBCS |
| ################################################################################ |
| |
| # RBCS分析 - 细胞应激反应拉曼条形码 |
| RBCS.markers <- tryCatch({{ |
| Raman.Markers.Rbcs( |
| object = data_processed, # Ramanome对象 |
| outfolder = "RF_imps", # 输出文件夹路径 |
| threshold = {threshold}, # RBCS特征重要性的阈值 |
| ntree = {ntree}, # 随机森林中树的数量 |
| errortype = "oob", # 随机森林模型评估中使用的错误类型 |
| verbose = FALSE, # 是否显示进度消息 |
| nfolds = {nfolds}, # 交叉验证中使用的折数 |
| rf.cv = FALSE, # 是否执行随机森林的交叉验证 |
| show = FALSE, # 是否显示热图 |
| save = TRUE # 是否保存热图 |
| ) |
| }}, error = function(e) {{ |
| message("RBCS标记分析失败: ", e$message) |
| return(NULL) |
| }}) |
| |
| if (!is.null(RBCS.markers)) {{ |
| result_list$rbcs <- list(status = "success") |
| # 将生成的热图保存到项目目录 |
| if(file.exists("RBCS.heatmap.png")) {{ |
| file.copy("RBCS.heatmap.png", "{imgs_dir}/RBCS.heatmap.png", overwrite = TRUE) |
| result_plots$rbcs_heatmap <- "/media/users/{user_id}/projects/{project_id}/imgs/meta_based/RBCS.heatmap.png" |
| }} |
| }} else {{ |
| result_list$rbcs <- list(status = "error", message = "RBCS标记分析失败") |
| }} |
| """ |
| |
| if 'roc' in raman_markers_methods: |
| roc_params = raman_markers_params.get('roc', {}) |
| threshold = roc_params.get('threshold', 0.75) |
| batch_size = roc_params.get('batch_size', 1000) |
| |
| r_code += f""" |
| ################################################################################ |
| # 4. Raman标记分析 - ROC |
| ################################################################################ |
| |
| # ROC分析 - 一对多ROC分析 |
| markers_Roc_cluster <- tryCatch({{ |
| Raman.Markers.Roc( |
| object = data_processed, # Ramanome对象 |
| threshold = {threshold}, # 将标记视为显著的最小AUC阈值 |
| paired = FALSE, # 是否考虑成对标记 |
| batch_size = {batch_size} # 遍历所有可能成对标记时的批处理大小 |
| ) |
| }}, error = function(e) {{ |
| message("ROC标记分析失败: ", e$message) |
| return(NULL) |
| }}) |
| |
| if (!is.null(markers_Roc_cluster)) {{ |
| result_list$roc <- list(status = "success") |
| |
| # 生成ROC分析图并保存 |
| tryCatch({{ |
| p <- Plot.Markers.Spectrum(data_processed, markers_Roc_cluster) |
| ggsave("{imgs_dir}/Markers_Roc_plot.png", plot = p, width = 8, height = 6, dpi = 150) |
| |
| # 添加图片路径到结果中 |
| result_plots$roc_plot <- "/media/users/{user_id}/projects/{project_id}/imgs/meta_based/Markers_Roc_plot.png" |
| |
| # 生成热图并保存 |
| p_heatmap <- Plot.Heatmap.Markers(data_processed, markers_Roc_cluster$markers_singular$wave, save = TRUE) |
| ggsave("{imgs_dir}/Markers_Roc_heatmap.png", plot = p_heatmap, width = 8, height = 6, dpi = 150) |
| |
| # 检查并添加由Plot.Heatmap.Markers保存的图片 |
| if(file.exists("Raman_Markers_heatmap.png")) {{ |
| file.copy("Raman_Markers_heatmap.png", "{imgs_dir}/Raman_Markers_heatmap.png", overwrite = TRUE) |
| result_plots$roc_markers_heatmap <- "/media/users/{user_id}/projects/{project_id}/imgs/meta_based/Raman_Markers_heatmap.png" |
| }} |
| |
| # 添加热图路径到结果中 |
| result_plots$roc_heatmap <- "/media/users/{user_id}/projects/{project_id}/imgs/meta_based/Markers_Roc_heatmap.png" |
| }}, error = function(e) {{ |
| message("ROC标记图生成失败: ", e$message) |
| }}) |
| }} else {{ |
| result_list$roc <- list(status = "error", message = "ROC标记分析失败") |
| }} |
| """ |
| |
| if 'correlations' in raman_markers_methods: |
| cor_params = raman_markers_params.get('correlations', {}) |
| min_cor = cor_params.get('min_cor', 0.8) |
| min_range = cor_params.get('min_range', 30) |
| by_average = cor_params.get('by_average', False) |
| |
| r_code += f""" |
| ################################################################################ |
| # 4. Raman标记分析 - 相关性 |
| ################################################################################ |
| |
| # 相关性分析 |
| tryCatch({{ |
| markers_population_cluster <- Raman.Markers.Correlations( |
| object = data_processed, # Ramanome对象 |
| group = data_processed$group, # 目标变量 |
| paired = FALSE, # 是否考虑成对标记 |
| min.cor = {min_cor}, # 最小相关性阈值 |
| by_average = {"TRUE" if by_average else "FALSE"}, # 是否对每个组的光谱取平均值 |
| min.range = {min_range}, # 使用气泡法考虑峰区域时的最小波数范围 |
| extract_num = TRUE # 是否从group列提取数值 |
| ) |
| |
| Plot.Markers.Spectrum(data_processed, markers_population_cluster) |
| }}, error = function(e) {{ |
| message("相关性标记分析失败: ", e$message) |
| return(NULL) |
| }}) |
| |
| print("生成相关性分析图并保存") |
| result_list$correlations <- list(status = "success") |
| |
| # 生成相关性分析图并保存 |
| tryCatch({{ |
| p <- Plot.Markers.Spectrum(data_processed, markers_population_cluster) |
| ggsave("{imgs_dir}/Markers_Correlations_plot.png", plot = p, width = 8, height = 6, dpi = 150) |
| |
| # 添加图片路径到结果中 |
| result_plots$correlations_plot <- "/media/users/{user_id}/projects/{project_id}/imgs/meta_based/Markers_Correlations_plot.png" |
| |
| print("相关性分析图生成完成") |
| }}, error = function(e) {{ |
| message("相关性分析图生成失败: ", e$message) |
| }}) |
| |
| """ |
| |
| |
| r_code += f""" |
| ################################################################################ |
| # 返回结果 |
| ################################################################################ |
| |
| # 将结果和图像路径合并 |
| result <- list( |
| status = "success", |
| analyses = result_list, |
| plots = result_plots |
| ) |
| |
| # 转换为JSON字符串 |
| library(jsonlite) |
| toJSON(result, auto_unbox = TRUE) |
| """ |
| |
| |
| try: |
| result_json = RExecutor.execute_r_code(r_code, capture_output=True, working_dir=project_dir) |
| |
| if result_json: |
| import json |
| import re |
| try: |
| |
| result_str = str(result_json[0] if hasattr(result_json, '__iter__') else result_json) |
| logger.info(f"原始返回字符串: {result_str}") |
| |
| |
| result_str = re.sub(r'^\[\d+\]\s*', '', result_str) |
| |
| |
| if result_str.startswith('"') and result_str.endswith('"'): |
| |
| result_str = result_str[1:-1] |
| |
| result_str = result_str.replace('\\"', '"') |
| |
| |
| result_str = result_str.replace("\\\\", "\\") |
| |
| logger.info(f"处理后的JSON字符串: {result_str}") |
| |
| |
| try: |
| result_dict = json.loads(result_str) |
| logger.info("JSON解析成功!") |
| |
| |
| if 'plots' in result_dict: |
| for analysis_type, plots in result_dict['plots'].items(): |
| if isinstance(plots, list): |
| |
| for i, plot in enumerate(plots): |
| if not plot.startswith("/media"): |
| plots[i] = f'/media/users/{user_id}/projects/{project_id}/imgs/meta_based/{plot}' |
| |
| return result_dict |
| except json.JSONDecodeError: |
| |
| match = re.search(r'(\{.*\})', result_str) |
| if match: |
| json_str = match.group(1) |
| logger.info(f"提取的JSON: {json_str}") |
| result_dict = json.loads(json_str) |
| logger.info("JSON提取并解析成功!") |
| return result_dict |
| else: |
| raise ValueError("无法从字符串中提取JSON对象") |
| |
| except Exception as e: |
| logger.error(f"解析R分析结果失败: {e}") |
| |
| try: |
| |
| match = re.search(r'(\{.*\})', str(result_json[0])) |
| if match: |
| json_str = match.group(1) |
| logger.info(f"简单提取的JSON: {json_str}") |
| |
| json_str = json_str.replace('\\\\', '\\').replace('\\"', '"') |
| result_dict = json.loads(json_str) |
| logger.info("简单方法解析成功!") |
| return result_dict |
| else: |
| |
| original_str = str(result_json[0]) |
| |
| start = original_str.find('{') |
| end = original_str.rfind('}') + 1 |
| if start >= 0 and end > start: |
| json_str = original_str[start:end] |
| result_dict = json.loads(json_str) |
| return result_dict |
| else: |
| return { |
| "status": "error", |
| "message": "无法提取JSON,所有方法均失败", |
| "raw_result": str(result_json) |
| } |
| except Exception as ex: |
| logger.error(f"所有JSON解析方法均失败: {ex}") |
| return { |
| "status": "error", |
| "message": f"解析分析结果失败: {str(e)}", |
| "raw_result": str(result_json).replace('"', '\\"') |
| } |
| except Exception as e: |
| logger.error(f"执行基于元数据分析失败: {e}") |
| import traceback |
| logger.error(traceback.format_exc()) |
| return {"status": "error", "message": f"执行基于元数据分析失败: {str(e)}"} |
| else: |
| return {"status": "error", "message": "分析过程未返回结果"} |
| except Exception as e: |
| logger.error(f"执行基于元数据分析失败: {e}") |
| import traceback |
| logger.error(traceback.format_exc()) |
| return {"status": "error", "message": f"执行基于元数据分析失败: {str(e)}"} |
| |
| except Exception as e: |
| logger.error(f"基于元数据分析失败: {e}") |
| import traceback |
| logger.error(traceback.format_exc()) |
| return {"status": "error", "message": f"基于元数据分析失败: {str(e)}"} |
|
|
| @staticmethod |
| def save_rds(project_id, group_order=None): |
| """保存RDS文件,并可选择更新分组顺序""" |
| try: |
| from api.models import Project |
| project = Project.objects.get(id=project_id) |
| user_id = project.user.id |
| |
| |
| project_dir = os.path.join(settings.MEDIA_ROOT, f'users/{user_id}/projects/{project_id}/data') |
| ramex_data_path = os.path.join(project_dir, 'ramex_data.rds') |
| |
| |
| if not os.path.exists(ramex_data_path): |
| return {"status": "error", "message": "RamEx数据文件不存在,请先上传数据"} |
| |
| |
| r_code = f""" |
| # 加载必要的包 |
| library(RamEx) |
| |
| # 加载RamEx数据对象 |
| RamEx_data <- readRDS("{ramex_data_path}") |
| |
| # 获取原始分组信息 |
| original_levels <- levels(RamEx_data@meta.data$group) |
| print("原始分组顺序:") |
| print(original_levels) |
| """ |
| |
| |
| if group_order and isinstance(group_order, list) and len(group_order) > 0: |
| |
| r_levels = ', '.join([f'"{level}"' for level in group_order]) |
| r_code += f""" |
| # 更新分组顺序 |
| new_levels <- c({r_levels}) |
| print("新分组顺序:") |
| print(new_levels) |
| |
| # 确保所有原始水平都存在于新的顺序中 |
| if (all(original_levels %in% new_levels)) {{ |
| # 重新排序因子水平 |
| RamEx_data@meta.data$group <- factor(RamEx_data@meta.data$group, |
| levels = new_levels) |
| |
| # 验证更新后的顺序 |
| updated_levels <- levels(RamEx_data@meta.data$group) |
| print("更新后的分组顺序:") |
| print(updated_levels) |
| }} else {{ |
| warning("新的分组顺序不包含所有原始分组,保持原始顺序") |
| }} |
| """ |
| |
| |
| r_code += f""" |
| # 保存RamEx数据对象 |
| saveRDS(RamEx_data, file = "{ramex_data_path}") |
| |
| # 返回成功状态 |
| "success" |
| """ |
| |
| |
| result = RExecutor.execute_r_code(r_code, working_dir=project_dir) |
| |
| if result is not None and str(result).strip() == "success": |
| return {"status": "success"} |
| else: |
| return {"status": "success", "message": "RDS文件保存成功,但返回值无法确认"} |
| |
| except Exception as e: |
| logger.error(f"Failed to save RDS file: {e}") |
| import traceback |
| logger.error(traceback.format_exc()) |
| return {"status": "error", "message": str(e)} |
|
|