pylocuszoom 0.6.0__py3-none-any.whl → 0.8.0__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.
@@ -0,0 +1,172 @@
1
+ """DataFrame validation builder for pyLocusZoom.
2
+
3
+ Provides a fluent API for validating pandas DataFrames with composable
4
+ validation rules. Accumulates all validation errors before raising.
5
+ """
6
+
7
+ from typing import List, Optional
8
+
9
+ import pandas as pd
10
+ from pandas.api.types import is_numeric_dtype
11
+
12
+ from .utils import ValidationError
13
+
14
+
15
+ class DataFrameValidator:
16
+ """Builder for composable DataFrame validation.
17
+
18
+ Validates DataFrames with method chaining and accumulates all errors
19
+ before raising. This enables clear, readable validation code with
20
+ comprehensive error messages.
21
+
22
+ Example:
23
+ >>> validator = DataFrameValidator(df, name="gwas_df")
24
+ >>> validator.require_columns(["chr", "pos", "p"])
25
+ ... .require_numeric(["pos", "p"])
26
+ ... .require_range("p", min_val=0, max_val=1)
27
+ ... .validate()
28
+ """
29
+
30
+ def __init__(self, df: pd.DataFrame, name: str = "DataFrame"):
31
+ """Initialize validator.
32
+
33
+ Args:
34
+ df: DataFrame to validate.
35
+ name: Name for error messages (e.g., "gwas_df", "genes_df").
36
+ """
37
+ self._df = df
38
+ self._name = name
39
+ self._errors: List[str] = []
40
+
41
+ def require_columns(self, columns: List[str]) -> "DataFrameValidator":
42
+ """Check that required columns exist in DataFrame.
43
+
44
+ Args:
45
+ columns: List of required column names.
46
+
47
+ Returns:
48
+ Self for method chaining.
49
+ """
50
+ if not columns:
51
+ return self
52
+
53
+ missing = [col for col in columns if col not in self._df.columns]
54
+ if missing:
55
+ available = list(self._df.columns)
56
+ self._errors.append(f"Missing columns: {missing}. Available: {available}")
57
+
58
+ return self
59
+
60
+ def require_numeric(self, columns: List[str]) -> "DataFrameValidator":
61
+ """Check that columns have numeric dtype.
62
+
63
+ Skips columns that don't exist (checked separately by require_columns).
64
+
65
+ Args:
66
+ columns: List of column names that should be numeric.
67
+
68
+ Returns:
69
+ Self for method chaining.
70
+ """
71
+ for col in columns:
72
+ # Skip missing columns - let require_columns handle that
73
+ if col not in self._df.columns:
74
+ continue
75
+
76
+ if not is_numeric_dtype(self._df[col]):
77
+ actual_dtype = self._df[col].dtype
78
+ self._errors.append(
79
+ f"Column '{col}' must be numeric, got {actual_dtype}"
80
+ )
81
+
82
+ return self
83
+
84
+ def require_range(
85
+ self,
86
+ column: str,
87
+ min_val: Optional[float] = None,
88
+ max_val: Optional[float] = None,
89
+ exclusive_min: bool = False,
90
+ exclusive_max: bool = False,
91
+ ) -> "DataFrameValidator":
92
+ """Check that column values are within specified range.
93
+
94
+ Args:
95
+ column: Column name to check.
96
+ min_val: Minimum allowed value (inclusive by default).
97
+ max_val: Maximum allowed value (inclusive by default).
98
+ exclusive_min: If True, minimum is exclusive (values must be > min_val).
99
+ exclusive_max: If True, maximum is exclusive (values must be < max_val).
100
+
101
+ Returns:
102
+ Self for method chaining.
103
+ """
104
+ # Skip missing columns
105
+ if column not in self._df.columns:
106
+ return self
107
+
108
+ col_data = self._df[column]
109
+
110
+ # Check minimum bound
111
+ if min_val is not None:
112
+ if exclusive_min:
113
+ invalid_count = (col_data <= min_val).sum()
114
+ if invalid_count > 0:
115
+ self._errors.append(
116
+ f"Column '{column}': {invalid_count} values <= {min_val}"
117
+ )
118
+ else:
119
+ invalid_count = (col_data < min_val).sum()
120
+ if invalid_count > 0:
121
+ self._errors.append(
122
+ f"Column '{column}': {invalid_count} values < {min_val}"
123
+ )
124
+
125
+ # Check maximum bound
126
+ if max_val is not None:
127
+ if exclusive_max:
128
+ invalid_count = (col_data >= max_val).sum()
129
+ if invalid_count > 0:
130
+ self._errors.append(
131
+ f"Column '{column}': {invalid_count} values >= {max_val}"
132
+ )
133
+ else:
134
+ invalid_count = (col_data > max_val).sum()
135
+ if invalid_count > 0:
136
+ self._errors.append(
137
+ f"Column '{column}': {invalid_count} values > {max_val}"
138
+ )
139
+
140
+ return self
141
+
142
+ def require_not_null(self, columns: List[str]) -> "DataFrameValidator":
143
+ """Check that columns have no null (NaN or None) values.
144
+
145
+ Args:
146
+ columns: List of column names to check for nulls.
147
+
148
+ Returns:
149
+ Self for method chaining.
150
+ """
151
+ for col in columns:
152
+ # Skip missing columns
153
+ if col not in self._df.columns:
154
+ continue
155
+
156
+ null_count = self._df[col].isna().sum()
157
+ if null_count > 0:
158
+ self._errors.append(f"Column '{col}' has {null_count} null values")
159
+
160
+ return self
161
+
162
+ def validate(self) -> None:
163
+ """Raise ValidationError if any validation rules failed.
164
+
165
+ Raises:
166
+ ValidationError: If any validation errors were accumulated.
167
+ Error message includes all accumulated errors.
168
+ """
169
+ if self._errors:
170
+ error_msg = f"{self._name} validation failed:\n"
171
+ error_msg += "\n".join(f" - {error}" for error in self._errors)
172
+ raise ValidationError(error_msg)
@@ -1,15 +1,15 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pylocuszoom
3
- Version: 0.6.0
3
+ Version: 0.8.0
4
4
  Summary: Publication-ready regional association plots with LD coloring, gene tracks, and recombination overlays
5
5
  Project-URL: Homepage, https://github.com/michael-denyer/pylocuszoom
6
6
  Project-URL: Documentation, https://github.com/michael-denyer/pylocuszoom#readme
7
7
  Project-URL: Repository, https://github.com/michael-denyer/pylocuszoom
8
- Author: Michael Denyer
8
+ Author-email: Michael Denyer <code.denyer@gmail.com>
9
9
  License-Expression: GPL-3.0-or-later
10
10
  License-File: LICENSE.md
11
11
  Keywords: genetics,gwas,locus-zoom,locuszoom,regional-plot,visualization
12
- Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Development Status :: 4 - Beta
13
13
  Classifier: Intended Audience :: Science/Research
14
14
  Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
15
15
  Classifier: Programming Language :: Python :: 3
@@ -44,20 +44,18 @@ Requires-Dist: pyspark>=3.0.0; extra == 'spark'
44
44
  Description-Content-Type: text/markdown
45
45
 
46
46
  [![CI](https://github.com/michael-denyer/pyLocusZoom/actions/workflows/ci.yml/badge.svg)](https://github.com/michael-denyer/pyLocusZoom/actions/workflows/ci.yml)
47
- [![codecov](https://codecov.io/gh/michael-denyer/pyLocusZoom/graph/badge.svg)](https://codecov.io/gh/michael-denyer/pyLocusZoom)
48
47
  [![PyPI](https://img.shields.io/pypi/v/pylocuszoom)](https://pypi.org/project/pylocuszoom/)
49
- [![Bioconda](https://img.shields.io/conda/vn/bioconda/pylocuszoom)](https://anaconda.org/bioconda/pylocuszoom)
50
48
  [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-red.svg)](https://www.gnu.org/licenses/gpl-3.0)
51
49
  [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
52
50
  [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
53
51
  [![Matplotlib](https://img.shields.io/badge/Matplotlib-3.5+-11557c.svg)](https://matplotlib.org/)
54
- [![Plotly](https://img.shields.io/badge/Plotly-5.0+-3F4F75.svg)](https://plotly.com/python/)
52
+ [![Plotly](https://img.shields.io/badge/Plotly-5.15+-3F4F75.svg)](https://plotly.com/python/)
55
53
  [![Bokeh](https://img.shields.io/badge/Bokeh-3.8+-E6526F.svg)](https://bokeh.org/)
56
54
  [![Pandas](https://img.shields.io/badge/Pandas-1.4+-150458.svg)](https://pandas.pydata.org/)
57
55
  <img src="logo.svg" alt="pyLocusZoom logo" width="120" align="right">
58
56
  # pyLocusZoom
59
57
 
60
- Publication-ready regional association plots with LD coloring, gene tracks, and recombination overlays.
58
+ Designed for publication-ready GWAS visualization with regional association plots, gene tracks, eQTL, PheWAS, fine-mapping, and forest plots.
61
59
 
62
60
  Inspired by [LocusZoom](http://locuszoom.org/) and [locuszoomr](https://github.com/myles-lewis/locuszoomr).
63
61
 
@@ -68,20 +66,22 @@ Inspired by [LocusZoom](http://locuszoom.org/) and [locuszoomr](https://github.c
68
66
  - **Multi-species support**: Built-in reference data for *Canis lupus familiaris* (CanFam3.1/CanFam4) and *Felis catus* (FelCat9), or optionally provide your own for any species
69
67
  - **LD coloring**: SNPs colored by linkage disequilibrium (R²) with lead variant
70
68
  - **Gene tracks**: Annotated gene/exon positions below the association plot
71
- - **Recombination rate**: Overlay showing recombination rate across region (*Canis lupus familiaris* only)
72
- - **SNP labels (matplotlib)**: Automatic labeling of lead SNPs with RS ID
73
- - **Tooltips (Bokeh and Plotly)**: Mouseover for detailed SNP data
69
+ - **Recombination rate**: Optional overlay across region (*Canis lupus familiaris* built-in, not shown in example image)
70
+ - **SNP labels (matplotlib)**: Automatic labeling of top SNPs by p-value (RS IDs)
71
+ - **Hover tooltips (Plotly and Bokeh)**: Detailed SNP data on hover
74
72
 
75
- ![Example regional association plot](examples/regional_plot.png)
73
+ ![Example regional association plot with LD coloring and gene track](examples/regional_plot.png)
74
+ *Regional association plot with LD coloring, gene/exon track, and top SNP labels (recombination overlay disabled in example).*
76
75
 
77
76
  2. **Stacked plots**: Compare multiple GWAS/phenotypes vertically
78
77
  3. **eQTL plot**: Expression QTL data aligned with association plots and gene tracks
79
78
  4. **Fine-mapping plots**: Visualize SuSiE credible sets with posterior inclusion probabilities
80
79
  5. **PheWAS plots**: Phenome-wide association study visualization across multiple phenotypes
81
80
  6. **Forest plots**: Meta-analysis effect size visualization with confidence intervals
82
- 7. **Multiple charting libraries**: matplotlib (static), plotly (interactive), bokeh (dashboards)
81
+ 7. **Multiple backends**: matplotlib (publication-ready), plotly (interactive), bokeh (dashboard integration)
83
82
  8. **Pandas and PySpark support**: Works with both Pandas and PySpark DataFrames for large-scale genomics data
84
83
  9. **Convenience data file loaders**: Load and validate common GWAS, eQTL and fine-mapping file formats
84
+ 10. **Automatic gene annotations**: Fetch gene/exon data from Ensembl REST API with caching (human, mouse, rat, canine, feline, and any Ensembl species)
85
85
 
86
86
  ## Installation
87
87
 
@@ -181,28 +181,46 @@ fig = plotter.plot(
181
181
  )
182
182
  ```
183
183
 
184
+ ## Automatic Gene Annotations
185
+
186
+ pyLocusZoom can automatically fetch gene annotations from Ensembl for any species:
187
+
188
+ ```python
189
+ # Enable automatic gene fetching
190
+ plotter = LocusZoomPlotter(species="human", auto_genes=True)
191
+
192
+ # No need to provide genes_df - fetched automatically
193
+ fig = plotter.plot(gwas_df, chrom=13, start=32000000, end=33000000)
194
+ ```
195
+
196
+ Supported species aliases: `human`, `mouse`, `rat`, `canine`/`dog`, `feline`/`cat`, or any Ensembl species name.
197
+ Data is cached locally for fast subsequent plots. Maximum region size is 5Mb (Ensembl API limit).
198
+
184
199
  ## Backends
185
200
 
186
- pyLocusZoom supports multiple rendering backends:
201
+ pyLocusZoom supports multiple rendering backends (set at initialization):
187
202
 
188
203
  ```python
189
204
  # Static publication-quality plot (default)
190
- fig = plotter.plot(gwas_df, chrom=1, start=1000000, end=2000000, backend="matplotlib")
205
+ plotter = LocusZoomPlotter(species="canine", backend="matplotlib")
206
+ fig = plotter.plot(gwas_df, chrom=1, start=1000000, end=2000000)
191
207
  fig.savefig("plot.png", dpi=150)
192
208
 
193
209
  # Interactive Plotly (hover tooltips, pan/zoom)
194
- fig = plotter.plot(gwas_df, chrom=1, start=1000000, end=2000000, backend="plotly")
210
+ plotter = LocusZoomPlotter(species="canine", backend="plotly")
211
+ fig = plotter.plot(gwas_df, chrom=1, start=1000000, end=2000000)
195
212
  fig.write_html("plot.html")
196
213
 
197
214
  # Interactive Bokeh (dashboard-ready)
198
- fig = plotter.plot(gwas_df, chrom=1, start=1000000, end=2000000, backend="bokeh")
215
+ plotter = LocusZoomPlotter(species="canine", backend="bokeh")
216
+ fig = plotter.plot(gwas_df, chrom=1, start=1000000, end=2000000)
199
217
  ```
200
218
 
201
219
  | Backend | Output | Best For | Features |
202
220
  |---------|--------|----------|----------|
203
- | `matplotlib` | Static PNG/PDF/SVG | Publications, presentations | Full feature set with SNP labels |
204
- | `plotly` | Interactive HTML | Web reports, data exploration | Hover tooltips, pan/zoom |
205
- | `bokeh` | Interactive HTML | Dashboards, web apps | Hover tooltips, pan/zoom |
221
+ | `matplotlib` | Static PNG/PDF/SVG | Publication-ready figures | Full feature set with SNP labels |
222
+ | `plotly` | Interactive HTML | Web reports, exploration | Hover tooltips, pan/zoom |
223
+ | `bokeh` | Interactive HTML | Dashboard integration | Hover tooltips, pan/zoom |
206
224
 
207
225
  > **Note:** All backends support scatter plots, gene tracks, recombination overlay, and LD legend. SNP labels (auto-positioned with adjustText) are matplotlib-only; interactive backends use hover tooltips instead.
208
226
 
@@ -221,7 +239,8 @@ fig = plotter.plot_stacked(
221
239
  )
222
240
  ```
223
241
 
224
- ![Example stacked plot](examples/stacked_plot.png)
242
+ ![Example stacked plot comparing two phenotypes](examples/stacked_plot.png)
243
+ *Stacked plot comparing two phenotypes with LD coloring and shared gene track.*
225
244
 
226
245
  ## eQTL Overlay
227
246
 
@@ -244,6 +263,7 @@ fig = plotter.plot_stacked(
244
263
  ```
245
264
 
246
265
  ![Example eQTL overlay plot](examples/eqtl_overlay.png)
266
+ *eQTL overlay with effect direction (up/down triangles) and magnitude binning.*
247
267
 
248
268
  ## Fine-mapping Visualization
249
269
 
@@ -266,6 +286,7 @@ fig = plotter.plot_stacked(
266
286
  ```
267
287
 
268
288
  ![Example fine-mapping plot](examples/finemapping_plot.png)
289
+ *Fine-mapping visualization with PIP line and credible set coloring (CS1/CS2).*
269
290
 
270
291
  ## PheWAS Plots
271
292
 
@@ -286,6 +307,7 @@ fig = plotter.plot_phewas(
286
307
  ```
287
308
 
288
309
  ![Example PheWAS plot](examples/phewas_plot.png)
310
+ *PheWAS plot showing associations across phenotype categories with significance threshold.*
289
311
 
290
312
  ## Forest Plots
291
313
 
@@ -308,19 +330,18 @@ fig = plotter.plot_forest(
308
330
  ```
309
331
 
310
332
  ![Example forest plot](examples/forest_plot.png)
333
+ *Forest plot with effect sizes, confidence intervals, and weight-proportional markers.*
311
334
 
312
335
  ## PySpark Support
313
336
 
314
- For large-scale genomics data, pass PySpark DataFrames directly:
337
+ For large-scale genomics data, convert PySpark DataFrames with `to_pandas()` before plotting:
315
338
 
316
339
  ```python
317
340
  from pylocuszoom import LocusZoomPlotter, to_pandas
318
341
 
319
- # PySpark DataFrame (automatically converted)
320
- fig = plotter.plot(spark_gwas_df, chrom=1, start=1000000, end=2000000)
321
-
322
- # Or convert manually with sampling for very large data
342
+ # Convert PySpark DataFrame (optionally sampled for very large data)
323
343
  pandas_df = to_pandas(spark_gwas_df, sample_size=100000)
344
+ fig = plotter.plot(pandas_df, chrom=1, start=1000000, end=2000000)
324
345
  ```
325
346
 
326
347
  Install PySpark support: `uv add pylocuszoom[spark]`
@@ -0,0 +1,29 @@
1
+ pylocuszoom/__init__.py,sha256=UtrNrjV0b0frxv3Zl4jw5D8aTMbNSE55j-PPkd8rz28,5585
2
+ pylocuszoom/colors.py,sha256=B28rfhWwGZ-e6Q-F43iXxC6NZpjUo0yWk4S_-vp9ZvU,7686
3
+ pylocuszoom/ensembl.py,sha256=q767o86FdcKn4V9aK48ESFwNI7ATlaX5tnwjZReYMEw,14436
4
+ pylocuszoom/eqtl.py,sha256=OrpWbFMR1wKMCmfQiC-2sqYx-99TT2i1cStIrPWIUOs,5948
5
+ pylocuszoom/finemapping.py,sha256=ZPcnc9E6N41Su8222wCqBkB3bhhyfASvj9u9Ot4td4o,5898
6
+ pylocuszoom/forest.py,sha256=302gULz9I0UiwqgcB18R756OOl1aa54OsPYHc6TnxGY,1092
7
+ pylocuszoom/gene_track.py,sha256=PkBwfqByVxhXlAPco9-d4P5X7cTg2rrOnw7BJVx48ow,17818
8
+ pylocuszoom/labels.py,sha256=Ams5WVZFNVT692BRiQ5wZcdbdNEAm5xtgRwmF5u0s_A,3492
9
+ pylocuszoom/ld.py,sha256=64xIulpDVvbMSryWUPoCQ99Odcjwf1wejpwVr_30MLU,6412
10
+ pylocuszoom/loaders.py,sha256=KpWPBO0BCb2yrGTtgdiOqOuhx2YLmjK_ywmpr3onnx8,25156
11
+ pylocuszoom/logging.py,sha256=nZHEkbnjp8zoyWj_S-Hy9UQvUYLoMoxyiOWRozBT2dg,4987
12
+ pylocuszoom/phewas.py,sha256=6g2LmwA5kmxYlHgPxJvuXIMerEqfqgsrth110Y3CgVU,968
13
+ pylocuszoom/plotter.py,sha256=gFywhaHPuXlbKPxWaWfw7Wrw8kqPMUPzKMgDcRB6wu8,50709
14
+ pylocuszoom/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
+ pylocuszoom/recombination.py,sha256=97GGBLDLTlQSRMp5sLOna3mCeRxeJiiWPHrw4dBRjQs,14546
16
+ pylocuszoom/schemas.py,sha256=vABBBlAR1vUP6BIewZ8E-TYpacccrcxavrdIDVCrQB4,11916
17
+ pylocuszoom/utils.py,sha256=_rI6ov0MbsWlZGJ7ni-V4387cirmJCX6IF2JAYhBx6A,6929
18
+ pylocuszoom/validation.py,sha256=UInqlhOWhWaCT_mrO7O7SfB1DNIYkjvEMudy4YjtUBg,5698
19
+ pylocuszoom/backends/__init__.py,sha256=xefVj3jVxmYwVLLY5AZtFqTPMehQxZ2qGd-Pk7_V_Bk,4267
20
+ pylocuszoom/backends/base.py,sha256=PBdm9t4f_qFDMkYR5z3edW4DvpuQSCAXuaxs2qjAeH0,21034
21
+ pylocuszoom/backends/bokeh_backend.py,sha256=11zRhXH2guUHiaYXyd7l2IDAv6uawdRAv6dyGPkHmJc,25512
22
+ pylocuszoom/backends/hover.py,sha256=Hjm_jcxJL8dDxO_Ye7jeWAUcHKlbH6oO8ZfGJ2MzIFM,6564
23
+ pylocuszoom/backends/matplotlib_backend.py,sha256=098ITnvNrBTaEztqez_7D0sZ_rKAYIxS6EDR5Yxt8is,20924
24
+ pylocuszoom/backends/plotly_backend.py,sha256=A6ZuHw0wVZaIIA6FgYJ4SH-Sz59tHOtnGUl-e-2VzZM,30574
25
+ pylocuszoom/reference_data/__init__.py,sha256=qqHqAUt1jebGlCN3CjqW3Z-_coHVNo5K3a3bb9o83hA,109
26
+ pylocuszoom-0.8.0.dist-info/METADATA,sha256=VqHRvFL1Wq5OJO3B727Rl0H8UfbBPaxVIJUOSA22s5A,17866
27
+ pylocuszoom-0.8.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
28
+ pylocuszoom-0.8.0.dist-info/licenses/LICENSE.md,sha256=U2y_hv8RcN5lECA3uK88irU3ODUE1TDAPictcmnP0Q4,698
29
+ pylocuszoom-0.8.0.dist-info/RECORD,,
@@ -1,26 +0,0 @@
1
- pylocuszoom/__init__.py,sha256=yb7NMehYOWYPPSBkO4EWwMY4NT-fj8oniNvI3h4oLL0,5219
2
- pylocuszoom/colors.py,sha256=B28rfhWwGZ-e6Q-F43iXxC6NZpjUo0yWk4S_-vp9ZvU,7686
3
- pylocuszoom/eqtl.py,sha256=9lZJ8jT1WEj3won6D9B54xdqUvbRvxpOitf97NCUR28,6167
4
- pylocuszoom/finemapping.py,sha256=PJ4HJYeCaHZecUmADCEGQxKd9HhhjrdIA1H5LQsUmLI,6332
5
- pylocuszoom/forest.py,sha256=WFX29gEcH-xS5G4kbb9J2WPcbRw7OdMegFuLqN4VfIE,1147
6
- pylocuszoom/gene_track.py,sha256=VWvPY0SrVFGJprTdttJ72r3JD-r3bdRDr0HDBai0oJw,18692
7
- pylocuszoom/labels.py,sha256=Ams5WVZFNVT692BRiQ5wZcdbdNEAm5xtgRwmF5u0s_A,3492
8
- pylocuszoom/ld.py,sha256=64xIulpDVvbMSryWUPoCQ99Odcjwf1wejpwVr_30MLU,6412
9
- pylocuszoom/loaders.py,sha256=28PqlUhbq1Y6Xzv9NFucWSAqRTqGj8h-pR7wOOmIHxI,25132
10
- pylocuszoom/logging.py,sha256=nZHEkbnjp8zoyWj_S-Hy9UQvUYLoMoxyiOWRozBT2dg,4987
11
- pylocuszoom/phewas.py,sha256=jrVDQvUu4rEH3YCE00LX-6STY96vMcK9xZ7AhiN9Jjo,984
12
- pylocuszoom/plotter.py,sha256=ywqlLMDalzWJaeIfkK_qYh9HRWbypJf9Pgd0II8jAss,53368
13
- pylocuszoom/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
- pylocuszoom/recombination.py,sha256=_KU9zwlhjk0MfyG4_i3rS0JPj5SIWcyTxglVlF-KMP8,13826
15
- pylocuszoom/schemas.py,sha256=vABBBlAR1vUP6BIewZ8E-TYpacccrcxavrdIDVCrQB4,11916
16
- pylocuszoom/utils.py,sha256=fKNX9WSTbfHR1EpPYijt6ycNjXEjwzunQMHXAvHaK3s,5211
17
- pylocuszoom/backends/__init__.py,sha256=7dlGvDoqMVK3fCtoMcI9zOP9qO0odQGPwfXhxnLfXfI,1196
18
- pylocuszoom/backends/base.py,sha256=ll6pKxVuzMNBDGowOYcPHpFkh4vIRoD_XomXQS8pPOk,11960
19
- pylocuszoom/backends/bokeh_backend.py,sha256=MQ-UJyGW46Rm6Cj6za9mPn9z8yUVnHibLrAfyNzYp-c,23851
20
- pylocuszoom/backends/matplotlib_backend.py,sha256=dK3n1KSGSTg4jgnwpa_5A5UvQhbN9hdyEtuDy-uUY1I,13178
21
- pylocuszoom/backends/plotly_backend.py,sha256=l6H4xabuxZPIGn4bqMb1BGRGylehklvxomun7nL8wIY,29174
22
- pylocuszoom/reference_data/__init__.py,sha256=qqHqAUt1jebGlCN3CjqW3Z-_coHVNo5K3a3bb9o83hA,109
23
- pylocuszoom-0.6.0.dist-info/METADATA,sha256=rSgBh890ygYNyPwv6oEBpsIU23BSevMw1Nyfhzv90Bs,16543
24
- pylocuszoom-0.6.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
25
- pylocuszoom-0.6.0.dist-info/licenses/LICENSE.md,sha256=U2y_hv8RcN5lECA3uK88irU3ODUE1TDAPictcmnP0Q4,698
26
- pylocuszoom-0.6.0.dist-info/RECORD,,