Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ versioning after the initial alpha releases.
- Site-aware aliases for selected Unimod accessions.
- Layerable JSON custom-modification registries, a JSON Schema, Python registration APIs, and
explicit override protection.
- Optional bilingual local GUI for creating, validating, previewing, importing, and downloading
custom modification registries.
- English project homepage with linked Simplified Chinese documentation.
- Structural formula tests, registry validation tests, custom CLI examples, and modification-system
documentation.
Expand Down
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ command-line interface.
- Linear L/D peptide construction with explicit termini, backbone N-methylation, curated PTMs, and
site-aware Unimod aliases
- Layerable JSON registries and a Python API for custom side-chain and terminal structures
- Optional bilingual browser GUI for building, validating, previewing, importing, and exporting
custom modification registries
- Morgan, RDKit path, MACCS, Atom Pair, Topological Torsion, and sequence k-mer fingerprints
- Formula, exact mass, MolLogP, TPSA, HBD/HBA, rotatable-bond, heavy-atom, and charge descriptors
- Optional PubChem PUG-REST name resolution, kept separate from deterministic local conversion
Expand All @@ -51,6 +53,16 @@ pytest
ruff check .
```

Install the optional graphical modification builder:

```bash
python -m pip install -e '.[gui]'
moltranscode gui
```

The interface opens locally at `http://localhost:8501`. It does not upload structures to a remote
service. Use `--no-browser`, `--host`, or `--port` when a different launch configuration is needed.

## Quick start

```bash
Expand Down Expand Up @@ -171,6 +183,18 @@ RDKit; registry provenance should be archived with generated data.
See [Modification system](docs/modifications.md) for the schema, attachment semantics, safety rules,
and Python registration API.

### Graphical modification builder

Run `moltranscode gui` to open the optional English/Chinese interface. It provides:

- forms for side-chain, N-terminal, and C-terminal definitions;
- immediate fragment, collision, and schema validation;
- an interactive peptide test with formula, exact mass, charge, canonical SMILES, and a 2D drawing;
- JSON import, removal of custom entries, explicit override control, and registry download.

The interface exports the same schema accepted by `--mod-config`; no separate GUI-only project
format is introduced.

## Batch conversion

```bash
Expand Down
22 changes: 22 additions & 0 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
以及依赖单体元数据的 FASTA/sequence/HELM
- 肽结构:混合 L/D 氨基酸、端基、骨架 N-甲基化、常见 PTM、按残基位点解析的 Unimod 别名
- 自定义修饰:可叠加的 JSON 配置和 Python 注册 API,无需修改源代码
- 可选中英文浏览器图形界面,用于创建、验证、预览、导入和导出自定义修饰
- 指纹:Morgan、RDKit path、MACCS、Atom Pair、Topological Torsion、序列 k-mer
- 描述符:分子式、精确质量、MolLogP、TPSA、HBD/HBA、可旋转键、重原子数、电荷
- 可选 PubChem PUG-REST 名称解析;批处理中逐条保留 `status` 和 `error`
Expand All @@ -45,6 +46,16 @@ pytest
ruff check .
```

安装可选图形界面:

```bash
python -m pip install -e '.[gui]'
moltranscode gui
```

界面默认在本机 `http://localhost:8501` 打开,不会把结构上传到远程服务。也可使用
`--no-browser`、`--host` 和 `--port` 调整启动方式。

## 快速开始

```bash
Expand Down Expand Up @@ -153,6 +164,17 @@ sanitization,但用户仍需对自定义片段的化学含义负责,并随

完整字段、连接规则和 Python API 见[修饰系统说明](docs/modifications.md)。

### 图形化修饰构建器

运行 `moltranscode gui` 可以打开中英文界面,支持:

- 手动填写侧链、N 端和 C 端修饰;
- 即时检查 SMILES 片段、名称冲突和 JSON 字段;
- 输入测试肽并查看分子式、精确质量、电荷、canonical SMILES 与二维结构图;
- 导入现有 JSON、删除自定义条目、控制显式覆盖并下载最终修饰库。

图形界面导出的文件与 `--mod-config` 使用相同格式,不会产生只能由 GUI 读取的项目文件。

## 批处理与 Python API

```bash
Expand Down
24 changes: 24 additions & 0 deletions docs/modifications.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,30 @@ result = convert("AK[AzK]", "peptide", "smiles", modification_registry=registry)
Registry instances are mutable by design. Create one during application initialization and treat it
as read-only while conversions run; call `clone()` before making request-specific changes.

## Graphical registry builder

Install and launch the optional local interface:

```bash
python -m pip install -e '.[gui]'
moltranscode gui
```

The Streamlit application supports English and Simplified Chinese. It validates each definition
against the same runtime loader used by the CLI, previews a test peptide as descriptors and a 2D
structure, imports existing registries, and downloads schema-versioned JSON. All processing occurs
in the local Python process; the application does not send molecular structures to MolTranscode or
another hosted service.

For a headless or remote development environment:

```bash
moltranscode gui --no-browser --host 127.0.0.1 --port 8765
```

Binding to a non-loopback interface may expose the application to other machines. Add appropriate
network access controls before doing so, especially when working with confidential structures.

## Provenance and contribution rules

Archive custom JSON files alongside generated outputs and record the MolTranscode and RDKit
Expand Down
4 changes: 4 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,13 @@ dev = [
"pytest-cov>=5.0",
"ruff>=0.5",
]
gui = [
"streamlit>=1.60",
]

[project.scripts]
moltranscode = "moltranscode.cli:main"
moltranscode-gui = "moltranscode.gui:main"

[project.urls]
Homepage = "https://github.com/PerinMu/MolTranscode"
Expand Down
2 changes: 2 additions & 0 deletions src/moltranscode/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
ModificationRegistry,
default_modification_registry,
load_modification_registry,
modification_registry_from_dict,
)
from .peptide import parse_peptide, peptide_to_smiles

Expand All @@ -27,6 +28,7 @@
"describe",
"fingerprint",
"load_modification_registry",
"modification_registry_from_dict",
"parse_peptide",
"peptide_to_smiles",
]
Expand Down
20 changes: 20 additions & 0 deletions src/moltranscode/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,16 @@ def _base_parser() -> argparse.ArgumentParser:
formats_parser.add_argument("--json", action="store_true", help="emit machine-readable JSON")
_add_modification_config_argument(formats_parser)
formats_parser.set_defaults(handler=_handle_formats)

gui_parser = subparsers.add_parser(
"gui", help="launch the optional graphical modification builder"
)
gui_parser.add_argument("--host", default="localhost", help="interface to bind (localhost)")
gui_parser.add_argument("--port", type=int, default=8501, help="local web interface port")
gui_parser.add_argument(
"--no-browser", action="store_true", help="do not open a browser automatically"
)
gui_parser.set_defaults(handler=_handle_gui)
return parser


Expand Down Expand Up @@ -251,6 +261,16 @@ def _handle_formats(args: argparse.Namespace) -> int:
return 0


def _handle_gui(args: argparse.Namespace) -> int:
from .gui import launch_gui

return launch_gui(
host=args.host,
port=args.port,
open_browser=not args.no_browser,
)


def main(argv: list[str] | None = None) -> int:
parser = _base_parser()
args = parser.parse_args(argv)
Expand Down
138 changes: 138 additions & 0 deletions src/moltranscode/gui.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
"""Launcher and testable helpers for the optional Streamlit interface."""

from __future__ import annotations

import copy
import importlib.util
import json
import subprocess
import sys
from pathlib import Path
from typing import Any

from .exceptions import InputFormatError
from .modifications import modification_registry_from_dict

REGISTRY_SECTIONS = (
"side_chain_modifications",
"n_terminal_modifications",
"c_terminal_modifications",
)


def new_registry_payload() -> dict[str, Any]:
"""Return an empty schema-versioned custom modification registry."""

return {
"schema_version": 1,
"override": False,
"side_chain_modifications": [],
"n_terminal_modifications": [],
"c_terminal_modifications": [],
}


def normalize_registry_payload(payload: dict[str, Any]) -> dict[str, Any]:
"""Fill optional sections and validate a GUI/import payload."""

normalized = new_registry_payload()
normalized.update(copy.deepcopy(payload))
for section in REGISTRY_SECTIONS:
normalized.setdefault(section, [])
modification_registry_from_dict(normalized, source="GUI registry")
return normalized


def append_registry_entry(
payload: dict[str, Any],
section: str,
entry: dict[str, Any],
) -> dict[str, Any]:
"""Return a validated payload containing one additional definition."""

if section not in REGISTRY_SECTIONS:
raise InputFormatError(f"Unknown modification section: {section!r}.")
updated = normalize_registry_payload(payload)
updated[section].append(copy.deepcopy(entry))
modification_registry_from_dict(updated, source="GUI registry")
return updated


def remove_registry_entry(
payload: dict[str, Any],
section: str,
index: int,
) -> dict[str, Any]:
"""Return a validated payload with one custom definition removed."""

if section not in REGISTRY_SECTIONS:
raise InputFormatError(f"Unknown modification section: {section!r}.")
updated = normalize_registry_payload(payload)
try:
del updated[section][index]
except IndexError as exc:
raise InputFormatError(f"No modification at index {index} in {section}.") from exc
modification_registry_from_dict(updated, source="GUI registry")
return updated


def aliases_from_text(value: str) -> list[str]:
"""Parse comma-separated aliases while preserving order."""

aliases: list[str] = []
for item in value.split(","):
alias = item.strip()
if alias and alias not in aliases:
aliases.append(alias)
return aliases


def registry_payload_json(payload: dict[str, Any]) -> str:
"""Serialize a validated registry for download."""

normalized = normalize_registry_payload(payload)
return json.dumps(normalized, ensure_ascii=False, indent=2) + "\n"


def launch_gui(
*,
host: str = "localhost",
port: int = 8501,
open_browser: bool = True,
) -> int:
"""Launch the optional local Streamlit application."""

if not 1 <= port <= 65535:
raise InputFormatError("GUI port must be between 1 and 65535.")
if importlib.util.find_spec("streamlit") is None:
raise InputFormatError(
"The GUI dependency is not installed. Run: python -m pip install -e '.[gui]'"
)
app_path = Path(__file__).with_name("gui_app.py")
command = [
sys.executable,
"-m",
"streamlit",
"run",
str(app_path),
"--server.address",
host,
"--server.port",
str(port),
"--server.headless",
"false" if open_browser else "true",
"--browser.gatherUsageStats",
"false",
]
try:
return subprocess.run(command, check=False).returncode
except KeyboardInterrupt:
return 130


def main() -> int:
try:
return launch_gui()
except InputFormatError as exc:
print(f"moltranscode-gui: error: {exc}", file=sys.stderr)
return 2
Loading
Loading