ms-contactmap 0.1.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,41 @@
1
+ """Maestro-style 2D protein-ligand interaction diagrams for PySide6.
2
+
3
+ Public objects are imported lazily to reduce startup time and keep analysis,
4
+ layout and Qt drawing independently usable.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ from importlib import import_module
9
+
10
+ _PUBLIC = {
11
+ "Diagram": ("model", "Diagram"),
12
+ "DiagramView": ("widget", "DiagramView"),
13
+ "Interaction": ("model", "Interaction"),
14
+ "InteractionDiagramWidget": ("widget", "InteractionDiagramWidget"),
15
+ "LayoutResult": ("layout", "LayoutResult"),
16
+ "LigandGeometry": ("chem", "LigandGeometry"),
17
+ "Residue": ("model", "Residue"),
18
+ "ResidueRef": ("model", "ResidueRef"),
19
+ "build_diagram": ("interactions", "build_diagram"),
20
+ "build_scene": ("render", "build_scene"),
21
+ "document_from_dict": ("json_io", "document_from_dict"),
22
+ "document_to_dict": ("json_io", "document_to_dict"),
23
+ "export_png": ("export", "export_png"),
24
+ "export_svg": ("export", "export_svg"),
25
+ "load_json": ("json_io", "load_json"),
26
+ "load_ligand": ("chem", "load_ligand"),
27
+ "save_json": ("json_io", "save_json"),
28
+ "solve_layout": ("layout", "solve_layout"),
29
+ }
30
+
31
+ __all__ = list(_PUBLIC)
32
+
33
+
34
+ def __getattr__(name: str):
35
+ try:
36
+ module_name, attribute = _PUBLIC[name]
37
+ except KeyError as exc:
38
+ raise AttributeError(name) from exc
39
+ value = getattr(import_module(f".{module_name}", __name__), attribute)
40
+ globals()[name] = value
41
+ return value
@@ -0,0 +1,96 @@
1
+ """Command line entry point.
2
+
3
+ python -m ms_contactmap data/4ps5.pdb --ligand 2TA --smiles "..." --json out.json
4
+ python -m ms_contactmap --from-json out.json --png out.png
5
+ python -m ms_contactmap --from-json out.json --show
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import argparse
10
+ import sys
11
+ from pathlib import Path
12
+
13
+
14
+ def main(argv: list[str] | None = None) -> int:
15
+ parser = argparse.ArgumentParser(prog="ms_contactmap", description=__doc__)
16
+ parser.add_argument("pdb", nargs="?", type=Path, help="PDB file of the complex")
17
+ parser.add_argument("--from-json", type=Path, default=None,
18
+ help="visualize a previously computed diagram JSON")
19
+ parser.add_argument("--ligand", help="ligand residue name, e.g. 2TA")
20
+ parser.add_argument("--chain", default=None, help="restrict to this chain")
21
+ parser.add_argument("--resnum", type=int, default=None, help="restrict to this residue number")
22
+ parser.add_argument("--smiles", default=None, help="ligand SMILES (required with a PDB input)")
23
+ parser.add_argument("--no-exposure", action="store_true",
24
+ help="skip SASA halos for a faster first analysis")
25
+ parser.add_argument("--json", dest="json_output", type=Path, default=None,
26
+ help="write reusable analysis and layout JSON here")
27
+ parser.add_argument("--png", type=Path, default=None, help="write a PNG here")
28
+ parser.add_argument("--svg", type=Path, default=None, help="write an SVG here")
29
+ parser.add_argument("--scale", type=float, default=2.0, help="PNG scale factor")
30
+ parser.add_argument("--transparent", action="store_true", help="PNG without white background")
31
+ parser.add_argument("--show", action="store_true", help="open the interactive window")
32
+ args = parser.parse_args(argv)
33
+
34
+ if not args.png and not args.svg and not args.json_output and not args.show:
35
+ parser.error("nothing to do: pass --json, --png, --svg or --show")
36
+ if args.from_json and args.pdb:
37
+ parser.error("choose either a PDB input or --from-json, not both")
38
+ if not args.from_json and not args.pdb:
39
+ parser.error("pass a PDB input or --from-json")
40
+ if not args.from_json and not args.ligand:
41
+ parser.error("--ligand is required with a PDB input")
42
+ if not args.from_json and not args.smiles:
43
+ parser.error("--smiles is required with a PDB input")
44
+
45
+ from .export import ensure_app
46
+ app = ensure_app()
47
+ from .widget import InteractionDiagramWidget
48
+
49
+ if args.from_json:
50
+ widget = InteractionDiagramWidget.from_json(args.from_json)
51
+ diagram = widget.diagram
52
+ else:
53
+ from .interactions import build_diagram
54
+
55
+ diagram = build_diagram(
56
+ args.pdb,
57
+ args.ligand,
58
+ args.smiles,
59
+ chain=args.chain,
60
+ resnum=args.resnum,
61
+ name=args.pdb.stem,
62
+ compute_exposure=not args.no_exposure,
63
+ )
64
+ # Batch exports need a solved scene immediately. A show-only command
65
+ # displays the window first and solves in the responsive Qt thread.
66
+ widget = InteractionDiagramWidget(
67
+ diagram if (args.png or args.svg or args.json_output) else None
68
+ )
69
+ assert diagram is not None
70
+ print(
71
+ f"{diagram.name}: {diagram.ligand_name}, "
72
+ f"{len(diagram.residues)} residues, {len(diagram.interactions)} interactions",
73
+ file=sys.stderr,
74
+ )
75
+
76
+ if args.json_output:
77
+ widget.export_json(args.json_output)
78
+ print(f"wrote {args.json_output}", file=sys.stderr)
79
+ if args.png:
80
+ widget.export_png(args.png, scale=args.scale, background=None if args.transparent else "#ffffff")
81
+ print(f"wrote {args.png}", file=sys.stderr)
82
+ if args.svg:
83
+ widget.export_svg(args.svg)
84
+ print(f"wrote {args.svg}", file=sys.stderr)
85
+ if args.show:
86
+ widget.resize(1100, 780)
87
+ widget.setWindowTitle(f"{diagram.name} - {diagram.ligand_name}")
88
+ widget.show()
89
+ if widget.layout_result is None:
90
+ widget.set_diagram_async(diagram)
91
+ return app.exec()
92
+ return 0
93
+
94
+
95
+ if __name__ == "__main__":
96
+ raise SystemExit(main())