19 Commits

Author SHA1 Message Date
6a4c8d96c3 Adjust Matomo Site ID (#57)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#57
2025-08-26 06:17:09 +12:00
97d0527565 Merge pull request 'fixed calls to create MLRelativeReasoning during bootstrap.py and test_model.py' (#56) from fix/mlrr_arguments into develop
Reviewed-on: enviPath/enviPy#56
2025-08-22 11:29:02 +12:00
b45c99f7d3 fixed calls to create MLRelativeReasoning during bootstrap.py and test_model.py 2025-08-22 11:28:01 +12:00
b95ec98a2f Added Shortcut to make Packages Public (#54)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#54
2025-08-22 07:31:08 +12:00
c79a1f2040 Fix "Impersonation" (#53)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#53
2025-08-22 07:01:49 +12:00
6e6b394289 Cleanup (#52)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#52
2025-08-22 06:36:22 +12:00
ec387cc12e Added UI elements to add/remove Scenarios to various objects (#51)
Fixes #23

Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#51
2025-08-21 07:56:44 +12:00
a7637d046a External References (#50)
Fix for #24

Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#50
2025-08-21 06:11:05 +12:00
fc8192fb0d Fix App Domain Bug when a Rule can be applied more than once (#49)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#49
2025-08-19 22:10:18 +12:00
c3c1d4f5cf App Domain Pathway Prediction (#47)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#47
2025-08-19 02:53:56 +12:00
3308d47071 Fix bond breaking (#46)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#46
2025-08-15 09:06:07 +12:00
1267ca8ace Enable App Domain Assessment on Model Page (#45)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#45
2025-08-12 09:02:11 +12:00
ec52b8872d Functional Group Calculation (#44)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#44
2025-08-11 09:07:07 +12:00
579cd519d0 Experimental App Domain (#43)
Backend App Domain done, Frontend missing

Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#43
2025-08-08 20:52:21 +12:00
280ddc7205 Fixed UUID vs str comparison when getting User (#42)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#42
2025-07-31 09:11:52 +12:00
c9d6d8b024 Delete Stale Edges when removing a Node from a Pathway (#41)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#41
2025-07-31 07:50:50 +12:00
a1aebfa54d Model Building UI Flag (#39)
Fixes #8
Flag only disables UI Elements

Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#39
2025-07-31 07:00:54 +12:00
79b4b1586c Download Pathway Functionality (#38)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#38
2025-07-31 01:30:16 +12:00
aec61151ce Correct Label assignment (#37)
Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#37
2025-07-31 00:43:28 +12:00
62 changed files with 3060 additions and 1079 deletions

View File

@ -260,6 +260,8 @@ CELERY_RESULT_BACKEND = 'redis://localhost:6379/1'
CELERY_ACCEPT_CONTENT = ['json'] CELERY_ACCEPT_CONTENT = ['json']
CELERY_TASK_SERIALIZER = 'json' CELERY_TASK_SERIALIZER = 'json'
MODEL_BUILDING_ENABLED = os.environ.get('MODEL_BUILDING_ENABLED', 'False') == 'True'
APPLICABILITY_DOMAIN_ENABLED = os.environ.get('APPLICABILITY_DOMAIN_ENABLED', 'False') == 'True'
DEFAULT_RF_MODEL_PARAMS = { DEFAULT_RF_MODEL_PARAMS = {
'base_clf': RandomForestClassifier( 'base_clf': RandomForestClassifier(
n_estimators=100, n_estimators=100,
@ -273,14 +275,14 @@ DEFAULT_RF_MODEL_PARAMS = {
'num_chains': 10, 'num_chains': 10,
} }
DEFAULT_DT_MODEL_PARAMS = { DEFAULT_MODEL_PARAMS = {
'base_clf': DecisionTreeClassifier( 'base_clf': DecisionTreeClassifier(
criterion='entropy', criterion='entropy',
max_depth=3, max_depth=3,
min_samples_split=5, min_samples_split=5,
min_samples_leaf=5, # min_samples_leaf=5,
max_features='sqrt', max_features='sqrt',
class_weight='balanced', # class_weight='balanced',
random_state=42 random_state=42
), ),
'num_chains': 10, 'num_chains': 10,
@ -312,3 +314,13 @@ if SENTRY_ENABLED:
# see https://docs.sentry.io/platforms/python/data-management/data-collected/ for more info # see https://docs.sentry.io/platforms/python/data-management/data-collected/ for more info
send_default_pii=True, send_default_pii=True,
) )
# compile into digestible flags
FLAGS = {
'MODEL_BUILDING': MODEL_BUILDING_ENABLED,
'CELERY': FLAG_CELERY_PRESENT,
'PLUGINS': PLUGINS_ENABLED,
'SENTRY': SENTRY_ENABLED,
'ENVIFORMER': ENVIFORMER_PRESENT,
'APPLICABILITY_DOMAIN': APPLICABILITY_DOMAIN_ENABLED,
}

View File

@ -1,40 +1,105 @@
from django.contrib import admin from django.contrib import admin
from .models import User, Group, UserPackagePermission, GroupPackagePermission, Setting, SimpleAmbitRule, Scenario from .models import (
User,
UserPackagePermission,
Group,
GroupPackagePermission,
Package,
MLRelativeReasoning,
Compound,
CompoundStructure,
SimpleAmbitRule,
ParallelRule,
Reaction,
Pathway,
Node,
Edge,
Scenario,
Setting
)
class UserAdmin(admin.ModelAdmin): class UserAdmin(admin.ModelAdmin):
pass pass
class GroupAdmin(admin.ModelAdmin):
pass
class UserPackagePermissionAdmin(admin.ModelAdmin): class UserPackagePermissionAdmin(admin.ModelAdmin):
pass pass
class GroupAdmin(admin.ModelAdmin):
pass
class GroupPackagePermissionAdmin(admin.ModelAdmin): class GroupPackagePermissionAdmin(admin.ModelAdmin):
pass pass
class SettingAdmin(admin.ModelAdmin): class EPAdmin(admin.ModelAdmin):
search_fields = ['name', 'description']
class PackageAdmin(EPAdmin):
pass
class MLRelativeReasoningAdmin(EPAdmin):
pass pass
class SimpleAmbitRuleAdmin(admin.ModelAdmin): class CompoundAdmin(EPAdmin):
pass pass
class ScenarioAdmin(admin.ModelAdmin): class CompoundStructureAdmin(EPAdmin):
pass
class SimpleAmbitRuleAdmin(EPAdmin):
pass
class ParallelRuleAdmin(EPAdmin):
pass
class ReactionAdmin(EPAdmin):
pass
class PathwayAdmin(EPAdmin):
pass
class NodeAdmin(EPAdmin):
pass
class EdgeAdmin(EPAdmin):
pass
class ScenarioAdmin(EPAdmin):
pass
class SettingAdmin(EPAdmin):
pass pass
admin.site.register(User, UserAdmin) admin.site.register(User, UserAdmin)
admin.site.register(Group, GroupAdmin)
admin.site.register(UserPackagePermission, UserPackagePermissionAdmin) admin.site.register(UserPackagePermission, UserPackagePermissionAdmin)
admin.site.register(Group, GroupAdmin)
admin.site.register(GroupPackagePermission, GroupPackagePermissionAdmin) admin.site.register(GroupPackagePermission, GroupPackagePermissionAdmin)
admin.site.register(Setting, SettingAdmin) admin.site.register(Package, PackageAdmin)
admin.site.register(MLRelativeReasoning, MLRelativeReasoningAdmin)
admin.site.register(Compound, CompoundAdmin)
admin.site.register(CompoundStructure, CompoundStructureAdmin)
admin.site.register(SimpleAmbitRule, SimpleAmbitRuleAdmin) admin.site.register(SimpleAmbitRule, SimpleAmbitRuleAdmin)
admin.site.register(ParallelRule, ParallelRuleAdmin)
admin.site.register(Reaction, ReactionAdmin)
admin.site.register(Pathway, PathwayAdmin)
admin.site.register(Node, NodeAdmin)
admin.site.register(Edge, EdgeAdmin)
admin.site.register(Setting, SettingAdmin)
admin.site.register(Scenario, ScenarioAdmin) admin.site.register(Scenario, ScenarioAdmin)

View File

@ -4,3 +4,6 @@ from django.apps import AppConfig
class EPDBConfig(AppConfig): class EPDBConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField' default_auto_field = 'django.db.models.BigAutoField'
name = 'epdb' name = 'epdb'
def ready(self):
import epdb.signals # noqa: F401

View File

@ -62,7 +62,7 @@ class UserManager(object):
@staticmethod @staticmethod
def get_user_by_id(user, user_uuid: str): def get_user_by_id(user, user_uuid: str):
if user.uuid != user_uuid and not user.is_superuser: if str(user.uuid) != user_uuid and not user.is_superuser:
raise ValueError("Getting user failed!") raise ValueError("Getting user failed!")
return get_user_model().objects.get(uuid=user_uuid) return get_user_model().objects.get(uuid=user_uuid)
@ -183,6 +183,25 @@ class PackageManager(object):
return True return True
return False return False
@staticmethod
def administrable(user, package):
if UserPackagePermission.objects.filter(package=package, user=user, permission=Permission.ALL[0]).exists() or \
GroupPackagePermission.objects.filter(package=package, group__in=GroupManager.get_groups(user), permission=Permission.ALL[0]).exists() or \
user.is_superuser:
return True
return False
# @staticmethod
# def get_package_permission(user: 'User', package: Union[str, 'Package']):
# if PackageManager.administrable(user, package):
# return Permission.ALL[0]
# elif PackageManager.writable(user, package):
# return Permission.WRITE[0]
# elif PackageManager.readable(user, package):
# return Permission.READ[0]
# else:
# return None
@staticmethod @staticmethod
def has_package_permission(user: 'User', package: Union[str, 'Package'], permission: str): def has_package_permission(user: 'User', package: Union[str, 'Package'], permission: str):
@ -339,7 +358,7 @@ class PackageManager(object):
@staticmethod @staticmethod
@transaction.atomic @transaction.atomic
def import_package(data: dict, owner: User, keep_ids=False): def import_package(data: dict, owner: User, keep_ids=False, add_import_timestamp=True):
from uuid import UUID, uuid4 from uuid import UUID, uuid4
from datetime import datetime from datetime import datetime
from collections import defaultdict from collections import defaultdict
@ -349,7 +368,12 @@ class PackageManager(object):
pack = Package() pack = Package()
pack.uuid = UUID(data['id'].split('/')[-1]) if keep_ids else uuid4() pack.uuid = UUID(data['id'].split('/')[-1]) if keep_ids else uuid4()
if add_import_timestamp:
pack.name = '{} - {}'.format(data['name'], datetime.now().strftime('%Y-%m-%d %H:%M')) pack.name = '{} - {}'.format(data['name'], datetime.now().strftime('%Y-%m-%d %H:%M'))
else:
pack.name = data['name']
pack.reviewed = True if data['reviewStatus'] == 'reviewed' else False pack.reviewed = True if data['reviewStatus'] == 'reviewed' else False
pack.description = data['description'] pack.description = data['description']
pack.save() pack.save()
@ -890,9 +914,10 @@ class SearchManager(object):
class SNode(object): class SNode(object):
def __init__(self, smiles: str, depth: int): def __init__(self, smiles: str, depth: int, app_domain_assessment: dict = None):
self.smiles = smiles self.smiles = smiles
self.depth = depth self.depth = depth
self.app_domain_assessment = app_domain_assessment
def __hash__(self): def __hash__(self):
return hash(self.smiles) return hash(self.smiles)
@ -1035,7 +1060,7 @@ class SPathway(object):
def depth(self): def depth(self):
return max([v.depth for v in self.smiles_to_node.values()]) return max([v.depth for v in self.smiles_to_node.values()])
def _get_nodes_for_depth(self, depth: int): def _get_nodes_for_depth(self, depth: int) -> List[SNode]:
if depth == 0: if depth == 0:
return self.root_nodes return self.root_nodes
@ -1046,7 +1071,7 @@ class SPathway(object):
return sorted(res, key=lambda x: x.smiles) return sorted(res, key=lambda x: x.smiles)
def _get_edges_for_depth(self, depth: int): def _get_edges_for_depth(self, depth: int) -> List[SEdge]:
res = [] res = []
for e in self.edges: for e in self.edges:
for n in e.educts: for n in e.educts:
@ -1061,7 +1086,7 @@ class SPathway(object):
if from_depth is not None: if from_depth is not None:
substrates = self._get_nodes_for_depth(from_depth) substrates = self._get_nodes_for_depth(from_depth)
elif from_node is not None: elif from_node is not None:
for k,v in self.snode_persist_lookup.items(): for k, v in self.snode_persist_lookup.items():
if from_node == v: if from_node == v:
substrates = [k] substrates = [k]
break break
@ -1071,15 +1096,44 @@ class SPathway(object):
new_tp = False new_tp = False
if substrates: if substrates:
for sub in substrates: for sub in substrates:
if sub.app_domain_assessment is None:
if self.prediction_setting.model:
if self.prediction_setting.model.app_domain:
app_domain_assessment = self.prediction_setting.model.app_domain.assess(sub.smiles)[0]
if self.persist is not None:
n = self.snode_persist_lookup[sub]
assert n.id is not None, "Node has no id! Should have been saved already... aborting!"
node_data = n.simple_json()
node_data['image'] = f"{n.url}?image=svg"
app_domain_assessment['assessment']['node'] = node_data
n.kv['app_domain_assessment'] = app_domain_assessment
n.save()
sub.app_domain_assessment = app_domain_assessment
candidates = self.prediction_setting.expand(self, sub) candidates = self.prediction_setting.expand(self, sub)
# candidates is a List of PredictionResult. The length of the List is equal to the number of rules
for cand_set in candidates: for cand_set in candidates:
if cand_set: if cand_set:
new_tp = True new_tp = True
# cand_set is a PredictionResult object that can consist of multiple candidate reactions
for cand in cand_set: for cand in cand_set:
cand_nodes = [] cand_nodes = []
# candidate reactions can have multiple fragments
for c in cand: for c in cand:
if c not in self.smiles_to_node: if c not in self.smiles_to_node:
self.smiles_to_node[c] = SNode(c, sub.depth + 1) # For new nodes do an AppDomain Assessment if an AppDomain is attached
app_domain_assessment = None
if self.prediction_setting.model:
if self.prediction_setting.model.app_domain:
app_domain_assessment = self.prediction_setting.model.app_domain.assess(c)[0]
self.smiles_to_node[c] = SNode(c, sub.depth + 1, app_domain_assessment)
node = self.smiles_to_node[c] node = self.smiles_to_node[c]
cand_nodes.append(node) cand_nodes.append(node)
@ -1092,18 +1146,30 @@ class SPathway(object):
if len(substrates) == 0 or from_node is not None: if len(substrates) == 0 or from_node is not None:
self.done = True self.done = True
# Check if we need to write back data to database # Check if we need to write back data to the database
if new_tp and self.persist: if new_tp and self.persist:
self._sync_to_pathway() self._sync_to_pathway()
# call save to update internal modified field # call save to update the internal modified field
self.persist.save() self.persist.save()
def _sync_to_pathway(self): def _sync_to_pathway(self) -> None:
logger.info("Updating Pathway with SPathway") logger.info("Updating Pathway with SPathway")
for snode in self.smiles_to_node.values(): for snode in self.smiles_to_node.values():
if snode not in self.snode_persist_lookup: if snode not in self.snode_persist_lookup:
n = Node.create(self.persist, snode.smiles, snode.depth) n = Node.create(self.persist, snode.smiles, snode.depth)
if snode.app_domain_assessment is not None:
app_domain_assessment = snode.app_domain_assessment
assert n.id is not None, "Node has no id! Should have been saved already... aborting!"
node_data = n.simple_json()
node_data['image'] = f"{n.url}?image=svg"
app_domain_assessment['assessment']['node'] = node_data
n.kv['app_domain_assessment'] = app_domain_assessment
n.save()
self.snode_persist_lookup[snode] = n self.snode_persist_lookup[snode] = n
for sedge in self.edges: for sedge in self.edges:
@ -1125,7 +1191,6 @@ class SPathway(object):
self.sedge_persist_lookup[sedge] = e self.sedge_persist_lookup[sedge] = e
logger.info("Update done!") logger.info("Update done!")
pass
def to_json(self): def to_json(self):
nodes = [] nodes = []

View File

@ -5,7 +5,7 @@ from django.core.management.base import BaseCommand
from django.db import transaction from django.db import transaction
from epdb.logic import UserManager, GroupManager, PackageManager, SettingManager from epdb.logic import UserManager, GroupManager, PackageManager, SettingManager
from epdb.models import UserSettingPermission, MLRelativeReasoning, EnviFormer, Permission, User from epdb.models import UserSettingPermission, MLRelativeReasoning, EnviFormer, Permission, User, ExternalDatabase
class Command(BaseCommand): class Command(BaseCommand):
@ -58,7 +58,7 @@ class Command(BaseCommand):
return anon, admin, g, jebus return anon, admin, g, jebus
def import_package(self, data, owner): def import_package(self, data, owner):
return PackageManager.import_package(data, owner, keep_ids=True) return PackageManager.import_package(data, owner, keep_ids=True, add_import_timestamp=False)
def create_default_setting(self, owner, packages): def create_default_setting(self, owner, packages):
s = SettingManager.create_setting( s = SettingManager.create_setting(
@ -74,6 +74,76 @@ class Command(BaseCommand):
return s return s
def populate_common_external_databases(self):
"""
Helper function to populate common external databases.
This can be called from a Django management command.
"""
databases = [
{
'name': 'PubChem Compound',
'full_name': 'PubChem Compound Database',
'description': 'Chemical database of small organic molecules',
'base_url': 'https://pubchem.ncbi.nlm.nih.gov',
'url_pattern': 'https://pubchem.ncbi.nlm.nih.gov/compound/{id}'
},
{
'name': 'PubChem Substance',
'full_name': 'PubChem Substance Database',
'description': 'Database of chemical substances',
'base_url': 'https://pubchem.ncbi.nlm.nih.gov',
'url_pattern': 'https://pubchem.ncbi.nlm.nih.gov/substance/{id}'
},
{
'name': 'ChEBI',
'full_name': 'Chemical Entities of Biological Interest',
'description': 'Dictionary of molecular entities',
'base_url': 'https://www.ebi.ac.uk/chebi',
'url_pattern': 'https://www.ebi.ac.uk/chebi/searchId.do?chebiId=CHEBI:{id}'
},
{
'name': 'RHEA',
'full_name': 'RHEA Reaction Database',
'description': 'Comprehensive resource of biochemical reactions',
'base_url': 'https://www.rhea-db.org',
'url_pattern': 'https://www.rhea-db.org/rhea/{id}'
},
{
'name': 'CAS',
'full_name': 'Chemical Abstracts Service Registry',
'description': 'Registry of chemical substances',
'base_url': 'https://www.cas.org',
'url_pattern': None # CAS doesn't have a free public URL pattern
},
{
'name': 'KEGG Reaction',
'full_name': 'KEGG Reaction Database',
'description': 'Database of biochemical reactions',
'base_url': 'https://www.genome.jp',
'url_pattern': 'https://www.genome.jp/entry/reaction+{id}'
},
{
'name': 'MetaCyc',
'full_name': 'MetaCyc Metabolic Pathway Database',
'description': 'Database of metabolic pathways and enzymes',
'base_url': 'https://metacyc.org',
'url_pattern': None
},
{
'name': 'UniProt',
'full_name': 'MetaCyc Metabolic Pathway Database',
'description': 'UniProt is a freely accessible database of protein sequence and functional information',
'base_url': 'https://www.uniprot.org',
'url_pattern': 'https://www.uniprot.org/uniprotkb?query="{id}"'
}
]
for db_info in databases:
ExternalDatabase.objects.get_or_create(
name=db_info['name'],
defaults=db_info
)
@transaction.atomic @transaction.atomic
def handle(self, *args, **options): def handle(self, *args, **options):
# Create users # Create users
@ -117,17 +187,17 @@ class Command(BaseCommand):
# Create RR # Create RR
ml_model = MLRelativeReasoning.create( ml_model = MLRelativeReasoning.create(
pack, package=pack,
'ECC - BBD - T0.5', rule_packages=[mapping['EAWAG-BBD']],
'ML Relative Reasoning', data_packages=[mapping['EAWAG-BBD']],
[mapping['EAWAG-BBD']], eval_packages=[],
[mapping['EAWAG-BBD']], threshold=0.5,
[], name='ECC - BBD - T0.5',
0.5 description='ML Relative Reasoning',
) )
X, y = ml_model.build_dataset() ml_model.build_dataset()
ml_model.build_model(X, y) ml_model.build_model()
# ml_model.evaluate_model() # ml_model.evaluate_model()
# If available create EnviFormerModel # If available create EnviFormerModel

File diff suppressed because it is too large Load Diff

20
epdb/signals.py Normal file
View File

@ -0,0 +1,20 @@
from django.db import transaction
from django.db.models.signals import pre_delete
from django.dispatch import receiver
from epdb.models import Node, Edge
@receiver(pre_delete, sender=Node)
@transaction.atomic
def delete_orphan_edges(sender, instance, **kwargs):
# check if the node that is about to be deleted is the only start node
for edge in Edge.objects.filter(start_nodes=instance):
if edge.start_nodes.count() == 1:
edge.delete()
# same for end_nodes
for edge in Edge.objects.filter(end_nodes=instance):
# check if the node that is about to be deleted is the only start node
if edge.end_nodes.count() == 1:
edge.delete()

View File

@ -31,8 +31,8 @@ def send_registration_mail(user_pk: int):
@shared_task(queue='model') @shared_task(queue='model')
def build_model(model_pk: int): def build_model(model_pk: int):
mod = EPModel.objects.get(id=model_pk) mod = EPModel.objects.get(id=model_pk)
X, y = mod.build_dataset() mod.build_dataset()
mod.build_model(X, y) mod.build_model()
@shared_task(queue='model') @shared_task(queue='model')
@ -58,7 +58,7 @@ def predict(pw_pk: int, pred_setting_pk: int, limit: Optional[int] = None, node_
spw.predict_step(from_depth=level) spw.predict_step(from_depth=level)
level += 1 level += 1
# break in case we are in incremental model # break in case we are in incremental mode
if limit != -1: if limit != -1:
if level >= limit: if level >= limit:
break break

View File

@ -4,6 +4,9 @@ from typing import List, Dict, Any
from django.conf import settings as s from django.conf import settings as s
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.db.models import F, Value
from django.db.models.fields import CharField
from django.db.models.functions import Concat
from django.http import JsonResponse, HttpResponse, HttpResponseNotAllowed, HttpResponseBadRequest from django.http import JsonResponse, HttpResponse, HttpResponseNotAllowed, HttpResponseBadRequest
from django.shortcuts import render, redirect from django.shortcuts import render, redirect
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
@ -103,7 +106,10 @@ def login(request):
else: else:
context['message'] = "Account has been created! You'll receive a mail to activate your account shortly." context['message'] = "Account has been created! You'll receive a mail to activate your account shortly."
return render(request, 'login.html', context) return render(request, 'login.html', context)
else:
return HttpResponseBadRequest()
else:
return HttpResponseNotAllowed(['GET', 'POST'])
def logout(request): def logout(request):
if request.method == 'POST': if request.method == 'POST':
@ -136,7 +142,7 @@ def editable(request, user):
f"{s.SERVER_URL}/group", f"{s.SERVER_URL}/search"]: f"{s.SERVER_URL}/group", f"{s.SERVER_URL}/search"]:
return True return True
else: else:
print(f"Unknown url: {url}") logger.debug(f"Unknown url: {url}")
return False return False
@ -158,7 +164,7 @@ def get_base_context(request, for_user=None) -> Dict[str, Any]:
'writeable_packages': PackageManager.get_all_writeable_packages(current_user), 'writeable_packages': PackageManager.get_all_writeable_packages(current_user),
'available_groups': GroupManager.get_groups(current_user), 'available_groups': GroupManager.get_groups(current_user),
'available_settings': SettingManager.get_all_settings(current_user), 'available_settings': SettingManager.get_all_settings(current_user),
'enabled_features': [], 'enabled_features': s.FLAGS,
'debug': s.DEBUG, 'debug': s.DEBUG,
}, },
} }
@ -196,6 +202,15 @@ def breadcrumbs(first_level_object=None, second_level_namespace=None, second_lev
return bread return bread
def set_scenarios(current_user, attach_object, scenario_urls: List[str]):
scens = []
for scenario_url in scenario_urls:
package = PackageManager.get_package_by_url(current_user, scenario_url)
scen = Scenario.objects.get(package=package, uuid=scenario_url.split('/')[-1])
scens.append(scen)
attach_object.set_scenarios(scens)
def index(request): def index(request):
context = get_base_context(request) context = get_base_context(request)
context['title'] = 'enviPath - Home' context['title'] = 'enviPath - Home'
@ -424,8 +439,16 @@ def scenarios(request):
if request.GET.get('all'): if request.GET.get('all'):
return JsonResponse({ return JsonResponse({
"objects": [ "objects": [
{"name": pw.name, "url": pw.url, "reviewed": True} {"name": s.name, "url": s.full_url, "reviewed": True}
for pw in reviewed_scenario_qs for s in reviewed_scenario_qs.annotate(
full_url=Concat(
Value(s.SERVER_URL + '/package/'),
F("package__uuid"),
Value("/scenario/"),
F("uuid"),
output_field=CharField(),
)
)
] ]
}) })
@ -505,6 +528,7 @@ def search(request):
packages = PackageManager.get_reviewed_packages() packages = PackageManager.get_reviewed_packages()
search_result = SearchManager.search(packages, searchterm, mode) search_result = SearchManager.search(packages, searchterm, mode)
return JsonResponse(search_result, safe=False) return JsonResponse(search_result, safe=False)
context = get_base_context(request) context = get_base_context(request)
@ -530,6 +554,7 @@ def search(request):
packages = PackageManager.get_reviewed_packages() packages = PackageManager.get_reviewed_packages()
context['search_result'] = SearchManager.search(packages, searchterm, mode) context['search_result'] = SearchManager.search(packages, searchterm, mode)
context['search_result']['searchterm'] = searchterm
return render(request, 'search.html', context) return render(request, 'search.html', context)
@ -572,15 +597,21 @@ def package_models(request, package_uuid):
context['model_types'] = { context['model_types'] = {
'ML Relative Reasoning': 'ml-relative-reasoning', 'ML Relative Reasoning': 'ml-relative-reasoning',
'Rule Based Relative Reasoning': 'rule-based-relative-reasoning', 'Rule Based Relative Reasoning': 'rule-based-relative-reasoning',
'EnviFormer': 'enviformer',
} }
if s.FLAGS.get('ENVIFORMER', False):
context['model_types']['EnviFormer'] = 'enviformer'
if s.FLAGS.get('PLUGINS', False):
for k, v in s.CLASSIFIER_PLUGINS.items(): for k, v in s.CLASSIFIER_PLUGINS.items():
context['model_types'][v.display()] = k context['model_types'][v.display()] = k
return render(request, 'collections/objects_list.html', context) return render(request, 'collections/objects_list.html', context)
elif request.method == 'POST': elif request.method == 'POST':
log_post_params(request)
name = request.POST.get('model-name') name = request.POST.get('model-name')
description = request.POST.get('model-description') description = request.POST.get('model-description')
@ -603,14 +634,25 @@ def package_models(request, package_uuid):
data_package_objs = [PackageManager.get_package_by_url(current_user, p) for p in data_packages] data_package_objs = [PackageManager.get_package_by_url(current_user, p) for p in data_packages]
eval_packages_objs = [PackageManager.get_package_by_url(current_user, p) for p in eval_packages] eval_packages_objs = [PackageManager.get_package_by_url(current_user, p) for p in eval_packages]
# App Domain related parameters
build_ad = request.POST.get('build-app-domain', False) == 'on'
num_neighbors = request.POST.get('num-neighbors', 5)
reliability_threshold = request.POST.get('reliability-threshold', 0.5)
local_compatibility_threshold = request.POST.get('local-compatibility-threshold', 0.5)
mod = MLRelativeReasoning.create( mod = MLRelativeReasoning.create(
current_package, package=current_package,
name, name=name,
description, description=description,
rule_package_objs, rule_packages=rule_package_objs,
data_package_objs, data_packages=data_package_objs,
eval_packages_objs, eval_packages=eval_packages_objs,
threshold threshold=threshold,
# fingerprinter=fingerprinter,
build_app_domain=build_ad,
app_domain_num_neighbours=num_neighbors,
app_domain_reliability_threshold=reliability_threshold,
app_domain_local_compatibility_threshold=local_compatibility_threshold,
) )
from .tasks import build_model from .tasks import build_model
@ -646,7 +688,7 @@ def package_model(request, package_uuid, model_uuid):
if len(pr) > 0: if len(pr) > 0:
products = [] products = []
for prod_set in pr.product_sets: for prod_set in pr.product_sets:
print(f"Checking {prod_set}") logger.debug(f"Checking {prod_set}")
products.append(tuple([x for x in prod_set])) products.append(tuple([x for x in prod_set]))
res.append({ res.append({
@ -657,6 +699,12 @@ def package_model(request, package_uuid, model_uuid):
return JsonResponse(res, safe=False) return JsonResponse(res, safe=False)
elif request.GET.get('app-domain-assessment', False):
smiles = request.GET['smiles']
stand_smiles = FormatConverter.standardize(smiles)
app_domain_assessment = current_model.app_domain.assess(stand_smiles)[0]
return JsonResponse(app_domain_assessment, safe=False)
context = get_base_context(request) context = get_base_context(request)
context['title'] = f'enviPath - {current_package.name} - {current_model.name}' context['title'] = f'enviPath - {current_package.name} - {current_model.name}'
@ -665,12 +713,13 @@ def package_model(request, package_uuid, model_uuid):
context['breadcrumbs'] = breadcrumbs(current_package, 'model', current_model) context['breadcrumbs'] = breadcrumbs(current_package, 'model', current_model)
context['model'] = current_model context['model'] = current_model
context['current_object'] = current_model
return render(request, 'objects/model.html', context) return render(request, 'objects/model.html', context)
elif request.method == 'POST': elif request.method == 'POST':
if hidden := request.POST.get('hidden', None): if hidden := request.POST.get('hidden', None):
if hidden == 'delete-model': if hidden == 'delete':
current_model.delete() current_model.delete()
return redirect(current_package.url + '/model') return redirect(current_package.url + '/model')
else: else:
@ -696,8 +745,6 @@ def package(request, package_uuid):
context['breadcrumbs'] = breadcrumbs(current_package) context['breadcrumbs'] = breadcrumbs(current_package)
context['package'] = current_package context['package'] = current_package
# context['package_group'] = GroupPackagePermission.objects.filter(package=current_package,
# permission=GroupPackagePermission.ALL)
user_perms = UserPackagePermission.objects.filter(package=current_package) user_perms = UserPackagePermission.objects.filter(package=current_package)
users = get_user_model().objects.exclude( users = get_user_model().objects.exclude(
@ -716,14 +763,16 @@ def package(request, package_uuid):
elif request.method == 'POST': elif request.method == 'POST':
if s.DEBUG: log_post_params(request)
for k, v in request.POST.items():
logger.debug(f"{k}\t{v}")
if hidden := request.POST.get('hidden', None): if hidden := request.POST.get('hidden', None):
if hidden == 'delete-package': if hidden == 'delete':
logger.debug(current_package.delete()) logger.debug(current_package.delete())
return redirect(s.SERVER_URL + '/package') return redirect(s.SERVER_URL + '/package')
elif hidden == 'publish-package':
for g in Group.objects.filter(public=True):
PackageManager.update_permissions(current_user, current_package, g, Permission.READ[0])
return redirect(current_package.url)
else: else:
return HttpResponseBadRequest() return HttpResponseBadRequest()
@ -855,17 +904,24 @@ def package_compound(request, package_uuid, compound_uuid):
context['breadcrumbs'] = breadcrumbs(current_package, 'compound', current_compound) context['breadcrumbs'] = breadcrumbs(current_package, 'compound', current_compound)
context['compound'] = current_compound context['compound'] = current_compound
context['current_object'] = current_compound
return render(request, 'objects/compound.html', context) return render(request, 'objects/compound.html', context)
elif request.method == 'POST': elif request.method == 'POST':
if hidden := request.POST.get('hidden', None): if hidden := request.POST.get('hidden', None):
if hidden == 'delete-compound': if hidden == 'delete':
current_compound.delete() current_compound.delete()
return redirect(current_package.url + '/compound') return redirect(current_package.url + '/compound')
else: else:
return HttpResponseBadRequest() return HttpResponseBadRequest()
selected_scenarios = request.POST.getlist('selected-scenarios')
if selected_scenarios:
set_scenarios(current_user, current_compound, selected_scenarios)
return redirect(current_compound.url)
new_compound_name = request.POST.get('compound-name') new_compound_name = request.POST.get('compound-name')
new_compound_description = request.POST.get('compound-description') new_compound_description = request.POST.get('compound-description')
@ -897,6 +953,7 @@ def package_compound_structures(request, package_uuid, compound_uuid):
context['meta']['current_package'] = current_package context['meta']['current_package'] = current_package
context['object_type'] = 'structure' context['object_type'] = 'structure'
context['breadcrumbs'] = breadcrumbs(current_package, 'compound', current_compound, 'structure')
reviewed_compound_structure_qs = CompoundStructure.objects.none() reviewed_compound_structure_qs = CompoundStructure.objects.none()
unreviewed_compound_structure_qs = CompoundStructure.objects.none() unreviewed_compound_structure_qs = CompoundStructure.objects.none()
@ -936,12 +993,45 @@ def package_compound_structure(request, package_uuid, compound_uuid, structure_u
context['title'] = f'enviPath - {current_package.name} - {current_compound.name} - {current_structure.name}' context['title'] = f'enviPath - {current_package.name} - {current_compound.name} - {current_structure.name}'
context['meta']['current_package'] = current_package context['meta']['current_package'] = current_package
context['object_type'] = 'compound' context['object_type'] = 'structure'
context['compound_structure'] = current_structure context['compound_structure'] = current_structure
context['current_object'] = current_structure
context['breadcrumbs'] = breadcrumbs(current_package, 'compound', current_compound, 'structure', current_structure)
return render(request, 'objects/compound_structure.html', context) return render(request, 'objects/compound_structure.html', context)
elif request.method == 'POST':
if hidden := request.POST.get('hidden', None):
if hidden == 'delete':
# Check if we have to delete the compound as no structure is left
if len(current_structure.compound.structures.all()) == 1:
# This will delete the structure as well
current_compound.delete()
return redirect(current_package.url + '/compound')
else:
if current_structure.normalized_structure:
current_compound.delete()
return redirect(current_package.url + '/compound')
else:
if current_compound.default_structure == current_structure:
current_structure.delete()
current_compound.default_structure = current_compound.structures.all().first()
return redirect(current_compound.url + '/structure')
else:
current_structure.delete()
return redirect(current_compound.url + '/structure')
else:
return HttpResponseBadRequest()
selected_scenarios = request.POST.getlist('selected-scenarios')
if selected_scenarios:
set_scenarios(current_user, current_structure, selected_scenarios)
return redirect(current_structure.url)
return HttpResponseBadRequest()
else: else:
return HttpResponseNotAllowed(['GET', ]) return HttpResponseNotAllowed(['GET', ])
@ -1022,6 +1112,24 @@ def package_rule(request, package_uuid, rule_uuid):
if request.method == 'GET': if request.method == 'GET':
context = get_base_context(request) context = get_base_context(request)
if smiles := request.GET.get('smiles', False):
stand_smiles = FormatConverter.standardize(smiles)
res = current_rule.apply(stand_smiles)
if len(res) > 1:
logger.info(f"Rule {current_rule.uuid} returned multiple product sets on {smiles}, picking the first one.")
smirks = f"{stand_smiles}>>{'.'.join(sorted(res[0]))}"
# Usually the functional groups are a mapping of fg -> count
# As we are doing it on the fly here fake a high count to ensure that its properly highlighted
educt_functional_groups = {x: 1000 for x in current_rule.reactants_smarts}
product_functional_groups = {x: 1000 for x in current_rule.products_smarts}
return HttpResponse(
IndigoUtils.smirks_to_svg(smirks, False, 0, 0,
educt_functional_groups=educt_functional_groups,
product_functional_groups=product_functional_groups),
content_type='image/svg+xml')
context['title'] = f'enviPath - {current_package.name} - {current_rule.name}' context['title'] = f'enviPath - {current_package.name} - {current_rule.name}'
context['meta']['current_package'] = current_package context['meta']['current_package'] = current_package
@ -1029,6 +1137,8 @@ def package_rule(request, package_uuid, rule_uuid):
context['breadcrumbs'] = breadcrumbs(current_package, 'rule', current_rule) context['breadcrumbs'] = breadcrumbs(current_package, 'rule', current_rule)
context['rule'] = current_rule context['rule'] = current_rule
context['current_object'] = current_rule
if isinstance(current_rule, SimpleAmbitRule): if isinstance(current_rule, SimpleAmbitRule):
return render(request, 'objects/simple_rule.html', context) return render(request, 'objects/simple_rule.html', context)
else: # isinstance(current_rule, ParallelRule) or isinstance(current_rule, SequentialRule): else: # isinstance(current_rule, ParallelRule) or isinstance(current_rule, SequentialRule):
@ -1036,12 +1146,18 @@ def package_rule(request, package_uuid, rule_uuid):
elif request.method == 'POST': elif request.method == 'POST':
if hidden := request.POST.get('hidden', None): if hidden := request.POST.get('hidden', None):
if hidden == 'delete-rule': if hidden == 'delete':
current_rule.delete() current_rule.delete()
return redirect(current_package.url + '/rule') return redirect(current_package.url + '/rule')
else: else:
return HttpResponseBadRequest() return HttpResponseBadRequest()
selected_scenarios = request.POST.getlist('selected-scenarios')
if selected_scenarios:
set_scenarios(current_user, current_rule, selected_scenarios)
return redirect(current_rule.url)
rule_name = request.POST.get('rule-name', '').strip() rule_name = request.POST.get('rule-name', '').strip()
rule_description = request.POST.get('rule-description', '').strip() rule_description = request.POST.get('rule-description', '').strip()
@ -1127,17 +1243,24 @@ def package_reaction(request, package_uuid, reaction_uuid):
context['breadcrumbs'] = breadcrumbs(current_package, 'reaction', current_reaction) context['breadcrumbs'] = breadcrumbs(current_package, 'reaction', current_reaction)
context['reaction'] = current_reaction context['reaction'] = current_reaction
context['current_object'] = current_reaction
return render(request, 'objects/reaction.html', context) return render(request, 'objects/reaction.html', context)
elif request.method == 'POST': elif request.method == 'POST':
if hidden := request.POST.get('hidden', None): if hidden := request.POST.get('hidden', None):
if hidden == 'delete-reaction': if hidden == 'delete':
current_reaction.delete() current_reaction.delete()
return redirect(current_package.url + '/reaction') return redirect(current_package.url + '/reaction')
else: else:
return HttpResponseBadRequest() return HttpResponseBadRequest()
selected_scenarios = request.POST.getlist('selected-scenarios')
if selected_scenarios:
set_scenarios(current_user, current_reaction, selected_scenarios)
return redirect(current_reaction.url)
new_reaction_name = request.POST.get('reaction-name') new_reaction_name = request.POST.get('reaction-name')
new_reaction_description = request.POST.get('reaction-description') new_reaction_description = request.POST.get('reaction-description')
@ -1195,8 +1318,8 @@ def package_pathways(request, package_uuid):
log_post_params(request) log_post_params(request)
name = request.POST.get('name', 'Pathway ' + str(Pathway.objects.filter(package=current_package).count())) name = request.POST.get('name')
description = request.POST.get('description', s.DEFAULT_VALUES['description']) description = request.POST.get('description')
pw_mode = request.POST.get('predict', 'predict') pw_mode = request.POST.get('predict', 'predict')
smiles = request.POST.get('smiles') smiles = request.POST.get('smiles')
@ -1217,7 +1340,14 @@ def package_pathways(request, package_uuid):
return error(request, "Pathway prediction failed!", return error(request, "Pathway prediction failed!",
f'Pathway prediction failed as received mode "{pw_mode}" is none of {modes}') f'Pathway prediction failed as received mode "{pw_mode}" is none of {modes}')
prediction_setting = request.POST.get('prediction-setting', None)
if prediction_setting:
prediction_setting = SettingManager.get_setting_by_url(current_user, prediction_setting)
else:
prediction_setting = current_user.prediction_settings()
pw = Pathway.create(current_package, stand_smiles, name=name, description=description) pw = Pathway.create(current_package, stand_smiles, name=name, description=description)
# set mode # set mode
pw.kv.update({'mode': pw_mode}) pw.kv.update({'mode': pw_mode})
pw.save() pw.save()
@ -1230,12 +1360,11 @@ def package_pathways(request, package_uuid):
if pw_mode == 'incremental': if pw_mode == 'incremental':
limit = 1 limit = 1
pred_setting = current_user.prediction_settings() pw.setting = prediction_setting
pw.setting = pred_setting
pw.save() pw.save()
from .tasks import predict from .tasks import predict
predict.delay(pw.pk, pred_setting.pk, limit=limit) predict.delay(pw.pk, prediction_setting.pk, limit=limit)
return redirect(pw.url) return redirect(pw.url)
@ -1254,6 +1383,14 @@ def package_pathway(request, package_uuid, pathway_uuid):
if request.GET.get("last_modified", False): if request.GET.get("last_modified", False):
return JsonResponse({'modified': current_pathway.modified.strftime('%Y-%m-%d %H:%M:%S')}) return JsonResponse({'modified': current_pathway.modified.strftime('%Y-%m-%d %H:%M:%S')})
if request.GET.get("download", False) == "true":
filename = f"{current_pathway.name.replace(' ', '_')}_{current_pathway.uuid}.csv"
csv_pw = current_pathway.to_csv()
response = HttpResponse(csv_pw, content_type='text/csv')
response['Content-Disposition'] = f'attachment; filename="{filename}"'
return response
context = get_base_context(request) context = get_base_context(request)
context['title'] = f'enviPath - {current_package.name} - {current_pathway.name}' context['title'] = f'enviPath - {current_package.name} - {current_pathway.name}'
@ -1262,6 +1399,7 @@ def package_pathway(request, package_uuid, pathway_uuid):
context['breadcrumbs'] = breadcrumbs(current_package, 'pathway', current_pathway) context['breadcrumbs'] = breadcrumbs(current_package, 'pathway', current_pathway)
context['pathway'] = current_pathway context['pathway'] = current_pathway
context['current_object'] = current_pathway
context['breadcrumbs'] = [ context['breadcrumbs'] = [
{'Home': s.SERVER_URL}, {'Home': s.SERVER_URL},
@ -1276,12 +1414,18 @@ def package_pathway(request, package_uuid, pathway_uuid):
elif request.method == 'POST': elif request.method == 'POST':
if hidden := request.POST.get('hidden', None): if hidden := request.POST.get('hidden', None):
if hidden == 'delete-pathway': if hidden == 'delete':
current_pathway.delete() current_pathway.delete()
return redirect(current_package.url + '/pathway') return redirect(current_package.url + '/pathway')
else: else:
return HttpResponseBadRequest() return HttpResponseBadRequest()
selected_scenarios = request.POST.getlist('selected-scenarios')
if selected_scenarios:
set_scenarios(current_user, current_pathway, selected_scenarios)
return redirect(current_pathway.url)
pathway_name = request.POST.get('pathway-name') pathway_name = request.POST.get('pathway-name')
pathway_description = request.POST.get('pathway-description') pathway_description = request.POST.get('pathway-description')
@ -1402,19 +1546,33 @@ def package_pathway_node(request, package_uuid, pathway_uuid, node_uuid):
] ]
context['node'] = current_node context['node'] = current_node
context['current_object'] = current_node
context['app_domain_assessment_data'] = json.dumps(current_node.get_app_domain_assessment_data())
return render(request, 'objects/node.html', context) return render(request, 'objects/node.html', context)
elif request.method == 'POST': elif request.method == 'POST':
if s.DEBUG:
for k, v in request.POST.items(): log_post_params(request)
print(k, v)
if hidden := request.POST.get('hidden', None): if hidden := request.POST.get('hidden', None):
if hidden == 'delete-node': if hidden == 'delete':
current_node.delete()
return redirect(current_pathway.url)
# pre_delete signal will take care of edge deletion
current_node.delete()
return redirect(current_pathway.url)
else:
return HttpResponseBadRequest()
selected_scenarios = request.POST.getlist('selected-scenarios')
if selected_scenarios:
set_scenarios(current_user, current_node, selected_scenarios)
return redirect(current_node.url)
return HttpResponseBadRequest()
else: else:
return HttpResponseNotAllowed(['GET', 'POST']) return HttpResponseNotAllowed(['GET', 'POST'])
@ -1463,6 +1621,8 @@ def package_pathway_edges(request, package_uuid, pathway_uuid):
elif request.method == 'POST': elif request.method == 'POST':
log_post_params(request)
edge_name = request.POST.get('edge-name') edge_name = request.POST.get('edge-name')
edge_description = request.POST.get('edge-description') edge_description = request.POST.get('edge-description')
edge_substrates = request.POST.getlist('edge-substrates') edge_substrates = request.POST.getlist('edge-substrates')
@ -1499,22 +1659,30 @@ def package_pathway_edge(request, package_uuid, pathway_uuid, edge_uuid):
'title'] = f'enviPath - {current_package.name} - {current_pathway.name} - {current_edge.edge_label.name}' 'title'] = f'enviPath - {current_package.name} - {current_pathway.name} - {current_edge.edge_label.name}'
context['meta']['current_package'] = current_package context['meta']['current_package'] = current_package
context['object_type'] = 'reaction' context['object_type'] = 'edge'
context['breadcrumbs'] = breadcrumbs(current_package, 'pathway', current_pathway, 'edge', current_edge) context['breadcrumbs'] = breadcrumbs(current_package, 'pathway', current_pathway, 'edge', current_edge)
context['edge'] = current_edge context['edge'] = current_edge
context['current_object'] = current_edge
return render(request, 'objects/edge.html', context) return render(request, 'objects/edge.html', context)
elif request.method == 'POST': elif request.method == 'POST':
if s.DEBUG:
for k, v in request.POST.items(): log_post_params(request)
print(k, v)
if hidden := request.POST.get('hidden', None): if hidden := request.POST.get('hidden', None):
if hidden == 'delete-edge': if hidden == 'delete':
current_edge.delete() current_edge.delete()
return redirect(current_pathway.url) return redirect(current_pathway.url)
selected_scenarios = request.POST.getlist('selected-scenarios')
if selected_scenarios:
set_scenarios(current_user, current_edge, selected_scenarios)
return redirect(current_edge.url)
return HttpResponseBadRequest()
else: else:
return HttpResponseNotAllowed(['GET', 'POST']) return HttpResponseNotAllowed(['GET', 'POST'])
@ -1525,6 +1693,12 @@ def package_scenarios(request, package_uuid):
current_package = PackageManager.get_package_by_id(current_user, package_uuid) current_package = PackageManager.get_package_by_id(current_user, package_uuid)
if request.method == 'GET': if request.method == 'GET':
if 'application/json' in request.META.get('HTTP_ACCEPT'): #request.headers.get('Accept') == 'application/json':
scens = Scenario.objects.filter(package=current_package).order_by('name')
res = [{'name': s.name, 'url': s.url, 'uuid': s.uuid} for s in scens]
return JsonResponse(res, safe=False)
context = get_base_context(request) context = get_base_context(request)
context['title'] = f'enviPath - {current_package.name} - Scenarios' context['title'] = f'enviPath - {current_package.name} - Scenarios'
@ -1587,7 +1761,6 @@ def users(request):
context = get_base_context(request) context = get_base_context(request)
context['title'] = f'enviPath - Users' context['title'] = f'enviPath - Users'
context['meta']['current_package'] = context['meta']['user'].default_package
context['object_type'] = 'user' context['object_type'] = 'user'
context['breadcrumbs'] = [ context['breadcrumbs'] = [
{'Home': s.SERVER_URL}, {'Home': s.SERVER_URL},
@ -1611,27 +1784,27 @@ def user(request, user_uuid):
if str(current_user.uuid) != user_uuid and not current_user.is_superuser: if str(current_user.uuid) != user_uuid and not current_user.is_superuser:
return HttpResponseBadRequest() return HttpResponseBadRequest()
user = UserManager.get_user_by_id(current_user, user_uuid) requested_user = UserManager.get_user_by_id(current_user, user_uuid)
context = get_base_context(request) context = get_base_context(request, for_user=requested_user)
context['title'] = f'enviPath - User' context['title'] = f'enviPath - User'
context['object_type'] = 'user' context['object_type'] = 'user'
context['breadcrumbs'] = [ context['breadcrumbs'] = [
{'Home': s.SERVER_URL}, {'Home': s.SERVER_URL},
{'User': s.SERVER_URL + '/user'}, {'User': s.SERVER_URL + '/user'},
{current_user.username: current_user.url} {current_user.username: requested_user.url}
] ]
context['user'] = user context['user'] = requested_user
model_qs = EPModel.objects.none() model_qs = EPModel.objects.none()
for p in PackageManager.get_all_readable_packages(current_user, include_reviewed=True): for p in PackageManager.get_all_readable_packages(requested_user, include_reviewed=True):
model_qs |= p.models model_qs |= p.models
context['models'] = model_qs context['models'] = model_qs
context['tokens'] = APIToken.objects.filter(user=current_user) context['tokens'] = APIToken.objects.filter(user=requested_user)
return render(request, 'objects/user.html', context) return render(request, 'objects/user.html', context)
@ -1700,8 +1873,6 @@ def user(request, user_uuid):
} }
} }
print(setting)
return HttpResponseBadRequest() return HttpResponseBadRequest()
else: else:
@ -1715,7 +1886,6 @@ def groups(request):
context = get_base_context(request) context = get_base_context(request)
context['title'] = f'enviPath - Groups' context['title'] = f'enviPath - Groups'
context['meta']['current_package'] = context['meta']['user'].default_package
context['object_type'] = 'group' context['object_type'] = 'group'
context['breadcrumbs'] = [ context['breadcrumbs'] = [
{'Home': s.SERVER_URL}, {'Home': s.SERVER_URL},
@ -1764,12 +1934,10 @@ def group(request, group_uuid):
elif request.method == 'POST': elif request.method == 'POST':
if s.DEBUG: log_post_params(request)
for k, v in request.POST.items():
print(k, v)
if hidden := request.POST.get('hidden', None): if hidden := request.POST.get('hidden', None):
if hidden == 'delete-group': if hidden == 'delete':
current_group.delete() current_group.delete()
return redirect(s.SERVER_URL + '/group') return redirect(s.SERVER_URL + '/group')
else: else:

File diff suppressed because one or more lines are too long

View File

@ -38,10 +38,8 @@ def migration(request):
res = True res = True
for comp, ambit_prod in zip(bt_rule['compounds'], bt_rule['products']): for comp, ambit_prod in zip(bt_rule['compounds'], bt_rule['products']):
# if comp['smiles'] != 'CC1=C(C(=C(C=N1)CO)C=O)O':
# continue
products = FormatConverter.apply(comp['smiles'], smirks, preprocess_smiles=True, bracketize=False) products = FormatConverter.apply(comp['smiles'], smirks)
all_rdkit_prods = [] all_rdkit_prods = []
for ps in products: for ps in products:
@ -130,7 +128,7 @@ def migration_detail(request, package_uuid, rule_uuid):
# if comp['smiles'] != 'CC1=C(C(=C(C=N1)CO)C=O)O': # if comp['smiles'] != 'CC1=C(C(=C(C=N1)CO)C=O)O':
# continue # continue
products = FormatConverter.apply(comp['smiles'], smirks, preprocess_smiles=True, bracketize=False) products = FormatConverter.apply(comp['smiles'], smirks)
all_rdkit_prods = [] all_rdkit_prods = []
for ps in products: for ps in products:

View File

@ -638,3 +638,150 @@ function fillPRCurve(modelUri, onclick){
}); });
} }
function handleAssessmentResponse(depict_url, data) {
var inside_app_domain = "<a class='list-group-item'>This compound is " + (data["assessment"]["inside_app_domain"] ? "inside" : "outside") + " the Applicability Domain derived from the chemical (PCA) space constructed using the training data." + "</a>";
var functionalGroupsImgSrc = null;
var reactivityCentersImgSrc = null;
if (data['assessment']['node'] !== undefined) {
functionalGroupsImgSrc = "<img width='400' src='" + data['assessment']['node']['image'] + "'>";
reactivityCentersImgSrc = "<img width='400' src='" + data['assessment']['node']['image'] + "'>"
} else {
functionalGroupsImgSrc = "<img width='400' src=\"" + depict_url + "?smiles=" + encodeURIComponent(data['assessment']['smiles']) + "\">";
reactivityCentersImgSrc = "<img width='400' src=\"" + depict_url + "?smiles=" + encodeURIComponent(data['assessment']['smiles']) + "\">"
}
tpl = `<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title">
<a id="app-domain-assessment-functional-groups-link" data-toggle="collapse" data-parent="#app-domain-assessment" href="#app-domain-assessment-functional-groups">Functional Groups Covered by Model</a>
</h4>
</div>
<div id="app-domain-assessment-functional-groups" class="panel-collapse collapse">
<div class="panel-body list-group-item">
${inside_app_domain}
<p></p>
<div id="image-div" align="center">
${functionalGroupsImgSrc}
</div>
</div>
</div>
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title">
<a id="app-domain-assessment-reactivity-centers-link" data-toggle="collapse" data-parent="#app-domain-assessment" href="#app-domain-assessment-reactivity-centers">Reactivity Centers</a>
</h4>
</div>
<div id="app-domain-assessment-reactivity-centers" class="panel-collapse collapse">
<div class="panel-body list-group-item">
<div id="image-div" align="center">
${reactivityCentersImgSrc}
</div>
</div>
</div>`
var transformations = '';
for (t in data['assessment']['transformations']) {
transObj = data['assessment']['transformations'][t];
var neighbors = '';
for (n in transObj['neighbors']) {
neighObj = transObj['neighbors'][n];
var neighImg = "<img width='100%' src='" + transObj['rule']['url'] + "?smiles=" + encodeURIComponent(neighObj['smiles']) + "'>";
var objLink = `<a class='list-group-item' href="${neighObj['url']}">${neighObj['name']}</a>`
var neighPredProb = "<a class='list-group-item'>Predicted probability: " + neighObj['probability'].toFixed(2) + "</a>";
var pwLinks = '';
for (pw in neighObj['related_pathways']) {
var pwObj = neighObj['related_pathways'][pw];
pwLinks += "<a class='list-group-item' href=" + pwObj['url'] + ">" + pwObj['name'] + "</a>";
}
var expPathways = `
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title">
<a id="transformation-${t}-neighbor-${n}-exp-pathway-link" data-toggle="collapse" data-parent="#transformation-${t}-neighbor-${n}" href="#transformation-${t}-neighbor-${n}-exp-pathway">Experimental Pathways</a>
</h4>
</div>
<div id="transformation-${t}-neighbor-${n}-exp-pathway" class="panel-collapse collapse">
<div class="panel-body list-group-item">
${pwLinks}
</div>
</div>
`
if (pwLinks === '') {
expPathways = ''
}
neighbors += `
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title">
<a id="transformation-${t}-neighbor-${n}-link" data-toggle="collapse" data-parent="#transformation-${t}" href="#transformation-${t}-neighbor-${n}">Analog Transformation on ${neighObj['name']}</a>
</h4>
</div>
<div id="transformation-${t}-neighbor-${n}" class="panel-collapse collapse">
<div class="panel-body list-group-item">
${objLink}
${neighPredProb}
${expPathways}
<p></p>
<div id="image-div" align="center">
${neighImg}
</div>
</div>
</div>
`
}
var panelName = null;
var objLink = null;
if (transObj['is_predicted']) {
panelName = `Predicted Transformation by ${transObj['rule']['name']}`;
for (e in transObj['edges']) {
objLink = `<a class='list-group-item' href="${transObj['edges'][e]['url']}">${transObj['edges'][e]['name']}</a>`
break;
}
} else {
panelName = `Potential Transformation by applying ${transObj['rule']['name']}`;
objLink = `<a class='list-group-item' href="${transObj['rule']['url']}">${transObj['rule']['name']}</a>`
}
var predProb = "<a class='list-group-item'>Predicted probability: " + transObj['probability'].toFixed(2) + "</a>";
var timesTriggered = "<a class='list-group-item'>This rule has triggered " + transObj['times_triggered'] + " times in the training set</a>";
var reliability = "<a class='list-group-item'>Reliability: " + transObj['reliability'].toFixed(2) + " (" + (transObj['reliability'] > data['ad_params']['reliability_threshold'] ? "&gt" : "&lt") + " Reliability Threshold of " + data['ad_params']['reliability_threshold'] + ") </a>";
var localCompatibility = "<a class='list-group-item'>Local Compatibility: " + transObj['local_compatibility'].toFixed(2) + " (" + (transObj['local_compatibility'] > data['ad_params']['local_compatibilty_threshold'] ? "&gt" : "&lt") + " Local Compatibility Threshold of " + data['ad_params']['local_compatibilty_threshold'] + ")</a>";
var transImg = "<img width='100%' src='" + transObj['rule']['url'] + "?smiles=" + encodeURIComponent(data['assessment']['smiles']) + "'>";
var transformation = `
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title">
<a id="transformation-${t}-link" data-toggle="collapse" data-parent="#transformation-${t}" href="#transformation-${t}">${panelName}</a>
</h4>
</div>
<div id="transformation-${t}" class="panel-collapse collapse">
<div class="panel-body list-group-item">
${objLink}
${predProb}
${timesTriggered}
${reliability}
${localCompatibility}
<p></p>
<div id="image-div" align="center">
${transImg}
</div>
<p></p>
${neighbors}
</div>
</div>
`
transformations += transformation;
}
res = tpl + transformations;
$("#appDomainAssessmentResultTable").append(res);
}

View File

@ -1,5 +1,4 @@
console.log("loaded") console.log("loaded pw.js")
function predictFromNode(url) { function predictFromNode(url) {
$.post("", {node: url}) $.post("", {node: url})
@ -28,61 +27,164 @@ function draw(pathway, elem) {
const horizontalSpacing = 75; // horizontal space between nodes const horizontalSpacing = 75; // horizontal space between nodes
const depthMap = new Map(); const depthMap = new Map();
nodes.forEach(node => { // Sort nodes by depth first to minimize crossings
const sortedNodes = [...nodes].sort((a, b) => a.depth - b.depth);
sortedNodes.forEach(node => {
if (!depthMap.has(node.depth)) { if (!depthMap.has(node.depth)) {
depthMap.set(node.depth, 0); depthMap.set(node.depth, 0);
} }
const nodesInLevel = nodes.filter(n => n.depth === node.depth).length; const nodesInLevel = nodes.filter(n => n.depth === node.depth).length;
node.fx = width / 2 + depthMap.get(node.depth) * horizontalSpacing - ((nodesInLevel - 1) * horizontalSpacing) / 2;
node.fy = node.depth * levelSpacing + 50;
// For pseudo nodes, try to position them to minimize crossings
if (node.pseudo) {
const parentLinks = links.filter(l => l.target.id === node.id);
const childLinks = links.filter(l => l.source.id === node.id);
if (parentLinks.length > 0 && childLinks.length > 0) {
const parentX = parentLinks[0].source.x || (width / 2);
const childrenX = childLinks.map(l => l.target.x || (width / 2));
const avgChildX = childrenX.reduce((sum, x) => sum + x, 0) / childrenX.length;
// Position pseudo node between parent and average child position
node.fx = (parentX + avgChildX) / 2;
} else {
node.fx = width / 2 + depthMap.get(node.depth) * horizontalSpacing - ((nodesInLevel - 1) * horizontalSpacing) / 2;
}
} else {
node.fx = width / 2 + depthMap.get(node.depth) * horizontalSpacing - ((nodesInLevel - 1) * horizontalSpacing) / 2;
}
node.fy = node.depth * levelSpacing + 50;
depthMap.set(node.depth, depthMap.get(node.depth) + 1); depthMap.set(node.depth, depthMap.get(node.depth) + 1);
}); });
} }
// Funktion für das Update der Positionen // Function to update pseudo node positions based on connected nodes
function updatePseudoNodePositions() {
nodes.forEach(node => {
if (node.pseudo && !node.isDragging) { // Don't auto-update if being dragged
const parentLinks = links.filter(l => l.target.id === node.id);
const childLinks = links.filter(l => l.source.id === node.id);
if (parentLinks.length > 0 && childLinks.length > 0) {
const parent = parentLinks[0].source;
const children = childLinks.map(l => l.target);
// Calculate optimal position to minimize crossing
const parentX = parent.x;
const parentY = parent.y;
const childrenX = children.map(c => c.x);
const childrenY = children.map(c => c.y);
const avgChildX = d3.mean(childrenX);
const avgChildY = d3.mean(childrenY);
// Position pseudo node between parent and average child position
node.fx = (parentX + avgChildX) / 2;
node.fy = (parentY + avgChildY) / 2; // Allow vertical movement too
}
}
});
}
// Enhanced ticked function
function ticked() { function ticked() {
// Update pseudo node positions first
updatePseudoNodePositions();
link.attr("x1", d => d.source.x) link.attr("x1", d => d.source.x)
.attr("y1", d => d.source.y) .attr("y1", d => d.source.y)
.attr("x2", d => d.target.x) .attr("x2", d => d.target.x)
.attr("y2", d => d.target.y); .attr("y2", d => d.target.y);
node.attr("transform", d => `translate(${d.x},${d.y})`); node.attr("transform", d => `translate(${d.x},${d.y})`);
nodes.forEach(n => {
if (n.pseudo) {
// Alle Kinder dieses Pseudonodes finden
const childLinks = links.filter(l => l.source.id === n.id);
const childNodes = childLinks.map(l => l.target);
if (childNodes.length > 0) {
// Durchschnitt der Kinderpositionen berechnen
const avgX = d3.mean(childNodes, d => d.x);
const avgY = d3.mean(childNodes, d => d.y);
n.fx = avgX;
// keep level as is
n.fy = n.y;
}
}
});
//simulation.alpha(0.3).restart();
} }
function dragstarted(event, d) { function dragstarted(event, d) {
if (!event.active) simulation.alphaTarget(0.3).restart(); if (!event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x; // Setzt die Fixierung auf die aktuelle Position d.fx = d.x;
d.fy = d.y; d.fy = d.y;
// Mark if this node is being dragged
d.isDragging = true;
// If dragging a non-pseudo node, mark connected pseudo nodes for update
if (!d.pseudo) {
markConnectedPseudoNodes(d);
}
} }
function dragged(event, d) { function dragged(event, d) {
d.fx = event.x; // Position direkt an Maus anpassen d.fx = event.x;
d.fy = event.y; d.fy = event.y;
// Update connected pseudo nodes in real-time
if (!d.pseudo) {
updateConnectedPseudoNodes(d);
}
} }
function dragended(event, d) { function dragended(event, d) {
if (!event.active) simulation.alphaTarget(0); if (!event.active) simulation.alphaTarget(0);
// Knoten bleibt an der neuen Position und wird nicht zurückgezogen
// Mark that dragging has ended
d.isDragging = false;
// Final update of connected pseudo nodes
if (!d.pseudo) {
updateConnectedPseudoNodes(d);
} }
}
// Helper function to mark connected pseudo nodes
function markConnectedPseudoNodes(draggedNode) {
// Find pseudo nodes connected to this node
const connectedPseudos = new Set();
// Check as parent of pseudo nodes
links.filter(l => l.source.id === draggedNode.id && l.target.pseudo)
.forEach(l => connectedPseudos.add(l.target));
// Check as child of pseudo nodes
links.filter(l => l.target.id === draggedNode.id && l.source.pseudo)
.forEach(l => connectedPseudos.add(l.source));
return connectedPseudos;
}
// Helper function to update connected pseudo nodes
function updateConnectedPseudoNodes(draggedNode) {
const connectedPseudos = markConnectedPseudoNodes(draggedNode);
connectedPseudos.forEach(pseudoNode => {
if (!pseudoNode.isDragging) { // Don't update if pseudo node is being dragged
const parentLinks = links.filter(l => l.target.id === pseudoNode.id);
const childLinks = links.filter(l => l.source.id === pseudoNode.id);
if (parentLinks.length > 0 && childLinks.length > 0) {
const parent = parentLinks[0].source;
const children = childLinks.map(l => l.target);
const parentX = parent.fx || parent.x;
const parentY = parent.fy || parent.y;
const childrenX = children.map(c => c.fx || c.x);
const childrenY = children.map(c => c.fy || c.y);
const avgChildX = d3.mean(childrenX);
const avgChildY = d3.mean(childrenY);
// Update pseudo node position - allow both X and Y movement
pseudoNode.fx = (parentX + avgChildX) / 2;
pseudoNode.fy = (parentY + avgChildY) / 2;
}
}
});
// Restart simulation with lower alpha to smooth the transition
simulation.alpha(0.1).restart();
}
// t -> ref to "this" from d3 // t -> ref to "this" from d3
function nodeClick(event, node, t) { function nodeClick(event, node, t) {
@ -140,11 +242,20 @@ function draw(pathway, elem) {
}); });
} }
function node_popup(n) { function node_popup(n) {
popupContent = "<a href='" + n.url +"'>" + n.name + "</a><br>"; popupContent = "<a href='" + n.url + "'>" + n.name + "</a><br>";
popupContent += "Depth " + n.depth + "<br>" popupContent += "Depth " + n.depth + "<br>"
popupContent += "<img src='" + n.image + "' width='"+ 20 * nodeRadius +"'><br>"
if (appDomainViewEnabled) {
if (n.app_domain != null) {
popupContent += "This compound is " + (n.app_domain['inside_app_domain'] ? "inside" : "outside") + " the Applicability Domain derived from the chemical (PCA) space constructed using the training data." + "<br>"
if (n.app_domain['uncovered_functional_groups']) {
popupContent += "Compound contains functional groups not covered by the training set <br>"
}
}
}
popupContent += "<img src='" + n.image + "' width='" + 20 * nodeRadius + "'><br>"
if (n.scenarios.length > 0) { if (n.scenarios.length > 0) {
popupContent += '<b>Half-lives and related scenarios:</b><br>' popupContent += '<b>Half-lives and related scenarios:</b><br>'
for (var s of n.scenarios) { for (var s of n.scenarios) {
@ -153,7 +264,7 @@ function draw(pathway, elem) {
} }
var isLeaf = pathway.links.filter(obj => obj.source.id === n.id).length === 0; var isLeaf = pathway.links.filter(obj => obj.source.id === n.id).length === 0;
if(pathway.isIncremental && isLeaf) { if (pathway.isIncremental && isLeaf) {
popupContent += '<br><a class="btn btn-primary" onclick="predictFromNode(\'' + n.url + '\')" href="#">Predict from here</a><br>'; popupContent += '<br><a class="btn btn-primary" onclick="predictFromNode(\'' + n.url + '\')" href="#">Predict from here</a><br>';
} }
@ -161,13 +272,24 @@ function draw(pathway, elem) {
} }
function edge_popup(e) { function edge_popup(e) {
popupContent = "<a href='" + e.url +"'>" + e.name + "</a><br>"; popupContent = "<a href='" + e.url + "'>" + e.name + "</a><br>";
popupContent += "<img src='" + e.image + "' width='"+ 20 * nodeRadius +"'><br>"
if (e.app_domain) {
adcontent = "<p>";
if (e.app_domain["times_triggered"]) {
adcontent += "This rule triggered " + e.app_domain["times_triggered"] + " times in the training set<br>";
}
adcontent += "Reliability " + e.app_domain["reliability"].toFixed(2) + " (" + (e.app_domain["reliability"] > e.app_domain["reliability_threshold"] ? "&gt" : "&lt") + " Reliability Threshold of " + e.app_domain["reliability_threshold"] + ")<br>";
adcontent += "Local Compatibility " + e.app_domain["local_compatibility"].toFixed(2) + " (" + (e.app_domain["local_compatibility"] > e.app_domain["local_compatibility_threshold"] ? "&gt" : "&lt") + " Local Compatibility Threshold of " + e.app_domain["local_compatibility_threshold"] + ")<br>";
adcontent += "</p>";
popupContent += adcontent;
}
popupContent += "<img src='" + e.image + "' width='" + 20 * nodeRadius + "'><br>"
if (e.reaction_probability) { if (e.reaction_probability) {
popupContent += '<b>Probability:</b><br>' + e.reaction_probability.toFixed(3) + '<br>'; popupContent += '<b>Probability:</b><br>' + e.reaction_probability.toFixed(3) + '<br>';
} }
if (e.scenarios.length > 0) { if (e.scenarios.length > 0) {
popupContent += '<b>Half-lives and related scenarios:</b><br>' popupContent += '<b>Half-lives and related scenarios:</b><br>'
for (var s of e.scenarios) { for (var s of e.scenarios) {
@ -180,9 +302,9 @@ function draw(pathway, elem) {
var clientX; var clientX;
var clientY; var clientY;
document.addEventListener('mousemove', function(event) { document.addEventListener('mousemove', function (event) {
clientX = event.clientX; clientX = event.clientX;
clientY =event.clientY; clientY = event.clientY;
}); });
const zoomable = d3.select("#zoomable"); const zoomable = d3.select("#zoomable");
@ -233,13 +355,12 @@ function draw(pathway, elem) {
.enter().append("line") .enter().append("line")
// Check if target is pseudo and draw marker only if not pseudo // Check if target is pseudo and draw marker only if not pseudo
.attr("class", d => d.target.pseudo ? "link_no_arrow" : "link") .attr("class", d => d.target.pseudo ? "link_no_arrow" : "link")
// .on("mouseover", (event, d) => { .attr("marker-end", d => d.target.pseudo ? '' : 'url(#arrow)')
// tooltip.style("visibility", "visible")
// .text(`Link: ${d.source.id} → ${d.target.id}`) // add element to links array
// .style("top", `${event.pageY + 5}px`) link.each(function (d) {
// .style("left", `${event.pageX + 5}px`); d.el = this; // attach the DOM element to the data object
// }) });
// .on("mouseout", () => tooltip.style("visibility", "hidden"));
pop_add(link, "Reaction", edge_popup); pop_add(link, "Reaction", edge_popup);
@ -255,20 +376,10 @@ function draw(pathway, elem) {
.on("click", function (event, d) { .on("click", function (event, d) {
d3.select(this).select("circle").classed("highlighted", !d3.select(this).select("circle").classed("highlighted")); d3.select(this).select("circle").classed("highlighted", !d3.select(this).select("circle").classed("highlighted"));
}) })
// .on("mouseover", (event, d) => {
// if (d.pseudo) {
// return
// }
// tooltip.style("visibility", "visible")
// .text(`Node: ${d.id} Depth: ${d.depth}`)
// .style("top", `${event.pageY + 5}px`)
// .style("left", `${event.pageX + 5}px`);
// })
// .on("mouseout", () => tooltip.style("visibility", "hidden"));
// Kreise für die Knoten hinzufügen // Kreise für die Knoten hinzufügen
node.append("circle") node.append("circle")
// make radius "invisible" // make radius "invisible" for pseudo nodes
.attr("r", d => d.pseudo ? 0.01 : nodeRadius) .attr("r", d => d.pseudo ? 0.01 : nodeRadius)
.style("fill", "#e8e8e8"); .style("fill", "#e8e8e8");
@ -280,5 +391,10 @@ function draw(pathway, elem) {
.attr("width", nodeRadius * 2) .attr("width", nodeRadius * 2)
.attr("height", nodeRadius * 2); .attr("height", nodeRadius * 2);
// add element to nodes array
node.each(function (d) {
d.el = this; // attach the DOM element to the data object
});
pop_add(node, "Compound", node_popup); pop_add(node, "Compound", node_popup);
} }

View File

@ -1,4 +1,4 @@
{% if meta.can_edit %} {% if meta.can_edit and meta.enabled_features.MODEL_BUILDING %}
<li> <li>
<a role="button" data-toggle="modal" data-target="#new_model_modal"> <a role="button" data-toggle="modal" data-target="#new_model_modal">
<span class="glyphicon glyphicon-plus"></span> New Model</a> <span class="glyphicon glyphicon-plus"></span> New Model</a>

View File

@ -8,7 +8,11 @@
<i class="glyphicon glyphicon-plus"></i> Add Structure</a> <i class="glyphicon glyphicon-plus"></i> Add Structure</a>
</li> </li>
<li> <li>
<a class="button" data-toggle="modal" data-target="#delete_compound_modal"> <a role="button" data-toggle="modal" data-target="#set_scenario_modal">
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a>
</li>
<li>
<a class="button" data-toggle="modal" data-target="#generic_delete_modal">
<i class="glyphicon glyphicon-trash"></i> Delete Compound</a> <i class="glyphicon glyphicon-trash"></i> Delete Compound</a>
</li> </li>
{% endif %} {% endif %}

View File

@ -4,7 +4,11 @@
<i class="glyphicon glyphicon-edit"></i> Edit Compound Structure</a> <i class="glyphicon glyphicon-edit"></i> Edit Compound Structure</a>
</li> </li>
<li> <li>
<a class="button" data-toggle="modal" data-target="#delete_compound_structure_modal"> <a role="button" data-toggle="modal" data-target="#set_scenario_modal">
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a>
</li>
<li>
<a class="button" data-toggle="modal" data-target="#generic_delete_modal">
<i class="glyphicon glyphicon-trash"></i> Delete Compound Structure</a> <i class="glyphicon glyphicon-trash"></i> Delete Compound Structure</a>
</li> </li>
{% endif %} {% endif %}

View File

@ -0,0 +1,10 @@
{% if meta.can_edit %}
<li>
<a role="button" data-toggle="modal" data-target="#set_scenario_modal">
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a>
</li>
<li>
<a class="button" data-toggle="modal" data-target="#generic_delete_modal">
<i class="glyphicon glyphicon-trash"></i> Delete Edge</a>
</li>
{% endif %}

View File

@ -1,10 +1,10 @@
{% if meta.can_edit %} {% if meta.can_edit %}
<li>
<a role="button" data-toggle="modal" data-target="#delete_group_modal">
<i class="glyphicon glyphicon-trash"></i> Delete Group</a>
</li>
<li> <li>
<a role="button" data-toggle="modal" data-target="#edit_group_member_modal"> <a role="button" data-toggle="modal" data-target="#edit_group_member_modal">
<i class="glyphicon glyphicon-trash"></i> Add/Remove Member</a> <i class="glyphicon glyphicon-trash"></i> Add/Remove Member</a>
</li> </li>
<li>
<a role="button" data-toggle="modal" data-target="#generic_delete_modal">
<i class="glyphicon glyphicon-trash"></i> Delete Group</a>
</li>
{% endif %} {% endif %}

View File

@ -1,6 +1,6 @@
{% if meta.can_edit %} {% if meta.can_edit %}
<li> <li>
<a class="button" data-toggle="modal" data-target="#delete_model_modal"> <a class="button" data-toggle="modal" data-target="#generic_delete_modal">
<i class="glyphicon glyphicon-trash"></i> Delete Model</a> <i class="glyphicon glyphicon-trash"></i> Delete Model</a>
</li> </li>
{% endif %} {% endif %}

View File

@ -4,7 +4,11 @@
<i class="glyphicon glyphicon-edit"></i> Edit Node</a> <i class="glyphicon glyphicon-edit"></i> Edit Node</a>
</li> </li>
<li> <li>
<a class="button" data-toggle="modal" data-target="#delete_node_modal"> <a role="button" data-toggle="modal" data-target="#set_scenario_modal">
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a>
</li>
<li>
<a class="button" data-toggle="modal" data-target="#generic_delete_modal">
<i class="glyphicon glyphicon-trash"></i> Delete Node</a> <i class="glyphicon glyphicon-trash"></i> Delete Node</a>
</li> </li>
{% endif %} {% endif %}

View File

@ -7,12 +7,16 @@
<a role="button" data-toggle="modal" data-target="#edit_package_permissions_modal"> <a role="button" data-toggle="modal" data-target="#edit_package_permissions_modal">
<i class="glyphicon glyphicon-user"></i> Edit Permissions</a> <i class="glyphicon glyphicon-user"></i> Edit Permissions</a>
</li> </li>
<li>
<a role="button" data-toggle="modal" data-target="#publish_package_modal">
<i class="glyphicon glyphicon-bullhorn"></i> Publish Package</a>
</li>
<li> <li>
<a role="button" data-toggle="modal" data-target="#set_license_modal"> <a role="button" data-toggle="modal" data-target="#set_license_modal">
<i class="glyphicon glyphicon-duplicate"></i> License</a> <i class="glyphicon glyphicon-duplicate"></i> License</a>
</li> </li>
<li> <li>
<a class="button" data-toggle="modal" data-target="#delete_package_modal"> <a class="button" data-toggle="modal" data-target="#generic_delete_modal">
<i class="glyphicon glyphicon-trash"></i> Delete Package</a> <i class="glyphicon glyphicon-trash"></i> Delete Package</a>
</li> </li>
{% endif %} {% endif %}

View File

@ -9,13 +9,22 @@
</li> </li>
<li role="separator" class="divider"></li> <li role="separator" class="divider"></li>
<li> <li>
<a class="button" data-toggle="modal" data-target="#edit_pathway_modal"> <a class="button" data-toggle="modal" data-target="#download_pathway_modal">
<i class="glyphicon glyphicon-plus"></i> Edit Pathway</a> <i class="glyphicon glyphicon-floppy-save"></i> Download Pathway</a>
</li> </li>
{# <li>#} <li role="separator" class="divider"></li>
{# <a class="button" data-toggle="modal" data-target="#add_pathway_edge_modal">#} <li>
{# <i class="glyphicon glyphicon-plus"></i> Calculate Compound Properties</a>#} <a class="button" data-toggle="modal" data-target="#edit_pathway_modal">
{# </li>#} <i class="glyphicon glyphicon-edit"></i> Edit Pathway</a>
</li>
<li>
<a role="button" data-toggle="modal" data-target="#set_scenario_modal">
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a>
</li>
{# <li>#}
{# <a class="button" data-toggle="modal" data-target="#add_pathway_edge_modal">#}
{# <i class="glyphicon glyphicon-plus"></i> Calculate Compound Properties</a>#}
{# </li>#}
<li role="separator" class="divider"></li> <li role="separator" class="divider"></li>
<li> <li>
<a class="button" data-toggle="modal" data-target="#delete_pathway_node_modal"> <a class="button" data-toggle="modal" data-target="#delete_pathway_node_modal">
@ -26,7 +35,7 @@
<i class="glyphicon glyphicon-trash"></i> Delete Reaction</a> <i class="glyphicon glyphicon-trash"></i> Delete Reaction</a>
</li> </li>
<li> <li>
<a class="button" data-toggle="modal" data-target="#delete_pathway_modal"> <a class="button" data-toggle="modal" data-target="#generic_delete_modal">
<i class="glyphicon glyphicon-trash"></i> Delete Pathway</a> <i class="glyphicon glyphicon-trash"></i> Delete Pathway</a>
</li> </li>
{% endif %} {% endif %}

View File

@ -4,7 +4,11 @@
<i class="glyphicon glyphicon-edit"></i> Edit Reaction</a> <i class="glyphicon glyphicon-edit"></i> Edit Reaction</a>
</li> </li>
<li> <li>
<a class="button" data-toggle="modal" data-target="#delete_reaction_modal"> <a role="button" data-toggle="modal" data-target="#set_scenario_modal">
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a>
</li>
<li>
<a class="button" data-toggle="modal" data-target="#generic_delete_modal">
<i class="glyphicon glyphicon-trash"></i> Delete Reaction</a> <i class="glyphicon glyphicon-trash"></i> Delete Reaction</a>
</li> </li>
{% endif %} {% endif %}

View File

@ -3,4 +3,12 @@
<a role="button" data-toggle="modal" data-target="#edit_rule_modal"> <a role="button" data-toggle="modal" data-target="#edit_rule_modal">
<i class="glyphicon glyphicon-edit"></i> Edit Rule</a> <i class="glyphicon glyphicon-edit"></i> Edit Rule</a>
</li> </li>
<li>
<a role="button" data-toggle="modal" data-target="#set_scenario_modal">
<i class="glyphicon glyphicon-plus"></i> Set Scenarios</a>
</li>
<li>
<a class="button" data-toggle="modal" data-target="#generic_delete_modal">
<i class="glyphicon glyphicon-trash"></i> Delete Rule</a>
</li>
{% endif %} {% endif %}

View File

@ -16,7 +16,7 @@
{# <i class="glyphicon glyphicon-console"></i> Manage API Token</a>#} {# <i class="glyphicon glyphicon-console"></i> Manage API Token</a>#}
{# </li>#} {# </li>#}
<li> <li>
<a role="button" data-toggle="modal" data-target="#delete_user_modal"> <a role="button" data-toggle="modal" data-target="#generic_delete_modal">
<i class="glyphicon glyphicon-trash"></i> Delete Account</a> <i class="glyphicon glyphicon-trash"></i> Delete Account</a>
</li> </li>
{% endif %} {% endif %}

View File

@ -51,7 +51,7 @@
(function () { (function () {
var u = "//matomo.envipath.com/"; var u = "//matomo.envipath.com/";
_paq.push(['setTrackerUrl', u + 'matomo.php']); _paq.push(['setTrackerUrl', u + 'matomo.php']);
_paq.push(['setSiteId', '7']); _paq.push(['setSiteId', '10']);
var d = document, g = d.createElement('script'), s = d.getElementsByTagName('script')[0]; var d = document, g = d.createElement('script'), s = d.getElementsByTagName('script')[0];
g.async = true; g.async = true;
g.src = u + 'matomo.js'; g.src = u + 'matomo.js';
@ -83,21 +83,26 @@
<!-- Collect the nav links, forms, and other content for toggling --> <!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse collapse-framework navbar-collapse-framework" id="navbarCollapse"> <div class="collapse navbar-collapse collapse-framework navbar-collapse-framework" id="navbarCollapse">
<ul class="nav navbar-nav navbar-nav-framework"> <ul class="nav navbar-nav navbar-nav-framework">
<li class="dropdown">
<a data-toggle="dropdown" class="dropdown-toggle" href="#">Predict Pathway<b class="caret"></b></a>
<ul role="menu" class="dropdown-menu">
<li> <li>
<a class="button" data-toggle="modal" data-target="#predict_modal"> <a class="button" data-toggle="modal" data-target="#predict_modal">
<i class=" glyphicon glyphicon-tag"></i> Predict Pathway Predict Pathway
</a> </a>
</li> </li>
<li> {# <li class="dropdown">#}
<a class="button" data-toggle="modal" data-target="#batch_predict_modal"> {# <a data-toggle="dropdown" class="dropdown-toggle" href="#">Predict Pathway<b class="caret"></b></a>#}
<i class=" glyphicon glyphicon-tags"></i> Batch Prediction {# <ul role="menu" class="dropdown-menu">#}
</a> {# <li>#}
</li> {# <a class="button" data-toggle="modal" data-target="#predict_modal">#}
</ul> {# <i class=" glyphicon glyphicon-tag"></i> Predict Pathway#}
</li> {# </a>#}
{# </li>#}
{# <li>#}
{# <a class="button" data-toggle="modal" data-target="#batch_predict_modal">#}
{# <i class=" glyphicon glyphicon-tags"></i> Batch Prediction#}
{# </a>#}
{# </li>#}
{# </ul>#}
{# </li>#}
<li><a href="{{ meta.server_url }}/package" id="packageLink">Package</a></li> <li><a href="{{ meta.server_url }}/package" id="packageLink">Package</a></li>
<li><a href="{{ meta.server_url }}/search" id="searchLink">Search</a></li> <li><a href="{{ meta.server_url }}/search" id="searchLink">Search</a></li>
<li><a href="{{ meta.server_url }}/model" id="modelLink">Modelling</a></li> <li><a href="{{ meta.server_url }}/model" id="modelLink">Modelling</a></li>
@ -192,6 +197,23 @@
{% endif %} {% endif %}
{% block content %} {% block content %}
{% endblock content %} {% endblock content %}
{% if meta.current_package.license %}
<p></p>
<div class="panel-group" id="license_accordion">
<div class="panel panel-default list-group-item" style="background-color:#f5f5f5">
<div class="panel-title">
<a data-toggle="collapse" data-parent="#licence_accordion" href="#license">License</a>
</div>
</div>
<div id="license" class="panel-collapse collapse in">
<div class="panel-body list-group-item">
<a target="_blank" href="{{ meta.current_package.license.link }}">
<img src="{{ meta.current_package.license.image_link }}">
</a>
</div>
</div>
</div>
{% endif %}
</div> </div>
<!-- FOOTER --> <!-- FOOTER -->

View File

@ -143,6 +143,14 @@
} }
$(function () { $(function () {
$('#index-form').on("keydown", function (e) {
if (e.key === "Enter") {
e.preventDefault();
goButtonClicked();
}
});
// Code that should be executed once DOM is ready goes here // Code that should be executed once DOM is ready goes here
$('#dropdown-predict').on('click', actionDropdownClicked); $('#dropdown-predict').on('click', actionDropdownClicked);
$('#dropdown-search').on('click', actionDropdownClicked); $('#dropdown-search').on('click', actionDropdownClicked);

View File

@ -30,7 +30,7 @@
.center-button { .center-button {
position: absolute; position: absolute;
top: 50%; top: 70%;
left: 50%; left: 50%;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
z-index: 1; z-index: 1;
@ -38,7 +38,7 @@
.center-message { .center-message {
position: absolute; position: absolute;
top: 40%; top: 35%;
left: 50%; left: 50%;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
z-index: 1; z-index: 1;

View File

@ -16,14 +16,14 @@
<div class="jumbotron">Create a new Model to <div class="jumbotron">Create a new Model to
limit the number of degradation products in the limit the number of degradation products in the
prediction. You just need to set a name and the packages prediction. You just need to set a name and the packages
you want the object to be based on. If you want to use the you want the object to be based on. There are multiple types of models available.
default options suggested by us, simply click Submit, For additional information have a look at our
otherwise click Advanced Options. <a target="_blank" href="https://wiki.envipath.org/index.php/relative-reasoning" role="button">wiki &gt;&gt;</a>
</div> </div>
<label for="name">Name</label> <label for="model-name">Name</label>
<input id="name" name="model-name" class="form-control" placeholder="Name"/> <input id="model-name" name="model-name" class="form-control" placeholder="Name"/>
<label for="description">Description</label> <label for="model-description">Description</label>
<input id="description" name="model-description" class="form-control" <input id="model-description" name="model-description" class="form-control"
placeholder="Description"/> placeholder="Description"/>
<label for="model-type">Model Type</label> <label for="model-type">Model Type</label>
<select id="model-type" name="model-type" class="form-control" data-width='100%'> <select id="model-type" name="model-type" class="form-control" data-width='100%'>
@ -35,7 +35,7 @@
<!-- ML Based Form--> <!-- ML Based Form-->
<div id="ml-relative-reasoning-specific-form"> <div id="ml-relative-reasoning-specific-form">
<!-- Rule Packages --> <!-- Rule Packages -->
<label>Rule Packages</label><br> <label for="ml-relative-reasoning-rule-packages">Rule Packages</label>
<select id="ml-relative-reasoning-rule-packages" name="ml-relative-reasoning-rule-packages" <select id="ml-relative-reasoning-rule-packages" name="ml-relative-reasoning-rule-packages"
data-actions-box='true' class="form-control" multiple data-width='100%'> data-actions-box='true' class="form-control" multiple data-width='100%'>
<option disabled>Reviewed Packages</option> <option disabled>Reviewed Packages</option>
@ -53,7 +53,7 @@
{% endfor %} {% endfor %}
</select> </select>
<!-- Data Packages --> <!-- Data Packages -->
<label>Data Packages</label><br> <label for="ml-relative-reasoning-data-packages" >Data Packages</label>
<select id="ml-relative-reasoning-data-packages" name="ml-relative-reasoning-data-packages" <select id="ml-relative-reasoning-data-packages" name="ml-relative-reasoning-data-packages"
data-actions-box='true' class="form-control" multiple data-width='100%'> data-actions-box='true' class="form-control" multiple data-width='100%'>
<option disabled>Reviewed Packages</option> <option disabled>Reviewed Packages</option>
@ -77,22 +77,24 @@
class="form-control"> class="form-control">
<option value="MACCS" selected>MACCS Fingerprinter</option> <option value="MACCS" selected>MACCS Fingerprinter</option>
</select> </select>
{% if 'plugins' in meta.enabled_features %} {% if meta.enabled_features.PLUGINS and additional_descriptors %}
<!-- Property Plugins go here --> <!-- Property Plugins go here -->
<label for="ml-relative-reasoning-additional-fingerprinter">Fingerprinter</label> <label for="ml-relative-reasoning-additional-fingerprinter">Additional Fingerprinter / Descriptors</label>
<select id="ml-relative-reasoning-additional-fingerprinter" <select id="ml-relative-reasoning-additional-fingerprinter" name="ml-relative-reasoning-additional-fingerprinter" class="form-control">
name="ml-relative-reasoning-additional-fingerprinter" <option disabled selected>Select Additional Fingerprinter / Descriptor</option>
class="form-control"> {% for k, v in additional_descriptors.items %}
<option value="{{ v }}">{{ k }}</option>
{% endfor %}
</select> </select>
{% endif %} {% endif %}
<label for="ml-relative-reasoning-threshold">Threshold</label> <label for="ml-relative-reasoning-threshold">Threshold</label>
<input type="number" min="0" , max="1" step="0.05" value="0.5" <input type="number" min="0" max="1" step="0.05" value="0.5"
id="ml-relative-reasoning-threshold" id="ml-relative-reasoning-threshold"
name="ml-relative-reasoning-threshold" class="form-control"> name="ml-relative-reasoning-threshold" class="form-control">
<!-- Evaluation --> <!-- Evaluation -->
<label for="ml-relative-reasoning-evaluation-packages">Evaluation Packages</label>
<label>Evaluation Packages</label><br>
<select id="ml-relative-reasoning-evaluation-packages" name="ml-relative-reasoning-evaluation-packages" <select id="ml-relative-reasoning-evaluation-packages" name="ml-relative-reasoning-evaluation-packages"
data-actions-box='true' class="form-control" multiple data-width='100%'> data-actions-box='true' class="form-control" multiple data-width='100%'>
<option disabled>Reviewed Packages</option> <option disabled>Reviewed Packages</option>
@ -110,6 +112,26 @@
{% endfor %} {% endfor %}
</select> </select>
{% if meta.enabled_features.APPLICABILITY_DOMAIN %}
<!-- Build AD? -->
<div class="checkbox">
<label>
<input type="checkbox" id="build-app-domain" name="build-app-domain">Also build an Applicability Domain?
</label>
</div>
<!-- Num Neighbors -->
<label for="num-neighbors">Number of Neighbors</label>
<input id="num-neighbors" name="num-neighbors" type="number" class="form-control" value="5"
step="1" min="0" max="10">
<!-- Local Compatibility -->
<label for="local-compatibility-threshold">Local Compatibility Threshold</label>
<input id="local-compatibility-threshold" name="local-compatibility-threshold" type="number"
class="form-control" value="0.5" step="0.01" min="0" max="1">
<!-- Reliability -->
<label for="reliability-threshold">Reliability Threshold</label>
<input id="reliability-threshold" name="reliability-threshold" type="number"
class="form-control" value="0.5" step="0.01" min="0" max="1">
{% endif %}
</div> </div>
<!-- Rule Based Based Form--> <!-- Rule Based Based Form-->
<div id="rule-based-relative-reasoning-specific-form"> <div id="rule-based-relative-reasoning-specific-form">
@ -118,47 +140,9 @@
<!-- EnviFormer--> <!-- EnviFormer-->
<div id="enviformer-specific-form"> <div id="enviformer-specific-form">
<label for="enviformer-threshold">Threshold</label> <label for="enviformer-threshold">Threshold</label>
<input type="number" min="0" , max="1" step="0.05" value="0.5" id="enviformer-threshold" <input type="number" min="0" max="1" step="0.05" value="0.5" id="enviformer-threshold"
name="enviformer-threshold" class="form-control"> name="enviformer-threshold" class="form-control">
</div> </div>
{% if 'applicability_domain' in enabled_features %}
<div class="modal-body hide" data-step="3" data-title="Advanced Options II">
<div class="jumbotron">Selection of parameter values for the Applicability Domain process.
Number of Neighbours refers to a requirement on the minimum number of compounds from the
training
dataset that has at least one triggered transformation rule that is common with the compound
being
analyzed.
Reliability Threshold is a requirement on the average tanimoto distance to the set number of
"nearest neighbours" (Number of neighbours with the smallest tanimoto distances).
Local Compatibility Threshold is a requirement on the average F1 score determined from the
number of
nearest neighbours, using their respective precision and recall values computed from the
agreement
between their observed and triggered rules.
You can learn more about it in our wiki!
</div>
<!-- Use AD? -->
<div class="checkbox">
<label>
<input type="checkbox" id="buildAD" name="buildAD">Also build an Applicability Domain?
</label>
</div>
<!-- Num Neighbours -->
<label for="adK">Number of Neighbours</label>
<input id="adK" name="adK" type="number" class="form-control" value="5" step="1" min="0"
max="10">
<!-- F1 Threshold -->
<label for="localCompatibilityThreshold">Local Compatibility Threshold</label>
<input id="localCompatibilityThreshold" name="localCompatibilityThreshold" type="number"
class="form-control" value="0.5" step="0.01" min="0" max="1">
<!-- Percentile Threshold -->
<label for="reliabilityThreshold">Reliability Threshold</label>
<input id="reliabilityThreshold" name="reliabilityThreshold" type="number" class="form-control"
value="0.5" step="0.01" min="0" max="1">
</div>
{% endif %}
</form> </form>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
@ -179,6 +163,9 @@ $(function() {
$("#ml-relative-reasoning-rule-packages").selectpicker(); $("#ml-relative-reasoning-rule-packages").selectpicker();
$("#ml-relative-reasoning-data-packages").selectpicker(); $("#ml-relative-reasoning-data-packages").selectpicker();
$("#ml-relative-reasoning-evaluation-packages").selectpicker(); $("#ml-relative-reasoning-evaluation-packages").selectpicker();
if ($('#ml-relative-reasoning-additional-fingerprinter').length > 0) {
$("#ml-relative-reasoning-additional-fingerprinter").selectpicker();
}
// On change hide all and show only selected // On change hide all and show only selected
$("#model-type").change(function() { $("#model-type").change(function() {

View File

@ -1,38 +0,0 @@
{% load static %}
<!-- Delete Group -->
<div id="delete_group_modal" class="modal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title">Delete Group</h3>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="alert alert-danger">
Clicking "Delete" will <strong>permanently</strong> delete the Group.
This action can't be undone!
</div>
<form id="delete-group-modal-form" accept-charset="UTF-8" action="" data-remote="true" method="post">
{% csrf_token %}
<input type="hidden" name="hidden" value="delete-group">
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-danger" id="delete-group-modal-submit">Delete</button>
</div>
</div>
</div>
</div>
<script>
$(function() {
$('#delete-group-modal-submit').click(function(e){
e.preventDefault();
$('#delete-group-modal-form').submit();
});
})
</script>

View File

@ -1,35 +0,0 @@
{% load static %}
<!-- Delete Model -->
<div id="delete_model_modal" class="modal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title">Delete Model</h3>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
Deletes the Model.
<form id="delete-model-modal-form" accept-charset="UTF-8" action="" data-remote="true" method="post">
{% csrf_token %}
<input type="hidden" id="hidden" name="hidden" value="delete-model"/>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" id="delete-model-modal-submit">Delete</button>
</div>
</div>
</div>
</div>
<script>
$(function () {
$('#delete-model-modal-submit').click(function (e) {
e.preventDefault();
$('#delete-model-modal-form').submit();
});
})
</script>

View File

@ -1,35 +0,0 @@
{% load static %}
<!-- Delete Node -->
<div id="delete_node_modal" class="modal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title">Delete Node</h3>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
Deletes the Node as well as ingoing and outgoing edges.
<form id="delete-node-modal-form" accept-charset="UTF-8" action="" data-remote="true" method="post">
{% csrf_token %}
<input type="hidden" id="hidden" name="hidden" value="delete-node"/>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" id="delete-node-modal-submit">Delete</button>
</div>
</div>
</div>
</div>
<script>
$(function () {
$('#delete-node-modal-submit').click(function (e) {
e.preventDefault();
$('#delete-node-modal-form').submit();
});
})
</script>

View File

@ -1,36 +0,0 @@
{% load static %}
<!-- Delete Package -->
<div id="delete_package_modal" class="modal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title">Delete Package</h3>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
Deleting a Package deletes the very Package
as well as all Objects stored in the Package.
<form id="delete-package-modal-form" accept-charset="UTF-8" action="" data-remote="true" method="post">
{% csrf_token %}
<input type="hidden" id="hidden" name="hidden" value="delete-package"/>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" id="delete-package-modal-submit">Delete</button>
</div>
</div>
</div>
</div>
<script>
$(function() {
$('#delete-package-modal-submit').click(function(e){
e.preventDefault();
$('#delete-package-modal-form').submit();
});
})
</script>

View File

@ -22,7 +22,7 @@
<option value="{{ e.url }}">{{ e.edge_label.name }}</option> <option value="{{ e.url }}">{{ e.edge_label.name }}</option>
{% endfor %} {% endfor %}
</select> </select>
<input type="hidden" id="hidden" name="hidden" value="delete-edge"/> <input type="hidden" id="hidden" name="hidden" value="delete"/>
</form> </form>
<p></p> <p></p>
<div id="delete_pathway_edge_image"></div> <div id="delete_pathway_edge_image"></div>

View File

@ -22,7 +22,7 @@
<option value="{{ n.url }}">{{ n.default_node_label.name }}</option> <option value="{{ n.url }}">{{ n.default_node_label.name }}</option>
{% endfor %} {% endfor %}
</select> </select>
<input type="hidden" id="hidden" name="hidden" value="delete-node"/> <input type="hidden" id="hidden" name="hidden" value="delete"/>
</form> </form>
<p></p> <p></p>
<div id="delete_pathway_node_image"></div> <div id="delete_pathway_node_image"></div>

View File

@ -1,35 +0,0 @@
{% load static %}
<!-- Delete Reaction -->
<div id="delete_reaction_modal" class="modal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title">Delete Reaction</h3>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
Deletes the Reaction.
<form id="delete-reaction-modal-form" accept-charset="UTF-8" action="" data-remote="true" method="post">
{% csrf_token %}
<input type="hidden" id="hidden" name="hidden" value="delete-reaction"/>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" id="delete-reaction-modal-submit">Delete</button>
</div>
</div>
</div>
</div>
<script>
$(function () {
$('#delete-reaction-modal-submit').click(function (e) {
e.preventDefault();
$('#delete-reaction-modal-form').submit();
});
})
</script>

View File

@ -1,38 +0,0 @@
{% load static %}
<!-- Delete User -->
<div id="delete_user_modal" class="modal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title">Delete User</h3>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="alert alert-danger">
Clicking "Delete" will <strong>permanently</strong> delete the User and associated data.
This action can't be undone!
</div>
<form id="delete-user-modal-form" accept-charset="UTF-8" action="" data-remote="true" method="post">
{% csrf_token %}
<input type="hidden" name="hidden" value="delete-user">
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-danger" id="delete-user-modal-submit">Delete</button>
</div>
</div>
</div>
</div>
<script>
$(function() {
$('#delete-user-modal-submit').click(function(e){
e.preventDefault();
$('#delete-user-modal-form').submit();
});
})
</script>

View File

@ -1,24 +1,24 @@
{% load static %} {% load static %}
<!-- Delete Pathway --> <!-- Download Pathway -->
<div id="delete_pathway_modal" class="modal" tabindex="-1"> <div id="download_pathway_modal" class="modal" tabindex="-1">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h3 class="modal-title">Delete Pathway</h3> <h3 class="modal-title">Download Pathway</h3>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> <button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
Deletes the Pathway together with all Nodes and Edges. By clicking on Download the Pathway will be converted into a CSV and directly downloaded.
<form id="delete-pathway-modal-form" accept-charset="UTF-8" action="" data-remote="true" method="post"> <form id="download-pathway-modal-form" accept-charset="UTF-8" action="{{ pathway.url }}"
{% csrf_token %} data-remote="true" method="GET">
<input type="hidden" id="hidden" name="hidden" value="delete-pathway"/> <input type="hidden" name="download" value="true"/>
</form> </form>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button> <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" id="delete-pathway-modal-submit">Delete</button> <button type="button" class="btn btn-primary" id="download-pathway-modal-submit">Download</button>
</div> </div>
</div> </div>
</div> </div>
@ -26,9 +26,10 @@
<script> <script>
$(function () { $(function () {
$('#delete-pathway-modal-submit').click(function (e) { $('#download-pathway-modal-submit').click(function (e) {
e.preventDefault(); e.preventDefault();
$('#delete-pathway-modal-form').submit(); $('#download-pathway-modal-form').submit();
$('#download_pathway_modal').modal('hide');
}); });
}) })

View File

@ -0,0 +1,42 @@
{% load static %}
<!-- Delete Object -->
<div id="generic_delete_modal" class="modal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title">Delete {{ object_type|capfirst }}</h3>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
{% if object_type == 'user' %}
Clicking "Delete" will <strong>permanently</strong> delete the User and associated data.
This action can't be undone!
{% else %}
Deletes the {{ object_type|capfirst }}. Related objects that depend on this {{ object_type|capfirst }}
will be deleted as well.
{% endif %}
<form id="generic-delete-modal-form" accept-charset="UTF-8" action="{{ current_object.url }}"
data-remote="true" method="post">
{% csrf_token %}
<input type="hidden" id="hidden" name="hidden" value="delete"/>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" id="generic-delete-modal-form-submit">Delete</button>
</div>
</div>
</div>
</div>
<script>
$(function () {
$('#generic-delete-modal-form-submit').click(function (e) {
e.preventDefault();
$('#generic-delete-modal-form').submit();
});
})
</script>

View File

@ -0,0 +1,72 @@
{% load static %}
<div class="modal fade bs-modal-lg" id="set_scenario_modal" tabindex="-1" aria-labelledby="set_scenario_modal"
aria-modal="true" role="dialog">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
<h4 class="modal-title">Set Scenarios for {{ current_object.name }}</h4>
</div>
<div class="modal-body">
<div id="loading_scenario_div" class="text-center"></div>
<form id="set_scenario_modal_form" accept-charset="UTF-8" action="{{ current_object.url }}"
data-remote="true" method="post">
{% csrf_token %}
<label for="scenario-select">Scenarios</label>
<select id="scenario-select" name="selected-scenarios" data-actions-box='true' class="form-control"
multiple data-width='100%'>
<option disabled>Select Scenarios</option>
</select>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary pull-left" data-dismiss="modal">Close
</button>
<button type="button" class="btn btn-primary" id="set_scenario_modal_form_submit">Submit</button>
</div>
</div>
</div>
</div>
<script>
$(function () {
var loaded = false;
var attachedScenarios = []
{% if current_object.scenarios.all %}
{% for scen in current_object.scenarios.all %}
attachedScenarios.push('{{ scen.url }}')
{% endfor %}
{% endif %}
$('#scenario-select').selectpicker();
$('#set_scenario_modal').on('shown.bs.modal', function () {
if (!loaded) {
makeLoadingGif("#loading_scenario_div", "{% static '/images/wait.gif' %}");
$('#loading_scenario_div').append("<p></p><div class='alert alert-info'>Loading Scenarios...</div>");
$.getJSON("{% url 'package scenario list' meta.current_package.uuid %}").then(function (data) {
for(s in data) {
scenario = data[s]
var selected = attachedScenarios.includes(scenario.url);
$('#scenario-select').append(`<option value="${scenario.url}" ${selected ? 'selected' : ''}>${scenario.name}</option>`);
}
$('#scenario-select').selectpicker('refresh');
$("#loading_scenario_div").empty();
});
loaded = true;
}
$('#set_scenario_modal_form_submit').on('click', function (e) {
e.preventDefault();
$('#set_scenario_modal_form').submit();
});
});
});
</script>

View File

@ -1,24 +1,24 @@
{% load static %} {% load static %}
<!-- Delete Compound --> <!-- Publish a Package -->
<div id="delete_compound_modal" class="modal" tabindex="-1"> <div id="publish_package_modal" class="modal" tabindex="-1">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h3 class="modal-title">Delete Compound</h3> <h5 class="modal-title">Publish Package</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> <button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
Deletes the Compound and associated Structures. <p>Clicking on Publish will make this Package publicly available!</p>
<form id="delete-compound-modal-form" accept-charset="UTF-8" action="" data-remote="true" method="post"> <form id="publish-package-modal-form" accept-charset="UTF-8" action="{{ current_package.url }}" data-remote="true" method="post">
{% csrf_token %} {% csrf_token %}
<input type="hidden" id="hidden" name="hidden" value="delete-compound"/> <input type="hidden" name="hidden" value="publish-package">
</form> </form>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button> <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" id="delete-compound-modal-submit">Delete</button> <button type="button" class="btn btn-primary" id="publish-package-modal-form-submit">Publish</button>
</div> </div>
</div> </div>
</div> </div>
@ -26,9 +26,9 @@
<script> <script>
$(function() { $(function() {
$('#delete-compound-modal-submit').click(function(e){ $('#publish-package-modal-form-submit').click(function(e){
e.preventDefault(); e.preventDefault();
$('#delete-compound-modal-form').submit(); $('#publish-package-modal-form').submit();
}); });
}) })

View File

@ -49,6 +49,18 @@
<iframe id="predict-modal-ketcher" src="{% static '/js/ketcher2/ketcher.html' %}" width="100%" <iframe id="predict-modal-ketcher" src="{% static '/js/ketcher2/ketcher.html' %}" width="100%"
height="510"></iframe> height="510"></iframe>
</div> </div>
<label for="prediction-setting">Default Prediction Setting</label>
<select id="prediction-setting" name="prediction-setting" class="form-control"
data-width='100%'>
<option disabled>Select a Setting</option>
{% for s in meta.available_settings %}
<option value="{{ s.url }}"{% if s.id == meta.user.default_setting.id %}selected{% endif %}>
{{ s.name }}{% if s.id == meta.user.default_setting.id %} <i>(User default)</i>{% endif %}
</option>
{% endfor %}
</select>
</form> </form>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">

View File

@ -4,6 +4,8 @@
{% block action_modals %} {% block action_modals %}
{% include "modals/objects/edit_rule_modal.html" %} {% include "modals/objects/edit_rule_modal.html" %}
{% include "modals/objects/generic_set_scenario_modal.html" %}
{% include "modals/objects/generic_delete_modal.html" %}
{% endblock action_modals %} {% endblock action_modals %}
<div class="panel-group" id="rule-detail"> <div class="panel-group" id="rule-detail">
@ -49,7 +51,22 @@
</div> </div>
</div> </div>
<!-- Scenarios -->
{% if rule.scenarios.all %}
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title">
<a id="rule-scenario-link" data-toggle="collapse" data-parent="#rule-detail"
href="#rule-scenario">Scenarios</a>
</h4>
</div>
<div id="rule-scenario" class="panel-collapse collapse in">
<div class="panel-body list-group-item">
{% for s in rule.scenarios.all %}
<a class="list-group-item" href="{{ s.url }}">{{ s.name }} <i>({{ s.package.name }})</i></a>
{% endfor %}
</div>
</div>
{% endif %}
<!-- EC Numbers --> <!-- EC Numbers -->
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver"> <div class="panel panel-default panel-heading list-group-item" style="background-color:silver">

View File

@ -5,7 +5,8 @@
{% block action_modals %} {% block action_modals %}
{% include "modals/objects/edit_compound_modal.html" %} {% include "modals/objects/edit_compound_modal.html" %}
{% include "modals/objects/add_structure_modal.html" %} {% include "modals/objects/add_structure_modal.html" %}
{% include "modals/objects/delete_compound_modal.html" %} {% include "modals/objects/generic_set_scenario_modal.html" %}
{% include "modals/objects/generic_delete_modal.html" %}
{% endblock action_modals %} {% endblock action_modals %}
<div class="panel-group" id="compound-detail"> <div class="panel-group" id="compound-detail">
@ -134,7 +135,69 @@
</div> </div>
</div> </div>
{% endif %} {% endif %}
<!-- Scenarios -->
{% if compound.scenarios.all %}
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title">
<a id="compound-scenario-link" data-toggle="collapse" data-parent="#compound-detail"
href="#compound-scenario">Scenarios</a>
</h4>
</div>
<div id="compound-scenario" class="panel-collapse collapse in">
<div class="panel-body list-group-item">
{% for s in compound.scenarios.all %}
<a class="list-group-item" href="{{ s.url }}">{{ s.name }} <i>({{ s.package.name }})</i></a>
{% endfor %}
</div>
</div>
{% endif %}
<!-- External Identifiers --> <!-- External Identifiers -->
{% if compound.get_external_identifiers %}
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title">
<a id="compound-external-identifier-link" data-toggle="collapse" data-parent="#compound-detail"
href="#compound-external-identifier">External Identifier</a>
</h4>
</div>
<div id="compound-external-identifier" class="panel-collapse collapse in">
<div class="panel-body list-group-item">
{% if compound.get_pubchem_identifiers %}
<div class="panel panel-default panel-heading list-group-item"
style="background-color:silver">
<h4 class="panel-title">
<a id="compound-pubchem-identifier-link" data-toggle="collapse"
data-parent="#compound-external-identifier"
href="#compound-pubchem-identifier">PubChem Compound Identifier</a>
</h4>
</div>
<div id="compound-pubchem-identifier" class="panel-collapse collapse in">
{% for eid in compound.get_pubchem_identifiers %}
<a class="list-group-item"
href="{{ eid.external_url }}">CID{{ eid.identifier_value }}</a>
{% endfor %}
</div>
{% endif %}
{% if compound.get_chebi_identifiers %}
<div class="panel panel-default panel-heading list-group-item"
style="background-color:silver">
<h4 class="panel-title">
<a id="compound-chebi-identifier-link" data-toggle="collapse"
data-parent="#compound-external-identifier"
href="#compound-chebi-identifier">ChEBI Identifier</a>
</h4>
</div>
<div id="compound-chebi-identifier" class="panel-collapse collapse in">
{% for eid in compound.get_chebi_identifiers %}
<a class="list-group-item"
href="{{ eid.external_url }}">CHEBI:{{ eid.identifier_value }}</a>
{% endfor %}
</div>
{% endif %}
</div>
</div>
{% endif %}
</div> </div>

View File

@ -4,6 +4,8 @@
{% block action_modals %} {% block action_modals %}
{% include "modals/objects/edit_compound_structure_modal.html" %} {% include "modals/objects/edit_compound_structure_modal.html" %}
{% include "modals/objects/generic_set_scenario_modal.html" %}
{% include "modals/objects/generic_delete_modal.html" %}
{% endblock action_modals %} {% endblock action_modals %}
<div class="panel-group" id="compound-structure-detail"> <div class="panel-group" id="compound-structure-detail">
@ -54,6 +56,22 @@
</div> </div>
</div> </div>
{% if compound_structure.scenarios.all %}
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title">
<a id="compound_structure-scenario-link" data-toggle="collapse" data-parent="#compound-structure-detail"
href="#compound-structure-scenario">Scenarios</a>
</h4>
</div>
<div id="compound-structure-scenario" class="panel-collapse collapse in">
<div class="panel-body list-group-item">
{% for s in compound_structure.scenarios.all %}
<a class="list-group-item" href="{{ s.url }}">{{ s.name }} <i>({{ s.package.name }})</i></a>
{% endfor %}
</div>
</div>
{% endif %}
<!-- Reactions --> <!-- Reactions -->
<!-- Pathways --> <!-- Pathways -->

View File

@ -4,7 +4,8 @@
{% block action_modals %} {% block action_modals %}
{# {% include "modals/objects/edit_edge_modal.html" %}#} {# {% include "modals/objects/edit_edge_modal.html" %}#}
{# {% include "modals/objects/delete_edge_modal.html" %}#} {% include "modals/objects/generic_set_scenario_modal.html" %}
{% include "modals/objects/generic_delete_modal.html" %}
{% endblock action_modals %} {% endblock action_modals %}
<div class="panel-group" id="edge-detail"> <div class="panel-group" id="edge-detail">
@ -19,7 +20,7 @@
style="padding-right:1em"></span></a> style="padding-right:1em"></span></a>
<ul id="actionsList" class="dropdown-menu"> <ul id="actionsList" class="dropdown-menu">
{% block actions %} {% block actions %}
{# {% include "actions/objects/edge.html" %}#} {% include "actions/objects/edge.html" %}
{% endblock %} {% endblock %}
</ul> </ul>
</div> </div>
@ -103,6 +104,21 @@
</div> </div>
{% endif %} {% endif %}
{% if edge.scenarios.all %}
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title">
<a id="edge-scenario-link" data-toggle="collapse" data-parent="#edge-detail"
href="#edge-scenario">Scenarios</a>
</h4>
</div>
<div id="edge-scenario" class="panel-collapse collapse in">
<div class="panel-body list-group-item">
{% for s in edge.scenarios.all %}
<a class="list-group-item" href="{{ s.url }}">{{ s.name }} <i>({{ s.package.name }})</i></a>
{% endfor %}
</div>
</div>
{% endif %}
</div> </div>
{% endblock content %} {% endblock content %}

View File

@ -5,7 +5,7 @@
{% block action_modals %} {% block action_modals %}
{% include "modals/objects/edit_group_modal.html" %} {% include "modals/objects/edit_group_modal.html" %}
{% include "modals/objects/edit_group_member_modal.html" %} {% include "modals/objects/edit_group_member_modal.html" %}
{% include "modals/objects/delete_group_modal.html" %} {% include "modals/objects/generic_delete_modal.html" %}
{% endblock action_modals %} {% endblock action_modals %}
<div class="panel-group" id="package-detail"> <div class="panel-group" id="package-detail">

View File

@ -4,7 +4,7 @@
{% block content %} {% block content %}
{% block action_modals %} {% block action_modals %}
{% include "modals/objects/delete_model_modal.html" %} {% include "modals/objects/generic_delete_modal.html" %}
{% endblock action_modals %} {% endblock action_modals %}
<!-- Include required libs --> <!-- Include required libs -->
@ -90,6 +90,7 @@
</div> </div>
</div> </div>
{% endif %} {% endif %}
{% if model.ready_for_prediction %}
<!-- Predict Panel --> <!-- Predict Panel -->
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver"> <div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title"> <h4 class="panel-title">
@ -106,11 +107,36 @@
<button class="btn btn-default" type="submit" id="predict-button">Predict!</button> <button class="btn btn-default" type="submit" id="predict-button">Predict!</button>
</span> </span>
</div> </div>
<div id="loading"></div> <div id="predictLoading"></div>
<div id="predictResultTable"></div> <div id="predictResultTable"></div>
</div> </div>
</div> </div>
<!-- End Predict Panel --> <!-- End Predict Panel -->
{% endif %}
{% if model.app_domain %}
<!-- App Domain -->
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title">
<a id="app-domain-assessment-link" data-toggle="collapse" data-parent="#model-detail"
href="#app-domain-assessment">Applicability Domain Assessment</a>
</h4>
</div>
<div id="app-domain-assessment" class="panel-collapse collapse in">
<div class="panel-body list-group-item">
<div class="input-group">
<input id="smiles-to-assess" type="text" class="form-control" placeholder="CCN(CC)C(=O)C1=CC(=CC=C1)C">
<span class="input-group-btn">
<button class="btn btn-default" type="submit" id="assess-button">Assess!</button>
</span>
</div>
<div id="appDomainLoading"></div>
<div id="appDomainAssessmentResultTable"></div>
</div>
</div>
<!-- End App Domain -->
{% endif %}
{% if model.model_status == 'FINISHED' %} {% if model.model_status == 'FINISHED' %}
<!-- Single Gen Curve Panel --> <!-- Single Gen Curve Panel -->
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver"> <div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
@ -243,7 +269,7 @@
<script> <script>
function handleResponse(data) { function handlePredictionResponse(data) {
res = "<table class='table table-striped'>" res = "<table class='table table-striped'>"
res += "<thead>" res += "<thead>"
res += "<th scope='col'>#</th>" res += "<th scope='col'>#</th>"
@ -277,9 +303,9 @@
$("#predictResultTable").append(res); $("#predictResultTable").append(res);
} }
function clear() { function clear(divid) {
$("#predictResultTable").removeClass("alert alert-danger"); $("#" + divid).removeClass("alert alert-danger");
$("#predictResultTable").empty(); $("#" + divid).empty();
} }
if ($('#predict-button').length > 0) { if ($('#predict-button').length > 0) {
@ -291,32 +317,70 @@
"classify": "ILikeCats!" "classify": "ILikeCats!"
} }
clear(); clear("predictResultTable");
makeLoadingGif("#loading", "{% static '/images/wait.gif' %}"); makeLoadingGif("#predictLoading", "{% static '/images/wait.gif' %}");
$.ajax({ $.ajax({
type: 'get', type: 'get',
data: data, data: data,
url: '', url: '',
success: function (data, textStatus) { success: function (data, textStatus) {
try { try {
$("#loading").empty(); $("#predictLoading").empty();
handleResponse(data); handlePredictionResponse(data);
} catch (error) { } catch (error) {
console.log("Error"); console.log("Error");
$("#loading").empty(); $("#predictLoading").empty();
$("#predictResultTable").addClass("alert alert-danger"); $("#predictResultTable").addClass("alert alert-danger");
$("#predictResultTable").append("Error while processing request :/"); $("#predictResultTable").append("Error while processing request :/");
} }
}, },
error: function (jqXHR, textStatus, errorThrown) { error: function (jqXHR, textStatus, errorThrown) {
$("#loading").empty(); $("#predictLoading").empty();
$("#predictResultTable").addClass("alert alert-danger"); $("#predictResultTable").addClass("alert alert-danger");
$("#predictResultTable").append("Error while processing request :/"); $("#predictResultTable").append("Error while processing request :/");
} }
}); });
}); });
} }
if ($('#assess-button').length > 0) {
$("#assess-button").on("click", function (e) {
e.preventDefault();
data = {
"smiles": $("#smiles-to-assess").val(),
"app-domain-assessment": "ILikeCats!"
}
clear("appDomainAssessmentResultTable");
makeLoadingGif("#appDomainLoading", "{% static '/images/wait.gif' %}");
$.ajax({
type: 'get',
data: data,
url: '',
success: function (data, textStatus) {
try {
$("#appDomainLoading").empty();
handleAssessmentResponse("{% url 'depict' %}", data);
console.log(data);
} catch (error) {
console.log("Error");
$("#appDomainLoading").empty();
$("#appDomainAssessmentResultTable").addClass("alert alert-danger");
$("#appDomainAssessmentResultTable").append("Error while processing request :/");
}
},
error: function (jqXHR, textStatus, errorThrown) {
$("#appDomainLoading").empty();
$("#appDomainAssessmentResultTable").addClass("alert alert-danger");
$("#appDomainAssessmentResultTable").append("Error while processing request :/");
}
});
});
}
</script> </script>
{% endblock content %} {% endblock content %}

View File

@ -4,7 +4,8 @@
{% block action_modals %} {% block action_modals %}
{% include "modals/objects/edit_node_modal.html" %} {% include "modals/objects/edit_node_modal.html" %}
{% include "modals/objects/delete_node_modal.html" %} {% include "modals/objects/generic_set_scenario_modal.html" %}
{% include "modals/objects/generic_delete_modal.html" %}
{% endblock action_modals %} {% endblock action_modals %}
<div class="panel-group" id="node-detail"> <div class="panel-group" id="node-detail">
@ -69,7 +70,34 @@
</div> </div>
</div> </div>
{% if node.scenarios.all %}
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title">
<a id="node-scenario-link" data-toggle="collapse" data-parent="#node-detail"
href="#node-scenario">Scenarios</a>
</h4>
</div>
<div id="node-scenario" class="panel-collapse collapse in">
<div class="panel-body list-group-item">
{% for s in node.scenarios.all %}
<a class="list-group-item" href="{{ s.url }}">{{ s.name }} <i>({{ s.package.name }})</i></a>
{% endfor %}
</div>
</div>
{% endif %}
{% if app_domain_assessment_data %}
<div id="appDomainAssessmentResultTable"></div>
<script>
$(document).ready(function () {
handleAssessmentResponse("{% url 'depict' %}", {{ app_domain_assessment_data|safe }})
})
</script>
{% endif %}
</div> </div>
</div> </div>
{% endblock content %} {% endblock content %}

View File

@ -5,8 +5,9 @@
{% block action_modals %} {% block action_modals %}
{% include "modals/objects/edit_package_modal.html" %} {% include "modals/objects/edit_package_modal.html" %}
{% include "modals/objects/edit_package_permissions_modal.html" %} {% include "modals/objects/edit_package_permissions_modal.html" %}
{% include "modals/objects/publish_package_modal.html" %}
{% include "modals/objects/set_license_modal.html" %} {% include "modals/objects/set_license_modal.html" %}
{% include "modals/objects/delete_package_modal.html" %} {% include "modals/objects/generic_delete_modal.html" %}
{% endblock action_modals %} {% endblock action_modals %}
<div class="panel-group" id="package-detail"> <div class="panel-group" id="package-detail">
@ -52,23 +53,5 @@
</div> </div>
{% if package.license %}
<p></p>
<div class="panel-group" id="license_accordion">
<div class="panel panel-default list-group-item" style="background-color:#f5f5f5">
<div class="panel-title">
<a data-toggle="collapse" data-parent="#licence_accordion" href="#license">License</a>
</div>
</div>
<div id="license" class="panel-collapse collapse in">
<div class="panel-body list-group-item">
<a target="_blank" href="{{ package.license.link }}">
<img src="{{ package.license.image_link }}">
</a>
</div>
</div>
</div>
{% endif %}
</div> </div>
{% endblock content %} {% endblock content %}

View File

@ -4,16 +4,22 @@
<script src="https://d3js.org/d3.v7.min.js"></script> <script src="https://d3js.org/d3.v7.min.js"></script>
<style> <style>
svg { #vizdiv {
width: 100%; width: 100%;
height: 600px; height: 600px;
background: white;
}
#pwsvg {
width: 100%;
height: 100%;
color: red; color: red;
} }
.link { .link {
stroke: #999; stroke: #999;
stroke-opacity: 0.6; stroke-opacity: 0.6;
marker-end: url(#arrow); //marker-end: url(#arrow);
} }
.link_no_arrow { .link_no_arrow {
@ -31,6 +37,31 @@
stroke-width: 1.5px; stroke-width: 1.5px;
} }
.inside_app_domain {
fill: green;
stroke: green;
stroke-width: 1.5px;
}
.outside_app_domain {
fill: red;
stroke: red;
stroke-width: 1.5px;
}
.passes_app_domain {
stroke: green;
stroke-width: 1.5px;
stroke-opacity: 0.6;
}
.fails_app_domain {
stroke: red;
stroke-width: 1.5px;
stroke-opacity: 0.6;
}
.highlighted { .highlighted {
stroke: red; stroke: red;
stroke-width: 3px; stroke-width: 3px;
@ -50,10 +81,12 @@
{% block action_modals %} {% block action_modals %}
{% include "modals/objects/add_pathway_node_modal.html" %} {% include "modals/objects/add_pathway_node_modal.html" %}
{% include "modals/objects/add_pathway_edge_modal.html" %} {% include "modals/objects/add_pathway_edge_modal.html" %}
{% include "modals/objects/download_pathway_modal.html" %}
{% include "modals/objects/edit_pathway_modal.html" %} {% include "modals/objects/edit_pathway_modal.html" %}
{% include "modals/objects/generic_set_scenario_modal.html" %}
{% include "modals/objects/delete_pathway_node_modal.html" %} {% include "modals/objects/delete_pathway_node_modal.html" %}
{% include "modals/objects/delete_pathway_edge_modal.html" %} {% include "modals/objects/delete_pathway_edge_modal.html" %}
{% include "modals/objects/delete_pathway_modal.html" %} {% include "modals/objects/generic_delete_modal.html" %}
{% endblock action_modals %} {% endblock action_modals %}
<p></p> <p></p>
@ -79,8 +112,7 @@
<ul class="nav navbar-nav"> <ul class="nav navbar-nav">
<li class="dropdown requiresWritePerm"> <li class="dropdown requiresWritePerm">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
aria-haspopup="true" aria-haspopup="true" aria-expanded="false">
aria-expanded="false">
<span class="glyphicon glyphicon-edit"></span> <span class="glyphicon glyphicon-edit"></span>
Edit Edit
<span class="caret"></span></a> <span class="caret"></span></a>
@ -90,11 +122,26 @@
{% endblock %} {% endblock %}
</ul> </ul>
</li> </li>
{% if pathway.setting.model.app_domain %}
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
aria-haspopup="true" aria-expanded="false">
<span class="glyphicon glyphicon-eye-open"></span>
View
<span class="caret"></span></a>
<ul id="editingList" class="dropdown-menu">
<li>
<a class="button" id="app-domain-toggle-button">
<i id="app-domain-toggle-button" class="glyphicon glyphicon-eye-open"></i> App Domain View</a>
</li>
</ul>
</li>
{% endif %}
</ul> </ul>
<ul class="nav navbar-nav navbar-right"> <ul class="nav navbar-nav navbar-right">
<li> <li>
<a role="button" data-toggle="modal" onclick="goFullscreen('pwcontent')"> <a role="button" data-toggle="modal" onclick="goFullscreen('vizdiv')">
<span class="glyphicon glyphicon-fullscreen"></span> <span class="glyphicon glyphicon-fullscreen"></span>
Fullscreen Fullscreen
</a> </a>
@ -125,16 +172,24 @@
</div> </div>
</nav> </nav>
<div id="vizdiv"> <div id="vizdiv" >
<svg width="2000" height="2000"> <svg id="pwsvg">
{% if debug %} {% if debug %}
<rect width="100%" height="100%" fill="aliceblue"/> <rect width="100%" height="100%" fill="aliceblue"/>
{% endif %} {% endif %}
<defs> <defs>
<marker id="arrow" viewBox="0 0 10 10" refX="43" refY="5" markerWidth="6" markerHeight="6" <marker id="arrow" viewBox="0 0 10 10" refX="43" refY="5" markerWidth="6" markerHeight="6"
orient="auto-start-reverse"> orient="auto-start-reverse" markerUnits="userSpaceOnUse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#999"/> <path d="M 0 0 L 10 5 L 0 10 z" fill="#999"/>
</marker> </marker>
<marker id="arrow_passes_app_domain" viewBox="0 0 10 10" refX="43" refY="5" markerWidth="6" markerHeight="6"
orient="auto-start-reverse" markerUnits="userSpaceOnUse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="green"/>
</marker>
<marker id="arrow_fails_app_domain" viewBox="0 0 10 10" refX="43" refY="5" markerWidth="6" markerHeight="6"
orient="auto-start-reverse" markerUnits="userSpaceOnUse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="red"/>
</marker>
</defs> </defs>
<g id="zoomable"></g> <g id="zoomable"></g>
</svg> </svg>
@ -153,13 +208,29 @@
</div> </div>
</div> </div>
{% if pathway.scenarios.all %}
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title">
<a id="pathway-scenario-link" data-toggle="collapse" data-parent="#pathway-detail"
href="#pathway-scenario">Scenarios</a>
</h4>
</div>
<div id="pathway-scenario" class="panel-collapse collapse in">
<div class="panel-body list-group-item">
{% for s in pathway.scenarios.all %}
<a class="list-group-item" href="{{ s.url }}">{{ s.name }} <i>({{ s.package.name }})</i></a>
{% endfor %}
</div>
</div>
{% endif %}
{% if pathway.setting %} {% if pathway.setting %}
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver"> <div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title"> <h4 class="panel-title">
<a id="pathwaySettingLink" data-toggle="collapse" data-parent="#pathwayAccordion" <a id="pathwaySettingLink" data-toggle="collapse" data-parent="#pathwayAccordion"
href="#pathwaySetting">Setting</a></h4> href="#pathwaySetting">Setting</a></h4>
</div> </div>
<div id="pathwaySetting" class="panel-collapse collapse in"> <div id="pathwaySetting" class="panel-collapse collapse">
<div class="panel-body list-group-item" id="pathwaySettingContent"> <div class="panel-body list-group-item" id="pathwaySettingContent">
<table class="table table-bordered table-hover"> <table class="table table-bordered table-hover">
<tr style="background-color: rgba(0, 0, 0, 0.08);"> <tr style="background-color: rgba(0, 0, 0, 0.08);">
@ -245,6 +316,8 @@
</div> </div>
</div> </div>
<script> <script>
// Globla switch for app domain view
var appDomainViewEnabled = false;
function goFullscreen(id) { function goFullscreen(id) {
var element = document.getElementById(id); var element = document.getElementById(id);
@ -266,6 +339,51 @@
// TODO fix somewhere else... // TODO fix somewhere else...
var newDesc = transformReferences($('#DescriptionContent')[0].innerText); var newDesc = transformReferences($('#DescriptionContent')[0].innerText);
$('#DescriptionContent').html(newDesc); $('#DescriptionContent').html(newDesc);
$('#app-domain-toggle-button').on('click', function () {
// glyphicon glyphicon-eye-close
// glyphicon glyphicon-eye-open
appDomainViewEnabled = !appDomainViewEnabled;
if (appDomainViewEnabled) {
$('#app-domain-toggle-button > i').removeClass('glyphicon-eye-open');
$('#app-domain-toggle-button > i').addClass('glyphicon-eye-close');
nodes.forEach((x) => {
if(x.app_domain) {
if (x.app_domain.inside_app_domain) {
d3.select(x.el).select("circle").classed("inside_app_domain", true);
} else {
d3.select(x.el).select("circle").classed("outside_app_domain", true);
}
}
});
links.forEach((x) => {
if(x.app_domain) {
if (x.app_domain.passes_app_domain) {
d3.select(x.el).attr("marker-end", d => d.target.pseudo ? "" : "url(#arrow_passes_app_domain)");
d3.select(x.el).classed("passes_app_domain", true);
} else {
d3.select(x.el).attr("marker-end", d => d.target.pseudo ? "" : "url(#arrow_fails_app_domain)");
d3.select(x.el).classed("fails_app_domain", true);
}
}
});
} else {
$('#app-domain-toggle-button > i').removeClass('glyphicon-eye-close');
$('#app-domain-toggle-button > i').addClass('glyphicon-eye-open');
nodes.forEach((x) => {
d3.select(x.el).select("circle").classed("inside_app_domain", false);
d3.select(x.el).select("circle").classed("outside_app_domain", false);
});
links.forEach((x) => {
d3.select(x.el).attr("marker-end", d => d.target.pseudo ? "" : "url(#arrow)");
d3.select(x.el).classed("passes_app_domain", false);
d3.select(x.el).classed("fails_app_domain", false);
});
}
})
}); });
</script> </script>

View File

@ -4,7 +4,8 @@
{% block action_modals %} {% block action_modals %}
{% include "modals/objects/edit_reaction_modal.html" %} {% include "modals/objects/edit_reaction_modal.html" %}
{% include "modals/objects/delete_reaction_modal.html" %} {% include "modals/objects/generic_set_scenario_modal.html" %}
{% include "modals/objects/generic_delete_modal.html" %}
{% endblock action_modals %} {% endblock action_modals %}
<div class="panel-group" id="reaction-detail"> <div class="panel-group" id="reaction-detail">
@ -118,9 +119,70 @@
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
{% endif %}
{% if reaction.scenarios.all %}
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title">
<a id="reaction-scenario-link" data-toggle="collapse" data-parent="#reaction-detail"
href="#reaction-scenario">Scenarios</a>
</h4>
</div>
<div id="reaction-scenario" class="panel-collapse collapse in">
<div class="panel-body list-group-item">
{% for s in reaction.scenarios.all %}
<a class="list-group-item" href="{{ s.url }}">{{ s.name }} <i>({{ s.package.name }})</i></a>
{% endfor %}
</div>
</div>
{% endif %}
<!-- External Identifiers -->
{% if reaction.get_external_identifiers %}
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title">
<a id="reaction-external-identifier-link" data-toggle="collapse" data-parent="#reaction-detail"
href="#reaction-external-identifier">External Identifier</a>
</h4>
</div>
<div id="reaction-external-identifier" class="panel-collapse collapse in">
<div class="panel-body list-group-item">
{% if reaction.get_rhea_identifiers %}
<div class="panel panel-default panel-heading list-group-item"
style="background-color:silver">
<h4 class="panel-title">
<a id="reaction-rhea-identifier-link" data-toggle="collapse"
data-parent="#reaction-external-identifier"
href="#reaction-rhea-identifier">Rhea</a>
</h4>
</div>
<div id="reaction-rhea-identifier" class="panel-collapse collapse in">
{% for eid in reaction.get_rhea_identifiers %}
<a class="list-group-item"
href="{{ eid.external_url }}">{{ eid.identifier_value }}</a>
{% endfor %}
</div>
{% endif %}
{% if reaction.get_uniprot_identifiers %}
<div class="panel panel-default panel-heading list-group-item"
style="background-color:silver">
<h4 class="panel-title">
<a id="reaction-uniprot-identifier-link" data-toggle="collapse"
data-parent="#reaction-external-identifier"
href="#reaction-uniprot-identifier">UniProt</a>
</h4>
</div>
<div id="reaction-uniprot-identifier" class="panel-collapse collapse in">
{% for eid in reaction.get_uniprot_identifiers %}
<a class="list-group-item"
href="{{ eid.external_url }}">10 SwissProt entries ({{ eid.identifier_value }})</a>
{% endfor %}
</div>
{% endif %}
</div>
</div> </div>
{% endif %} {% endif %}
</div> </div>
</div>
{% endblock content %} {% endblock content %}

View File

@ -3,7 +3,7 @@
{% block content %} {% block content %}
{% block action_modals %} {% block action_modals %}
{% include "modals/objects/generic_delete_modal.html" %}
{% endblock action_modals %} {% endblock action_modals %}
<div class="panel-group" id="scenario-detail"> <div class="panel-group" id="scenario-detail">
<div class="panel panel-default"> <div class="panel panel-default">

View File

@ -4,6 +4,8 @@
{% block action_modals %} {% block action_modals %}
{% include "modals/objects/edit_rule_modal.html" %} {% include "modals/objects/edit_rule_modal.html" %}
{% include "modals/objects/generic_set_scenario_modal.html" %}
{% include "modals/objects/generic_delete_modal.html" %}
{% endblock action_modals %} {% endblock action_modals %}
<div class="panel-group" id="rule-detail"> <div class="panel-group" id="rule-detail">

View File

@ -7,7 +7,7 @@
{% include "modals/objects/edit_password_modal.html" %} {% include "modals/objects/edit_password_modal.html" %}
{% include "modals/collections/new_prediction_setting_modal.html" %} {% include "modals/collections/new_prediction_setting_modal.html" %}
{% include "modals/objects/manage_api_token_modal.html" %} {% include "modals/objects/manage_api_token_modal.html" %}
{% include "modals/objects/delete_user_modal.html" %} {% include "modals/objects/generic_delete_modal.html" %}
{% endblock action_modals %} {% endblock action_modals %}
<div class="panel-group" id="user-detail"> <div class="panel-group" id="user-detail">

View File

@ -79,6 +79,10 @@
allEmpty = true; allEmpty = true;
for (key in data) { for (key in data) {
if (key === 'searchterm') {
continue;
}
if (data[key].length < 1) { if (data[key].length < 1) {
continue; continue;
} }
@ -176,8 +180,16 @@
$("#selPackages").selectpicker(); $("#selPackages").selectpicker();
$("#search-button").on("click", search); $("#search-button").on("click", search);
$("#searchbar").on("keydown", function (e) {
if (e.key === "Enter") {
e.preventDefault();
search(e);
}
});
}); });
{% if search_result %} {% if search_result %}
$('#searchbar').val('{{ search_result.searchterm }}')
handleSearchResponse("results", {{ search_result|safe }}); handleSearchResponse("results", {{ search_result|safe }});
{% endif %} {% endif %}
</script> </script>

52
tests/test_dataset.py Normal file
View File

@ -0,0 +1,52 @@
from django.test import TestCase
from epdb.logic import PackageManager
from epdb.models import Reaction, Compound, User, Rule
from utilities.ml import Dataset
class DatasetTest(TestCase):
fixtures = ["test_fixture.cleaned.json"]
def setUp(self):
self.cs1 = Compound.create(
self.package,
name='2,6-Dibromohydroquinone',
description='http://localhost:8000/package/32de3cf4-e3e6-4168-956e-32fa5ddb0ce1/compound/d6435251-1a54-4327-b4b1-fd6e9a8f4dc9/structure/d8a0225c-dbb5-4e6c-a642-730081c09c5b',
smiles='C1=C(C(=C(C=C1O)Br)O)Br',
).default_structure
self.cs2 = Compound.create(
self.package,
smiles='O=C(O)CC(=O)/C=C(/Br)C(=O)O',
).default_structure
self.rule1 = Rule.create(
rule_type='SimpleAmbitRule',
package=self.package,
smirks='[#8:8]([H])-[c:4]1[c:3]([H])[c:2](-[#1,#17,#35:9])[c:1](-[#8:7]([H]))[c:6](-[#1,#17,#35])[c:5]([H])1>>[#8-]-[#6:6](=O)-[#6:5]-[#6:4](=[O:8])\[#6:3]=[#6:2](\[#1,#17,#35:9])-[#6:1](-[#8-])=[O:7]',
description='http://localhost:8000/package/32de3cf4-e3e6-4168-956e-32fa5ddb0ce1/simple-ambit-rule/f6a56c0f-a4a0-4ee3-b006-d765b4767cf6'
)
self.reaction1 = Reaction.create(
package=self.package,
educts=[self.cs1],
products=[self.cs2],
rules=[self.rule1],
multi_step=False
)
@classmethod
def setUpClass(cls):
super(DatasetGeneratorTest, cls).setUpClass()
cls.user = User.objects.get(username='anonymous')
cls.package = PackageManager.create_package(cls.user, 'Anon Test Package', 'No Desc')
def test_smoke(self):
reactions = [r for r in Reaction.objects.filter(package=self.package)]
applicable_rules = [self.rule1]
ds = Dataset.generate_dataset(reactions, applicable_rules)
self.assertEqual(len(ds.y()), 1)
self.assertEqual(sum(ds.y()[0]), 1)

View File

@ -1,111 +0,0 @@
from django.test import TestCase
from epdb.models import ParallelRule
from utilities.ml import Compound, Reaction, DatasetGenerator
class CompoundTest(TestCase):
def setUp(self):
self.c1 = Compound(smiles="CCN(CC)C(=O)C1=CC(=CC=C1)C", uuid='c1')
self.c2 = Compound(smiles="CCN(CC)C(=O)C1=CC(=CC=C1)C", uuid='c2')
def test_compound_eq_ignores_uuid(self):
self.assertEqual(self.c1, self.c2)
class ReactionTest(TestCase):
def setUp(self):
self.c1 = Compound(smiles="CCN(CC)C(=O)C1=CC(=CC=C1)C")
self.c2 = Compound(smiles="CCN(CCO)C(=O)C1=CC(C)=CC=C1")
# self.r1 = Rule(uuid="bt0334")
# c1 --r1--> c2
self.c3_1 = Compound(smiles="CCNC(=O)C1=CC(C)=CC=C1")
self.c3_2 = Compound(smiles="CC=O")
# self.r2 = Rule(uuid="bt0243")
# c1 --r2--> c3_1, c3_2
def test_reaction_equality_ignores_uuid(self):
r1 = Reaction([self.c1], [self.c2], self.r1, uuid="abc")
r2 = Reaction([self.c1], [self.c2], self.r1, uuid="xyz")
self.assertEqual(r1, r2)
def test_reaction_inequality_on_data_change(self):
r1 = Reaction([self.c1], [self.c2], self.r1)
r2 = Reaction([self.c1], [self.c3_1], self.r1)
self.assertNotEqual(r1, r2)
def test_reaction_is_hashable(self):
r = Reaction([self.c1], [self.c2], self.r1)
reactions = {r}
self.assertIn(Reaction([self.c1], [self.c2], self.r1), reactions)
def test_rule_is_optional(self):
r = Reaction([self.c1], [self.c2])
self.assertIsNone(r.rule)
def test_uuid_is_optional(self):
r = Reaction([self.c1], [self.c2], self.r1)
self.assertIsNone(r.uuid)
def test_repr_includes_uuid(self):
r = Reaction([self.c1], [self.c2], self.r1, uuid="abc")
self.assertIn("abc", repr(r))
def test_reaction_equality_with_multiple_compounds_different_ordering(self):
r1 = Reaction([self.c1], [self.c3_1, self.c3_2], self.r2)
r2 = Reaction([self.c1], [self.c3_2, self.c3_1], self.r2)
self.assertEqual(r1, r2, "Reaction equality should not rely on list order")
class RuleTest(TestCase):
def setUp(self):
pass
# self.r1 = Rule(uuid="bt0334")
# self.r2 = Rule(uuid="bt0243")
class DatasetGeneratorTest(TestCase):
fixtures = ['bootstrap.json']
def setUp(self):
self.c1 = Compound(smiles="CCN(CC)C(=O)C1=CC(=CC=C1)C")
self.c2 = Compound(smiles="CCN(CCO)C(=O)C1=CC(C)=CC=C1")
self.c3_1 = Compound(smiles="CCNC(=O)C1=CC(C)=CC=C1")
self.c3_2 = Compound(smiles="CC=O")
# self.r1 = Rule(uuid="bt0334") # trig
# self.r2 = Rule(uuid="bt0243") # trig
# self.r3 = Rule(uuid="bt0003") # no trig
self.reaction1 = Reaction([self.c1], [self.c2], self.r3)
self.reaction2 = Reaction([self.c1], [self.c3_1, self.c3_2], self.r2)
def test_test(self):
compounds = [
self.c1,
self.c2,
self.c3_1,
self.c3_2,
]
reactions = [
self.reaction1,
self.reaction2,
]
applicable_rules = [
# Rule('bt0334', ParallelRule.objects.get(name='bt0334')),
# Rule('bt0243', ParallelRule.objects.get(name='bt0243')),
# Rule('bt0003', ParallelRule.objects.get(name='bt0003')),
]
ds = DatasetGenerator.generate_dataset(compounds, reactions, applicable_rules)
self.assertIsNotNone(ds)

55
tests/test_model.py Normal file
View File

@ -0,0 +1,55 @@
import json
from django.test import TestCase
from epdb.logic import PackageManager
from epdb.models import Compound, User, CompoundStructure, Reaction, Rule, MLRelativeReasoning
class ModelTest(TestCase):
fixtures = ["test_fixture.cleaned.json"]
def setUp(self):
pass
@classmethod
def setUpClass(cls):
super(ModelTest, cls).setUpClass()
cls.user = User.objects.get(username='anonymous')
cls.package = PackageManager.create_package(cls.user, 'Anon Test Package', 'No Desc')
bbd_data = json.load(open('fixtures/packages/2025-07-18/EAWAG-BBD.json'))
cls.BBD = PackageManager.import_package(bbd_data, cls.user)
@classmethod
def tearDownClass(cls):
pass
def tearDown(self):
pass
def test_smoke(self):
threshold = float(0.5)
# get Package objects from urls
rule_package_objs = [self.BBD]
data_package_objs = [self.BBD]
eval_packages_objs = []
mod = MLRelativeReasoning.create(
self.package,
rule_package_objs,
data_package_objs,
eval_packages_objs,
threshold,
'ECC - BBD - 0.5',
'Created MLRelativeReasoning in Testcase',
)
ds = mod.load_dataset()
mod.build_model()
print("Model built!")
mod.evaluate_model()
print("Model Evaluated")
results = mod.predict('CCN(CC)C(=O)C1=CC(=CC=C1)C')
print(results)

View File

@ -19,9 +19,8 @@ class RuleApplicationTest(TestCase):
@classmethod @classmethod
def tearDownClass(cls): def tearDownClass(cls):
from collections import Counter print(f"\nTotal Errors across Rules {len(cls.error_smiles)}")
# print(Counter(cls.error_smiles)) # print(cls.error_smiles)
pass
def tearDown(self): def tearDown(self):
print(f"\nTotal errors {self.total_errors}") print(f"\nTotal errors {self.total_errors}")
@ -36,7 +35,7 @@ class RuleApplicationTest(TestCase):
for comp, ambit_prod in zip(bt_rule['compounds'], bt_rule['products']): for comp, ambit_prod in zip(bt_rule['compounds'], bt_rule['products']):
smi = comp['smiles'] smi = comp['smiles']
products = FormatConverter.apply(smi, smirks, preprocess_smiles=True, bracketize=False) products = FormatConverter.apply(smi, smirks)
all_rdkit_prods = [] all_rdkit_prods = []
for ps in products: for ps in products:
@ -53,15 +52,15 @@ class RuleApplicationTest(TestCase):
# TODO mode "intersection" # TODO mode "intersection"
# partial_res = (len(set(ambit_smiles).intersection(set(rdkit_smiles))) > 0) or (len(ambit_smiles) == 0) # partial_res = (len(set(ambit_smiles).intersection(set(rdkit_smiles))) > 0) or (len(ambit_smiles) == 0)
# FAILED (failures=42) # FAILED (failures=33)
# TODO mode = "full ambit" # TODO mode = "full ambit"
# partial_res = len(set(ambit_smiles).intersection(set(rdkit_smiles))) == len(ambit_smiles) # partial_res = len(set(ambit_smiles).intersection(set(rdkit_smiles))) == len(ambit_smiles)
# FAILED (failures=52) # FAILED (failures=44)
# TODO mode = "equality" # TODO mode = "equality"
partial_res = set(ambit_smiles) == set(rdkit_smiles) partial_res = set(ambit_smiles) == set(rdkit_smiles)
# FAILED (failures=71) # FAILED (failures=64)
if len(ambit_smiles) and not partial_res: if len(ambit_smiles) and not partial_res:
print(f""" print(f"""

View File

@ -12,6 +12,8 @@ from rdkit.Chem import MACCSkeys
from rdkit.Chem import rdChemReactions from rdkit.Chem import rdChemReactions
from rdkit.Chem.Draw import rdMolDraw2D from rdkit.Chem.Draw import rdMolDraw2D
from rdkit.Chem.MolStandardize import rdMolStandardize from rdkit.Chem.MolStandardize import rdMolStandardize
from rdkit.Chem.rdmolops import GetMolFrags
from rdkit.Contrib.IFG import ifg
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
RDLogger.DisableLog('rdApp.*') RDLogger.DisableLog('rdApp.*')
@ -87,6 +89,21 @@ class FormatConverter(object):
bitvec = MACCSkeys.GenMACCSKeys(mol) bitvec = MACCSkeys.GenMACCSKeys(mol)
return bitvec.ToList() return bitvec.ToList()
@staticmethod
def get_functional_groups(smiles: str) -> List[str]:
res = list()
try:
m = Chem.MolFromSmiles(smiles)
fgs = ifg.identify_functional_groups(m)
for fg in fgs:
# TODO atoms or type?
res.append(fg.atoms)
except AttributeError:
logger.debug(f"Could not get functional groups for {smiles}")
return res
@staticmethod @staticmethod
def to_svg(smiles, mol_size=(200, 150), kekulize=True): def to_svg(smiles, mol_size=(200, 150), kekulize=True):
mol = FormatConverter.from_smiles(smiles) mol = FormatConverter.from_smiles(smiles)
@ -131,6 +148,24 @@ class FormatConverter(object):
# TODO call to AMBIT Service # TODO call to AMBIT Service
return smiles return smiles
@staticmethod
def ep_standardize(smiles):
change = True
while change:
change = False
for standardizer in MATCH_STANDARDIZER:
tmp_smiles = standardizer.standardize(smiles)
if tmp_smiles != smiles:
print(f"change {smiles} to {tmp_smiles}")
change = True
smiles = tmp_smiles
if change is False:
print(f"nothing changed")
return smiles
@staticmethod @staticmethod
def standardize(smiles): def standardize(smiles):
# Taken from https://bitsilla.com/blog/2021/06/standardizing-a-molecule-using-rdkit/ # Taken from https://bitsilla.com/blog/2021/06/standardizing-a-molecule-using-rdkit/
@ -180,54 +215,6 @@ class FormatConverter(object):
atom.UpdatePropertyCache() atom.UpdatePropertyCache()
return mol return mol
# @staticmethod
# def apply(smiles, smirks, preprocess_smiles=True, bracketize=False, standardize=True):
# logger.debug(f'Applying {smirks} on {smiles}')
#
# if bracketize:
# smirks = smirks.split('>>')[0] + ">>(" + smirks.split('>>')[1] + ")"
#
# res = set()
# try:
# rxn = rdChemReactions.ReactionFromSmarts(smirks)
# mol = Chem.MolFromSmiles(smiles)
#
# # Inplace
# if preprocess_smiles:
# Chem.SanitizeMol(mol)
# mol = Chem.AddHs(mol)
#
# # apply!
# reacts = rxn.RunReactants((mol,))
# if len(reacts):
# # Sanitize mols
# for product_set in reacts:
# prod_set = list()
# for product in product_set:
# # Fixes
# # [2025-01-30 23:00:50] ERROR chem - Sanitizing and converting failed:
# # non-ring atom 3 marked aromatic
# # But does not improve overall performance
# #
# # for a in product.GetAtoms():
# # if (not a.IsInRing()) and a.GetIsAromatic():
# # a.SetIsAromatic(False)
# # for b in product.GetBonds():
# # if (not b.IsInRing()) and b.GetIsAromatic():
# # b.SetIsAromatic(False)
#
# try:
# Chem.SanitizeMol(product)
# prod_set.append(FormatConverter.standardize(Chem.MolToSmiles(product)))
# except ValueError as e:
# logger.error(f'Sanitizing and converting failed:\n{e}')
# continue
# res.add(tuple(list(set(prod_set))))
# except Exception as e:
# logger.error(f'Applying {smirks} on {smiles} failed:\n{e}')
#
# return list(res)
@staticmethod @staticmethod
def is_valid_smirks(smirks: str) -> bool: def is_valid_smirks(smirks: str) -> bool:
try: try:
@ -237,7 +224,7 @@ class FormatConverter(object):
return False return False
@staticmethod @staticmethod
def apply(smiles: str, smirks: str, preprocess_smiles: bool = True, bracketize: bool = False, def apply(smiles: str, smirks: str, preprocess_smiles: bool = True, bracketize: bool = True,
standardize: bool = True, kekulize: bool = True) -> List['ProductSet']: standardize: bool = True, kekulize: bool = True) -> List['ProductSet']:
logger.debug(f'Applying {smirks} on {smiles}') logger.debug(f'Applying {smirks} on {smiles}')
@ -266,8 +253,10 @@ class FormatConverter(object):
for product in product_set: for product in product_set:
try: try:
Chem.SanitizeMol(product) Chem.SanitizeMol(product)
product = GetMolFrags(product, asMols=True)
product = FormatConverter.standardize(Chem.MolToSmiles(product)) for p in product:
p = FormatConverter.standardize(Chem.MolToSmiles(p))
prods.append(p)
# if kekulize: # if kekulize:
# # from rdkit.Chem import MolStandardize # # from rdkit.Chem import MolStandardize
@ -292,13 +281,12 @@ class FormatConverter(object):
# # bond.SetIsAromatic(False) # # bond.SetIsAromatic(False)
# Chem.Kekulize(product) # Chem.Kekulize(product)
prods.append(product)
except ValueError as e: except ValueError as e:
logger.error(f'Sanitizing and converting failed:\n{e}') logger.error(f'Sanitizing and converting failed:\n{e}')
continue continue
# TODO doc! if len(prods):
if len(prods) and len(prods) == len(product_set):
ps = ProductSet(prods) ps = ProductSet(prods)
pss.add(ps) pss.add(ps)
@ -651,7 +639,7 @@ class IndigoUtils(object):
environment.add(mappedAtom.index()) environment.add(mappedAtom.index())
for k, v in functional_groups.items(): for k, v in functional_groups.items():
try:
sanitized = IndigoUtils.sanitize_functional_group(k) sanitized = IndigoUtils.sanitize_functional_group(k)
query = indigo.loadSmarts(sanitized) query = indigo.loadSmarts(sanitized)
@ -666,6 +654,9 @@ class IndigoUtils(object):
counts[mappedAtom.index()] = max(v, counts[mappedAtom.index()]) counts[mappedAtom.index()] = max(v, counts[mappedAtom.index()])
except IndigoException as e:
logger.debug(f'Colorizing failed due to {e}')
for k, v in counts.items(): for k, v in counts.items():
if is_reaction: if is_reaction:
color = "128, 0, 128" color = "128, 0, 128"

View File

@ -1,46 +1,29 @@
from __future__ import annotations from __future__ import annotations
import dataclasses import logging
from abc import ABC, abstractmethod
from collections import defaultdict from collections import defaultdict
from datetime import datetime from datetime import datetime
from typing import List, Optional from typing import List, Dict, Set, Tuple
import numpy as np import numpy as np
from sklearn.base import BaseEstimator, ClassifierMixin from sklearn.base import BaseEstimator, ClassifierMixin
from sklearn.decomposition import PCA from sklearn.decomposition import PCA
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score from sklearn.metrics import accuracy_score
from sklearn.multioutput import ClassifierChain from sklearn.multioutput import ClassifierChain
from sklearn.preprocessing import StandardScaler from sklearn.preprocessing import StandardScaler
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
# @dataclasses.dataclass logger = logging.getLogger(__name__)
# class Feature:
# name: str
# value: float
#
#
#
# class Row:
# def __init__(self, compound_uuid: str, compound_smiles: str, descriptors: List[int]):
# self.data = {}
#
#
#
# class DataSet(object):
#
# def __init__(self):
# self.rows: List[Row] = []
#
# def add_row(self, row: Row):
# pass
from dataclasses import dataclass, field from dataclasses import dataclass, field
from utilities.chem import FormatConverter from utilities.chem import FormatConverter, PredictionResult
@dataclass @dataclass
class Compound: class SCompound:
smiles: str smiles: str
uuid: str = field(default=None, compare=False, hash=False) uuid: str = field(default=None, compare=False, hash=False)
@ -53,10 +36,10 @@ class Compound:
@dataclass @dataclass
class Reaction: class SReaction:
educts: List[Compound] educts: List[SCompound]
products: List[Compound] products: List[SCompound]
rule_uuid: str = field(default=None, compare=False, hash=False) rule_uuid: SRule = field(default=None, compare=False, hash=False)
reaction_uuid: str = field(default=None, compare=False, hash=False) reaction_uuid: str = field(default=None, compare=False, hash=False)
def __hash__(self): def __hash__(self):
@ -68,7 +51,7 @@ class Reaction:
return self._hash return self._hash
def __eq__(self, other): def __eq__(self, other):
if not isinstance(other, Reaction): if not isinstance(other, SReaction):
return NotImplemented return NotImplemented
return ( return (
sorted(self.educts, key=lambda x: x.smiles) == sorted(other.educts, key=lambda x: x.smiles) and sorted(self.educts, key=lambda x: x.smiles) == sorted(other.educts, key=lambda x: x.smiles) and
@ -76,69 +59,296 @@ class Reaction:
) )
class Dataset(object): @dataclass
class SRule(ABC):
def __init__(self, headers=List['str'], data=List[List[str|int|float]]): @abstractmethod
self.headers = headers def apply(self):
pass
@dataclass
class SSimpleRule:
pass
@dataclass
class SParallelRule:
pass
class Dataset:
def __init__(self, columns: List[str], num_labels: int, data: List[List[str | int | float]] = None):
self.columns: List[str] = columns
self.num_labels: int = num_labels
if data is None:
self.data: List[List[str | int | float]] = list()
else:
self.data = data self.data = data
self.num_features: int = len(columns) - self.num_labels
self._struct_features: Tuple[int, int] = self._block_indices('feature_')
self._triggered: Tuple[int, int] = self._block_indices('trig_')
self._observed: Tuple[int, int] = self._block_indices('obs_')
def features(self): def _block_indices(self, prefix) -> Tuple[int, int]:
pass indices: List[int] = []
for i, feature in enumerate(self.columns):
if feature.startswith(prefix):
indices.append(i)
def labels(self): return min(indices), max(indices)
pass
def to_json(self): def structure_id(self):
pass return self.data[0][0]
def to_csv(self): def add_row(self, row: List[str | int | float]):
pass if len(self.columns) != len(row):
raise ValueError(f"Header and Data are not aligned {len(self.columns)} vs. {len(row)}")
self.data.append(row)
def to_arff(self): def times_triggered(self, rule_uuid) -> int:
pass idx = self.columns.index(f'trig_{rule_uuid}')
times_triggered = 0
for row in self.data:
if row[idx] == 1:
times_triggered += 1
return times_triggered
def struct_features(self) -> Tuple[int, int]:
return self._struct_features
def triggered(self) -> Tuple[int, int]:
return self._triggered
def observed(self) -> Tuple[int, int]:
return self._observed
def at(self, position: int) -> Dataset:
return Dataset(self.columns, self.num_labels, [self.data[position]])
def limit(self, limit: int) -> Dataset:
return Dataset(self.columns, self.num_labels, self.data[:limit])
def __iter__(self):
return (self.at(i) for i, _ in enumerate(self.data))
def classification_dataset(self, structures: List[str | 'CompoundStructure'], applicable_rules: List['Rule']) -> Tuple[Dataset, List[List[PredictionResult]]]:
classify_data = []
classify_products = []
for struct in structures:
class DatasetGenerator(object): if isinstance(struct, str):
struct_id = None
struct_smiles = struct
else:
struct_id = str(struct.uuid)
struct_smiles = struct.smiles
features = FormatConverter.maccs(struct_smiles)
trig = []
prods = []
for rule in applicable_rules:
products = rule.apply(struct_smiles)
if len(products):
trig.append(1)
prods.append(products)
else:
trig.append(0)
prods.append([])
classify_data.append([struct_id] + features + trig + ([-1] * len(trig)))
classify_products.append(prods)
return Dataset(columns=self.columns, num_labels=self.num_labels, data=classify_data), classify_products
@staticmethod @staticmethod
def generate_dataset(compounds: List[Compound], reactions: List[Reaction], applicable_rules: 'Rule', def generate_dataset(reactions: List['Reaction'], applicable_rules: List['Rule'], educts_only: bool = True) -> Dataset:
compounds_to_exclude: Optional[Compound] = None, educts_only: bool = False) -> Dataset: _structures = set()
rows = []
if educts_only:
compounds = set()
for r in reactions: for r in reactions:
for e in r.educts: for e in r.educts.all():
compounds.add(e) _structures.add(e)
compounds = list(compounds)
total = len(compounds) if not educts_only:
for i, c in enumerate(compounds): for e in r.products:
row = [] _structures.add(e)
print(f"{i + 1}/{total} - {c.smiles}")
for r in applicable_rules: compounds = sorted(_structures, key=lambda x: x.url)
product_sets = r.rule.apply(c.smiles)
triggered: Dict[str, Set[str]] = defaultdict(set)
observed: Set[str] = set()
# Apply rules on collected compounds and store tps
for i, comp in enumerate(compounds):
logger.debug(f"{i + 1}/{len(compounds)}...")
for rule in applicable_rules:
product_sets = rule.apply(comp.smiles)
if len(product_sets) == 0: if len(product_sets) == 0:
row.append([])
continue continue
#triggered.add(f"{r.uuid} + {c.uuid}") key = f"{rule.uuid} + {comp.uuid}"
reacts = set()
for ps in product_sets:
products = []
for p in ps:
products.append(Compound(FormatConverter.standardize(p)))
reacts.add(Reaction([c], products, r)) if key in triggered:
row.append(list(reacts)) logger.info(f"{key} already present. Duplicate reaction?")
rows.append(row) for prod_set in product_sets:
for smi in prod_set:
return rows try:
smi = FormatConverter.standardize(smi)
except Exception:
# :shrug:
logger.debug(f'Standardizing SMILES failed for {smi}')
pass
triggered[key].add(smi)
for i, r in enumerate(reactions):
logger.debug(f"{i + 1}/{len(reactions)}...")
if len(r.educts.all()) != 1:
logger.debug(f"Skipping {r.url} as it has {len(r.educts.all())} substrates!")
continue
for comp in r.educts.all():
for rule in applicable_rules:
key = f"{rule.uuid} + {comp.uuid}"
if key not in triggered:
continue
# standardize products from reactions for comparison
standardized_products = []
for cs in r.products.all():
smi = cs.smiles
try:
smi = FormatConverter.standardize(smi)
except Exception as e:
# :shrug:
logger.debug(f'Standardizing SMILES failed for {smi}')
pass
standardized_products.append(smi)
if len(set(standardized_products).difference(triggered[key])) == 0:
observed.add(key)
else:
pass
ds = None
for i, comp in enumerate(compounds):
# Features
feat = FormatConverter.maccs(comp.smiles)
trig = []
obs = []
for rule in applicable_rules:
key = f"{rule.uuid} + {comp.uuid}"
# Check triggered
if key in triggered:
trig.append(1)
else:
trig.append(0)
# Check obs
if key in observed:
obs.append(1)
elif key not in triggered:
obs.append(None)
else:
obs.append(0)
if ds is None:
header = ['structure_id'] + \
[f'feature_{i}' for i, _ in enumerate(feat)] \
+ [f'trig_{r.uuid}' for r in applicable_rules] \
+ [f'obs_{r.uuid}' for r in applicable_rules]
ds = Dataset(header, len(applicable_rules))
ds.add_row([str(comp.uuid)] + feat + trig + obs)
return ds
def X(self, exclude_id_col=True, na_replacement=0):
res = self.__getitem__((slice(None), slice(1 if exclude_id_col else 0, len(self.columns) - self.num_labels)))
if na_replacement is not None:
res = [[x if x is not None else na_replacement for x in row] for row in res]
return res
def y(self, na_replacement=0):
res = self.__getitem__((slice(None), slice(len(self.columns) - self.num_labels, None)))
if na_replacement is not None:
res = [[x if x is not None else na_replacement for x in row] for row in res]
return res
def __getitem__(self, key):
if not isinstance(key, tuple):
raise TypeError("Dataset must be indexed with dataset[rows, columns]")
row_key, col_key = key
# Normalize rows
if isinstance(row_key, int):
rows = [self.data[row_key]]
else:
rows = self.data[row_key]
# Normalize columns
if isinstance(col_key, int):
res = [row[col_key] for row in rows]
else:
res = [[row[i] for i in range(*col_key.indices(len(row)))] if isinstance(col_key, slice)
else [row[i] for i in col_key] for row in rows]
return res
def save(self, path: 'Path'):
import pickle
with open(path, "wb") as fh:
pickle.dump(self, fh)
@staticmethod
def load(path: 'Path'):
import pickle
return pickle.load(open(path, "rb"))
def to_arff(self, path: 'Path'):
arff = f"@relation 'enviPy-dataset: -C {self.num_labels}'\n"
arff += "\n"
for c in self.columns[-self.num_labels:] + self.columns[:self.num_features]:
if c == 'structure_id':
arff += f"@attribute {c} string\n"
else:
arff += f"@attribute {c} {{0,1}}\n"
arff += f"\n@data\n"
for d in self.data:
ys = ','.join([str(v if v is not None else '?') for v in d[-self.num_labels:]])
xs = ','.join([str(v if v is not None else '?') for v in d[:self.num_features]])
arff += f'{ys},{xs}\n'
with open(path, "w") as fh:
fh.write(arff)
fh.flush()
def __repr__(self):
return f"<Dataset #rows={len(self.data)} #cols={len(self.columns)} #labels={self.num_labels}>"
class SparseLabelECC(BaseEstimator, ClassifierMixin): class SparseLabelECC(BaseEstimator, ClassifierMixin):
@ -166,8 +376,7 @@ class SparseLabelECC(BaseEstimator, ClassifierMixin):
self.keep_columns_.append(col) self.keep_columns_.append(col)
y_reduced = y[:, self.keep_columns_] y_reduced = y[:, self.keep_columns_]
self.chains_ = [ClassifierChain(self.base_clf, order='random', random_state=i) self.chains_ = [ClassifierChain(self.base_clf) for i in range(self.num_chains)]
for i in range(self.num_chains)]
for i, chain in enumerate(self.chains_): for i, chain in enumerate(self.chains_):
print(f"{datetime.now()} fitting {i + 1}/{self.num_chains}") print(f"{datetime.now()} fitting {i + 1}/{self.num_chains}")
@ -208,26 +417,169 @@ class SparseLabelECC(BaseEstimator, ClassifierMixin):
return accuracy_score(y_true, y_pred, sample_weight=sample_weight) return accuracy_score(y_true, y_pred, sample_weight=sample_weight)
class ApplicabilityDomain(PCA):
def __init__(self, n_components=5): import copy
super().__init__(n_components=n_components)
import numpy as np
from sklearn.dummy import DummyClassifier
from sklearn.tree import DecisionTreeClassifier
class BinaryRelevance:
def __init__(self, baseline_clf):
self.clf = baseline_clf
self.classifiers = None
def fit(self, X, Y):
if self.classifiers is None:
self.classifiers = []
for l in range(len(Y[0])):
X_l = X[~np.isnan(Y[:, l])]
Y_l = (Y[~np.isnan(Y[:, l]), l])
if len(X_l) == 0: # all labels are nan -> predict 0
clf = DummyClassifier(strategy='constant', constant=0)
clf.fit([X[0]], [0])
self.classifiers.append(clf)
continue
elif len(np.unique(Y_l)) == 1: # only one class -> predict that class
clf = DummyClassifier(strategy='most_frequent')
else:
clf = copy.deepcopy(self.clf)
clf.fit(X_l, Y_l)
self.classifiers.append(clf)
def predict(self, X):
labels = []
for clf in self.classifiers:
labels.append(clf.predict(X))
return np.column_stack(labels)
def predict_proba(self, X):
labels = np.empty((len(X), 0))
for clf in self.classifiers:
pred = clf.predict_proba(X)
if pred.shape[1] > 1:
pred = pred[:, 1]
else:
pred = pred * clf.predict([X[0]])[0]
labels = np.column_stack((labels, pred))
return labels
class MissingValuesClassifierChain:
def __init__(self, base_clf):
self.base_clf = base_clf
self.permutation = None
self.classifiers = None
def fit(self, X, Y):
X = np.array(X)
Y = np.array(Y)
if self.permutation is None:
self.permutation = np.random.permutation(len(Y[0]))
Y = Y[:, self.permutation]
if self.classifiers is None:
self.classifiers = []
for p in range(len(self.permutation)):
X_p = X[~np.isnan(Y[:, p])]
Y_p = Y[~np.isnan(Y[:, p]), p]
if len(X_p) == 0: # all labels are nan -> predict 0
clf = DummyClassifier(strategy='constant', constant=0)
self.classifiers.append(clf.fit([X[0]], [0]))
elif len(np.unique(Y_p)) == 1: # only one class -> predict that class
clf = DummyClassifier(strategy='most_frequent')
self.classifiers.append(clf.fit(X_p, Y_p))
else:
clf = copy.deepcopy(self.base_clf)
self.classifiers.append(clf.fit(X_p, Y_p))
newcol = Y[:, p]
pred = clf.predict(X)
newcol[np.isnan(newcol)] = pred[np.isnan(newcol)] # fill in missing values with clf predictions
X = np.column_stack((X, newcol))
def predict(self, X):
labels = np.empty((len(X), 0))
for clf in self.classifiers:
pred = clf.predict(np.column_stack((X, labels)))
labels = np.column_stack((labels, pred))
return labels[:, np.argsort(self.permutation)]
def predict_proba(self, X):
labels = np.empty((len(X), 0))
for clf in self.classifiers:
pred = clf.predict_proba(np.column_stack((X, np.round(labels))))
if pred.shape[1] > 1:
pred = pred[:, 1]
else:
pred = pred * clf.predict(np.column_stack(([X[0]], np.round([labels[0]]))))[0]
labels = np.column_stack((labels, pred))
return labels[:, np.argsort(self.permutation)]
class EnsembleClassifierChain:
def __init__(self, base_clf, num_chains=10):
self.base_clf = base_clf
self.num_chains = num_chains
self.num_labels = None
self.classifiers = None
def fit(self, X, Y):
if self.classifiers is None:
self.classifiers = []
if self.num_labels is None:
self.num_labels = len(Y[0])
for p in range(self.num_chains):
print(f"{datetime.now()} fitting {p + 1}/{self.num_chains}")
clf = MissingValuesClassifierChain(self.base_clf)
clf.fit(X, Y)
self.classifiers.append(clf)
def predict(self, X):
labels = np.zeros((len(X), self.num_labels))
for clf in self.classifiers:
labels += clf.predict(X)
return np.round(labels / self.num_chains)
def predict_proba(self, X):
labels = np.zeros((len(X), self.num_labels))
for clf in self.classifiers:
labels += clf.predict_proba(X)
return labels / self.num_chains
class ApplicabilityDomainPCA(PCA):
def __init__(self, num_neighbours: int = 5):
super().__init__(n_components=num_neighbours)
self.scaler = StandardScaler() self.scaler = StandardScaler()
self.num_neighbours = num_neighbours
self.min_vals = None self.min_vals = None
self.max_vals = None self.max_vals = None
def build(self, X): def build(self, train_dataset: 'Dataset'):
# transform # transform
X_scaled = self.scaler.fit_transform(X) X_scaled = self.scaler.fit_transform(train_dataset.X())
# fit pca # fit pca
X_pca = self.fit_transform(X_scaled) X_pca = self.fit_transform(X_scaled)
self.max_vals = np.max(X_pca, axis=0) self.max_vals = np.max(X_pca, axis=0)
self.min_vals = np.min(X_pca, axis=0) self.min_vals = np.min(X_pca, axis=0)
def is_applicable(self, instances): def __transform(self, instances):
instances_scaled = self.scaler.transform(instances) instances_scaled = self.scaler.transform(instances)
instances_pca = self.transform(instances_scaled) instances_pca = self.transform(instances_scaled)
return instances_pca
def is_applicable(self, classify_instances: 'Dataset'):
instances_pca = self.__transform(classify_instances.X())
is_applicable = [] is_applicable = []
for i, instance in enumerate(instances_pca): for i, instance in enumerate(instances_pca):
@ -237,3 +589,17 @@ class ApplicabilityDomain(PCA):
is_applicable[i] = False is_applicable[i] = False
return is_applicable return is_applicable
def tanimoto_distance(a: List[int], b: List[int]):
if len(a) != len(b):
raise ValueError(f"Lists must be the same length {len(a)} != {len(b)}")
sum_a = sum(a)
sum_b = sum(b)
sum_c = sum(v1 and v2 for v1, v2 in zip(a, b))
if sum_a + sum_b - sum_c == 0:
return 0.0
return 1 - (sum_c / (sum_a + sum_b - sum_c))