mirror of
https://github.com/molstar/molstar.git
synced 2026-06-04 13:30:24 +08:00
MVSX (#1041)
* MVSX format provider * MVS: Integration examples * MVS: drag-and-drop support for MVSX * MVS: support for URL param mvs-format=mvsx * MVS: docs for MVSX * MVS: mvs-render supports MVSX * Update README
This commit is contained in:
@@ -6,6 +6,8 @@ Note that since we don't clearly distinguish between a public and private interf
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
MolViewSpec extension: support for MVSX file format
|
||||
|
||||
## [v4.0.0] - 2023-02-04
|
||||
|
||||
- Add Mesoscale Explorer app for investigating large systems
|
||||
|
||||
@@ -44,6 +44,8 @@ A complete list of supported node types and their parameters is described by the
|
||||
|
||||
### Encoding
|
||||
|
||||
#### MVSJ
|
||||
|
||||
A MolViewSpec tree can be encoded and stored in `.mvsj` format, which is basically a JSON representation of the tree with additional metadata:
|
||||
|
||||
```json
|
||||
@@ -68,6 +70,24 @@ A MolViewSpec tree can be encoded and stored in `.mvsj` format, which is basical
|
||||
```
|
||||
Complete file: [1cbs.mvsj](../../../examples/mvs/1cbs.mvsj)
|
||||
|
||||
#### MVSX
|
||||
|
||||
The MolViewSpec tree can also be stored in a `.mvsx` format. This is simply a ZIP archive containing:
|
||||
- main file `index.mvsj` (contains the MolViewSpec tree encoded as MVSJ),
|
||||
- any number of other files, such as MVS annotations or structure files.
|
||||
|
||||
The advantage of this format is that the main file can reference other files in the archive using relative URIs. Thus the view description along with all necessary data can be stored as a single MVSX file.
|
||||
|
||||
It is important that the `index.mvsj` be at the top level of the archive, not in a subdirectory (
|
||||
```
|
||||
$ ls example/
|
||||
annotations-1h9t.cif index.mvsj
|
||||
$ zip -r example.mvsx example/ # Wrong, won't create a valid MVSX file
|
||||
$ cd example/; zip -r ../example.mvsx * # Correct
|
||||
```
|
||||
|
||||
Example: [1ht9.mvsx](../../../examples/mvs/1h9t.mvsx)
|
||||
|
||||
|
||||
## MolViewSpec extension functionality
|
||||
|
||||
@@ -75,20 +95,24 @@ Mol* MolViewSpec extension provides functionality for building, validating, and
|
||||
|
||||
### Graphical user interface
|
||||
|
||||
- **Drag&drop support:** The easiest way to load a MVS view into Mol* Viewer is to drag a `.mvsj` file and drop it in a browser window with Mol* Viewer.
|
||||
- **Drag&drop support:** The easiest way to load a MVS view into Mol* Viewer is to drag a `.mvsj` or `.mvsx` file and drop it in a browser window with Mol* Viewer.
|
||||
|
||||
- **Load via menu:** Another way to load a MVS view is to use "Download File" or "Open Files" action, available in the "Home" tab in the left panel. For these actions, the "Format" parameter must be set to "MVSJ" (in the "Miscellaneous" category) or "Auto".
|
||||
- **Load via menu:** Another way to load a MVS view is to use "Download File" or "Open Files" action, available in the "Home" tab in the left panel. For these actions, the "Format" parameter must be set to "MVSJ" or "MVSX" (in the "Miscellaneous" category) or "Auto".
|
||||
|
||||
- **URL parameters:** Mol* Viewer supports `mvs-url`, `mvs-data`, and `mvs-format` URL parameters to specify a MVS view to be loaded when the viewer is initialized.
|
||||
- `mvs-url` specifies the address from which the MVS view should be retrieved.
|
||||
- `mvs-data` specifies the MVS view data directly. Keep in mind that some characters must be escaped to be used in the URL. Also beware that URLs longer than 2000 character may not work in all browsers.
|
||||
- `mvs-format` specifies the format of the MVS view data (from `mvs-url` or `mvs-data`). The only allowed (and default) value is `mvsj`, as this is currently the only supported format.
|
||||
- `mvs-data` specifies the MVS view data directly. Keep in mind that some characters must be escaped to be used in the URL. Also beware that URLs longer than 2000 character may not work in all browsers. Because of these limitations, the preferred method it to host the data somewhere and use `mvs-url` instead.
|
||||
- `mvs-format` specifies the format of the MVS view data from `mvs-url` or `mvs-data`. Allowed values are `mvsj` and `mvsx` (default is `mvsj`).
|
||||
|
||||
Examples of URL parameter usage:
|
||||
|
||||
- https://molstar.org/viewer?mvs-format=mvsj&mvs-url=https://raw.githubusercontent.com/molstar/molstar/master/examples/mvs/1cbs.mvsj
|
||||
- https://molstar.org/viewer/?mvs-format=mvsj&mvs-url=https://raw.githubusercontent.com/molstar/molstar/master/examples/mvs/1cbs.mvsj
|
||||
|
||||
- https://molstar.org/viewer?mvs-format=mvsj&mvs-data=%7B%22metadata%22%3A%7B%22title%22%3A%22Example%20MolViewSpec%20-%201cbs%20with%20labelled%20protein%20and%20ligand%22%2C%22version%22%3A%221%22%2C%22timestamp%22%3A%222023-11-24T10%3A38%3A17.483%22%7D%2C%22root%22%3A%7B%22kind%22%3A%22root%22%2C%22children%22%3A%5B%7B%22kind%22%3A%22download%22%2C%22params%22%3A%7B%22url%22%3A%22https%3A//www.ebi.ac.uk/pdbe/entry-files/1cbs.bcif%22%7D%2C%22children%22%3A%5B%7B%22kind%22%3A%22parse%22%2C%22params%22%3A%7B%22format%22%3A%22bcif%22%7D%2C%22children%22%3A%5B%7B%22kind%22%3A%22structure%22%2C%22params%22%3A%7B%22type%22%3A%22model%22%7D%2C%22children%22%3A%5B%7B%22kind%22%3A%22component%22%2C%22params%22%3A%7B%22selector%22%3A%22polymer%22%7D%2C%22children%22%3A%5B%7B%22kind%22%3A%22representation%22%2C%22params%22%3A%7B%22type%22%3A%22cartoon%22%7D%2C%22children%22%3A%5B%7B%22kind%22%3A%22color%22%2C%22params%22%3A%7B%22color%22%3A%22green%22%7D%7D%2C%7B%22kind%22%3A%22color%22%2C%22params%22%3A%7B%22selector%22%3A%7B%22label_asym_id%22%3A%22A%22%2C%22beg_label_seq_id%22%3A1%2C%22end_label_seq_id%22%3A50%7D%2C%22color%22%3A%22%236688ff%22%7D%7D%5D%7D%2C%7B%22kind%22%3A%22label%22%2C%22params%22%3A%7B%22text%22%3A%22Protein%22%7D%7D%5D%7D%2C%7B%22kind%22%3A%22component%22%2C%22params%22%3A%7B%22selector%22%3A%22ligand%22%7D%2C%22children%22%3A%5B%7B%22kind%22%3A%22representation%22%2C%22params%22%3A%7B%22type%22%3A%22ball_and_stick%22%7D%2C%22children%22%3A%5B%7B%22kind%22%3A%22color%22%2C%22params%22%3A%7B%22color%22%3A%22%23cc3399%22%7D%7D%5D%7D%2C%7B%22kind%22%3A%22label%22%2C%22params%22%3A%7B%22text%22%3A%22Retinoic%20Acid%22%7D%7D%5D%7D%5D%7D%5D%7D%5D%7D%2C%7B%22kind%22%3A%22canvas%22%2C%22params%22%3A%7B%22background_color%22%3A%22%23ffffee%22%7D%7D%2C%7B%22kind%22%3A%22camera%22%2C%22params%22%3A%7B%22target%22%3A%5B17%2C21%2C27%5D%2C%22position%22%3A%5B41%2C34%2C69%5D%2C%22up%22%3A%5B-0.129%2C0.966%2C-0.224%5D%7D%7D%5D%7D%7D
|
||||
- https://molstar.org/viewer/?mvs-format=mvsx&mvs-url=https://raw.githubusercontent.com/molstar/molstar/master/examples/mvs/1h9t.mvsx
|
||||
|
||||
- https://molstar.org/viewer/?mvs-format=mvsj&mvs-data=%7B%22metadata%22%3A%7B%22title%22%3A%22Example%20MolViewSpec%20-%201cbs%20with%20labelled%20protein%20and%20ligand%22%2C%22version%22%3A%221%22%2C%22timestamp%22%3A%222023-11-24T10%3A38%3A17.483%22%7D%2C%22root%22%3A%7B%22kind%22%3A%22root%22%2C%22children%22%3A%5B%7B%22kind%22%3A%22download%22%2C%22params%22%3A%7B%22url%22%3A%22https%3A//www.ebi.ac.uk/pdbe/entry-files/1cbs.bcif%22%7D%2C%22children%22%3A%5B%7B%22kind%22%3A%22parse%22%2C%22params%22%3A%7B%22format%22%3A%22bcif%22%7D%2C%22children%22%3A%5B%7B%22kind%22%3A%22structure%22%2C%22params%22%3A%7B%22type%22%3A%22model%22%7D%2C%22children%22%3A%5B%7B%22kind%22%3A%22component%22%2C%22params%22%3A%7B%22selector%22%3A%22polymer%22%7D%2C%22children%22%3A%5B%7B%22kind%22%3A%22representation%22%2C%22params%22%3A%7B%22type%22%3A%22cartoon%22%7D%2C%22children%22%3A%5B%7B%22kind%22%3A%22color%22%2C%22params%22%3A%7B%22color%22%3A%22green%22%7D%7D%2C%7B%22kind%22%3A%22color%22%2C%22params%22%3A%7B%22selector%22%3A%7B%22label_asym_id%22%3A%22A%22%2C%22beg_label_seq_id%22%3A1%2C%22end_label_seq_id%22%3A50%7D%2C%22color%22%3A%22%236688ff%22%7D%7D%5D%7D%2C%7B%22kind%22%3A%22label%22%2C%22params%22%3A%7B%22text%22%3A%22Protein%22%7D%7D%5D%7D%2C%7B%22kind%22%3A%22component%22%2C%22params%22%3A%7B%22selector%22%3A%22ligand%22%7D%2C%22children%22%3A%5B%7B%22kind%22%3A%22representation%22%2C%22params%22%3A%7B%22type%22%3A%22ball_and_stick%22%7D%2C%22children%22%3A%5B%7B%22kind%22%3A%22color%22%2C%22params%22%3A%7B%22color%22%3A%22%23cc3399%22%7D%7D%5D%7D%2C%7B%22kind%22%3A%22label%22%2C%22params%22%3A%7B%22text%22%3A%22Retinoic%20Acid%22%7D%7D%5D%7D%5D%7D%5D%7D%5D%7D%2C%7B%22kind%22%3A%22canvas%22%2C%22params%22%3A%7B%22background_color%22%3A%22%23ffffee%22%7D%7D%2C%7B%22kind%22%3A%22camera%22%2C%22params%22%3A%7B%22target%22%3A%5B17%2C21%2C27%5D%2C%22position%22%3A%5B41%2C34%2C69%5D%2C%22up%22%3A%5B-0.129%2C0.966%2C-0.224%5D%7D%7D%5D%7D%7D
|
||||
|
||||
- https://molstar.org/viewer/?mvs-format=mvsx&mvs-data=base64,UEsDBBQAAAAIADSFPlhDx8RXYwEAAGwFAAAUABwAYW5ub3RhdGlvbnMtMWg5dC5jaWZVVAkAA8MmuWVuvbtldXgLAAEE9gEAAAQUAAAAlZJNj4IwEIbv/RVNPPSkacGPckT8TDabPe6emkq72ARbF9iD/34LFBV1FSbh7YTh4Z1hELzgjOyDgnGtTcELZXQOQGrMkQEWm8PRaKmLfJTynUwZz08HpkSrck5BCD8yU0ilwfycRXDxHoJFpUu4NqkAm/pYwWifmkwJCdaXdHtJmy6uOrtt47q0kwmry7n8uatKLZ5UY2M9Hzi1byWZ+T2WbAhtBPaiPoRoIMR0ijGyqZ1yuFNaKJ0geI5RBdAxhJ5PSgBjSmsgjE/pMDIhbFEj+wFbDnHcAP85zO8dKH3mEFWHEzQgBGPOnQP8vHp347BoA6KKBvh6ACzbwLeNeoZy/XfPW2DTF1i1AYyFqIHmH0I3wLovsO0LlJuezJygTDZ9ozeVcC2aLcBcFSVLKmA6c9IRIB5x8hrwamDipDMQOOkKzKiT14BfAh72S+nY0rzf0OMa6D60A7oPXQF9ZhiDP1BLAwQUAAAACACagEFYhgn8CJECAAAbFQAACgAcAGluZGV4Lm12c2pVVAkAAyTBu2UlwbtldXgLAAEE9gEAAAQUAAAA7VjBjtowEL3vV0S5tiSBFnXhttpKPbWq1KqXqoqMPWwsHDuynaVoxb/XNhAIiUOEtK26m5zIzLzJG8+8gfB0EwShFEKH8+DJfDZ3K8qJudtZ3+5sOKOMSODG/tNZgn10DUHEmjOByB7lfAWSKFdV9r21lMwCMq0LNY/j9XodwYJGCEflKi7IAmLgWm5GS8pAxYe88Tib6bQsCNJAIkyXYZVze/LIFrJ1wjXShqCCE8Z+1s6zFDJH9rTCPK8xOGPRxaTJpsZIaVliXcpzVt3MnFdvCnDcBAEWNiK2LQk7OLbzrHHFIi8EN81Kl1LkaSlpC+nLxF2EBZuUUYw4FxppKrga2Za7VrenrXWkMwyboXkQcpNylEONuurITYGRJsIPWDCBV2kGiIC0EDexJ/X4kQpnkCP3mAxRfonTI2IlKE/T9pFfpdBgUnkifrXaW2bEJbswJ/bydfZ0qUAhQZkjdMfhLbLPvLiow8BjJLUQ3lK9ZbkkPUqzVxeRmiSYkJfkUKF6lekir5NHBe8nkyq8IZc+Q3x82JluzIlcBl2tnSrDUUNmyCgpIZWIP4B/Kuy17fD6fe3i8SHaolsn8nWv22fYgR+/3L2G/bdAjBmtkFRpilfDGvTC/+oaHPZRhRj20S7yk2CkS/b3mZENJd4ZeUlLS5VyiXCHHIZtNWwrT/Q124qhBbD/b1P1en2s/+J2lT7vW2qPGWnr5jV9M292TNNi6Ny/7VzDdq7LesSp9+g5WHcWe+cmIsxBI4I0Ov4P+QhS2a8bwziJxod/IgkoLGmh9547HsBvlBcMgjXVWfD5x7egeRyhpjkobeIsZpJM3o+SySgZfx+P59Pb+btZNPtwO5lO3yTJPEnskWxvtn8AUEsBAh4DFAAAAAgANIU+WEPHxFdjAQAAbAUAABQAGAAAAAAAAQAAAKSBAAAAAGFubm90YXRpb25zLTFoOXQuY2lmVVQFAAPDJrlldXgLAAEE9gEAAAQUAAAAUEsBAh4DFAAAAAgAmoBBWIYJ/AiRAgAAGxUAAAoAGAAAAAAAAQAAAKSBsQEAAGluZGV4Lm12c2pVVAUAAyTBu2V1eAsAAQT2AQAABBQAAABQSwUGAAAAAAIAAgCqAAAAhgQAAAAA
|
||||
|
||||
|
||||
### Programming interface
|
||||
@@ -126,7 +150,7 @@ const mvsData2: MVSData = builder.getState();
|
||||
await loadMVS(this.plugin, mvsData2, { replaceExisting: false });
|
||||
```
|
||||
|
||||
When using the pre-built Mol* plugin bundle, `MVSData` and `loadMVS` are exposed as `molstar.PluginExtensions.mvs.MVSData` and `molstar.PluginExtensions.mvs.loadMVS`. Furthermore, the `molstar.Viewer` class has `loadMvsFromUrl` and `loadMvsData` methods, providing the same functionality as `mvs-url` and `mvs-data` URL parameters.
|
||||
When using the pre-built Mol* plugin bundle, `MVSData` and `loadMVS` are exposed as `molstar.PluginExtensions.mvs.MVSData` and `molstar.PluginExtensions.mvs.loadMVS`. Furthermore, the `molstar.Viewer` class has `loadMvsFromUrl` and `loadMvsData` methods, providing the same functionality as `mvs-url` and `mvs-data` URL parameters. See the [integration examples](./integration-examples.html) page for a demonstration.
|
||||
|
||||
|
||||
### Command-line utilities
|
||||
|
||||
@@ -101,6 +101,8 @@ assuming that the JSON annotation file shown in the previous section is availabl
|
||||
|
||||
The `uri` parameter can also hold a URI reference (relative URI). In such cases, this URI reference is relative to the URI of the MVS file itself (e.g. if the MVS file is available from `https://example.org/spanish/inquisition/expectations.mvsj`, then the relative URI `./annotations.json` is equivalent to `https://example.org/spanish/inquisition/annotations.json`). This is however not applicable in all cases (e.g. the MVS tree can be constructed ad-hoc within a web application, therefore it has no URI; or the MVS file is loaded from a local disk using drag&drop, therefore the relative location is not accessible by the browser).
|
||||
|
||||
A special case is when the MVS tree is saved in MVSX format. An MVSX file is a ZIP archive containing the MVS tree in `index.mvsj` and possibly other files. In this case, the relative URIs will resolve to the files within the archive (e.g. `./annotations.json` points to the file `annotations.json` stored in the MSVX archive).
|
||||
|
||||
### From source
|
||||
|
||||
The MVS annotations can in fact be stored within the same mmCIF file from which the structure coordinates are loaded. To reference these annotations, we can use `color_from_source`, `label_from_source`, `tooltip_from_source`, and `component_from_source` nodes. Example:
|
||||
|
||||
112
docs/extensions/mvs/integration-examples.html
Normal file
112
docs/extensions/mvs/integration-examples.html
Normal file
@@ -0,0 +1,112 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<!-- Replace "latest" by the specific version you want to use, e.g. "4.0.0" -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/molstar@latest/build/viewer/molstar.js"></script>
|
||||
<!-- Replace "latest" by the specific version you want to use, e.g. "4.0.0" -->
|
||||
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/molstar@latest/build/viewer/molstar.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Integration of Mol* with MolViewSpec Extension</h1>
|
||||
<p>
|
||||
This page demonstrates several methods to integrate Mol* Viewer in a web page and use MolViewSpec functionality.
|
||||
See the source HTML to see the actual code.
|
||||
</p>
|
||||
|
||||
|
||||
<h2>Method 1: Get MVS view from a server and pass to the viewer</h2>
|
||||
<p>
|
||||
The recommended method is to serve the MVS view files by your server (either as static files or generated by the
|
||||
server on-demand) and call the <code>loadMvsFromUrl</code> method to retrieve and load them.
|
||||
This example uses a MVS view file from the address specified in the <code>sourceUrl</code> variable.
|
||||
If the MVS view file contains relative references, they will be resolved as relative to <code>sourceUrl</code>.
|
||||
</p>
|
||||
|
||||
<div id="viewer1" style="position: relative; width: 500px; height: 500px;"></div>
|
||||
<script>
|
||||
const sourceUrl = 'https://raw.githubusercontent.com/molstar/molstar/master/examples/mvs/1h9t_domain_labels.mvsj';
|
||||
molstar.Viewer.create('viewer1', { layoutIsExpanded: false, layoutShowControls: false })
|
||||
.then(viewer => viewer.loadMvsFromUrl(sourceUrl, 'mvsj'));
|
||||
</script>
|
||||
|
||||
|
||||
<p>
|
||||
A variation of this method uses <code>molstar.PluginExtensions.mvs.loadMVS</code> instead of
|
||||
<code>loadMvsFromUrl</code> and allows replacing the MVS view after it has been loaded.
|
||||
</p>
|
||||
|
||||
<div id="viewer1b" style="position: relative; width: 500px; height: 500px;"></div>
|
||||
<button onclick="loadView1();">View 1</button>
|
||||
<button onclick="loadView2();">View 2</button>
|
||||
<script>
|
||||
let theViewer;
|
||||
function load(viewer, url, replace) {
|
||||
fetch(url)
|
||||
.then(response => response.text())
|
||||
.then(text => molstar.PluginExtensions.mvs.MVSData.fromMVSJ(text))
|
||||
.then(mvsData => molstar.PluginExtensions.mvs.loadMVS(viewer.plugin, mvsData, { sourceUrl: url, sanityChecks: true, replaceExisting: replace }));
|
||||
}
|
||||
function loadView1() {
|
||||
load(theViewer, 'https://raw.githubusercontent.com/molstar/molstar/master/examples/mvs/1cbs.mvsj', true);
|
||||
}
|
||||
function loadView2() {
|
||||
load(theViewer, 'https://raw.githubusercontent.com/molstar/molstar/master/examples/mvs/1cbs-focus.mvsj', true);
|
||||
}
|
||||
molstar.Viewer.create('viewer1b', { layoutIsExpanded: false, layoutShowControls: false })
|
||||
.then(viewer => {
|
||||
theViewer = viewer;
|
||||
loadView1();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
<h2>Method 2: Construct MVS view on frontend and pass to the viewer</h2>
|
||||
<p>
|
||||
Another option is to utilize the MVS builder provided by the extension to build the view on frontend and then
|
||||
pass it to the viewer. This example builds the view in plain JavaScript, directly in a <script> tag in
|
||||
HTML. However, for a better developer experience consider writing the code in TypeScript.
|
||||
If the built MVS view contains relative references, they will be resolved as relative to the URL of this HTML
|
||||
page.
|
||||
</p>
|
||||
|
||||
<div id="viewer2" style="position: relative; width: 500px; height: 500px;"></div>
|
||||
<script>
|
||||
// Build an ad-hoc MVS view
|
||||
const builder = molstar.PluginExtensions.mvs.MVSData.createBuilder();
|
||||
const structure = builder
|
||||
.download({ url: 'https://www.ebi.ac.uk/pdbe/entry-files/1cbs.bcif' })
|
||||
.parse({ format: 'bcif' })
|
||||
.modelStructure({});
|
||||
structure
|
||||
.component({ selector: 'polymer' })
|
||||
.representation({ type: 'cartoon' })
|
||||
.color({ color: 'green' });
|
||||
structure
|
||||
.component({ selector: 'ligand' })
|
||||
.label({ text: 'Retinoic acid' })
|
||||
.focus({})
|
||||
.representation({ type: 'ball_and_stick' })
|
||||
.color({ color: '#cc3399' });
|
||||
const mvsData = builder.getState();
|
||||
|
||||
// Initialize viewer and load MVSJ
|
||||
const mvsj = molstar.PluginExtensions.mvs.MVSData.toMVSJ(mvsData);
|
||||
molstar.Viewer.create('viewer2', { layoutIsExpanded: false, layoutShowControls: false })
|
||||
.then(viewer => viewer.loadMvsData(mvsj, 'mvsj'));
|
||||
|
||||
// // Alternative initialization and loading (avoids encoding and again decoding the data, allows changing the view by using `replaceExisting: true`):
|
||||
// molstar.Viewer.create('viewer2', { layoutIsExpanded: false, layoutShowControls: false })
|
||||
// .then(viewer => molstar.PluginExtensions.mvs.loadMVS(viewer.plugin, mvsData, { sourceUrl: undefined, sanityChecks: true, replaceExisting: false }));
|
||||
</script>
|
||||
|
||||
|
||||
<p>
|
||||
Again, there is variation with using <code>molstar.PluginExtensions.mvs.loadMVS</code> instead of
|
||||
<code>loadMvsData</code>.
|
||||
</p>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
BIN
examples/mvs/1h9t.mvsx
Normal file
BIN
examples/mvs/1h9t.mvsx
Normal file
Binary file not shown.
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
@@ -15,6 +15,7 @@ import { QualityAssessment } from '../../extensions/model-archive/quality-assess
|
||||
import { ModelExport } from '../../extensions/model-export';
|
||||
import { Mp4Export } from '../../extensions/mp4-export';
|
||||
import { MolViewSpec } from '../../extensions/mvs/behavior';
|
||||
import { loadMVSX } from '../../extensions/mvs/components/formats';
|
||||
import { loadMVS } from '../../extensions/mvs/load';
|
||||
import { MVSData } from '../../extensions/mvs/mvs-data';
|
||||
import { PDBeStructureQualityReport } from '../../extensions/pdbe';
|
||||
@@ -50,6 +51,7 @@ import { PluginLayoutControlsDisplay } from '../../mol-plugin/layout';
|
||||
import { PluginSpec } from '../../mol-plugin/spec';
|
||||
import { PluginState } from '../../mol-plugin/state';
|
||||
import { StateObjectRef, StateObjectSelector } from '../../mol-state';
|
||||
import { Task } from '../../mol-task';
|
||||
import { Asset } from '../../mol-util/assets';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import '../../mol-util/polyfill';
|
||||
@@ -468,25 +470,46 @@ export class Viewer {
|
||||
return { model, coords, preset };
|
||||
}
|
||||
|
||||
async loadMvsFromUrl(url: string, format: 'mvsj') {
|
||||
async loadMvsFromUrl(url: string, format: 'mvsj' | 'mvsx') {
|
||||
if (format === 'mvsj') {
|
||||
const data = await this.plugin.runTask(this.plugin.fetch({ url, type: 'string' }));
|
||||
const mvsData = MVSData.fromMVSJ(data);
|
||||
await loadMVS(this.plugin, mvsData, { sanityChecks: true, sourceUrl: url });
|
||||
} else if (format === 'mvsx') {
|
||||
const data = await this.plugin.runTask(this.plugin.fetch({ url, type: 'binary' }));
|
||||
await this.plugin.runTask(Task.create('Load MVSX file', async ctx => {
|
||||
const parsed = await loadMVSX(this.plugin, ctx, data);
|
||||
await loadMVS(this.plugin, parsed.mvsData, { sanityChecks: true, sourceUrl: parsed.sourceUrl });
|
||||
}));
|
||||
} else {
|
||||
throw new Error(`Unknown MolViewSpec format: ${format}`);
|
||||
}
|
||||
// We might add more formats in the future
|
||||
}
|
||||
|
||||
async loadMvsData(data: string, format: 'mvsj') {
|
||||
/** Load MolViewSpec from `data`.
|
||||
* If `format` is 'mvsj', `data` must be a string or a Uint8Array containing a UTF8-encoded string.
|
||||
* If `format` is 'mvsx', `data` must be a Uint8Array or a string containing base64-encoded binary data prefixed with 'base64,'. */
|
||||
async loadMvsData(data: string | Uint8Array, format: 'mvsj' | 'mvsx') {
|
||||
if (typeof data === 'string' && data.startsWith('base64')) {
|
||||
data = Uint8Array.from(atob(data.substring(7)), c => c.charCodeAt(0)); // Decode base64 string to Uint8Array
|
||||
}
|
||||
if (format === 'mvsj') {
|
||||
if (typeof data !== 'string') {
|
||||
data = new TextDecoder().decode(data); // Decode Uint8Array to string using UTF8
|
||||
}
|
||||
const mvsData = MVSData.fromMVSJ(data);
|
||||
await loadMVS(this.plugin, mvsData, { sanityChecks: true, sourceUrl: undefined });
|
||||
} else if (format === 'mvsx') {
|
||||
if (typeof data === 'string') {
|
||||
throw new Error("loadMvsData: if `format` is 'mvsx', then `data` must be a Uint8Array or a base64-encoded string prefixed with 'base64,'.");
|
||||
}
|
||||
await this.plugin.runTask(Task.create('Load MVSX file', async ctx => {
|
||||
const parsed = await loadMVSX(this.plugin, ctx, data as Uint8Array);
|
||||
await loadMVS(this.plugin, parsed.mvsData, { sanityChecks: true, sourceUrl: parsed.sourceUrl });
|
||||
}));
|
||||
} else {
|
||||
throw new Error(`Unknown MolViewSpec format: ${format}`);
|
||||
}
|
||||
// We might add more formats in the future
|
||||
}
|
||||
|
||||
handleResize() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2023-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Adam Midlik <midlik@gmail.com>
|
||||
*
|
||||
@@ -17,19 +17,21 @@ import path from 'path';
|
||||
import pngjs from 'pngjs';
|
||||
|
||||
import { Canvas3DParams } from '../../mol-canvas3d/canvas3d';
|
||||
import { setCanvasModule } from '../../mol-geo/geometry/text/font-atlas';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { HeadlessPluginContext } from '../../mol-plugin/headless-plugin-context';
|
||||
import { DefaultPluginSpec, PluginSpec } from '../../mol-plugin/spec';
|
||||
import { ExternalModules, defaultCanvas3DParams } from '../../mol-plugin/util/headless-screenshot';
|
||||
import { Task } from '../../mol-task';
|
||||
import { setFSModule } from '../../mol-util/data-source';
|
||||
import { onelinerJsonString } from '../../mol-util/json';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
|
||||
// MolViewSpec must be imported after HeadlessPluginContext
|
||||
import { MolViewSpec } from '../../extensions/mvs/behavior';
|
||||
import { loadMVSX } from '../../extensions/mvs/components/formats';
|
||||
import { loadMVS } from '../../extensions/mvs/load';
|
||||
import { MVSData } from '../../extensions/mvs/mvs-data';
|
||||
import { setCanvasModule } from '../../mol-geo/geometry/text/font-atlas';
|
||||
|
||||
|
||||
setFSModule(fs);
|
||||
@@ -48,7 +50,7 @@ interface Args {
|
||||
/** Return parsed command line arguments for `main` */
|
||||
function parseArguments(): Args {
|
||||
const parser = new ArgumentParser({ description: 'Command-line application for rendering images from MolViewSpec files' });
|
||||
parser.add_argument('-i', '--input', { required: true, nargs: '+', help: 'Input file(s) in .mvsj format' });
|
||||
parser.add_argument('-i', '--input', { required: true, nargs: '+', help: 'Input file(s) in .mvsj or .mvsx format. File format is inferred from the file extension.' });
|
||||
parser.add_argument('-o', '--output', { required: true, nargs: '+', help: 'File path(s) for output files (one output path for each input file). Output format is inferred from the file extension (.png or .jpg)' });
|
||||
parser.add_argument('-s', '--size', { help: `Output image resolution, {width}x{height}. Default: ${DEFAULT_SIZE}.`, default: DEFAULT_SIZE });
|
||||
parser.add_argument('-m', '--molj', { action: 'store_true', help: `Save Mol* state (.molj) in addition to rendered images (use the same output file paths but with .molj extension)` });
|
||||
@@ -75,10 +77,22 @@ async function main(args: Args): Promise<void> {
|
||||
const output = args.output[i];
|
||||
console.log(`Processing ${input} -> ${output}`);
|
||||
|
||||
const data = fs.readFileSync(input, { encoding: 'utf8' });
|
||||
const mvsData = MVSData.fromMVSJ(data);
|
||||
let mvsData: MVSData;
|
||||
let sourceUrl: string | undefined;
|
||||
if (input.toLowerCase().endsWith('.mvsj')) {
|
||||
const data = fs.readFileSync(input, { encoding: 'utf8' });
|
||||
mvsData = MVSData.fromMVSJ(data);
|
||||
sourceUrl = `file://${path.resolve(input)}`;
|
||||
} else if (input.toLowerCase().endsWith('.mvsx')) {
|
||||
const data = fs.readFileSync(input);
|
||||
const mvsx = await plugin.runTask(Task.create('Load MVSX', async ctx => loadMVSX(plugin, ctx, data)));
|
||||
mvsData = mvsx.mvsData;
|
||||
sourceUrl = mvsx.sourceUrl;
|
||||
} else {
|
||||
throw new Error(`Input file name must end with .mvsj or .mvsx: ${input}`);
|
||||
}
|
||||
await loadMVS(plugin, mvsData, { sanityChecks: true, replaceExisting: true, sourceUrl: sourceUrl });
|
||||
|
||||
await loadMVS(plugin, mvsData, { sanityChecks: true, replaceExisting: true, sourceUrl: `file://${path.resolve(input)}` });
|
||||
fs.mkdirSync(path.dirname(output), { recursive: true });
|
||||
if (args.molj) {
|
||||
await plugin.saveStateSnapshot(withExtension(output, '.molj'));
|
||||
|
||||
@@ -13,6 +13,7 @@ import { PluginBehavior } from '../../mol-plugin/behavior/behavior';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { StructureRepresentationProvider } from '../../mol-repr/structure/representation';
|
||||
import { StateAction } from '../../mol-state';
|
||||
import { Task } from '../../mol-task';
|
||||
import { ColorTheme } from '../../mol-theme/color';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { MVSAnnotationColorThemeProvider } from './components/annotation-color-theme';
|
||||
@@ -21,7 +22,7 @@ import { MVSAnnotationsProvider } from './components/annotation-prop';
|
||||
import { MVSAnnotationTooltipsLabelProvider, MVSAnnotationTooltipsProvider } from './components/annotation-tooltips-prop';
|
||||
import { CustomLabelRepresentationProvider } from './components/custom-label/representation';
|
||||
import { CustomTooltipsLabelProvider, CustomTooltipsProvider } from './components/custom-tooltips-prop';
|
||||
import { LoadMvsData, MVSJFormatProvider } from './components/formats';
|
||||
import { LoadMvsData, MVSJFormatProvider, MVSXFormatProvider, loadMVSX } from './components/formats';
|
||||
import { IsMVSModelProvider } from './components/is-mvs-model-prop';
|
||||
import { makeMultilayerColorThemeProvider } from './components/multilayer-color-theme';
|
||||
import { loadMVS } from './load';
|
||||
@@ -76,6 +77,7 @@ export const MolViewSpec = PluginBehavior.create<{ autoAttach: boolean }>({
|
||||
],
|
||||
dataFormats: [
|
||||
{ name: 'MVSJ', provider: MVSJFormatProvider },
|
||||
{ name: 'MVSX', provider: MVSXFormatProvider },
|
||||
],
|
||||
actions: [
|
||||
LoadMvsData,
|
||||
@@ -158,18 +160,32 @@ interface DragAndDropHandler {
|
||||
handle: PluginDragAndDropHandler,
|
||||
}
|
||||
|
||||
/** DragAndDropHandler handler for `.mvsj` files */
|
||||
/** DragAndDropHandler handler for `.mvsj` and `.mvsx` files */
|
||||
const MVSDragAndDropHandler: DragAndDropHandler = {
|
||||
name: 'mvs-mvsj',
|
||||
/** Load .mvsj files. Delete previous plugin state before loading.
|
||||
* If multiple files are provided, merge their MVS data into one state. */
|
||||
name: 'mvs-mvsj-mvsx',
|
||||
/** Load .mvsj and .mvsx files. Delete previous plugin state before loading.
|
||||
* If multiple files are provided, merge their MVS data into one state.
|
||||
* Return `true` if at least one file has been loaded. */
|
||||
async handle(files: File[], plugin: PluginContext): Promise<boolean> {
|
||||
let applied = false;
|
||||
for (const file of files) {
|
||||
if (file.name.toLowerCase().endsWith('.mvsj')) {
|
||||
const data = await file.text();
|
||||
const mvsData = MVSData.fromMVSJ(data);
|
||||
await loadMVS(plugin, mvsData, { sanityChecks: true, replaceExisting: !applied, sourceUrl: undefined });
|
||||
const task = Task.create('Load MVSJ file', async ctx => {
|
||||
const data = await file.text();
|
||||
const mvsData = MVSData.fromMVSJ(data);
|
||||
await loadMVS(plugin, mvsData, { sanityChecks: true, replaceExisting: !applied, sourceUrl: undefined });
|
||||
});
|
||||
await plugin.runTask(task);
|
||||
applied = true;
|
||||
}
|
||||
if (file.name.toLowerCase().endsWith('.mvsx')) {
|
||||
const task = Task.create('Load MVSX file', async ctx => {
|
||||
const buffer = await file.arrayBuffer();
|
||||
const array = new Uint8Array(buffer);
|
||||
const parsed = await loadMVSX(plugin, ctx, array);
|
||||
await loadMVS(plugin, parsed.mvsData, { sanityChecks: true, replaceExisting: !applied, sourceUrl: parsed.sourceUrl });
|
||||
});
|
||||
await plugin.runTask(task);
|
||||
applied = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
/**
|
||||
* Copyright (c) 2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2023-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Adam Midlik <midlik@gmail.com>
|
||||
*/
|
||||
|
||||
import { hashFnv32a } from '../../../mol-data/util';
|
||||
import { DataFormatProvider } from '../../../mol-plugin-state/formats/provider';
|
||||
import { PluginStateObject as SO } from '../../../mol-plugin-state/objects';
|
||||
import { Download } from '../../../mol-plugin-state/transforms/data';
|
||||
import { PluginContext } from '../../../mol-plugin/context';
|
||||
import { StateAction, StateObjectRef } from '../../../mol-state';
|
||||
import { Task } from '../../../mol-task';
|
||||
import { Asset } from '../../../mol-util/assets';
|
||||
import { RuntimeContext, Task } from '../../../mol-task';
|
||||
import { Asset, AssetManager } from '../../../mol-util/assets';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { unzip } from '../../../mol-util/zip/zip';
|
||||
import { loadMVS } from '../load';
|
||||
import { MVSData } from '../mvs-data';
|
||||
import { MVSTransform } from './annotation-structure-component';
|
||||
@@ -23,7 +25,7 @@ export class Mvs extends SO.Create<{ mvsData: MVSData, sourceUrl?: string }>({ n
|
||||
/** Transformer for parsing data in MVSJ format */
|
||||
export const ParseMVSJ = MVSTransform({
|
||||
name: 'mvs-parse-mvsj',
|
||||
display: { name: 'MVS Annotation Component', description: 'A molecular structure component defined by MVS annotation data.' },
|
||||
display: { name: 'MolViewSpec from MVSJ', description: 'Create MolViewSpec view from MVSJ data' },
|
||||
from: SO.Data.String,
|
||||
to: Mvs,
|
||||
})({
|
||||
@@ -34,16 +36,27 @@ export const ParseMVSJ = MVSTransform({
|
||||
},
|
||||
});
|
||||
|
||||
/** If the PluginStateObject `pso` comes from a Download transform, try to get its `url` parameter. */
|
||||
function tryGetDownloadUrl(pso: SO.Data.String, plugin: PluginContext): string | undefined {
|
||||
const theCell = plugin.state.data.selectQ(q => q.ofTransformer(Download)).find(cell => cell.obj === pso);
|
||||
const urlParam = theCell?.transform.params?.url;
|
||||
return urlParam ? Asset.getUrl(urlParam) : undefined;
|
||||
}
|
||||
/** Transformer for parsing data in MVSX format (= zipped MVSJ + referenced files like structures and annotations) */
|
||||
export const ParseMVSX = MVSTransform({
|
||||
name: 'mvs-parse-mvsx',
|
||||
display: { name: 'MolViewSpec from MVSX', description: 'Create MolViewSpec view from MVSX data' },
|
||||
from: SO.Data.Binary,
|
||||
to: Mvs,
|
||||
params: {
|
||||
mainFilePath: PD.Text('index.mvsj'),
|
||||
},
|
||||
})({
|
||||
apply({ a, params }, plugin: PluginContext) {
|
||||
return Task.create('Parse MVSX file', async ctx => {
|
||||
const data = await loadMVSX(plugin, ctx, a.data, params.mainFilePath);
|
||||
return new Mvs(data);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
/** Params for the `LoadMvsData` action */
|
||||
const LoadMvsDataParams = {
|
||||
export const LoadMvsDataParams = {
|
||||
replaceExisting: PD.Boolean(false, { description: 'If true, the loaded MVS view will replace the current state; if false, the MVS view will be added to the current state.' }),
|
||||
};
|
||||
|
||||
@@ -75,3 +88,73 @@ export const MVSJFormatProvider: DataFormatProvider<{}, StateObjectRef<Mvs>, any
|
||||
return await plugin.state.data.applyAction(LoadMvsData, params, ref).run();
|
||||
},
|
||||
});
|
||||
|
||||
/** Data format provider for MVSX format.
|
||||
* If Visuals:On, it will load the parsed MVS view;
|
||||
* otherwise it will just create a plugin state object with parsed data. */
|
||||
export const MVSXFormatProvider: DataFormatProvider<{}, StateObjectRef<Mvs>, any> = DataFormatProvider({
|
||||
label: 'MVSX',
|
||||
description: 'MVSX',
|
||||
category: 'Miscellaneous',
|
||||
binaryExtensions: ['mvsx'],
|
||||
parse: async (plugin, data) => {
|
||||
return plugin.state.data.build().to(data).apply(ParseMVSX).commit();
|
||||
},
|
||||
visuals: MVSJFormatProvider.visuals,
|
||||
});
|
||||
|
||||
|
||||
/** Parse binary data `data` as MVSX archive,
|
||||
* add all contained files to `plugin`'s asset manager,
|
||||
* and parse the main file in the archive as MVSJ.
|
||||
* Return parsed MVS data and `sourceUrl` for resolution of relative URIs. */
|
||||
export async function loadMVSX(plugin: PluginContext, runtimeCtx: RuntimeContext, data: Uint8Array, mainFilePath: string = 'index.mvsj'): Promise<{ mvsData: MVSData, sourceUrl: string }> {
|
||||
const archiveId = `ni,fnv1a;${hashFnv32a(data)}`;
|
||||
let files: { [path: string]: Uint8Array };
|
||||
try {
|
||||
files = await unzip(runtimeCtx, data) as typeof files;
|
||||
} catch (err) {
|
||||
plugin.log.error('Invalid MVSX file');
|
||||
throw err;
|
||||
}
|
||||
for (const path in files) {
|
||||
const url = arcpUri(archiveId, path);
|
||||
ensureUrlAsset(plugin.managers.asset, url, files[path]);
|
||||
}
|
||||
const mainFile = files[mainFilePath];
|
||||
if (!mainFile) throw new Error(`File ${mainFilePath} not found in the MVSX archive`);
|
||||
const mvsData = MVSData.fromMVSJ(decodeUtf8(mainFile));
|
||||
const sourceUrl = arcpUri(archiveId, mainFilePath);
|
||||
return { mvsData, sourceUrl };
|
||||
}
|
||||
|
||||
/** If the PluginStateObject `pso` comes from a Download transform, try to get its `url` parameter. */
|
||||
function tryGetDownloadUrl(pso: SO.Data.String, plugin: PluginContext): string | undefined {
|
||||
const theCell = plugin.state.data.selectQ(q => q.ofTransformer(Download)).find(cell => cell.obj === pso);
|
||||
const urlParam = theCell?.transform.params?.url;
|
||||
return urlParam ? Asset.getUrl(urlParam) : undefined;
|
||||
}
|
||||
|
||||
/** Return a URI referencing a file within an archive, using ARCP scheme (https://arxiv.org/pdf/1809.06935.pdf).
|
||||
* `archiveId` corresponds to the `authority` part of URI (e.g. 'uuid,EYVwjDiZhM20PWbF1OWWvQ' or 'ni,fnv1a;938511930')
|
||||
* `path` corresponds to the path to a file within the archive */
|
||||
function arcpUri(archiveId: string, path: string): string {
|
||||
return new URL(path, `arcp://${archiveId}/`).href;
|
||||
}
|
||||
|
||||
/** Add a URL asset to asset manager.
|
||||
* Skip if an asset with the same URL already exists. */
|
||||
function ensureUrlAsset(manager: AssetManager, url: string, data: Uint8Array) {
|
||||
const asset = Asset.getUrlAsset(manager, url);
|
||||
if (!manager.has(asset)) {
|
||||
const filename = url.split('/').pop() ?? 'file';
|
||||
manager.set(asset, new File([data], filename));
|
||||
}
|
||||
}
|
||||
|
||||
/** Decode bytes to text using UTF-8 encoding */
|
||||
function decodeUtf8(bytes: Uint8Array): string {
|
||||
_decoder ??= new TextDecoder();
|
||||
return _decoder.decode(bytes);
|
||||
}
|
||||
let _decoder: TextDecoder | undefined;
|
||||
|
||||
@@ -22,6 +22,9 @@ export function Unzip(buf: ArrayBuffer, onlyNames = false) {
|
||||
export async function unzip(runtime: RuntimeContext, buf: ArrayBuffer, onlyNames = false) {
|
||||
const out: { [k: string]: Uint8Array | { size: number, csize: number } } = Object.create(null);
|
||||
const data = new Uint8Array(buf);
|
||||
if (readUshort(data, 0) !== 0x4b50) {
|
||||
throw new Error('Invalid ZIP file. A valid ZIP file must start with two magic bytes \\x50\\x4b ("PK" in ASCII).');
|
||||
}
|
||||
let eocd = data.length - 4;
|
||||
|
||||
while (readUint(data, eocd) !== 0x06054b50) eocd--;
|
||||
|
||||
Reference in New Issue
Block a user