• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

int-brain-lab / ibllib / 29088413539

10 Jul 2026 11:06AM UTC coverage: 64.314% (+2.7%) from 61.575%
29088413539

push

github

web-flow
Merge a5e844d4b into 087557586

1825 of 3050 new or added lines in 81 files covered. (59.84%)

2994 existing lines in 75 files now uncovered.

12192 of 18957 relevant lines covered (64.31%)

0.64 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

67.25
/brainbox/plot_base.py
1
import numpy as np
1✔
2
import matplotlib.pyplot as plt
1✔
3
from matplotlib.image import NonUniformImage
1✔
4
import matplotlib
1✔
5
from matplotlib import cm
1✔
6
from iblutil.util import Bunch
1✔
7

8

9
axis_dict = {'x': 0, 'y': 1, 'z': 2}
1✔
10

11

12
class DefaultPlot(object):
1✔
13
    def __init__(self, plot_type, data):
1✔
14
        """
15
        Base class for organising data into a structure that can be easily used to create plots.
16
        The idea is that the dictionary is independent of plotting method and so can be fed into
17
        matplotlib, pyqtgraph, datoviz (or any plotting method of choice).
18

19
        :param plot_type: type of plot (just for reference)
20
        :type plot_type: string
21
        :param data: dict of data containing at least 'x', 'y', and may additionally contain 'z'
22
        for 3D plots and 'c' 2D (image or scatter plots) with third variable represented by colour
23
        :type data: dict
24
        """
25
        self.plot_type = plot_type
1✔
26
        self.data = data
1✔
27
        self.hlines = []
1✔
28
        self.vlines = []
1✔
29
        self.set_labels()
1✔
30

31
    def add_lines(self, pos, orientation, lim=None, style='--', width=3, color='k'):
1✔
32
        """
33
        Method to specify position and style of horizontal or vertical reference lines
34
        :param pos: position of line
35
        :param orientation: either 'v' for vertical line or 'h' for horizontal line
36
        :param lim: extent of lines
37
        :param style: line style
38
        :param width: line width
39
        :param color: line colour
40
        :return:
41
        """
42
        if orientation == 'v':
1✔
43
            lim = self._set_default(lim, self.ylim)
1✔
44
            self.vlines.append(Bunch({'pos': pos, 'lim': lim, 'style': style, 'width': width, 'color': color}))
1✔
45
        if orientation == 'h':
1✔
46
            lim = self._set_default(lim, self.xlim)
1✔
47
            self.hlines.append(Bunch({'pos': pos, 'lim': lim, 'style': style, 'width': width, 'color': color}))
1✔
48

49
    def set_labels(self, title=None, xlabel=None, ylabel=None, zlabel=None, clabel=None):
1✔
50
        """
51
        Set labels for plot
52

53
        :param title: title
54
        :param xlabel: x axis label
55
        :param ylabel: y axis label
56
        :param zlabel: z axis label
57
        :param clabel: cbar label
58
        :return:
59
        """
60
        self.labels = Bunch({'title': title, 'xlabel': xlabel, 'ylabel': ylabel, 'zlabel': zlabel, 'clabel': clabel})
1✔
61

62
    def set_xlim(self, xlim=None):
1✔
63
        """
64
        Set xlim values
65

66
        :param xlim: xlim values (min, max) supports tuple, list or np.array of len(2). If not
67
        specified will compute as min, max of y data
68
        """
69
        self.xlim = self._set_lim('x', lim=xlim)
1✔
70

71
    def set_ylim(self, ylim=None):
1✔
72
        """
73
        Set ylim values
74

75
        :param ylim: ylim values (min, max) supports tuple, list or np.array of len(2). If not
76
        specified will compute as min, max of y data
77
        """
78
        self.ylim = self._set_lim('y', lim=ylim)
1✔
79

80
    def set_zlim(self, zlim=None):
1✔
81
        """
82
        Set zlim values
83

84
        :param zlim: zlim values (min, max) supports tuple, list or np.array of len(2). If not
85
        specified will compute as min, max of z data
86
        """
87
        self.zlim = self._set_lim('z', lim=zlim)
1✔
88

89
    def set_clim(self, clim=None):
1✔
90
        """
91
        Set clim values
92

93
        :param clim: clim values (min, max) supports tuple, list or np.array of len(2). If not
94
        specified will compute as min, max of c data
95
        """
96
        self.clim = self._set_lim('c', lim=clim)
1✔
97

98
    def _set_lim(self, axis, lim=None):
1✔
99
        """
100
        General function to set limits to either specified value if lim is not None or to nanmin,
101
        nanmin of data
102

103
        :param axis: x, y, z or c
104
        :param lim: lim values (min, max) supports tuple, list or np.array of len(2)
105
        :return:
106
        """
107
        if lim is not None:
1✔
108
            assert len(lim) == 2
1✔
109
        else:
110
            lim = (np.nanmin(self.data[axis]), np.nanmax(self.data[axis]))
1✔
111
        return lim
1✔
112

113
    def _set_default(self, val, default):
1✔
114
        """
115
        General function to set value of attribute. If val is not None, the value of val will be
116
        returned otherwise default value will be returned
117

118
        :param val: non-default value to set attribute to
119
        :param default: default value of attribute
120
        :return:
121
        """
122
        if val is None:
1✔
123
            return default
1✔
124
        else:
125
            return val
1✔
126

127
    def convert2dict(self):
1✔
128
        """
129
        Convert class object to dictionary
130

131
        :return: dict with variables needed for plotting
132
        """
133
        return vars(self)
1✔
134

135

136
class ImagePlot(DefaultPlot):
1✔
137
    def __init__(self, img, x=None, y=None, cmap=None):
1✔
138
        """
139
        Class for organising data that will be used to create 2D image plots
140

141
        :param img: 2D image data
142
        :param x: x coordinate of each image voxel in x dimension
143
        :param y: y coordinate of each image voxel in y dimension
144
        :param cmap: name of colormap to use
145
        """
146

147
        data = Bunch({
1✔
148
            'x': self._set_default(x, np.arange(img.shape[0])),
149
            'y': self._set_default(y, np.arange(img.shape[1])),
150
            'c': img,
151
        })
152

153
        # Make sure dimensions agree
154
        assert data['c'].shape[0] == data['x'].shape[0], 'dimensions must agree'
1✔
155
        assert data['c'].shape[1] == data['y'].shape[0], 'dimensions must agree'
1✔
156

157
        # Initialise default plot class with data
158
        super().__init__('image', data)
1✔
159
        self.scale = None
1✔
160
        self.offset = None
1✔
161
        self.cmap = self._set_default(cmap, 'viridis')
1✔
162

163
        self.set_xlim()
1✔
164
        self.set_ylim()
1✔
165
        self.set_clim()
1✔
166

167
    def set_scale(self, scale=None):
1✔
168
        """
169
        Set the scaling factor to apply to image (mainly for pyqtgraph implementation)
170

171
        :param scale: scale values (xscale, yscale), supports tuple, list or np.array of len(2).
172
        If not specified will automatically compute from xlims/ylims and shape of data
173
        :return:
174
        """
175
        # For pyqtgraph implementation
176
        if scale is not None:
1✔
UNCOV
177
            assert len(scale) == 2
×
178
        self.scale = self._set_default(scale, (self._get_scale('x'), self._get_scale('y')))
1✔
179

180
    def _get_scale(self, axis):
1✔
181
        """
182
        Calculate scaling factor to apply along axis. Don't use directly, use set_scale() method
183

184
        :param axis: 'x' or 'y'
185
        :return:
186
        """
187
        if axis == 'x':
1✔
188
            lim = self.xlim
1✔
189
        else:
190
            lim = self.ylim
1✔
191
        lim = self._set_lim(axis, lim=lim)
1✔
192
        scale = (lim[1] - lim[0]) / self.data['c'].shape[axis_dict[axis]]
1✔
193
        return scale
1✔
194

195
    def set_offset(self, offset=None):
1✔
196
        """
197
        Set the offset to apply to the image (mainly for pyqtgraph implementation)
198

199
        :param offset: offset values (xoffset, yoffset), supports tuple, list or np.array of len(2)
200
        If not specified will automatically compute from minimum of xlim and ylim
201
        :return:
202
        """
203
        # For pyqtgraph implementation
204
        if offset is not None:
1✔
UNCOV
205
            assert len(offset) == 2
×
206
        self.offset = self._set_default(offset, (self._get_offset('x'), self._get_offset('y')))
1✔
207

208
    def _get_offset(self, axis):
1✔
209
        """
210
        Calculate offset to apply to axis. Don't use directly, use set_offset() method
211
        :param axis: 'x' or 'y'
212
        :return:
213
        """
214
        offset = np.nanmin(self.data[axis])
1✔
215
        return offset
1✔
216

217

218
class ProbePlot(DefaultPlot):
1✔
219
    def __init__(self, img, x, y, cmap=None):
1✔
220
        """
221
        Class for organising data that will be used to create 2D probe plots. Use function
222
        plot_base.arrange_channels2bank to prepare data in correct format before using this class
223

224
        :param img: list of image data for each bank of probe
225
        :param x: list of x coordinate for each bank of probe
226
        :param y: list of y coordinate for each bank or probe
227
        :param cmap: name of cmap
228
        """
229

230
        # Make sure we have inputs as lists, can get input from arrange_channels2banks
231
        assert isinstance(img, list)
1✔
232
        assert isinstance(x, list)
1✔
233
        assert isinstance(y, list)
1✔
234

235
        data = Bunch({'x': x, 'y': y, 'c': img})
1✔
236
        super().__init__('probe', data)
1✔
237
        self.cmap = self._set_default(cmap, 'viridis')
1✔
238

239
        self.set_xlim()
1✔
240
        self.set_ylim()
1✔
241
        self.set_clim()
1✔
242
        self.set_scale()
1✔
243
        self.set_offset()
1✔
244

245
    def set_scale(self, idx=None, scale=None):
1✔
246
        if scale is not None:
1✔
UNCOV
247
            self.scale[idx] = scale
×
248
        else:
249
            self.scale = [(self._get_scale(i, 'x'), self._get_scale(i, 'y')) for i in range(len(self.data['x']))]
1✔
250

251
    def _get_scale(self, idx, axis):
1✔
252
        lim = self._set_lim_list(axis, idx)
1✔
253
        scale = (lim[1] - lim[0]) / self.data['c'][idx].shape[axis_dict[axis]]
1✔
254
        return scale
1✔
255

256
    def _set_lim_list(self, axis, idx, lim=None):
1✔
257
        if lim is not None:
1✔
UNCOV
258
            assert len(lim) == 2
×
259
        else:
260
            lim = (np.nanmin(self.data[axis][idx]), np.nanmax(self.data[axis][idx]))
1✔
261
        return lim
1✔
262

263
    def set_offset(self, idx=None, offset=None):
1✔
264
        if offset is not None:
1✔
UNCOV
265
            self.offset[idx] = offset
×
266
        else:
267
            self.offset = [(np.min(self.data['x'][i]), np.min(self.data['y'][i])) for i in range(len(self.data['x']))]
1✔
268

269
    def _set_lim(self, axis, lim=None):
1✔
270
        if lim is not None:
1✔
271
            assert len(lim) == 2
1✔
272
        else:
273
            data = np.concatenate([np.squeeze(np.ravel(d)) for d in self.data[axis]]).ravel()
1✔
274
            lim = (np.nanmin(data), np.nanmax(data))
1✔
275
        return lim
1✔
276

277

278
class ScatterPlot(DefaultPlot):
1✔
279
    def __init__(self, x, y, z=None, c=None, cmap=None, plot_type='scatter'):
1✔
280
        """
281
        Class for organising data that will be used to create scatter plots. Can be 2D or 3D (if
282
        z given). Can also represent variable through color by specifying c
283

284
        :param x: x values for data
285
        :param y: y values for data
286
        :param z: z values for data
287
        :param c: values to use to represent color of scatter points
288
        :param cmap: name of colormap to use if c is given
289
        :param plot_type:
290
        """
291
        data = Bunch({'x': x, 'y': y, 'z': z, 'c': c})
1✔
292

293
        assert len(data['x']) == len(data['y']), 'dimensions must agree'
1✔
294
        if data['z'] is not None:
1✔
UNCOV
295
            assert len(data['z']) == len(data['x']), 'dimensions must agree'
×
296
        if data['c'] is not None:
1✔
297
            assert len(data['c']) == len(data['x']), 'dimensions must agree'
1✔
298

299
        super().__init__(plot_type, data)
1✔
300

301
        self._set_init_style()
1✔
302
        self.set_xlim()
1✔
303
        self.set_ylim()
1✔
304
        # If we have 3D data
305
        if data['z'] is not None:
1✔
UNCOV
306
            self.set_zlim()
×
307
        # If we want colorbar associated with scatter plot
308
        self.set_clim()
1✔
309
        self.cmap = self._set_default(cmap, 'viridis')
1✔
310

311
    def _set_init_style(self):
1✔
312
        """
313
        Initialise defaults
314
        :return:
315
        """
316
        self.set_color()
1✔
317
        self.set_marker_size()
1✔
318
        self.set_marker_type('o')
1✔
319
        self.set_opacity()
1✔
320
        self.set_line_color()
1✔
321
        self.set_line_width()
1✔
322
        self.set_line_style()
1✔
323

324
    def set_color(self, color=None):
1✔
325
        """
326
        Color of scatter points.
327
        :param color: string e.g 'k', single RGB e,g [0,0,0] or np.array of RGB. In the latter case
328
        must give same no. of colours as datapoints i.e. len(np.array(RGB)) == len(data['x'])
329
        :return:
330
        """
331
        self.color = self._set_default(color, 'b')
1✔
332

333
    def set_marker_size(self, marker_size=None):
1✔
334
        """
335
        Size of each scatter point
336
        :param marker_size: int or np.array of int. In the latter case must give same no. of
337
        marker_size as datapoints i.e len(np.array(marker_size)) == len(data['x'])
338
        :return:
339
        """
340
        self.marker_size = self._set_default(marker_size, None)
1✔
341

342
    def set_marker_type(self, marker_type=None):
1✔
343
        """
344
        Shape of each scatter point
345

346
        :param marker_type:
347
        :return:
348
        """
349
        self.marker_type = self._set_default(marker_type, None)
1✔
350

351
    def set_opacity(self, opacity=None):
1✔
352
        """
353
        Opacity of each scatter point
354

355
        :param opacity:
356
        :return:
357
        """
358
        self.opacity = self._set_default(opacity, 1)
1✔
359

360
    def set_line_color(self, line_color=None):
1✔
361
        """
362
        Colour of edge of scatter point
363

364
        :param line_color: string e.g 'k' or RGB e.g [0,0,0]
365
        :return:
366
        """
367
        self.line_color = self._set_default(line_color, None)
1✔
368

369
    def set_line_width(self, line_width=None):
1✔
370
        """
371
        Width of line on edge of scatter point
372

373
        :param line_width: int
374
        :return:
375
        """
376
        self.line_width = self._set_default(line_width, None)
1✔
377

378
    def set_line_style(self, line_style=None):
1✔
379
        """
380
        Style of line on edge of scatter point
381

382
        :param line_style:
383
        :return:
384
        """
385
        self.line_style = self._set_default(line_style, '-')
1✔
386

387

388
class LinePlot(ScatterPlot):
1✔
389
    def __init__(self, x, y):
1✔
390
        """
391
        Class for organising data that will be used to create line plots.
392

393
        :param x: x values for data
394
        :param y: y values for data
395
        """
396
        super().__init__(x, y, plot_type='line')
1✔
397

398
        self._set_init_style()
1✔
399
        self.set_xlim()
1✔
400
        self.set_ylim()
1✔
401

402
    def _set_init_style(self):
1✔
403
        self.set_line_color('k')
1✔
404
        self.set_line_width(2)
1✔
405
        self.set_line_style()
1✔
406
        self.set_marker_size()
1✔
407
        self.set_marker_type()
1✔
408

409

410
def add_lines(ax, data, **kwargs):
1✔
411
    """
412
    Function to add vertical and horizontal reference lines to matplotlib axis
413

414
    :param ax: matplotlib axis
415
    :param data: dict of plot data
416
    :param kwargs: matplotlib keywords arguments associated with vlines/hlines
417
    :return:
418
    """
419

UNCOV
420
    for vline in data['vlines']:
×
NEW
421
        ax.vlines(
×
422
            vline['pos'],
423
            ymin=vline['lim'][0],
424
            ymax=vline['lim'][1],
425
            linestyles=vline['style'],
426
            linewidth=vline['width'],
427
            colors=vline['color'],
428
            **kwargs,
429
        )
430

UNCOV
431
    for hline in data['hlines']:
×
NEW
432
        ax.hlines(
×
433
            hline['pos'],
434
            xmin=hline['lim'][0],
435
            xmax=hline['lim'][1],
436
            linestyles=hline['style'],
437
            linewidth=hline['width'],
438
            colors=hline['color'],
439
            **kwargs,
440
        )
441

UNCOV
442
    return ax
×
443

444

445
def plot_image(data, ax=None, show_cbar=True, fig_kwargs=dict(), line_kwargs=dict(), img_kwargs=dict()):
1✔
446
    """
447
    Function to create matplotlib plot from ImagePlot object
448

449
    :param data: ImagePlot object, either class or dict
450
    :param ax: matplotlib axis to plot on, if None, will create figure
451
    :param show_cbar: whether or not to display colour bar
452
    :param fig_kwargs: dict of matplotlib keywords associcated with plt.subplots e.g can be
453
    fig size, tight layout etc.
454
    :param line_kwargs: dict of matplotlib keywords associated with ax.hlines/ax.vlines
455
    :param img_kwargs: dict of matplotlib keywords associated with matplotlib.imshow
456
    :return: matplotlib axis and figure handles
457
    """
UNCOV
458
    if not isinstance(data, dict):
×
UNCOV
459
        data = data.convert2dict()
×
460

UNCOV
461
    if not ax:
×
UNCOV
462
        fig, ax = plt.subplots(**fig_kwargs)
×
463
    else:
UNCOV
464
        fig = plt.gcf()
×
465

NEW
466
    img = ax.imshow(
×
467
        data['data']['c'].T,
468
        extent=np.r_[data['xlim'], data['ylim']],
469
        cmap=data['cmap'],
470
        vmin=data['clim'][0],
471
        vmax=data['clim'][1],
472
        origin='lower',
473
        aspect='auto',
474
        **img_kwargs,
475
    )
476

477
    ax.set_xlim(data['xlim'][0], data['xlim'][1])
×
UNCOV
478
    ax.set_ylim(data['ylim'][0], data['ylim'][1])
×
479
    ax.set_xlabel(data['labels']['xlabel'])
×
UNCOV
480
    ax.set_ylabel(data['labels']['ylabel'])
×
481
    ax.set_title(data['labels']['title'])
×
482

UNCOV
483
    if show_cbar:
×
UNCOV
484
        cbar = fig.colorbar(img, ax=ax)
×
UNCOV
485
        cbar.set_label(data['labels']['clabel'])
×
486

UNCOV
487
    ax = add_lines(ax, data, **line_kwargs)
×
488

UNCOV
489
    return ax, fig
×
490

491

492
def plot_scatter(data, ax=None, show_cbar=True, fig_kwargs=dict(), line_kwargs=dict(), scat_kwargs=None):
1✔
493
    """
494
    Function to create matplotlib plot from ScatterPlot object. If data['colors'] is given for each
495
    data point it will override automatic colours that would be generated from data['data']['c']
496

497
    :param data: ScatterPlot object, either class or dict
498
    :param ax: matplotlib axis to plot on, if None, will create figure
499
    :param show_cbar: whether or not to display colour bar
500
    :param fig_kwargs: dict of matplotlib keywords associcated with plt.subplots e.g can be
501
    fig size, tight layout etc.
502
    :param line_kwargs: dict of matplotlib keywords associated with ax.hlines/ax.vlines
503
    :param scat_kwargs: dict of matplotlib keywords associated with matplotlib.scatter
504
    :return: matplotlib axis and figure handles
505
    """
UNCOV
506
    scat_kwargs = scat_kwargs or dict()
×
UNCOV
507
    if not isinstance(data, dict):
×
UNCOV
508
        data = data.convert2dict()
×
509

UNCOV
510
    if not ax:
×
UNCOV
511
        fig, ax = plt.subplots(**fig_kwargs)
×
512
    else:
UNCOV
513
        fig = plt.gcf()
×
514

515
    # Single color for all points
UNCOV
516
    if data['data']['c'] is None:
×
NEW
517
        scat = ax.scatter(
×
518
            x=data['data']['x'],
519
            y=data['data']['y'],
520
            c=data['color'],
521
            s=data['marker_size'],
522
            marker=data['marker_type'],
523
            edgecolors=data['line_color'],
524
            linewidths=data['line_width'],
525
            **scat_kwargs,
526
        )
527
    else:
528
        # Colour for each point specified
UNCOV
529
        if len(data['color']) == len(data['data']['x']):
×
530
            if np.max(data['color']) > 1:
×
531
                data['color'] = data['color'] / 255
×
532

NEW
533
            scat = ax.scatter(
×
534
                x=data['data']['x'],
535
                y=data['data']['y'],
536
                c=data['color'],
537
                s=data['marker_size'],
538
                marker=data['marker_type'],
539
                edgecolors=data['line_color'],
540
                linewidths=data['line_width'],
541
                **scat_kwargs,
542
            )
UNCOV
543
            if show_cbar:
×
NEW
544
                norm = matplotlib.colors.Normalize(vmin=data['clim'][0], vmax=data['clim'][1], clip=True)
×
UNCOV
545
                cbar = fig.colorbar(cm.ScalarMappable(norm=norm, cmap=data['cmap']), ax=ax)
×
UNCOV
546
                cbar.set_label(data['labels']['clabel'])
×
547
        # Automatically generate from c data
548
        else:
NEW
549
            scat = ax.scatter(
×
550
                x=data['data']['x'],
551
                y=data['data']['y'],
552
                c=data['data']['c'],
553
                s=data['marker_size'],
554
                marker=data['marker_type'],
555
                cmap=data['cmap'],
556
                vmin=data['clim'][0],
557
                vmax=data['clim'][1],
558
                edgecolors=data['line_color'],
559
                linewidths=data['line_width'],
560
                **scat_kwargs,
561
            )
562
            if show_cbar:
×
563
                cbar = fig.colorbar(scat, ax=ax)
×
564
                cbar.set_label(data['labels']['clabel'])
×
565

566
    ax = add_lines(ax, data, **line_kwargs)
×
567

UNCOV
568
    ax.set_xlim(data['xlim'][0], data['xlim'][1])
×
UNCOV
569
    ax.set_ylim(data['ylim'][0], data['ylim'][1])
×
UNCOV
570
    ax.set_xlabel(data['labels']['xlabel'])
×
UNCOV
571
    ax.set_ylabel(data['labels']['ylabel'])
×
UNCOV
572
    ax.set_title(data['labels']['title'])
×
573

UNCOV
574
    return ax, fig
×
575

576

577
def plot_probe(data, ax=None, show_cbar=True, make_pretty=True, fig_kwargs=dict(), line_kwargs=dict()):
1✔
578
    """
579
    Function to create matplotlib plot from ProbePlot object
580

581
    :param data: ProbePlot object, either class or dict
582
    :param ax: matplotlib axis to plot on, if None, will create figure
583
    :param show_cbar: whether or not to display colour bar
584
    :param make_pretty: get rid of spines on axis
585
    :param fig_kwargs: dict of matplotlib keywords associcated with plt.subplots e.g can be
586
    fig size, tight layout etc.
587
    :param line_kwargs: dict of matplotlib keywords associated with ax.hlines/ax.vlines
588
    :return: matplotlib axis and figure handles
589
    """
590

UNCOV
591
    if not isinstance(data, dict):
×
UNCOV
592
        data = data.convert2dict()
×
593

594
    if not ax:
×
595
        fig, ax = plt.subplots(figsize=(2, 8), **fig_kwargs)
×
596
    else:
UNCOV
597
        fig = plt.gcf()
×
598

NEW
599
    for x, y, dat in zip(data['data']['x'], data['data']['y'], data['data']['c']):
×
600
        im = NonUniformImage(ax, interpolation='nearest', cmap=data['cmap'])
×
601
        im.set_clim(data['clim'][0], data['clim'][1])
×
602
        im.set_data(x, y, dat.T)
×
603
        ax.add_image(im)
×
604

UNCOV
605
    ax.set_xlim(data['xlim'][0], data['xlim'][1])
×
606
    ax.set_ylim(data['ylim'][0], data['ylim'][1])
×
UNCOV
607
    ax.set_xlabel(data['labels']['xlabel'])
×
UNCOV
608
    ax.set_ylabel(data['labels']['ylabel'])
×
UNCOV
609
    ax.set_title(data['labels']['title'])
×
610

UNCOV
611
    if make_pretty:
×
UNCOV
612
        ax.get_xaxis().set_visible(False)
×
UNCOV
613
        ax.spines['right'].set_visible(False)
×
UNCOV
614
        ax.spines['top'].set_visible(False)
×
UNCOV
615
        ax.spines['bottom'].set_visible(False)
×
616

UNCOV
617
    if show_cbar:
×
NEW
618
        cbar = fig.colorbar(im, orientation='horizontal', pad=0.02, ax=ax)
×
UNCOV
619
        cbar.set_label(data['labels']['clabel'])
×
620

UNCOV
621
    ax = add_lines(ax, data, **line_kwargs)
×
622

623
    return ax, fig
×
624

625

626
def plot_line(data, ax=None, fig_kwargs=dict(), line_kwargs=dict()):
1✔
627
    """
628
    Function to create matplotlib plot from LinePlot object
629

630
    :param data: LinePlot object either class or dict
631
    :param ax: matplotlib axis to plot on
632
    :param fig_kwargs: dict of matplotlib keywords associcated with plt.subplots e.g can be
633
    fig size, tight layout etc.
634
    :param line_kwargs: dict of matplotlib keywords associated with ax.hlines/ax.vlines
635
    :return: matplotlib axis and figure handles
636
    """
637
    if not isinstance(data, dict):
×
638
        data = data.convert2dict()
×
639

640
    if not ax:
×
641
        fig, ax = plt.subplots(**fig_kwargs)
×
642
    else:
643
        fig = plt.gcf()
×
644

NEW
645
    ax.plot(
×
646
        data['data']['x'],
647
        data['data']['y'],
648
        color=data['line_color'],
649
        linestyle=data['line_style'],
650
        linewidth=data['line_width'],
651
        marker=data['marker_type'],
652
        markersize=data['marker_size'],
653
    )
654
    ax = add_lines(ax, data, **line_kwargs)
×
655

656
    ax.set_xlim(data['xlim'][0], data['xlim'][1])
×
657
    ax.set_ylim(data['ylim'][0], data['ylim'][1])
×
UNCOV
658
    ax.set_xlabel(data['labels']['xlabel'])
×
659
    ax.set_ylabel(data['labels']['ylabel'])
×
UNCOV
660
    ax.set_title(data['labels']['title'])
×
661

UNCOV
662
    return ax, fig
×
663

664

665
def scatter_xyc_plot(x, y, c, cmap=None, clim=None, rgb=False):
1✔
666
    """
667
    General function for preparing x y scatter plot with third variable encoded by colour of points
668
    :param x:
669
    :param y:
670
    :param c:
671
    :param cmap:
672
    :param clim:
673
    :param rgb: Whether to compute rgb (set True when preparing pyqtgraph data)
674
    :return:
675
    """
676

677
    data = ScatterPlot(x=x, y=y, c=c, cmap=cmap)
1✔
678
    data.set_clim(clim=clim)
1✔
679
    if rgb:
1✔
680
        norm = matplotlib.colors.Normalize(vmin=data.clim[0], vmax=data.clim[1], clip=True)
1✔
681
        mapper = cm.ScalarMappable(norm=norm, cmap=plt.get_cmap(cmap))
1✔
682
        cluster_color = np.array([mapper.to_rgba(col) for col in c])
1✔
683
        data.set_color(color=cluster_color)
1✔
684

685
    return data
1✔
686

687

688
def arrange_channels2banks(data, chn_coords, depth=None, pad=True, x_offset=1):
1✔
689
    """
690
    Rearranges data on channels so it matches geometry of probe. e.g For Neuropixel 2.0 rearranges
691
    channels into 4 banks with checkerboard pattern
692

693
    :param data: data on channels
694
    :param chn_coords: local coordinates of channels on probe
695
    :param depth: depth location of electrode (for example could be relative to bregma). If none
696
    given will stay in probe local coordinates
697
    :param pad: for matplotlib implementation with NonUniformImage we need to surround our data
698
    with nans so that it shows as finite display
699
    :param x_offset: spacing between banks in x direction
700
    :return: list, data, x position and y position for each bank
701
    """
702
    data_bank = []
1✔
703
    x_bank = []
1✔
704
    y_bank = []
1✔
705

706
    if depth is None:
1✔
707
        depth = chn_coords[:, 1]
1✔
708

709
    for iX, x in enumerate(np.unique(chn_coords[:, 0])):
1✔
710
        bnk_idx = np.where(chn_coords[:, 0] == x)[0]
1✔
711
        bnk_data = data[bnk_idx, np.newaxis].T
1✔
712
        # This is a hack! Although data is 1D we give it two x coords so we can correctly set
713
        # scale and extent (compatible with pyqtgraph and matplotlib.imshow)
714
        # For matplotlib.image.Nonuniformimage must use pad=True option
715
        bnk_x = np.array((iX * x_offset, (iX + 1) * x_offset))
1✔
716
        bnk_y = depth[bnk_idx]
1✔
717
        if pad:
1✔
718
            # pad data in y direction
719
            bnk_data = np.insert(bnk_data, 0, np.nan)
1✔
720
            bnk_data = np.append(bnk_data, np.nan)
1✔
721
            # pad data in x direction
722
            bnk_data = bnk_data[:, np.newaxis].T
1✔
723
            bnk_data = np.insert(bnk_data, 0, np.full(bnk_data.shape[1], np.nan), axis=0)
1✔
724
            bnk_data = np.append(bnk_data, np.full((1, bnk_data.shape[1]), np.nan), axis=0)
1✔
725

726
            # pad the x values
727
            bnk_x = np.arange(iX * x_offset, (iX + 3) * x_offset, x_offset)
1✔
728

729
            # pad the y values
730
            diff = np.diff(bnk_y)
1✔
731
            diff = diff[np.nonzero(diff)]
1✔
732

733
            bnk_y = np.insert(bnk_y, 0, bnk_y[0] - np.abs(diff[0]))
1✔
734
            bnk_y = np.append(bnk_y, bnk_y[-1] + np.abs(diff[-1]))
1✔
735

736
        data_bank.append(bnk_data)
1✔
737
        x_bank.append(bnk_x)
1✔
738
        y_bank.append(bnk_y)
1✔
739

740
    return data_bank, x_bank, y_bank
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc