FScanpy 1.0.0__tar.gz → 1.0.1__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 (42) hide show
  1. {fscanpy-1.0.0 → fscanpy-1.0.1}/CHANGELOG.md +19 -19
  2. {fscanpy-1.0.0 → fscanpy-1.0.1}/FScanpy/__init__.py +52 -61
  3. fscanpy-1.0.1/FScanpy/data/test_data/full_seq.xlsx +0 -0
  4. fscanpy-1.0.1/FScanpy/features/cnn_input.py +24 -0
  5. fscanpy-1.0.1/FScanpy/features/sequence.py +171 -0
  6. fscanpy-1.0.1/FScanpy/predictor.py +605 -0
  7. {fscanpy-1.0.0 → fscanpy-1.0.1}/FScanpy/pretrained/long.pth +1 -1
  8. {fscanpy-1.0.0 → fscanpy-1.0.1}/FScanpy/utils.py +79 -70
  9. {fscanpy-1.0.0 → fscanpy-1.0.1/FScanpy.egg-info}/PKG-INFO +10 -11
  10. {fscanpy-1.0.0 → fscanpy-1.0.1}/FScanpy.egg-info/SOURCES.txt +1 -0
  11. fscanpy-1.0.1/FScanpy_Demo.ipynb +910 -0
  12. {fscanpy-1.0.0 → fscanpy-1.0.1}/LICENSE +21 -21
  13. {fscanpy-1.0.0/FScanpy.egg-info → fscanpy-1.0.1}/PKG-INFO +10 -11
  14. {fscanpy-1.0.0 → fscanpy-1.0.1}/README.md +231 -232
  15. {fscanpy-1.0.0 → fscanpy-1.0.1}/README_zh.md +224 -225
  16. {fscanpy-1.0.0 → fscanpy-1.0.1}/pyproject.toml +24 -24
  17. {fscanpy-1.0.0 → fscanpy-1.0.1}/tests/run_notebooks.py +31 -11
  18. fscanpy-1.0.1/tests/test_package.py +433 -0
  19. fscanpy-1.0.1/tutorial/predict_sample.ipynb +1024 -0
  20. fscanpy-1.0.1/tutorial/predict_sample_zh.ipynb +1012 -0
  21. {fscanpy-1.0.0 → fscanpy-1.0.1}/tutorial/tutorial.md +12 -9
  22. {fscanpy-1.0.0 → fscanpy-1.0.1}/tutorial/tutorial_zh.md +12 -9
  23. fscanpy-1.0.0/FScanpy/data/test_data/full_seq.xlsx +0 -0
  24. fscanpy-1.0.0/FScanpy/features/cnn_input.py +0 -79
  25. fscanpy-1.0.0/FScanpy/features/sequence.py +0 -283
  26. fscanpy-1.0.0/FScanpy/predictor.py +0 -616
  27. fscanpy-1.0.0/FScanpy_Demo.ipynb +0 -679
  28. fscanpy-1.0.0/tests/test_package.py +0 -86
  29. fscanpy-1.0.0/tutorial/predict_sample.ipynb +0 -223
  30. {fscanpy-1.0.0 → fscanpy-1.0.1}/FScanpy/data/__init__.py +0 -0
  31. {fscanpy-1.0.0 → fscanpy-1.0.1}/FScanpy/data/test_data/blastx_example.xlsx +0 -0
  32. {fscanpy-1.0.0 → fscanpy-1.0.1}/FScanpy/data/test_data/mrna_example.fasta +0 -0
  33. {fscanpy-1.0.0 → fscanpy-1.0.1}/FScanpy/data/test_data/region_example.csv +0 -0
  34. {fscanpy-1.0.0 → fscanpy-1.0.1}/FScanpy/features/__init__.py +0 -0
  35. {fscanpy-1.0.0 → fscanpy-1.0.1}/FScanpy/pretrained/short.pkl +0 -0
  36. {fscanpy-1.0.0 → fscanpy-1.0.1}/FScanpy.egg-info/dependency_links.txt +0 -0
  37. {fscanpy-1.0.0 → fscanpy-1.0.1}/FScanpy.egg-info/requires.txt +0 -0
  38. {fscanpy-1.0.0 → fscanpy-1.0.1}/FScanpy.egg-info/top_level.txt +0 -0
  39. {fscanpy-1.0.0 → fscanpy-1.0.1}/MANIFEST.in +0 -0
  40. {fscanpy-1.0.0 → fscanpy-1.0.1}/setup.cfg +0 -0
  41. {fscanpy-1.0.0 → fscanpy-1.0.1}/tutorial/image/ML.png +0 -0
  42. {fscanpy-1.0.0 → fscanpy-1.0.1}/tutorial/image/structure.jpg +0 -0
@@ -1,19 +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.
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.
@@ -1,15 +1,15 @@
1
- from .predictor import PRFPredictor
1
+ from .predictor import PRFPredictor, plot_prediction_results, plot_prediction_regions
2
2
  from . import data
3
3
  from .utils import fscanr, extract_prf_regions
4
4
  import pandas as pd
5
5
  import numpy as np
6
6
  from typing import Union, List, Dict
7
7
 
8
- __version__ = '1.0.0'
9
- __author__ = 'Yang Yuhao'
10
- __email__ = 'ykongxiang@qq.com'
8
+ __version__ = '1.0.1'
9
+ __author__ = 'Yang Yuhao'
10
+ __email__ = 'ykongxiang@qq.com'
11
11
 
12
- __all__ = ['PRFPredictor', 'predict_prf', 'plot_prf_prediction', 'fscanr', 'extract_prf_regions', 'data', '__version__', '__author__', '__email__']
12
+ __all__ = ['PRFPredictor', 'predict_prf', 'plot_prf_prediction', 'plot_prediction_results', 'plot_prediction_regions', 'fscanr', 'extract_prf_regions', 'data', '__version__', '__author__', '__email__']
13
13
 
14
14
  def predict_prf(
15
15
  sequence: Union[str, List[str], None] = None,
@@ -21,35 +21,35 @@ def predict_prf(
21
21
  ) -> pd.DataFrame:
22
22
  """
23
23
  PRF site prediction function
24
-
24
+
25
25
  Args:
26
26
  sequence: Single or multiple DNA sequences for sliding window prediction
27
27
  data: DataFrame data, must contain 'Long_Sequence' or '399bp' column for region prediction
28
- window_size: Sliding window size (default: 3)
28
+ window_size: Scanning interval in nucleotides (default: 3)
29
29
  short_threshold: Short model (HistGB) probability threshold (default: 0.1)
30
30
  ensemble_weight: Weight of short model in ensemble (default: 0.4, long weight: 0.6)
31
31
  model_dir: Model directory path (optional)
32
-
32
+
33
33
  Returns:
34
34
  pandas.DataFrame: Prediction results containing the following main fields:
35
35
  - Short_Probability: Short model prediction probability
36
- - Long_Probability: Long model prediction probability
36
+ - Long_Probability: Long model prediction probability
37
37
  - Ensemble_Probability: Ensemble prediction probability (main result)
38
38
  - Ensemble_Weights: Weight configuration information
39
-
39
+
40
40
  Examples:
41
41
  # 1. Single sequence sliding window prediction
42
42
  >>> from FScanpy import predict_prf
43
43
  >>> sequence = "ATGCGTACGT..."
44
44
  >>> results = predict_prf(sequence=sequence)
45
-
45
+
46
46
  # 2. Multiple sequences sliding window prediction
47
47
  >>> sequences = ["ATGCGTACGT...", "GCTATAGCAT..."]
48
48
  >>> results = predict_prf(sequence=sequences)
49
-
49
+
50
50
  # 3. Custom ensemble weight ratio
51
51
  >>> results = predict_prf(sequence=sequence, ensemble_weight=0.3) # 3:7 ratio
52
-
52
+
53
53
  # 4. DataFrame region prediction
54
54
  >>> import pandas as pd
55
55
  >>> data = pd.DataFrame({
@@ -57,8 +57,6 @@ def predict_prf(
57
57
  ... })
58
58
  >>> results = predict_prf(data=data)
59
59
  """
60
- predictor = PRFPredictor(model_dir=model_dir)
61
-
62
60
  # Validate input parameters
63
61
  if sequence is None and data is None:
64
62
  raise ValueError("Must provide either sequence or data parameter")
@@ -66,7 +64,9 @@ def predict_prf(
66
64
  raise ValueError("Cannot provide both sequence and data parameters")
67
65
  if not (0.0 <= ensemble_weight <= 1.0):
68
66
  raise ValueError("ensemble_weight must be between 0.0 and 1.0")
69
-
67
+
68
+ predictor = PRFPredictor(model_dir=model_dir)
69
+
70
70
  # Sliding window prediction mode
71
71
  if sequence is not None:
72
72
  if isinstance(sequence, str):
@@ -83,14 +83,16 @@ def predict_prf(
83
83
  result['Sequence_ID'] = f'seq_{i}'
84
84
  results.append(result)
85
85
  except Exception as e:
86
- print(f"Warning: Sequence {i} prediction failed - {str(e)}")
86
+ raise RuntimeError(f"Prediction failed for sequence {i}: {e}") from e
87
87
  return pd.concat(results, ignore_index=True) if results else pd.DataFrame()
88
-
88
+ else:
89
+ raise ValueError("sequence must be a string or a list/tuple of strings")
90
+
89
91
  # Region prediction mode
90
92
  else:
91
93
  if not isinstance(data, pd.DataFrame):
92
94
  raise ValueError("data parameter must be pandas DataFrame type")
93
-
95
+
94
96
  # Check column names (support both new and old naming conventions)
95
97
  seq_column = None
96
98
  if 'Long_Sequence' in data.columns:
@@ -99,35 +101,14 @@ def predict_prf(
99
101
  seq_column = '399bp'
100
102
  else:
101
103
  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
104
+
105
+ results = predictor.predict_regions(data[seq_column], short_threshold, ensemble_weight)
106
+ # Preserve metadata without allowing stale scores to replace model output.
107
+ for column in data.columns:
108
+ if column not in results.columns and column not in ['Long_Sequence', '399bp', 'Short_Sequence', '33bp']:
109
+ results[column] = data[column].values
110
+ return results
111
+
131
112
 
132
113
  def plot_prf_prediction(
133
114
  sequence: str,
@@ -139,14 +120,18 @@ def plot_prf_prediction(
139
120
  save_path: str = None,
140
121
  figsize: tuple = (12, 8),
141
122
  dpi: int = 300,
142
- model_dir: str = None
123
+ model_dir: str = None,
124
+ *,
125
+ reference_positions=None,
126
+ heatmap_ratios=(0.1, 0.1, 1),
127
+ candidate_threshold=0.8
143
128
  ) -> tuple:
144
129
  """
145
130
  Plot PRF prediction results for sequence frameshifting probability
146
-
131
+
147
132
  Args:
148
133
  sequence: Input DNA sequence
149
- window_size: Sliding window size (default: 3)
134
+ window_size: Scanning interval in nucleotides (default: 3)
150
135
  short_threshold: Short model (HistGB) filtering threshold (default: 0.65)
151
136
  long_threshold: Long model (BiLSTM-CNN) filtering threshold (default: 0.8)
152
137
  ensemble_weight: Weight of short model in ensemble (default: 0.4, long weight: 0.6)
@@ -154,34 +139,37 @@ def plot_prf_prediction(
154
139
  save_path: Save path (optional, saves plot if provided)
155
140
  figsize: Figure size (default: (12, 8))
156
141
  dpi: Figure resolution (default: 300)
142
+ reference_positions: Optional independently supplied 0-based reference coordinates
143
+ heatmap_ratios: Three panel heights; use (0.35, 0.35, 2.8) for thick heatmaps
144
+ candidate_threshold: Ensemble cutoff for predicted candidate marks (default: 0.8)
157
145
  model_dir: Model directory path (optional)
158
-
146
+
159
147
  Returns:
160
148
  tuple: (pd.DataFrame, matplotlib.figure.Figure) prediction results and figure object
161
-
149
+
162
150
  Examples:
163
151
  # 1. Simple plotting
164
152
  >>> from FScanpy import plot_prf_prediction
165
153
  >>> sequence = "ATGCGTACGT..."
166
154
  >>> results, fig = plot_prf_prediction(sequence)
167
155
  >>> plt.show()
168
-
156
+
169
157
  # 2. Custom thresholds and ensemble weights
170
158
  >>> results, fig = plot_prf_prediction(
171
- ... sequence,
172
- ... short_threshold=0.7,
159
+ ... sequence,
160
+ ... short_threshold=0.7,
173
161
  ... long_threshold=0.85,
174
162
  ... ensemble_weight=0.3, # 3:7 weight ratio
175
163
  ... title="Custom Weight Prediction Results",
176
164
  ... save_path="prediction_result.png"
177
165
  ... )
178
-
166
+
179
167
  # 3. Equal weight combination
180
168
  >>> results, fig = plot_prf_prediction(
181
169
  ... sequence,
182
170
  ... ensemble_weight=0.5 # 5:5 equal weights
183
171
  ... )
184
-
172
+
185
173
  # 4. Long model dominated
186
174
  >>> results, fig = plot_prf_prediction(
187
175
  ... sequence,
@@ -190,9 +178,9 @@ def plot_prf_prediction(
190
178
  """
191
179
  if not (0.0 <= ensemble_weight <= 1.0):
192
180
  raise ValueError("ensemble_weight must be between 0.0 and 1.0")
193
-
181
+
194
182
  predictor = PRFPredictor(model_dir=model_dir)
195
-
183
+
196
184
  return predictor.plot_sequence_prediction(
197
185
  sequence=sequence,
198
186
  window_size=window_size,
@@ -202,5 +190,8 @@ def plot_prf_prediction(
202
190
  title=title,
203
191
  save_path=save_path,
204
192
  figsize=figsize,
205
- dpi=dpi
193
+ dpi=dpi,
194
+ reference_positions=reference_positions,
195
+ heatmap_ratios=heatmap_ratios,
196
+ candidate_threshold=candidate_threshold
206
197
  )
@@ -0,0 +1,24 @@
1
+ import numpy as np
2
+ from typing import List, Union
3
+
4
+ class CNNInputProcessor:
5
+ """CNN模型输入数据处理器"""
6
+
7
+ def __init__(self, max_length: int = 399):
8
+ self.max_length = max_length
9
+ self.base_to_num = {'A': 0, 'T': 1, 'C': 2, 'G': 3, 'N': 4}
10
+
11
+ def trim_sequence(self, seq, target_length):
12
+ """Center-crop to exactly target_length, removing an odd extra base on the right."""
13
+ if len(seq) <= target_length:
14
+ return seq
15
+ start = (len(seq) - target_length) // 2
16
+ return seq[start:start + target_length]
17
+
18
+ def prepare_sequence(self, sequence: str) -> np.ndarray:
19
+ """Encode an exactly sized input; U is treated as T and unknown bases as N."""
20
+ sequence = str(sequence).upper().replace('U', 'T')
21
+ sequence = self.trim_sequence(sequence, self.max_length)
22
+ encoded = [self.base_to_num.get(base, 4) for base in sequence]
23
+ encoded.extend([4] * (self.max_length - len(encoded)))
24
+ return np.array(encoded).reshape(1, self.max_length, 1)
@@ -0,0 +1,171 @@
1
+ import numpy as np
2
+ import pandas as pd
3
+ import itertools
4
+ from typing import List, Dict, Union
5
+
6
+ class SequenceFeatureExtractor:
7
+ """DNA序列特征提取器"""
8
+
9
+ def __init__(self, seq_length=33):
10
+ """初始化特征提取器"""
11
+ self.bases = ['A', 'T', 'G', 'C']
12
+ self.valid_bases = set('ATGCN')
13
+ self.seq_length = seq_length # 添加序列长度配置
14
+ self.feature_names = self._get_feature_names()
15
+
16
+ def _get_feature_names(self) -> List[str]:
17
+ """
18
+ 返回特征名称列表,包含所有可能的碱基特征
19
+
20
+ Returns:
21
+ features: 特征名称列表
22
+ """
23
+ features = []
24
+
25
+ # 基础特征 (包含N)
26
+ bases = ['A', 'T', 'G', 'C', 'N']
27
+ features.extend(bases)
28
+
29
+ # 3-mer特征
30
+ kmers_3 = [''.join(p) for p in itertools.product(bases, repeat=3)] # 125个特征
31
+ features.extend(kmers_3)
32
+
33
+ # 密码子特征
34
+ codons = [''.join(p) for p in itertools.product(['A', 'T', 'G', 'C'], repeat=3)] # 64个密码子
35
+ n_codons = self.seq_length // 3 # 计算序列中包含的完整密码子数量
36
+ for i in range(n_codons):
37
+ for codon in codons:
38
+ features.append(f'codon_pos_{i}_{codon}')
39
+
40
+ # GC含量特征
41
+ features.append('gc_content')
42
+
43
+ # 序列复杂度特征
44
+ features.append('sequence_complexity')
45
+
46
+ return features
47
+
48
+ def trim_sequence(self, seq, target_length):
49
+ """Center-crop to exactly target_length, removing an odd extra base on the right."""
50
+ if len(seq) <= target_length:
51
+ return seq
52
+ start = (len(seq) - target_length) // 2
53
+ return seq[start:start + target_length]
54
+
55
+ def _preprocess_sequence(self, sequence):
56
+ """
57
+ 将DNA序列转换为特征向量
58
+
59
+ Args:
60
+ sequence: DNA序列
61
+
62
+ Returns:
63
+ feature_vector: 特征向量
64
+ """
65
+ try:
66
+ feature_names = self.feature_names
67
+
68
+ if pd.isna(sequence) or not isinstance(sequence, str):
69
+ sequence = str(sequence)
70
+ sequence = sequence.upper().replace('U', 'T') # 统一为大写字母
71
+ sequence = ''.join(base if base in self.valid_bases else 'N' for base in sequence)
72
+
73
+ # 如果序列长度不等于目标长度,进行截取或填充
74
+ if len(sequence) > self.seq_length:
75
+ sequence = self.trim_sequence(sequence, self.seq_length)
76
+ else:
77
+ sequence = sequence[:self.seq_length].ljust(self.seq_length, 'N')
78
+
79
+ # 初始化特征字典
80
+ features = {
81
+ 'A': 0,
82
+ 'T': 0,
83
+ 'G': 0,
84
+ 'C': 0,
85
+ 'N': 0
86
+ }
87
+ kmer_features = {}
88
+
89
+ # 碱基组成
90
+ for base in ['A', 'T', 'G', 'C', 'N']:
91
+ features[base] = sequence.count(base) / self.seq_length
92
+
93
+ # 3-mer特征
94
+ for kmer in [''.join(p) for p in itertools.product(['A', 'T', 'G', 'C', 'N'], repeat=3)]:
95
+ kmer_count = 0
96
+ for i in range(self.seq_length - 2):
97
+ if sequence[i:i+3] == kmer:
98
+ kmer_count += 1
99
+ kmer_features[kmer] = kmer_count / max(1, self.seq_length - 2)
100
+
101
+ # 密码子特征
102
+ codon_features = {}
103
+ codons = [''.join(p) for p in itertools.product(['A', 'T', 'G', 'C'], repeat=3)] # 64个密码子
104
+ n_codons = self.seq_length // 3 # 计算序列中包含的完整密码子数量
105
+ for i in range(n_codons):
106
+ pos_start = i * 3
107
+ current_codon = sequence[pos_start:pos_start+3]
108
+ for codon in codons:
109
+ codon_features[f'codon_pos_{i}_{codon}'] = 1 if current_codon == codon and 'N' not in current_codon else 0
110
+
111
+ # GC含量
112
+ valid_bases = [b for b in sequence if b != 'N']
113
+ gc_content = (valid_bases.count('G') + valid_bases.count('C')) / len(valid_bases) if valid_bases else 0
114
+
115
+ # 序列复杂度(Shannon熵)
116
+ from collections import Counter
117
+ valid_counts = Counter(valid_bases)
118
+ total_valid = sum(valid_counts.values())
119
+ entropy = 0
120
+ for cnt in valid_counts.values():
121
+ p = cnt / total_valid
122
+ entropy += -p * np.log2(p)
123
+ entropy /= np.log2(4) # 归一化到0-1
124
+
125
+ # 合并所有特征
126
+ all_features = {**features, **kmer_features, **codon_features}
127
+ all_features['gc_content'] = gc_content
128
+ all_features['sequence_complexity'] = entropy
129
+
130
+ # 确保特征顺序一致
131
+ feature_vector = [all_features.get(f, 0.0) for f in feature_names]
132
+
133
+ return feature_vector
134
+ except Exception as e:
135
+ raise ValueError(f"特征提取失败: {str(e)}")
136
+
137
+ def extract_features_batch(self, sequences: List[Union[str, float]]) -> np.ndarray:
138
+ """
139
+ 批量提取特征
140
+
141
+ Args:
142
+ sequences: DNA序列列表
143
+
144
+ Returns:
145
+ np.ndarray: 特征矩阵
146
+ """
147
+ try:
148
+ return np.array([self.extract_features(seq) for seq in sequences])
149
+ except Exception as e:
150
+ raise ValueError(f"批量特征提取失败: {str(e)}")
151
+
152
+ def predict_region_batch(self, data: pd.DataFrame, gb_threshold: float = 0.1) -> pd.DataFrame:
153
+ """Deprecated compatibility wrapper for the public region predictor.
154
+
155
+ Uses the central 33 bp of Long_Sequence/399bp through predict_prf;
156
+ feature extraction itself does not own classification models.
157
+ """
158
+ import warnings
159
+ from .. import predict_prf
160
+ warnings.warn('Use PRFPredictor.predict_regions() or predict_prf(data=...) instead',
161
+ DeprecationWarning, stacklevel=2)
162
+ return predict_prf(data=data, short_threshold=gb_threshold)
163
+
164
+ def extract_features(self, sequence: str) -> list:
165
+ """Return the trained feature dimensions after trimming or N-padding the input.
166
+
167
+ This shares the preprocessing used by batch feature extraction, including
168
+ U-to-T normalization. Short inputs are right-padded to ``seq_length``.
169
+ Feature extraction errors are raised rather than replaced by zero vectors.
170
+ """
171
+ return self._preprocess_sequence(sequence)