Spaces:
Sleeping
Sleeping
import io | |
from pathlib import Path | |
from typing import Callable, Optional, cast | |
from urllib.parse import parse_qsl | |
import altair as alt | |
import polars as pl | |
import reacton.core | |
import solara | |
import solara.lab | |
from cmap import Catalog, Colormap | |
from ipyvuetify.extra import FileInput | |
from make_link import encode_url | |
from viewer import AxisProperties, ColorTransform, ProteinView, RoutedView | |
DEFAULT_CMAP = "tol:rainbow_PuRd" | |
NORM_CATEGORIES = ["linear", "diverging", "categorical"] | |
VMIN_DEFAULT = 0.0 | |
VMAX_DEFAULT = 1.0 | |
HIGHLIGHT_COLOR = "#e933f8" | |
MISSING_DATA_COLOR = "#8c8c8c" | |
CMAP_OPTIONS = list(Catalog().namespaced_keys()) | |
# BASE_URL = "http://localhost:8765" # local testing | |
BASE_URL = "https://huggingface.co/spaces/Jhsmit/ipymolstar-annotate-colors" | |
pth = Path(__file__).parent | |
def FileInputComponent( | |
on_file: Callable[[solara.components.file_drop.FileInfo | None], None], | |
): | |
"""Adaptation of _FileDrop.""" | |
file_info, set_file_info = solara.use_state(None) | |
wired_files, set_wired_files = solara.use_state( | |
cast(Optional[list[solara.components.file_drop.FileInfo]], None) | |
) | |
file_drop = FileInput.element(on_file_info=set_file_info, multiple=False) # type: ignore | |
def wire_files(): | |
if not file_info: | |
set_wired_files([]) | |
return | |
real = cast(FileInput, solara.get_widget(file_drop)) | |
# workaround for @observe being cleared | |
real.version += 1 | |
real.reset_stats() | |
set_wired_files( | |
cast(list[solara.components.file_drop.FileInfo], real.get_files()) | |
) | |
solara.use_effect(wire_files, [file_info]) | |
def handle_file(): | |
if not wired_files: | |
on_file(None) | |
return | |
if on_file: | |
f = wired_files[0].copy() | |
f["data"] = None | |
on_file(f) | |
solara.lab.use_task(handle_file, dependencies=[wired_files]) | |
return file_drop | |
def ColorPickerMenuButton(title: str, color: solara.Reactive[str]): | |
local_color = solara.use_reactive(color.value) | |
def on_open(value: bool): | |
if not value: | |
color.set(local_color.value) | |
btn = solara.Button(title, color=local_color.value) | |
with solara.lab.Menu( | |
activator=btn, close_on_content_click=False, on_open_value=on_open | |
): | |
solara.v.ColorPicker( | |
v_model=local_color.value, | |
on_v_model=local_color.set, | |
) | |
empty_frame = pl.DataFrame() | |
R_DEFAULT = "" | |
V_DEFAULT = "" | |
# %% | |
def MainApp(): | |
dark_effective = solara.lab.use_dark_effective() | |
title = solara.use_reactive("My annotated protein view") | |
description = solara.use_reactive("") | |
molecule_id = solara.use_reactive("1QYN") | |
data = solara.use_reactive(empty_frame) | |
warning_text = solara.use_reactive("") | |
residue_column = solara.use_reactive(R_DEFAULT) | |
color_column = solara.use_reactive(V_DEFAULT) | |
label = solara.use_reactive("value") | |
unit = solara.use_reactive("au") | |
highlight_color = solara.use_reactive(HIGHLIGHT_COLOR) | |
missing_data_color = solara.use_reactive(MISSING_DATA_COLOR) | |
autoscale_y = solara.use_reactive(True) | |
cmap_name = solara.use_reactive(DEFAULT_CMAP) | |
reverse = solara.use_reactive(False) | |
full_cmap_name = cmap_name.value + "_r" if reverse.value else cmap_name.value | |
cmap = Colormap(full_cmap_name) | |
vmin = solara.use_reactive(VMIN_DEFAULT) | |
vmax = solara.use_reactive(VMAX_DEFAULT) | |
norm_type = solara.use_reactive(NORM_CATEGORIES[0]) | |
rc = reacton.core.get_render_context() | |
def on_file(file_info: solara.components.file_drop.FileInfo | None): | |
if not file_info: | |
data.set(pl.DataFrame()) | |
return | |
try: | |
df = pl.read_csv(file_info["file_obj"]) | |
except Exception as e: | |
warning_text.set(str(e)) | |
return | |
if len(df.columns) < 2: | |
warning_text.set(f"Expected at least 2 columns, got {len(df.columns)}") | |
data.set(pl.DataFrame()) | |
return | |
warning_text.set("") | |
# order matters! | |
# with solara.batch_update(): | |
# https://github.com/widgetti/solara/issues/637 | |
with rc: | |
residue_column.set(df.columns[0]) | |
color_column.set(df.columns[1]) | |
data.set(df) | |
colors = ColorTransform( | |
name=full_cmap_name, | |
norm_type=norm_type.value, | |
vmin=vmin.value, | |
vmax=vmax.value, | |
missing_data_color=missing_data_color.value, | |
highlight_color=highlight_color.value, | |
) | |
axis_properties = AxisProperties( | |
label=label.value, | |
unit=unit.value, | |
autoscale_y=autoscale_y.value, | |
) | |
if data.value.is_empty(): | |
data_view = pl.DataFrame({"residue_number": [], "value": []}) | |
else: | |
data_view = pl.DataFrame( | |
{ | |
"residue_number": data.value[residue_column.value], | |
"value": data.value[color_column.value], | |
} | |
) | |
def load_example_data(): | |
bio = io.BytesIO(Path("example_data.csv").read_bytes()) | |
bio.seek(0) | |
file_info = solara.components.file_drop.FileInfo( | |
name="example_data.csv", | |
size=Path("example_data.csv").stat().st_size, | |
file_obj=bio, | |
data=None, | |
) | |
# with solara.batch_update(): | |
# https://github.com/widgetti/solara/issues/637 | |
with rc: | |
molecule_id.set("6GOX") | |
title.set("SecA HDX-MS protein local flexibility") | |
on_file(file_info) | |
vmin.set(4e4) | |
vmax.set(1e4) | |
autoscale_y.set(False) | |
description.set(Path("example_data_description.md").read_text()) | |
with solara.AppBar(): | |
with solara.Tooltip("Load example data and settings"): | |
solara.Button( | |
icon_name="mdi-test-tube", | |
icon=True, | |
on_click=load_example_data, | |
) | |
query_string = encode_url( | |
title=title.value, | |
molecule_id=molecule_id.value, | |
colors=colors, | |
axis_properties=axis_properties, | |
data=data_view, | |
description=description.value, | |
) | |
ProteinView( | |
title.value, | |
molecule_id=molecule_id.value, | |
data=data_view, | |
colors=colors, | |
axis_properties=axis_properties, | |
dark_effective=dark_effective, | |
description=description.value, | |
) | |
with solara.Sidebar(): | |
with solara.Card("Settings"): | |
solara.InputText(label="Title", value=title) | |
solara.InputText(label="PDB ID", value=molecule_id) | |
solara.Text("Choose .csv data file:") | |
FileInputComponent(on_file) | |
if warning_text.value: | |
solara.Warning(warning_text.value) | |
if not data.value.is_empty(): | |
with solara.Row(): | |
solara.Select( | |
label="Residue Column", | |
value=residue_column, | |
values=list(data.value.columns), | |
) | |
solara.Select( | |
label="Color Column", | |
value=color_column, | |
values=list(data.value.columns), | |
) | |
with solara.Row(): | |
solara.InputText(label="Label", value=label) | |
solara.InputText(label="Unit", value=unit) | |
solara.Text("Colors") | |
with solara.Row(gap="10px", justify="space-around"): | |
ColorPickerMenuButton("Highlight", highlight_color) | |
ColorPickerMenuButton("Missing data", missing_data_color) | |
def set_cmap_name(name: str): | |
try: | |
Colormap(name) | |
cmap_name.set(name) | |
except TypeError: | |
pass | |
# with solara.Row(): | |
solara.v.Autocomplete( | |
v_model=cmap_name.value, | |
on_v_model=set_cmap_name, | |
items=CMAP_OPTIONS, | |
) | |
solara.Select( | |
label="Normalization type", value=norm_type, values=NORM_CATEGORIES | |
) | |
with solara.Row(): | |
def set_vmin(value: float): | |
if norm_type.value == "diverging": | |
vmin.set(value) | |
vmax.set(-value) | |
else: | |
vmin.set(value) | |
solara.InputFloat( | |
label="vmin", | |
value=vmin.value, | |
on_value=set_vmin, | |
disabled=norm_type.value == "categorical", | |
) | |
solara.InputFloat( | |
label="vmax", | |
value=vmax, | |
disabled=norm_type.value in ["diverging", "categorical"], | |
) | |
with solara.GridFixed(columns=2): | |
with solara.Tooltip("Reverses the color map"): | |
solara.Checkbox(label="Reverse", value=reverse) | |
with solara.Tooltip( | |
"Uncheck to use color range as the scatterplot y scale" | |
): | |
solara.Checkbox(label="Autoscale Y", value=autoscale_y) | |
solara.Image(cmap._repr_png_(height=24), width="100%") | |
solara.InputTextArea( | |
label="Description", value=description, continuous_update=True | |
) | |
solara.Div(style={"height": "10px"}) | |
solara.Button( | |
label="Open view in new tab", | |
attributes={"href": BASE_URL + "?" + query_string, "target": "_blank"}, | |
block=True, | |
) | |
def Page(): | |
route = solara.use_router() | |
solara.Style(Path("style.css")) | |
dark_effective = solara.lab.use_dark_effective() | |
dark_previous = solara.use_previous(dark_effective) | |
if dark_previous != dark_effective: | |
if dark_effective: | |
alt.themes.enable("dark") | |
else: | |
alt.themes.enable("default") | |
# todo: aways true, check valid | |
if route.search: | |
query_dict = {k: v for k, v in parse_qsl(route.search)} | |
# needs more keys but if this is there then at least there has been an attempt | |
if "molecule_id" in query_dict: | |
RoutedView() | |
else: | |
MainApp() | |
# %% | |