Creating custom preset cuffs from instances of part primitives
The operations by which cuffs are added to the COMSOL “model” object are
contained in the Java Part class (src/model/Part.java
). A complete cuff
design is defined by a JSON file (e.g., Purdue.json
) stored in
config/system/cuffs/
, which we call a “preset” cuff, containing
parameterizations and material assignments for part primitives that
together represent an entire cuff electrode, including contact “recess”
and “fill” (i.e., saline, mineral oil, or encapsulation tissue). The
contents of the “preset” JSON file direct the Part class on which part
primitives to add as well as their size, shape, placement, and material
function (i.e., cuff “insulator”, contact “conductor”, contact “recess”,
and cuff “fill”). Fig 3A shows some examples of “preset” cuffs
constructed from our library of COMSOL part primitives which are
included in the pipeline repository in config/system/cuffs/
. Users
should not modify existing “preset” cuff files. Rather, a user should
use our “preset” cuffs as a guide in constructing their own custom
cuffs, which require a unique file name. Once a cuff is defined as a
“preset” in config/system/cuffs/
as its own JSON file, the user may
choose to place the cuff on a nerve in COMSOL using the “preset”
parameter in Model (Model Parameters).
We provide a COMSOL file in examples/parts/sandbox.mph
that contains our
library of “Geometry Parts” (i.e., part primitives) for users to
assemble into their own cuffs in the COMSOL GUI. See Creating New Part Primitives for instructions on how to add new part primitives. After a part primitive
is defined in the “Geometry Parts” node in the COMSOL GUI, under
“Component 1”, the user may secondary-click on the “Geometry 1” node ->
“Parts” and select a part primitive. Importantly, the order of
instantiated parts in the “Geometry 1” node matters for proper material
assignment; the user must consider that when a new part occupies volume
within a previously instantiated part, COMSOL will override the previous
material assignment for the shared volume with the latter created part’s
material assignment. For example, if a user is modeling a cuff
containing an embedded contact electrode (i.e., the contact surface is
flush with the insulator’s inner surface) and the entire cuff is bathed
in saline (i.e., surgical pocket), the user would (1) add the part
primitive for the saline cuff “fill” since it is the outermost domain,
(2) add the cuff “insulator” which would override its volume within the
saline, and (3) add the contact “conductor” which would override the
cuff insulator domain within the contact conductor. The order of
instantiation of part primitives in COMSOL mirrors the order of the
parts listed in the “preset” JSON file.
The part’s required instantiation “Input Parameters” (in the “Settings” tab) have default values (found in the “Expression” dialogue boxes) that should be overridden and populated using parameter values to define the geometry of the part in your preset’s implementation. The “Expression” dialogue boxes must contain parameter values already defined in a “Parameter Group” under the “Global Definitions” node (i.e., parameter names for either constants with units (e.g., “5 [um]”) or mathematical relationships between other parameters (e.g., “parameter1 + parameter2”)). The parameter values in the “Parameter Group” under the “Global Definitions” node are populated with the list of “params” in the “preset” cuff JSON file (explained in more detail below).
Once the user has succeeded in assembling their cuff in the COMSOL GUI
from parameterized instantiations of parts in
examples/parts/sandbox.mph
, they are ready to create a new “preset” cuff
JSON file in config/system/cuffs/
. See the existing files in that
directory for examples. The required elements of a “preset” cuff JSON
file are shown in the skeleton structure below:
{
"code": String
"instances": [
{
"type": String,
"label": Double,
"def": {
"parameter1": String // key is name of expected parameter in COMSOL
… // for all parameters (specific for part primitive "type")
},
"materials": [
{
"info": String,
"label_index": Integer
}
… // for all materials in a part
]
}
… // for all instances
]
"params": [
{
"name": String, // e.g., "parameter1_<code_value>" … such as "pitch_Pitt"
"expression": String,
"description": String
}
… // for all parameters
]
},
"expandable": Boolean,
"fixed_point": String,
"angle_to_contacts_deg": Double,
"offset": {
"parameter1": Double,
// parameter must be defined in "params" list, value is weight of
parameter value
// (e.g., if radius of wire contact, need 2 to get contributing radial
distance
// between nerve and cuff)
… // for all parameters informing how much "extra" space needed in cuff
}
}
“code”: The value (String)
is a unique identifier for the parameters
that are needed to define this cuff. All parameters in the "params" [Object, …]
will need to end with the characters of this code preceded
by "_"
(e.g., “code” = “Pitt”, the “pitch” parameter for the separation
between contacts would be "pitch_Pitt"
).
“instances”: The value is a list of JSON Objects, one for each part instance needed to represent the cuff. Within each part instance JSON Object, the user must define:
"type"
: The value (String) defines which known primitive to instantiate, which matches the switch-case in BOTHPart.createCuffPartPrimitive()
ANDPart.createCuffPartInstance()
in Java (src/model/Part.java
) behind the scenes."label"
: The value (String) defines the label that will show up in the COMSOL file to annotate the instance of the part primitive in the construction of your COMSOL FEM."def"
: The value (Object) contains all parameters required to instantiate the chosen part primitive. The key-value pairs will match the values entered in the COMSOL GUI for a part (i.e., “Settings” -> “Input Parameters” panel) inexamples/parts/sandbox.mph
.Key-value pairs in this JSON Object will vary depending on the part primitive as defined in “type”. For each parameter key, the value is a String containing a mathematical expression (of parameters) for COMSOL to evaluate.
"materials"
: List of JSON Objects for each material assignment in a part instance (usually this is just one material; contacts with recessed domains will have one material for the conductor and one material for the recessed domain as the part instance will create two separate domains with independent selections)"info"
: The value (String) is the function of the domain in the FEM (i.e., “medium”, cuff “fill”, cuff “insulator”, contact “conductor”, and contact “recess”) that is used to assign material properties to a selected domain. The value will match a key in the “conductivities” JSON Object in Model."label_index"
: The value (Integer) corresponds to the index of the selection for the domain (inim.labels
, defined independently for each primitive case inPart.createCuffPartPrimitive()
– see code insrc/model/Part.java
) to be assigned to the material function. Note thatim.labels
are indexed starting at 0.
"params"
: The value is a list of JSON Objects, one for each parameter
used to define parameterizations of part primitives in COMSOL’s Global
Definitions. The structure of each JSON Object is consistent with the
format of the dialogue boxes in each “Parameters” group (i.e., “Name”,
“Expression”, “Description”) where the parameters are populated in the
COMSOL GUI:
"name"
: The value (String) is the name of the parameter."expression"
: The value (String) is the expression/constant with units that COMSOL will evaluate. Therefore, if the value is a constant, units wrapped in “[]” are required (e.g., “5 [um]”). If the value is an expression relating other parameters (that already are dimensioned with units) with known mathematical expressions (e.g., multiply"*"
, divide"/"
, add"+"
, subtract"-"
, exponent"^"
, trigonometric formulas:"sin()"
,"cos()"
,"tan()"
,"asin()"
,"acos()"
,"atan()"
), do not add units after the expression."description"
: The value (String) can be empty (i.e., “”) or may contain a description of the parameter such as a reference to the source (e.g., published patent/schematics) or a note to your future self.
"expandable"
: The value (Boolean) tells the system whether to expect the
implementation of the cuff in COMSOL to be able to expand beyond the
manufactured resting cuff diameter to fit around a nerve. For a cuff to
be expandable, it must be constructed from part primitives that have
been parameterized to expand as a function of "R_in"
. See
config/system/cuffs/Purdue.json
for an example of an expandable cuff. Expandable
cuffs should be parameterized such that the contact length remains constant.
"fixed_point"
: The value (String) defines which point on the cuff remains fixed
when expanding. Note that these are not behaviors, this option will change nothing
regarding how the cuff is generated. This parameter is descriptive only, indicating
how the cuff is parameterized based on the part primitives.
This parameter is to inform the cuff shift algorithm on how
it should account for cuff expansion. Currently, two options are implemented:
"center"
: In this case, the center of the contact always remains at the same angle when expanding."clockwise_end"
: In this case, the clockwise end of either the cuff, or the contact (both will work correctly) remains fixed. Note, this option assumes that the fixed point for the clockwise end is at theta = 0.
"angle_to_contacts_deg"
: The value (Double, units: degrees) defines
the angle to the contact point of the nerve on the inside of the cuff of
the cuff (measured counterclockwise from the +x-axis before any
rotation/deformation of the cuff).
"offset"
: The JSON Object contains keys that are names of parameters
defined in the list of “params” in this “preset” cuff file. If the inner
diameter of the cuff must expand to allow for additional distance
between the nerve and "R_in"
(the inner surface of the cuff insulator),
the user adds key-value pairs for the offset buffer here. For each known
parameter key, the user sets the value (Double) to be the multiplicative
factor for the parameter key. The list of key-value pairs can be empty,
as is the case for most cuffs. Once offset is added to a cuff, the
values are automatically used in Runner’s compute_cuff_shift()
method.
For example usage of this functionality, see
config/system/cuffs/Purdue.json
as replicated below:
"offset": {
"sep_wire_P": 1, // sep_wire_P is the separation between the wire contact and the
// inner diameter of the cuff
"r_wire_P": 2 // r_wire_P is the radius of the circular cross section of the
// wire contact (i.e., half of the wire’s gauge)
}
// the above JSON Object adds an offset buffer between the nerve and the inner
// diameter of the cuff of : (1*sep_wire_P) + (2*r_wire_P)