gpse 0.0.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gpse/__init__.py +11 -0
- gpse/batch/__init__.py +3 -0
- gpse/batch/cli.py +72 -0
- gpse/batch/runner.py +206 -0
- gpse/cli.py +215 -0
- gpse/config/__init__.py +15 -0
- gpse/config/_topsis_config.py +205 -0
- gpse/config/constants.py +127 -0
- gpse/config/default.yaml +15 -0
- gpse/config/software.yaml +20 -0
- gpse/config/topsis.yaml +18 -0
- gpse/convert/__init__.py +11 -0
- gpse/convert/cli.py +65 -0
- gpse/convert/external.py +400 -0
- gpse/convert/genotype_matrix.py +349 -0
- gpse/convert/phenotype.py +409 -0
- gpse/convert/processor.py +815 -0
- gpse/convert/qc.py +424 -0
- gpse/convert/validators.py +181 -0
- gpse/convert/workflow.py +214 -0
- gpse/models/__init__.py +30 -0
- gpse/models/classification_model_optimizer.py +393 -0
- gpse/models/classification_models.py +8 -0
- gpse/models/model_optimizers.py +14 -0
- gpse/models/regression_model_optimizer.py +679 -0
- gpse/predict/__init__.py +5 -0
- gpse/predict/__main__.py +3 -0
- gpse/predict/cli.py +95 -0
- gpse/predict/core.py +315 -0
- gpse/tasks/__init__.py +12 -0
- gpse/tasks/classification.py +565 -0
- gpse/tools/__init__.py +1 -0
- gpse/tools/analyze_phenotypes.py +140 -0
- gpse/train/__init__.py +26 -0
- gpse/train/_cv_manager.py +234 -0
- gpse/train/_data_io.py +261 -0
- gpse/train/_ensemble.py +235 -0
- gpse/train/_feature_selection.py +287 -0
- gpse/train/_fold_training.py +304 -0
- gpse/train/_model_pipeline.py +125 -0
- gpse/train/_model_tools.py +97 -0
- gpse/train/_optimization.py +244 -0
- gpse/train/_pipeline.py +490 -0
- gpse/train/_repeat_training.py +665 -0
- gpse/train/_results.py +262 -0
- gpse/train/cli.py +408 -0
- gpse/train/predictor.py +305 -0
- gpse/train/stacking.py +230 -0
- gpse/train/topsis.py +214 -0
- gpse/train/workflow.py +416 -0
- gpse/utils/__init__.py +54 -0
- gpse/utils/cli_display.py +529 -0
- gpse/utils/configuration.py +192 -0
- gpse/utils/dependency_checker.py +280 -0
- gpse/utils/feature_manifest.py +63 -0
- gpse/utils/genomic_utils.py +812 -0
- gpse/utils/log_utils.py +569 -0
- gpse/utils/logo.py +235 -0
- gpse/utils/paralle.py +266 -0
- gpse/utils/print_utils.py +75 -0
- gpse/utils/snp_ids.py +61 -0
- gpse/utils/version.py +159 -0
- gpse-0.0.2.dist-info/METADATA +1123 -0
- gpse-0.0.2.dist-info/RECORD +67 -0
- gpse-0.0.2.dist-info/WHEEL +4 -0
- gpse-0.0.2.dist-info/entry_points.txt +3 -0
- gpse-0.0.2.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
TOPSIS Configuration and Environment Logging Module
|
|
6
|
+
====================================================
|
|
7
|
+
Provides TOPSIS evaluation setup, representative model saving, and environment logging.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
import traceback
|
|
12
|
+
import json
|
|
13
|
+
import joblib
|
|
14
|
+
import pandas as pd
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from sklearn.preprocessing import StandardScaler
|
|
17
|
+
from typing import Any, Tuple
|
|
18
|
+
|
|
19
|
+
from loguru import logger as main_logger
|
|
20
|
+
|
|
21
|
+
from gpse.config.constants import ModelConstants
|
|
22
|
+
from gpse.utils.configuration import load_topsis_config
|
|
23
|
+
from gpse.utils.genomic_utils import create_representative_model_directory
|
|
24
|
+
from threadpoolctl import threadpool_limits
|
|
25
|
+
from gpse.train._feature_selection import (
|
|
26
|
+
make_model_artifact,
|
|
27
|
+
)
|
|
28
|
+
from gpse.train._model_pipeline import build_training_pipeline
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _format_weight(weight: Any) -> str:
|
|
32
|
+
"""Format a YAML weight value for TOPSISEvaluator's comma-string interface."""
|
|
33
|
+
if isinstance(weight, float):
|
|
34
|
+
return f"{weight:g}"
|
|
35
|
+
return str(weight)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _parse_topsis_task_config(task_type: str) -> Tuple[list, list, str]:
|
|
39
|
+
"""Parse and validate the TOPSIS config for a task type."""
|
|
40
|
+
config = load_topsis_config()
|
|
41
|
+
task_configs = config.get("tasks", {})
|
|
42
|
+
if not isinstance(task_configs, dict):
|
|
43
|
+
raise ValueError("TOPSIS config must define 'tasks' as a mapping")
|
|
44
|
+
|
|
45
|
+
task_key = "classification" if task_type == "classification" else "regression"
|
|
46
|
+
task_config = task_configs.get(task_key)
|
|
47
|
+
|
|
48
|
+
if not isinstance(task_config, dict):
|
|
49
|
+
raise ValueError(f"Missing TOPSIS configuration for task '{task_key}'")
|
|
50
|
+
|
|
51
|
+
criteria_config = task_config.get("criteria")
|
|
52
|
+
if not isinstance(criteria_config, list) or not criteria_config:
|
|
53
|
+
raise ValueError(f"TOPSIS task '{task_key}' must define a non-empty criteria list")
|
|
54
|
+
|
|
55
|
+
criteria = []
|
|
56
|
+
criteria_types = []
|
|
57
|
+
weights = []
|
|
58
|
+
for index, item in enumerate(criteria_config, start=1):
|
|
59
|
+
if not isinstance(item, dict):
|
|
60
|
+
raise ValueError(f"TOPSIS criterion #{index} for task '{task_key}' must be a mapping")
|
|
61
|
+
|
|
62
|
+
name = item.get("name")
|
|
63
|
+
criterion_type = item.get("type")
|
|
64
|
+
weight = item.get("weight")
|
|
65
|
+
|
|
66
|
+
if not name:
|
|
67
|
+
raise ValueError(f"TOPSIS criterion #{index} for task '{task_key}' is missing 'name'")
|
|
68
|
+
if criterion_type not in {"max", "min"}:
|
|
69
|
+
raise ValueError(
|
|
70
|
+
f"TOPSIS criterion '{name}' for task '{task_key}' must use type 'max' or 'min'"
|
|
71
|
+
)
|
|
72
|
+
if weight is None:
|
|
73
|
+
raise ValueError(f"TOPSIS criterion '{name}' for task '{task_key}' is missing 'weight'")
|
|
74
|
+
try:
|
|
75
|
+
float(weight)
|
|
76
|
+
except (TypeError, ValueError) as exc:
|
|
77
|
+
raise ValueError(
|
|
78
|
+
f"TOPSIS criterion '{name}' for task '{task_key}' has a non-numeric weight"
|
|
79
|
+
) from exc
|
|
80
|
+
|
|
81
|
+
criteria.append(str(name))
|
|
82
|
+
criteria_types.append(str(criterion_type))
|
|
83
|
+
weights.append(weight)
|
|
84
|
+
|
|
85
|
+
manual_weights = ",".join(_format_weight(weight) for weight in weights)
|
|
86
|
+
return criteria, criteria_types, manual_weights
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def get_topsis_configuration(self) -> Tuple[list, list, str]:
|
|
90
|
+
"""
|
|
91
|
+
Get TOPSIS evaluation configuration based on task type.
|
|
92
|
+
|
|
93
|
+
Returns
|
|
94
|
+
-------
|
|
95
|
+
Tuple[list, list, str]
|
|
96
|
+
(criteria, criteria_types, manual_weights)
|
|
97
|
+
"""
|
|
98
|
+
criteria, criteria_types, manual_weights = _parse_topsis_task_config(self.task_type)
|
|
99
|
+
task_name = "Classification" if self.task_type == "classification" else "Regression"
|
|
100
|
+
main_logger.info(
|
|
101
|
+
f"TOPSIS config: {task_name} task - "
|
|
102
|
+
f"{' : '.join(criteria)}; weights={manual_weights}"
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
return criteria, criteria_types, manual_weights
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _save_representative_model(
|
|
109
|
+
self,
|
|
110
|
+
model_name: str,
|
|
111
|
+
params: dict,
|
|
112
|
+
X: pd.DataFrame,
|
|
113
|
+
y: pd.Series,
|
|
114
|
+
model_dir: Path,
|
|
115
|
+
repeat_info: dict,
|
|
116
|
+
) -> str:
|
|
117
|
+
"""
|
|
118
|
+
Train and save a representative model.
|
|
119
|
+
|
|
120
|
+
Parameters
|
|
121
|
+
----------
|
|
122
|
+
model_name : str
|
|
123
|
+
Model name.
|
|
124
|
+
params : dict
|
|
125
|
+
Model parameters.
|
|
126
|
+
X : pd.DataFrame
|
|
127
|
+
Feature matrix.
|
|
128
|
+
y : pd.Series
|
|
129
|
+
Target variable.
|
|
130
|
+
model_dir : Path
|
|
131
|
+
Model directory.
|
|
132
|
+
repeat_info : dict
|
|
133
|
+
Repeat information dictionary.
|
|
134
|
+
|
|
135
|
+
Returns
|
|
136
|
+
-------
|
|
137
|
+
str
|
|
138
|
+
Model save path, or None if saving failed.
|
|
139
|
+
"""
|
|
140
|
+
representative_model_dir = create_representative_model_directory(model_dir)
|
|
141
|
+
|
|
142
|
+
try:
|
|
143
|
+
pipeline = build_training_pipeline(
|
|
144
|
+
self.create_model(model_name, params),
|
|
145
|
+
task_type=self.task_type,
|
|
146
|
+
model_name=model_name,
|
|
147
|
+
feature_selection_config=self.feature_selection_config,
|
|
148
|
+
genotype_imputation_config=self.genotype_imputation_config,
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
with threadpool_limits(limits=self.n_threads):
|
|
152
|
+
pipeline.fit(X, y)
|
|
153
|
+
preprocessor = pipeline.named_steps["preprocess"]
|
|
154
|
+
|
|
155
|
+
model_path = representative_model_dir / ModelConstants.model_pkl_file
|
|
156
|
+
joblib.dump(
|
|
157
|
+
make_model_artifact(
|
|
158
|
+
pipeline.named_steps["model"],
|
|
159
|
+
preprocessor.scaler_,
|
|
160
|
+
preprocessor.selector_,
|
|
161
|
+
self.feature_selection_config,
|
|
162
|
+
preprocessor.selected_features_,
|
|
163
|
+
imputer=preprocessor.imputer_,
|
|
164
|
+
imputation_config=self.genotype_imputation_config,
|
|
165
|
+
task_type=self.task_type,
|
|
166
|
+
),
|
|
167
|
+
model_path,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
info_path = representative_model_dir / ModelConstants.model_info_file
|
|
171
|
+
with open(info_path, "w") as f:
|
|
172
|
+
json.dump(
|
|
173
|
+
{
|
|
174
|
+
**repeat_info,
|
|
175
|
+
"feature_selection": self.feature_selection_config.as_dict(),
|
|
176
|
+
"genotype_imputation": self.genotype_imputation_config.as_dict(),
|
|
177
|
+
"selected_feature_count": len(preprocessor.selected_features_),
|
|
178
|
+
},
|
|
179
|
+
f,
|
|
180
|
+
indent=2,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
return str(model_path)
|
|
184
|
+
|
|
185
|
+
except Exception as e:
|
|
186
|
+
main_logger.error(f"Saving representative model failed: {str(e)}")
|
|
187
|
+
main_logger.error(traceback.format_exc())
|
|
188
|
+
return None
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def log_environment_settings(self) -> None:
|
|
192
|
+
"""Log environment settings."""
|
|
193
|
+
main_logger.info("Initializing GenomicPredictorV2, base configuration:")
|
|
194
|
+
main_logger.info(f"- Results directory: {self.results_dir}")
|
|
195
|
+
main_logger.info(f"- Logs directory: {self.logs_dir}")
|
|
196
|
+
main_logger.info(f"- Random seed: {self.random_seed}")
|
|
197
|
+
main_logger.info(f"- Threads per model: {self.n_threads}")
|
|
198
|
+
main_logger.info(f"- Model workers: {self.max_parallel_jobs}")
|
|
199
|
+
main_logger.info(f"- Repeat workers: {self.repeat_workers}")
|
|
200
|
+
main_logger.info(f"- Train folds: {self.n_splits}")
|
|
201
|
+
main_logger.info(f"- Repeats: {self.n_repeats}")
|
|
202
|
+
main_logger.info(f"- Use default params: {self.use_default_params}")
|
|
203
|
+
|
|
204
|
+
for env_var in ModelConstants.thread_env_vars:
|
|
205
|
+
main_logger.info(f"{env_var}={os.environ.get(env_var, 'not set')}")
|
gpse/config/constants.py
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""Global constant configuration and log message templates."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import json
|
|
5
|
+
import numpy as np
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from typing import Any, Callable
|
|
8
|
+
|
|
9
|
+
from gpse.utils.cli_display import show_gpse_logo
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class _LogoHelpAction(argparse.Action):
|
|
13
|
+
"""Custom help action that prints the logo before the help text."""
|
|
14
|
+
|
|
15
|
+
def __call__(self, parser, namespace, values, option_string=None):
|
|
16
|
+
show_gpse_logo()
|
|
17
|
+
parser.print_help()
|
|
18
|
+
parser.exit()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class ModelConfig:
|
|
23
|
+
"""Configuration for model hyperparameter optimization"""
|
|
24
|
+
model_class: Any
|
|
25
|
+
param_func: Callable
|
|
26
|
+
is_regression: bool
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class ClassificationModelConfig:
|
|
31
|
+
"""Configuration for classification model hyperparameter optimization"""
|
|
32
|
+
model_class: Any
|
|
33
|
+
param_func: Callable
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class NumpyEncoder(json.JSONEncoder):
|
|
37
|
+
"""
|
|
38
|
+
JSON encoder that handles NumPy types
|
|
39
|
+
"""
|
|
40
|
+
def default(self, obj):
|
|
41
|
+
if isinstance(obj, np.integer):
|
|
42
|
+
return int(obj)
|
|
43
|
+
if isinstance(obj, np.floating):
|
|
44
|
+
return float(obj)
|
|
45
|
+
if isinstance(obj, np.ndarray):
|
|
46
|
+
return obj.tolist()
|
|
47
|
+
return super(NumpyEncoder, self).default(obj)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass(frozen=True)
|
|
51
|
+
class _ModelConstants:
|
|
52
|
+
"""
|
|
53
|
+
Constants configuration related to model training
|
|
54
|
+
"""
|
|
55
|
+
# Optuna optimization related
|
|
56
|
+
optuna_n_startup_trials: int = 10
|
|
57
|
+
optuna_n_warmup_steps: int = 5
|
|
58
|
+
optuna_interval_steps: int = 1
|
|
59
|
+
optuna_n_jobs: int = 1 # Enforce Optuna serial execution
|
|
60
|
+
|
|
61
|
+
# Thread environment variables
|
|
62
|
+
thread_env_vars: tuple = field(default_factory=lambda: (
|
|
63
|
+
'OMP_NUM_THREADS', 'MKL_NUM_THREADS', 'OPENBLAS_NUM_THREADS',
|
|
64
|
+
'NUMEXPR_NUM_THREADS', 'VECLIB_MAXIMUM_THREADS', 'BLIS_NUM_THREADS'
|
|
65
|
+
))
|
|
66
|
+
|
|
67
|
+
# Default directory names
|
|
68
|
+
default_results_dir: str = "optimization_results"
|
|
69
|
+
default_logs_dir: str = "logs"
|
|
70
|
+
default_cv_dir: str = "cv_folds"
|
|
71
|
+
representative_model_dir: str = "representative_model"
|
|
72
|
+
|
|
73
|
+
# Default file names
|
|
74
|
+
main_log_file: str = "main.log"
|
|
75
|
+
model_log_file: str = "model.log"
|
|
76
|
+
repeat_results_file: str = "repeat_results.json"
|
|
77
|
+
summary_results_file: str = "summary_results.json"
|
|
78
|
+
all_predictions_file: str = "all_predictions.json"
|
|
79
|
+
model_info_file: str = "info.json"
|
|
80
|
+
model_pkl_file: str = "model.pkl"
|
|
81
|
+
comparison_csv_file: str = "model_comparison.csv"
|
|
82
|
+
|
|
83
|
+
# Log format templates
|
|
84
|
+
log_separator_short: str = field(default_factory=lambda: "=" * 50)
|
|
85
|
+
log_separator_long: str = field(default_factory=lambda: "=" * 70)
|
|
86
|
+
|
|
87
|
+
# Numerical precision constants
|
|
88
|
+
metrics_precision: int = 6
|
|
89
|
+
time_precision: int = 2
|
|
90
|
+
float_precision: int = 4
|
|
91
|
+
correlation_min_threshold: float = 1e-10 # Threshold to avoid constant predictions
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
# NOTE: _LogMessages has been deprecated. All log messages are now inline strings.
|
|
95
|
+
# If you need to restore it, uncomment the block below.
|
|
96
|
+
#
|
|
97
|
+
# @dataclass(frozen=True)
|
|
98
|
+
# class _LogMessages:
|
|
99
|
+
# """Log message template constants"""
|
|
100
|
+
# logging_initialized: str = "Logging system initialization complete"
|
|
101
|
+
# predictor_init: str = "Initializing GenomicPredictorV2, basic configuration:"
|
|
102
|
+
# data_loading: str = "Loading data..."
|
|
103
|
+
# data_loaded: str = "Successfully loaded genotype data: {}, phenotype data: {}"
|
|
104
|
+
# common_samples: str = "Number of genotype samples: {}, number of phenotype samples: {}"
|
|
105
|
+
# final_data_info: str = "Final data dimensions - Number of features: {}, number of samples: {}"
|
|
106
|
+
# target_stats: str = "Target variable statistics - Mean: {:.4f}, Std: {:.4f}"
|
|
107
|
+
# model_training_start: str = "Starting training for model: {} repeat: {}"
|
|
108
|
+
# repeat_training_start: str = "Starting {} repetitions of training for model {}"
|
|
109
|
+
# fold_training_start: str = "Training fold {}/{}"
|
|
110
|
+
# parameter_optimization_start: str = "Starting parameter optimization for model {} (repeat {}/{})"
|
|
111
|
+
# optimization_start: str = "Starting parameter optimization, max trials: {}"
|
|
112
|
+
# fold_results: str = "Fold {} results:"
|
|
113
|
+
# repeat_average_performance: str = "Repeat {} average performance:"
|
|
114
|
+
# model_summary_performance: str = "Model {} - average performance across {} repeats:"
|
|
115
|
+
# ensemble_test_performance: str = "Fold ensemble test set performance:"
|
|
116
|
+
# data_file_not_found: str = "Failed to load data file: {}"
|
|
117
|
+
# data_file_read_error: str = "Error reading data file: {}"
|
|
118
|
+
# target_trait_not_found: str = "Target trait '{}' not found in phenotype data. Available columns: {}"
|
|
119
|
+
# id_column_missing_geno: str = "ID column '{}' missing in genotype data"
|
|
120
|
+
# id_column_missing_pheno: str = "ID column '{}' missing in phenotype data"
|
|
121
|
+
# no_common_samples: str = "No common sample IDs between genotype and phenotype data"
|
|
122
|
+
# dimension_mismatch: str = "Mismatch in number of samples between feature matrix and target variable: {} vs {}"
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
# Module-level singleton instance
|
|
126
|
+
ModelConstants = _ModelConstants()
|
|
127
|
+
# LogMessages = _LogMessages() # deprecated
|
gpse/config/default.yaml
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
software:
|
|
2
|
+
version: "0.0.2"
|
|
3
|
+
app_name: gpse
|
|
4
|
+
author: "XIAOLIU, JZHANG"
|
|
5
|
+
email: "1468835852@qq.com, zhangjian199567@outlook.com"
|
|
6
|
+
url: https://github.com/xsx123123/GPSE
|
|
7
|
+
description: "Genomic Prediction with Stacking Ensemble for horticultural crops"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
logs:
|
|
11
|
+
log_level: INFO
|
|
12
|
+
more_info: false
|
|
13
|
+
Label: gpse
|
|
14
|
+
|
|
15
|
+
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
software:
|
|
2
|
+
version: "0.0.2"
|
|
3
|
+
app_name: gpse
|
|
4
|
+
author: "XIAOLIU, JZHANG"
|
|
5
|
+
email: "1468835852@qq.com, zhangjian199567@outlook.com"
|
|
6
|
+
url: https://github.com/xsx123123/GPSE
|
|
7
|
+
description: "Genomic Prediction with Stacking Ensemble for horticultural crops"
|
|
8
|
+
|
|
9
|
+
external_tools:
|
|
10
|
+
- name: plink
|
|
11
|
+
cmd: plink
|
|
12
|
+
version_flag: --version
|
|
13
|
+
min_version: "1.9"
|
|
14
|
+
required: true
|
|
15
|
+
description: "PLINK 1.9 for genomic data format conversion and SNP extraction"
|
|
16
|
+
- name: java
|
|
17
|
+
cmd: java
|
|
18
|
+
version_flag: -version
|
|
19
|
+
required: false
|
|
20
|
+
description: "Java runtime for optional Beagle imputation"
|
gpse/config/topsis.yaml
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
tasks:
|
|
2
|
+
classification:
|
|
3
|
+
criteria:
|
|
4
|
+
- name: "Test Accuracy"
|
|
5
|
+
type: max
|
|
6
|
+
weight: 0.8
|
|
7
|
+
- name: "Test Accuracy (std)"
|
|
8
|
+
type: min
|
|
9
|
+
weight: 0.2
|
|
10
|
+
|
|
11
|
+
regression:
|
|
12
|
+
criteria:
|
|
13
|
+
- name: "Test Pearson"
|
|
14
|
+
type: max
|
|
15
|
+
weight: 0.8
|
|
16
|
+
- name: "Test Pearson (std)"
|
|
17
|
+
type: min
|
|
18
|
+
weight: 0.2
|
gpse/convert/__init__.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""
|
|
2
|
+
GPSE convert package — data conversion and genotype QC utilities.
|
|
3
|
+
|
|
4
|
+
This package provides tools for converting between common genomic data
|
|
5
|
+
formats, running quality-control filters, and preparing genotype matrices
|
|
6
|
+
for downstream GPSE analyses.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from .processor import GenomicDataProcessor
|
|
10
|
+
|
|
11
|
+
__all__ = ["GenomicDataProcessor"]
|
gpse/convert/cli.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Command-line and workflow entry point for the GPSE convert module.
|
|
5
|
+
|
|
6
|
+
This module does two things:
|
|
7
|
+
1. Defines the command-line arguments for ``gpse convert``.
|
|
8
|
+
2. Starts the selected convert workflow: genotype conversion, QC/LD pruning,
|
|
9
|
+
numeric recoding, or external dependency validation.
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import os
|
|
14
|
+
import sys
|
|
15
|
+
import argparse
|
|
16
|
+
from datetime import datetime
|
|
17
|
+
|
|
18
|
+
from gpse.convert.workflow import run_convert_workflow, validate_convert_mode
|
|
19
|
+
from gpse.utils.cli_display import _build_convert_parser
|
|
20
|
+
from gpse.utils.log_utils import logger_init
|
|
21
|
+
|
|
22
|
+
# define convert main function
|
|
23
|
+
def main(argv: list[str] | None = None,*,formatter_class=None,prog: str | None = None,help_action=None,parents: list[argparse.ArgumentParser] | None = None) -> int:
|
|
24
|
+
"""
|
|
25
|
+
Parse convert arguments, select a mode, and start the workflow.
|
|
26
|
+
"""
|
|
27
|
+
parser = _build_convert_parser(
|
|
28
|
+
formatter_class = formatter_class or argparse.HelpFormatter,
|
|
29
|
+
prog = prog or "python -m gpse.convert.cli",
|
|
30
|
+
help_action = help_action,
|
|
31
|
+
parents = parents,
|
|
32
|
+
)
|
|
33
|
+
args = parser.parse_args(argv)
|
|
34
|
+
|
|
35
|
+
# Determine workflow mode first so that missing-argument errors and --help
|
|
36
|
+
# are handled before initialising the logger. This avoids spurious
|
|
37
|
+
# "GPSE config loaded" messages when the user only wants usage info.
|
|
38
|
+
mode = validate_convert_mode(parser, args)
|
|
39
|
+
|
|
40
|
+
# Initialise unified GPSE logging so that convert uses the same
|
|
41
|
+
# Rich-styled console format and file rotation as the train module.
|
|
42
|
+
log_level = getattr(args, "log_level", "INFO")
|
|
43
|
+
log_file = None
|
|
44
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
45
|
+
if getattr(args, "out_prefix", None):
|
|
46
|
+
log_dir = os.path.dirname(args.out_prefix) or "."
|
|
47
|
+
os.makedirs(log_dir, exist_ok=True)
|
|
48
|
+
log_file = os.path.join(log_dir, f"gpse_convert_{timestamp}.log")
|
|
49
|
+
elif getattr(args, "recode_prefix", None):
|
|
50
|
+
log_dir = os.path.dirname(args.recode_prefix) or "."
|
|
51
|
+
os.makedirs(log_dir, exist_ok=True)
|
|
52
|
+
log_file = os.path.join(log_dir, f"gpse_convert_{timestamp}.log")
|
|
53
|
+
else:
|
|
54
|
+
# No output prefix specified (e.g. --check-deps).
|
|
55
|
+
# Skip file logging — only use console output.
|
|
56
|
+
log_file = None
|
|
57
|
+
|
|
58
|
+
logger_init(logger_name=log_file, log_level=log_level)
|
|
59
|
+
|
|
60
|
+
return run_convert_workflow(args, mode)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
if __name__ == "__main__":
|
|
64
|
+
# Allows direct execution: python -m gpse.convert.cli ...
|
|
65
|
+
sys.exit(main())
|