Source code for DatabaseLayer.Database

import os
import csv
import time
import json
import numpy as np
import logging as log

[docs]class Database(): """ Stores tables to respond to :meth:`API._feature_info` Arguments ---------- path: str The file path to store and access the database _runtime: :class:`RUNTIME` Gets stored as :data:`RUNTIME` Attributes ----------- RUNTIME: :class:`RUNTIME` With keywords needed to load files and use tables """ def __init__(self, path, _runtime): # Get the database keywords self.RUNTIME = _runtime
[docs] def load_config(self, config): """ Loads all files from ``config`` into database Arguments ---------- config: dict all data from :mod:`UtilityLayer.rh_config` Returns --------- :class:`Database` the derived database class instance """ # Get file fields k_files = self.RUNTIME.DB.FILE # Get keywords for the BFLY_CONFIG k_list = k_files.CONFIG.GROUP_LIST k_range = range(len(k_list) - 1) # Join lists from config groups def cat_lists(groups, level): # Get the next group key lev_key = k_list[level] # Get a list of lists of all the next groups g_lists = [g.get(lev_key, []) for g in groups] # Get list of all groups from groups return [g for l in g_lists for g in l] # Join all lists from within config all_lists = reduce(cat_lists, k_range, [config]) # Load all files for each dataset map(self.load_all, all_lists) # Save to disk self.commit() return self
[docs] def load_all(self, source): """ Load the tables, synapses, and neuron configs Arguments ---------- source: dict The configuration options for a dataset """ # Get file fields k_files = self.RUNTIME.DB.FILE # Get keywords for the BFLY_CONFIG k_list = k_files.CONFIG.GROUP_LIST k_dpath = k_files.CONFIG.DPATH.NAME k_path = k_files.CONFIG.PATH.NAME # Get the key to the channels k_channel = k_list[-1] # Set custom names of files for nf in map(k_files.get, k_files.DB_LIST): nf.VALUE = source.get(nf.NAME, nf.DEFAULT) # list of channels for the dataset path c_list = source.get(k_channel, []) d_path = source.get(k_path, '') # List used paths done_paths = [''] # Add all channel paths to database for c_dict in c_list: # Get paths to map to data c_path = c_dict.get(k_path, '') c_dpath = c_dict.get(k_dpath, d_path) if c_dpath: # Map the path to the data path self.add_path(c_path, c_dpath) # if we found a new data path if c_dpath not in done_paths: # Add all tables for the dataset path self.add_tables(c_dpath) # Load all synapses and neurons synapses = self.load_synapses(c_dpath) self.load_neurons(c_dpath, synapses) # Mark the dataset path as fully loaded done_paths.append(c_dpath)
[docs] def add_path(self, c_path, d_path): """ store a link from a ``c_path`` to a ``d_path`` Must be overridden by derived class. Arguments ---------- c_path: str The path to a given channel with images d_path: str The path to the dataset with metadata files """ pass
[docs] def add_tables(self, path): """ Store all the tables for a given path Arguments ---------- path: str The dataset path to metadata files """ # Get keywords for the database k_list = self.RUNTIME.DB.TABLE.LIST # Create all tables for the path for k_table in k_list: # Make a table from path and table name self.add_table(k_table, path) return self
[docs] def add_table(self, table, path): """ Add a table to the database Must be overridden by derived classes Arguments ---------- table: str The category of table for the database path: str The dataset path to metadata files Returns -------- str or bool The table name combining ``table`` and ``path`` \ The derived classes should return whether the table was \ added successfully. """ k_join = self.RUNTIME.DB.JOIN.NAME return k_join.format(table, path)
[docs] def load_synapses(self, path): """ Load all the synapse information from files Arguments ---------- path: str The dataset path to metadata files Returns -------- numpy.ndarray The Nx5 array of pre, post, z, y, x values \ where N is the number of synapses for the ``path``. """ # Get file fields k_files = self.RUNTIME.DB.FILE # Get keywords for input file k_file = k_files.SYNAPSE.VALUE k_point = k_files.SYNAPSE.POINT.NAME k_points_in = k_files.SYNAPSE.POINT.LIST k_nodes_in = k_files.SYNAPSE.NEURON_LIST # Get the full path to the synapse file full_path = os.path.join(path, k_file) try: # Load the file with the synapses with open(full_path, 'r') as f: all_dict = json.load(f) point_dict = all_dict[k_point] # Return if not valid file or json except (IOError, ValueError): return [] # Transpose the list of all synapses links = map(all_dict.get, k_nodes_in) center = map(point_dict.get, k_points_in) synapses = np.uint32(links + center).T # Add synapses to database self.add_synapses(path, synapses) return synapses
[docs] def load_neurons(self, path, synapses): """ Load all the neuron information from files Arguments ---------- path: str The dataset path to metadata files synapses: numpy.ndarray The Nx5 array of pre, post, z, y, x values \ where N is the number of synapses for the ``path``. Returns -------- numpy.ndarray The Nx4 array of id, z, y, x values \ where N is the number of neurons for the ``path``. """ # return if not synapses if not len(synapses): return synapses #### # Get neurons from loaded synapses #### # All unqiue source nodes and their keys all_src, src_keys = np.unique(synapses.T[0], True) # All unique target nodes and their keys all_tgt, tgt_keys = np.unique(synapses.T[1], True) # Find keys for neurons that are never targets only_src = list(set(all_src) - set(all_tgt)) src_dict = dict(zip(all_src, src_keys)) src_keys = map(src_dict.get, only_src) # Get all neuron lists from synapse targets, sources neuron_tgt = np.delete(synapses[tgt_keys], 0, 1) neuron_src = np.delete(synapses[src_keys], 1, 1) # Get full neuron list from source and target neurons = np.r_[neuron_src, neuron_tgt] # Get file fields k_files = self.RUNTIME.DB.FILE # Get keywords for input file k_file = k_files.SOMA.VALUE k_file = os.path.join(path, k_file) if os.path.exists(k_file): msg = "Loading neuron centers from {}" log.info(msg.format(k_file)) # Load the csv with open(k_file, 'r') as jf: # Keep a list of synapseless soma new_neurons = [] # Add each new center point to database for soma in json.load(jf): # Make a numpy uint32 coorinate array k_soma = ['neuron_id', 'z', 'y', 'x'] new_soma = np.uint32(map(soma.get, k_soma)) soma_id = new_soma[0] # Find the correct ID neuron_ids = neurons.T[0] # If the soma has a synapse if soma_id in neuron_ids: # Insert into the correct ID soma_index = np.argwhere(neuron_ids == soma_id)[0][0] neurons[soma_index] = new_soma else: # Add new synapseless neuron new_neurons.append(new_soma) # Add new neurons to full neurons list if len(new_neurons): neurons = np.r_[neurons, new_neurons] # Add neurons to database self.add_neurons(path, neurons) return neurons
[docs] def add_synapses(self, path, synapses): """ Add all the synapases to the database Arguments ---------- path: str The dataset path to metadata files synapses: numpy.ndarray The Nx5 array of pre, post, z, y, x values \ where N is the number of synapses for the ``path``. Returns -------- list A list of dicts from each row of ``synapses`` \ with dictionary keys taken from ``SYNAPSE.FULL_LIST`` \ field of :data:`RUNTIME.DB` """ # Get database fields k_tables = self.RUNTIME.DB.TABLE # Get keywords for the database k_synapse = k_tables.SYNAPSE.NAME # List all the syapse database keys k_keys = k_tables.SYNAPSE.FULL_LIST # Add entries return self.add_entries(k_synapse, path, k_keys, synapses)
[docs] def add_neurons(self, path, neurons): """ Add all the neurons to the database Arguments ---------- path: str The dataset path to metadata files neurons: numpy.ndarray The Nx4 array of id, z, y, x values \ where N is the number of neurons for the ``path``. Returns -------- list A list of dicts from each row of ``neurons`` \ with dictionary keys from the ``NEURON.FULL_LIST`` \ field of :data:`RUNTIME.DB` """ # Get database fields k_tables = self.RUNTIME.DB.TABLE # Get keywords for the database k_neuron = k_tables.NEURON.NAME # List all the syapse database keys k_keys = k_tables.NEURON.FULL_LIST # Add entries return self.add_entries(k_neuron, path, k_keys, neurons)
[docs] def add_entries(self, table, path, t_keys, entries): """ Add an array or list of entries to a table Must be overridden by derived class. """ k_join = self.RUNTIME.DB.JOIN.NAME return k_join.format(table, path)
[docs] def add_entry(self, table, path, entry, update=1): """ and a single entry to a table for a path Overides :meth:`Database.add_entry` Arguments ---------- table: str The category of table for the database path: str The dataset path to metadata files entry: dict The mapping of keys to values for the entry update: int 1 to update old entries matching keys, and \ 0 to write new entries ignoring old entries. Default 1. Returns -------- dict The value of the entry """ k_join = self.RUNTIME.DB.JOIN.NAME return k_join.format(table, path)
[docs] def get_path(self, path): """ Map a channel path to a dataset path Must be overridden by derived class. Arguments ---------- path: str The path to the given channel Returns -------- str The dataset path for the given ``path`` """ pass
[docs] def get_table(self, table, path): """ Get the actual table for a given path Must be overridden by derived class. Arguments ---------- table: str The category of table for the database path: str The dataset path to metadata files Returns -------- str or object Full database name of the table for a path. \ The derived classes should actually return the python \ object reference to the real table. """ real_path = self.get_path(path) k_join = self.RUNTIME.DB.JOIN.NAME return k_join.format(table, real_path)
[docs] def synapse_ids(self, table, path, start, stop): """ Must be overridden by derived class. """ return self.get_table(table, path)
[docs] def is_synapse(self, table, path, id_key): """ Must be overridden by derived class. """ return self.get_table(table, path)
[docs] def is_neuron(self, table, path, id_key): """ Must be overridden by derived class. """ return self.get_table(table, path)
[docs] def synapse_keypoint(self, table, path, id_key, scales): """ Must be overridden by derived class. """ return self.get_table(table, path)
[docs] def neuron_keypoint(self, table, path, id_key, scales): """ Must be overridden by derived class. """ return self.get_table(table, path)
[docs] def synapse_parent(self, table, path, id_key): """ Must be overridden by derived class. """ return self.get_table(table, path)
[docs] def neuron_children(self, table, path, id_key, start, stop): """ Must be overridden by derived class. """ return self.get_table(table, path)
[docs] def all_neurons(self, table, path): """ Must be overridden by derived class. """ return self.get_table(table, path)
[docs] def get_by_key(self, table, path, key): """ Get the entry for the key in the table. Must be overridden by derived class. Arguments ---------- table: str The category of table for the database path: str The dataset path to metadata files key: int The primary key value for any entry Returns -------- object or dict The object reference from :meth:`get_table`. \ The derived class should give an entry in the table. """ return self.get_table(table, path)
[docs] def commit(self): """ Save all database changes to the database file. Must be overridden by derived class. """ pass