Skip to content

Commit 114c4f0

Browse files
committed
Merge branch 'development'
2 parents 6694479 + 23cd228 commit 114c4f0

9 files changed

Lines changed: 449 additions & 201 deletions

File tree

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,4 +62,4 @@ jobs:
6262
flake8 --per-file-ignores="__init__.py:F401" --ignore E402,E501,W503 $(git ls-files 'app/*.py') .
6363
- name: Run test
6464
run: |
65-
python3 -m pytest -p no:warnings --ignore=lwreg/lwreg/test-cli.py --ignore=lwreg/lwreg/test_lwreg.py
65+
python3 -m pytest -p no:warnings --ignore=lwreg/lwreg/test-cli.py --ignore=lwreg/lwreg/test_lwreg.py --ignore=lwreg/lwreg/test_dbutils.py

Dockerfile

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM continuumio/miniconda3 AS nmrkit-ms
1+
FROM continuumio/miniconda3:24.1.2-0 AS nmrkit-ms
22

33
ENV PYTHON_VERSION=3.10
44
ENV OPENBABEL_VERSION=v3.1
@@ -8,10 +8,12 @@ ENV RELEASE_VERSION=${RELEASE_VERSION}
88

99
# Install runtime dependencies
1010
RUN apt-get update && \
11-
apt-get install -y software-properties-common && \
12-
apt-get update -y && \
13-
apt-get install -y openjdk-11-jre && \
14-
apt-get install -y curl && \
11+
apt-get install -y --no-install-recommends \
12+
software-properties-common \
13+
openjdk-17-jre \
14+
curl && \
15+
apt-get clean && \
16+
rm -rf /var/lib/apt/lists/* && \
1517
conda update -n base -c defaults conda
1618

1719
RUN apt-get update && apt-get -y install docker.io
@@ -23,7 +25,7 @@ RUN pip3 install rdkit
2325

2426
RUN python3 -m pip install -U pip
2527

26-
ENV JAVA_HOME /usr/lib/jvm/java-11-openjdk-amd64/
28+
ENV JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64/
2729
RUN export JAVA_HOME
2830

2931
RUN git clone "https://github.com/rinikerlab/lightweight-registration.git" lwreg

app/routers/spectra.py

Lines changed: 213 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1-
from fastapi import APIRouter, HTTPException, status, UploadFile
1+
from fastapi import APIRouter, HTTPException, status, UploadFile, File, Form
22
from app.schemas import HealthCheck
3+
from pydantic import BaseModel, HttpUrl, Field
34
import subprocess
5+
import tempfile
6+
import os
7+
import json
8+
from pathlib import Path
49

510
router = APIRouter(
611
prefix="/spectra",
@@ -9,6 +14,26 @@
914
responses={404: {"description": "Not Found"}},
1015
)
1116

17+
# Container name for nmr-cli (from docker-compose.yml)
18+
NMR_CLI_CONTAINER = "nmr-converter"
19+
20+
21+
class UrlParseRequest(BaseModel):
22+
"""Request model for parsing spectra from URL"""
23+
url: HttpUrl = Field(..., description="URL of the spectra file")
24+
capture_snapshot: bool = Field(
25+
False,
26+
description="Generate an image snapshot of the spectra"
27+
)
28+
auto_processing: bool = Field(
29+
False,
30+
description="Enable automatic processing of spectrum (FID → FT spectra)"
31+
)
32+
auto_detection: bool = Field(
33+
False,
34+
description="Enable ranges and zones automatic detection"
35+
)
36+
1237

1338
@router.get("/", include_in_schema=False)
1439
@router.get(
@@ -33,42 +58,205 @@ def get_health() -> HealthCheck:
3358
return HealthCheck(status="OK")
3459

3560

61+
def run_command(
62+
file_path: str = None,
63+
url: str = None,
64+
capture_snapshot: bool = False,
65+
auto_processing: bool = False,
66+
auto_detection: bool = False,
67+
) -> dict:
68+
"""Execute nmr-cli command in Docker container"""
69+
70+
cmd = ["nmr-cli", "parse-spectra"]
71+
72+
if url:
73+
cmd.extend(["-u", url])
74+
elif file_path:
75+
cmd.extend(["-p", file_path])
76+
77+
if capture_snapshot:
78+
cmd.append("-s")
79+
if auto_processing:
80+
cmd.append("-p")
81+
if auto_detection:
82+
cmd.append("-d")
83+
84+
try:
85+
result = subprocess.run(
86+
["docker", "exec", NMR_CLI_CONTAINER] + cmd,
87+
capture_output=True,
88+
text=False,
89+
timeout=120
90+
)
91+
except subprocess.TimeoutExpired:
92+
raise HTTPException(
93+
status_code=408,
94+
detail="Processing timeout exceeded"
95+
)
96+
except FileNotFoundError:
97+
raise HTTPException(
98+
status_code=500,
99+
detail="Docker not found or nmr-converter container not running."
100+
)
101+
102+
if result.returncode != 0:
103+
error_msg = result.stderr.decode(
104+
"utf-8") if result.stderr else "Unknown error"
105+
raise HTTPException(
106+
status_code=422,
107+
detail=f"NMR CLI error: {error_msg}"
108+
)
109+
110+
# Parse output
111+
try:
112+
return json.loads(result.stdout.decode("utf-8"))
113+
except json.JSONDecodeError as e:
114+
raise HTTPException(
115+
status_code=500,
116+
detail=f"Invalid JSON from NMR CLI: {e}"
117+
)
118+
119+
120+
def copy_file_to_container(local_path: str, container_path: str) -> None:
121+
"""Copy a file to the nmr-converter container."""
122+
try:
123+
subprocess.run(
124+
["docker", "cp", local_path,
125+
f"{NMR_CLI_CONTAINER}:{container_path}"],
126+
check=True,
127+
capture_output=True,
128+
timeout=30
129+
)
130+
except subprocess.CalledProcessError as e:
131+
error_msg = e.stderr.decode("utf-8") if e.stderr else "Unknown error"
132+
raise HTTPException(
133+
status_code=500,
134+
detail=f"Failed to copy file to container: {error_msg}"
135+
)
136+
137+
138+
def remove_file_from_container(container_path: str) -> None:
139+
"""Remove a file from the nmr-converter container."""
140+
try:
141+
subprocess.run(
142+
["docker", "exec", NMR_CLI_CONTAINER, "rm", "-f", container_path],
143+
capture_output=True,
144+
timeout=10
145+
)
146+
except Exception:
147+
pass
148+
149+
36150
@router.post(
37-
"/parse",
151+
"/parse/file",
38152
tags=["spectra"],
39-
summary="Parse the input spectra format and extract metadata",
40-
response_description="",
153+
summary="Parse spectra from uploaded file",
154+
response_description="Spectra data in JSON format",
41155
status_code=status.HTTP_200_OK,
42156
)
43-
async def parse_spectra(file: UploadFile):
157+
async def parse_spectra_from_file(
158+
file: UploadFile = File(..., description="Upload a spectra file"),
159+
capture_snapshot: bool = Form(
160+
False,
161+
description="Generate an image snapshot of the spectra"
162+
),
163+
auto_processing: bool = Form(
164+
False,
165+
description="Enable automatic processing of spectrum (FID → FT spectra)"
166+
),
167+
auto_detection: bool = Form(
168+
False,
169+
description="Enable ranges and zones automatic detection"
170+
),
171+
):
44172
"""
45-
## Parse the spectra file and extract meta-data
46-
Endpoint uses NMR-load-save to read the input spectra file (.jdx,.nmredata,.dx) and extracts metadata
173+
## Parse spectra from uploaded file
174+
175+
Upload a spectra file along with processing options using multipart/form-data.
176+
177+
Processing Options:
178+
- `capture_snapshot (s)` : Capture snapshot of the spectra
179+
- `auto_processing (p)` : Enable automatic processing of spectrum (FID → FT spectra)
180+
- `auto_detection (d)` : Enable ranges and zones automatic detection
47181
48182
Returns:
49-
data: spectra data in JSON format
183+
Spectra data in JSON format
50184
"""
185+
186+
local_tmp_path = None
187+
container_tmp_path = None
188+
51189
try:
52-
contents = file.file.read()
53-
file_path = "/tmp/" + file.filename
54-
with open(file_path, "wb") as f:
55-
f.write(contents)
56-
p = subprocess.Popen(
57-
"npx nmr-cli -p " + file_path, stdout=subprocess.PIPE, shell=True
190+
contents = await file.read()
191+
192+
with tempfile.NamedTemporaryFile(
193+
delete=False,
194+
suffix=Path(file.filename).suffix
195+
) as tmp_file:
196+
tmp_file.write(contents)
197+
local_tmp_path = tmp_file.name
198+
199+
container_tmp_path = f"/tmp/{Path(local_tmp_path).name}"
200+
201+
# Copy file to nmr-converter container
202+
copy_file_to_container(local_tmp_path, container_tmp_path)
203+
204+
# Run nmr-cli and get JSON output
205+
return run_command(
206+
file_path=container_tmp_path,
207+
capture_snapshot=capture_snapshot,
208+
auto_processing=auto_processing,
209+
auto_detection=auto_detection,
58210
)
59-
(output, err) = p.communicate()
60-
p_status = p.wait()
61-
return output
211+
212+
except HTTPException:
213+
raise
62214
except Exception as e:
63215
raise HTTPException(
64216
status_code=422,
65-
detail="Error parsing the structure "
66-
+ e.message
67-
+ ". Error: "
68-
+ err
69-
+ ". Status:"
70-
+ p_status,
71-
headers={"X-Error": "RDKit molecule input parse error"},
217+
detail=f"Error parsing the spectra file: {e}"
72218
)
73219
finally:
74-
file.file.close()
220+
if local_tmp_path and os.path.exists(local_tmp_path):
221+
os.unlink(local_tmp_path)
222+
if container_tmp_path:
223+
remove_file_from_container(container_tmp_path)
224+
await file.close()
225+
226+
227+
@router.post(
228+
"/parse/url",
229+
tags=["spectra"],
230+
summary="Parse spectra from URL",
231+
response_description="Spectra data in JSON format",
232+
status_code=status.HTTP_200_OK,
233+
)
234+
async def parse_spectra_from_url(request: UrlParseRequest):
235+
"""
236+
Parse spectra from URL
237+
238+
Provide a URL to a spectra file along with processing options using JSON body.
239+
240+
Processing Options:
241+
- `capture_snapshot (s)` : Capture snapshot of the spectra
242+
- `auto_processing (p)` : Enable automatic processing of spectrum (FID → FT spectra)
243+
- `auto_detection (d)` : Enable ranges and zones automatic detection
244+
245+
Returns:
246+
Spectra data in JSON format
247+
"""
248+
try:
249+
return run_command(
250+
url=str(request.url),
251+
capture_snapshot=request.capture_snapshot,
252+
auto_processing=request.auto_processing,
253+
auto_detection=request.auto_detection,
254+
)
255+
256+
except HTTPException:
257+
raise
258+
except Exception as e:
259+
raise HTTPException(
260+
status_code=422,
261+
detail=f"Error parsing spectra from URL: {e}"
262+
)

app/scripts/nmr-cli/.dockerignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package-lock.json
2+
node_modules/
3+
build/
4+
5+
Dockerfile*
6+
.dockerignore
7+
.gitignore

app/scripts/nmr-cli/Dockerfile

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,22 @@ WORKDIR /app
1212
ENV BASE_NMRIUM_URL=https://nmriumdev.nmrxiv.org/
1313
ENV NMR_PREDICTION_URL=https://nmrshiftdb.nmr.uni-koeln.de/NmrshiftdbServlet/nmrshiftdbaction/quickcheck
1414

15+
# Install Java (OpenJDK 17 for Ubuntu Noble)
16+
RUN apt-get update && \
17+
apt-get install -y --no-install-recommends openjdk-17-jre && \
18+
apt-get clean && \
19+
rm -rf /var/lib/apt/lists/*
20+
1521
COPY package.json ./
16-
COPY package-lock.json ./
1722

1823
RUN npm install
1924

2025
COPY . ./
2126

2227
RUN npm run build
2328

24-
#install the nmr-cli as a global package
25-
# for example, nmr-cli parse-spectra -u https://cheminfo.github.io/bruker-data-test/data/zipped/aspirin-1h.zip
29+
# Install the nmr-cli as a global package
30+
# For example, nmr-cli parse-spectra -u https://cheminfo.github.io/bruker-data-test/data/zipped/aspirin-1h.zip
2631
# nmr-cli predict -n "1H" --id 1 --type "nmr;1H;1d" --shifts "1" --solvent "Dimethylsulphoxide-D6 (DMSO-D6, C2D6SO)" -m $"\n Ketcher 6122516162D 1 1.00000 0.00000 0\n\n 16 17 0 0 0 0 0 0 0 0999 V2000\n 1.1954 -4.6484 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0\n 2.9258 -4.6479 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0\n 2.0622 -4.1483 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0\n 2.9258 -5.6488 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0\n 1.1954 -5.6533 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0\n 2.0644 -6.1483 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0\n 3.7902 -4.1495 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0\n 4.6574 -4.6498 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0\n 3.7964 -6.1512 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0\n 4.6596 -5.6458 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0\n 5.5228 -4.1488 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0\n 5.5277 -6.1421 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0\n 6.3895 -4.6477 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0\n 5.5216 -3.1488 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0\n 7.2548 -4.1466 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0\n 8.1215 -4.6455 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0\n 3 1 2 0 0 0 0\n 1 5 1 0 0 0 0\n 5 6 2 0 0 0 0\n 6 4 1 0 0 0 0\n 4 2 1 0 0 0 0\n 2 3 1 0 0 0 0\n 4 9 1 0 0 0 0\n 9 10 2 0 0 0 0\n 10 8 1 0 0 0 0\n 8 7 2 0 0 0 0\n 7 2 1 0 0 0 0\n 8 11 1 0 0 0 0\n 10 12 1 0 0 0 0\n 11 13 1 0 0 0 0\n 11 14 2 0 0 0 0\n 13 15 1 0 0 0 0\n 15 16 1 0 0 0 0\nM END"
2732
RUN npm install . -g
2833

0 commit comments

Comments
 (0)