Skip to content

utils

Raise on CMTK transform error.

Source code in navis/utils/exceptions.py
23
24
25
class CMTKError(Exception):
    """Raise on CMTK transform error."""
    pass

Raised when class can't be constructed from input.

Source code in navis/utils/exceptions.py
15
16
17
class ConstructionError(Exception):
    """Raised when class can't be constructed from input."""
    pass

Raise when volume is invalid (e.g. not watertight).

Source code in navis/utils/exceptions.py
19
20
21
class VolumeError(Exception):
    """Raise when volume is invalid (e.g. not watertight)."""
    pass

Check that vispy works.

RETURNS DESCRIPTION
vispy.Viewer

A viewer which can be closed after use.

Examples:

>>> import navis
>>> viewer = navis.utils.check_vispy()
>>> # When the viewer and neurons show up...
>>> navis.close3d()
Source code in navis/utils/misc.py
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
def check_vispy():
    """Check that vispy works.

    Returns
    -------
    vispy.Viewer
        A viewer which can be closed after use.

    Examples
    --------
    >>> import navis
    >>> viewer = navis.utils.check_vispy()
    >>> # When the viewer and neurons show up...
    >>> navis.close3d()
    """
    from ..data import example_neurons
    nl = example_neurons()
    return nl.plot3d(backend='vispy')

Split list of strings into positive (no "~") and negative ("~").

Examples:

>>> eval_conditions('~negative condition')
([], ['negative condition'])
>>> eval_conditions(['positive cond1', '~negative cond1', 'positive cond2'])
(['positive cond1', 'positive cond2'], ['negative cond1'])
Source code in navis/utils/eval.py
140
141
142
143
144
145
146
147
148
149
150
151
152
153
def eval_conditions(x) -> Tuple[List[bool], List[bool]]:
    """Split list of strings into positive (no "~") and negative ("~").

    Examples
    --------
    >>> eval_conditions('~negative condition')
    ([], ['negative condition'])
    >>> eval_conditions(['positive cond1', '~negative cond1', 'positive cond2'])
    (['positive cond1', 'positive cond2'], ['negative cond1'])

    """
    x = make_iterable(x, force_type=str)

    return [i for i in x if not i.startswith('~')], [i[1:] for i in x if i.startswith('~')]

Evaluate neuron ID(s).

PARAMETER DESCRIPTION
x
           For Neuron/List or pandas.DataFrames/Series will
           look for `id` attribute/column.

TYPE: str | uuid.UUID | Tree/MeshNeuron | NeuronList | DataFrame

warn_duplicates
           If True, will warn if duplicate IDs are found.
           Only applies to NeuronLists.

TYPE: bool DEFAULT: True

RETURNS DESCRIPTION
list

List containing IDs.

Source code in navis/utils/eval.py
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
def eval_id(x: Union[uuid.UUID, str, 'core.NeuronObject', pd.DataFrame],
              warn_duplicates: bool = True) -> List[uuid.UUID]:
    """Evaluate neuron ID(s).

    Parameters
    ----------
    x :                str | uuid.UUID | Tree/MeshNeuron | NeuronList | DataFrame
                       For Neuron/List or pandas.DataFrames/Series will
                       look for `id` attribute/column.
    warn_duplicates :  bool, optional
                       If True, will warn if duplicate IDs are found.
                       Only applies to NeuronLists.

    Returns
    -------
    list
                    List containing IDs.

    """
    if isinstance(x, (uuid.UUID, str, np.str, int, np.integer)):
        return [x]
    elif isinstance(x, (list, np.ndarray, set)):
        uu: List[uuid.UUID] = []
        for e in x:
            temp = eval_id(e, warn_duplicates=warn_duplicates)
            if isinstance(temp, (list, np.ndarray)):
                uu += temp
            else:
                uu.append(temp)  # type: ignore
        return sorted(set(uu), key=uu.index)
    elif isinstance(x, core.BaseNeuron):
        return [x.id]
    elif isinstance(x, core.NeuronList):
        if len(x.id) != len(set(x.id)) and warn_duplicates:
            logger.warning('Duplicate IDs found in NeuronList.'
                           'The function you are using might not respect '
                           'fragments of the same neuron. For explanation see '
                           'http://navis.readthedocs.io/en/latest/source/conn'
                           'ectivity_analysis.html.')
        return list(x.id)
    elif isinstance(x, pd.DataFrame):
        if 'id' not in x.columns:
            raise ValueError('Expect "id" column in pandas DataFrames')
        return x.id.tolist()
    elif isinstance(x, pd.Series):
        if x.name == 'id':
            return x.tolist()
        elif 'id' in x:
            return [x.id]
        else:
            raise ValueError(f'Unable to extract ID from pandas series {x}')
    elif isinstance(x, type(None)):
        return None
    else:
        msg = f'Unable to extract ID(s) from data of type "{type(x)}"'
        logger.error(msg)
        raise TypeError(msg)

Extract neurons.

PARAMETER DESCRIPTION
x
           Data to be checked for neurons.

TYPE: Any

warn_duplicates
           If True, will warn if duplicate neurons are found.
           Only applies to NeuronLists.

TYPE: bool DEFAULT: True

raise_other
           If True, will raise error if non- neurons are found.

TYPE: bool DEFAULT: True

RETURNS DESCRIPTION
list

List containing neurons.

None

If no neurons found.

Source code in navis/utils/eval.py
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
def eval_neurons(x: Any,
                 warn_duplicates: bool = True,
                 raise_other: bool = True) -> Optional[List['core.TreeNeuron']]:
    """Extract neurons.

    Parameters
    ----------
    x
                       Data to be checked for neurons.
    warn_duplicates :  bool, optional
                       If True, will warn if duplicate neurons are found.
                       Only applies to NeuronLists.
    raise_other :      bool, optional
                       If True, will raise error if non- neurons are found.

    Returns
    -------
    list
                    List containing neurons.
    None
                    If no neurons found.

    """
    if isinstance(x, core.BaseNeuron):
        return [x]
    elif isinstance(x, (list, np.ndarray, set)):
        neurons: List['core.BaseNeuron'] = []
        for e in x:
            temp = eval_neurons(e, warn_duplicates=warn_duplicates,
                                raise_other=raise_other)
            if isinstance(temp, (list, np.ndarray)):
                neurons += temp
            elif temp:
                neurons.append(temp)
        return sorted(set(neurons), key=neurons.index)
    elif isinstance(x, core.NeuronList):
        if len(x.id) != len(set(x.id)) and warn_duplicates:
            logger.warning('Duplicate IDs found in NeuronList.'
                           'The function you are using might not respect '
                           'fragments of the same neuron. For explanation see '
                           'http://navis.readthedocs.io/en/latest/source/conn'
                           'ectivity_analysis.html.')
        return x.neurons
    elif isinstance(x, type(None)):
        return None
    elif raise_other:
        msg = f'Unable to extract neurons from data of type "{type(x)}"'
        logger.error(msg)
        raise TypeError(msg)
    return None

Extract node IDs from data.

PARAMETER DESCRIPTION
x
        Your options are either::
        1. int or list of ints will be assumed to be node IDs
        2. str or list of str will be checked if convertible to int
        3. For TreeNeuron/List or pandas.DataFrames will try
           to extract node IDs

TYPE: int | str | TreeNeuron | NeuronList | DataFrame

RETURNS DESCRIPTION
list

List containing node IDs (integer)

Source code in navis/utils/eval.py
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
def eval_node_ids(x: Union[int, str,
                           Sequence[Union[str, int]],
                           'core.NeuronObject',
                           pd.DataFrame]
                  ) -> List[int]:
    """Extract node IDs from data.

    Parameters
    ----------
    x :             int | str | TreeNeuron | NeuronList | DataFrame
                    Your options are either::
                    1. int or list of ints will be assumed to be node IDs
                    2. str or list of str will be checked if convertible to int
                    3. For TreeNeuron/List or pandas.DataFrames will try
                       to extract node IDs

    Returns
    -------
    list
                    List containing node IDs (integer)

    """
    if isinstance(x, (int, np.integer)):
        return [x]
    elif isinstance(x, (str, np.str)):
        try:
            return [int(x)]
        except BaseException:
            raise TypeError(f'Unable to extract node ID from string "{x}"')
    elif isinstance(x, (set, list, np.ndarray)):
        # Check non-integer entries
        ids: List[int] = []
        for e in x:
            temp = eval_node_ids(e)
            if isinstance(temp, (list, np.ndarray)):
                ids += temp
            else:
                ids.append(temp)  # type: ignore
        # Preserving the order after making a set is super costly
        # return sorted(set(ids), key=ids.index)
        return list(set(ids))
    elif isinstance(x, core.TreeNeuron):
        return x.nodes.node_id.astype(int).tolist()
    elif isinstance(x, core.NeuronList):
        to_return: List[int] = []
        for n in x:
            to_return += n.nodes.node_id.astype(int).tolist()
        return to_return
    elif isinstance(x, (pd.DataFrame, pd.Series)):
        to_return = []
        if 'node_id' in x:
            to_return += x.node_id.astype(int).tolist()
        return to_return
    else:
        raise TypeError(f'Unable to extract node IDs from type {type(x)}')

Check if parameter has expected type and/or value.

PARAMETER DESCRIPTION
value
            Value to be checked.

TYPE: any

name
            Name of the parameter. Used for warnings/exceptions.

TYPE: str

allowed_values
            Iterable containing the allowed values.

TYPE: tuple DEFAULT: None

allowed_types
            Iterable containing the allowed types.

TYPE: Optional[tuple] DEFAULT: None

on_error
            What to do if `value` is not in `allowed_values`.

TYPE: "raise" | "warn" DEFAULT: 'raise'

RETURNS DESCRIPTION
None
Source code in navis/utils/eval.py
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
def eval_param(value: Any,
               name: str,
               allowed_values: Optional[tuple] = None,
               allowed_types: Optional[tuple] = None,
               on_error: str = 'raise'):
    """Check if parameter has expected type and/or value.

    Parameters
    ----------
    value :             any
                        Value to be checked.
    name :              str
                        Name of the parameter. Used for warnings/exceptions.
    allowed_values :    tuple
                        Iterable containing the allowed values.
    allowed_types  :    tuple
                        Iterable containing the allowed types.
    on_error :          "raise" | "warn"
                        What to do if `value` is not in `allowed_values`.

    Returns
    -------
    None

    """
    assert on_error in ('raise', 'warn')
    assert isinstance(allowed_values, (tuple, type(None)))
    assert isinstance(allowed_types, (tuple, type(None)))

    if allowed_types:
        if not isinstance(value, allowed_types):
            msg = (f'Unexpected type for "{name}": {type(value)}. '
                   f'Allowed type(s): {", ".join([str(t) for t in allowed_types])}')
            if on_error == 'raise':
                raise ValueError(msg)
            elif on_error == 'warn':
                logger.warning(msg)

    if allowed_values:
        if value not in allowed_values:
            msg = (f'Unexpected value for "{name}": {value}. '
                   f'Allowed value(s): {", ".join([str(t) for t in allowed_values])}')
            if on_error == 'raise':
                raise ValueError(msg)
            elif on_error == 'warn':
                logger.warning(msg)

Test if navis is run inside Blender.

Examples:

>>> from navis.utils import is_blender
>>> # If run outside Blender
>>> is_blender()
False
Source code in navis/utils/misc.py
141
142
143
144
145
146
147
148
149
150
151
152
def is_blender() -> bool:
    """Test if navis is run inside Blender.

    Examples
    --------
    >>> from navis.utils import is_blender
    >>> # If run outside Blender
    >>> is_blender()
    False

    """
    return 'blender' in sys.executable.lower()

Test if input is iterable (but not str).

Examples:

>>> from navis.utils import is_iterable
>>> is_iterable(['a'])
True
>>> is_iterable('a')
False
>>> is_iterable({'a': 1})
True
Source code in navis/utils/iterables.py
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
def is_iterable(x: Any) -> bool:
    """Test if input is iterable (but not str).

    Examples
    --------
    >>> from navis.utils import is_iterable
    >>> is_iterable(['a'])
    True
    >>> is_iterable('a')
    False
    >>> is_iterable({'a': 1})
    True

    """
    if isinstance(x, pint.Quantity):
        x = x.magnitude

    if isinstance(x, Iterable) and not isinstance(x, (six.string_types, pd.DataFrame)):
        return True
    else:
        return False

Test if navis is run in a Jupyter notebook.

Also returns True if inside Google colaboratory!

Examples:

>>> from navis.utils import is_jupyter
>>> # If run outside a Jupyter environment
>>> is_jupyter()
False
Source code in navis/utils/misc.py
125
126
127
128
129
130
131
132
133
134
135
136
137
138
def is_jupyter() -> bool:
    """Test if navis is run in a Jupyter notebook.

    Also returns True if inside Google colaboratory!

    Examples
    --------
    >>> from navis.utils import is_jupyter
    >>> # If run outside a Jupyter environment
    >>> is_jupyter()
    False

    """
    return _type_of_script() in ('jupyter', 'colab')

Check if object is mesh (i.e. contains vertices and faces).

Examples:

>>> import navis
>>> is_mesh(navis.example_neurons(1))
False
>>> is_mesh(navis.example_volume('LH'))
True
Source code in navis/utils/eval.py
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
def is_mesh(x) -> Tuple[List[bool], List[bool]]:
    """Check if object is mesh (i.e. contains vertices and faces).

    Examples
    --------
    >>> import navis
    >>> is_mesh(navis.example_neurons(1))
    False
    >>> is_mesh(navis.example_volume('LH'))
    True

    """
    if hasattr(x, 'vertices') and hasattr(x, 'faces'):
        return True

    return False

Determine whether the argument has a numeric datatype.

Booleans, unsigned integers, signed integers, floats and complex numbers are the kinds of numeric datatype.

Arrays with "dtype=object" will return True if data can be cast to floats.

PARAMETER DESCRIPTION
array
        The array to check.

TYPE: array-like

bool_numeric
        If True (default), we count booleans as numeric data types.

TYPE: bool DEFAULT: True

try_convert
        If True, will try to convert array to floats.

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
is_numeric

True if the array has a numeric datatype, False if not.

TYPE: `bool`

Source code in navis/utils/eval.py
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
def is_numeric(array: np.ndarray,
               bool_numeric: bool = True,
               try_convert: bool = False) -> bool:
    """Determine whether the argument has a numeric datatype.

    Booleans, unsigned integers, signed integers, floats and complex
    numbers are the kinds of numeric datatype.

    Arrays with "dtype=object" will return True if data can be cast to floats.

    Parameters
    ----------
    array :         array-like
                    The array to check.
    bool_numeric :  bool
                    If True (default), we count booleans as numeric data types.
    try_convert :   bool
                    If True, will try to convert array to floats.

    Returns
    -------
    is_numeric :    `bool`
                    True if the array has a numeric datatype, False if not.

    """
    array = np.asarray(array)

    # If array
    if array.dtype.kind == 'O' and try_convert:
        try:
            array = array.astype(float)
        except ValueError:
            pass

    if not bool_numeric:
        _NUMERIC_KINDS_NO_BOOL = _NUMERIC_KINDS.copy()
        _NUMERIC_KINDS_NO_BOOL.remove('b')
        return array.dtype.kind in _NUMERIC_KINDS_NO_BOOL

    return array.dtype.kind in _NUMERIC_KINDS

Return True if str is URL.

Examples:

>>> from navis.utils import is_url
>>> is_url('www.google.com')
False
>>> is_url('http://www.google.com')
True
>>> is_url("ftp://download.ft-server.org:8000")
True
Source code in navis/utils/misc.py
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
def is_url(x: str) -> bool:
    """Return True if str is URL.

    Examples
    --------
    >>> from navis.utils import is_url
    >>> is_url('www.google.com')
    False
    >>> is_url('http://www.google.com')
    True
    >>> is_url("ftp://download.ft-server.org:8000")
    True

    """
    parsed = urllib.parse.urlparse(x)

    if parsed.netloc and parsed.scheme:
        return True
    else:
        return False

Lock neuron while function is executed.

This makes sure that temporary attributes aren't re-calculated as changes are being made.

Source code in navis/utils/decorators.py
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
def lock_neuron(function):
    """Lock neuron while function is executed.

    This makes sure that temporary attributes aren't re-calculated as changes
    are being made.

    """

    @wraps(function)
    def wrapper(*args, **kwargs):
        # Lazy import to avoid issues with circular imports and pickling
        from .. import core

        # Lock if first argument is a neuron
        if isinstance(args[0], core.BaseNeuron):
            args[0]._lock = getattr(args[0], "_lock", 0) + 1
        try:
            # Execute function
            res = function(*args, **kwargs)
        except BaseException:
            raise
        finally:
            # Unlock neuron
            if isinstance(args[0], core.BaseNeuron):
                args[0]._lock -= 1
        # Return result
        return res

    return wrapper

Force input into a numpy array.

For dicts, keys will be turned into array.

Examples:

>>> from navis.utils import make_iterable
>>> make_iterable(1)
array([1])
>>> make_iterable([1])
array([1])
>>> make_iterable({'a': 1})
array(['a'], dtype='<U1')
Source code in navis/utils/iterables.py
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
def make_iterable(x,
                  force_type: Optional[type] = None
                  ) -> np.ndarray:
    """Force input into a numpy array.

    For dicts, keys will be turned into array.

    Examples
    --------
    >>> from navis.utils import make_iterable
    >>> make_iterable(1)
    array([1])
    >>> make_iterable([1])
    array([1])
    >>> make_iterable({'a': 1})
    array(['a'], dtype='<U1')

    """
    # Quantities are a special case
    if isinstance(x, pint.Quantity) and not isinstance(x.magnitude, np.ndarray):
        return config.ureg.Quantity(np.array([x.magnitude]), x.units)

    if not isinstance(x, Iterable) or isinstance(x, six.string_types):
        x = [x]

    if isinstance(x, (dict, set)):
        x = list(x)

    return np.asarray(x, dtype=force_type)

Turn input into non-iterable, if it isn't already.

Will raise error if len(x) > 1.

Examples:

>>> from navis.utils import make_non_iterable
>>> make_non_iterable([1])
1
>>> make_non_iterable(1)
1
>>> make_non_iterable([1, 2])
Traceback (most recent call last):
ValueError: Iterable must not contain more than one entry.
Source code in navis/utils/iterables.py
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
def make_non_iterable(x):
    """Turn input into non-iterable, if it isn't already.

    Will raise error if `len(x) > 1`.

    Examples
    --------
    >>> from navis.utils import make_non_iterable
    >>> make_non_iterable([1])
    1
    >>> make_non_iterable(1)
    1
    >>> make_non_iterable([1, 2])
    Traceback (most recent call last):
    ValueError: Iterable must not contain more than one entry.

    """
    if not is_iterable(x):
        return x
    elif len(x) == 1:
        return x[0]
    else:
        raise ValueError('Iterable must not contain more than one entry.')

Generate URL.

PARAMETER DESCRIPTION
*args
    Will be turned into the URL. For example:

        >>> make_url('http://neuromorpho.org', 'neuron', 'fields')
        'http://neuromorpho.org/neuron/fields'

TYPE: str DEFAULT: ()

**GET
    Keyword arguments are assumed to be GET request queries
    and will be encoded in the url. For example:

        >>> make_url('http://neuromorpho.org', 'neuron', 'fields',
        ...          page=1)
        'http://neuromorpho.org/neuron/fields?page=1'

DEFAULT: {}

RETURNS DESCRIPTION
url

TYPE: str

Examples:

>>> from navis.utils import is_url, make_url
>>> url = make_url('http://www.google.com', 'test', query='test')
>>> url
'http://www.google.com/test?query=test'
>>> is_url(url)
True
Source code in navis/utils/misc.py
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
def make_url(baseurl, *args: str, **GET) -> str:
    """Generate URL.

    Parameters
    ----------
    *args
                Will be turned into the URL. For example:

                    >>> make_url('http://neuromorpho.org', 'neuron', 'fields')
                    'http://neuromorpho.org/neuron/fields'

    **GET
                Keyword arguments are assumed to be GET request queries
                and will be encoded in the url. For example:

                    >>> make_url('http://neuromorpho.org', 'neuron', 'fields',
                    ...          page=1)
                    'http://neuromorpho.org/neuron/fields?page=1'

    Returns
    -------
    url :       str


    Examples
    --------
    >>> from navis.utils import is_url, make_url
    >>> url = make_url('http://www.google.com', 'test', query='test')
    >>> url
    'http://www.google.com/test?query=test'
    >>> is_url(url)
    True

    """
    url = baseurl
    # Generate the URL
    for arg in args:
        arg_str = str(arg)
        joiner = '' if url.endswith('/') else '/'
        relative = arg_str[1:] if arg_str.startswith('/') else arg_str
        url = requests.compat.urljoin(url + joiner, relative)
    if GET:
        url += f'?{urllib.parse.urlencode(GET)}'
    return url

Try making a navis.Volume from input object.

Source code in navis/utils/misc.py
75
76
77
78
79
80
81
82
83
84
85
86
def make_volume(x: Any) -> 'core.Volume':
    """Try making a navis.Volume from input object."""
    if isinstance(x, core.Volume):
        return x
    if is_mesh(x):
        inits = dict(vertices=x.vertices, faces=x.faces)
        for p in ['name', 'id', 'color']:
            if hasattr(x, p):
                inits[p] = getattr(x, p, None)
        return core.Volume(**inits)

    raise TypeError(f'Unable to coerce input of type "{type(x)}" to navis.Volume')

Decorate function to run on all neurons in the NeuronList.

This also updates the docstring.

PARAMETER DESCRIPTION
desc
         Descriptor to show in the progress bar if run over multiple
         neurons.

TYPE: str DEFAULT: ''

can_zip

TYPE: List[Union[str, int]] DEFAULT: []

must_zip
         Names of keyword arguments that need to be zipped together
         with the neurons in the neuronlist. For example:

           some_function(NeuronList([n1, n2, n3]), [p1, p2, p3])

         Should be executed as:

           some_function(n1, p1)
           some_function(n2, p2)
           some_function(n3, p3)

         `can_zip` will be zipped only if the length matches the
         length of the neuronlist. If a `can_zip` argument has only
         one value it will be re-used for all neurons.

         `must_zip` arguments have to have one value for each of the
         neurons.

         Single `None` values are always just passed through.

         Note that for this to consistently work the parameters in
         question have to be keyword-only (*).

TYPE: list DEFAULT: []

allow_parallel
         If True and the function is called with `parallel=True`,
         will use multiple cores to process the neuronlist. Number
         of cores a can be set using `n_cores` keyword argument.

TYPE: bool DEFAULT: False

Source code in navis/utils/decorators.py
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
def map_neuronlist(
    desc: str = "",
    can_zip: List[Union[str, int]] = [],
    must_zip: List[Union[str, int]] = [],
    allow_parallel: bool = False,
):
    """Decorate function to run on all neurons in the NeuronList.

    This also updates the docstring.

    Parameters
    ----------
    desc :           str
                     Descriptor to show in the progress bar if run over multiple
                     neurons.
    can_zip/
    must_zip :       list
                     Names of keyword arguments that need to be zipped together
                     with the neurons in the neuronlist. For example:

                       some_function(NeuronList([n1, n2, n3]), [p1, p2, p3])

                     Should be executed as:

                       some_function(n1, p1)
                       some_function(n2, p2)
                       some_function(n3, p3)

                     `can_zip` will be zipped only if the length matches the
                     length of the neuronlist. If a `can_zip` argument has only
                     one value it will be re-used for all neurons.

                     `must_zip` arguments have to have one value for each of the
                     neurons.

                     Single `None` values are always just passed through.

                     Note that for this to consistently work the parameters in
                     question have to be keyword-only (*).
    allow_parallel : bool
                     If True and the function is called with `parallel=True`,
                     will use multiple cores to process the neuronlist. Number
                     of cores a can be set using `n_cores` keyword argument.

    """

    # TODO:
    # - make can_zip/must_zip work with positional-only argumens to, i.e. let
    #   it work with integers instead of strings
    def decorator(function):
        @wraps(function)
        def wrapper(*args, **kwargs):
            from .. import core

            # Get the function's signature
            sig = inspect.signature(function)

            try:
                fnname = function.__name__
            except BaseException:
                fnname = str(function)

            parallel = kwargs.pop("parallel", False)
            if parallel and not allow_parallel:
                raise ValueError(
                    f"Function {fnname} does not support parallel processing."
                )

            # First, we need to extract the neuronlist
            if args:
                # If there are positional arguments, the first one is
                # the input neuron(s)
                nl = args[0]
                nl_key = "__args"
            else:
                # If not, we need to look for the name of the first argument
                # in the signature
                nl_key = list(sig.parameters.keys())[0]
                nl = kwargs.get(nl_key, None)

            # Complain if we did not get what we expected
            if isinstance(nl, type(None)):
                raise ValueError(
                    "Unable to identify the neurons for call"
                    f"{fnname}:\n {args}\n {kwargs}"
                )

            # If we have a neuronlist
            if isinstance(nl, core.NeuronList):
                # Pop the neurons from kwargs or args so we don't pass the
                # neurons twice
                if nl_key == "__args":
                    args = args[1:]
                else:
                    _ = kwargs.pop(nl_key)

                # Check "can zip" arguments
                for p in can_zip:
                    # Skip if not present or is None
                    if p not in kwargs or isinstance(kwargs[p], type(None)):
                        continue

                    if is_iterable(kwargs[p]):
                        # If iterable but length does not match: complain
                        le = len(kwargs[p])
                        if le != len(nl):
                            raise ValueError(
                                f"Got {le} values of `{p}` for {len(nl)} neurons."
                            )

                # Parse "must zip" arguments
                for p in must_zip:
                    # Skip if not present or is None
                    if p not in kwargs or isinstance(kwargs[p], type(None)):
                        continue

                    values = make_iterable(kwargs[p])
                    if len(values) != len(nl):
                        raise ValueError(
                            f"Got {len(values)} values of `{p}` for {len(nl)} neurons."
                        )

                # If we use parallel processing it makes sense to modify neurons
                # "inplace" since they will be copied into the child processes
                # anyway and that way we can avoid making an additional copy
                if "inplace" in kwargs:
                    # First check keyword arguments
                    inplace = kwargs["inplace"]
                elif "inplace" in sig.parameters:
                    # Next check signatures default
                    inplace = sig.parameters["inplace"].default
                else:
                    # All things failing assume it's not inplace
                    inplace = False

                if parallel and "inplace" in sig.parameters:
                    kwargs["inplace"] = True

                # Prepare processor
                n_cores = kwargs.pop("n_cores", os.cpu_count() // 2)
                chunksize = kwargs.pop("chunksize", 1)
                excl = list(kwargs.keys()) + list(range(1, len(args) + 1))
                proc = core.NeuronProcessor(
                    nl,
                    function,
                    parallel=parallel,
                    desc=desc,
                    warn_inplace=False,
                    progress=kwargs.pop("progress", True),
                    omit_failures=kwargs.pop("omit_failures", False),
                    chunksize=chunksize,
                    exclude_zip=excl,
                    n_cores=n_cores,
                )
                # Apply function
                res = proc(nl, *args, **kwargs)

                # When using parallel processing, the neurons will not actually
                # have been modified inplace - in that case we will simply
                # replace the neurons in `nl`
                if inplace:
                    nl.neurons = res.neurons
                else:
                    nl = res

                return nl
            else:
                # If single neuron just pass through
                return function(*args, **kwargs)

        # Update the docstring
        wrapper = map_neuronlist_update_docstring(wrapper, allow_parallel)

        return wrapper

    return decorator

Decorate function to run on all neurons in the NeuronList.

This version of the decorator is meant for functions that return a DataFrame. This decorator will add a neuron column with the respective neuron's ID and will then concatenate the dataframes.

PARAMETER DESCRIPTION
desc
         Descriptor to show in the progress bar if run over multiple
         neurons.

TYPE: str DEFAULT: ''

id_col
         Name of the ID column to be added to the results dataframe.

TYPE: str DEFAULT: 'neuron'

reset_index
         Whether to reset the index of the dataframe after
         concatenating.

TYPE: bool DEFAULT: True

allow_parallel
         If True and the function is called with `parallel=True`,
         will use multiple cores to process the neuronlist. Number
         of cores a can be set using `n_cores` keyword argument.

TYPE: bool DEFAULT: False

Source code in navis/utils/decorators.py
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
def map_neuronlist_df(
    desc: str = "",
    id_col: str = "neuron",
    reset_index: bool = True,
    allow_parallel: bool = False,
):
    """Decorate function to run on all neurons in the NeuronList.

    This version of the decorator is meant for functions that return a
    DataFrame. This decorator will add a `neuron` column with the respective
    neuron's ID and will then concatenate the dataframes.

    Parameters
    ----------
    desc :           str
                     Descriptor to show in the progress bar if run over multiple
                     neurons.
    id_col :         str
                     Name of the ID column to be added to the results dataframe.
    reset_index :    bool
                     Whether to reset the index of the dataframe after
                     concatenating.
    allow_parallel : bool
                     If True and the function is called with `parallel=True`,
                     will use multiple cores to process the neuronlist. Number
                     of cores a can be set using `n_cores` keyword argument.

    """

    # TODO:
    # - make can_zip/must_zip work with positional-only argumens to, i.e. let
    #   it work with integers instead of strings
    def decorator(function):
        @wraps(function)
        def wrapper(*args, **kwargs):
            # Lazy import to avoid issues with circular imports and pickling
            from .. import core

            # Get the function's signature
            sig = inspect.signature(function)

            try:
                fnname = function.__name__
            except BaseException:
                fnname = str(function)

            parallel = kwargs.pop("parallel", False)
            if parallel and not allow_parallel:
                raise ValueError(
                    f"Function {fnname} does not allow parallel processing."
                )

            # First, we need to extract the neuronlist
            if args:
                # If there are positional arguments, the first one is
                # the input neuron(s)
                nl = args[0]
                nl_key = "__args"
            else:
                # If not, we need to look for the name of the first argument
                # in the signature
                nl_key = list(sig.parameters.keys())[0]
                nl = kwargs.get(nl_key, None)

            # Complain if we did not get what we expected
            if isinstance(nl, type(None)):
                raise ValueError(
                    "Unable to identify the neurons for call"
                    f"{fnname}:\n {args}\n {kwargs}"
                )

            # If we have a neuronlist
            if isinstance(nl, core.NeuronList):
                # Pop the neurons from kwargs or args so we don't pass the
                # neurons twice
                if nl_key == "__args":
                    args = args[1:]
                else:
                    _ = kwargs.pop(nl_key)

                # Prepare processor
                n_cores = kwargs.pop("n_cores", os.cpu_count() // 2)
                chunksize = kwargs.pop("chunksize", 1)
                excl = list(kwargs.keys()) + list(range(1, len(args) + 1))
                proc = core.NeuronProcessor(
                    nl,
                    function,
                    parallel=parallel,
                    desc=desc,
                    warn_inplace=False,
                    progress=kwargs.pop("progress", True),
                    omit_failures=kwargs.pop("omit_failures", False),
                    chunksize=chunksize,
                    exclude_zip=excl,
                    n_cores=n_cores,
                )
                # Apply function
                res = proc(nl, *args, **kwargs)

                for n, df in zip(nl, res):
                    df.insert(0, column=id_col, value=n.id)

                df = pd.concat(res, axis=0)

                if reset_index:
                    df = df.reset_index(drop=True)

            else:
                # If single neuron just pass through
                df = function(*args, **kwargs)
                # df.insert(0, column=id_col, value=nl.id)

            return df

        # Update the docstring
        wrapper = map_neuronlist_update_docstring(wrapper, allow_parallel)

        return wrapper

    return decorator

Decorate function such that MeshNeurons are automatically skeletonized, the function is run on the skeleton and changes are propagated back to the meshe.

PARAMETER DESCRIPTION
method
    What to do with the results:
      - 'subset': subset MeshNeuron to what's left of the skeleton
      - 'split': split MeshNeuron following the skeleton's splits
      - 'node_to_vertex': map the returned node ID to the vertex IDs
      - 'node_properties' map node properties to vertices (requires
        `node_props` parameter)
      - 'pass_through' simply passes through the return value

TYPE: str

include_connectors
    If True, will try to make sure that if the MeshNeuron has
    connectors, they will be carried over to the skeleton.

TYPE: bool DEFAULT: False

copy_properties
    Any additional properties that need to be copied from the
    skeleton to the mesh.

TYPE: list DEFAULT: []

disallowed_kwargs
    Keyword arguments (name + value) that are not permitted when
    input is MeshNeuron.

TYPE: dict DEFAULT: {}

node_props
    For method 'node_properties'. String must be column names in
    node table of skeleton.

TYPE: list DEFAULT: []

reroot_soma
    If True and neuron has a soma (.soma_pos), will reroot to
    that soma.

TYPE: bool DEFAULT: False

heal
    Whether or not to heal the skeleton if the mesh is fragmented.

TYPE: bool DEFAULT: False

Source code in navis/utils/decorators.py
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
def meshneuron_skeleton(
    method: Union[
        Literal["subset"],
        Literal["split"],
        Literal["node_properties"],
        Literal["node_to_vertex"],
        Literal["pass_through"],
    ],
    include_connectors: bool = False,
    copy_properties: list = [],
    disallowed_kwargs: dict = {},
    node_props: list = [],
    reroot_soma: bool = False,
    heal: bool = False,
):
    """Decorate function such that MeshNeurons are automatically skeletonized,
    the function is run on the skeleton and changes are propagated
    back to the meshe.

    Parameters
    ----------
    method :    str
                What to do with the results:
                  - 'subset': subset MeshNeuron to what's left of the skeleton
                  - 'split': split MeshNeuron following the skeleton's splits
                  - 'node_to_vertex': map the returned node ID to the vertex IDs
                  - 'node_properties' map node properties to vertices (requires
                    `node_props` parameter)
                  - 'pass_through' simply passes through the return value
    include_connectors : bool
                If True, will try to make sure that if the MeshNeuron has
                connectors, they will be carried over to the skeleton.
    copy_properties : list
                Any additional properties that need to be copied from the
                skeleton to the mesh.
    disallowed_kwargs : dict
                Keyword arguments (name + value) that are not permitted when
                input is MeshNeuron.
    node_props : list
                For method 'node_properties'. String must be column names in
                node table of skeleton.
    reroot_soma :  bool
                If True and neuron has a soma (.soma_pos), will reroot to
                that soma.
    heal :      bool
                Whether or not to heal the skeleton if the mesh is fragmented.

    """
    assert isinstance(copy_properties, list)
    assert isinstance(disallowed_kwargs, dict)
    assert isinstance(node_props, list)

    allowed_methods = (
        "subset",
        "node_to_vertex",
        "split",
        "node_properties",
        "pass_through",
    )
    if method not in allowed_methods:
        raise ValueError(f'Unknown method "{method}"')

    if method == "node_properties" and not node_props:
        raise ValueError('Must provide `node_props` for method "node_properties"')

    def decorator(function):
        @wraps(function)
        def wrapper(*args, **kwargs):
            # Get the function's signature
            sig = inspect.signature(function)

            try:
                fnname = function.__name__
            except BaseException:
                fnname = str(function)

            # First, we need to extract the neuron from args and kwargs
            if args:
                # If there are positional arguments, the first one is assumed to
                # be the input neuron
                x = args[0]
                args = args[1:]
                x_key = "__args"
            else:
                # If not, we need to look for the name of the first argument
                # in the signature
                x_key = list(sig.parameters.keys())[0]
                x = kwargs.pop(x_key, None)

            # Complain if we did not get what we expected
            if isinstance(x, type(None)):
                raise ValueError(
                    "Unable to identify the neurons for call"
                    f"{fnname}:\n {args}\n {kwargs}"
                )

            # If input not a MeshNeuron, just pass through
            # Note delayed import to avoid circular imports and IMPORTANTLY
            # funky interactions with pickle/dill
            from .. import core

            if not isinstance(x, core.MeshNeuron):
                return function(x, *args, **kwargs)

            # Check for disallowed kwargs
            for k, v in disallowed_kwargs.items():
                if k in kwargs and kwargs[k] == v:
                    raise ValueError(
                        f"{k}={v} is not allowed when input is MeshNeuron(s)."
                    )

            # See if this is meant to be done inplace
            if "inplace" in kwargs:
                # First check keyword arguments
                inplace = kwargs["inplace"]
            elif "inplace" in sig.parameters:
                # Next check signatures default
                inplace = sig.parameters["inplace"].default
            else:
                # All things failing assume it's not inplace
                inplace = False

            # Now skeletonize
            sk = x.skeleton

            # Delayed import to avoid circular imports
            # Note that this HAS to be in the inner function otherwise
            # we get a weird error when pickling for parallel processing
            from .. import morpho

            if heal:
                sk = morpho.heal_skeleton(sk, method="LEAFS")

            if reroot_soma and sk.has_soma:
                sk = sk.reroot(sk.soma)

            if include_connectors and x.has_connectors and not sk.has_connectors:
                sk._connectors = x.connectors.copy()
                sk._connectors["node_id"] = sk.snap(
                    sk.connectors[["x", "y", "z"]].values
                )[0]

            # Apply function
            res = function(sk, *args, **kwargs)

            if method == "subset":
                # See which vertices we need to keep
                keep = np.isin(sk.vertex_map, res.nodes.node_id.values)

                x = morpho.subset_neuron(x, keep, inplace=inplace)

                for p in copy_properties:
                    setattr(x, p, getattr(sk, p, None))
            elif method == "split":
                meshes = []
                for n in res:
                    # See which vertices we need to keep
                    keep = np.isin(sk.vertex_map, n.nodes.node_id.values)

                    meshes.append(morpho.subset_neuron(x, keep, inplace=False))

                    for p in copy_properties:
                        setattr(meshes[-1], p, getattr(n, p, None))
                x = core.NeuronList(meshes)
            elif method == "node_to_vertex":
                x = np.where(sk.vertex_map == res)[0]
            elif method == "node_properties":
                for p in node_props:
                    node_map = sk.nodes.set_index("node_id")[p].to_dict()
                    vertex_props = np.array([node_map[n] for n in sk.vertex_map])
                    setattr(x, p, vertex_props)
            elif method == "pass_through":
                return res

            return x

        return wrapper

    return decorator

Split string at any of the given separators.

RETURNS DESCRIPTION
list

Examples:

>>> from navis.utils import multi_split
>>> s = '123;456,789:'
>>> multi_split(s, ';')
['123', '456,789']
>>> multi_split(s, [';', ','])
['123', '456', '789']
Source code in navis/utils/iterables.py
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
def multi_split(s, sep, strip=False):
    """Split string at any of the given separators.

    Returns
    -------
    list

    Examples
    --------
    >>> from navis.utils import multi_split
    >>> s = '123;456,789:'
    >>> multi_split(s, ';')
    ['123', '456,789']
    >>> multi_split(s, [';', ','])
    ['123', '456', '789']

    """
    assert isinstance(s, str)

    if isinstance(sep, str):
        sep = [sep]

    splits = []
    prev_sp = 0
    for i in range(len(s)):
        if s[i] in sep or i == (len(s) - 1):
            splits.append(s[prev_sp:i])
            prev_sp = i + 1

    if strip:
        splits = [sp.strip() for sp in splits]

    return splits

Categorize objects e.g. for plotting.

RETURNS DESCRIPTION
Neurons

TYPE: navis.NeuronList

Volume

TYPE: list of navis.Volume (trimesh.Trimesh will be converted)

Points

TYPE: list of arrays

Visuals

TYPE: list of vispy and pygfx visuals

Examples:

This is mostly for doc tests:

>>> from navis.utils import parse_objects
>>> from navis.data import example_neurons, example_volume
>>> import numpy as np
>>> nl = example_neurons(3)
>>> v = example_volume('LH')
>>> p = nl[0].nodes[['x', 'y', 'z']].values
>>> n, vols, points, vis = parse_objects([nl, v, p])
>>> type(n), len(n)
(<class 'navis.core.neuronlist.NeuronList'>, 3)
>>> type(vols), len(vols)
(<class 'list'>, 1)
>>> type(vols[0])
<class 'navis.core.volumes.Volume'>
>>> type(points), len(points)
(<class 'list'>, 1)
>>> type(points[0])
<class 'numpy.ndarray'>
>>> type(vis), len(vis)
(<class 'list'>, 0)
Source code in navis/utils/misc.py
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
def parse_objects(x) -> Tuple['core.NeuronList',
                              List['core.Volume'],
                              List[np.ndarray],
                              List]:
    """Categorize objects e.g. for plotting.

    Returns
    -------
    Neurons :       navis.NeuronList
    Volume :        list of navis.Volume (trimesh.Trimesh will be converted)
    Points :        list of arrays
    Visuals :       list of vispy and pygfx visuals

    Examples
    --------
    This is mostly for doc tests:

    >>> from navis.utils import parse_objects
    >>> from navis.data import example_neurons, example_volume
    >>> import numpy as np
    >>> nl = example_neurons(3)
    >>> v = example_volume('LH')
    >>> p = nl[0].nodes[['x', 'y', 'z']].values
    >>> n, vols, points, vis = parse_objects([nl, v, p])
    >>> type(n), len(n)
    (<class 'navis.core.neuronlist.NeuronList'>, 3)
    >>> type(vols), len(vols)
    (<class 'list'>, 1)
    >>> type(vols[0])
    <class 'navis.core.volumes.Volume'>
    >>> type(points), len(points)
    (<class 'list'>, 1)
    >>> type(points[0])
    <class 'numpy.ndarray'>
    >>> type(vis), len(vis)
    (<class 'list'>, 0)

    """
    # Make sure this is a list.
    if not isinstance(x, list):
        x = [x]

    # If any list in x, flatten first
    if any([isinstance(i, list) for i in x]):
        # We need to be careful to preserve order because of colors
        y = []
        for i in x:
            y += i if isinstance(i, list) else [i]
        x = y

    # Collect neuron objects, make a single NeuronList and split into types
    neurons = core.NeuronList([ob for ob in x if isinstance(ob,
                                                            (core.BaseNeuron,
                                                             core.NeuronList))],
                              make_copy=False)

    # Collect visuals
    visuals = [ob for ob in x if 'vispy' in str(type(ob)) or 'pygfx.objects' in str(type(ob))]

    # Collect and parse volumes
    volumes = [ob for ob in x if not isinstance(ob, (core.BaseNeuron,
                                                     core.NeuronList))
               and is_mesh(ob)]
    # Add templatebrains
    volumes += [ob.mesh for ob in x if isinstance(ob, TemplateBrain)]
    # Converts any non-navis meshes into Volumes
    volumes = [core.Volume(v) if not isinstance(v, core.Volume) else v for v in volumes]

    # Collect dataframes with X/Y/Z coordinates
    dataframes = [ob for ob in x if isinstance(ob, pd.DataFrame)]
    if [d for d in dataframes if False in np.isin(['x', 'y', 'z'], d.columns)]:
        logger.warning('DataFrames must have x, y and z columns.')
    # Filter to and extract x/y/z coordinates
    dataframes = [d for d in dataframes if False not in [c in d.columns for c in ['x', 'y', 'z']]]
    dataframes = [d[['x', 'y', 'z']].values for d in dataframes]

    # Collect arrays
    arrays = [ob.copy() for ob in x if isinstance(ob, np.ndarray)]
    # Remove arrays with wrong dimensions
    if [ob for ob in arrays if ob.shape[1] != 3 and ob.shape[0] != 2]:
        logger.warning('Arrays need to be of shape (N, 3) for scatter or (2, N)'
                       ' for line plots.')
    arrays = [ob for ob in arrays if any(np.isin(ob.shape, [2, 3]))]

    points = dataframes + arrays

    return neurons, volumes, points, visuals

Round number intelligently to produce Human-readable numbers.

This functions rounds to the Nth decimal, where N is precision minus the number of digits before the decimal. The general idea is that the bigger the number, the less we care about decimals - and vice versa.

PARAMETER DESCRIPTION
num
    A number.

TYPE: float | int

prec
    The precision we are aiming for.

TYPE: int DEFAULT: 8

Examples:

>>> import navis
>>> navis.utils.round_smart(0.00999)
0.00999
>>> navis.utils.round_smart(10000000.00999)
10000000.0
Source code in navis/utils/misc.py
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
def round_smart(num: Union[int, float], prec: int = 8) -> float:
    """Round number intelligently to produce Human-readable numbers.

    This functions rounds to the Nth decimal, where N is `precision` minus the
    number of digits before the decimal. The general idea is that the bigger
    the number, the less we care about decimals - and vice versa.

    Parameters
    ----------
    num :       float | int
                A number.
    prec :      int
                The precision we are aiming for.

    Examples
    --------
    >>> import navis
    >>> navis.utils.round_smart(0.00999)
    0.00999
    >>> navis.utils.round_smart(10000000.00999)
    10000000.0

    """
    # Number of digits before decimal
    lg = math.log10(num)
    if lg < 0:
        N = 0
    else:
        N = int(lg)

    return round(num, max(prec - N, 0))

Bytes to Human readable.

Source code in navis/utils/misc.py
66
67
68
69
70
71
72
def sizeof_fmt(num, suffix='B'):
    """Bytes to Human readable."""
    for unit in ['', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi']:
        if abs(num) < 1024.0:
            return "%3.1f%s%s" % (num, unit, suffix)
        num /= 1024.0
    return "%.1f%s%s" % (num, 'Yi', suffix)

Unpack neurons and returns a list of individual neurons.

Examples:

This is mostly for doc tests:

>>> from navis.utils import unpack_neurons
>>> from navis.data import example_neurons
>>> nl = example_neurons(3)
>>> type(nl)
<class 'navis.core.neuronlist.NeuronList'>
>>> # Unpack list of neuronlists
>>> unpacked = unpack_neurons([nl, nl])
>>> type(unpacked)
<class 'list'>
>>> type(unpacked[0])
<class 'navis.core.skeleton.TreeNeuron'>
>>> len(unpacked)
6
Source code in navis/utils/misc.py
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
def unpack_neurons(x: Union[Iterable, 'core.NeuronList', 'core.NeuronObject'],
                   raise_on_error: bool = True
                   ) -> List['core.NeuronObject']:
    """Unpack neurons and returns a list of individual neurons.

    Examples
    --------
    This is mostly for doc tests:

    >>> from navis.utils import unpack_neurons
    >>> from navis.data import example_neurons
    >>> nl = example_neurons(3)
    >>> type(nl)
    <class 'navis.core.neuronlist.NeuronList'>
    >>> # Unpack list of neuronlists
    >>> unpacked = unpack_neurons([nl, nl])
    >>> type(unpacked)
    <class 'list'>
    >>> type(unpacked[0])
    <class 'navis.core.skeleton.TreeNeuron'>
    >>> len(unpacked)
    6

    """
    neurons: list = []

    if isinstance(x, (list, np.ndarray, tuple)):
        for l in x:
            neurons += unpack_neurons(l)
    elif isinstance(x, core.BaseNeuron):
        neurons.append(x)
    elif isinstance(x, core.NeuronList):
        neurons += x.neurons
    elif raise_on_error:
        raise TypeError(f'Unknown neuron format: "{type(x)}"')

    return neurons

Check if neuron contains all required data.

E.g. for plot3d(plot_connectors=True).

PARAMETER DESCRIPTION
x
            Neuron to check for data.

TYPE: TreeNeuron

options
            Options to check, e.g. "plot".

TYPE: str | list of str

kwargs
            Keyword arguments to check for options.

TYPE: dict

raise_on_error
            If True, will raise error if data not found.

TYPE: bool DEFAULT: True

RETURNS DESCRIPTION
None
Source code in navis/utils/validate.py
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
def validate_options(x: 'core.TreeNeuron',
                     options: Union[List[str], str],
                     kwargs: dict,
                     raise_on_error: bool = True) -> None:
    """Check if neuron contains all required data.

    E.g. for `plot3d(plot_connectors=True)`.

    Parameters
    ----------
    x :                 TreeNeuron
                        Neuron to check for data.
    options :           str | list of str
                        Options to check, e.g. "plot".
    kwargs :            dict
                        Keyword arguments to check for options.
    raise_on_error :    bool, optional
                        If True, will raise error if data not found.

    Returns
    -------
    None

    """
    options = make_iterable(options)

    for o in options:
        for k in kwargs:
            if isinstance(k, str) and k.startswith(o):
                d = k[k.index('_'):]
                if not hasattr(x, d):
                    msg = f'Option "{k}" but {type(x)} has no "{d}"'
                    if raise_on_error:
                        raise ValueError(msg)
                    else:
                        logger.warning(msg)

Validate DataFrame.

PARAMETER DESCRIPTION
x
    DataFrame to validate.

TYPE: pd.DataFrame

required
    Columns to check for. If column is given as tuple (e.g.
    `('type', 'relation', 'label')` one of these columns
    has to exist)

TYPE: iterable

rename
    If True and a required column is given as tuple, will rename
    that column to the first entry in tuple.

TYPE: bool DEFAULT: False

restrict
    If True, will return only `required` columns.

TYPE: bool DEFAULT: False

optional
    Optional columns. If column not present will be generated.
    Dictionary must map column name to default value. Keys can also
    be tuples - like `required`.

TYPE: dict DEFAULT: {}

RETURNS DESCRIPTION
pandas.DataFrame

If restrict=True will return DataFrame subset to only the required columns. Columns defined in optional will be added if they don't already exist.

Examples:

>>> from navis.utils import validate_table
>>> from navis.data import example_neurons
>>> n = example_neurons(1)
>>> tbl = validate_table(n.nodes, ['x', 'y', 'z', 'node_id'])
>>> tbl = validate_table(n.nodes, ['does_not_exist'])
ValueError: Table missing required column: "does_not_exist"
RAISES DESCRIPTION
ValueError

If any of the required columns are not in the table.

Source code in navis/utils/validate.py
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
def validate_table(x: pd.DataFrame,
                   required: List[Union[str, Tuple[str]]],
                   rename: bool = False,
                   restrict: bool = False,
                   optional: dict = {}) -> pd.DataFrame:
    """Validate DataFrame.

    Parameters
    ----------
    x :         pd.DataFrame
                DataFrame to validate.
    required :  iterable
                Columns to check for. If column is given as tuple (e.g.
                `('type', 'relation', 'label')` one of these columns
                has to exist)
    rename :    bool, optional
                If True and a required column is given as tuple, will rename
                that column to the first entry in tuple.
    restrict :  bool, optional
                If True, will return only `required` columns.
    optional :  dict, optional
                Optional columns. If column not present will be generated.
                Dictionary must map column name to default value. Keys can also
                be tuples - like `required`.

    Returns
    -------
    pandas.DataFrame
                If `restrict=True` will return DataFrame subset to only the
                required columns. Columns defined in `optional` will be
                added if they don't already exist.

    Examples
    --------
    >>> from navis.utils import validate_table
    >>> from navis.data import example_neurons
    >>> n = example_neurons(1)
    >>> tbl = validate_table(n.nodes, ['x', 'y', 'z', 'node_id'])
    >>> tbl = validate_table(n.nodes, ['does_not_exist'])       # doctest: +SKIP
    ValueError: Table missing required column: "does_not_exist"

    Raises
    ------
    ValueError
            If any of the required columns are not in the table.

    """
    if not isinstance(x, pd.DataFrame):
        raise TypeError(f'Need DataFrame, got "{type(x)}"')

    for r in required:
        if isinstance(r, (tuple, list)):
            if not any(set(r) & set(x.columns)):
                raise ValueError('Table must contain either of these columns'
                                 f' {", ".join(r)}')
        else:
            if r not in x.columns:
                raise ValueError(f'Table missing required column: "{r}"')

    # Rename columns if necessary
    if rename:
        # Generate mapping. Order makes sure that required columns take
        # precedence in case of a name clash
        new_name = {c: t[0] for t in optional if isinstance(t, (tuple, list)) for c in t[1:]}
        new_name.update({c: t[0] for t in required if isinstance(t, (tuple, list)) for c in t[1:]})

        # Apply mapping
        x.columns = [new_name.get(c, c) for c in x.columns]

    if restrict:
        flat_req = [r for r in required if not isinstance(r, (list, tuple))]
        flat_req += [r for l in required if isinstance(l, (list, tuple)) for r in l]

        x = x[[r for r in flat_req if r in x.columns]]

    for c, v in optional.items():
        # Convert to tuples
        if not isinstance(c, (tuple, list)):
            c = (c, )

        if not any(set(c) & set(x.columns)):
            x[c[0]] = v

    return x