Skip to content

Commit

Permalink
Merge pull request #91 from ProLint/89-fix-the-command-line-usage
Browse files Browse the repository at this point in the history
89 fix the command line usage
  • Loading branch information
danielpastor97 authored May 20, 2024
2 parents a6f9b9b + 37adf04 commit eb8e36c
Show file tree
Hide file tree
Showing 6 changed files with 124 additions and 174 deletions.
7 changes: 5 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,10 @@ ENV/
*profraw

# data for tests
prolint2/data/*
prolint2/data/

# notebooks in docs
docs/notebooks/*
docs/notebooks/

# TEMP folder for tests
TEMP/
203 changes: 33 additions & 170 deletions bin/prolint2
Original file line number Diff line number Diff line change
Expand Up @@ -7,182 +7,45 @@ r"""Argument parser to use prolint2 from the command-line
"""
import os
import pathlib
import typer
from typing import List, Optional
from typing_extensions import Annotated
from typer.core import TyperGroup
from click import Context
import configparser
from jsonargparse import CLI

import prolint2._version as vers
from prolint2.server.server import ProLintDashboard
from prolint2.core.universe import Universe


# Getting the config file
config = configparser.ConfigParser(allow_no_value=True)
config.read(
os.path.join(os.path.abspath(os.path.dirname(__file__)), "../prolint2/config.ini")
)
parameters_config = config["Parameters"]


# fixing order of commands
class OrderCommands(TyperGroup):
def list_commands(self, ctx: Context):
"""Return list of commands in the order appear."""
return list(self.commands) # get commands using self.commands


# Creating the parser
prolint2 = typer.Typer(
help="[blue]Command-line version of the ProLint v.2 library.[/blue] :sparkles:",
epilog="[blue]Have fun analyzing lipid-protein interactions![/blue] ",
rich_markup_mode="rich",
add_completion=False,
cls=OrderCommands,
context_settings={"help_option_names": ["-h", "--help"]},
)


# creating the run command
@prolint2.command(epilog="[blue]Have fun analyzing lipid-protein interactions![/blue] ")
def run(
structure: Annotated[
pathlib.Path,
typer.Argument(
show_default=False,
help='path to the structure/topology file (E.g.: "coordinates.gro").',
),
],
trajectory: Annotated[
pathlib.Path,
typer.Argument(
show_default=False,
help='path to the trajectory file (E.g.: "trajectory.xtc").',
),
],
contacts_output: Annotated[
pathlib.Path,
typer.Argument(
show_default=False,
help='path to export the results to a file (E.g.: "contacts.csv").',
),
],
cutoff: Annotated[
float,
typer.Option(
"-c",
"--cutoff",
show_default=True,
help="cutoff distance to calculate lipid-protein interactions (in angstroms).",
),
] = parameters_config["cutoff"],
add_lipid: Annotated[
Optional[List[str]],
typer.Option(
"-al",
"--add-lipid",
show_default=True,
help="additional lipid types to be included in the database group.",
),
] = None,
):
"""[blue]ProLint v.2: Running calculation of lipid-protein interactions at a certain cutoff.[/blue] :sparkles:"""
# loading the trajectory
print("Loading trajectory...", end=" ")
System = Universe(structure, trajectory, add_lipid_types=add_lipid)
print("Done!")

# computing contacts
print("Computing contacts...(cutoff = {} angstroms)".format(cutoff))
SystemContacts = System.compute_contacts(cutoff=cutoff)

# save contacts to Pandas dataframe
print("Saving contacts to dataframe...", end=" ")
df = SystemContacts.create_dataframe(System.trajectory.n_frames)
df.to_csv(contacts_output)
print("Done!")


# creating the dashboard command
@prolint2.command(epilog="[blue]Have fun analyzing lipid-protein interactions![/blue] ")
def dashboard(
structure: Annotated[
pathlib.Path,
typer.Argument(
show_default=False,
help='path to the structure/topology file (E.g.: "coordinates.gro").',
),
],
trajectory: Annotated[
pathlib.Path,
typer.Argument(
show_default=False,
help='path to the trajectory file (E.g.: "trajectory.xtc").',
),
],
contacts: Annotated[
pathlib.Path,
typer.Option(
"-ic",
"--contacts",
show_default=False,
help='path to import the results from a file (E.g.: "contacts.csv").',
),
] = None,
host: Annotated[
str,
typer.Option(
"-r",
"--route",
show_default=True,
help="route to host the interactive dashboard.",
),
] = "localhost",
port: Annotated[
int,
typer.Option(
"-p",
"--port",
show_default=True,
help="port to host the interactive dashboard.",
),
] = 8351,
structure: pathlib.Path,
trajectory: pathlib.Path,
host: str = "localhost",
port: int = 8351,
):
"""[blue]ProLint v.2: Launching interactive dashboard to explore the results of lipid-protein interactions.[/blue] :sparkles:"""
# launching dashboard from contacts file if provided
if contacts:
# Starting the server
app = ProLintDashboard()
app.start_server(
payload={
"structure": structure,
"trajectory": trajectory,
"contacts": contacts,
"host": host,
"port": port,
}
)
import sys

sys.exit()
# launching dashboard from trajectory file if contacts file is not provided
else:
# Starting the server
app = ProLintDashboard()
app.start_server(
payload={
"structure": structure,
"trajectory": trajectory,
"host": host,
"port": port,
}
)
import sys
"""
Launch interactive dashboard to explore lipid-protein interactions.
"""
# Starting the server
app = ProLintDashboard()
app.start_server(
payload={
"structure": structure,
"trajectory": trajectory,
"host": host,
"port": port,
}
)
import sys

sys.exit()

def analyzer(
kk: str = 'kk',
):
"""Analyze lipid-protein interactions at different levels of detail."""
print("Option to be added yet.")

sys.exit()
def plotter(
kk: str = 'kk',
):
"""Plot analyses of lipid-protein interactions."""
print("Option to be added yet.")


if __name__ == "__main__":
prolint2()
CLI([dashboard, analyzer, plotter])
5 changes: 5 additions & 0 deletions prolint2/core/contact_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@ def pooled_contacts(self) -> NestedIterFloatDict:
def contact_frames(self) -> NestedIterIntDict:
"""The computed contacts."""
return self._contact_strategy.contact_frames

@property
def occupancies(self) -> NestedIterIntDict:
"""The computed contacts."""
return self._contact_strategy.occupancies

def create_dataframe(self, n_frames: int) -> pd.DataFrame:
"""Create a pandas DataFrame from the computed contacts.
Expand Down
62 changes: 62 additions & 0 deletions prolint2/core/universe.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,68 @@ def update_query(self, new_query):
"""
self.query = new_query

def write_lipid_occupancies_to_bfactor(self, occupancies_dict=None, lipid_type=None):
"""
Write lipid occupancies to `bfactor` topology attribute.
Args:
occupancies_dict: dict, optional. A dictionary of occupancies for each lipid type.
lipid_name: str, optional. The name of the lipid to search for occupancies.
Returns:
None
"""
if occupancies_dict is None:
# Raise an error if no metrics have been provided
raise ValueError("No dictionary of occupancies have been provided.")
elif lipid_type is None:
# Raise an error if no lipid type has been provided
raise ValueError("No lipid type has been provided.")
else:
occupancy_values = []
for res in self.residues:
if res.resid in occupancies_dict.keys():
occupancy_values.extend([occupancies_dict[res.resid][lipid_type]] * res.atoms.n_atoms)
else:
occupancy_values.extend([0] * res.atoms.n_atoms)
assert len(occupancy_values) == self.atoms.n_atoms
self.add_TopologyAttr("bfactor", occupancy_values)

def write_metrics_to_bfactor(self, metrics_dict=None, lipid_type=None, metric_name=None):
"""
Write metrics to `bfactor` topology attribute.
Args:
metrics_dict: dict, optional. A dictionary of metrics for each lipid type.
lipid_name: str, optional. The name of the lipid to search for metrics.
metric_name: str, optional. The name of the metric to write.
Returns:
None
"""
if metrics_dict is None:
# Raise an error if no metrics have been provided
raise ValueError("No dictionary of metrics have been provided.")
elif lipid_type is None:
# Raise an error if no lipid type has been provided
raise ValueError("No lipid type has been provided.")
elif metric_name is None:
# Raise an error if no metric name has been provided
raise ValueError("No metric name has been provided.")
else:
metric_values = []
for res in self.residues:
if res.resid in metrics_dict.keys() and metric_name in metrics_dict[res.resid][lipid_type].keys():
metric_values.extend([metrics_dict[res.resid][lipid_type][metric_name]] * res.atoms.n_atoms)
else:
metric_values.extend([0] * res.atoms.n_atoms)
assert len(metric_values) == self.atoms.n_atoms
# normalize values to be between 0 and 1
metric_values = (np.array(metric_values) - np.min(metric_values)) * 100 / (np.max(metric_values) - np.min(metric_values))
self.add_TopologyAttr("bfactor", metric_values)



@property
def database(self):
"""
Expand Down
18 changes: 18 additions & 0 deletions prolint2/metrics/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ def __init__(self, ts, contact_frames, norm_factor: float = 1.0):
self._resnames = ts.database.residues.resnames
self._database_unique_resnames = ts.database.unique_resnames
self._contacts = defaultdict(lambda: defaultdict(dict))
self._nframes = ts.trajectory.n_frames

def run(self, lipid_resnames: Union[str, List] = None):
"""Run the contact calculation for the given lipid resnames. If no resnames are given, all resnames are used."""
Expand Down Expand Up @@ -154,6 +155,23 @@ def contacts(self):
if self._contacts is None:
raise ValueError("No contacts have been computed yet. Call run() first.")
return self.pooled_results()

@property
def occupancies(self):
"""Get the computed occupancies all pooled together."""
if self.contact_frames is None:
raise ValueError("No contacts have been computed yet. Call run() first.")
occupancies_dict = {}
for res_id in self.contact_frames.keys():
occupancies_dict[res_id] = {}
for lipid_type in self._database_unique_resnames:
occupancy_frames = []
lipids_of_given_type = self._resids[self._resnames == lipid_type]
for lipid_id in lipids_of_given_type:
values = self.contact_frames[res_id][lipid_id]
occupancy_frames.extend([fr for fr in values if fr not in occupancy_frames])
occupancies_dict[res_id][lipid_type] = len(occupancy_frames) * 100 / self._nframes
return occupancies_dict


class FittingFunctionMeta(type):
Expand Down
3 changes: 1 addition & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,11 @@
"pandas",
"mdanalysis",
"bottle",
"typer[all]",
"jsonargparse[signatures]",
"requests",
"networkx",
"logomaker",
"seaborn",
"typing_extensions",
], # Required packages, pulls from pip if needed; do not use for Conda deployment
platforms=[
"Linux",
Expand Down

0 comments on commit eb8e36c

Please sign in to comment.