diff --git a/app/main.py b/app/main.py index a6955a8..221704b 100644 --- a/app/main.py +++ b/app/main.py @@ -2,7 +2,7 @@ from fastapi.responses import RedirectResponse from fastapi_versioning import VersionedFastAPI -from .routers import chem, converters, decimer +from .routers import chem, converters, depict, ocsr from fastapi.middleware.cors import CORSMiddleware from prometheus_fastapi_instrumentator import Instrumentator @@ -34,7 +34,8 @@ app.include_router(chem.router) app.include_router(converters.router) -app.include_router(decimer.router) +app.include_router(depict.router) +app.include_router(ocsr.router) app = VersionedFastAPI( app, diff --git a/app/modules/depict.py b/app/modules/depiction.py similarity index 100% rename from app/modules/depict.py rename to app/modules/depiction.py diff --git a/app/modules/toolkits/rdkitmodules.py b/app/modules/toolkits/rdkitmodules.py index 52d0ba0..57a17c7 100644 --- a/app/modules/toolkits/rdkitmodules.py +++ b/app/modules/toolkits/rdkitmodules.py @@ -212,3 +212,20 @@ def get2Dmol(smiles: str): return molfile else: return "Error reading SMILES string, check again." + + +def getRDKitCXSMILES(smiles: str): + """This function takes an input as a SMILES string and + returns a CXSMILES with coordinates. + Args (str): SMILES string. + Returns (str): CXSMILES with coordinates. + """ + if any(char.isspace() for char in smiles): + smiles = smiles.replace(" ", "+") + mol = Chem.MolFromSmiles(smiles) + + if mol: + AllChem.Compute2DCoords(mol) + return Chem.MolToCXSmiles(mol) + else: + return "Error reading SMILES string, check again." diff --git a/app/routers/chem.py b/app/routers/chem.py index c9ed74b..f4c149f 100644 --- a/app/routers/chem.py +++ b/app/routers/chem.py @@ -1,4 +1,4 @@ -from fastapi import Body, Request, APIRouter +from fastapi import Body, APIRouter from typing import Optional from typing_extensions import Annotated from rdkit import Chem @@ -6,16 +6,14 @@ EnumerateStereoisomers, ) from chembl_structure_pipeline import standardizer, checker -from fastapi.responses import Response, HTMLResponse, JSONResponse +from fastapi.responses import Response, JSONResponse from app.modules.npscorer import getNPScore from app.modules.classyfire import classify, result from app.modules.toolkits.cdkmodules import ( getTanimotoSimilarityCDK, getCDKHOSECodes, ) -from app.modules.depict import getRDKitDepiction, getCDKDepiction from app.modules.toolkits.rdkitmodules import ( - get3Dconformers, getTanimotoSimilarityRDKit, getRDKitHOSECodes, ) @@ -148,24 +146,6 @@ async def ClassyFire_result(id: str): return data -@router.get("/rdkit3d") -async def RDKit3D_Mol(smiles: str): - """ - Generate 3D Coordinates using RDKit and return the mol block. - - - **SMILES**: required (query) - """ - if smiles: - mol = Chem.MolFromSmiles(smiles) - if mol: - return Response( - content=get3Dconformers(smiles).replace("$$$$\n", ""), - media_type="text/plain", - ) - else: - return "Error reading SMILES string, check again." - - @router.get("/tanimoto") async def Tanimoto_Similarity(smiles: str, toolkit: Optional[str] = "cdk"): """ @@ -194,38 +174,6 @@ async def Tanimoto_Similarity(smiles: str, toolkit: Optional[str] = "cdk"): return 'Please give a SMILES pair with "," separated. (Example: api.naturalproducts.net/chem/tanimoto?smiles=CN1C=NC2=C1C(=O)N(C(=O)N2C)C,CN1C=NC2=C1C(=O)NC(=O)N2C)' -@router.get("/depict") -async def Depict2D_molecule( - smiles: str, - generator: Optional[str] = "cdksdg", - width: Optional[int] = 512, - height: Optional[int] = 512, - rotate: Optional[int] = 0, - CIP: Optional[bool] = False, - unicolor: Optional[bool] = False, -): - """ - Generate 2D Depictions using CDK or RDKit using given parameters. - - - **SMILES**: required (query) - - **generator**: optional (defaults: cdk) - - **width**: optional (defaults: 512) - - **height**: optional (defaults: 512) - - **rotate**: optional (defaults: 0) - """ - if generator: - if generator == "cdksdg": - return Response( - content=getCDKDepiction(smiles, [width, height], rotate, CIP, unicolor), - media_type="image/svg+xml", - ) - else: - return Response( - content=getRDKitDepiction(smiles, [width, height], rotate), - media_type="image/svg+xml", - ) - - @router.get("/checkerrors") async def Check_Errors(smiles: str, fix: Optional[bool] = False): """ @@ -272,27 +220,25 @@ async def Check_Errors(smiles: str, fix: Optional[bool] = False): return "Error reading SMILES string, check again." -@router.get("/depict3D", response_class=HTMLResponse) -async def Depict3D_Molecule( - request: Request, +@router.get("/hosecode") +async def HOSE_Codes( smiles: str, + spheres: int, + toolkit: Optional[str] = "cdk", + ringsize: Optional[bool] = False, ): """ - Generate 3D Depictions using RDKit. + Generates HOSE Codes using CDK/RDKit. - **SMILES**: required (query) + - **spheres**: required (query) + - **toolkit**: Optional (default:CDK) + - **ringsize**: Optional (default:False) """ if smiles: - content = {"request": request, "molecule": get3Dconformers(smiles)} - return templates.TemplateResponse("mol.html", content) - - -@router.get("/hosecode") -async def HOSE_Codes(framework: str, smiles: str, spheres: int, ringsize: bool = False): - if smiles: - if framework == "cdk": + if toolkit == "cdk": return await getCDKHOSECodes(smiles, spheres, ringsize) - elif framework == "rdkit": + elif toolkit == "rdkit": return await getRDKitHOSECodes(smiles, spheres) else: return "Error reading SMILES string, check again." diff --git a/app/routers/converters.py b/app/routers/converters.py index 9a2354c..7da366c 100644 --- a/app/routers/converters.py +++ b/app/routers/converters.py @@ -11,7 +11,11 @@ getCanonSMILES, getInChI, ) -from app.modules.toolkits.rdkitmodules import get3Dconformers, get2Dmol +from app.modules.toolkits.rdkitmodules import ( + get3Dconformers, + get2Dmol, + getRDKitCXSMILES, +) from app.modules.toolkits.openbabelmodules import ( getOBMol, getOBCanonicalSMILES, @@ -124,12 +128,12 @@ async def SMILES_to_InChI(smiles: str, generator: Optional[str] = "cdk"): if generator: if generator == "cdk": return str(getInChI(smiles)) - elif generator == "rdkit": - mol = Chem.MolFromSmiles(smiles) - if mol: - return Chem.inchi.MolToInchi(mol) - elif generator == "openbabel": - return getOBInChI(smiles) + elif generator == "rdkit": + mol = Chem.MolFromSmiles(smiles) + if mol: + return Chem.inchi.MolToInchi(mol) + elif generator == "openbabel": + return getOBInChI(smiles) else: return "Error reading SMILES string check again." else: @@ -149,12 +153,12 @@ async def SMILES_to_InChIKey(smiles: str, generator: Optional[str] = "cdk"): if generator: if generator == "cdk": return str(getInChI(smiles, InChIKey=True)) - elif generator == "rdkit": - mol = Chem.MolFromSmiles(smiles) - if mol: - return Chem.inchi.MolToInchiKey(mol) - elif generator == "openbabel": - return getOBInChI(smiles, InChIKey=True) + elif generator == "rdkit": + mol = Chem.MolFromSmiles(smiles) + if mol: + return Chem.inchi.MolToInchiKey(mol) + elif generator == "openbabel": + return getOBInChI(smiles, InChIKey=True) else: return "Error reading SMILES string check again." else: @@ -162,7 +166,7 @@ async def SMILES_to_InChIKey(smiles: str, generator: Optional[str] = "cdk"): @router.get("/cxsmiles") -async def SMILES_to_CXSMILES(smiles: str): +async def SMILES_to_CXSMILES(smiles: str, generator: Optional[str] = "cdk"): """ Convert SMILES to CXSMILES: @@ -171,8 +175,12 @@ async def SMILES_to_CXSMILES(smiles: str): if any(char.isspace() for char in smiles): smiles = smiles.replace(" ", "+") if smiles: - cxsmiles = getCXSMILES(smiles) - return cxsmiles + if generator: + if generator == "cdk": + cxsmiles = getCXSMILES(smiles) + return cxsmiles + else: + return getRDKitCXSMILES(smiles) else: return "Error reading SMILES string check again." diff --git a/app/routers/depict.py b/app/routers/depict.py new file mode 100644 index 0000000..d854c58 --- /dev/null +++ b/app/routers/depict.py @@ -0,0 +1,67 @@ +from fastapi import Request, APIRouter +from typing import Optional +from fastapi.responses import Response, HTMLResponse +from app.modules.depiction import getRDKitDepiction, getCDKDepiction +from app.modules.toolkits.rdkitmodules import get3Dconformers +from fastapi.templating import Jinja2Templates + +templates = Jinja2Templates(directory="app/templates") + +router = APIRouter( + prefix="/depict", + tags=["depict"], + dependencies=[], + responses={404: {"description": "Not found"}}, +) + + +@router.get("/") +async def depict_index(): + return {"module": "depict", "message": "Successful", "status": 200} + + +@router.get("/2D") +async def Depict2D_molecule( + smiles: str, + generator: Optional[str] = "cdksdg", + width: Optional[int] = 512, + height: Optional[int] = 512, + rotate: Optional[int] = 0, + CIP: Optional[bool] = False, + unicolor: Optional[bool] = False, +): + """ + Generate 2D Depictions using CDK or RDKit using given parameters. + + - **SMILES**: required (query) + - **generator**: optional (defaults: cdk) + - **width**: optional (defaults: 512) + - **height**: optional (defaults: 512) + - **rotate**: optional (defaults: 0) + """ + if generator: + if generator == "cdksdg": + return Response( + content=getCDKDepiction(smiles, [width, height], rotate, CIP, unicolor), + media_type="image/svg+xml", + ) + else: + return Response( + content=getRDKitDepiction(smiles, [width, height], rotate), + media_type="image/svg+xml", + ) + + +@router.get("/3D", response_class=HTMLResponse) +async def Depict3D_Molecule( + request: Request, + smiles: str, +): + """ + Generate 3D Depictions using RDKit. + + - **SMILES**: required (query) + """ + if smiles: + content = {"request": request, "molecule": get3Dconformers(smiles)} + return templates.TemplateResponse("mol.html", content) diff --git a/app/routers/decimer.py b/app/routers/ocsr.py similarity index 91% rename from app/routers/decimer.py rename to app/routers/ocsr.py index 1fa9a6a..b7f2923 100644 --- a/app/routers/decimer.py +++ b/app/routers/ocsr.py @@ -9,16 +9,16 @@ from app.modules.decimermodules import getPredictedSegments router = APIRouter( - prefix="/decimer", - tags=["decimer"], + prefix="/ocsr", + tags=["ocsr"], dependencies=[], responses={404: {"description": "Not found"}}, ) @router.get("/") -async def DECIMER_Index(): - return {"module": "decimer", "message": "Successful", "status": 200} +async def ocsr_index(): + return {"module": "ocsr", "message": "Successful", "status": 200} @router.post("/process")