Skip to content

suite2p.gui package#

Copyright © 2023 Howard Hughes Medical Institute, Authored by Carsen Stringer and Marius Pachitariu.

QuadButton #

Bases: QPushButton

custom QPushButton class for quadrant plotting requires buttons to put into a QButtonGroup (parent.quadbtns) allows only 1 button to pressed at a time

Source code in suite2p/gui/buttons.py
 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
class QuadButton(QPushButton):
    """ custom QPushButton class for quadrant plotting
        requires buttons to put into a QButtonGroup (parent.quadbtns)
         allows only 1 button to pressed at a time
    """

    def __init__(self, bid, Text, parent=None):
        super(QuadButton, self).__init__(parent)
        self.setText(Text)
        self.setCheckable(True)
        self.setFont(QtGui.QFont("Arial", 8, QtGui.QFont.Bold))
        self.resize(self.minimumSizeHint())
        self.setMaximumWidth(22)
        self.xpos = bid % 3
        self.ypos = int(np.floor(bid / 3))
        self.clicked.connect(lambda: self.press(parent, bid))
        self.show()

    def press(self, parent, bid):
        self.xrange = np.array([self.xpos - .15, self.xpos + 1.15
                               ]) * parent.ops["Lx"] / 3
        self.yrange = np.array([self.ypos - .15, self.ypos + 1.15
                               ]) * parent.ops["Ly"] / 3
        # change the zoom
        parent.p1.setXRange(self.xrange[0], self.xrange[1])
        parent.p1.setYRange(self.yrange[0], self.yrange[1])
        parent.p2.setXRange(self.xrange[0], self.xrange[1])
        parent.p2.setYRange(self.yrange[0], self.yrange[1])
        parent.p2.setXLink("plot1")
        parent.p2.setYLink("plot1")
        parent.show()

SizeButton #

Bases: QPushButton

buttons to make trace box bigger or smaller

Source code in suite2p/gui/buttons.py
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
class SizeButton(QPushButton):
    """ buttons to make trace box bigger or smaller """

    def __init__(self, bid, Text, parent=None):
        super(SizeButton, self).__init__(parent)
        self.setText(Text)
        self.setCheckable(True)
        self.setFont(QtGui.QFont("Arial", 8, QtGui.QFont.Bold))
        self.resize(self.minimumSizeHint())
        self.clicked.connect(lambda: self.press(parent))
        self.bid = bid
        self.show()

    def press(self, parent):
        bid = self.bid
        ts = 100
        if bid == 0:
            parent.p2.linkView(parent.p2.XAxis, view=None)
            parent.p2.linkView(parent.p2.YAxis, view=None)
            parent.win.ci.layout.setColumnStretchFactor(0, ts)
            parent.win.ci.layout.setColumnStretchFactor(1, 0)
        elif bid == 1:
            parent.win.ci.layout.setColumnStretchFactor(0, ts)
            parent.win.ci.layout.setColumnStretchFactor(1, ts)
            parent.p2.setXLink("plot1")
            parent.p2.setYLink("plot1")
        elif bid == 2:
            parent.p2.linkView(parent.p2.XAxis, view=None)
            parent.p2.linkView(parent.p2.YAxis, view=None)
            parent.win.ci.layout.setColumnStretchFactor(0, 0)
            parent.win.ci.layout.setColumnStretchFactor(1, ts)
        # only enable selection buttons when not in "both" view
        if bid != 1:
            if parent.ops_plot["color"] != 0:
                for btn in parent.topbtns.buttons():
                    btn.setEnabled(True)
            else:
                parent.topbtns.button(0).setEnabled(True)
        else:
            parent.ROI_remove()
            for btn in parent.topbtns.buttons():
                btn.setEnabled(False)
        parent.win.show()
        parent.show()

TopButton #

Bases: QPushButton

selection of top neurons

Source code in suite2p/gui/buttons.py
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
215
216
217
218
219
220
221
222
223
224
225
226
class TopButton(QPushButton):
    """ selection of top neurons"""

    def __init__(self, bid, parent=None):
        super(TopButton, self).__init__(parent)
        text = [" draw selection", " select top n", " select bottom n"]
        self.bid = bid
        self.setText(text[bid])
        self.setCheckable(True)
        self.setFont(QtGui.QFont("Arial", 8, QtGui.QFont.Bold))
        self.resize(self.minimumSizeHint())
        self.clicked.connect(lambda: self.press(parent))
        self.show()

    def press(self, parent):
        bid = self.bid
        if not parent.sizebtns.button(1).isChecked():
            if parent.ops_plot["color"] == 0:
                for b in [1, 2]:
                    parent.topbtns.button(b).setEnabled(False)
            else:
                for b in [1, 2]:
                    parent.topbtns.button(b).setEnabled(True)
        else:
            for b in range(3):
                parent.topbtns.button(b).setEnabled(False)
        if bid == 0:
            parent.ROI_selection()
        else:
            self.top_selection(parent)

    def top_selection(self, parent):
        bid = self.bid
        parent.ROI_remove()
        draw = False
        ncells = len(parent.stat)
        icells = np.minimum(ncells, parent.ntop)
        if bid == 1:
            top = True
        elif bid == 2:
            top = False
        if parent.sizebtns.button(0).isChecked():
            wplot = 0
            draw = True
        elif parent.sizebtns.button(2).isChecked():
            wplot = 1
            draw = True
        if draw:
            if parent.ops_plot["color"] != 0:
                c = parent.ops_plot["color"]
                istat = parent.colors["istat"][c]
                if wplot == 0:
                    icell = np.array(parent.iscell.nonzero()).flatten()
                    istat = istat[parent.iscell]
                else:
                    icell = np.array((~parent.iscell).nonzero()).flatten()
                    istat = istat[~parent.iscell]
                inds = istat.argsort()
                if top:
                    inds = inds[-icells:]
                    parent.ichosen = icell[inds[-1]]
                else:
                    inds = inds[:icells]
                    parent.ichosen = icell[inds[0]]
                parent.imerge = []
                for n in inds:
                    parent.imerge.append(icell[n])
                # draw choices
                parent.update_plot()
                parent.show()

make_cellnotcell #

make_cellnotcell(parent)

buttons for cell / not cell views at top

Source code in suite2p/gui/buttons.py
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
def make_cellnotcell(parent):
    """ buttons for cell / not cell views at top """
    # number of ROIs in each image
    parent.lcell0 = QLabel("")
    parent.lcell0.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
    parent.l0.addWidget(parent.lcell0, 0, 12, 1, 2)
    parent.lcell1 = QLabel("")
    parent.l0.addWidget(parent.lcell1, 0, 20, 1, 2)

    parent.sizebtns = QButtonGroup(parent)
    b = 0
    labels = [" cells", " both", " not cells"]
    for l in labels:
        btn = SizeButton(b, l, parent)
        parent.sizebtns.addButton(btn, b)
        parent.l0.addWidget(btn, 0, 14 + 2 * b, 1, 2)
        btn.setEnabled(False)
        if b == 1:
            btn.setChecked(True)
        b += 1
    parent.sizebtns.setExclusive(True)

make_quadrants #

make_quadrants(parent)

make quadrant buttons

Source code in suite2p/gui/buttons.py
63
64
65
66
67
68
69
70
71
72
73
def make_quadrants(parent):
    """ make quadrant buttons """
    parent.quadbtns = QButtonGroup(parent)
    for b in range(9):
        btn = QuadButton(b, " " + str(b + 1), parent)
        parent.quadbtns.addButton(btn, b)
        parent.l0.addWidget(btn, 0 + parent.quadbtns.button(b).ypos,
                            29 + parent.quadbtns.button(b).xpos, 1, 1)
        btn.setEnabled(False)
        b += 1
    parent.quadbtns.setExclusive(True)

make_selection #

make_selection(parent)

buttons to draw a square on view

Source code in suite2p/gui/buttons.py
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
def make_selection(parent):
    """ buttons to draw a square on view """
    parent.topbtns = QButtonGroup()
    ql = QLabel("select cells")
    ql.setFont(QtGui.QFont("Arial", 8, QtGui.QFont.Bold))
    parent.l0.addWidget(ql, 0, 2, 1, 2)
    pos = [2, 3, 4]
    for b in range(3):
        btn = TopButton(b, parent)
        btn.setFont(QtGui.QFont("Arial", 8))
        parent.topbtns.addButton(btn, b)
        parent.l0.addWidget(btn, 0, (pos[b]) * 2, 1, 2)
        btn.setEnabled(False)
    parent.topbtns.setExclusive(True)
    parent.isROI = False
    parent.ROIplot = 0
    ql = QLabel("n=")
    ql.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
    ql.setFont(QtGui.QFont("Arial", 8, QtGui.QFont.Bold))
    parent.l0.addWidget(ql, 0, 10, 1, 1)
    parent.topedit = QLineEdit(parent)
    parent.topedit.setValidator(QtGui.QIntValidator(0, 500))
    parent.topedit.setText("40")
    parent.ntop = 40
    parent.topedit.setFixedWidth(35)
    parent.topedit.setAlignment(QtCore.Qt.AlignRight)
    parent.topedit.returnPressed.connect(parent.top_number_chosen)
    parent.l0.addWidget(parent.topedit, 0, 11, 1, 1)

Copyright © 2023 Howard Hughes Medical Institute, Authored by Carsen Stringer and Marius Pachitariu.

Copyright © 2023 Howard Hughes Medical Institute, Authored by Carsen Stringer and Marius Pachitariu.

masks_and_traces #

masks_and_traces(settings, stat_manual, stat_orig)

main extraction function inputs: settings and stat creates cell and neuropil masks and extracts traces returns: F (ROIs x time), Fneu (ROIs x time), F_chan2, Fneu_chan2, settings, stat F_chan2 and Fneu_chan2 will be empty if no second channel

Source code in suite2p/gui/drawroi.py
 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
 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
def masks_and_traces(settings, stat_manual, stat_orig):
    """ main extraction function
        inputs: settings and stat
        creates cell and neuropil masks and extracts traces
        returns: F (ROIs x time), Fneu (ROIs x time), F_chan2, Fneu_chan2, settings, stat
        F_chan2 and Fneu_chan2 will be empty if no second channel
    """
    # Merge with defaults to ensure all required keys are present
    settings = {**default_settings(), **settings}

    t0 = time.time()

    # Concatenate stat so a good neuropil function can be formed
    stat_all = stat_manual.copy()
    for n in range(len(stat_orig)):
        stat_all.append(stat_orig[n])

    stat_all = np.array(stat_all)
    stat_all = roi_stats(stat_all, settings["Ly"], settings["Lx"],
                         diameter=settings["diameter"])
    cell_masks = [
        masks.create_cell_mask(stat, Ly=settings["Ly"], Lx=settings["Lx"],
                               allow_overlap=settings["extraction"]["allow_overlap"]) for stat in stat_all
    ]
    cell_pix = masks.create_cell_pix(stat_all, Ly=settings["Ly"], Lx=settings["Lx"])
    manual_roi_stats = stat_all[:len(stat_manual)]
    manual_cell_masks = cell_masks[:len(stat_manual)]
    manual_neuropil_masks = masks.create_neuropil_masks(
        ypixs=[stat["ypix"] for stat in manual_roi_stats],
        xpixs=[stat["xpix"] for stat in manual_roi_stats],
        cell_pix=cell_pix,
        inner_neuropil_radius=settings["extraction"]["inner_neuropil_radius"],
        min_neuropil_pixels=settings["extraction"]["min_neuropil_pixels"],
    )
    print("Masks made in %0.2f sec." % (time.time() - t0))

    # Extract traces from binary file
    Ly, Lx = settings["Ly"], settings["Lx"]
    batch_size = settings["extraction"]["batch_size"]
    device = _assign_torch_device(settings["torch_device"])
    f_reg = BinaryFile(Ly, Lx, settings["reg_file"])
    F, Fneu = extract_traces(f_reg, manual_cell_masks, manual_neuropil_masks, batch_size=batch_size, device=device)
    f_reg.close()

    # Handle chan2 if present
    if "reg_file_chan2" in settings and settings["reg_file_chan2"]:
        f_reg_chan2 = BinaryFile(Ly, Lx, settings["reg_file_chan2"])
        F_chan2, Fneu_chan2 = extract_traces(f_reg_chan2, manual_cell_masks, manual_neuropil_masks, batch_size=batch_size, device=device)
        f_reg_chan2.close()
    else:
        F_chan2, Fneu_chan2 = None, None

    # compute activity statistics for classifier
    npix = np.array([stat_orig[n]["npix"] for n in range(len(stat_orig))
                    ]).astype("float32")
    for n in range(len(manual_roi_stats)):
        manual_roi_stats[n]["npix_norm"] = manual_roi_stats[n]["npix"] / np.mean(
            npix[:100])  # What if there are less than 100 cells?
        manual_roi_stats[n]["compact"] = 1
        manual_roi_stats[n]["footprint"] = 2
        manual_roi_stats[n]["manual"] = 1  # Add manual key
        if "iplane" in stat_orig[0]:
            manual_roi_stats[n]["iplane"] = stat_orig[0]["iplane"]

    # subtract neuropil and compute skew, std from F
    dF = F - settings["extraction"]["neuropil_coefficient"] * Fneu
    sk = stats.skew(dF, axis=1)
    sd = np.std(dF, axis=1)

    for n in range(F.shape[0]):
        manual_roi_stats[n]["skew"] = sk[n]
        manual_roi_stats[n]["std"] = sd[n]
        manual_roi_stats[n]["med"] = [
            np.mean(manual_roi_stats[n]["ypix"]),
            np.mean(manual_roi_stats[n]["xpix"])
        ]

    dF = preprocess(F=dF, baseline=settings["dcnv_preprocess"]["baseline"], win_baseline=settings["dcnv_preprocess"]["win_baseline"],
                    sig_baseline=settings["dcnv_preprocess"]["sig_baseline"], fs=settings["fs"],
                    prctile_baseline=settings["dcnv_preprocess"]["prctile_baseline"], device=device)
    spks = oasis(F=dF, batch_size=settings["extraction"]["batch_size"], tau=settings["tau"], fs=settings["fs"])

    return F, Fneu, F_chan2, Fneu_chan2, spks, settings, manual_roi_stats

Copyright © 2023 Howard Hughes Medical Institute, Authored by Carsen Stringer and Marius Pachitariu.

ROI_index #

ROI_index(settings, stat)

matrix Ly x Lx where each pixel is an ROI index (-1 if no ROI present)

Source code in suite2p/gui/graphics.py
119
120
121
122
123
124
125
126
127
128
129
130
def ROI_index(settings, stat):
    """matrix Ly x Lx where each pixel is an ROI index (-1 if no ROI present)"""
    ncells = len(stat) - 1
    Ly = settings["Ly"]
    Lx = settings["Lx"]
    iROI = -1 * np.ones((Ly, Lx), dtype=np.int32)
    for n in range(ncells):
        ypix = stat[n]["ypix"][~stat[n]["overlap"]]
        if ypix is not None:
            xpix = stat[n]["xpix"][~stat[n]["overlap"]]
            iROI[ypix, xpix] = n
    return iROI

Copyright © 2023 Howard Hughes Medical Institute, Authored by Carsen Stringer and Marius Pachitariu.

MainWindow #

Bases: QMainWindow

Source code in suite2p/gui/gui2p.py
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 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
 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
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
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
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
374
375
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
420
421
422
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
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
class MainWindow(QMainWindow):

    def __init__(self, statfile=None):
        super(MainWindow, self).__init__()
        import suite2p
        s2p_dir = pathlib.Path(suite2p.__file__).parent
        ### first time running, need to check for user files
        user_dir = pathlib.Path.home().joinpath(".suite2p")
        user_dir.mkdir(exist_ok=True)

        pg.setConfigOptions(imageAxisOrder="row-major")

        self.setGeometry(50, 50, 1500, 800)
        self.setWindowTitle("suite2p (run pipeline or load stat.npy)")
        icon_path = os.fspath(s2p_dir.joinpath("logo", "logo.png"))

        app_icon = QtGui.QIcon()
        app_icon.addFile(icon_path, QtCore.QSize(16, 16))
        app_icon.addFile(icon_path, QtCore.QSize(24, 24))
        app_icon.addFile(icon_path, QtCore.QSize(32, 32))
        app_icon.addFile(icon_path, QtCore.QSize(48, 48))
        app_icon.addFile(icon_path, QtCore.QSize(64, 64))
        app_icon.addFile(icon_path, QtCore.QSize(256, 256))
        self.setWindowIcon(app_icon)
        #self.setStyleSheet("QMainWindow {background: 'black';}")
        self.stylePressed = ("QPushButton {Text-align: left; "
                             "background-color: rgb(100,50,100); "
                             "color:white;}")
        self.styleUnpressed = ("QPushButton {Text-align: left; "
                               "background-color: rgb(50,50,50); "
                               "color:white;}")
        self.styleInactive = ("QPushButton {Text-align: left; "
                              "background-color: rgb(50,50,50); "
                              "color:gray;}")
        self.setStyleSheet(utils.stylesheet())
        self.loaded = False
        self.ops_plot = []


        # check for classifier file
        class_dir = user_dir.joinpath("classifiers")
        class_dir.mkdir(exist_ok=True)
        self.classuser = os.fspath(class_dir.joinpath("classifier_user.npy"))
        self.classorig = os.fspath(s2p_dir.joinpath("classifiers", "classifier.npy"))
        if not os.path.isfile(self.classuser):
            shutil.copy(self.classorig, self.classuser)
        self.classfile = self.classuser

        # check for settings file (for running suite2p)
        settings_dir = user_dir.joinpath("settings")
        settings_dir.mkdir(exist_ok=True)
        self.opsuser = os.fspath(settings_dir.joinpath("settings_user.npy"))
        if not os.path.isfile(self.opsuser):
            np.save(self.opsuser, default_settings())
        self.opsfile = self.opsuser

        menus.mainmenu(self)
        menus.classifier(self)
        menus.visualizations(self)
        menus.registration(self)
        menus.mergebar(self)
        menus.plugins(self)

        self.boldfont = QtGui.QFont("Arial", 10, QtGui.QFont.Bold)

        # default plot options
        self.ops_plot = {
            "ROIs_on": True,
            "color": 0,
            "view": 0,
            "opacity": [127, 255],
            "saturation": [0, 255],
            "colormap": "hsv"
        }
        self.rois = {"iROI": 0, "Sroi": 0, "Lam": 0, "LamMean": 0, "LamNorm": 0}
        self.colors = {"RGB": 0, "cols": 0, "colorbar": []}

        # --------- MAIN WIDGET LAYOUT ---------------------
        cwidget = QWidget()
        self.l0 = QGridLayout()
        cwidget.setLayout(self.l0)
        self.setCentralWidget(cwidget)

        b0 = self.make_buttons()
        self.make_graphics(b0)
        # so they"re on top of plot, draw last
        buttons.make_quadrants(self)

        # initialize merges
        self.merged = []
        self.imerge = [0]
        self.ichosen = 0
        self.rastermap = False
        model = np.load(self.classorig, allow_pickle=True).item()
        self.default_keys = model["keys"]


        # load initial file
        if statfile is not None:
            self.fname = statfile
            io.load_proc(self)
            #self.manual_label()
        self.setAcceptDrops(True)
        self.show()
        self.win.show()

        #RW = rungui.RunWindow(self)
        #RW.show()

    def dragEnterEvent(self, event):
        if event.mimeData().hasUrls():
            event.accept()
        else:
            event.ignore()

    def dropEvent(self, event):
        files = [u.toLocalFile() for u in event.mimeData().urls()]
        print(files)
        self.fname = files[0]
        if os.path.splitext(self.fname)[-1] == ".npy":
            io.load_proc(self)
        elif os.path.splitext(self.fname)[-1] == ".nwb":
            io.load_NWB(self)
        else:
            print("invalid extension %s, use .nwb or .npy" %
                  os.path.splitext(self.fname)[-1])

    def make_buttons(self):
        # ROI CHECKBOX
        self.l0.setVerticalSpacing(4)
        self.checkBox = QCheckBox("ROIs On [space bar]")
        self.checkBox.setStyleSheet("color: white;")
        self.checkBox.toggle()
        self.checkBox.stateChanged.connect(self.ROIs_on)
        self.l0.addWidget(self.checkBox, 0, 0, 1, 2)

        buttons.make_selection(self)
        buttons.make_cellnotcell(self)
        b0 = views.make_buttons(self)  # b0 says how many
        b0 = masks.make_buttons(self, b0)
        masks.make_colorbar(self, b0)
        b0 += 1
        b0 = classgui.make_buttons(self, b0)
        b0 += 1

        # ------ CELL STATS / ROI SELECTION --------
        # which stats
        self.stats_to_show = [
            "med", "npix_norm", "skew", "compact", "snr", "aspect_ratio"
        ]
        lilfont = QtGui.QFont("Arial", 8)
        qlabel = QLabel(self)
        qlabel.setFont(self.boldfont)
        qlabel.setText("<font color='white'>Selected ROI:</font>")
        self.l0.addWidget(qlabel, b0, 0, 1, 1)
        self.ROIedit = QLineEdit(self)
        self.ROIedit.setValidator(QtGui.QIntValidator(0, 10000))
        self.ROIedit.setText("0")
        self.ROIedit.setFixedWidth(45)
        self.ROIedit.setAlignment(QtCore.Qt.AlignRight)
        self.ROIedit.returnPressed.connect(self.number_chosen)
        self.l0.addWidget(self.ROIedit, b0, 1, 1, 1)
        b0 += 1
        self.ROIstats = []
        self.ROIstats.append(qlabel)
        for k in range(1, len(self.stats_to_show) + 1):
            llabel = QLabel(self.stats_to_show[k - 1])
            self.ROIstats.append(llabel)
            self.ROIstats[k].setFont(lilfont)
            self.ROIstats[k].setStyleSheet("color: white;")
            self.ROIstats[k].resize(self.ROIstats[k].minimumSizeHint())
            self.l0.addWidget(self.ROIstats[k], b0, 0, 1, 2)
            b0 += 1
        self.l0.addWidget(QLabel(""), b0, 0, 1, 2)
        self.l0.setRowStretch(b0, 1)
        b0 += 2
        b0 = traces.make_buttons(self, b0)

        # zoom to cell CHECKBOX
        self.l0.setVerticalSpacing(4)
        self.checkBoxz = QCheckBox("zoom to cell")
        self.checkBoxz.setStyleSheet("color: white;")
        self.zoomtocell = False
        self.checkBoxz.stateChanged.connect(self.zoom_cell)
        self.l0.addWidget(self.checkBoxz, b0, 15, 1, 2)

        self.checkBoxN = QCheckBox("add ROI # to plot")
        self.checkBoxN.setStyleSheet("color: white;")
        self.roitext = False
        self.checkBoxN.stateChanged.connect(self.roi_text)
        self.checkBoxN.setEnabled(False)
        self.l0.addWidget(self.checkBoxN, b0, 18, 1, 2)

        return b0

    def roi_text(self, state):
        if QtCore.Qt.CheckState(state) == QtCore.Qt.Checked:
            for n in range(len(self.roi_text_labels)):
                if self.iscell[n] == 1:
                    self.p1.addItem(self.roi_text_labels[n])
                else:
                    self.p2.addItem(self.roi_text_labels[n])
            self.roitext = True
        else:
            for n in range(len(self.roi_text_labels)):
                if self.iscell[n] == 1:
                    try:
                        self.p1.removeItem(self.roi_text_labels[n])
                    except:
                        pass
                else:
                    try:
                        self.p2.removeItem(self.roi_text_labels[n])
                    except:
                        pass

            self.roitext = False

    def zoom_cell(self, state):
        if self.loaded:
            if QtCore.Qt.CheckState(state) == QtCore.Qt.Checked:
                self.zoomtocell = True
            else:
                self.zoomtocell = False
            self.update_plot()

    def make_graphics(self, b0):
        ##### -------- MAIN PLOTTING AREA ---------- ####################
        self.win = pg.GraphicsLayoutWidget()
        self.win.move(600, 0)
        self.win.resize(1000, 500)
        self.l0.addWidget(self.win, 1, 2, b0 - 1, 30)
        layout = self.win.ci.layout
        # --- cells image
        self.p1 = graphics.ViewBox(parent=self, lockAspect=True, name="plot1",
                                   border=[100, 100, 100], invertY=True)
        self.win.addItem(self.p1, 0, 0)
        self.p1.setMenuEnabled(False)
        self.p1.scene().contextMenuItem = self.p1
        self.view1 = pg.ImageItem(viewbox=self.p1, parent=self)
        self.view1.autoDownsample = False
        self.color1 = pg.ImageItem(viewbox=self.p1, parent=self)
        self.color1.autoDownsample = False
        self.p1.addItem(self.view1)
        self.p1.addItem(self.color1)
        self.view1.setLevels([0, 255])
        self.color1.setLevels([0, 255])
        #self.view1.setImage(np.random.rand(500,500,3))
        #x = np.arange(0,500)
        #img = np.concatenate((np.zeros((500,500,3)), 127*(1+np.tile(np.sin(x/100)[:,np.newaxis,np.newaxis],(1,500,1)))),axis=-1)
        #self.color1.setImage(img)
        # --- noncells image
        self.p2 = graphics.ViewBox(parent=self, lockAspect=True, name="plot2",
                                   border=[100, 100, 100], invertY=True)
        self.win.addItem(self.p2, 0, 1)
        self.p2.setMenuEnabled(False)
        self.p2.scene().contextMenuItem = self.p2
        self.view2 = pg.ImageItem(viewbox=self.p1, parent=self)
        self.view2.autoDownsample = False
        self.color2 = pg.ImageItem(viewbox=self.p1, parent=self)
        self.color2.autoDownsample = False
        self.p2.addItem(self.view2)
        self.p2.addItem(self.color2)
        self.view2.setLevels([0, 255])
        self.color2.setLevels([0, 255])

        # LINK TWO VIEWS!
        self.p2.setXLink("plot1")
        self.p2.setYLink("plot1")

        # --- fluorescence trace plot
        self.p3 = graphics.TraceBox(parent=self, invertY=False)
        self.p3.setMouseEnabled(x=True, y=False)
        self.p3.enableAutoRange(x=True, y=True)
        self.win.addItem(self.p3, row=1, col=0, colspan=2)
        #self.p3 = pg.PlotItem()
        #self.v3.addItem(self.p3)
        self.win.ci.layout.setRowStretchFactor(0, 2)
        layout = self.win.ci.layout
        layout.setColumnMinimumWidth(0, 1)
        layout.setColumnMinimumWidth(1, 1)
        layout.setHorizontalSpacing(20)
        #self.win.scene().sigMouseClicked.connect(self.plot_clicked)

    def keyPressEvent(self, event):
        if self.loaded:
            if event.modifiers() != QtCore.Qt.ControlModifier and event.modifiers(
            ) != QtCore.Qt.ShiftModifier:
                if event.key() == QtCore.Qt.Key_Return:
                    if event.modifiers() == QtCore.Qt.AltModifier:
                        if len(self.imerge) > 1:
                            merge.do_merge(self)
                elif event.key() == QtCore.Qt.Key_Escape:
                    self.zoom_plot(1)
                    self.zoom_plot(3)
                    self.show()
                elif event.key() == QtCore.Qt.Key_Delete:
                    self.ROI_remove()
                elif event.key() == QtCore.Qt.Key_Q:
                    self.viewbtns.button(0).setChecked(True)
                    self.viewbtns.button(0).press(self, 0)
                elif event.key() == QtCore.Qt.Key_W:
                    self.viewbtns.button(1).setChecked(True)
                    self.viewbtns.button(1).press(self, 1)
                elif event.key() == QtCore.Qt.Key_E:
                    self.viewbtns.button(2).setChecked(True)
                    self.viewbtns.button(2).press(self, 2)
                elif event.key() == QtCore.Qt.Key_R:
                    self.viewbtns.button(3).setChecked(True)
                    self.viewbtns.button(3).press(self, 3)
                elif event.key() == QtCore.Qt.Key_T:
                    self.viewbtns.button(4).setChecked(True)
                    self.viewbtns.button(4).press(self, 4)
                elif event.key() == QtCore.Qt.Key_U:
                    if "meanImg_chan2" in self.ops:
                        self.viewbtns.button(6).setChecked(True)
                        self.viewbtns.button(6).press(self, 6)
                elif event.key() == QtCore.Qt.Key_Y:
                    if "meanImg_chan2_corrected" in self.ops:
                        self.viewbtns.button(5).setChecked(True)
                        self.viewbtns.button(5).press(self, 5)
                elif event.key() == QtCore.Qt.Key_Space:
                    self.checkBox.toggle()
                #Agus
                elif event.key() == QtCore.Qt.Key_N:
                    self.checkBoxd.toggle()
                elif event.key() == QtCore.Qt.Key_B:
                    self.checkBoxn.toggle()
                elif event.key() == QtCore.Qt.Key_V:
                    self.checkBoxt.toggle()
                #
                elif event.key() == QtCore.Qt.Key_A:
                    self.colorbtns.button(0).setChecked(True)
                    self.colorbtns.button(0).press(self, 0)
                elif event.key() == QtCore.Qt.Key_S:
                    self.colorbtns.button(1).setChecked(True)
                    self.colorbtns.button(1).press(self, 1)
                elif event.key() == QtCore.Qt.Key_D:
                    self.colorbtns.button(2).setChecked(True)
                    self.colorbtns.button(2).press(self, 2)
                elif event.key() == QtCore.Qt.Key_F:
                    self.colorbtns.button(3).setChecked(True)
                    self.colorbtns.button(3).press(self, 3)
                elif event.key() == QtCore.Qt.Key_G:
                    self.colorbtns.button(4).setChecked(True)
                    self.colorbtns.button(4).press(self, 4)
                elif event.key() == QtCore.Qt.Key_H:
                    if self.hasred:
                        self.colorbtns.button(5).setChecked(True)
                        self.colorbtns.button(5).press(self, 5)
                elif event.key() == QtCore.Qt.Key_J:
                    self.colorbtns.button(6).setChecked(True)
                    self.colorbtns.button(6).press(self, 6)
                elif event.key() == QtCore.Qt.Key_K:
                    self.colorbtns.button(7).setChecked(True)
                    self.colorbtns.button(7).press(self, 7)
                elif event.key() == QtCore.Qt.Key_L:
                    if self.bloaded:
                        self.colorbtns.button(8).setChecked(True)
                        self.colorbtns.button(8).press(self, 8)
                elif event.key() == QtCore.Qt.Key_M:
                    if self.rastermap:
                        self.colorbtns.button(9).setChecked(True)
                        self.colorbtns.button(9).press(self, 9)
                elif event.key() == QtCore.Qt.Key_Left:
                    ctype = self.iscell[self.ichosen]
                    while -1:
                        self.ichosen = (self.ichosen - 1) % len(self.stat)
                        if self.iscell[self.ichosen] is ctype:
                            break
                    self.imerge = [self.ichosen]
                    self.ROI_remove()
                    self.update_plot()

                elif event.key() == QtCore.Qt.Key_Right:
                    ##Agus
                    self.ROI_remove()
                    ctype = self.iscell[self.ichosen]
                    while 1:
                        self.ichosen = (self.ichosen + 1) % len(self.stat)
                        if self.iscell[self.ichosen] is ctype:
                            break
                    self.imerge = [self.ichosen]
                    self.update_plot()
                    self.show()
                ##Agus
                elif event.key() == QtCore.Qt.Key_Up:
                    masks.flip_plot(self)
                    self.ROI_remove()

    def update_plot(self):
        if self.ops_plot["color"] == 7:
            masks.corr_masks(self)
        masks.plot_colorbar(self)
        self.ichosen_stats()
        views.plot_views(self)
        M = masks.draw_masks(self)
        masks.plot_masks(self, M)
        traces.plot_trace(self)
        if self.zoomtocell:
            self.zoom_to_cell()
        self.p1.show()
        self.p2.show()
        self.win.show()
        self.show()

    def mode_change(self, i):
        """

            changes the activity mode that is used when multiple neurons are selected
            or in visualization windows like rastermap or for correlation computation!

            activityMode =
            0 : F
            1 : Fneu
            2 : F - 0.7 * Fneu (default)
            3 : spks

            uses binning set by self.bin

        """
        self.activityMode = i
        if self.loaded:
            # activity used for correlations
            self.bin = max(1, int(self.binedit.text()))
            nb = int(np.floor(float(self.Fcell.shape[1]) / float(self.bin)))
            if i == 0:
                f = self.Fcell
            elif i == 1:
                f = self.Fneu
            elif i == 2:
                f = self.Fcell - 0.7 * self.Fneu
            else:
                f = self.Spks
            ncells = len(self.stat)
            self.Fbin = f[:, :nb * self.bin].reshape(
                (ncells, nb, self.bin)).mean(axis=2)

            self.Fbin -= self.Fbin.mean(axis=1)[:, np.newaxis]
            self.Fstd = (self.Fbin**2).mean(axis=1)**0.5
            self.trange = np.arange(0, self.Fcell.shape[1])
            # if in behavior-view, recompute
            if self.ops_plot["color"] == 8:
                masks.beh_masks(self)
                masks.plot_colorbar(self)
            self.update_plot()

    def top_number_chosen(self):
        self.ntop = int(self.topedit.text())
        if self.loaded:
            if not self.sizebtns.button(1).isChecked():
                for b in [1, 2]:
                    if self.topbtns.button(b).isChecked():
                        self.topbtns.button(b).top_selection(self)
                        self.show()

    def ROI_selection(self):
        draw = False
        if self.sizebtns.button(0).isChecked():
            wplot = 0
            view = self.p1.viewRange()
            draw = True
        elif self.sizebtns.button(2).isChecked():
            wplot = 1
            view = self.p2.viewRange()
            draw = True
        if draw:
            self.ROI_remove()
            self.topbtns.button(0).setStyleSheet(self.stylePressed)
            self.ROIplot = wplot
            imx = (view[0][1] + view[0][0]) / 2
            imy = (view[1][1] + view[1][0]) / 2
            dx = (view[0][1] - view[0][0]) / 4
            dy = (view[1][1] - view[1][0]) / 4
            dx = np.minimum(dx, 300)
            dy = np.minimum(dy, 300)
            imx = imx - dx / 2
            imy = imy - dy / 2
            self.ROI = pg.RectROI([imx, imy], [dx, dy], pen="w", sideScalers=True)
            if wplot == 0:
                self.p1.addItem(self.ROI)
            else:
                self.p2.addItem(self.ROI)
            self.ROI_position()
            self.ROI.sigRegionChangeFinished.connect(self.ROI_position)
            self.isROI = True

    def ROI_remove(self):
        if self.isROI:
            if self.ROIplot == 0:
                self.p1.removeItem(self.ROI)
            else:
                self.p2.removeItem(self.ROI)
            self.isROI = False
        if self.sizebtns.button(1).isChecked():
            self.topbtns.button(0).setStyleSheet(self.styleInactive)
            self.topbtns.button(0).setEnabled(False)
        else:
            self.topbtns.button(0).setStyleSheet(self.styleUnpressed)

    def ROI_position(self):
        pos0 = self.ROI.getSceneHandlePositions()
        if self.ROIplot == 0:
            pos = self.p1.mapSceneToView(pos0[0][1])
        else:
            pos = self.p2.mapSceneToView(pos0[0][1])
        posy = pos.y()
        posx = pos.x()
        sizex, sizey = self.ROI.size()
        xrange = (np.arange(-1 * int(sizex), 1) + int(posx)).astype(np.int32)
        yrange = (np.arange(-1 * int(sizey), 1) + int(posy)).astype(np.int32)
        xrange = xrange[xrange >= 0]
        xrange = xrange[xrange < self.ops["Lx"]]
        yrange = yrange[yrange >= 0]
        yrange = yrange[yrange < self.ops["Ly"]]
        ypix, xpix = np.meshgrid(yrange, xrange)
        self.select_cells(ypix, xpix)

    def select_cells(self, ypix, xpix):
        i = self.ROIplot
        iROI0 = self.rois["iROI"][i, 0, ypix, xpix]
        icells = np.unique(iROI0[iROI0 >= 0])
        self.imerge = []
        for n in icells:
            if (self.rois["iROI"][i, :, ypix,
                                  xpix] == n).sum() > 0.6 * self.stat[n]["npix"]:
                self.imerge.append(n)
        if len(self.imerge) > 0:
            self.ichosen = self.imerge[0]
            self.update_plot()
            self.show()

    def number_chosen(self):
        if self.loaded:
            self.ichosen = int(self.ROIedit.text())
            if self.ichosen >= len(self.stat):
                self.ichosen = len(self.stat) - 1
            self.imerge = [self.ichosen]
            self.update_plot()
            self.show()

    def ROIs_on(self, state):
        if QtCore.Qt.CheckState(state) == QtCore.Qt.Checked:
            self.ops_plot["ROIs_on"] = True
            self.p1.addItem(self.color1)
            self.p2.addItem(self.color2)
        else:
            self.ops_plot["ROIs_on"] = False
            self.p1.removeItem(self.color1)
            self.p2.removeItem(self.color2)
        self.win.show()
        self.show()

    def plot_clicked(self, event):
        """left-click chooses a cell, right-click flips cell to other view"""
        flip = False
        choose = False
        zoom = False
        replot = False
        items = self.win.scene().items(event.scenePos())
        posx = 0
        posy = 0
        iplot = 0
        if self.loaded:
            # print(event.modifiers() == QtCore.Qt.ControlModifier)
            for x in items:
                if x == self.img1:
                    pos = self.p1.mapSceneToView(event.scenePos())
                    posy = pos.x()
                    posx = pos.y()
                    iplot = 1
                elif x == self.img2:
                    pos = self.p2.mapSceneToView(event.scenePos())
                    posy = pos.x()
                    posx = pos.y()
                    iplot = 2
                elif x == self.p3:
                    iplot = 3
                elif ((x == self.p1 or x == self.p2) and x != self.img1 and
                      x != self.img2):
                    iplot = 4
                    if event.double():
                        zoom = True
                if iplot == 1 or iplot == 2:
                    if event.button() == QtCore.Qt.RightButton:
                        flip = True
                    elif event.button() == QtCore.Qt.LeftButton:
                        if event.double():
                            zoom = True
                        else:
                            choose = True
                if iplot == 3 and event.double():
                    zoom = True
                posy = int(posy)
                posx = int(posx)
                if zoom:
                    self.zoom_plot(iplot)
                if (choose or flip) and (iplot == 1 or iplot == 2):
                    ichosen = int(self.iROI[iplot - 1, 0, posx, posy])
                    if ichosen < 0:
                        choose = False
                        flip = False
                if choose:
                    merged = False
                    if event.modifiers() == QtCore.Qt.ShiftModifier or event.modifiers(
                    ) == QtCore.Qt.ControlModifier:
                        if self.iscell[self.imerge[0]] == self.iscell[ichosen]:
                            if ichosen not in self.imerge:
                                self.imerge.append(ichosen)
                                self.ichosen = ichosen
                                merged = True
                            elif ichosen in self.imerge and len(self.imerge) > 1:
                                self.imerge.remove(ichosen)
                                self.ichosen = self.imerge[0]
                                merged = True
                    if not merged:
                        self.imerge = [ichosen]
                        self.ichosen = ichosen
                if flip:
                    if ichosen not in self.imerge:
                        self.imerge = [ichosen]
                        self.ichosen = ichosen
                    self.flip_plot(iplot)
                if choose or flip or replot:
                    if self.isROI:
                        self.ROI_remove()
                    if not self.sizebtns.button(1).isChecked():
                        for btn in self.topbtns.buttons():
                            if btn.isChecked():
                                btn.setStyleSheet(self.styleUnpressed)
                    self.update_plot()
                elif event.button() == QtCore.Qt.RightButton:
                    if iplot == 1:
                        event.acceptedItem = self.p1
                        self.p1.raiseContextMenu(event)
                    elif iplot == 2:
                        event.acceptedItem = self.p2
                        self.p2.raiseContextMenu(event)

    def ichosen_stats(self):
        n = self.ichosen
        self.ROIedit.setText(str(self.ichosen))
        for k in range(1, len(self.stats_to_show) + 1):
            key = self.stats_to_show[k - 1]
            ival = self.stat[n][key] if key in self.stat[n] else 0
            if k == 1:
                self.ROIstats[k].setText(key + ": [%d, %d]" % (ival[0], ival[1]))
            else:
                self.ROIstats[k].setText(key + ": %2.2f" % (ival))

    def zoom_to_cell(self):
        irange = 0.1 * np.array([self.Ly, self.Lx]).max()
        if len(self.imerge) > 1:
            apix = np.zeros((0, 2))
            for i, k in enumerate(self.imerge):
                apix = np.append(
                    apix,
                    np.concatenate((self.stat[k]["ypix"].flatten()[:, np.newaxis],
                                    self.stat[k]["xpix"].flatten()[:, np.newaxis]),
                                   axis=1), axis=0)

            imin = apix.min(axis=0)
            imax = apix.max(axis=0)
            icent = apix.mean(axis=0)
            imin[0] = min(icent[0] - irange, imin[0])
            imin[1] = min(icent[1] - irange, imin[1])
            imax[0] = max(icent[0] + irange, imax[0])
            imax[1] = max(icent[1] + irange, imax[1])
        else:
            icent = np.array(self.stat[self.ichosen]["med"])
            imin = icent - irange
            imax = icent + irange
        self.p1.setYRange(imin[0], imax[0])
        self.p1.setXRange(imin[1], imax[1])
        self.p2.setYRange(imin[0], imax[0])
        self.p2.setXRange(imin[1], imax[1])
        self.win.show()
        self.show()

mode_change #

mode_change(i)

changes the activity mode that is used when multiple neurons are selected or in visualization windows like rastermap or for correlation computation!

activityMode = 0 : F 1 : Fneu 2 : F - 0.7 * Fneu (default) 3 : spks

uses binning set by self.bin

Source code in suite2p/gui/gui2p.py
421
422
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
def mode_change(self, i):
    """

        changes the activity mode that is used when multiple neurons are selected
        or in visualization windows like rastermap or for correlation computation!

        activityMode =
        0 : F
        1 : Fneu
        2 : F - 0.7 * Fneu (default)
        3 : spks

        uses binning set by self.bin

    """
    self.activityMode = i
    if self.loaded:
        # activity used for correlations
        self.bin = max(1, int(self.binedit.text()))
        nb = int(np.floor(float(self.Fcell.shape[1]) / float(self.bin)))
        if i == 0:
            f = self.Fcell
        elif i == 1:
            f = self.Fneu
        elif i == 2:
            f = self.Fcell - 0.7 * self.Fneu
        else:
            f = self.Spks
        ncells = len(self.stat)
        self.Fbin = f[:, :nb * self.bin].reshape(
            (ncells, nb, self.bin)).mean(axis=2)

        self.Fbin -= self.Fbin.mean(axis=1)[:, np.newaxis]
        self.Fstd = (self.Fbin**2).mean(axis=1)**0.5
        self.trange = np.arange(0, self.Fcell.shape[1])
        # if in behavior-view, recompute
        if self.ops_plot["color"] == 8:
            masks.beh_masks(self)
            masks.plot_colorbar(self)
        self.update_plot()

plot_clicked #

plot_clicked(event)

left-click chooses a cell, right-click flips cell to other view

Source code in suite2p/gui/gui2p.py
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
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
def plot_clicked(self, event):
    """left-click chooses a cell, right-click flips cell to other view"""
    flip = False
    choose = False
    zoom = False
    replot = False
    items = self.win.scene().items(event.scenePos())
    posx = 0
    posy = 0
    iplot = 0
    if self.loaded:
        # print(event.modifiers() == QtCore.Qt.ControlModifier)
        for x in items:
            if x == self.img1:
                pos = self.p1.mapSceneToView(event.scenePos())
                posy = pos.x()
                posx = pos.y()
                iplot = 1
            elif x == self.img2:
                pos = self.p2.mapSceneToView(event.scenePos())
                posy = pos.x()
                posx = pos.y()
                iplot = 2
            elif x == self.p3:
                iplot = 3
            elif ((x == self.p1 or x == self.p2) and x != self.img1 and
                  x != self.img2):
                iplot = 4
                if event.double():
                    zoom = True
            if iplot == 1 or iplot == 2:
                if event.button() == QtCore.Qt.RightButton:
                    flip = True
                elif event.button() == QtCore.Qt.LeftButton:
                    if event.double():
                        zoom = True
                    else:
                        choose = True
            if iplot == 3 and event.double():
                zoom = True
            posy = int(posy)
            posx = int(posx)
            if zoom:
                self.zoom_plot(iplot)
            if (choose or flip) and (iplot == 1 or iplot == 2):
                ichosen = int(self.iROI[iplot - 1, 0, posx, posy])
                if ichosen < 0:
                    choose = False
                    flip = False
            if choose:
                merged = False
                if event.modifiers() == QtCore.Qt.ShiftModifier or event.modifiers(
                ) == QtCore.Qt.ControlModifier:
                    if self.iscell[self.imerge[0]] == self.iscell[ichosen]:
                        if ichosen not in self.imerge:
                            self.imerge.append(ichosen)
                            self.ichosen = ichosen
                            merged = True
                        elif ichosen in self.imerge and len(self.imerge) > 1:
                            self.imerge.remove(ichosen)
                            self.ichosen = self.imerge[0]
                            merged = True
                if not merged:
                    self.imerge = [ichosen]
                    self.ichosen = ichosen
            if flip:
                if ichosen not in self.imerge:
                    self.imerge = [ichosen]
                    self.ichosen = ichosen
                self.flip_plot(iplot)
            if choose or flip or replot:
                if self.isROI:
                    self.ROI_remove()
                if not self.sizebtns.button(1).isChecked():
                    for btn in self.topbtns.buttons():
                        if btn.isChecked():
                            btn.setStyleSheet(self.styleUnpressed)
                self.update_plot()
            elif event.button() == QtCore.Qt.RightButton:
                if iplot == 1:
                    event.acceptedItem = self.p1
                    self.p1.raiseContextMenu(event)
                elif iplot == 2:
                    event.acceptedItem = self.p2
                    self.p2.raiseContextMenu(event)

Copyright © 2023 Howard Hughes Medical Institute, Authored by Carsen Stringer and Marius Pachitariu.

load_files #

load_files(name)

give stat.npy path and load all needed files for suite2p

Source code in suite2p/gui/io.py
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
def load_files(name):
    """ give stat.npy path and load all needed files for suite2p """
    try:
        stat = np.load(name, allow_pickle=True)
        ypix = stat[0]["ypix"]
    except (ValueError, KeyError, OSError, RuntimeError, TypeError, NameError):
        print("ERROR: this is not a stat.npy file :( "
              "(needs stat[n]['ypix']!)")
        stat = None
    goodfolder = False
    if stat is not None:
        basename, fname = os.path.split(name)
        goodfolder = True
        try:
            Fcell = np.load(basename + "/F.npy")
            Fneu = np.load(basename + "/Fneu.npy")
        except (ValueError, OSError, RuntimeError, TypeError, NameError):
            print("ERROR: there are no fluorescence traces in this folder "
                  "(F.npy/Fneu.npy)")
            goodfolder = False
        try:
            Spks = np.load(basename + "/spks.npy")
        except (ValueError, OSError, RuntimeError, TypeError, NameError):
            print("there are no spike deconvolved traces in this folder "
                  "(spks.npy)")
            goodfolder = False
        noops = True
        try:
            ops = np.load(os.path.join(basename, "ops.npy"), allow_pickle=True).item()
            noops = False
        except:
            noops = True
        if noops:
            try:
                settings = np.load(basename + "/settings.npy", allow_pickle=True).item()
                db = np.load(basename + "/db.npy", allow_pickle=True).item()
                try:
                    reg_outputs = np.load(basename + "/reg_outputs.npy", allow_pickle=True).item()
                    detect_outputs = np.load(basename + "/detect_outputs.npy", allow_pickle=True).item()
                    ops = {**db, **settings, **reg_outputs, **detect_outputs}
                except:
                    ops = {**db, **settings}
                    print("no reg_outputs.npy or detect_outputs.npy found")
            except (ValueError, OSError, RuntimeError, TypeError, NameError):
                if noops:
                    print("ERROR: there is no settings or db file in this folder (settings.npy / db.npy)")
                    goodfolder = False
        try:
            iscell = np.load(basename + "/iscell.npy")
            probcell = iscell[:, 1]
            iscell = iscell[:, 0].astype("bool")
        except (ValueError, OSError, RuntimeError, TypeError, NameError):
            print("no manual labels found (iscell.npy)")
            if goodfolder:
                NN = Fcell.shape[0]
                iscell = np.ones((NN,), "bool")
                probcell = np.ones((NN,), np.float32)
        try:
            redcell = np.load(basename + "/redcell.npy")
            probredcell = redcell[:, 1].copy()
            redcell = redcell[:, 0].astype("bool")
            hasred = True
        except (ValueError, OSError, RuntimeError, TypeError, NameError):
            print("no channel 2 labels found (redcell.npy)")
            hasred = False
            if goodfolder:
                NN = Fcell.shape[0]
                redcell = np.zeros((NN,), "bool")
                probredcell = np.zeros((NN,), np.float32)
    else:
        print("incorrect file, not a stat.npy")
        return None

    if goodfolder:
        return stat, ops, Fcell, Fneu, Spks, iscell, probcell, redcell, probredcell, hasred
    else:
        print("stat.npy found, but other files not in folder")
        return None

resample_frames #

resample_frames(y, x, xt)

resample y (defined at x) at times xt

Source code in suite2p/gui/io.py
426
427
428
429
430
431
432
def resample_frames(y, x, xt):
    """ resample y (defined at x) at times xt """
    ts = x.size / xt.size
    y = gaussian_filter1d(y, np.ceil(ts / 2), axis=0)
    f = interp1d(x, y, fill_value="extrapolate")
    yt = f(xt)
    return yt

Copyright © 2023 Howard Hughes Medical Institute, Authored by Carsen Stringer and Marius Pachitariu.

add_roi #

add_roi(parent, n, i)

add roi n to view i

Source code in suite2p/gui/masks.py
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
def add_roi(parent, n, i):
    """
    add roi n to view i
    """
    ypix = parent.stat[n]["ypix"]
    xpix = parent.stat[n]["xpix"]
    lam = parent.stat[n]["lam"]
    parent.rois["iROI"][i, 2, ypix, xpix] = parent.rois["iROI"][i, 1, ypix, xpix]
    parent.rois["iROI"][i, 1, ypix, xpix] = parent.rois["iROI"][i, 0, ypix, xpix]
    parent.rois["iROI"][i, 0, ypix, xpix] = n
    parent.rois["Lam"][i, 2, ypix, xpix] = parent.rois["Lam"][i, 1, ypix, xpix]
    parent.rois["Lam"][i, 1, ypix, xpix] = parent.rois["Lam"][i, 0, ypix, xpix]
    parent.rois["Lam"][i, 0, ypix, xpix] = lam  #/ lam.sum()

    # set whether or not an ROI + weighting of pixels
    parent.rois["Sroi"][i, ypix, xpix] = 1
    parent.rois["LamNorm"][:, ypix, xpix] = np.maximum(
        0,
        np.minimum(1, 0.75 * parent.rois["Lam"][:, 0, ypix, xpix] /
                   parent.rois["LamMean"]))

draw_masks #

draw_masks(parent)

creates RGB masks using stat and puts them in M0 or M1 depending on whether or not iscell is True for a given ROI args: settings: mean_image, Vcorr stat: xpix,ypix iscell: vector with True if ROI is cell settings_plot: plotROI, view, color, randcols outputs: M0: ROIs that are True in iscell M1: ROIs that are False in iscell

Source code in suite2p/gui/masks.py
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
def draw_masks(parent):  #settings, stat, settings_plot, iscell, ichosen):
    """

    creates RGB masks using stat and puts them in M0 or M1 depending on
    whether or not iscell is True for a given ROI
    args:
        settings: mean_image, Vcorr
        stat: xpix,ypix
        iscell: vector with True if ROI is cell
        settings_plot: plotROI, view, color, randcols
    outputs:
        M0: ROIs that are True in iscell
        M1: ROIs that are False in iscell

    """
    ncells = parent.iscell.shape[0]
    plotROI = parent.ops_plot["ROIs_on"]
    view = parent.ops_plot["view"]
    color = parent.ops_plot["color"]
    opacity = parent.ops_plot["opacity"]

    wplot = int(1 - parent.iscell[parent.ichosen])
    # reset transparency
    for i in range(2):
        parent.colors["RGB"][i, color, :, :,
                             3] = (opacity[view == 0] * parent.rois["Sroi"][i] *
                                   parent.rois["LamNorm"][i]).astype(np.uint8)
    M = [
        np.array(parent.colors["RGB"][0, color]),
        np.array(parent.colors["RGB"][1, color])
    ]

    if view == 0:
        for n in parent.imerge:
            ypix = parent.stat[n]["ypix"].flatten()
            xpix = parent.stat[n]["xpix"].flatten()
            v = (parent.rois["iROI"][wplot][:, ypix, xpix] > -1).sum(axis=0) - 1
            v = 1 - v / 3
            M[wplot] = make_chosen_ROI(M[wplot], ypix, xpix, v)
    else:
        for n in parent.imerge:
            ycirc = parent.stat[n]["ycirc"]
            xcirc = parent.stat[n]["xcirc"]
            ypix = parent.stat[n]["ypix"].flatten()
            xpix = parent.stat[n]["xpix"].flatten()
            M[wplot][ypix, xpix, 3] = 0
            col = parent.colors["cols"][color, n]
            sat = 1
            M[wplot] = make_chosen_circle(M[wplot], ycirc, xcirc, col, sat)

    return M[0], M[1]

flip_roi #

flip_roi(parent)

flips roi to other plot there are 3 levels of overlap so this may be buggy if more than 3 cells are on top of each other

Source code in suite2p/gui/masks.py
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
def flip_roi(parent):
    """
    flips roi to other plot
    there are 3 levels of overlap so this may be buggy if more than 3 cells are on
    top of each other
    """
    cols = parent.ops_plot["color"]
    n = parent.ichosen
    i = int(1 - parent.iscell[n])
    i0 = 1 - i
    if parent.checkBoxN.isChecked():
        if i0 == 0:
            parent.p1.removeItem(parent.roi_text_labels[n])
            parent.p2.addItem(parent.roi_text_labels[n])
        else:
            parent.p2.removeItem(parent.roi_text_labels[n])
            parent.p1.addItem(parent.roi_text_labels[n])

    # remove ROI
    remove_roi(parent, n, i0)
    # add cell to other side (on top) and push down overlaps
    add_roi(parent, n, i)
    # redraw colors
    ypix = parent.stat[n]["ypix"]
    xpix = parent.stat[n]["xpix"]
    redraw_masks(parent, ypix, xpix)

init_masks #

init_masks(parent)

creates RGB masks using stat and puts them in M0 or M1 depending on whether or not iscell is True for a given ROI args: settings: mean_image, Vcorr stat: xpix,ypix,xext,yext iscell: vector with True if ROI is cell settings_plot: plotROI, view, color, randcols outputs: M0: ROIs that are True in iscell M1: ROIs that are False in iscell

Source code in suite2p/gui/masks.py
209
210
211
212
213
214
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
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
def init_masks(parent):
    """
    creates RGB masks using stat and puts them in M0 or M1 depending on
    whether or not iscell is True for a given ROI
    args:
        settings: mean_image, Vcorr
        stat: xpix,ypix,xext,yext
        iscell: vector with True if ROI is cell
        settings_plot: plotROI, view, color, randcols
    outputs:
        M0: ROIs that are True in iscell
        M1: ROIs that are False in iscell

    """
    stat = parent.stat
    iscell = parent.iscell
    cols = parent.colors["cols"]
    ncells = len(stat)
    Ly = parent.Ly
    Lx = parent.Lx
    parent.rois["Sroi"] = np.zeros((2, Ly, Lx), "bool")
    LamAll = np.zeros((Ly, Lx), np.float32)
    # these have 3 layers
    parent.rois["Lam"] = np.zeros((2, 3, Ly, Lx), np.float32)
    parent.rois["iROI"] = -1 * np.ones((2, 3, Ly, Lx), np.int32)

    if parent.checkBoxN.isChecked():
        parent.checkBoxN.setChecked(False)

    # ignore merged cells
    iignore = np.zeros(ncells, "bool")
    parent.roi_text_labels = []
    for n in np.arange(ncells - 1, -1, -1, int):
        ypix = stat[n]["ypix"]
        if ypix is not None and not iignore[n]:
            if "imerge" in stat[n]:
                for k in stat[n]["imerge"]:
                    iignore[k] = True
                    print(f"ROI {k} in merged ROI")
            xpix = stat[n]["xpix"]
            lam = stat[n]["lam"]
            lam = lam / lam.sum()
            i = int(1 - iscell[n])
            # add cell on top
            parent.rois["iROI"][i, 2, ypix, xpix] = parent.rois["iROI"][i, 1, ypix,
                                                                        xpix]
            parent.rois["iROI"][i, 1, ypix, xpix] = parent.rois["iROI"][i, 0, ypix,
                                                                        xpix]
            parent.rois["iROI"][i, 0, ypix, xpix] = n

            # add weighting to all layers
            parent.rois["Lam"][i, 2, ypix, xpix] = parent.rois["Lam"][i, 1, ypix, xpix]
            parent.rois["Lam"][i, 1, ypix, xpix] = parent.rois["Lam"][i, 0, ypix, xpix]
            parent.rois["Lam"][i, 0, ypix, xpix] = lam
            parent.rois["Sroi"][i, ypix, xpix] = 1
            LamAll[ypix, xpix] = lam
            med = stat[n]["med"]
            cell_str = str(n)
        else:
            cell_str = ""
            med = (0, 0)
        txt = pg.TextItem(cell_str, color=(180, 180, 180), anchor=(0.5, 0.5))
        txt.setPos(med[1], med[0])
        txt.setFont(QtGui.QFont("Times", 8, weight=QtGui.QFont.Bold))
        parent.roi_text_labels.append(txt)
    parent.roi_text_labels = parent.roi_text_labels[::-1]

    parent.rois["LamMean"] = LamAll[LamAll > 1e-10].mean()
    parent.rois["LamNorm"] = np.maximum(
        0, np.minimum(1, 0.75 * parent.rois["Lam"][:, 0] / parent.rois["LamMean"]))
    parent.colors["RGB"] = np.zeros((2, cols.shape[0], Ly, Lx, 4), np.uint8)

    for c in range(0, cols.shape[0]):
        rgb_masks(parent, cols[c], c)

make_buttons #

make_buttons(parent, b0)

color buttons at row b0

Source code in suite2p/gui/masks.py
16
17
18
19
20
21
22
23
24
25
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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
def make_buttons(parent, b0):
    """ color buttons at row b0 """
    # color buttons
    parent.color_names = [
        "A: random", "S: skew", "D: compact", "F: snr", "G: aspect_ratio",
        "H: chan2_prob", "J: classifier, cell prob=", "K: correlations, bin=",
        "L: corr with 1D var, bin=^^^", "M: rastermap / custom"
    ]
    parent.colorbtns = QButtonGroup(parent)
    clabel = QLabel(parent)
    clabel.setText("<font color='white'>Colors</font>")
    clabel.setFont(parent.boldfont)
    parent.l0.addWidget(clabel, b0, 0, 1, 1)

    iwid = 65

    # add colormaps
    parent.CmapChooser = QComboBox()
    cmaps = [
        "hsv", "viridis", "plasma", "inferno", "magma", "cividis", "viridis_r",
        "plasma_r", "inferno_r", "magma_r", "cividis_r"
    ]
    parent.CmapChooser.addItems(cmaps)
    parent.CmapChooser.setCurrentIndex(0)
    parent.CmapChooser.activated.connect(lambda: cmap_change(parent))
    parent.CmapChooser.setFont(QtGui.QFont("Arial", 8))
    parent.CmapChooser.setFixedWidth(iwid)
    parent.l0.addWidget(parent.CmapChooser, b0, 1, 1, 1)

    nv = b0
    b = 0
    # colorbars for different statistics
    colorsAll = parent.color_names.copy()
    for names in colorsAll:
        btn = ColorButton(b, "&" + names, parent)
        parent.colorbtns.addButton(btn, b)
        if b > 4 and b < 8:
            parent.l0.addWidget(btn, nv + b + 1, 0, 1, 1)
        else:
            parent.l0.addWidget(btn, nv + b + 1, 0, 1, 2)
        btn.setEnabled(False)
        parent.color_names[b] = parent.color_names[b][3:]
        b += 1
    parent.chan2edit = QLineEdit(parent)
    parent.chan2edit.setText("0.6")
    parent.chan2edit.setFixedWidth(iwid)
    parent.chan2edit.setAlignment(QtCore.Qt.AlignRight)
    parent.chan2edit.returnPressed.connect(lambda: chan2_prob(parent))
    parent.l0.addWidget(parent.chan2edit, nv + b - 4, 1, 1, 1)

    parent.probedit = QLineEdit(parent)
    parent.probedit.setText("0.5")
    parent.probedit.setFixedWidth(iwid)
    parent.probedit.setAlignment(QtCore.Qt.AlignRight)
    parent.probedit.returnPressed.connect(lambda: suite2p.gui.merge.apply(parent))
    parent.l0.addWidget(parent.probedit, nv + b - 3, 1, 1, 1)

    parent.binedit = QLineEdit(parent)
    parent.binedit.setValidator(QtGui.QIntValidator(0, 500))
    parent.binedit.setText("1")
    parent.binedit.setFixedWidth(iwid)
    parent.binedit.setAlignment(QtCore.Qt.AlignRight)
    parent.binedit.returnPressed.connect(
        lambda: parent.mode_change(parent.activityMode))
    parent.l0.addWidget(parent.binedit, nv + b - 2, 1, 1, 1)
    b0 = nv + b + 2
    return b0

redraw_masks #

redraw_masks(parent, ypix, xpix)

redraw masks after roi added/removed

Source code in suite2p/gui/masks.py
544
545
546
547
548
549
550
551
552
def redraw_masks(parent, ypix, xpix):
    """
    redraw masks after roi added/removed
    """
    for c in range(parent.colors["cols"].shape[0]):
        for i in range(2):
            col = parent.colors["cols"][c]
            rgb = col[parent.rois["iROI"][i, 0, ypix, xpix], :]
            parent.colors["RGB"][i, c, ypix, xpix, :3] = rgb

remove_roi #

remove_roi(parent, n, i0)

removes roi n from view i0

Source code in suite2p/gui/masks.py
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
def remove_roi(parent, n, i0):
    """
    removes roi n from view i0
    """
    ypix = parent.stat[n]["ypix"]
    xpix = parent.stat[n]["xpix"]
    # cell indices
    ipix = np.array((parent.rois["iROI"][i0, 0, :, :] == n).nonzero()).astype(np.int32)
    ipix1 = np.array((parent.rois["iROI"][i0, 1, :, :] == n).nonzero()).astype(np.int32)
    ipix2 = np.array((parent.rois["iROI"][i0, 2, :, :] == n).nonzero()).astype(np.int32)
    # get rid of cell and push up overlaps on main views
    parent.rois["Lam"][i0, 0, ipix[0, :],
                       ipix[1, :]] = parent.rois["Lam"][i0, 1, ipix[0, :], ipix[1, :]]
    parent.rois["Lam"][i0, 1, ipix[0, :], ipix[1, :]] = 0
    parent.rois["Lam"][i0, 1, ipix1[0, :],
                       ipix1[1, :]] = parent.rois["Lam"][i0, 2, ipix1[0, :],
                                                         ipix1[1, :]]
    parent.rois["Lam"][i0, 2, ipix1[0, :], ipix1[1, :]] = 0
    parent.rois["Lam"][i0, 2, ipix2[0, :], ipix2[1, :]] = 0
    parent.rois["iROI"][i0, 0, ipix[0, :],
                        ipix[1, :]] = parent.rois["iROI"][i0, 1, ipix[0, :], ipix[1, :]]
    parent.rois["iROI"][i0, 1, ipix[0, :], ipix[1, :]] = -1
    parent.rois["iROI"][i0, 1, ipix1[0, :],
                        ipix1[1, :]] = parent.rois["iROI"][i0, 2, ipix1[0, :],
                                                           ipix1[1, :]]
    parent.rois["iROI"][i0, 2, ipix1[0, :], ipix1[1, :]] = -1
    parent.rois["iROI"][i0, 2, ipix2[0, :], ipix2[1, :]] = -1

    # remove +/- 1 ROI exists
    parent.rois["Sroi"][i0, ypix, xpix] = parent.rois["iROI"][i0, 0, ypix, xpix] > 0

    parent.rois["LamNorm"][i0, ypix, xpix] = np.maximum(
        0,
        np.minimum(
            1, 0.75 * parent.rois["Lam"][i0, 0, ypix, xpix] / parent.rois["LamMean"]))

Copyright © 2023 Howard Hughes Medical Institute, Authored by Carsen Stringer and Marius Pachitariu.

Copyright © 2023 Howard Hughes Medical Institute, Authored by Carsen Stringer and Marius Pachitariu.

Copyright © 2023 Howard Hughes Medical Institute, Authored by Carsen Stringer and Marius Pachitariu.

from kilosort GUI

MyLog #

Bases: QObject

solution from https://stackoverflow.com/questions/52479442/running-a-long-python-calculation-in-a-thread-with-logging-to-a-qt-window-cras

Source code in suite2p/gui/rungui_utils.py
17
18
19
20
21
22
23
24
25
26
27
28
class MyLog(QtCore.QObject):
    """
    solution from https://stackoverflow.com/questions/52479442/running-a-long-python-calculation-in-a-thread-with-logging-to-a-qt-window-cras
    """
    # create a new Signal
    # - have to be a static element
    # - class  has to inherit from QObject to be able to emit signals
    signal = QtCore.Signal(str)

    # not sure if it's necessary to implement this
    def __init__(self):
        super().__init__()

Suite2pWorker #

Bases: QObject

Worker that runs suite2p in a separate process to avoid QThread stack limitations on macOS.

Source code in suite2p/gui/rungui_utils.py
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
class Suite2pWorker(QtCore.QObject):
    """Worker that runs suite2p in a separate process to avoid QThread stack limitations on macOS."""
    finished = QtCore.Signal(str)

    def __init__(self, parent, db_file, settings_file):
        super(Suite2pWorker, self).__init__()
        self.db_file = db_file
        self.settings_file = settings_file
        self.parent = parent
        self.process = None

    def start(self):
        """Start suite2p in a separate process using QProcess."""
        self.process = QtCore.QProcess()
        self.process.setProcessChannelMode(QtCore.QProcess.MergedChannels)
        self.process.readyReadStandardOutput.connect(self._on_output)
        self.process.finished.connect(self._on_finished)

        # Create a Python script to run suite2p
        script = f'''
import numpy as np
from suite2p.run_s2p import logger_setup, run_s2p, get_save_folder

db = np.load("{self.db_file}", allow_pickle=True).item()
settings = np.load("{self.settings_file}", allow_pickle=True).item()

logger_setup(get_save_folder(db))
run_s2p(db=db, settings=settings)
'''
        self.process.start(sys.executable, ["-c", script])

    def _on_output(self):
        """Handle output from the subprocess."""
        if self.process:
            data = self.process.readAllStandardOutput()
            text = bytes(data).decode("utf-8", errors="replace")
            print(text, end="")

    def _on_finished(self, exit_code, exit_status):
        """Handle process completion."""
        if exit_code == 0:
            self.finished.emit("finished")
        else:
            self.finished.emit("error")

    def terminate(self):
        """Terminate the subprocess if running."""
        if self.process and self.process.state() != QtCore.QProcess.NotRunning:
            self.process.terminate()

    def quit(self):
        """Stop the subprocess (alias for terminate, for QThread compatibility)."""
        self.terminate()

    def wait(self):
        """Wait for the process to finish (for compatibility)."""
        if self.process:
            self.process.waitForFinished(-1)

    def isRunning(self):
        """Check if the process is still running (for QThread compatibility)."""
        if self.process:
            return self.process.state() == QtCore.QProcess.Running
        return False

isRunning #

isRunning()

Check if the process is still running (for QThread compatibility).

Source code in suite2p/gui/rungui_utils.py
177
178
179
180
181
def isRunning(self):
    """Check if the process is still running (for QThread compatibility)."""
    if self.process:
        return self.process.state() == QtCore.QProcess.Running
    return False

quit #

quit()

Stop the subprocess (alias for terminate, for QThread compatibility).

Source code in suite2p/gui/rungui_utils.py
168
169
170
def quit(self):
    """Stop the subprocess (alias for terminate, for QThread compatibility)."""
    self.terminate()

start #

start()

Start suite2p in a separate process using QProcess.

Source code in suite2p/gui/rungui_utils.py
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
    def start(self):
        """Start suite2p in a separate process using QProcess."""
        self.process = QtCore.QProcess()
        self.process.setProcessChannelMode(QtCore.QProcess.MergedChannels)
        self.process.readyReadStandardOutput.connect(self._on_output)
        self.process.finished.connect(self._on_finished)

        # Create a Python script to run suite2p
        script = f'''
import numpy as np
from suite2p.run_s2p import logger_setup, run_s2p, get_save_folder

db = np.load("{self.db_file}", allow_pickle=True).item()
settings = np.load("{self.settings_file}", allow_pickle=True).item()

logger_setup(get_save_folder(db))
run_s2p(db=db, settings=settings)
'''
        self.process.start(sys.executable, ["-c", script])

terminate #

terminate()

Terminate the subprocess if running.

Source code in suite2p/gui/rungui_utils.py
163
164
165
166
def terminate(self):
    """Terminate the subprocess if running."""
    if self.process and self.process.state() != QtCore.QProcess.NotRunning:
        self.process.terminate()

wait #

wait()

Wait for the process to finish (for compatibility).

Source code in suite2p/gui/rungui_utils.py
172
173
174
175
def wait(self):
    """Wait for the process to finish (for compatibility)."""
    if self.process:
        self.process.waitForFinished(-1)

ThreadLogger #

Bases: Handler

solution from https://stackoverflow.com/questions/52479442/running-a-long-python-calculation-in-a-thread-with-logging-to-a-qt-window-cras

Source code in suite2p/gui/rungui_utils.py
32
33
34
35
36
37
38
39
40
41
42
43
class ThreadLogger(logging.Handler):
    """
    solution from https://stackoverflow.com/questions/52479442/running-a-long-python-calculation-in-a-thread-with-logging-to-a-qt-window-cras
    """
    def __init__(self):
        super().__init__()
        self.log = MyLog()

    # logging.Handler.emit() is intended to be implemented by subclasses
    def emit(self, record):
        msg = self.format(record)
        self.log.signal.emit(msg)

Copyright © 2023 Howard Hughes Medical Institute, Authored by Carsen Stringer and Marius Pachitariu.

Copyright © 2023 Howard Hughes Medical Institute, Authored by Carsen Stringer and Marius Pachitariu.

Copyright © 2023 Howard Hughes Medical Institute, Authored by Carsen Stringer and Marius Pachitariu.

DarkPalette #

Bases: QPalette

Class that inherits from pyqtgraph.QtGui.QPalette and renders dark colours for the application. (from pykilosort/kilosort4)

Source code in suite2p/gui/utils.py
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
class DarkPalette(QtGui.QPalette):
    """Class that inherits from pyqtgraph.QtGui.QPalette and renders dark colours for the application.
    (from pykilosort/kilosort4)
    """

    def __init__(self):
        QtGui.QPalette.__init__(self)
        self.setup()

    def setup(self):
        self.setColor(QtGui.QPalette.Window, QtGui.QColor(50, 50, 50))
        self.setColor(QtGui.QPalette.WindowText, QtGui.QColor(255, 255, 255))
        self.setColor(QtGui.QPalette.Base, QtGui.QColor(0, 0, 0))
        self.setColor(QtGui.QPalette.AlternateBase, QtGui.QColor(0, 0, 0))
        self.setColor(QtGui.QPalette.ToolTipBase, QtGui.QColor(255, 255, 255))
        self.setColor(QtGui.QPalette.ToolTipText, QtGui.QColor(255, 255, 255))
        self.setColor(QtGui.QPalette.Text, QtGui.QColor(255, 255, 255))
        self.setColor(QtGui.QPalette.Button, QtGui.QColor(40, 40, 40))
        self.setColor(QtGui.QPalette.ButtonText, QtGui.QColor(255, 255, 255))
        self.setColor(QtGui.QPalette.BrightText, QtGui.QColor(255, 0, 0))
        self.setColor(QtGui.QPalette.Link, QtGui.QColor(42, 130, 218))
        self.setColor(QtGui.QPalette.Highlight, QtGui.QColor(42, 130, 218))
        self.setColor(QtGui.QPalette.HighlightedText, QtGui.QColor(0, 0, 0))
        self.setColor(QtGui.QPalette.Disabled, QtGui.QPalette.Text,
                      QtGui.QColor(128, 128, 128))
        self.setColor(
            QtGui.QPalette.Disabled,
            QtGui.QPalette.ButtonText,
            QtGui.QColor(128, 128, 128),
        )
        self.setColor(
            QtGui.QPalette.Disabled,
            QtGui.QPalette.WindowText,
            QtGui.QColor(128, 128, 128),
        )

boundary #

boundary(ypix, xpix)

returns pixels of mask that are on the exterior of the mask

Source code in suite2p/gui/utils.py
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
def boundary(ypix, xpix):
    """ returns pixels of mask that are on the exterior of the mask """
    ypix = np.expand_dims(ypix.flatten(), axis=1)
    xpix = np.expand_dims(xpix.flatten(), axis=1)
    npix = ypix.shape[0]
    if npix > 0:
        msk = np.zeros((np.ptp(ypix) + 6, np.ptp(xpix) + 6), "bool")
        msk[ypix - ypix.min() + 3, xpix - xpix.min() + 3] = True
        msk = binary_dilation(msk)
        msk = binary_fill_holes(msk)
        k = np.ones((3, 3), dtype=int)  # for 4-connected
        k = np.zeros((3, 3), dtype=int)
        k[1] = 1
        k[:, 1] = 1  # for 8-connected
        out = binary_dilation(msk == 0, k) & msk

        yext, xext = np.nonzero(out)
        yext, xext = yext + ypix.min() - 3, xext + xpix.min() - 3
    else:
        yext = np.zeros((0,))
        xext = np.zeros((0,))
    return yext, xext

circle #

circle(med, r)

returns pixels of circle with radius 1.25x radius of cell (r)

Source code in suite2p/gui/utils.py
113
114
115
116
117
118
119
120
def circle(med, r):
    """ returns pixels of circle with radius 1.25x radius of cell (r) """
    theta = np.linspace(0.0, 2 * np.pi, 100)
    x = r * 1.25 * np.cos(theta) + med[0]
    y = r * 1.25 * np.sin(theta) + med[1]
    x = x.astype(np.int32)
    y = y.astype(np.int32)
    return x, y

Copyright © 2023 Howard Hughes Medical Institute, Authored by Carsen Stringer and Marius Pachitariu.

RangeSlider #

Bases: QSlider

A slider for ranges.

This class provides a dual-slider for ranges, where there is a defined maximum and minimum, as is a normal slider, but instead of having a single slider value, there are 2 slider values.

This class emits the same signals as the QSlider base class, with the exception of valueChanged

Found this slider here: https://www.mail-archive.com/pyqt@riverbankcomputing.com/msg22889.html and modified it

Source code in suite2p/gui/views.py
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
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
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
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
class RangeSlider(QSlider):
    """ A slider for ranges.

        This class provides a dual-slider for ranges, where there is a defined
        maximum and minimum, as is a normal slider, but instead of having a
        single slider value, there are 2 slider values.

        This class emits the same signals as the QSlider base class, with the
        exception of valueChanged

        Found this slider here: https://www.mail-archive.com/pyqt@riverbankcomputing.com/msg22889.html
        and modified it
    """

    def __init__(self, parent=None, *args):
        super(RangeSlider, self).__init__(*args)

        self._low = self.minimum()
        self._high = self.maximum()

        self.pressed_control = QStyle.SC_None
        self.hover_control = QStyle.SC_None
        self.click_offset = 0

        self.setOrientation(QtCore.Qt.Vertical)
        self.setTickPosition(QSlider.TicksRight)
        self.setStyleSheet(\
                "QSlider::handle:horizontal {\
                background-color: white;\
                border: 1px solid #5c5c5c;\
                border-radius: 0px;\
                border-color: black;\
                height: 8px;\
                width: 6px;\
                margin: -8px 2; \
                }"                                                                        )

        #self.opt = QStyleOptionSlider()
        #self.opt.orientation=QtCore.Qt.Vertical
        #self.initStyleOption(self.opt)
        # 0 for the low, 1 for the high, -1 for both
        self.active_slider = 0
        self.parent = parent

    def level_change(self):
        if self.parent is not None:
            if self.parent.loaded:
                self.parent.ops_plot["saturation"] = [self._low, self._high]
                self.parent.update_plot()

    def low(self):
        return self._low

    def setLow(self, low):
        self._low = low
        self.update()

    def high(self):
        return self._high

    def setHigh(self, high):
        self._high = high
        self.update()

    def paintEvent(self, event):
        # based on http://qt.gitorious.org/qt/qt/blobs/master/src/gui/widgets/qslider.cpp
        painter = QPainter(self)
        style = QApplication.style()

        for i, value in enumerate([self._low, self._high]):
            opt = QStyleOptionSlider()
            self.initStyleOption(opt)

            # Only draw the groove for the first slider so it doesn"t get drawn
            # on top of the existing ones every time
            if i == 0:
                opt.subControls = QStyle.SC_SliderHandle  #QStyle.SC_SliderGroove | QStyle.SC_SliderHandle
            else:
                opt.subControls = QStyle.SC_SliderHandle

            if self.tickPosition() != self.NoTicks:
                opt.subControls |= QStyle.SC_SliderTickmarks

            if self.pressed_control:
                opt.activeSubControls = self.pressed_control
                opt.state |= QStyle.State_Sunken
            else:
                opt.activeSubControls = self.hover_control

            opt.sliderPosition = value
            opt.sliderValue = value
            style.drawComplexControl(QStyle.CC_Slider, opt, painter, self)

    def mousePressEvent(self, event):
        event.accept()

        style = QApplication.style()
        button = event.button()
        # In a normal slider control, when the user clicks on a point in the
        # slider"s total range, but not on the slider part of the control the
        # control would jump the slider value to where the user clicked.
        # For this control, clicks which are not direct hits will slide both
        # slider parts
        if button:
            opt = QStyleOptionSlider()
            self.initStyleOption(opt)

            self.active_slider = -1

            for i, value in enumerate([self._low, self._high]):
                opt.sliderPosition = value
                hit = style.hitTestComplexControl(style.CC_Slider, opt, event.pos(),
                                                  self)
                if hit == style.SC_SliderHandle:
                    self.active_slider = i
                    self.pressed_control = hit

                    self.triggerAction(self.SliderMove)
                    self.setRepeatAction(self.SliderNoAction)
                    self.setSliderDown(True)

                    break

            if self.active_slider < 0:
                self.pressed_control = QStyle.SC_SliderHandle
                self.click_offset = self.__pixelPosToRangeValue(self.__pick(
                    event.pos()))
                self.triggerAction(self.SliderMove)
                self.setRepeatAction(self.SliderNoAction)
        else:
            event.ignore()

    def mouseMoveEvent(self, event):
        if self.pressed_control != QStyle.SC_SliderHandle:
            event.ignore()
            return

        event.accept()
        new_pos = self.__pixelPosToRangeValue(self.__pick(event.pos()))
        opt = QStyleOptionSlider()
        self.initStyleOption(opt)

        if self.active_slider < 0:
            offset = new_pos - self.click_offset
            self._high += offset
            self._low += offset
            if self._low < self.minimum():
                diff = self.minimum() - self._low
                self._low += diff
                self._high += diff
            if self._high > self.maximum():
                diff = self.maximum() - self._high
                self._low += diff
                self._high += diff
        elif self.active_slider == 0:
            if new_pos >= self._high:
                new_pos = self._high - 1
            self._low = new_pos
        else:
            if new_pos <= self._low:
                new_pos = self._low + 1
            self._high = new_pos

        self.click_offset = new_pos
        self.update()

    def mouseReleaseEvent(self, event):
        self.level_change()

    def __pick(self, pt):
        if self.orientation() == QtCore.Qt.Horizontal:
            return pt.x()
        else:
            return pt.y()

    def __pixelPosToRangeValue(self, pos):
        opt = QStyleOptionSlider()
        self.initStyleOption(opt)
        style = QApplication.style()

        gr = style.subControlRect(style.CC_Slider, opt, style.SC_SliderGroove, self)
        sr = style.subControlRect(style.CC_Slider, opt, style.SC_SliderHandle, self)

        if self.orientation() == QtCore.Qt.Horizontal:
            slider_length = sr.width()
            slider_min = gr.x()
            slider_max = gr.right() - slider_length + 1
        else:
            slider_length = sr.height()
            slider_min = gr.y()
            slider_max = gr.bottom() - slider_length + 1

        return style.sliderValueFromPosition(self.minimum(), self.maximum(),
                                             pos - slider_min, slider_max - slider_min,
                                             opt.upsideDown)

ViewButton #

Bases: QPushButton

custom QPushButton class for quadrant plotting requires buttons to put into a QButtonGroup (parent.viewbtns) allows only 1 button to pressed at a time

Source code in suite2p/gui/views.py
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
class ViewButton(QPushButton):
    """ custom QPushButton class for quadrant plotting
        requires buttons to put into a QButtonGroup (parent.viewbtns)
         allows only 1 button to pressed at a time
    """

    def __init__(self, bid, Text, parent=None):
        super(ViewButton, self).__init__(parent)
        self.setText(Text)
        self.setCheckable(True)
        self.setStyleSheet(parent.styleInactive)
        self.setFont(QtGui.QFont("Arial", 8, QtGui.QFont.Bold))
        self.resize(self.minimumSizeHint())
        self.clicked.connect(lambda: self.press(parent, bid))
        self.show()

    def press(self, parent, bid):
        for b in range(len(parent.views)):
            if parent.viewbtns.button(b).isEnabled():
                parent.viewbtns.button(b).setStyleSheet(parent.styleUnpressed)
        self.setStyleSheet(parent.stylePressed)
        parent.ops_plot["view"] = bid
        parent.update_plot()

init_views #

init_views(parent)

make views using parent.ops

views in order: "Q: ROIs", "W: mean img", "E: mean img (enhanced)", "R: correlation map", "T: max projection", "Y: mean img chan2, corr", "U: mean img chan2",

assigns parent.views

Source code in suite2p/gui/views.py
 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
def init_views(parent):
    """ make views using parent.ops

    views in order:
        "Q: ROIs",
        "W: mean img",
        "E: mean img (enhanced)",
        "R: correlation map",
        "T: max projection",
        "Y: mean img chan2, corr",
        "U: mean img chan2",

    assigns parent.views

    """
    parent.Ly, parent.Lx = parent.ops["Ly"], parent.ops["Lx"]
    parent.views = np.zeros((7, parent.Ly, parent.Lx, 3), np.float32)
    for k in range(7):
        if k == 2:
            if "meanImgE" not in parent.ops:
                meanImgE = registration.highpass_mean_image(parent.ops["meanImg"], 
                                                              parent.ops.get("aspect", 1))
                parent.ops["meanImgE"] = meanImgE
            mimg = parent.ops["meanImgE"]
        elif k == 1:
            img = parent.ops["meanImg"]
            mimg1 = np.percentile(img, 1)
            mimg99 = np.percentile(img, 99)
            img = (img - mimg1) / (mimg99 - mimg1)
            img = np.clip(img, 0, 1)
            mimg = np.zeros((parent.Ly, parent.Lx), np.float32)
            if img.shape[0] != parent.Ly or img.shape[1] != parent.Lx:
                mimg[parent.ops["yrange"][0]:parent.ops["yrange"][1],
                     parent.ops["xrange"][0]:parent.ops["xrange"][1]] = img
            else:
                mimg = img
        elif k == 3:
            if "Vcorr" in parent.ops:
                vcorr = parent.ops["Vcorr"]
                mimg1 = np.percentile(vcorr, 1)
                mimg99 = np.percentile(vcorr, 99)
                vcorr = (vcorr - mimg1) / (mimg99 - mimg1)
                vcorr = np.clip(vcorr, 0, 1)
                mimg = np.zeros((parent.Ly, parent.Lx), np.float32)
                mimg[parent.ops["yrange"][0]:parent.ops["yrange"][1],
                     parent.ops["xrange"][0]:parent.ops["xrange"][1]] = vcorr
                mimg = np.maximum(0, np.minimum(1, mimg))
            else:
                mimg = np.zeros((parent.Ly, parent.Lx), np.float32)
        elif k == 4:
            if "max_proj" in parent.ops:
                mproj = parent.ops["max_proj"]
                mimg1 = np.percentile(mproj, 1)
                mimg99 = np.percentile(mproj, 99)
                mproj = (mproj - mimg1) / (mimg99 - mimg1)
                mimg = np.zeros((parent.Ly, parent.Lx), np.float32)
                try:
                    mimg[parent.ops["yrange"][0]:parent.ops["yrange"][1],
                         parent.ops["xrange"][0]:parent.ops["xrange"][1]] = mproj
                except:
                    print("maxproj not in combined view")
                mimg = np.maximum(0, np.minimum(1, mimg))
            else:
                mimg = 0.5 * np.ones((parent.Ly, parent.Lx), np.float32)
        elif k == 5:
            if "meanImg_chan2_corrected" in parent.ops:
                mimg = parent.ops["meanImg_chan2_corrected"]
                mimg1 = np.percentile(mimg, 1)
                mimg99 = np.percentile(mimg, 99)
                mimg = (mimg - mimg1) / (mimg99 - mimg1)
                mimg = np.maximum(0, np.minimum(1, mimg))
        elif k == 6:
            if "meanImg_chan2" in parent.ops:
                mimg = parent.ops["meanImg_chan2"]
                mimg1 = np.percentile(mimg, 1)
                mimg99 = np.percentile(mimg, 99)
                mimg = (mimg - mimg1) / (mimg99 - mimg1)
                mimg = np.maximum(0, np.minimum(1, mimg))
        else:
            mimg = np.zeros((parent.Ly, parent.Lx), np.float32)

        mimg *= 255
        mimg = mimg.astype(np.uint8)
        parent.views[k] = np.tile(mimg[:, :, np.newaxis], (1, 1, 3))

make_buttons #

make_buttons(parent)

view buttons

Source code in suite2p/gui/views.py
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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
def make_buttons(parent):
    """ view buttons"""
    # view buttons
    parent.view_names = [
        "Q: ROIs",
        "W: mean img",
        "E: mean img (enhanced)",
        "R: correlation map",
        "T: max projection",
        "Y: mean img chan2, corr",
        "U: mean img chan2",
    ]
    b = 0
    parent.viewbtns = QButtonGroup(parent)
    vlabel = QLabel(parent)
    vlabel.setText("<font color='white'>Background</font>")
    vlabel.setFont(parent.boldfont)
    vlabel.resize(vlabel.minimumSizeHint())
    parent.l0.addWidget(vlabel, 1, 0, 1, 1)
    for names in parent.view_names:
        btn = ViewButton(b, "&" + names, parent)
        parent.viewbtns.addButton(btn, b)
        if b > 0:
            parent.l0.addWidget(btn, b + 2, 0, 1, 1)
        else:
            parent.l0.addWidget(btn, b + 2, 0, 1, 1)
            label = QLabel("sat: ")
            label.setStyleSheet("color: white;")
            parent.l0.addWidget(label, b + 2, 1, 1, 1)
        btn.setEnabled(False)
        b += 1
    parent.viewbtns.setExclusive(True)
    slider = RangeSlider(parent)
    slider.setMinimum(0)
    slider.setMaximum(255)
    slider.setLow(0)
    slider.setHigh(255)
    slider.setTickPosition(QSlider.TicksBelow)
    parent.l0.addWidget(slider, 3, 1, len(parent.view_names) - 2, 1)

    b += 2
    return b

plot_views #

plot_views(parent)

set parent.view1 and parent.view2 image based on parent.ops_plot["view"]

Source code in suite2p/gui/views.py
142
143
144
145
146
147
148
def plot_views(parent):
    """ set parent.view1 and parent.view2 image based on parent.ops_plot["view"]"""
    k = parent.ops_plot["view"]
    parent.view1.setImage(parent.views[k], levels=parent.ops_plot["saturation"])
    parent.view2.setImage(parent.views[k], levels=parent.ops_plot["saturation"])
    parent.view1.show()
    parent.view2.show()

Copyright © 2023 Howard Hughes Medical Institute, Authored by Carsen Stringer and Marius Pachitariu.

RangeSlider #

Bases: QSlider

A slider for ranges.

This class provides a dual-slider for ranges, where there is a defined maximum and minimum, as is a normal slider, but instead of having a single slider value, there are 2 slider values.

This class emits the same signals as the QSlider base class, with the exception of valueChanged

Found this slider here: https://www.mail-archive.com/pyqt@riverbankcomputing.com/msg22889.html and modified it

Source code in suite2p/gui/visualize.py
 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
 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
class RangeSlider(QSlider):
    """ A slider for ranges.

        This class provides a dual-slider for ranges, where there is a defined
        maximum and minimum, as is a normal slider, but instead of having a
        single slider value, there are 2 slider values.

        This class emits the same signals as the QSlider base class, with the
        exception of valueChanged

        Found this slider here: https://www.mail-archive.com/pyqt@riverbankcomputing.com/msg22889.html
        and modified it
    """

    def __init__(self, parent=None, *args):
        super(RangeSlider, self).__init__(*args)

        self._low = self.minimum()
        self._high = self.maximum()

        self.pressed_control = QStyle.SC_None
        self.hover_control = QStyle.SC_None
        self.click_offset = 0

        self.setOrientation(QtCore.Qt.Vertical)
        self.setTickPosition(QSlider.TicksRight)
        self.setStyleSheet(\
                "QSlider::handle:horizontal {\
                background-color: white;\
                border: 1px solid #5c5c5c;\
                border-radius: 0px;\
                border-color: black;\
                height: 8px;\
                width: 6px;\
                margin: -8px 2; \
                }"                                                                        )
        # 0 for the low, 1 for the high, -1 for both
        self.active_slider = 0
        self.parent = parent

    def level_change(self):
        self.saturation = [self._low, self._high]

    def low(self):
        return self._low

    def setLow(self, low):
        self._low = low
        self.update()

    def high(self):
        return self._high

    def setHigh(self, high):
        self._high = high
        self.update()

    def paintEvent(self, event):
        # based on http://qt.gitorious.org/qt/qt/blobs/master/src/gui/widgets/qslider.cpp
        painter = QtGui.QPainter(self)
        style = QApplication.style()
        for i, value in enumerate([self._low, self._high]):
            opt = QStyleOptionSlider()
            self.initStyleOption(opt)
            # Only draw the groove for the first slider so it doesn"t get drawn
            # on top of the existing ones every time
            if i == 0:
                opt.subControls = QStyle.SC_SliderHandle  #QStyle.SC_SliderGroove | QStyle.SC_SliderHandle
            else:
                opt.subControls = QStyle.SC_SliderHandle
            if self.tickPosition() != self.NoTicks:
                opt.subControls |= QStyle.SC_SliderTickmarks
            if self.pressed_control:
                opt.activeSubControls = self.pressed_control
                opt.state |= QStyle.State_Sunken
            else:
                opt.activeSubControls = self.hover_control
            opt.sliderPosition = value
            opt.sliderValue = value
            style.drawComplexControl(QStyle.CC_Slider, opt, painter, self)

    def mousePressEvent(self, event):
        event.accept()
        style = QApplication.style()
        button = event.button()
        if button:
            opt = QStyleOptionSlider()
            self.initStyleOption(opt)
            self.active_slider = -1
            for i, value in enumerate([self._low, self._high]):
                opt.sliderPosition = value
                hit = style.hitTestComplexControl(style.CC_Slider, opt, event.pos(),
                                                  self)
                if hit == style.SC_SliderHandle:
                    self.active_slider = i
                    self.pressed_control = hit
                    self.triggerAction(self.SliderMove)
                    self.setRepeatAction(self.SliderNoAction)
                    self.setSliderDown(True)
                    break
            if self.active_slider < 0:
                self.pressed_control = QStyle.SC_SliderHandle
                self.click_offset = self.__pixelPosToRangeValue(self.__pick(
                    event.pos()))
                self.triggerAction(self.SliderMove)
                self.setRepeatAction(self.SliderNoAction)
        else:
            event.ignore()

    def mouseMoveEvent(self, event):
        if self.pressed_control != QStyle.SC_SliderHandle:
            event.ignore()
            return
        event.accept()
        new_pos = self.__pixelPosToRangeValue(self.__pick(event.pos()))
        opt = QStyleOptionSlider()
        self.initStyleOption(opt)
        if self.active_slider < 0:
            offset = new_pos - self.click_offset
            self._high += offset
            self._low += offset
            if self._low < self.minimum():
                diff = self.minimum() - self._low
                self._low += diff
                self._high += diff
            if self._high > self.maximum():
                diff = self.maximum() - self._high
                self._low += diff
                self._high += diff
        elif self.active_slider == 0:
            if new_pos >= self._high:
                new_pos = self._high - 1
            self._low = new_pos
        else:
            if new_pos <= self._low:
                new_pos = self._low + 1
            self._high = new_pos
        self.click_offset = new_pos
        self.update()

    def mouseReleaseEvent(self, event):
        self.level_change()

    def __pick(self, pt):
        if self.orientation() == QtCore.Qt.Horizontal:
            return pt.x()
        else:
            return pt.y()

    def __pixelPosToRangeValue(self, pos):
        opt = QStyleOptionSlider()
        self.initStyleOption(opt)
        style = QApplication.style()

        gr = style.subControlRect(style.CC_Slider, opt, style.SC_SliderGroove, self)
        sr = style.subControlRect(style.CC_Slider, opt, style.SC_SliderHandle, self)

        if self.orientation() == QtCore.Qt.Horizontal:
            slider_length = sr.width()
            slider_min = gr.x()
            slider_max = gr.right() - slider_length + 1
        else:
            slider_length = sr.height()
            slider_min = gr.y()
            slider_max = gr.bottom() - slider_length + 1

        return style.sliderValueFromPosition(self.minimum(), self.maximum(),
                                             pos - slider_min, slider_max - slider_min,
                                             opt.upsideDown)