Skip to content

Commit

Permalink
feat: added file upload option to ocsr routes (fixes #328) (#365)
Browse files Browse the repository at this point in the history
* feat: added file upload option to ocsr routes (fixes #365 

---------

Co-authored-by: NishaSharma14 <sharmanis14@gmail.com>
Co-authored-by: Nisha Sharma <54287612+NishaSharma14@users.noreply.github.com>
  • Loading branch information
3 people authored Sep 4, 2023
1 parent 9c6630c commit 66e5e5e
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 28 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<p align="center"><a href="https://api.naturalproducts.net/" target="_blank"><img src="/public/img/logo.png" width="400" alt="CMS Logo"></a></p>
<p align="center"><a href="https://api.naturalproducts.net/" target="_blank"><img src="/public/img/logo.png" width="400" alt="CMS Logo"></a></p>

[![License](https://img.shields.io/badge/License-MIT%202.0-blue.svg)](https://opensource.org/licenses/MIT)
[![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-blue.svg)](https://GitHub.com/Steinbeck-Lab/cheminformatics-python-microservice/graphs/commit-activity)
Expand Down
19 changes: 19 additions & 0 deletions app/modules/decimer.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,22 @@ def getPredictedSegments(path: str) -> str:
smiles_predicted.append(smiles)
os.remove(segment_path)
return ".".join(smiles_predicted)


def getPredictedSegmentsFromFile(content: any, filename: str) -> tuple:
"""
Takes an image file path and returns a set of paths and image names of segmented images.
Args:
input_path (str): the path of an image.
Returns:
image_name (str): image file name.
segments (list): a set of segmented images.
"""

with open(filename, "wb") as f:
f.write(content)
smiles = getPredictedSegments(filename)
os.remove(filename)
return smiles
108 changes: 81 additions & 27 deletions app/routers/ocsr.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import os
import requests

import uuid
from fastapi.responses import JSONResponse
from urllib.request import urlopen
from urllib.parse import urlsplit
from fastapi import Body, APIRouter, status, HTTPException
from app.modules.decimer import getPredictedSegments
from app.modules.decimer import getPredictedSegmentsFromFile
from app.schemas import HealthCheck
from app.schemas.error import ErrorResponse, BadRequestModel, NotFoundModel
from app.schemas.ocsr_schema import ExtractChemicalInfoResponse
from fastapi import UploadFile

router = APIRouter(
prefix="/ocsr",
Expand Down Expand Up @@ -59,63 +59,117 @@ def get_health() -> HealthCheck:
422: {"description": "Unprocessable Entity", "model": ErrorResponse},
},
)
async def extract_chemicalInfo(
async def Extract_ChemicalInfo_From_File(
path: str = Body(
None,
embed=True,
description="URL or local file path to the chemical structure depiction image.",
openapi_examples={
"example1": {
"summary": "Cheminformatics - Article example image",
"value": "https://static-content.springer.com/image/art%3A10.1186%2Fs13321-023-00744-6/MediaObjects/13321_2023_744_Figa_HTML.png",
},
"example2": {
"summary": "Cheminformatics - Article example image",
"value": "https://static-content.springer.com/image/art%3A10.1186%2Fs13321-023-00743-7/MediaObjects/13321_2023_743_Figa_HTML.png",
},
},
),
reference: str = Body(None, embed=True, description="Reference information."),
img: str = Body(
None, embed=True, description="URL of the chemical structure depiction image."
None,
embed=True,
description="Bytes content of the chemical structure depiction image.",
),
):
"""
Detect, segment and convert a chemical structure depiction into a SMILES string using the DECIMER modules.
Parameters:
- **Images**: required (str): URL or local file path to the chemical structure depiction image.
- **path**: optional if img is provided (str): URL or local file path to the chemical structure depiction image.
- **reference**: optional (str): URL or local file path to the chemical structure depiction image.
- **img**: optional if a valid path is provided (str): URL or local file path to the chemical structure depiction image.
Returns:
- JSONResponse: A JSON response containing the extracted SMILES and the reference (if provided).
Raises:
- HTTPException: If the 'path' parameter is not provided or if it is an invalid URL or file path.
- HTTPException: If the 'img' parameter is provided, but the URL is not accessible.
- HTTPException: If the 'img' parameter is provided, but the content is not accessible.
"""

split = urlsplit(path)
filename = "/tmp/" + split.path.split("/")[-1]
if img:
try:
filename = "/tmp/" + str(uuid.uuid4())
response = urlopen(img)
with open(filename, "wb") as f:
f.write(response.file.read())
smiles = getPredictedSegments(filename)
os.remove(filename)
return JSONResponse(
content={"reference": reference, "smiles": smiles.split(".")}
)
smiles = getPredictedSegmentsFromFile(response.file.read(), filename)
return JSONResponse(
content={"reference": reference, "smiles": smiles.split(".")}
)
except Exception as e:
raise HTTPException(
status_code=400, detail="Error accessing image URL: " + str(e)
status_code=400, detail="Error accessing image content: " + str(e)
)
else:
try:
split = urlsplit(path)
filename = "/tmp/" + split.path.split("/")[-1]
response = requests.get(path)
if response.status_code == 200:
with open(filename, "wb") as f:
f.write(response.content)
smiles = getPredictedSegments(filename)
os.remove(filename)
return JSONResponse(
content={"reference": reference, "smiles": smiles.split(".")}
)
else:
smiles = getPredictedSegmentsFromFile(response.content, filename)
return JSONResponse(
content={"reference": reference, "smiles": smiles.split(".")}
)
except Exception as e:
raise HTTPException(
status_code=400, detail="Invalid URL or file path: " + str(e)
)


@router.post(
"/process-upload",
summary="Detect, segment and convert a chemical structure depiction in the uploaded file into a SMILES string using the DECIMER",
responses={
200: {
"description": "Successful response",
"model": ExtractChemicalInfoResponse,
},
400: {"description": "Bad Request", "model": BadRequestModel},
404: {"description": "Not Found", "model": NotFoundModel},
422: {"description": "Unprocessable Entity", "model": ErrorResponse},
},
)
async def Extract_ChemicalInfo(file: UploadFile):
"""
Detect, segment and convert a chemical structure depiction in the uploaded image file into a SMILES string using the DECIMER modules.
Parameters:
- **file**: required (File):
Returns:
- JSONResponse: A JSON response containing the extracted SMILES and the reference (if provided).
Raises:
- HTTPException: If the 'path' parameter is not provided or if it is an invalid URL or file path.
- HTTPException: If the 'img' parameter is provided, but the URL is not accessible.
"""

if file:
filename = file.filename
try:
contents = file.file.read()
try:
smiles = getPredictedSegmentsFromFile(contents, filename)
return JSONResponse(
content={"reference": None, "smiles": smiles.split(".")}
)
except Exception as e:
raise HTTPException(
status_code=400, detail="Error accessing provided URL"
status_code=400, detail="Error accessing image URL: " + str(e)
)
except Exception as e:
raise HTTPException(
status_code=400, detail="Invalid URL or file path: " + str(e)
status_code=400, detail="Error accessing image URL: " + str(e)
)
finally:
file.file.close()
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@ pillow-heif==0.10.0
selfies>=2.1.1
httpx>=0.24.1
keras_preprocessing==1.1.2
python-multipart

0 comments on commit 66e5e5e

Please sign in to comment.