|
10 | 10 |
|
11 | 11 | import sys |
12 | 12 | import os |
13 | | -import subprocess |
14 | 13 | import logging |
15 | 14 | import shutil |
16 | | -import uuid |
17 | 15 | import time |
18 | | -import ast |
19 | | -import gzip |
20 | 16 | import atexit |
21 | 17 | import bcftbx.IlluminaData as IlluminaData |
22 | 18 | import bcftbx.utils as bcf_utils |
|
25 | 21 | from .analysis import run_reference_id |
26 | 22 | from .metadata import AnalysisDirParameters |
27 | 23 | from .metadata import AnalysisDirMetadata |
| 24 | +from .metadata import AnalysisProjectInfo |
28 | 25 | from .metadata import ProjectMetadataFile |
29 | 26 | from .utils import edit_file |
30 | 27 | from .utils import get_numbered_subdir |
@@ -140,6 +137,64 @@ def create_directory(self,dirn): |
140 | 137 | print("Making %s" % dir_path) |
141 | 138 | bcf_utils.mkdir(dir_path) |
142 | 139 |
|
| 140 | + def update_paths(self, base_path=None, new_path=None): |
| 141 | + """ |
| 142 | + Update the paths stored in the analysis directory |
| 143 | +
|
| 144 | + Checks the paths stored in the analysis directory |
| 145 | + metadata and parameter files, and updates them if |
| 146 | + they're inconsistent with the current location. |
| 147 | +
|
| 148 | + By default the original 'base' path is taken from |
| 149 | + the path stored in the analysis directory parameters, |
| 150 | + and the new 'base' path is assumed to be the current |
| 151 | + path for the analysis directory (these settings |
| 152 | + are sensible if the directory has been relocated or |
| 153 | + copied). |
| 154 | +
|
| 155 | + Arguments: |
| 156 | + base_path (str): current 'base' directory path |
| 157 | + (defaults to path stored in analysis directory |
| 158 | + parameters) |
| 159 | + new_path (str): new 'base' directory path |
| 160 | + (defaults to the current path of the analysis |
| 161 | + directory) |
| 162 | + """ |
| 163 | + # Update paths in the top-level parameter file |
| 164 | + # (if analysis dir has been moved or copied) |
| 165 | + if base_path is None: |
| 166 | + # Use stored path as original base path |
| 167 | + base_path = self.params.analysis_dir |
| 168 | + if new_path is None: |
| 169 | + # Use current path as new base path |
| 170 | + new_path = self.analysis_dir |
| 171 | + if base_path != new_path: |
| 172 | + print("Updating analysis directory paths in parameter file") |
| 173 | + print(f"-- old base path: {base_path}") |
| 174 | + print(f"-- new base path: {new_path}") |
| 175 | + for p in ('analysis_dir', |
| 176 | + 'primary_data_dir', |
| 177 | + 'sample_sheet'): |
| 178 | + if not self.params[p]: |
| 179 | + continue |
| 180 | + self.params[p] = os.path.normpath( |
| 181 | + os.path.join(new_path, |
| 182 | + os.path.relpath(self.params[p], base_path))) |
| 183 | + print(f"...updated '{p}' (set to '{self.params[p]}'") |
| 184 | + # Update paths in QC metadata in projects |
| 185 | + for project in self.get_analysis_projects_from_dirs(): |
| 186 | + # Iterate through all project directories |
| 187 | + for qc_dir in project.qc_dirs: |
| 188 | + qc_info = project.qc_info(qc_dir) |
| 189 | + qc_info['fastq_dir'] = os.path.normpath( |
| 190 | + os.path.join(new_path, |
| 191 | + os.path.relpath(qc_info.fastq_dir, |
| 192 | + base_path))) |
| 193 | + print(f"...updated QC info for {project.name}/{qc_dir}") |
| 194 | + qc_info.save() |
| 195 | + # Save the updated parameter data |
| 196 | + self.save_parameters(force=True) |
| 197 | + |
143 | 198 | def load_parameters(self,allow_save=True): |
144 | 199 | """ |
145 | 200 | Load parameter values from file |
@@ -477,6 +532,58 @@ def update_project_metadata_file(self,unaligned_dir=None, |
477 | 532 | print("Updated project metadata file '%s'" % |
478 | 533 | self.params.project_metadata) |
479 | 534 |
|
| 535 | + def sync_project_metadata_file(self): |
| 536 | + """ |
| 537 | + Synchronise 'projects.info' file with directory contents |
| 538 | + """ |
| 539 | + # Load information from 'projects.info' |
| 540 | + project_metadata = self.load_project_metadata() |
| 541 | + # Comment out projects which don't exist on filesystem |
| 542 | + save_required = False |
| 543 | + for line in project_metadata: |
| 544 | + # Iterate through the named projects |
| 545 | + name = line['Project'] |
| 546 | + if name.startswith('#'): |
| 547 | + # Commented out, ignore |
| 548 | + continue |
| 549 | + # Look for a matching project directory |
| 550 | + project_dir = os.path.join(self.analysis_dir, name) |
| 551 | + if not os.path.exists(project_dir): |
| 552 | + print(f"Commenting out missing project '{name}'") |
| 553 | + line['Project'] = f"#{name}" |
| 554 | + save_required = True |
| 555 | + if save_required: |
| 556 | + project_metadata.save() |
| 557 | + # Add any project directories without entries |
| 558 | + save_required = False |
| 559 | + projects = [line['Project'] for line in project_metadata] |
| 560 | + for project in self.get_analysis_projects_from_dirs(): |
| 561 | + if project.name.endswith(".bak") \ |
| 562 | + or project.name.endswith(".orig") \ |
| 563 | + or project.name.endswith(".tmp"): |
| 564 | + # Skip directories with extensions indicating they |
| 565 | + # should be ignored |
| 566 | + print(f"Not adding entry for unlisted project '{project.name}'") |
| 567 | + continue |
| 568 | + elif project.name == "undetermined": |
| 569 | + # Skip undetermined |
| 570 | + continue |
| 571 | + elif project.name not in projects and f"#{project.name}" not in projects: |
| 572 | + # Add new entry |
| 573 | + print(f"Adding entry for unlisted project '{project.name}'") |
| 574 | + project_metadata.add_project(project.name, |
| 575 | + [s.name for s in project.samples], |
| 576 | + user=project.info.user, |
| 577 | + PI=project.info.PI, |
| 578 | + organism=project.info.organism, |
| 579 | + library_type=project.info.library_type, |
| 580 | + sc_platform=project.info.single_cell_platform, |
| 581 | + comments=project.info.comments) |
| 582 | + save_required = True |
| 583 | + # Save the updated project metadata if required |
| 584 | + if save_required: |
| 585 | + project_metadata.save() |
| 586 | + |
480 | 587 | def detect_unaligned_dir(self): |
481 | 588 | # Attempt to detect an existing 'bcl2fastq' or 'Unaligned' directory |
482 | 589 | # containing data from bcl2fastq |
@@ -920,6 +1027,65 @@ def get_analysis_projects_from_dirs(self,pattern=None,strict=False): |
920 | 1027 | projects.append(test_project) |
921 | 1028 | return projects |
922 | 1029 |
|
| 1030 | + def sync_project_metadata(self): |
| 1031 | + """ |
| 1032 | + Update metadata stored in project dirs with 'projects.info' |
| 1033 | + """ |
| 1034 | + # Load information from 'projects.info' |
| 1035 | + project_metadata = self.load_project_metadata() |
| 1036 | + save_required = False |
| 1037 | + for line in project_metadata: |
| 1038 | + # Iterate through the named projects |
| 1039 | + name = line['Project'] |
| 1040 | + if name.startswith('#'): |
| 1041 | + # Commented out, ignore |
| 1042 | + continue |
| 1043 | + # Look for a matching project directory |
| 1044 | + project_dir = os.path.join(self.analysis_dir, name) |
| 1045 | + if os.path.exists(project_dir): |
| 1046 | + project = AnalysisProject(project_dir) |
| 1047 | + print(f"Checking metadata for project '{name}'") |
| 1048 | + # Synchronise metadata in projects with projects.info |
| 1049 | + metadata_items = dict( |
| 1050 | + name=name, |
| 1051 | + user=line['User'], |
| 1052 | + PI=line['PI'], |
| 1053 | + organism=line['Organism'], |
| 1054 | + library_type=line['Library'], |
| 1055 | + single_cell_platform=line['SC_Platform'], |
| 1056 | + comments=line['Comments'], |
| 1057 | + samples=project.sample_summary() |
| 1058 | + ) |
| 1059 | + # Only update items where values differ |
| 1060 | + project_metadata_updated = False |
| 1061 | + for item in metadata_items: |
| 1062 | + new_value = (metadata_items[item] |
| 1063 | + if metadata_items[item] != '.' else None) |
| 1064 | + if project.info[item] != new_value: |
| 1065 | + print("...updating '%s' => %r" % (item, new_value)) |
| 1066 | + project.info[item] = new_value |
| 1067 | + project_metadata_updated = True |
| 1068 | + # Check paired-end info |
| 1069 | + project_info = AnalysisProjectInfo(project.info_file) |
| 1070 | + if project_info.paired_end != project.info.paired_end: |
| 1071 | + print("...updating paired-end info") |
| 1072 | + project_metadata_updated = True |
| 1073 | + # Save the updated project metadata if required |
| 1074 | + if project_metadata_updated: |
| 1075 | + print(f"...saving project metadata for {project.name}") |
| 1076 | + project.info.save() |
| 1077 | + # Update list of sample names in projects.info |
| 1078 | + sample_list = ','.join(sort_sample_names( |
| 1079 | + [s.name for s in project.samples])) |
| 1080 | + if line['Samples'] != sample_list: |
| 1081 | + print("...updating sample list in projects.info") |
| 1082 | + line['Samples'] = sample_list |
| 1083 | + save_required = True |
| 1084 | + # Save master project metadata |
| 1085 | + if save_required: |
| 1086 | + print("Saving projects.info") |
| 1087 | + project_metadata.save() |
| 1088 | + |
923 | 1089 | def undetermined(self): |
924 | 1090 | # Return analysis project directory for undetermined indices |
925 | 1091 | # or None if not found |
|
0 commit comments