pylocuszoom 0.5.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.
- pylocuszoom/__init__.py +38 -2
- pylocuszoom/backends/__init__.py +116 -17
- pylocuszoom/backends/base.py +424 -35
- pylocuszoom/backends/bokeh_backend.py +192 -34
- pylocuszoom/backends/hover.py +198 -0
- pylocuszoom/backends/matplotlib_backend.py +332 -3
- pylocuszoom/backends/plotly_backend.py +187 -38
- pylocuszoom/colors.py +41 -0
- pylocuszoom/ensembl.py +476 -0
- pylocuszoom/eqtl.py +15 -19
- pylocuszoom/finemapping.py +17 -26
- pylocuszoom/forest.py +35 -0
- pylocuszoom/gene_track.py +161 -135
- pylocuszoom/loaders.py +38 -18
- pylocuszoom/phewas.py +34 -0
- pylocuszoom/plotter.py +370 -190
- pylocuszoom/recombination.py +64 -34
- pylocuszoom/schemas.py +37 -26
- pylocuszoom/utils.py +52 -0
- pylocuszoom/validation.py +172 -0
- {pylocuszoom-0.5.0.dist-info → pylocuszoom-0.8.0.dist-info}/METADATA +97 -28
- pylocuszoom-0.8.0.dist-info/RECORD +29 -0
- pylocuszoom-0.5.0.dist-info/RECORD +0 -24
- {pylocuszoom-0.5.0.dist-info → pylocuszoom-0.8.0.dist-info}/WHEEL +0 -0
- {pylocuszoom-0.5.0.dist-info → pylocuszoom-0.8.0.dist-info}/licenses/LICENSE.md +0 -0
pylocuszoom/__init__.py
CHANGED
|
@@ -34,14 +34,31 @@ Species Support:
|
|
|
34
34
|
- Custom: User provides all reference data
|
|
35
35
|
"""
|
|
36
36
|
|
|
37
|
-
__version__ = "0.
|
|
37
|
+
__version__ = "0.6.0"
|
|
38
38
|
|
|
39
39
|
# Main plotter class
|
|
40
40
|
# Backend types
|
|
41
41
|
from .backends import BackendType, get_backend
|
|
42
42
|
|
|
43
43
|
# Colors and LD
|
|
44
|
-
from .colors import
|
|
44
|
+
from .colors import (
|
|
45
|
+
LEAD_SNP_COLOR,
|
|
46
|
+
PHEWAS_CATEGORY_COLORS,
|
|
47
|
+
get_ld_bin,
|
|
48
|
+
get_ld_color,
|
|
49
|
+
get_ld_color_palette,
|
|
50
|
+
get_phewas_category_color,
|
|
51
|
+
get_phewas_category_palette,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# Ensembl integration
|
|
55
|
+
from .ensembl import (
|
|
56
|
+
clear_ensembl_cache,
|
|
57
|
+
fetch_exons_from_ensembl,
|
|
58
|
+
fetch_genes_from_ensembl,
|
|
59
|
+
get_ensembl_species_name,
|
|
60
|
+
get_genes_for_region,
|
|
61
|
+
)
|
|
45
62
|
|
|
46
63
|
# eQTL support
|
|
47
64
|
from .eqtl import (
|
|
@@ -65,6 +82,9 @@ from .finemapping import (
|
|
|
65
82
|
validate_finemapping_df,
|
|
66
83
|
)
|
|
67
84
|
|
|
85
|
+
# Forest plot support
|
|
86
|
+
from .forest import validate_forest_df
|
|
87
|
+
|
|
68
88
|
# Gene track
|
|
69
89
|
from .gene_track import get_nearest_gene, plot_gene_track
|
|
70
90
|
|
|
@@ -101,6 +121,9 @@ from .loaders import (
|
|
|
101
121
|
|
|
102
122
|
# Logging configuration
|
|
103
123
|
from .logging import disable_logging, enable_logging
|
|
124
|
+
|
|
125
|
+
# PheWAS support
|
|
126
|
+
from .phewas import validate_phewas_df
|
|
104
127
|
from .plotter import LocusZoomPlotter
|
|
105
128
|
|
|
106
129
|
# Reference data management
|
|
@@ -130,7 +153,10 @@ __all__ = [
|
|
|
130
153
|
"get_ld_color",
|
|
131
154
|
"get_ld_bin",
|
|
132
155
|
"get_ld_color_palette",
|
|
156
|
+
"get_phewas_category_color",
|
|
157
|
+
"get_phewas_category_palette",
|
|
133
158
|
"LEAD_SNP_COLOR",
|
|
159
|
+
"PHEWAS_CATEGORY_COLORS",
|
|
134
160
|
# Gene track
|
|
135
161
|
"get_nearest_gene",
|
|
136
162
|
"plot_gene_track",
|
|
@@ -164,6 +190,10 @@ __all__ = [
|
|
|
164
190
|
# Validation & Utils
|
|
165
191
|
"ValidationError",
|
|
166
192
|
"to_pandas",
|
|
193
|
+
# PheWAS
|
|
194
|
+
"validate_phewas_df",
|
|
195
|
+
# Forest plot
|
|
196
|
+
"validate_forest_df",
|
|
167
197
|
# GWAS loaders
|
|
168
198
|
"load_gwas",
|
|
169
199
|
"load_plink_assoc",
|
|
@@ -187,4 +217,10 @@ __all__ = [
|
|
|
187
217
|
"load_ensembl_genes",
|
|
188
218
|
# Schema validation
|
|
189
219
|
"LoaderValidationError",
|
|
220
|
+
# Ensembl integration
|
|
221
|
+
"get_genes_for_region",
|
|
222
|
+
"fetch_genes_from_ensembl",
|
|
223
|
+
"fetch_exons_from_ensembl",
|
|
224
|
+
"get_ensembl_species_name",
|
|
225
|
+
"clear_ensembl_cache",
|
|
190
226
|
]
|
pylocuszoom/backends/__init__.py
CHANGED
|
@@ -1,18 +1,77 @@
|
|
|
1
1
|
"""Pluggable plotting backends for pyLocusZoom.
|
|
2
2
|
|
|
3
3
|
Supports matplotlib (default), plotly, and bokeh backends.
|
|
4
|
+
|
|
5
|
+
Backend Registration:
|
|
6
|
+
Backends are registered using the @register_backend decorator.
|
|
7
|
+
This enables extensibility without modifying core code.
|
|
8
|
+
|
|
9
|
+
@register_backend("custom")
|
|
10
|
+
class CustomBackend:
|
|
11
|
+
...
|
|
12
|
+
|
|
13
|
+
Fallback Behavior:
|
|
14
|
+
When an optional backend (plotly, bokeh) is requested but not installed,
|
|
15
|
+
get_backend() falls back to matplotlib with a warning. This ensures
|
|
16
|
+
code works even without optional dependencies.
|
|
4
17
|
"""
|
|
5
18
|
|
|
19
|
+
import warnings
|
|
6
20
|
from typing import Literal
|
|
7
21
|
|
|
8
22
|
from .base import PlotBackend
|
|
9
|
-
from .matplotlib_backend import MatplotlibBackend
|
|
10
23
|
|
|
11
24
|
BackendType = Literal["matplotlib", "plotly", "bokeh"]
|
|
12
25
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
26
|
+
# LaTeX to Unicode conversion table for interactive backends
|
|
27
|
+
_LATEX_TO_UNICODE = [
|
|
28
|
+
(r"$-\log_{10}$ P", "-log10(P)"),
|
|
29
|
+
(r"$-\log_{10}$", "-log10"),
|
|
30
|
+
(r"\log_{10}", "log10"),
|
|
31
|
+
(r"$r^2$", "r"),
|
|
32
|
+
(r"$R^2$", "R"),
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def convert_latex_to_unicode(label: str) -> str:
|
|
37
|
+
"""Convert LaTeX-style labels to Unicode for display in interactive backends.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
label: Label text possibly containing LaTeX notation.
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
Label with LaTeX converted to Unicode characters.
|
|
44
|
+
"""
|
|
45
|
+
for latex, unicode_str in _LATEX_TO_UNICODE:
|
|
46
|
+
if latex in label:
|
|
47
|
+
label = label.replace(latex, unicode_str)
|
|
48
|
+
return label.replace("$", "")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# Backend registry - populated by @register_backend decorator
|
|
52
|
+
_BACKENDS: dict[str, type[PlotBackend]] = {}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def register_backend(name: str):
|
|
56
|
+
"""Decorator to register a backend class.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
name: Backend name (e.g., "matplotlib", "plotly", "bokeh").
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
Decorator function that registers the class.
|
|
63
|
+
|
|
64
|
+
Example:
|
|
65
|
+
@register_backend("custom")
|
|
66
|
+
class CustomBackend:
|
|
67
|
+
...
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
def decorator(cls: type[PlotBackend]) -> type[PlotBackend]:
|
|
71
|
+
_BACKENDS[name] = cls
|
|
72
|
+
return cls
|
|
73
|
+
|
|
74
|
+
return decorator
|
|
16
75
|
|
|
17
76
|
|
|
18
77
|
def get_backend(name: BackendType) -> PlotBackend:
|
|
@@ -25,24 +84,64 @@ def get_backend(name: BackendType) -> PlotBackend:
|
|
|
25
84
|
Instantiated backend.
|
|
26
85
|
|
|
27
86
|
Raises:
|
|
28
|
-
ValueError: If backend name is
|
|
29
|
-
ImportError: If backend dependencies are not installed.
|
|
30
|
-
"""
|
|
31
|
-
if name == "plotly":
|
|
32
|
-
from .plotly_backend import PlotlyBackend
|
|
87
|
+
ValueError: If backend name is completely unknown.
|
|
33
88
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
89
|
+
Note:
|
|
90
|
+
When optional backends (plotly, bokeh) are unavailable,
|
|
91
|
+
falls back to matplotlib with a UserWarning.
|
|
92
|
+
"""
|
|
93
|
+
# Ensure matplotlib is always registered (it's always available)
|
|
94
|
+
if "matplotlib" not in _BACKENDS:
|
|
95
|
+
from .matplotlib_backend import MatplotlibBackend # noqa: F401
|
|
37
96
|
|
|
38
|
-
|
|
97
|
+
# Try lazy import for optional backends
|
|
98
|
+
if name not in _BACKENDS:
|
|
99
|
+
if name == "plotly":
|
|
100
|
+
try:
|
|
101
|
+
from .plotly_backend import PlotlyBackend # noqa: F401
|
|
102
|
+
except ImportError:
|
|
103
|
+
warnings.warn(
|
|
104
|
+
"Plotly not installed, falling back to matplotlib. "
|
|
105
|
+
"Install plotly with: pip install plotly",
|
|
106
|
+
UserWarning,
|
|
107
|
+
stacklevel=2,
|
|
108
|
+
)
|
|
109
|
+
name = "matplotlib"
|
|
110
|
+
elif name == "bokeh":
|
|
111
|
+
try:
|
|
112
|
+
from .bokeh_backend import BokehBackend # noqa: F401
|
|
113
|
+
except ImportError:
|
|
114
|
+
warnings.warn(
|
|
115
|
+
"Bokeh not installed, falling back to matplotlib. "
|
|
116
|
+
"Install bokeh with: pip install bokeh",
|
|
117
|
+
UserWarning,
|
|
118
|
+
stacklevel=2,
|
|
119
|
+
)
|
|
120
|
+
name = "matplotlib"
|
|
39
121
|
|
|
40
122
|
if name not in _BACKENDS:
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
)
|
|
123
|
+
available = list(_BACKENDS.keys())
|
|
124
|
+
raise ValueError(f"Unknown backend: {name}. Available: {available}")
|
|
44
125
|
|
|
45
126
|
return _BACKENDS[name]()
|
|
46
127
|
|
|
47
128
|
|
|
48
|
-
|
|
129
|
+
# Lazy import for backward compatibility - MatplotlibBackend available at module level
|
|
130
|
+
# The actual registration happens when matplotlib_backend is imported
|
|
131
|
+
def __getattr__(name: str):
|
|
132
|
+
"""Lazy attribute access for backward compatibility."""
|
|
133
|
+
if name == "MatplotlibBackend":
|
|
134
|
+
from .matplotlib_backend import MatplotlibBackend
|
|
135
|
+
|
|
136
|
+
return MatplotlibBackend
|
|
137
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
__all__ = [
|
|
141
|
+
"PlotBackend",
|
|
142
|
+
"BackendType",
|
|
143
|
+
"get_backend",
|
|
144
|
+
"register_backend",
|
|
145
|
+
"MatplotlibBackend",
|
|
146
|
+
"convert_latex_to_unicode",
|
|
147
|
+
]
|