569 lines
19 KiB
Python
569 lines
19 KiB
Python
from fishbook.backend.database import Cells, Datasets, CellDatasetMap, Subjects, SubjectProperties, SubjectDatasetMap, Stimuli, Repros
|
|
from .util import safe_get_val, results_check
|
|
import nixio as nix
|
|
import os
|
|
import numpy as np
|
|
from IPython import embed
|
|
from fishbook.backend.util import progress
|
|
|
|
class Cell:
|
|
def __init__(self, cell_id=None, tuple=None):
|
|
if tuple:
|
|
self.__tuple = tuple
|
|
elif cell_id:
|
|
pattern = "cell_id like '{0:s}'".format(cell_id)
|
|
cells = (Cells & pattern)
|
|
results_check(cells, cell_id, "Cell ID")
|
|
self.__tuple = cells.fetch(as_dict=True)[0]
|
|
else:
|
|
print("Empty Cell, not linked to any database entry!")
|
|
|
|
@property
|
|
def id(self):
|
|
return self.__tuple["cell_id"] if "cell_id" in self.__tuple.keys() else ""
|
|
|
|
@property
|
|
def type(self):
|
|
return self.__tuple["cell_type"] if "cell_type" in self.__tuple.keys() else ""
|
|
|
|
@property
|
|
def firing_rate(self):
|
|
return self.__tuple["firing_rate"] if "firing_rate" in self.__tuple.keys() else 0.0
|
|
|
|
@property
|
|
def location(self):
|
|
keys = ["structure", "region", "subregion", "depth", "lateral_pos", "transversal_section"]
|
|
loc = {}
|
|
for k in keys:
|
|
if k in self.__tuple.keys():
|
|
loc[k] = self.__tuple[k]
|
|
else:
|
|
loc[k] = ""
|
|
return loc
|
|
|
|
@property
|
|
def subject(self):
|
|
return Subject(tuple=(Subjects & {"subject_id": self.__tuple["subject_id"]}).fetch(limit=1, as_dict=True)[0])
|
|
|
|
@property
|
|
def datasets(self):
|
|
return [Dataset(tuple=d) for d in (Datasets & (CellDatasetMap & {"cell_id": self.id})).fetch(as_dict=True)]
|
|
|
|
@property
|
|
def repro_runs(self):
|
|
repros = (Repros & "cell_id = '%s'" % self.id)
|
|
return [RePro(tuple=r) for r in repros]
|
|
|
|
@staticmethod
|
|
def unique_cell_types():
|
|
return np.unique(Cells.fetch("cell_type"))
|
|
|
|
@staticmethod
|
|
def find(cell_type=None, species=None, quality="good"):
|
|
cs = Cells * CellDatasetMap * Datasets * Subjects
|
|
if cell_type:
|
|
cs = cs & "cell_type like '{0:s}'".format(cell_type)
|
|
if species:
|
|
cs = cs & "species like '%{0:s}%'".format(species)
|
|
if quality:
|
|
cs = cs & "quality like '{0:s}'".format(quality)
|
|
return [Cell(tuple=c) for c in cs]
|
|
|
|
def __str__(self):
|
|
str = ""
|
|
str += "Cell: %s \t type: %s\n"%(self.id, self.type)
|
|
return str
|
|
|
|
@property
|
|
def _tuple(self):
|
|
return self.__tuple.copy()
|
|
|
|
|
|
class Dataset:
|
|
""" The Dataset class represents an entry in the "datasets" table in the database. In the relacs
|
|
context a Dataset represents all that has been recorded between pressing enter. For example
|
|
all that is stored in the folder 2020-01-01-aa.
|
|
|
|
Dataset contains several basic properties and allows access to associated cells and subjects.
|
|
"""
|
|
def __init__(self, dataset_id=None, tuple=None):
|
|
"""Constructor of a Dataset entity.
|
|
|
|
Args:
|
|
dataset_id (str, optional): the id. Defaults to None.
|
|
tuple (tuple, optional): The properties used to identify the entity in the database. Usually only for internal use. Defaults to None.
|
|
"""
|
|
self.__samplerate = 0.0
|
|
if tuple:
|
|
self.__tuple = tuple
|
|
elif dataset_id:
|
|
pattern = "dataset_id like '{0:s}'".format(dataset_id)
|
|
dsets = (Datasets & pattern)
|
|
results_check(dsets, dataset_id, "Dataset ID")
|
|
self.__tuple = dsets.fetch(limit=1, as_dict=True)[0]
|
|
else:
|
|
print("Empty dataset, not linked to any database entry!")
|
|
if len(self.__tuple.keys()) > 0:
|
|
self.__find_samplerate()
|
|
|
|
@property
|
|
def id(self):
|
|
"""The id of the dataset.
|
|
|
|
Returns:
|
|
str: the id
|
|
"""
|
|
return self.__tuple["dataset_id"]
|
|
|
|
@property
|
|
def experimenter(self):
|
|
"""The person who did record the dataset.
|
|
|
|
Returns:
|
|
str: The experimenter
|
|
"""
|
|
return self.__tuple["experimenter"]
|
|
|
|
@property
|
|
def recording_date(self):
|
|
"""The Dataset's recording date.
|
|
|
|
Returns:
|
|
str: the recording date.
|
|
"""
|
|
return self.__tuple["recording_date"]
|
|
|
|
@property
|
|
def recording_duration(self):
|
|
"""Recording duration.
|
|
|
|
Returns:
|
|
float: recprding duration given in seconds.
|
|
"""
|
|
return self.__tuple["duration"]
|
|
|
|
@property
|
|
def quality(self):
|
|
"""The quality assessment as assigned by the experimenter.
|
|
|
|
Returns:
|
|
str: the quality
|
|
"""
|
|
return self.__tuple["quality"]
|
|
|
|
@property
|
|
def has_nix(self):
|
|
"""Whether or not a nix file was recorded.
|
|
|
|
Returns:
|
|
bool: true or false
|
|
"""
|
|
return self.__tuple["has_nix"]
|
|
|
|
@property
|
|
def comment(self):
|
|
"""The comment entered by the experimenter.
|
|
|
|
Returns:
|
|
str: the comment
|
|
"""
|
|
return self.__tuple["comment"]
|
|
|
|
@property
|
|
def data_source(self):
|
|
"""Where the data is stored (at least at the time of import).
|
|
|
|
Returns:
|
|
str: the path to the data.
|
|
"""
|
|
return self.__tuple["data_source"]
|
|
|
|
@property
|
|
def setup(self):
|
|
"""The recording setup.
|
|
|
|
Returns:
|
|
str: the setup
|
|
"""
|
|
return self.__tuple["setup"]
|
|
|
|
@property
|
|
def cells(self):
|
|
"""The list of cells that are associated with this dataset.
|
|
|
|
Returns:
|
|
list of fishbook.Cell: the cells.
|
|
"""
|
|
|
|
cell_list = (Cells * (CellDatasetMap & self.__tuple))
|
|
return [Cell(tuple=c) for c in cell_list]
|
|
|
|
@property
|
|
def subjects(self):
|
|
"""The subjects that are associated with this dataset.
|
|
|
|
Returns:
|
|
list of fishbook.Subject: list of subjects
|
|
"""
|
|
subject_list = (Subjects * (SubjectDatasetMap & self.__tuple))
|
|
return [Subject(tuple=s) for s in subject_list]
|
|
|
|
@property
|
|
def samplerate(self):
|
|
"""Get the samplerate of the data.
|
|
|
|
Returns:
|
|
float: the sample rate
|
|
"""
|
|
return self.__samplerate
|
|
|
|
@staticmethod
|
|
def datasetCount():
|
|
"""Returns the total number of datasets defined in the database.
|
|
|
|
Returns:
|
|
int: the count
|
|
"""
|
|
return len(Datasets())
|
|
|
|
@staticmethod
|
|
def find(min_duration=None, experimenter=None, quality=None, test=False):
|
|
"""Find dataset entries in the database. You may restrict the search by providing the following arguments.
|
|
|
|
Args:
|
|
min_duration (float, optional): minimum duration of the recording session, if not given, any length datasets will be returned. Defaults to None.
|
|
experimenter (str, optional): the name of the one who did the recording. The name does not need to be the full name. Defaults to None.
|
|
quality (str, optional): the quality assigned to the dataset during recording (e.g. good, fair, poor). Defaults to None.
|
|
test (bool, optional): defines whether this is a test run and thus whether or not the search results should be fetched, which can take a while. Defaults to False.
|
|
Returns:
|
|
list: list of Dataset object matching the search criteria, empty if test==True
|
|
int: Count of matching results
|
|
"""
|
|
dataset_list = Datasets()
|
|
if min_duration:
|
|
dataset_list = dataset_list & "duration > %.2f" % min_duration
|
|
if experimenter:
|
|
dataset_list = dataset_list & "experimenter like '%{0:s}%'".format(experimenter)
|
|
if quality:
|
|
dataset_list = dataset_list & "quality like '{0:s}'".format(quality)
|
|
|
|
results = []
|
|
total = len(dataset_list)
|
|
if not test:
|
|
for i, d in enumerate(dataset_list):
|
|
progress(i+1, total, "fetching data")
|
|
results.append(Dataset(tuple=d))
|
|
return results, total
|
|
|
|
def __find_samplerate(self, trace_name="V-1"):
|
|
if self.has_nix and os.path.exists(os.path.join(self.data_source, self.id, '.nix')):
|
|
f = nix.File.open(os.path.join(self.data_source, self.id, '.nix'), nix.FileMode.ReadOnly)
|
|
b = f.blocks[0]
|
|
if trace_name in b.data_arrays:
|
|
trace = b.data_arrays[trace_name]
|
|
if trace.dimensions[0].dimension_type == nix.DimensionType.Sample:
|
|
self.__samplerate = 1./trace.dimensions[0].sampling_interval
|
|
else:
|
|
print("Requested trace %s has no sampled dimension!" % trace_name)
|
|
else:
|
|
print("Requested trace %s was not found!" % trace_name)
|
|
f.close()
|
|
else:
|
|
stimulus_file = os.path.join(self.data_source , 'stimuli.dat')
|
|
if not os.path.exists(stimulus_file):
|
|
return
|
|
lines = None
|
|
with open(stimulus_file, 'r') as f:
|
|
lines = f.readlines()
|
|
for l in lines:
|
|
if "sample interval1" in l:
|
|
si = l.strip().split(":")[-1][:-2]
|
|
break
|
|
self.__samplerate = 1000. / float(si)
|
|
|
|
@property
|
|
def _tuple(self):
|
|
return self.__tuple.copy()
|
|
|
|
def __str__(self):
|
|
str = "id: %s\n" % self.id
|
|
str += "recorded: %s \t by:%s\n" % (self.recording_date, self.experimenter)
|
|
str += "duration: %ss \t quality:%s\n" % (self.recording_duration, self.quality)
|
|
str += "comment: %s" % self.comment
|
|
return str
|
|
|
|
|
|
class RePro:
|
|
"""Repro class represents an entry in the repro table. This is a run of a certain relacs "Research Protocol".
|
|
|
|
"""
|
|
def __init__(self, repro_id=None, tuple=None):
|
|
if tuple:
|
|
self.__tuple = tuple
|
|
elif repro_id:
|
|
repros = (RePro & "repro_id like '{0:s}'".format(repro_id))
|
|
results_check(repros, repro_id, "RePro ID")
|
|
self.__tuple = repros.fetch(limit=1, as_dict=True)[0]
|
|
else:
|
|
self.__tuple = {}
|
|
print("Empty RePro, not linked to any database entry!")
|
|
|
|
@property
|
|
def id(self):
|
|
return safe_get_val(self.__tuple, "repro_id", "")
|
|
|
|
@property
|
|
def run(self):
|
|
return safe_get_val(self.__tuple, "run", -1)
|
|
|
|
@property
|
|
def cell_id(self):
|
|
return safe_get_val(self.__tuple, "cell_id", "")
|
|
|
|
@property
|
|
def cell(self):
|
|
return Cell(self.cell_id)
|
|
|
|
@property
|
|
def dataset(self):
|
|
datasets = (Cells & "cell_id = '%s'" % self.cell_id) * CellDatasetMap * Datasets
|
|
d = datasets.proj('dataset_id', 'data_source', 'experimenter', 'setup', 'recording_date',
|
|
'quality', 'comment', 'duration', 'has_nix').fetch(limit=1, as_dict=True)[0]
|
|
del d["cell_id"]
|
|
return Dataset(tuple=d)
|
|
|
|
@property
|
|
def name(self):
|
|
return safe_get_val(self.__tuple, "repro_name", "")
|
|
|
|
@property
|
|
def settings(self):
|
|
return safe_get_val(self.__tuple, "settings", "")
|
|
|
|
@property
|
|
def start(self):
|
|
return safe_get_val(self.__tuple, "start", 0.0)
|
|
|
|
@property
|
|
def duration(self):
|
|
return safe_get_val(self.__tuple, "duration", 0.0)
|
|
|
|
@property
|
|
def stimuli(self):
|
|
stims = Stimuli & "repro_id = '%s'" % self.id & "cell_id = '%s'" % self.cell_id
|
|
return [Stimulus(tuple=s) for s in stims]
|
|
|
|
@staticmethod
|
|
def find(name=None, cell_id=None, cell_type=None, species=None, settings=[], quality=None, test=False):
|
|
""" Finds Repro runs in the database. When called without arguments, all RePro runs are
|
|
found and returned.
|
|
Search can be narrowed by providing further search hints.
|
|
|
|
**Note:** Cell type, quality, and species are ignored, if cell_id is provided
|
|
**Note:** If there are many results fetching and creating objects may take a while. Consider running as test.
|
|
Args:
|
|
name (str, optional): The RePro name, or a part of it. Defaults to None.
|
|
cell_id (str, optional): The cell_id. If given type, quality and species are ignored. Defaults to None.
|
|
cell_type (str, optional): type of cell. Defaults to None.
|
|
species (str, optional): The species name, or parts of it. Defaults to None.
|
|
settings (list of string, optional): List of repro settings e.g. ["contrast: 20", "am: false"]. An AND connection is assumed. Defaults to None.
|
|
quality (str, optional): The quality assessment. Defaults to None.
|
|
test (bool, optional): defines whether or not the database matches should be fetched from the database.
|
|
Returns:
|
|
list of fishbook.RePro: list of results or empty list if test == True
|
|
int: number of matches
|
|
"""
|
|
|
|
repros = Repros & True
|
|
if name:
|
|
repros = repros & "repro_name like '%{0:s}%'".format(name)
|
|
if len(settings) > 0:
|
|
settings_pattern = " AND ".join(["settings like '%{0:s}%'".format(s) for s in settings])
|
|
repros = repros & settings_pattern
|
|
if cell_id:
|
|
repros = repros & "cell_id = '{0:s}'".format(cell_id)
|
|
if not cell_id and (cell_type or species or quality):
|
|
cells = (Cells * CellDatasetMap * Datasets) * Subjects
|
|
if cell_type:
|
|
cells = cells & "cell_type like '%{0:s}%'".format(cell_type)
|
|
if species:
|
|
cells = cells & "species like '%{0:s}%'".format(species)
|
|
if quality:
|
|
cells = cells & "quality like '{0:s}'".format(quality)
|
|
p = cells.proj("quality", "experimenter")
|
|
repros = repros & p
|
|
|
|
results = []
|
|
total = len(repros)
|
|
if not test:
|
|
for i, r in enumerate(repros):
|
|
results.append(RePro(tuple=r))
|
|
progress(i+1, total, "fetching %i matches" % total)
|
|
|
|
return results, total
|
|
|
|
@property
|
|
def _tuple(self):
|
|
return self.__tuple.copy()
|
|
|
|
|
|
class Stimulus:
|
|
"""The stimulus class represents a Stimulus that was presented. A Stimulus has several properties
|
|
such as the start time, the duarion
|
|
"""
|
|
def __init__(self, stimulus_id=None, tuple=None):
|
|
if tuple:
|
|
self.__tuple = tuple
|
|
elif stimulus_id:
|
|
stimuli = Stimuli & "stimulus_id = '%s'" %stimulus_id
|
|
results_check(stimuli, stimulus_id, "Stimulus ID")
|
|
self.__tuple = stimuli.fetch(limit=1, as_dict=True)[0]
|
|
else:
|
|
print("Empty RePro, not linked to any database entry!")
|
|
|
|
def __str__(self):
|
|
str = "Stimulus %s: " % safe_get_val(self.__tuple, "stimulus_id", "")
|
|
str += "\nStart time/index: %0.4f/%i, duration: %.3f" % (safe_get_val(self.__tuple, "start_time", 0.0),
|
|
safe_get_val(self.__tuple, "start_index", -1),
|
|
safe_get_val(self.__tuple, "duration", 0.0))
|
|
return str
|
|
|
|
@property
|
|
def id(self):
|
|
return self.__tuple["stimulus_id"]
|
|
|
|
@property
|
|
def name(self):
|
|
return self.__tuple["stimulus_name"]
|
|
|
|
@property
|
|
def cell(self):
|
|
return Cells & "cell_id = %s" % self.__tuple["cell_id"]
|
|
|
|
@property
|
|
def repro(self):
|
|
return Repros & "repro_id = %s" % self.__tuple["repro_id"]
|
|
|
|
@property
|
|
def start_time(self):
|
|
return self.__tuple["start_time"]
|
|
|
|
@property
|
|
def start_index(self):
|
|
return self.__tuple["start_index"]
|
|
|
|
@property
|
|
def duration(self):
|
|
return self.__tuple["duration"]
|
|
|
|
@property
|
|
def multi_tag_id(self):
|
|
return self.__tuple["mtag_id"]
|
|
|
|
@property
|
|
def run(self):
|
|
return self.__tuple["run"]
|
|
|
|
@property
|
|
def index(self):
|
|
return self.__tuple["stimulus_index"]
|
|
|
|
@property
|
|
def settings(self):
|
|
return safe_get_val(self.__tuple, "settings", "")
|
|
|
|
@property
|
|
def _tuple(self):
|
|
return self.__tuple.copy()
|
|
|
|
@staticmethod
|
|
def find(cell_id=None, repro_id=None, settings=[], test=False):
|
|
"""Find stimulus presentations that match in certain properties.
|
|
Examples:
|
|
|
|
Args:
|
|
cell_id (str, optional): [description]. Defaults to None.
|
|
repro_id (str, optional): [description]. Defaults to None.
|
|
settings (str, optional): [description]. Defaults to []].
|
|
test (bool, optional): whether or not this is a test run. When true, results will not be fetched from the database. Defaults to False.
|
|
Returns:
|
|
List: of matching Stimulus presentations, empty if test==True
|
|
int: count of matches
|
|
"""
|
|
stimuli = Stimuli & True
|
|
if cell_id:
|
|
stimuli = stimuli & "cell_id = '{0:s}'".format(cell_id)
|
|
if repro_id:
|
|
stimuli = stimuli & "repro_id = '{0:s}'".format(repro_id)
|
|
if len(settings) > 0:
|
|
settings_pattern = " AND ".join(["settings like '%{0:s}%'".format(s) for s in settings])
|
|
stimuli = stimuli & settings_pattern
|
|
|
|
results = []
|
|
total = len(stimuli)
|
|
if not test:
|
|
for i, s in enumerate(stimuli):
|
|
results.append(Stimulus(tuple=s))
|
|
progress(i+1, total, "fetching %i matches" % total)
|
|
|
|
return results, total
|
|
|
|
|
|
class Subject:
|
|
def __init__(self, subject_id=None, tuple=None):
|
|
if tuple:
|
|
self.__tuple = tuple
|
|
elif subject_id:
|
|
subjects = Subjects & "subject_id like '{0:s}'".format(subject_id)
|
|
results_check(subjects, subject_id, "Subject ID")
|
|
self.__tuple = subjects.fetch()[0]
|
|
else:
|
|
self.__tuple = {}
|
|
print("Empty Subject, not linked to any database entry!")
|
|
|
|
@property
|
|
def id(self):
|
|
return self.__tuple["subject_id"]
|
|
|
|
@property
|
|
def species(self):
|
|
return self.__tuple["species"]
|
|
|
|
@property
|
|
def cells(self):
|
|
cs = Cells & self.__tuple
|
|
return [Cell(tuple=c) for c in cs]
|
|
|
|
@property
|
|
def properties(self):
|
|
return (SubjectProperties & self.__tuple).fetch(as_dict=True)
|
|
|
|
@property
|
|
def _tuple(self):
|
|
return self.__tuple.copy()
|
|
|
|
@staticmethod
|
|
def find(species=None):
|
|
subjs = Subjects & True
|
|
if species:
|
|
subjs = (Subjects & "species like '%{0:s}%'".format(species))
|
|
|
|
results = []
|
|
total = len(subjs)
|
|
for i, s in enumerate(subjs):
|
|
results.append(Subject(tuple=s))
|
|
progress(i+1, total, "fetching %i matches" % total)
|
|
|
|
return results, total
|
|
|
|
@staticmethod
|
|
def unique_species():
|
|
all_species = (Subjects & True).fetch("species")
|
|
return np.unique(all_species)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
from IPython import embed
|
|
cell = Cell("2010-04-16-ak")
|
|
embed()
|