class FlowGeom(Polygon): """ A `FlowGeom` is a geometric representation of a flow from a begin node to end node. """ angular_thickness = Field( """ Thickness of the flow, measured in radians """, __default_value__=0.05) network_geom = Field( """ An object that will produce sides of the polygon to be drawn for this `FlowGeom`. """) begin_node_geom = Field( """ The node geometry where this flow begins. """) end_node_geom = Field( """ The node geometry where this flow ends. """) @lazyfield def sides(self): """ Sides of this flow. """ return self.network_geom.get_sides(self)
class NodeGeom(Polygon): """ Geometry to represent nodes. Nodes will be placed on the circumference of a circle. """ network_geom = Field( """ The network geometry that controls this `NodeGeom`. """) position = Field( """ Position of this node --- type should be determined by the network geometry object. """) thickness = Field( """ Thickness of this node --- type should be the same as position. """) @lazyfield def sides(self): """ Sides to determine where this node geometry should be placed. """ return self.network_geom.get_sides(self)
class Sphere(WithFields): """ Region in 3D """ position_center = Field( """ Three dimensional array providing position of the cube's center. """) radius = Field( """ Radius of the sphere """) closed = Field( """ Is the sphere closed? """, __default_value__=True) @property def volume(self): return (4. / 3.) * math.pi * math.pow(self.radius, 3) @property def bbox(self): return (self.center - self.radius, self.center + self.radius) def contains(self, position): check = np.less_equal if self.closed else np.less return check( np.linalg.norm(position - self.center, axis=0), self.radius)
class AnalysisSpec(WithFields): """ Specify a brain-circuit analysis. """ sample_size = Field(""" Number of individual sample measurements for each set of parameter values. """, __default_value__=100) size_roi = Field(""" Size of ROIs that will be sampled in circuit's physical space. """, __default_value__=50. * np.ones(3)) path_reports = Field(""" Location where the reports will be posted. """, __default_value__=os.path.join( os.getcwd(), "reports")) morphologies_interneurons = Field(""" Interneuron morphologies that are stained by markers. """, __default_value__=[ "BP", "BTC", "CHC", "DB", "LBC", "NBC", "MC", "SBC", "SSC" ]) number_cortical_thickness_bins = Field(""" Number of bins for by depth or by height analyses. """, __default_value__=np.int(50))
class Document(WithFields): """ The root document """ title = Field(""" Title of this document element. """) author = Field(""" Author of this document. """, __default_value__=Author.anonymous) chapters = Field(""" A chapter is any document that may have sections. """, __default_value__=[]) def __init__(self, title, *args, **kwargs): if "parent" not in kwargs: kwargs["parent"] = self for section in self.get_class_sections(): name_section = section.__attr_name__ if name_section in kwargs: try: kwargs[name_section]["parent"] = self except TypeError: pass super().__init__(title=title, *args, **kwargs) for chapter in self.chapters: chapter.parent = self @field def parent(self): """ Parent of this document instance... """ return None @field def sections(self): """ An ordered sequence of elements contained in this document. """ return OrderedDict() @classmethod def get_class_sections(cls): """ Sections defined in this `Document class` Override this if the behavior changes. TODO: Figure out how to introspect attributes to automate... """ return []
class SimpleUniformRandomConnectivity(CircuitConnectivity, WithFields): """ A circuit in which a neuron has a prescribed efferent degree, and is assigned that many randomly chosen efferent neighbors. """ mean_afferent_degree = Field(""" A post-synaptic cell (irrespecitive of it's mtype) will be given a Poisson distributed number of afferent connections with the mean equal to this Field's value. """) mean_synapse_count = Field(""" Mean number of synapses of a connection. """, __default_value__=5) def get_afferent_degree(self, post_synaptic_cell, *args, **kwargs): """ Number of out-going connections of a neuron of given 'mtype'. """ return\ np.random.poisson(self.mean_afferent_degree) def _synapse_count(self, *args, **kwargs): return 1. + np.random.poisson(self.mean_synapse_count) def get_synapse_counts(self, connections): """ ... """ return\ 1. + np.random.poisson( self.mean_synapse_count, size=connections.shape[0]) def get_afferent_gids(self, post_synaptic_cell, cells): """ GIDs of cells afferent on a post-synaptic cell. Arguments ------------- post_gid :: GID of the post_synaptic cell. cells :: pandas.DataFrame containing cells in the circuit """ return\ np.sort(np.random.choice( cells.index.values, self.get_afferent_degree(post_synaptic_cell), replace=False)) def get_afferent_connections(self, post_gid, post_cell, cells): """... """ return pd.DataFrame({ "pre_gid": self.get_afferent_gids(post_cell, cells), "post_gid": post_gid })
class Geometry(ABC, WithFields): """ Base class for Geometries. """ label = Field( """ A label to be displayed. """) children = Field( """ `Geometry`s spawned from this `Geometry`. """, __default_value__=[]) @lazyfield def identifier(self): """ Identifier can be used as a key in a mapping providing features for this `Geometry`. Override the return to specialize... """ return self.label def spawn(self, type_geometry, *args, **kwargs): """ Create a new geometry. """ self.children.append(type_geometry(*args, **kwargs)) return self.children @abstractmethod def points(self, number): """ Points on this `Geometry`'s boundary. """ raise NotImplementedError @abstractmethod def _draw_this(self, *args, **kwargs): """ Draw only this geometry. """ raise NotImplementedError def draw(self, *args, **kwargs): """ Draw this geometry, and all the geometries it has spawned. """ for child in self.children: child.draw(*args, **kwargs) return self._draw_this(*args, **kwargs)
class CircuitAnalysisReport(Report): """ Add some circuit analysis specific attributes to `Report` """ provenance_model = Field(""" Either a `class CircuitProvenance` instance or a dict providing values for the fields of `class CircuitProvenance`. """, __as__=CircuitProvenance) figures = Field(""" A dict mapping label to an object with a `.graphic` and `.caption` attributes. """, __default_value__={}) references = Field(""" References of literature cited in this report. """, __type__=Mapping, __default_value__={}) content = LambdaField( """ All text as a single string. """, lambda self: (self.abstract + self.introduction + self.methods + self.results + self.discussion)) @lazyfield def field_values(self): """...""" try: name_phenomenon = self.phenomenon.name except AttributeError: name_phenomenon = make_name(self.phenomenon, separator="-") return\ dict( circuit=OrderedDict(( ("animal", self.provenance_model.animal), ("age", self.provenance_model.age), ("brain_region", self.provenance_model.brain_region), ("uri", self.provenance_model.uri), ("references", self.references), ("date_release", self.provenance_model.date_release), ("authors", '; '.join( "{}. {}".format(i+1, a) for i, a in enumerate(self.provenance_model.authors))))), author=self.author, phenomenon=name_phenomenon, label=make_label(self.label, separator='-'), title=make_name(self.label, separator='-'), abstract=self.abstract, introduction=self.introduction, methods=self.methods, results=self.results, content=self.content, discussion=self.discussion)
class Cuboid(WithFields): """ A region. """ position_corner_0 = Field( """ A corner of this cuboid. """) position_corner_1 = Field( """ Another corner of this cuboid, diagonally opposite to the other. """) closed = Field( """ Is the geometry closed? """, __default_value__=True) def __init__(self, positions_corner_0, positions_corner_1, closed=False, *args, **kwargs): """...""" super().__init__( *args, position_corner_0=positions_corner_0, position_corner_1=positions_corner_1, closed=closed, **kwargs) @property def volume(self): """ Volume of this cuboid. """ return np.abs(np.prod(self.position_corner_0, self.position_corner_1)) @property def bbox(self): """ A box that bounds this cuboid. """ return (self.position_corner_0, self.position_corner_1) def contains(self, position): """???""" check = np.less_equal if self.closed else np.less return np.all( np.logical_and( check(self.position_corner_0, position), check(self.position_corner_1, position)), axis=1)
class Collector(WithFields): """ Collect a measurement... """ label = Field(""" Single word string to name the measurement column. """, __default_value__="value") sample_size = Field(""" Number of repetitions for each set of parameter values. """, __default_value__=1) @field.cast(Parameters) def parameters(self): """ Parameter sets to measure with. Or a callable that produces such parameters... """ raise FieldIsRequired @field def method(self, *args, **kwargs): """ That makes a measurement. """ raise FieldIsRequired @field def collection(self): """ A policy to collect the measurements over all parameter sets """ return measurement_collection.primitive_type def __call__(self, adapter, model, *args, **kwargs): """ Collect a measurement """ return\ self.collection( (p, self.method(adapter, model, **p, **kwargs)) for p in self.parameters( adapter, model, sample_size=self.sample_size, **kwargs) ).rename( columns={"value": self.label} )
class SynapseCollection(WithFields): """ A collection of synapses that stores synapses. SynapseCollection builds on pandas DataFrame to store data in memory, and provides an efficient secondary index on the stored data. If there are two many synapses to store in memory, SynapseCollection will respond to queries by loading the data from disk. """ adjacency = Field(""" List of 2-tuples holding connected cell gid and synapse count. """) direction = Field(""" Direction of the connections in 'adjacency' data. """)
class Figure(WithFields): """ A `Figure` is a graphic with a caption. """ graphic = Field(""" A matplotlib figure, or PDF, PNG... """) caption = Field(""" A text to go with the graphic. """, __as__=paragraphs) def __init__(self, figure, caption="No caption provided", *args, **kwargs): """ Initialize with graphic as an argument. `WithField` provides an `__init__` method that accepts initializer arguments as keyword arguments. However, with this `__init__` method we cannot pass the graphic as a keyword argument. We still allow `*args, **kwargs` that will allow `class Figure` to be mixed in some other class. """ try: graphic = figure.graphic except AttributeError: graphic = figure super().__init__(graphic=graphic, caption=caption, *args, **kwargs) def save(self, path, dpi=100): """ Save the figure. """ if isinstance(self.graphic, (str, Path)): shutil.copy(self.graphic, path) return path try: result = self.graphic.savefig(path, dpi=dpi) except AttributeError: try: result = self.graphic.figure.savefig(path, dpi=dpi) except AttributeError: raise TypeError("Figure type {} not supported".format( self.graphic.__class__)) result = None return result
class CircuitProvenance(WithFields): """ Provenance of a circuit. """ label = Field(""" A label that names the circuit model. """, __default_value__="") authors = Field(""" A list of authors who built the circuit model. """, __default_value__=["Not Available"]) date_release = Field(""" When the circuit model was released in its final form. """, __default_value__="YYYYMMDD") uri = Field(""" URI from where the circuit model can be loaded. """, __default_value__="https://www.example.com") animal = Field(""" The animal whose brain was modeled. """, __default_value__="Not Available") age = Field(""" Age of the animal at which its brain was modeled. """, __default_value__="XYZ Weeks") brain_region = Field(""" Brain region that was modeled. """, __default_value__="Somatosensory Cortex (SSCx)")
class Arc(Circle): """ Part of a circle. """ angle_begin = Field( """ Angle at which the arc starts. """) angle_end = Field( """ Angle at which the arc stops. """) def points(self, number=100): """...""" return self.segment_points( angle_begin=self.angle_begin, angle_end=self.angle_end, number=number)
class Polygon(Geometry): """ Polygon is a sequences of vertices. """ sides = Field( """ Sequence of `Paths` """) facecolor = Field( """ Colors to use for this `Polygon`. """, __default_value__="red") def points(self, number=None): """ Points of a given number does not make sense. """ return [ point for side in self.sides for point in side.vertices] def _draw_this(self, axes=None, *args, **kwargs): """ Draw this polygon. """ if axes is None: axes = plt.gca() patch_polygon =\ PatchPolygon( np.vstack([ side.vertices for side in self.sides])) axes.add_collection( PatchCollection( [patch_polygon], facecolors=(self.facecolor,), edgecolors=("grey",), linewidths=(1,),)) return axes
class Interval(WithFields): """ An `Interval` is two floats. """ bottom = Field( """ The bottom of the interval. """) top = Field( """ The top of the interval,. """) @lazyfield def thickness(self): """ Thickness is the length of the this `Interval`. """ return self.top - self.bottom
class QueryDB(WithFields): """ Cache of data associated with a circuit query, that the adapter will use. """ method_to_memoize = Field(""" A callable to get values to cache. """) def __init__(self, method_to_memoize): """...""" super().__init__(method_to_memoize=method_to_memoize) @lazyfield def store(self): return {} @staticmethod def _hashable(query_dict): """...""" return tuple( sorted([(key, make_hashable(value)) for key, value in query_dict.items() if value is not None], key=lambda key_value: key_value[0])) @staticmethod def _hashed(query_dict): """...""" return\ hash(QueryDB._hashable(query_dict)) def __call__(self, circuit_model, query_dict): """ Call Me. """ if circuit_model not in self.store: self.store[circuit_model] = {} cache_circuit_model = self.store[circuit_model] hash_query = self._hashable(query_dict) if hash_query not in cache_circuit_model: cache_circuit_model[hash_query] =\ self.method_to_memoize(circuit_model, query_dict) return cache_circuit_model[hash_query]
class Path(Curve): """ A curve with points on the curve provided explicitly. """, vertices = Field( """ A sequence of 2D vertices that define this `Path`. """) def points(self, number=100): """ Convert vertices to the number of points demanded. """ if number >= len(self.vertices): return self.vertices return self.vertices[ np.random.choice( range(len(self.vertices)), number, replace=False) ]
class DocElem(WithFields, AIBase): """...""" title = Field(""" Title of this document element. """) label = LambdaField( """ A single word tag for this document element. """, lambda self: make_label(self.title)) @field def parent(self): """ Parent `DocElem` that contains this one. """ raise FieldIsRequired @field def children(self): """ A sequence of `DocElem`s that are contained in this one. """ return tuple() def save(self, record, path): """Save this `DocElem`""" try: save_super = super().save except AttributeError: return path return save_super(record, Path(path).joinpath(self.label)) def __call__(self, adapter, model, *args, **kwargs): """...""" try: get_record = super().__call__ except AttributeError: return Record(title=self.title, label=self.label) return get_record(adapter, model, *args, **kwargs).assign(title=self.title, label=self.label)