Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/sckan 275 #250

Merged
merged 22 commits into from
Apr 2, 2024
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
e88b3b1
SCKAN-275 feat: Add layer and region models
afonsobspinto Mar 14, 2024
416f255
SCKAN-275 feat: Update neurondm ingestion
afonsobspinto Mar 16, 2024
31e88aa
Merge branch 'feature/SCKAN-274' into feature/SCKAN-275
afonsobspinto Mar 18, 2024
86a7c3a
SCKAN-275 feat: WIP - Update anatomical entity model
afonsobspinto Mar 19, 2024
de97874
SCKAN-275 feat: WIP - Remove old anatomical entity model
afonsobspinto Mar 19, 2024
96ffa1e
SCKAN-275 feat: Connect new anatomical entity model
afonsobspinto Mar 19, 2024
776237e
SCKAN-275 feat: Add basic admin panel controls
afonsobspinto Mar 19, 2024
40f20b9
SCKAN-275 feat: Update models
afonsobspinto Mar 19, 2024
75a980b
SCKAN-275 feat: Add basic serializers
afonsobspinto Mar 20, 2024
87633da
SCKAN-275 refactor: Split ingestion script in multiple modules
afonsobspinto Mar 20, 2024
0ce72d8
SCKAN-275 feat: Update changes detector
afonsobspinto Mar 20, 2024
bce80aa
SCKAN-275 feat: Update validators
afonsobspinto Mar 20, 2024
ec80e8b
SCKAN-275 feat: Update ingest anatomical entity script
afonsobspinto Mar 20, 2024
3ce0636
SCKAN-275 fix: Fix typo
afonsobspinto Mar 20, 2024
318eb51
SCKAN-275 feat: Update frontend to be compliant with model changes (b…
afonsobspinto Mar 20, 2024
7b735ed
SCKAN-275 feat: Update Anatomical Entity Admin page
afonsobspinto Mar 21, 2024
fadd3aa
SCKAN-275 feat: Add automatic creation of anatomical entities
afonsobspinto Mar 21, 2024
f40300c
SCKAN-275 feat: Update admin page names
afonsobspinto Mar 21, 2024
3e043ea
SCKAN-275 feat: Add anatomical entity constraint
afonsobspinto Mar 21, 2024
a7daca8
chore(): added feature to use a local PostgreSQL database
zsinnema Mar 22, 2024
99c0245
SCKAN-275 feat: Set name as string representation of AnatomicalEntity
afonsobspinto Mar 27, 2024
2bd433a
Feature/csckan-277 (#252)
D-GopalKrishna Apr 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 36 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,17 +48,50 @@ python3 manage.py migrate
python3 manage.py runsslserver
```

### Running on docker with docker-compose
### Running the Backend on docker with docker-compose
the command below will start a docker container that maps/uses the backend folder
into the container. It will also start the Django development server with DEBUG=True

```bash
BUILDKIT_PROGRESS=plain docker-compose -f docker-compose-dev.yaml up --build
```

to stop:
### Running the PostgreSQL database with docker-compose
the command below will start a docker container that runs the PostgreSQL database.
To use it within your development Django server you need to set the following env vars
in your launch(file)

```
USE_PG=True
DB_HOST=localhost
DB_PORT=5432
DB_NAME=composer
DB_USER=composer
DB_PASSWORD=composer
```

To start the database server run this command:
```
docker-compose --file docker-compose-db.yaml up --build
```

to stop the database run this command:
```bash
docker-compose -f docker-compose-dev.yaml down
docker-compose -f docker-compose-db.yaml down
```

Example to run the backend using the Docker PostgreSQL database
```
cd backend

export USE_PG=True
export DB_HOST=localhost
export DB_PORT=5432
export DB_NAME=composer
export DB_USER=composer
export DB_PASSWORD=composer

python ./manage.py runsslserver
```

### Ingest sample NLP data
Expand Down
5 changes: 3 additions & 2 deletions backend/backend/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

# SECURITY WARNING: don't run with debug turned on in production!
PRODUCTION = os.environ.get("PRODUCTION", "False").lower() in ("true", "1")
USE_PG = os.environ.get("USE_PG", "False").lower() in ("true", "1")
DEBUG = os.environ.get("DEBUG", str(not PRODUCTION)).lower() in ("true", "1")

ALLOWED_HOSTS = [
Expand Down Expand Up @@ -101,7 +102,7 @@

# Database
# https://docs.djangoproject.com/en/4.1/ref/settings/#databases
if PRODUCTION:
if PRODUCTION or USE_PG:
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
Expand Down Expand Up @@ -148,7 +149,7 @@

USE_I18N = True

USE_TZ = False
USE_TZ = True


# Static files (CSS, JavaScript, Images)
Expand Down
41 changes: 35 additions & 6 deletions backend/composer/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
from fsm_admin.mixins import FSMTransitionMixin

from composer.models import (
AnatomicalEntity,
Phenotype,
Sex,
ConnectivityStatement,
Expand All @@ -19,7 +18,9 @@
Tag,
Via,
FunctionalCircuitRole,
ProjectionPhenotype, Destination, Synonym
ProjectionPhenotype, Destination, Synonym, AnatomicalEntityMeta, Layer, Region,
AnatomicalEntityIntersection,
AnatomicalEntity
)


Expand Down Expand Up @@ -95,10 +96,36 @@ class SynonymInline(admin.TabularInline):


class AnatomicalEntityAdmin(admin.ModelAdmin):
search_fields = ('simple_entity__name', 'region_layer__layer__name', 'region_layer__region__name')

def get_model_perms(self, request):
"""
Return empty dict to hide the model from admin index.
"""
return {}


class AnatomicalEntityMetaAdmin(admin.ModelAdmin):
list_display = ("name", "ontology_uri")
list_display_links = ("name", "ontology_uri")
search_fields = ("name",) # or ("^name",) for search to start with
inlines = [SynonymInline]
search_fields = ("name",)


class LayerAdmin(admin.ModelAdmin):
list_display = ('name', 'ontology_uri',)
search_fields = ('name',)


class RegionAdmin(admin.ModelAdmin):
list_display = ('name', 'ontology_uri',)
search_fields = ('name',)
filter_horizontal = ('layers',)


class AnatomicalEntityIntersectionAdmin(admin.ModelAdmin):
list_display = ('layer', 'region',)
list_filter = ('layer', 'region',)
raw_id_fields = ('layer', 'region',)


class ViaInline(SortableStackedInline):
Expand Down Expand Up @@ -143,8 +170,6 @@ class ConnectivityStatementAdmin(
"sentence__pmid",
"sentence__pmcid",
"knowledge_statement",
"origins__name",
"destinations__anatomical_entities__name",
)

fieldsets = ()
Expand Down Expand Up @@ -205,6 +230,10 @@ def get_form(self, request, obj=None, change=False, **kwargs):
admin.site.register(User, UserAdmin)

#
admin.site.register(AnatomicalEntityMeta, AnatomicalEntityMetaAdmin)
admin.site.register(Layer, LayerAdmin)
admin.site.register(Region, RegionAdmin)
admin.site.register(AnatomicalEntityIntersection, AnatomicalEntityIntersectionAdmin)
admin.site.register(AnatomicalEntity, AnatomicalEntityAdmin)
admin.site.register(Phenotype)
admin.site.register(Sex)
Expand Down
59 changes: 52 additions & 7 deletions backend/composer/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
Sentence,
Specie,
Tag,
Via, Destination,
Via, Destination, AnatomicalEntityIntersection, Region, Layer, AnatomicalEntityMeta,
)
from ..services.connections_service import get_complete_from_entities_for_destination, \
get_complete_from_entities_for_via
Expand Down Expand Up @@ -80,17 +80,63 @@ class Meta:
dept = 2


class AnatomicalEntitySerializer(UniqueFieldsMixin, serializers.ModelSerializer):
"""Anatomical Entity"""
class LayerSerializer(serializers.ModelSerializer):
class Meta:
model = Layer
fields = (
"id",
"name",
"ontology_uri",
)


class RegionSerializer(serializers.ModelSerializer):
layers = LayerSerializer(many=True, read_only=True)

class Meta:
model = AnatomicalEntity
model = Region
fields = (
"id",
"name",
"ontology_uri",
"layers",
)


class AnatomicalEntityMetaSerializer(serializers.ModelSerializer):
class Meta:
model = AnatomicalEntityMeta
fields = (
"id",
"name",
"ontology_uri",
)
read_only_fields = ("ontology_uri",)


class AnatomicalEntityIntersectionSerializer(serializers.ModelSerializer):
layer = LayerSerializer(read_only=True)
region = RegionSerializer(read_only=True)

class Meta:
model = AnatomicalEntityIntersection
fields = (
"id",
"layer",
"region",
)


class AnatomicalEntitySerializer(serializers.ModelSerializer):
simple_entity = AnatomicalEntityMetaSerializer(read_only=True)
region_layer = AnatomicalEntityIntersectionSerializer(read_only=True)

class Meta:
model = AnatomicalEntity
fields = (
"id",
"simple_entity",
"region_layer",
)


class NoteSerializer(serializers.ModelSerializer):
Expand Down Expand Up @@ -486,7 +532,6 @@ def get_statement_preview(self, instance):
self.context['journey'] = instance.get_journey()
return self.create_statement_preview(instance, self.context['journey'])


def create_statement_preview(self, instance, journey):
sex = instance.sex.sex_str if instance.sex else None

Expand Down Expand Up @@ -514,7 +559,7 @@ def create_statement_preview(self, instance, journey):
statement = f"In {sex or ''} {species}, the {phenotype.lower()} connection goes {journey_sentence}.\n"
else:
statement = f"A {phenotype.lower()} connection goes {journey_sentence}.\n"

statement += f"This "
if projection:
statement += f"{projection.lower()} "
Expand Down
29 changes: 18 additions & 11 deletions backend/composer/management/commands/ingest_anatomical_entities.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from django.core.management.base import BaseCommand, CommandError
from django.db import transaction
from django.db.utils import IntegrityError
from composer.models import AnatomicalEntity, Synonym
from composer.models import AnatomicalEntity, Synonym, AnatomicalEntityMeta

URI = "o"
NAME = "o_label"
Expand All @@ -24,18 +24,23 @@ def _process_anatomical_entity(self, name, ontology_uri, synonym, show_complete_
try:
is_first_occurrence = ontology_uri not in processed_uris

anatomical_entity, created = AnatomicalEntity.objects.get_or_create(
entity_meta, meta_created = AnatomicalEntityMeta.objects.get_or_create(
ontology_uri=ontology_uri,
defaults={"name": name},
)
if not created and is_first_occurrence:
if anatomical_entity.name != name:
anatomical_entity.name = name
anatomical_entity.save()
if show_complete_logs:
self.stdout.write(
self.style.SUCCESS(f"Updated {anatomical_entity.ontology_uri} name to {name}.")
)

# Update the name if it has changed and this is the first occurrence of the ontology URI
if not meta_created and is_first_occurrence and entity_meta.name != name:
entity_meta.name = name
entity_meta.save()
if show_complete_logs:
self.stdout.write(
self.style.SUCCESS(f"Updated {entity_meta.ontology_uri} name to {name}.")
)

anatomical_entity, created = AnatomicalEntity.objects.get_or_create(
simple_entity=entity_meta
)

processed_uris.add(ontology_uri)

Expand All @@ -45,7 +50,9 @@ def _process_anatomical_entity(self, name, ontology_uri, synonym, show_complete_
unique_synonyms[synonym_key] = Synonym(anatomical_entity=anatomical_entity, name=synonym)
if show_complete_logs:
self.stdout.write(
self.style.SUCCESS(f"Synonym '{synonym}' added for {anatomical_entity.ontology_uri}."))
self.style.SUCCESS(
f"Synonym '{synonym}' added for {anatomical_entity.simple_entity.ontology_uri}.")
)
except IntegrityError as e:
self.stdout.write(self.style.ERROR(f"Error processing {ontology_uri}: {e}"))

Expand Down
10 changes: 8 additions & 2 deletions backend/composer/management/commands/ingest_statements.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import time

from django.core.management.base import BaseCommand, CommandError
from django.core.management.base import BaseCommand
from composer.services.cs_ingestion.cs_ingestion_services import ingest_statements


Expand All @@ -13,13 +13,19 @@ def add_arguments(self, parser):
action='store_true',
help='Set this flag to update upstream statements.',
)
parser.add_argument(
'--update_anatomical_entities',
action='store_true',
help='Set this flag to try move anatomical entities to specific layer, region.',
)

def handle(self, *args, **options):
update_upstream = options['update_upstream']
update_anatomical_entities = options['update_anatomical_entities']

start_time = time.time()

ingest_statements(update_upstream)
ingest_statements(update_upstream, update_anatomical_entities)

end_time = time.time()

Expand Down
Loading