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.
Files changed (34) hide show
  1. fscanpy-1.0.0/CHANGELOG.md +19 -0
  2. fscanpy-1.0.0/FScanpy/__init__.py +206 -0
  3. fscanpy-1.0.0/FScanpy/data/__init__.py +122 -0
  4. fscanpy-1.0.0/FScanpy/data/test_data/blastx_example.xlsx +0 -0
  5. fscanpy-1.0.0/FScanpy/data/test_data/full_seq.xlsx +0 -0
  6. fscanpy-1.0.0/FScanpy/data/test_data/mrna_example.fasta +2584 -0
  7. fscanpy-1.0.0/FScanpy/data/test_data/region_example.csv +4 -0
  8. fscanpy-1.0.0/FScanpy/features/__init__.py +4 -0
  9. fscanpy-1.0.0/FScanpy/features/cnn_input.py +79 -0
  10. fscanpy-1.0.0/FScanpy/features/sequence.py +283 -0
  11. fscanpy-1.0.0/FScanpy/predictor.py +616 -0
  12. fscanpy-1.0.0/FScanpy/pretrained/long.pth +4 -0
  13. fscanpy-1.0.0/FScanpy/pretrained/short.pkl +0 -0
  14. fscanpy-1.0.0/FScanpy/utils.py +203 -0
  15. fscanpy-1.0.0/FScanpy.egg-info/PKG-INFO +262 -0
  16. fscanpy-1.0.0/FScanpy.egg-info/SOURCES.txt +32 -0
  17. fscanpy-1.0.0/FScanpy.egg-info/dependency_links.txt +1 -0
  18. fscanpy-1.0.0/FScanpy.egg-info/requires.txt +8 -0
  19. fscanpy-1.0.0/FScanpy.egg-info/top_level.txt +1 -0
  20. fscanpy-1.0.0/FScanpy_Demo.ipynb +679 -0
  21. fscanpy-1.0.0/LICENSE +21 -0
  22. fscanpy-1.0.0/MANIFEST.in +5 -0
  23. fscanpy-1.0.0/PKG-INFO +262 -0
  24. fscanpy-1.0.0/README.md +232 -0
  25. fscanpy-1.0.0/README_zh.md +225 -0
  26. fscanpy-1.0.0/pyproject.toml +48 -0
  27. fscanpy-1.0.0/setup.cfg +4 -0
  28. fscanpy-1.0.0/tests/run_notebooks.py +92 -0
  29. fscanpy-1.0.0/tests/test_package.py +86 -0
  30. fscanpy-1.0.0/tutorial/image/ML.png +0 -0
  31. fscanpy-1.0.0/tutorial/image/structure.jpg +0 -0
  32. fscanpy-1.0.0/tutorial/predict_sample.ipynb +223 -0
  33. fscanpy-1.0.0/tutorial/tutorial.md +617 -0
  34. 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']