Skip to content

Commit 6856995

Browse files
jdcourcolJean-Denis CourcolJean-Denis Courcolmgeplfeleftherioszisis
authored
me model calibration result proposal (#188)
* me model calibration result proposal * make rin optional, remove holding/threshold current from MEModel * MEModelCalibrationResult schema * add filter and router * Remove unique constraint from Person (#199) * move calibrated_entity_id to base class * import calibration results * adding memodel to calibrationresult relationship * Add joinedload * Add nested created_by, updated_by loader * add test for the memodel to calibration relationship --------- Co-authored-by: Jean-Denis Courcol <courcol@mac.home> Co-authored-by: Jean-Denis Courcol <courcol@mac.lan> Co-authored-by: MikeG <mgevaert@openbraininstitute.org> Co-authored-by: Eleftherios Zisis <eleftherios.zisis@epfl.ch> Co-authored-by: Gil Arturo Barrios del Villar <gilarturo.barriosdelvillar@openbraininstitute.org>
1 parent 207f737 commit 6856995

17 files changed

+472
-34
lines changed
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
"""Default migration message
2+
3+
Revision ID: 1589bff44728
4+
Revises: 8d1610d7c882
5+
Create Date: 2025-05-26 22:23:51.082630
6+
7+
"""
8+
9+
from typing import Sequence, Union
10+
11+
from alembic import op
12+
import sqlalchemy as sa
13+
from alembic_postgresql_enum import TableReference
14+
15+
from sqlalchemy import Text
16+
import app.db.types
17+
18+
# revision identifiers, used by Alembic.
19+
revision: str = "1589bff44728"
20+
down_revision: Union[str, None] = "8d1610d7c882"
21+
branch_labels: Union[str, Sequence[str], None] = None
22+
depends_on: Union[str, Sequence[str], None] = None
23+
24+
25+
def upgrade() -> None:
26+
# ### commands auto generated by Alembic - please adjust! ###
27+
op.create_table(
28+
"memodel_calibration_result",
29+
sa.Column("id", sa.Uuid(), nullable=False),
30+
sa.Column("holding_current", sa.Float(), nullable=False),
31+
sa.Column("threshold_current", sa.Float(), nullable=False),
32+
sa.Column("rin", sa.Float(), nullable=True),
33+
sa.Column("calibrated_entity_id", sa.Uuid(), nullable=False),
34+
sa.ForeignKeyConstraint(
35+
["calibrated_entity_id"],
36+
["memodel.id"],
37+
name=op.f("fk_memodel_calibration_result_calibrated_entity_id_memodel"),
38+
),
39+
sa.ForeignKeyConstraint(
40+
["id"], ["entity.id"], name=op.f("fk_memodel_calibration_result_id_entity")
41+
),
42+
sa.PrimaryKeyConstraint("id", name=op.f("pk_memodel_calibration_result")),
43+
)
44+
op.create_index(
45+
op.f("ix_memodel_calibration_result_calibrated_entity_id"),
46+
"memodel_calibration_result",
47+
["calibrated_entity_id"],
48+
unique=False,
49+
)
50+
op.drop_column("memodel", "holding_current")
51+
op.drop_column("memodel", "threshold_current")
52+
op.sync_enum_values(
53+
enum_schema="public",
54+
enum_name="entitytype",
55+
new_values=[
56+
"analysis_software_source_code",
57+
"brain_atlas",
58+
"brain_atlas_region",
59+
"emodel",
60+
"cell_composition",
61+
"memodel_calibration_result",
62+
"experimental_bouton_density",
63+
"experimental_neuron_density",
64+
"experimental_synapses_per_connection",
65+
"memodel",
66+
"mesh",
67+
"me_type_density",
68+
"reconstruction_morphology",
69+
"electrical_cell_recording",
70+
"electrical_recording_stimulus",
71+
"single_neuron_simulation",
72+
"single_neuron_synaptome",
73+
"single_neuron_synaptome_simulation",
74+
"ion_channel_model",
75+
"subject",
76+
"validation_result",
77+
],
78+
affected_columns=[
79+
TableReference(table_schema="public", table_name="entity", column_name="type")
80+
],
81+
enum_values_to_rename=[],
82+
)
83+
# ### end Alembic commands ###
84+
85+
86+
def downgrade() -> None:
87+
# ### commands auto generated by Alembic - please adjust! ###
88+
op.sync_enum_values(
89+
enum_schema="public",
90+
enum_name="entitytype",
91+
new_values=[
92+
"analysis_software_source_code",
93+
"brain_atlas",
94+
"brain_atlas_region",
95+
"emodel",
96+
"cell_composition",
97+
"experimental_bouton_density",
98+
"experimental_neuron_density",
99+
"experimental_synapses_per_connection",
100+
"memodel",
101+
"mesh",
102+
"me_type_density",
103+
"reconstruction_morphology",
104+
"electrical_cell_recording",
105+
"electrical_recording_stimulus",
106+
"single_neuron_simulation",
107+
"single_neuron_synaptome",
108+
"single_neuron_synaptome_simulation",
109+
"ion_channel_model",
110+
"subject",
111+
"validation_result",
112+
],
113+
affected_columns=[
114+
TableReference(table_schema="public", table_name="entity", column_name="type")
115+
],
116+
enum_values_to_rename=[],
117+
)
118+
op.add_column(
119+
"memodel",
120+
sa.Column(
121+
"threshold_current",
122+
sa.DOUBLE_PRECISION(precision=53),
123+
autoincrement=False,
124+
nullable=True,
125+
),
126+
)
127+
op.add_column(
128+
"memodel",
129+
sa.Column(
130+
"holding_current", sa.DOUBLE_PRECISION(precision=53), autoincrement=False, nullable=True
131+
),
132+
)
133+
op.drop_index(
134+
op.f("ix_memodel_calibration_result_calibrated_entity_id"),
135+
table_name="memodel_calibration_result",
136+
)
137+
op.drop_table("memodel_calibration_result")
138+
# ### end Alembic commands ###

app/cli/import_data.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
Measurement,
5050
MeasurementAnnotation,
5151
MEModel,
52+
MEModelCalibrationResult,
5253
METypeDensity,
5354
MTypeClass,
5455
MTypeClassification,
@@ -901,13 +902,23 @@ def ingest(db, project_context, data_list, all_data_by_id, hierarchy_name: str):
901902
strain_id=morphology.strain_id,
902903
creation_date=createdAt,
903904
update_date=updatedAt,
904-
holding_current=data.get("holding_current"),
905-
threshold_current=data.get("threshold_current"),
906905
)
907906

908907
db.add(db_item)
909908
db.flush()
910-
909+
db_calibration = MEModelCalibrationResult(
910+
calibrated_entity_id=db_item.id,
911+
holding_current=data.get("holding_current", 0),
912+
threshold_current=data.get("threshold_current", 0),
913+
authorized_project_id=project_context.project_id,
914+
authorized_public=AUTHORIZED_PUBLIC,
915+
created_by_id=created_by_id,
916+
updated_by_id=updated_by_id,
917+
creation_date=createdAt,
918+
update_date=updatedAt,
919+
)
920+
db.add(db_calibration)
921+
db.flush()
911922
utils.import_contribution(data, db_item.id, db)
912923

913924
for annotation in ensurelist(data.get("annotation", [])):

app/db/model.py

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -482,13 +482,17 @@ class MEModel(
482482
"ReconstructionMorphology", foreign_keys=[morphology_id], uselist=False
483483
)
484484

485-
holding_current: Mapped[float | None]
486-
threshold_current: Mapped[float | None]
487-
488485
emodel_id: Mapped[uuid.UUID] = mapped_column(ForeignKey(f"{EntityType.emodel}.id"))
489486

490487
emodel = relationship("EModel", foreign_keys=[emodel_id], uselist=False)
491488

489+
calibration_result = relationship(
490+
"MEModelCalibrationResult",
491+
uselist=False,
492+
foreign_keys="MEModelCalibrationResult.calibrated_entity_id",
493+
lazy="joined",
494+
)
495+
492496
__mapper_args__ = {"polymorphic_identity": __tablename__} # noqa: RUF012
493497

494498

@@ -831,6 +835,25 @@ class ValidationResult(Entity):
831835
}
832836

833837

838+
class MEModelCalibrationResult(Entity):
839+
__tablename__ = EntityType.memodel_calibration_result.value
840+
id: Mapped[uuid.UUID] = mapped_column(ForeignKey("entity.id"), primary_key=True)
841+
holding_current: Mapped[float]
842+
threshold_current: Mapped[float]
843+
rin: Mapped[float | None]
844+
calibrated_entity_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("memodel.id"), index=True)
845+
calibrated_entity: Mapped[Entity] = relationship(
846+
"MEModel",
847+
uselist=False,
848+
foreign_keys=[calibrated_entity_id],
849+
)
850+
851+
__mapper_args__ = { # noqa: RUF012
852+
"polymorphic_identity": __tablename__,
853+
"inherit_condition": id == Entity.id,
854+
}
855+
856+
834857
class Asset(Identifiable):
835858
"""Asset table."""
836859

app/db/types.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ class EntityType(StrEnum):
5353
brain_atlas_region = auto()
5454
emodel = auto()
5555
cell_composition = auto()
56+
memodel_calibration_result = auto()
5657
experimental_bouton_density = auto()
5758
experimental_neuron_density = auto()
5859
experimental_synapses_per_connection = auto()
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import uuid
2+
from typing import Annotated
3+
4+
from fastapi_filter import FilterDepends
5+
6+
from app.db.model import MEModelCalibrationResult
7+
from app.filters.base import CustomFilter
8+
from app.filters.common import EntityFilterMixin
9+
10+
11+
class MEModelCalibrationResultFilter(
12+
CustomFilter,
13+
EntityFilterMixin,
14+
):
15+
passed: bool | None = None
16+
calibrated_entity_id: uuid.UUID | None = None
17+
18+
order_by: list[str] = ["calibrated_entity_id"] # noqa: RUF012
19+
20+
class Constants(CustomFilter.Constants):
21+
model = MEModelCalibrationResult
22+
ordering_model_fields = ["calibrated_entity_id"] # noqa: RUF012
23+
24+
25+
MEModelCalibrationResultFilterDep = Annotated[
26+
MEModelCalibrationResultFilter, FilterDepends(MEModelCalibrationResultFilter)
27+
]

app/routers/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
license,
2121
measurement_annotation,
2222
memodel,
23+
memodel_calibration_result,
2324
morphology,
2425
mtype,
2526
organization,
@@ -54,6 +55,7 @@
5455
license.router,
5556
measurement_annotation.router,
5657
memodel.router,
58+
memodel_calibration_result.router,
5759
morphology.router,
5860
mtype.router,
5961
organization.router,
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from fastapi import APIRouter
2+
3+
import app.service.memodel_calibration_result
4+
5+
router = APIRouter(
6+
prefix="/memodel-calibration-result",
7+
tags=["memodel-calibration-result"],
8+
)
9+
read_many = router.get("")(app.service.memodel_calibration_result.read_many)
10+
read_one = router.get("/{id_}")(app.service.memodel_calibration_result.read_one)
11+
create_one = router.post("")(app.service.memodel_calibration_result.create_one)

app/schemas/me_model.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
)
1818
from app.schemas.contribution import ContributionReadWithoutEntity
1919
from app.schemas.emodel import EModelRead
20+
from app.schemas.memodel_calibration_result import MEModelCalibrationResultRead
2021
from app.schemas.morphology import ReconstructionMorphologyRead
2122

2223

@@ -25,8 +26,6 @@ class MEModelBase(BaseModel):
2526
name: str
2627
description: str
2728
validation_status: ValidationStatus = ValidationStatus.created
28-
holding_current: float | None = None
29-
threshold_current: float | None = None
3029

3130

3231
# To be used by entities who reference MEModel
@@ -59,3 +58,4 @@ class MEModelRead(
5958
etypes: list[ETypeClassRead] | None
6059
morphology: ReconstructionMorphologyRead
6160
emodel: EModelRead
61+
calibration_result: MEModelCalibrationResultRead | None
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import uuid
2+
3+
from pydantic import BaseModel
4+
5+
from app.schemas.agent import CreatedByUpdatedByMixin
6+
from app.schemas.base import (
7+
CreationMixin,
8+
IdentifiableMixin,
9+
)
10+
11+
12+
class MEModelCalibrationResultBase(BaseModel):
13+
"""Base model for MEModel calibration results."""
14+
15+
holding_current: float
16+
threshold_current: float
17+
rin: float | None = None
18+
calibrated_entity_id: uuid.UUID
19+
20+
21+
class MEModelCalibrationResultRead(
22+
MEModelCalibrationResultBase, CreationMixin, IdentifiableMixin, CreatedByUpdatedByMixin
23+
):
24+
"""Read model for MEModel calibration results, including entity metadata."""
25+
26+
27+
class MEModelCalibrationResultCreate(MEModelCalibrationResultBase):
28+
"""Create model for MEModel calibration results."""

app/service/memodel.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
Contribution,
1515
EModel,
1616
MEModel,
17+
MEModelCalibrationResult,
1718
ReconstructionMorphology,
1819
)
1920
from app.dependencies.auth import UserContextDep, UserContextWithProjectIdDep
@@ -70,6 +71,8 @@ def _load(select: Select):
7071
joinedload(MEModel.etypes),
7172
joinedload(MEModel.created_by),
7273
joinedload(MEModel.updated_by),
74+
joinedload(MEModel.calibration_result).joinedload(MEModelCalibrationResult.created_by),
75+
joinedload(MEModel.calibration_result).joinedload(MEModelCalibrationResult.updated_by),
7376
raiseload("*"),
7477
)
7578

0 commit comments

Comments
 (0)