From d850fde603960d22cd1637cc5c8a0e2c40cefd9e Mon Sep 17 00:00:00 2001 From: Kohulan Date: Thu, 23 Mar 2023 20:43:24 +0100 Subject: [PATCH 1/4] feat: add CDK descriptors for COCONUT #78 --- app/modules/cdkmodules.py | 97 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 95 insertions(+), 2 deletions(-) diff --git a/app/modules/cdkmodules.py b/app/modules/cdkmodules.py index 494ed42..f0d9eec 100644 --- a/app/modules/cdkmodules.py +++ b/app/modules/cdkmodules.py @@ -30,6 +30,7 @@ jar_path = pystow.ensure("STOUT-V2", url=sru_path) startJVM("-ea", classpath=[cdkjar_path, srujar_path]) + cdk_base = "org.openscience.cdk" def getCDKSDG(smiles: str): @@ -40,7 +41,6 @@ def getCDKSDG(smiles: str): Returns: mol object : mol object with CDK SDG. """ - cdk_base = "org.openscience.cdk" SCOB = JClass(cdk_base + ".silent.SilentChemObjectBuilder") SmilesParser = JClass(cdk_base + ".smiles.SmilesParser")(SCOB.getInstance()) molecule = SmilesParser.parseSmiles(smiles) @@ -59,7 +59,6 @@ def getSugarInfo(smiles: str): Returns: (boolean): True or false values whtehr or not molecule has sugar. """ - cdk_base = "org.openscience.cdk" SCOB = JClass(cdk_base + ".silent.SilentChemObjectBuilder") SmilesParser = JClass(cdk_base + ".smiles.SmilesParser")(SCOB.getInstance()) molecule = SmilesParser.parseSmiles(smiles) @@ -111,3 +110,97 @@ def getCDKSDGMol(smiles: str): SDFW.flush() mol_str = str(StringW.toString()) return mol_str + + +def getCDKDescriptors(smiles: str): + """Take an input SMILES and generate a selected set of molecular + descriptors generated using CDK as a list. + Args (str): SMILES string + Returns (list): a list of calculated descriptors + """ + Mol = getCDKSDG(smiles) + if Mol: + AtomCountDescriptor = ( + JClass(cdk_base + ".qsar.descriptors.molecular.AtomCountDescriptor")() + .calculate(Mol) + .getValue() + ) + HeavyAtomsC = Mol.getAtomCount() + WeightDescriptor = ( + JClass(cdk_base + ".qsar.descriptors.molecular.WeightDescriptor")() + .calculate(Mol) + .getValue() + .toString() + ) + TotalExactMass = JClass( + cdk_base + ".tools.manipulator.AtomContainerManipulator" + ).getTotalExactMass(Mol) + ALogP = ( + JClass(cdk_base + ".qsar.descriptors.molecular.ALOGPDescriptor")() + .calculate(Mol) + .getValue() + ) + NumRotatableBonds = ( + JClass( + cdk_base + ".qsar.descriptors.molecular.RotatableBondsCountDescriptor" + )() + .calculate(Mol) + .getValue() + ) + TPSADescriptor = ( + JClass(cdk_base + ".qsar.descriptors.molecular.TPSADescriptor")() + .calculate(Mol) + .getValue() + .toString() + ) + HBondAcceptorCountDescriptor = ( + JClass( + cdk_base + ".qsar.descriptors.molecular.HBondAcceptorCountDescriptor" + )() + .calculate(Mol) + .getValue() + ) + HBondDonorCountDescriptor = ( + JClass( + cdk_base + ".qsar.descriptors.molecular.HBondAcceptorCountDescriptor" + )() + .calculate(Mol) + .getValue() + ) + RuleOfFiveDescriptor = ( + JClass(cdk_base + ".qsar.descriptors.molecular.RuleOfFiveDescriptor")() + .calculate(Mol) + .getValue() + ) + AromaticRings = None + QEDWeighted = None + FormalCharge = JClass( + cdk_base + ".tools.manipulator.AtomContainerManipulator" + ).getTotalFormalCharge(Mol) + FractionalCSP3Descriptor = ( + JClass(cdk_base + ".qsar.descriptors.molecular.FractionalCSP3Descriptor")() + .calculate(Mol) + .getValue() + .toString() + ) + NumRings = JClass(cdk_base + ".graph.Cycles").mcb(Mol).numberOfCycles() + + return ( + str(AtomCountDescriptor), + HeavyAtomsC, + "{:.2f}".format(float(str(WeightDescriptor))), + "{:.2f}".format(float(str(TotalExactMass))), + "{:.2f}".format(float(str(ALogP).split(",")[0])), + str(NumRotatableBonds), + "{:.2f}".format(float(str(TPSADescriptor))), + str(HBondAcceptorCountDescriptor), + str(HBondDonorCountDescriptor), + str(HBondAcceptorCountDescriptor), + str(HBondDonorCountDescriptor), + str(RuleOfFiveDescriptor), + str(AromaticRings), + str(QEDWeighted), + FormalCharge, + "{:.2f}".format(float(str(FractionalCSP3Descriptor))), + NumRings, + ) From 0e28187ae6a3e64eb1ed8de12aa80d496839058b Mon Sep 17 00:00:00 2001 From: Kohulan Date: Thu, 23 Mar 2023 20:45:24 +0100 Subject: [PATCH 2/4] feat: add All descriptor module for CDK and RDKit #79 --- app/modules/alldescriptors.py | 182 ++++++++++++++++++++++++++++++++++ 1 file changed, 182 insertions(+) create mode 100644 app/modules/alldescriptors.py diff --git a/app/modules/alldescriptors.py b/app/modules/alldescriptors.py new file mode 100644 index 0000000..4f87d6f --- /dev/null +++ b/app/modules/alldescriptors.py @@ -0,0 +1,182 @@ +from rdkit.Chem import Descriptors, QED, Lipinski, rdMolDescriptors, rdmolops +from app.modules.rdkitmodules import checkRo5Violations, checkSMILES +from app.modules.cdkmodules import getCDKSDG, JClass, cdk_base + + +def getAllRDKitDescriptors(smiles: str): + """Take an input SMILES and generate a selected set of molecular + descriptors generated using RDKit as a list. + Args (str): SMILES string + Returns (list): a list of calculated descriptors + """ + mol = checkSMILES(smiles) + if mol: + AtomC = rdMolDescriptors.CalcNumAtoms(mol) + BondC = mol.GetNumBonds() + HeavyAtomsC = rdMolDescriptors.CalcNumHeavyAtoms(mol) + MolWt = "%.2f" % Descriptors.MolWt(mol) + ExactMolWt = "%.2f" % Descriptors.ExactMolWt(mol) + ALogP = "%.2f" % QED.properties(mol).ALOGP + NumRotatableBonds = rdMolDescriptors.CalcNumRotatableBonds(mol) + PSA = "%.2f" % rdMolDescriptors.CalcTPSA(mol) + HBA = Descriptors.NumHAcceptors(mol) + HBD = Descriptors.NumHDonors(mol) + Lipinski_HBA = Lipinski.NumHAcceptors(mol) + Lipinski_HBD = Lipinski.NumHDonors(mol) + Ro5Violations = checkRo5Violations(mol) + AromaticRings = rdMolDescriptors.CalcNumAromaticRings(mol) + QEDWeighted = "%.2f" % QED.qed(mol) + FormalCharge = "%.2f" % rdmolops.GetFormalCharge(mol) + fsp3 = "%.3f" % rdMolDescriptors.CalcFractionCSP3(mol) + NumRings = rdMolDescriptors.CalcNumRings(mol) + return ( + AtomC, + BondC, + HeavyAtomsC, + MolWt, + ExactMolWt, + ALogP, + NumRotatableBonds, + PSA, + HBA, + HBD, + Lipinski_HBA, + Lipinski_HBD, + Ro5Violations, + AromaticRings, + QEDWeighted, + FormalCharge, + fsp3, + NumRings, + ) + else: + return "Error reading SMILES string, check again." + + +def getAllCDKDescriptors(smiles: str): + """Take an input SMILES and generate a selected set of molecular + descriptors generated using CDK as a list. + Args (str): SMILES string + Returns (list): a list of calculated descriptors + """ + Mol = getCDKSDG(smiles) + if Mol: + AtomCountDescriptor = ( + JClass(cdk_base + ".qsar.descriptors.molecular.AtomCountDescriptor")() + .calculate(Mol) + .getValue() + ) + BondCountDescriptor = JClass(cdk_base+".qsar.descriptors.molecular.BondCountDescriptor")().calculate(Mol).getValue() + HeavyAtomsC = Mol.getAtomCount() + WeightDescriptor = ( + JClass(cdk_base + ".qsar.descriptors.molecular.WeightDescriptor")() + .calculate(Mol) + .getValue().toString() + ) + TotalExactMass = JClass( + cdk_base + ".tools.manipulator.AtomContainerManipulator" + ).getTotalExactMass(Mol) + ALogP = ( + JClass(cdk_base + ".qsar.descriptors.molecular.ALOGPDescriptor")() + .calculate(Mol) + .getValue() + ) + NumRotatableBonds = ( + JClass( + cdk_base + ".qsar.descriptors.molecular.RotatableBondsCountDescriptor" + )() + .calculate(Mol) + .getValue() + ) + TPSADescriptor = ( + JClass(cdk_base + ".qsar.descriptors.molecular.TPSADescriptor")() + .calculate(Mol) + .getValue().toString() + ) + HBondAcceptorCountDescriptor = ( + JClass( + cdk_base + ".qsar.descriptors.molecular.HBondAcceptorCountDescriptor" + )() + .calculate(Mol) + .getValue() + ) + HBondDonorCountDescriptor = ( + JClass( + cdk_base + ".qsar.descriptors.molecular.HBondDonorCountDescriptor" + )() + .calculate(Mol) + .getValue() + ) + RuleOfFiveDescriptor = ( + JClass(cdk_base + ".qsar.descriptors.molecular.RuleOfFiveDescriptor")() + .calculate(Mol) + .getValue() + ) + AromaticRings = None + QEDWeighted = None + FormalCharge = JClass( + cdk_base + ".tools.manipulator.AtomContainerManipulator" + ).getTotalFormalCharge(Mol) + FractionalCSP3Descriptor = ( + JClass(cdk_base + ".qsar.descriptors.molecular.FractionalCSP3Descriptor")() + .calculate(Mol) + .getValue().toString() + ) + NumRings = JClass(cdk_base + ".graph.Cycles").mcb(Mol).numberOfCycles() + + return ( + str(AtomCountDescriptor), + str(BondCountDescriptor), + HeavyAtomsC, + "{:.2f}".format(float(str(WeightDescriptor))), + "{:.2f}".format(float(str(TotalExactMass))), + "{:.2f}".format(float(str(ALogP).split(",")[0])), + str(NumRotatableBonds), + "{:.2f}".format(float(str(TPSADescriptor))), + str(HBondAcceptorCountDescriptor), + str(HBondDonorCountDescriptor), + str(HBondAcceptorCountDescriptor), + str(HBondDonorCountDescriptor), + str(RuleOfFiveDescriptor), + str(AromaticRings), + str(QEDWeighted), + FormalCharge, + "{:.2f}".format(float(str(FractionalCSP3Descriptor))), + NumRings, + ) + + +def getCDKRDKitcombinedDescriptors(smiles: str): + """Take an input SMILES and generates a selected set of molecular + descriptors using CDK and RDKit and returns as a dictionary. + Args (str): SMILES string. + Returns (dist): a dictionary of calculated descriptors. + """ + RDKitDescriptors = getAllRDKitDescriptors(smiles) + CDKDescriptors = getAllCDKDescriptors(smiles) + AllDescriptors = { + "Atom count", + "Bond count", + "Heavy atom count", + "Molecular weight", + "Exact molecular weight", + "ALogP", + "Rotatable bond count", + "Topological polar surface area", + "Hydrogen bond acceptors", + "Hydrogen bond donors", + "Hydrogen bond acceptors(Lipinski)", + "Hydrogen bond donors(Lipinski)", + "Lipinski's rule of five violations", + "Aromatic rings count", + "QED drug likeliness", + "Formal Charge", + "FractionCSP3", + "Number of Minimal Rings", + } + + if len(AllDescriptors) == len(RDKitDescriptors) == len(CDKDescriptors): + combinedDict = dict(zip(AllDescriptors, zip(RDKitDescriptors, CDKDescriptors))) + return combinedDict + else: + return "Error dictionary lenth invalid" From a6db1747bc712c1b6ff317b4dab12b2f3fe3461f Mon Sep 17 00:00:00 2001 From: Kohulan Date: Thu, 23 Mar 2023 20:45:24 +0100 Subject: [PATCH 3/4] feat: add function to COCONUT descriptors based on toolkit #80 --- app/modules/coconutdescriptors.py | 112 +++++++++++++++++------------- app/modules/rdkitmodules.py | 2 +- 2 files changed, 63 insertions(+), 51 deletions(-) diff --git a/app/modules/coconutdescriptors.py b/app/modules/coconutdescriptors.py index 951f773..caabbe2 100644 --- a/app/modules/coconutdescriptors.py +++ b/app/modules/coconutdescriptors.py @@ -1,9 +1,32 @@ -from app.modules.rdkitmodules import getDescriptors, checkSMILES -from app.modules.cdkmodules import getSugarInfo, getMurkoFramework +from app.modules.rdkitmodules import getRDKitDescriptors, checkSMILES +from app.modules.cdkmodules import getSugarInfo, getMurkoFramework, getCDKDescriptors +from app.modules.alldescriptors import getCDKRDKitcombinedDescriptors from app.modules.npscorer import getNPScore +def getDescriptors(smiles: str, toolkit: str): + """This function takes a user input as + SMILES string and decides whether to get + Descriptor values from RDKit or CDK. + Args (str): SMILES input. + Returns (list): Decriptor list as list. + """ + mol = checkSMILES(smiles) + if mol: + if toolkit == "rdkit": + Descriptors = getRDKitDescriptors(smiles) + print("Im here R") + return Descriptors + elif toolkit == "cdk": + print("Im here C") + Descriptors = getCDKDescriptors(smiles) + return Descriptors + else: + return "Error calculating Descriptors" + else: + return "Error reading SMILES check again." -def getCOCONUTDescriptors(smiles: str): + +def getCOCONUTDescriptors(smiles: str, toolkit: str): """This function takes a user input as SMILES string and returns descriptors those are available in COCONUT. Uses @@ -11,55 +34,44 @@ def getCOCONUTDescriptors(smiles: str): Args (str): SMILES input. Returns (dict): Decriptor list as dictionary. """ - mol = checkSMILES(smiles) - if mol: - ( - AtomC, - HeavyAtomsC, - MolWt, - ExactMolWt, - ALogP, - NumRotatableBonds, - PSA, - HBA, - HBD, - Lipinski_HBA, - Lipinski_HBD, - Ro5Violations, - AromaticRings, - QEDWeighted, - FormalCharge, - fsp3, - NumRings, - ) = getDescriptors(smiles) + if toolkit == "all": + AllDescriptors = getCDKRDKitcombinedDescriptors(smiles) + return AllDescriptors + else: + Descriptors = getDescriptors(smiles,toolkit) + hasLinearSugar, hasCircularSugars = getSugarInfo(smiles) framework = getMurkoFramework(smiles) nplikeliness = getNPScore(smiles) + CombinedDescriptors = list(Descriptors) + CombinedDescriptors.extend([hasLinearSugar,hasCircularSugars,framework,nplikeliness]) - AllDescriptors = { - "atom_count": AtomC, - "heavy_atom_count": HeavyAtomsC, - "molecular_weight": MolWt, - "exact molecular_weight": ExactMolWt, - "alogp": ALogP, - "rotatable_bond_count": NumRotatableBonds, - "topological_polar_surface_area": PSA, - "hydrogen_bond_acceptors": HBA, - "hydrogen_bond_donors": HBD, - "hydrogen_bond_acceptors_lipinski": Lipinski_HBA, - "hydrogen_bond_donors_lipinski": Lipinski_HBD, - "lipinski_rule_of_five_violations": Ro5Violations, - "aromatic_rings_count": AromaticRings, - "qed_drug_likeliness": QEDWeighted, - "formal_charge": FormalCharge, - "fractioncsp3": fsp3, - "number_of_minimal_rings": NumRings, - "linear_sugars": hasLinearSugar, - "circular_sugars": hasCircularSugars, - "murko_framework": framework, - "nplikeliness": nplikeliness, - } + DescriptorList = ( + "atom_count", + "heavy_atom_count", + "molecular_weight", + "exactmolecular_weight", + "alogp", + "rotatable_bond_count", + "topological_polar_surface_area", + "hydrogen_bond_acceptors", + "hydrogen_bond_donors", + "hydrogen_bond_acceptors_lipinski", + "hydrogen_bond_donors_lipinski", + "lipinski_rule_of_five_violations", + "aromatic_rings_count", + "qed_drug_likeliness", + "formal_charge", + "fractioncsp3", + "number_of_minimal_rings", + "linear_sugars", + "circular_sugars", + "murko_framework", + "nplikeliness", + ) - return AllDescriptors - else: - return "Error reading SMILES check again." + if len(DescriptorList) == len(CombinedDescriptors): + combinedDict = dict(zip(DescriptorList, zip(CombinedDescriptors))) + return combinedDict + else: + return "Error Calculating Descriptors" diff --git a/app/modules/rdkitmodules.py b/app/modules/rdkitmodules.py index d7b83a2..c0e2554 100644 --- a/app/modules/rdkitmodules.py +++ b/app/modules/rdkitmodules.py @@ -36,7 +36,7 @@ def checkRo5Violations(mol): return num_of_violations -def getDescriptors(smiles: str): +def getRDKitDescriptors(smiles: str): """Take an input SMILES and generate a selected set of molecular descriptors as a dictionary. Args (str): SMILES string From 7e2e0632f22f1bccee02dcbed387a6c67db8b3cc Mon Sep 17 00:00:00 2001 From: Kohulan Date: Thu, 23 Mar 2023 22:14:59 +0100 Subject: [PATCH 4/4] feat: Add table preview & JSON,HTML toggle #76,#77 --- app/modules/alldescriptors.py | 19 ++++++--- app/modules/coconutdescriptors.py | 11 ++--- app/routers/chem.py | 29 ++++++++++--- app/templates/style.css | 71 +++++++++++++++++++++++++++++++ requirements.txt | 3 +- 5 files changed, 114 insertions(+), 19 deletions(-) create mode 100644 app/templates/style.css diff --git a/app/modules/alldescriptors.py b/app/modules/alldescriptors.py index 4f87d6f..7faa232 100644 --- a/app/modules/alldescriptors.py +++ b/app/modules/alldescriptors.py @@ -66,12 +66,17 @@ def getAllCDKDescriptors(smiles: str): .calculate(Mol) .getValue() ) - BondCountDescriptor = JClass(cdk_base+".qsar.descriptors.molecular.BondCountDescriptor")().calculate(Mol).getValue() + BondCountDescriptor = ( + JClass(cdk_base + ".qsar.descriptors.molecular.BondCountDescriptor")() + .calculate(Mol) + .getValue() + ) HeavyAtomsC = Mol.getAtomCount() WeightDescriptor = ( JClass(cdk_base + ".qsar.descriptors.molecular.WeightDescriptor")() .calculate(Mol) - .getValue().toString() + .getValue() + .toString() ) TotalExactMass = JClass( cdk_base + ".tools.manipulator.AtomContainerManipulator" @@ -91,7 +96,8 @@ def getAllCDKDescriptors(smiles: str): TPSADescriptor = ( JClass(cdk_base + ".qsar.descriptors.molecular.TPSADescriptor")() .calculate(Mol) - .getValue().toString() + .getValue() + .toString() ) HBondAcceptorCountDescriptor = ( JClass( @@ -101,9 +107,7 @@ def getAllCDKDescriptors(smiles: str): .getValue() ) HBondDonorCountDescriptor = ( - JClass( - cdk_base + ".qsar.descriptors.molecular.HBondDonorCountDescriptor" - )() + JClass(cdk_base + ".qsar.descriptors.molecular.HBondDonorCountDescriptor")() .calculate(Mol) .getValue() ) @@ -120,7 +124,8 @@ def getAllCDKDescriptors(smiles: str): FractionalCSP3Descriptor = ( JClass(cdk_base + ".qsar.descriptors.molecular.FractionalCSP3Descriptor")() .calculate(Mol) - .getValue().toString() + .getValue() + .toString() ) NumRings = JClass(cdk_base + ".graph.Cycles").mcb(Mol).numberOfCycles() diff --git a/app/modules/coconutdescriptors.py b/app/modules/coconutdescriptors.py index caabbe2..7ff2091 100644 --- a/app/modules/coconutdescriptors.py +++ b/app/modules/coconutdescriptors.py @@ -3,21 +3,20 @@ from app.modules.alldescriptors import getCDKRDKitcombinedDescriptors from app.modules.npscorer import getNPScore + def getDescriptors(smiles: str, toolkit: str): """This function takes a user input as SMILES string and decides whether to get Descriptor values from RDKit or CDK. Args (str): SMILES input. Returns (list): Decriptor list as list. - """ + """ mol = checkSMILES(smiles) if mol: if toolkit == "rdkit": Descriptors = getRDKitDescriptors(smiles) - print("Im here R") return Descriptors elif toolkit == "cdk": - print("Im here C") Descriptors = getCDKDescriptors(smiles) return Descriptors else: @@ -38,13 +37,15 @@ def getCOCONUTDescriptors(smiles: str, toolkit: str): AllDescriptors = getCDKRDKitcombinedDescriptors(smiles) return AllDescriptors else: - Descriptors = getDescriptors(smiles,toolkit) + Descriptors = getDescriptors(smiles, toolkit) hasLinearSugar, hasCircularSugars = getSugarInfo(smiles) framework = getMurkoFramework(smiles) nplikeliness = getNPScore(smiles) CombinedDescriptors = list(Descriptors) - CombinedDescriptors.extend([hasLinearSugar,hasCircularSugars,framework,nplikeliness]) + CombinedDescriptors.extend( + [hasLinearSugar, hasCircularSugars, framework, nplikeliness] + ) DescriptorList = ( "atom_count", diff --git a/app/routers/chem.py b/app/routers/chem.py index 8cebc18..121e473 100644 --- a/app/routers/chem.py +++ b/app/routers/chem.py @@ -12,7 +12,7 @@ from app.modules.depict import getRDKitDepiction, getCDKDepiction from app.modules.rdkitmodules import get3Dconformers from app.modules.coconutdescriptors import getCOCONUTDescriptors - +import pandas as pd from fastapi.templating import Jinja2Templates router = APIRouter( @@ -31,7 +31,7 @@ async def chem_index(): @router.get("/stereoisomers") -async def smiles_stereoisomers(smiles: str): +async def SMILES_stereoisomers(smiles: str): """ Enumerate all possible stereoisomers based on the chiral centers in the given smiles: @@ -72,18 +72,35 @@ async def standardize_mol(request: Request): @router.get("/descriptors") -async def smiles_descriptors(smiles: str): +async def SMILES_descriptors( + smiles: str, format: Optional[str] = "json", toolkit: Optional[str] = "rdkit" +): """ Generate standard descriptors for the input molecules (smiles): - **smiles**: required (query) """ if smiles: - return getCOCONUTDescriptors(smiles) + if format == "html": + data = getCOCONUTDescriptors(smiles, toolkit) + if toolkit == "all": + headers = ["Descriptor name", "RDKit Descriptors", "CDK Descriptors"] + df = pd.DataFrame.from_dict(data, orient="index", columns=headers[1:]) + df.insert(0, headers[0], df.index) + else: + headers = ["Descriptor name", "Values"] + df = pd.DataFrame.from_dict(data, orient="index", columns=headers[1:]) + df.insert(0, headers[0], df.index) + with open("app/templates/style.css", "r") as file: + css_style = file.read() + html_table = df.to_html(index=False) + return Response(content=css_style + html_table, media_type="text/html") + else: + return getCOCONUTDescriptors(smiles, toolkit) @router.get("/npscore") -async def nplikeliness_score(smiles: str): +async def NPlikeliness_score(smiles: str): """ Generate natural product likeliness score based on RDKit implementation @@ -109,7 +126,7 @@ async def classyfire_result(id: str): @router.get("/cdk2d") -async def cdk2d_coordinates(smiles: str): +async def CDK2D_coordinates(smiles: str): if smiles: mol = Chem.MolFromSmiles(smiles) if mol: diff --git a/app/templates/style.css b/app/templates/style.css new file mode 100644 index 0000000..d5a3d33 --- /dev/null +++ b/app/templates/style.css @@ -0,0 +1,71 @@ + \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 3834063..eddd6ee 100644 --- a/requirements.txt +++ b/requirements.txt @@ -16,4 +16,5 @@ efficientnet tensorflow==2.10.0 pyheif==0.7.1 selfies>=2.1.1 -jinja2 \ No newline at end of file +jinja2 +pandas \ No newline at end of file