import os # 设置 R 图形设备为 cairo 或 png 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 # def initialize(): # """初始化R环境""" # try: # # 激活pandas转换 # # pandas2ri.activate() # # 尝试导入必要的R包 # try: # base = importr('base') # utils = importr('utils') # # 尝试导入RamEx包,如果不存在则记录警告 # 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 # # finally: # # 确保在函数结束时不影响全局状态 # # pandas2ri.deactivate() # except Exception as e: # # logger.error(f"Failed in pandas2ri activation: {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 # def execute_r_code(r_code, capture_output=False, working_dir=None): # """执行R代码并返回结果""" # try: # # 如果提供了工作目录,在R代码开头添加setwd命令 # if working_dir: # # 确保工作目录路径格式正确,并且添加设置工作目录的命令到代码开头 # working_dir = working_dir.replace('\\', '/') # r_code = f""" # # 设置工作目录 # setwd("{working_dir}") # {r_code} # """ # # 在执行前输出完整R代码内容到日志 # print("正在执行R代码:\n%s" % r_code) # # 激活pandas转换 # # pandas2ri.activate() # # try: # if capture_output: # # 如果需要捕获输出,则使用R的capture.output函数 # capture = robjects.r('capture.output') # output = capture(robjects.r(r_code)) # return output # else: # # 直接执行R代码 # result = robjects.r(r_code) # return result # # finally: # # 确保在函数结束时不影响全局状态 # # pandas2ri.deactivate() # except Exception as e: # # 只记录错误信息,不再记录完整的R代码 # 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压缩包的解压缩和处理""" # 获取项目对象和用户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') os.makedirs(project_dir, exist_ok=True) # 处理zip文件 if is_zip: # 解压缩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: # 筛选只解压.txt文件 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 # 构建R代码读取数据 - 确保即使wavenumber_range为None也有默认值 if wavenumber_range: cutoff_range = f"c({wavenumber_range[0]}, {wavenumber_range[1]})" else: cutoff_range = f"c({default_min}, {default_max})" # 使用RamEx包的read.spec函数读取数据 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)') # 执行R代码,设置工作目录 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)}"} # try: # # 确保激活转换规则 # # pandas2ri.activate() # # 直接从R环境中获取分组名称,采用更简单的方式 # r_code_get_groups = """ # # 将分组名称提取到简单的字符向量 # if(exists("group_names_vector")) { # # 转换为纯字符串值,便于传递 # groups <- paste(group_names_vector, collapse="|") # print(paste("分组名称:", groups)) # groups # } else { # "" # } # """ # # 捕获输出,便于调试 # output = RExecutor.execute_r_code(r_code_get_groups, capture_output=True, working_dir=project_dir) # for line in output: # print(f"分组输出: {line}") # # 获取结果作为字符串 # groups_str = str(RExecutor.execute_r_code(r_code_get_groups, working_dir=project_dir)) # print(f"获取到的分组字符串: {groups_str}") # # 解析分组字符串 - 修复解析问题 # if groups_str and groups_str != "": # # 清理字符串中可能包含的引号和方括号 # groups_str = groups_str.strip("[]'\"") # group_names = groups_str.split("|") # group_names = [g.strip("'\" ") for g in group_names] # else: # group_names = [] # print(f"最终解析的分组列表: {group_names}") # 验证分组名称 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')}") """ # 执行R代码,设置工作目录 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') # 检查RamEx数据文件是否存在 if not os.path.exists(ramex_data_path): return {"status": "error", "message": "RamEx数据文件不存在,请先上传数据"} # 构建R代码以更简单的方式获取分组信息 r_code = f""" # 加载必要的包 library(RamEx) # 加载RamEx数据对象 RamEx_data <- readRDS("{ramex_data_path}") # 获取分组信息并转换为字符串 groups <- as.character(levels(RamEx_data@meta.data$group)) # 将结果转换为更容易处理的格式 paste(groups, collapse="|") """ # 执行R代码并获取结果 result = RExecutor.execute_r_code(r_code, working_dir=project_dir) # 处理结果 - 将R返回的字符串拆分为列表 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,如果为None则使用默认值2 group_index_value = 2 if hasattr(file, 'group_index') and file.group_index is not None: group_index_value = file.group_index # 使用RamEx包的read.spec函数重新读取数据 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')}") """ # 执行R代码 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参数 - 简化处理逻辑,直接使用提供的参数 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') # 检查RamEx数据文件是否存在 if not os.path.exists(ramex_data_path): return {"status": "error", "message": "RamEx数据文件不存在,请先上传数据"} # 构建R代码 - 首先清空R环境并加载所有必要的包 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) """ # 1. 质量控制 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("数据集在欧氏距离过滤后为空,请调整参数后重试。") }} """ # 信噪比SNR 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过滤后为空,请调整参数后重试。") }} """ # ICOD 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过滤后为空,请调整参数后重试。") }} """ # Hotelling T2 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过滤后为空,请调整参数后重试。") }} """ # PC-MCD 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过滤后为空,请调整参数后重试。") }} """ # 2. 峰值去除 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}" ) """ # 3. 平滑处理 - 支持多个方法顺序执行 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) """ # 4. 基线校正 - 支持多个方法顺序执行 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} ) """ # 5. 波数范围截断 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} # 结束波数 ) """ # 6. 归一化处理 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" """ # 执行R代码 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): # 总是返回成功状态和图像URL (如果图像文件存在) 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', {}) # 获取波段分组CSV文件路径(如果有上传) # 前端传入参数中的bands_csv_path只是一个标记,实际文件已保存在项目目录中 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代码 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)) }}) """ # 2. 特征降维方法 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降维失败") }} """ # 3. 聚类分析 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聚类失败") }} """ # 4. 谱图分解 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分解失败") }} """ # 5. Ramanome内部分析 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) ) ) """ # 如果提供了波段CSV文件,则读取 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) """ # 执行R代码 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}") # 清理R输出格式 - 去除类似[1]的前缀 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: # 如果直接解析失败,尝试提取JSON对象 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}") # 使用更加简单的方法尝试提取JSON对象 try: # 正则表达式寻找简单的JSON对象 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: # 最后的尝试:直接返回原始字符串中的嵌套JSON 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代码 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) }} """ # 2. 分类分析部分 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分类失败") }} """ # 3. 回归与量化分析 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回归分析失败") }} """ # 4. Raman标记分析 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) """ # 执行R代码 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}") # 清理R输出格式 - 去除类似[1]的前缀 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: # 如果直接解析失败,尝试提取JSON对象 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}") # 使用更加简单的方法尝试提取JSON对象 try: # 正则表达式寻找简单的JSON对象 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: # 最后的尝试:直接返回原始字符串中的嵌套JSON 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') # 检查RamEx数据文件是否存在 if not os.path.exists(ramex_data_path): return {"status": "error", "message": "RamEx数据文件不存在,请先上传数据"} # 根据是否提供了分组顺序来构建R代码 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: # 将Python列表转换为R向量字符串 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("新的分组顺序不包含所有原始分组,保持原始顺序") }} """ # 保存更新后的RamEx数据对象 r_code += f""" # 保存RamEx数据对象 saveRDS(RamEx_data, file = "{ramex_data_path}") # 返回成功状态 "success" """ # 执行R代码 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)}