"""Helper functions for wiring structure visualization in the Streamlit app.
Includes :func:`create_structure_visualizer`, UniProt-index validation, and
color-to-hex conversion helpers used by the app.
"""
import logging
from typing import TYPE_CHECKING
import webcolors
from mkt.databases.app.sequences import SequenceAlignment
from mkt.databases.app.structures import StructureVisualizer
if TYPE_CHECKING:
from mkt.databases.app.schema import StructureConfig
logger = logging.getLogger(__name__)
[docs]
def create_structure_visualizer(
seq_align: SequenceAlignment,
config_class: type["StructureConfig"],
config_kwargs: dict | None = None,
) -> StructureVisualizer:
"""Create a StructureVisualizer from a SequenceAlignment and config class.
This is the recommended way to create a StructureVisualizer with the new
architecture. The flow is:
1. Create SequenceAlignment
2. Pass it to this function with a config class
3. Get back a StructureVisualizer ready for visualization
Parameters
----------
seq_align : SequenceAlignment
SequenceAlignment object with aligned sequences.
config_class : Type[StructureConfig]
The config class to instantiate (e.g., PhosphositesConfig, KLIFSConservedConfig).
config_kwargs : dict | None, optional
Additional keyword arguments to pass to the config class, by default None.
Returns
-------
StructureVisualizer
StructureVisualizer object ready for visualization.
Examples
--------
>>> from mkt.databases.app.sequences import SequenceAlignment
>>> from mkt.databases.app.schema import PhosphositesConfig
>>>
>>> # Create sequence alignment
>>> seq_align = SequenceAlignment(str_kinase="EGFR", dict_color={"A": "blue", ...})
>>>
>>> # Create structure visualizer
>>> viz = create_structure_visualizer(seq_align, PhosphositesConfig)
>>>
>>> # Get highlight data
>>> list_idx, dict_color, dict_style = viz.get_highlight_data()
"""
config_kwargs = config_kwargs or {}
config = config_class(seq_align=seq_align, **config_kwargs)
viz = StructureVisualizer(config)
return viz
[docs]
def validate_uniprot_indices(
seq_align: SequenceAlignment,
list_uniprot_idx: list[int],
) -> None:
"""Validate that 1-indexed UniProt positions fall within the kinase sequence.
Parameters
----------
seq_align : SequenceAlignment
SequenceAlignment object providing the kinase and its canonical sequence.
list_uniprot_idx : list[int]
List of 1-indexed full-length UniProt positions to validate.
Returns
-------
None
Raises
------
ValueError
If any position falls outside the valid range [1, len(canonical_seq)].
"""
seq_len = len(seq_align.obj_kinase.uniprot.canonical_seq)
list_out_of_range = sorted({i for i in list_uniprot_idx if i < 1 or i > seq_len})
if list_out_of_range:
raise ValueError(
f"UniProt position(s) {list_out_of_range} out of range for "
f"{seq_align.obj_kinase.hgnc_name} (valid range: 1-{seq_len})."
)
[docs]
def convert_color_to_hex(color: str) -> str:
"""Convert named color to hex.
Parameters
----------
color : str
Color name or hex string.
Returns
-------
str
Hex color string.
"""
if color.startswith("#"):
return color
try:
return webcolors.name_to_hex(color)
except ValueError:
logger.warning(f"Color '{color}' not recognized, defaulting to gray")
return "#808080"