FScanpy 1.0.0__tar.gz
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.
- fscanpy-1.0.0/CHANGELOG.md +19 -0
- fscanpy-1.0.0/FScanpy/__init__.py +206 -0
- fscanpy-1.0.0/FScanpy/data/__init__.py +122 -0
- fscanpy-1.0.0/FScanpy/data/test_data/blastx_example.xlsx +0 -0
- fscanpy-1.0.0/FScanpy/data/test_data/full_seq.xlsx +0 -0
- fscanpy-1.0.0/FScanpy/data/test_data/mrna_example.fasta +2584 -0
- fscanpy-1.0.0/FScanpy/data/test_data/region_example.csv +4 -0
- fscanpy-1.0.0/FScanpy/features/__init__.py +4 -0
- fscanpy-1.0.0/FScanpy/features/cnn_input.py +79 -0
- fscanpy-1.0.0/FScanpy/features/sequence.py +283 -0
- fscanpy-1.0.0/FScanpy/predictor.py +616 -0
- fscanpy-1.0.0/FScanpy/pretrained/long.pth +4 -0
- fscanpy-1.0.0/FScanpy/pretrained/short.pkl +0 -0
- fscanpy-1.0.0/FScanpy/utils.py +203 -0
- fscanpy-1.0.0/FScanpy.egg-info/PKG-INFO +262 -0
- fscanpy-1.0.0/FScanpy.egg-info/SOURCES.txt +32 -0
- fscanpy-1.0.0/FScanpy.egg-info/dependency_links.txt +1 -0
- fscanpy-1.0.0/FScanpy.egg-info/requires.txt +8 -0
- fscanpy-1.0.0/FScanpy.egg-info/top_level.txt +1 -0
- fscanpy-1.0.0/FScanpy_Demo.ipynb +679 -0
- fscanpy-1.0.0/LICENSE +21 -0
- fscanpy-1.0.0/MANIFEST.in +5 -0
- fscanpy-1.0.0/PKG-INFO +262 -0
- fscanpy-1.0.0/README.md +232 -0
- fscanpy-1.0.0/README_zh.md +225 -0
- fscanpy-1.0.0/pyproject.toml +48 -0
- fscanpy-1.0.0/setup.cfg +4 -0
- fscanpy-1.0.0/tests/run_notebooks.py +92 -0
- fscanpy-1.0.0/tests/test_package.py +86 -0
- fscanpy-1.0.0/tutorial/image/ML.png +0 -0
- fscanpy-1.0.0/tutorial/image/structure.jpg +0 -0
- fscanpy-1.0.0/tutorial/predict_sample.ipynb +223 -0
- fscanpy-1.0.0/tutorial/tutorial.md +617 -0
- fscanpy-1.0.0/tutorial/tutorial_zh.md +688 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 1.0.0
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- Include pretrained model weights and example data in package distributions.
|
|
7
|
+
- Locate bundled models without the deprecated `pkg_resources` API.
|
|
8
|
+
- Load PyTorch checkpoints with `weights_only=True` and pin scikit-learn to 1.7.2 for the bundled short model.
|
|
9
|
+
- Accept region DataFrames containing `Long_Sequence` or `399bp`.
|
|
10
|
+
- Accept pathlib paths when saving prediction plots.
|
|
11
|
+
- Export `fscanr` and `extract_prf_regions` from the public package API.
|
|
12
|
+
- Repair notebook examples and document Jupyter installation.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
- Package regression tests and a notebook validation runner.
|
|
16
|
+
- PyPI project metadata, maintainer contact, MIT license file, and project links.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
- Align the package's reported version with distribution version 1.0.0.
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
from .predictor import PRFPredictor
|
|
2
|
+
from . import data
|
|
3
|
+
from .utils import fscanr, extract_prf_regions
|
|
4
|
+
import pandas as pd
|
|
5
|
+
import numpy as np
|
|
6
|
+
from typing import Union, List, Dict
|
|
7
|
+
|
|
8
|
+
__version__ = '1.0.0'
|
|
9
|
+
__author__ = 'Yang Yuhao'
|
|
10
|
+
__email__ = 'ykongxiang@qq.com'
|
|
11
|
+
|
|
12
|
+
__all__ = ['PRFPredictor', 'predict_prf', 'plot_prf_prediction', 'fscanr', 'extract_prf_regions', 'data', '__version__', '__author__', '__email__']
|
|
13
|
+
|
|
14
|
+
def predict_prf(
|
|
15
|
+
sequence: Union[str, List[str], None] = None,
|
|
16
|
+
data: Union[pd.DataFrame, None] = None,
|
|
17
|
+
window_size: int = 3,
|
|
18
|
+
short_threshold: float = 0.1,
|
|
19
|
+
ensemble_weight: float = 0.4,
|
|
20
|
+
model_dir: str = None
|
|
21
|
+
) -> pd.DataFrame:
|
|
22
|
+
"""
|
|
23
|
+
PRF site prediction function
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
sequence: Single or multiple DNA sequences for sliding window prediction
|
|
27
|
+
data: DataFrame data, must contain 'Long_Sequence' or '399bp' column for region prediction
|
|
28
|
+
window_size: Sliding window size (default: 3)
|
|
29
|
+
short_threshold: Short model (HistGB) probability threshold (default: 0.1)
|
|
30
|
+
ensemble_weight: Weight of short model in ensemble (default: 0.4, long weight: 0.6)
|
|
31
|
+
model_dir: Model directory path (optional)
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
pandas.DataFrame: Prediction results containing the following main fields:
|
|
35
|
+
- Short_Probability: Short model prediction probability
|
|
36
|
+
- Long_Probability: Long model prediction probability
|
|
37
|
+
- Ensemble_Probability: Ensemble prediction probability (main result)
|
|
38
|
+
- Ensemble_Weights: Weight configuration information
|
|
39
|
+
|
|
40
|
+
Examples:
|
|
41
|
+
# 1. Single sequence sliding window prediction
|
|
42
|
+
>>> from FScanpy import predict_prf
|
|
43
|
+
>>> sequence = "ATGCGTACGT..."
|
|
44
|
+
>>> results = predict_prf(sequence=sequence)
|
|
45
|
+
|
|
46
|
+
# 2. Multiple sequences sliding window prediction
|
|
47
|
+
>>> sequences = ["ATGCGTACGT...", "GCTATAGCAT..."]
|
|
48
|
+
>>> results = predict_prf(sequence=sequences)
|
|
49
|
+
|
|
50
|
+
# 3. Custom ensemble weight ratio
|
|
51
|
+
>>> results = predict_prf(sequence=sequence, ensemble_weight=0.3) # 3:7 ratio
|
|
52
|
+
|
|
53
|
+
# 4. DataFrame region prediction
|
|
54
|
+
>>> import pandas as pd
|
|
55
|
+
>>> data = pd.DataFrame({
|
|
56
|
+
... 'Long_Sequence': ['ATGCGT...', 'GCTATAG...'] # or use '399bp'
|
|
57
|
+
... })
|
|
58
|
+
>>> results = predict_prf(data=data)
|
|
59
|
+
"""
|
|
60
|
+
predictor = PRFPredictor(model_dir=model_dir)
|
|
61
|
+
|
|
62
|
+
# Validate input parameters
|
|
63
|
+
if sequence is None and data is None:
|
|
64
|
+
raise ValueError("Must provide either sequence or data parameter")
|
|
65
|
+
if sequence is not None and data is not None:
|
|
66
|
+
raise ValueError("Cannot provide both sequence and data parameters")
|
|
67
|
+
if not (0.0 <= ensemble_weight <= 1.0):
|
|
68
|
+
raise ValueError("ensemble_weight must be between 0.0 and 1.0")
|
|
69
|
+
|
|
70
|
+
# Sliding window prediction mode
|
|
71
|
+
if sequence is not None:
|
|
72
|
+
if isinstance(sequence, str):
|
|
73
|
+
# Single sequence prediction
|
|
74
|
+
return predictor.predict_sequence(
|
|
75
|
+
sequence, window_size, short_threshold, ensemble_weight)
|
|
76
|
+
elif isinstance(sequence, (list, tuple)):
|
|
77
|
+
# Multiple sequences prediction
|
|
78
|
+
results = []
|
|
79
|
+
for i, seq in enumerate(sequence, 1):
|
|
80
|
+
try:
|
|
81
|
+
result = predictor.predict_sequence(
|
|
82
|
+
seq, window_size, short_threshold, ensemble_weight)
|
|
83
|
+
result['Sequence_ID'] = f'seq_{i}'
|
|
84
|
+
results.append(result)
|
|
85
|
+
except Exception as e:
|
|
86
|
+
print(f"Warning: Sequence {i} prediction failed - {str(e)}")
|
|
87
|
+
return pd.concat(results, ignore_index=True) if results else pd.DataFrame()
|
|
88
|
+
|
|
89
|
+
# Region prediction mode
|
|
90
|
+
else:
|
|
91
|
+
if not isinstance(data, pd.DataFrame):
|
|
92
|
+
raise ValueError("data parameter must be pandas DataFrame type")
|
|
93
|
+
|
|
94
|
+
# Check column names (support both new and old naming conventions)
|
|
95
|
+
seq_column = None
|
|
96
|
+
if 'Long_Sequence' in data.columns:
|
|
97
|
+
seq_column = 'Long_Sequence'
|
|
98
|
+
elif '399bp' in data.columns:
|
|
99
|
+
seq_column = '399bp'
|
|
100
|
+
else:
|
|
101
|
+
raise ValueError("DataFrame must contain 'Long_Sequence' or '399bp' column")
|
|
102
|
+
|
|
103
|
+
# Call region prediction function
|
|
104
|
+
try:
|
|
105
|
+
results = predictor.predict_regions(
|
|
106
|
+
data[seq_column], short_threshold, ensemble_weight)
|
|
107
|
+
|
|
108
|
+
# Add other columns from original data
|
|
109
|
+
for col in data.columns:
|
|
110
|
+
if col not in ['Long_Sequence', '399bp', 'Short_Sequence', '33bp']:
|
|
111
|
+
results[col] = data[col].values
|
|
112
|
+
|
|
113
|
+
return results
|
|
114
|
+
|
|
115
|
+
except Exception as e:
|
|
116
|
+
print(f"Warning: Region prediction failed - {str(e)}")
|
|
117
|
+
# Create empty results
|
|
118
|
+
long_weight = 1.0 - ensemble_weight
|
|
119
|
+
results = pd.DataFrame({
|
|
120
|
+
'Short_Probability': [0.0] * len(data),
|
|
121
|
+
'Long_Probability': [0.0] * len(data),
|
|
122
|
+
'Ensemble_Probability': [0.0] * len(data),
|
|
123
|
+
'Ensemble_Weights': [f'Short:{ensemble_weight:.1f}, Long:{long_weight:.1f}'] * len(data)
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
# Add original data columns
|
|
127
|
+
for col in data.columns:
|
|
128
|
+
results[col] = data[col].values
|
|
129
|
+
|
|
130
|
+
return results
|
|
131
|
+
|
|
132
|
+
def plot_prf_prediction(
|
|
133
|
+
sequence: str,
|
|
134
|
+
window_size: int = 3,
|
|
135
|
+
short_threshold: float = 0.65,
|
|
136
|
+
long_threshold: float = 0.8,
|
|
137
|
+
ensemble_weight: float = 0.4,
|
|
138
|
+
title: str = None,
|
|
139
|
+
save_path: str = None,
|
|
140
|
+
figsize: tuple = (12, 8),
|
|
141
|
+
dpi: int = 300,
|
|
142
|
+
model_dir: str = None
|
|
143
|
+
) -> tuple:
|
|
144
|
+
"""
|
|
145
|
+
Plot PRF prediction results for sequence frameshifting probability
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
sequence: Input DNA sequence
|
|
149
|
+
window_size: Sliding window size (default: 3)
|
|
150
|
+
short_threshold: Short model (HistGB) filtering threshold (default: 0.65)
|
|
151
|
+
long_threshold: Long model (BiLSTM-CNN) filtering threshold (default: 0.8)
|
|
152
|
+
ensemble_weight: Weight of short model in ensemble (default: 0.4, long weight: 0.6)
|
|
153
|
+
title: Plot title (optional)
|
|
154
|
+
save_path: Save path (optional, saves plot if provided)
|
|
155
|
+
figsize: Figure size (default: (12, 8))
|
|
156
|
+
dpi: Figure resolution (default: 300)
|
|
157
|
+
model_dir: Model directory path (optional)
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
tuple: (pd.DataFrame, matplotlib.figure.Figure) prediction results and figure object
|
|
161
|
+
|
|
162
|
+
Examples:
|
|
163
|
+
# 1. Simple plotting
|
|
164
|
+
>>> from FScanpy import plot_prf_prediction
|
|
165
|
+
>>> sequence = "ATGCGTACGT..."
|
|
166
|
+
>>> results, fig = plot_prf_prediction(sequence)
|
|
167
|
+
>>> plt.show()
|
|
168
|
+
|
|
169
|
+
# 2. Custom thresholds and ensemble weights
|
|
170
|
+
>>> results, fig = plot_prf_prediction(
|
|
171
|
+
... sequence,
|
|
172
|
+
... short_threshold=0.7,
|
|
173
|
+
... long_threshold=0.85,
|
|
174
|
+
... ensemble_weight=0.3, # 3:7 weight ratio
|
|
175
|
+
... title="Custom Weight Prediction Results",
|
|
176
|
+
... save_path="prediction_result.png"
|
|
177
|
+
... )
|
|
178
|
+
|
|
179
|
+
# 3. Equal weight combination
|
|
180
|
+
>>> results, fig = plot_prf_prediction(
|
|
181
|
+
... sequence,
|
|
182
|
+
... ensemble_weight=0.5 # 5:5 equal weights
|
|
183
|
+
... )
|
|
184
|
+
|
|
185
|
+
# 4. Long model dominated
|
|
186
|
+
>>> results, fig = plot_prf_prediction(
|
|
187
|
+
... sequence,
|
|
188
|
+
... ensemble_weight=0.2 # 2:8 weights, long model dominated
|
|
189
|
+
... )
|
|
190
|
+
"""
|
|
191
|
+
if not (0.0 <= ensemble_weight <= 1.0):
|
|
192
|
+
raise ValueError("ensemble_weight must be between 0.0 and 1.0")
|
|
193
|
+
|
|
194
|
+
predictor = PRFPredictor(model_dir=model_dir)
|
|
195
|
+
|
|
196
|
+
return predictor.plot_sequence_prediction(
|
|
197
|
+
sequence=sequence,
|
|
198
|
+
window_size=window_size,
|
|
199
|
+
short_threshold=short_threshold,
|
|
200
|
+
long_threshold=long_threshold,
|
|
201
|
+
ensemble_weight=ensemble_weight,
|
|
202
|
+
title=title,
|
|
203
|
+
save_path=save_path,
|
|
204
|
+
figsize=figsize,
|
|
205
|
+
dpi=dpi
|
|
206
|
+
)
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FScanpy数据模块
|
|
3
|
+
提供测试数据访问和处理功能
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import List
|
|
9
|
+
|
|
10
|
+
def get_test_data_path(filename: str) -> str:
|
|
11
|
+
"""
|
|
12
|
+
获取测试数据文件的完整路径
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
filename: 测试数据文件名
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
str: 文件的完整路径
|
|
19
|
+
|
|
20
|
+
Examples:
|
|
21
|
+
>>> from FScanpy.data import get_test_data_path
|
|
22
|
+
>>> blastx_file = get_test_data_path('blastx_example.xlsx')
|
|
23
|
+
>>> mrna_file = get_test_data_path('mrna_example.fasta')
|
|
24
|
+
>>> region_file = get_test_data_path('region_example.csv')
|
|
25
|
+
>>> full_seq_file = get_test_data_path('full_seq.xlsx')
|
|
26
|
+
"""
|
|
27
|
+
current_dir = Path(__file__).parent
|
|
28
|
+
test_data_dir = current_dir / "test_data"
|
|
29
|
+
file_path = test_data_dir / filename
|
|
30
|
+
|
|
31
|
+
if not file_path.exists():
|
|
32
|
+
available_files = list_test_data()
|
|
33
|
+
raise FileNotFoundError(
|
|
34
|
+
f"测试数据文件不存在: {filename}\n"
|
|
35
|
+
f"可用的测试数据文件: {available_files}"
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
return str(file_path)
|
|
39
|
+
|
|
40
|
+
def list_test_data() -> List[str]:
|
|
41
|
+
"""
|
|
42
|
+
列出所有可用的测试数据文件
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
List[str]: 测试数据文件名列表
|
|
46
|
+
|
|
47
|
+
Examples:
|
|
48
|
+
>>> from FScanpy.data import list_test_data
|
|
49
|
+
>>> files = list_test_data()
|
|
50
|
+
>>> print(files)
|
|
51
|
+
['blastx_example.xlsx', 'mrna_example.fasta', 'region_example.csv']
|
|
52
|
+
"""
|
|
53
|
+
try:
|
|
54
|
+
current_dir = Path(__file__).parent
|
|
55
|
+
test_data_dir = current_dir / "test_data"
|
|
56
|
+
|
|
57
|
+
if not test_data_dir.exists():
|
|
58
|
+
return []
|
|
59
|
+
|
|
60
|
+
files = []
|
|
61
|
+
for file_path in test_data_dir.iterdir():
|
|
62
|
+
if file_path.is_file() and not file_path.name.startswith('.'):
|
|
63
|
+
files.append(file_path.name)
|
|
64
|
+
|
|
65
|
+
return sorted(files)
|
|
66
|
+
|
|
67
|
+
except Exception:
|
|
68
|
+
return []
|
|
69
|
+
|
|
70
|
+
def print_test_data_info():
|
|
71
|
+
"""
|
|
72
|
+
打印测试数据的详细信息
|
|
73
|
+
"""
|
|
74
|
+
print("📋 FScanpy 测试数据信息:")
|
|
75
|
+
print("=" * 50)
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
current_dir = Path(__file__).parent
|
|
79
|
+
test_data_dir = current_dir / "test_data"
|
|
80
|
+
|
|
81
|
+
if not test_data_dir.exists():
|
|
82
|
+
print("❌ 测试数据目录不存在")
|
|
83
|
+
return
|
|
84
|
+
|
|
85
|
+
files = list_test_data()
|
|
86
|
+
if not files:
|
|
87
|
+
print("❌ 没有找到测试数据文件")
|
|
88
|
+
return
|
|
89
|
+
|
|
90
|
+
print(f"📁 数据目录: {test_data_dir}")
|
|
91
|
+
print(f"📊 文件数量: {len(files)}")
|
|
92
|
+
print()
|
|
93
|
+
|
|
94
|
+
file_descriptions = {
|
|
95
|
+
'blastx_example.xlsx': '🧬 BLASTX比对结果示例 (1000条记录)',
|
|
96
|
+
'mrna_example.fasta': '🧬 mRNA序列示例数据',
|
|
97
|
+
'region_example.csv': '🎯 PRF区域验证数据 (含标签)',
|
|
98
|
+
'full_seq.xlsx': '🧬 完整序列示例数据'
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
for filename in files:
|
|
102
|
+
file_path = test_data_dir / filename
|
|
103
|
+
size_mb = file_path.stat().st_size / (1024 * 1024)
|
|
104
|
+
|
|
105
|
+
description = file_descriptions.get(filename, '📄 数据文件')
|
|
106
|
+
print(f" {description}")
|
|
107
|
+
print(f" 文件名: {filename}")
|
|
108
|
+
print(f" 大小: {size_mb:.2f} MB")
|
|
109
|
+
print(f" 路径: {file_path}")
|
|
110
|
+
print()
|
|
111
|
+
|
|
112
|
+
print("🚀 使用示例:")
|
|
113
|
+
print(" from FScanpy.data import get_test_data_path")
|
|
114
|
+
print(" blastx_file = get_test_data_path('blastx_example.xlsx')")
|
|
115
|
+
print(" mrna_file = get_test_data_path('mrna_example.fasta')")
|
|
116
|
+
print(" region_file = get_test_data_path('region_example.csv')")
|
|
117
|
+
print(" full_seq_file = get_test_data_path('full_seq.xlsx')")
|
|
118
|
+
except Exception as e:
|
|
119
|
+
print(f"❌ 获取数据信息时出错: {e}")
|
|
120
|
+
|
|
121
|
+
# 导出主要函数
|
|
122
|
+
__all__ = ['get_test_data_path', 'list_test_data', 'print_test_data_info']
|
|
Binary file
|
|
Binary file
|