Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions src/ethopy/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ def _set_defaults(self) -> None:
"behavior": "lab_behavior",
"interface": "lab_interface",
"recording": "lab_recordings",
"mice": "lab_mice",
},
"logging": {
"level": "INFO",
Expand Down
4 changes: 3 additions & 1 deletion src/ethopy/core/logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,19 +60,21 @@ def _set_connection() -> None:
behavior: The virtual module for behavior.
interface: The virtual module for interface.
recording: The virtual module for recording.
mice: The virtual module for the animal colony.
public_conn: The connection object for public access.

Returns:
None

"""
global experiment, stimulus, behavior, interface, recording, public_conn
global experiment, stimulus, behavior, interface, recording, mice, public_conn
virtual_modules, public_conn = create_virtual_modules(SCHEMATA)
experiment = virtual_modules["experiment"]
stimulus = virtual_modules["stimulus"]
behavior = virtual_modules["behavior"]
recording = virtual_modules["recording"]
interface = virtual_modules["interface"]
mice = virtual_modules["mice"]


_set_connection()
Expand Down
233 changes: 233 additions & 0 deletions src/ethopy/core/mice.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
"""Create tables for the lab_mice schema.

Mirrors the animal colony schema hosted at `lab_mice`: animal identity, lines and
genotypes, weights, surgeries/implants and location transfers. Importing this
module declares any missing table in the schema mapped to ``mice`` in SCHEMATA.
"""

import datajoint as dj

from ethopy.core.logger import mice # noqa: F401


@mice.schema
class Lines(dj.Manual):
"""Basic mouse line info."""

definition = """
# Basic mouse line info
line : varchar(100) # Mouse Line Abbreviation
---
line_full : varchar(100) # full line name
rec_strain : varchar(20) # recipient strain
donor_strain : varchar(20) # donor strain
n=null : tinyint # minimum number of backcrosses to recipient strain
seq : varchar(5000) # sequence of transgene, if available
line_notes : varchar(4096) # other comments
line_ts=CURRENT_TIMESTAMP : timestamp # automatic
"""


@mice.schema
class Mice(dj.Manual):
"""Basic mouse info."""

definition = """
# Basic mouse info
animal_id : int # id number
---
other_id='' : varchar(20) # alternative id number
dob=null : date # animal's date of birth
dow=null : date # animal's date of weaning
sex='unknown' : enum('M','F','unknown') # animal's sex
color='unknown' : enum('Black','Brown','White','unknown') # animal's color
line='' : varchar(255) # mouse line
genotype='unknown' : enum('homozygous','heterozygous','hemizygous','positive','negative','wild type','unknown')
ear_punch='unknown' : enum('None','R','L','RL','RR','LL','unknown') # animal's ear punch
owner='none' : enum('manolis','maria','emina','Other','Available','none') # mouse's owner
fluo_test='unknown' : enum('unknown','no','yes') # fluorescence test result
mouse_notes='' : varchar(4096) # other comments and distinguishing features
facility='unknown' : enum('TMF','Taub','Other','unknown') # animal's current facility
room='unknown' : enum('VK3','VH1','T014','T057','T086D','Other','unknown','T027') # animal's current room
rack=null : char(4) # animal's current rack
row='' : char(1) # animal's current row
mouse_ts=CURRENT_TIMESTAMP : timestamp # automatic
cage_id='' : varchar(100) # animal's current cage
usage='unknown' : enum('in use','available','euthanize','unknown') # availability
"""


@mice.schema
class Death(dj.Manual):
"""Info about each mouse's death."""

definition = """
# info about each mouse's death
-> Mice
---
dod=null : date # date of death
death_notes='' : varchar(4096) # other comments
death_ts=CURRENT_TIMESTAMP : timestamp # automatic
"""


@mice.schema
class Founders(dj.Manual):
"""Additional info about founder mice."""

definition = """
# Additional info about founder mice
-> Mice
-> Lines
---
source : varchar(100) # source of mouse (lab, company)
doa=null : date # date of arrival
founder_notes : varchar(4096) # other comments
founder_ts=CURRENT_TIMESTAMP : timestamp # automatic
"""


@mice.schema
class Genotypes(dj.Manual):
"""Info about each mouse's genotype."""

definition = """
# info about each mouse's genotype
-> Mice
-> Lines
---
genotype='unknown' : enum('homozygous','heterozygous','hemizygous','positive','negative','wild type','unknown') # animal's genotype
genotype_notes=null : varchar(4096) # other comments
genotype_ts=CURRENT_TIMESTAMP : timestamp # automatic
"""


@mice.schema
class Parents(dj.Manual):
"""Parent-child relationships between mice."""

definition = """
# parent-child relationships between mice
-> Mice
parent_id : varchar(20) # id number of parent
---
relation_notes='' : varchar(4096) # other comments
relation_ts=CURRENT_TIMESTAMP : timestamp # automatic
"""


@mice.schema
class Transfers(dj.Manual):
"""Completed transfers."""

definition = """
# completed transfers
-> Mice
dot : date # date of transfer
---
from_owner='none' : enum('alex','manolis','Other','Available','none') # previous owner
to_owner='none' : enum('alex','manolis','Other','Available','none') # new owner
from_facility='unknown' : enum('TMF','Taub','Other','unknown') # animal's previous facility
to_facility='unknown' : enum('TMF','Taub','Other','unknown') # animal's new facility
from_room='unknown' : enum('VD4','T014','T057','T086D','Other','unknown','VK3','T027','VH1') # animal's previous room
to_room='unknown' : enum('VD4','T014','T057','T086D','Other','unknown','VK3','T027','VH1') # animal's new room
from_rack=null : char(4) # animal's previous rack
to_rack=null : char(4) # animal's new rack
from_row='' : char(1) # animal's previous row
to_row='' : char(1) # animal's new row
transfer_notes='' : varchar(4096) # other comments
transfer_ts=CURRENT_TIMESTAMP : timestamp # automatic
"""


@mice.schema
class MouseWeight(dj.Manual):
"""Weight measurements, logged by the setup at session start."""

definition = """
animal_id : int unsigned # id number
timestamp=CURRENT_TIMESTAMP : timestamp # timestamp of weight
---
weight : double(5,2) # weight in grams
"""


@mice.schema
class GrowthCurve(dj.Lookup):
"""Reference weight per age, sex and genotype."""

definition = """
age : int # in weeks
gender : enum('male','female')
genotype : enum('C57BL/6J')
---
weight=null : double # in grams
std=null : double # standard deviation
"""


@mice.schema
class SurgeryType(dj.Lookup):
"""Surgery types."""

definition = """
# Surgery types
surgery : varchar(16) # aim
---
description='' : varchar(2048) # description
"""


@mice.schema
class Surgery(dj.Manual):
"""Surgery information."""

definition = """
# Surgery information
animal_id : smallint unsigned # animal id
timestamp : datetime # timestamp
---
user_name : varchar(16) # user performing the surgery
-> SurgeryType
note=null : varchar(2048) # surgery notes
"""


@mice.schema
class Implants(dj.Manual):
"""Implant information."""

definition = """
animal_id : int unsigned # id number
doi : date # date of implantation
---
experimenter='Other' : varchar(64) # name of experimenter
anesthesia='Other' : enum('isoflurane','ketamine/xylazine mix','Other') # anesthesia method
comments=null : varchar(100)
"""


@mice.schema
class Handling(dj.Manual):
"""Handling sessions."""

definition = """
animal_id : int unsigned # id number
timestamp : datetime # date of handling
---
experimenter='Other' : varchar(64) # name of experimenter
type='Touch' : enum('Touch','Other') # handling method
comments=null : varchar(100)
"""


@mice.schema
class Person(dj.Manual):
"""People in the lab."""

definition = """
# people in the lab
person : varchar(12) # person's short name
---
full_name : varchar(64) # person's full name
"""
60 changes: 54 additions & 6 deletions src/ethopy/experiments/calibrate.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,9 @@ def cleanup(self):
if hasattr(self, "menu"):
self.menu.disable()

# Not pygame.quit(): it frees fonts still cached by
# pygame_menu, segfaulting the next menu built.
pygame.display.quit()
pygame.quit()
except Exception as e:
log.warning(f"Error during pygame cleanup: {e}")

Expand All @@ -151,6 +152,47 @@ def cleanup(self):
except Exception as e:
log.warning(f"Error updating logger status: {e}")

def _clear_menu(self):
"""Clear the menu and re-add the always-available Abort button.

Every step rebuilds the menu from scratch, so the button has to be
re-added after each clear. It floats, so it does not shift the layout
of the widgets added after it.
"""
self.menu.clear()
self.menu.add.button(
"Abort",
self.abort,
align=pygame_menu.locals.ALIGN_LEFT,
float=True,
padding=(5, 10, 5, 10),
background_color=(153, 0, 0),
font_size=25,
).translate(650, 350)

def abort(self):
"""Stop the calibration immediately.

Measurements already written by log_pulse_weight are left untouched;
only the remaining pulses and weight prompts are skipped.
"""
log.warning("Calibration aborted by user")
try:
self.menu.clear()
self.menu.add.label(
"Calibration aborted!", float=True, font_size=30
).translate(20, 80)
try:
self.menu.draw(self.screen)
pygame.display.flip()
time.sleep(2)
except pygame.error:
pass # Display might already be quit
except Exception as e:
log.warning(f"Error during abort: {e}")
finally:
self.stop = True

def exit(self):
"""exit _summary_

Expand All @@ -176,15 +218,15 @@ def exit(self):

def create_pressure_menu(self):
"""The First menu in Calibration where is definde the air pressure in PSI"""
self.menu.clear()
self._clear_menu()
self.button_input("Enter air pressure (PSI)", self.create_pulsenum_menu)

def create_pulsenum_menu(self):
"""Before start pulses show a label in screen to place a pad under the ports"""
self.pressure = self.curr
self.curr = ""
if self.cal_idx < len(self.session_params["pulsenum"]):
self.menu.clear()
self._clear_menu()
self.menu.add.label(
"Place zero-weighted pad under the port", float=True, font_size=30
).translate(20, 80)
Expand All @@ -206,7 +248,7 @@ def create_pulse_num(self):
"""
self.pulse = 0
msg = f"Pulse {self.pulse + 1}/{self.session_params['pulsenum'][self.cal_idx]}"
self.menu.clear()
self._clear_menu()
pulses_label = self.menu.add.label(
msg,
float=True,
Expand All @@ -224,6 +266,12 @@ def run_pulses(self, widget, menu):
widget (_type_): The widget that uses the function
menu (_type_): The current menu
"""
# run() calls menu.update() and menu.draw() in the same iteration, so an
# Abort pressed during update would otherwise still deliver one more round
# of pulses when this draw callback fires.
if self.stop:
return

if self.pulse < self.session_params["pulsenum"][self.cal_idx]:
self.msg = f"Pulse {self.pulse + 1}/{self.session_params['pulsenum'][self.cal_idx]}"
log.info(f"\r{self.msg}")
Expand Down Expand Up @@ -251,10 +299,10 @@ def run_pulses(self, widget, menu):

def create_port_weight(self):
"""A menu with numpad for defining the water in every port"""
self.menu.clear()
self._clear_menu()
cal_idx = self.cal_idx - 1
if self.session_params["save"]:
self.menu.clear()
self._clear_menu()
if len(self.ports) != 0:
if len(self.ports) != len(self.session_params["ports"]):
self.log_pulse_weight(
Expand Down
1 change: 1 addition & 0 deletions src/ethopy/setup_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,7 @@ def createschema() -> None:
("core/interface", "from ethopy.core.interface import *"),
("core/behavior", "from ethopy.core.behavior import *"),
("core/recordings", "from ethopy.core.recordings import *"),
("core/mice", "from ethopy.core.mice import *"),
("stimuli", "from ethopy.stimuli import *"),
("behaviors", "from ethopy.behaviors import *"),
("experiments", "from ethopy.experiments import *"),
Expand Down
Loading
Loading