Skip to content

array map

HoverRegion

Source code in src/controller/plots/array_map.py
class HoverRegion():
    window = None
    region = None
    vLine, hLine = None, None
    vb = None
    proxy = None
    proxy2 = None
    HoverFunc, ClickFunc = None, None
    last_mouse_x, last_mouse_y = None, None

    def __init__(self, window_ref, HoverFunc, ClickFunc):
        self.window = window_ref
        #self.region = pg.LinearRegionItem()
        self.HoverFunc = HoverFunc
        self.ClickFunc = ClickFunc

        #self.window.addItem(self.region, ignoreBounds=True)
        #self.window.sigRangeChanged.connect(self.updateRegion)

        #self.region.setZValue(10)
        #self.region.setRegion([-10, 50])
        #self.region.sigRegionChanged.connect(self.update)

        self.vb = self.window.plotItem.vb
        self.proxy = pg.SignalProxy(self.window.scene().sigMouseMoved,
                                    rateLimit=10,
                                    slot=self.mouseMoved)
        #self.proxy2 = pg.SignalProxy(self.window.scene().sigMouseClicked,
        #                             rateLimit=60,
        #                             slot=self.mouseClicked)
        self.window.scene().sigMouseClicked.connect(self.mouseClicked)
    #def update(self):
    #    self.region.setZValue(10)
    #   self.window.setXRange(-10, 40, padding=0)

    #def updateRegion(self, window, viewRange):
    #    rgn = viewRange[0]
    #    self.region.setRegion(rgn)

    def mouseMoved(self, evt):
        """Updated when the mouse is moved by the user

        Args:
            evt: event encoded by PyQt

        Returns:
            None

        """
        pos = evt[0]  # using signal proxy turns original arguments into a tuple
        if self.window.sceneBoundingRect().contains(pos):
            mousePoint = self.vb.mapSceneToView(pos)
            self.last_mouse_x = mousePoint.x()
            self.last_mouse_y = mousePoint.y()
            self.HoverFunc(mousePoint.x(), mousePoint.y())

    def mouseClicked(self, evt):
        """Update when the mouse is clicked by the user

        Args:
            evt: event encoded by PyQt

        Returns:

        """
        pos = (self.last_mouse_x, self.last_mouse_y)
        #if self.window.sceneBoundingRect().contains(pos):
            # mousePoint = self.vb.mapSceneToView(pos)
        # print('entering click func')
        self.ClickFunc(self.last_mouse_x, self.last_mouse_y)

mouseClicked(self, evt)

Update when the mouse is clicked by the user

Parameters:

Name Type Description Default
evt

event encoded by PyQt

required
Source code in src/controller/plots/array_map.py
def mouseClicked(self, evt):
    """Update when the mouse is clicked by the user

    Args:
        evt: event encoded by PyQt

    Returns:

    """
    pos = (self.last_mouse_x, self.last_mouse_y)
    #if self.window.sceneBoundingRect().contains(pos):
        # mousePoint = self.vb.mapSceneToView(pos)
    # print('entering click func')
    self.ClickFunc(self.last_mouse_x, self.last_mouse_y)

mouseMoved(self, evt)

Updated when the mouse is moved by the user

Parameters:

Name Type Description Default
evt

event encoded by PyQt

required

Returns:

Type Description

None

Source code in src/controller/plots/array_map.py
def mouseMoved(self, evt):
    """Updated when the mouse is moved by the user

    Args:
        evt: event encoded by PyQt

    Returns:
        None

    """
    pos = evt[0]  # using signal proxy turns original arguments into a tuple
    if self.window.sceneBoundingRect().contains(pos):
        mousePoint = self.vb.mapSceneToView(pos)
        self.last_mouse_x = mousePoint.x()
        self.last_mouse_y = mousePoint.y()
        self.HoverFunc(mousePoint.x(), mousePoint.y())

calculate_one_elec_color_and_size(app, idx)

Parameters:

Name Type Description Default
app

MainWindow

required
idx int

the index of the electrode (0-1023) to be calculated

required

Returns:

Type Description

None

Source code in src/controller/plots/array_map.py
def calculate_one_elec_color_and_size(app, idx: int):
    """

    Args:
        app: MainWindow
        idx: the index of the electrode (0-1023) to be calculated

    Returns:
        None

    """
    # calculate the dot color from electrode's average spike amplitude
    spike_avg_amp = app.data.df.at[idx, "spikes_avg_amp"]

    levels = app.array_map_color_bar.levels()
    spike_avg_amp = np.clip(spike_avg_amp, levels[0], levels[1])
    spike_avg_amp = (spike_avg_amp - levels[0]) / (levels[1] - levels[0])
    color = spike_avg_amp
    app.data.df.at[idx, "array_dot_color"] = color

    # calculate the dot size from electrode's average spike count
    spikes_cnt = app.data.df.at[idx, "spikes_cnt"]
    size = (spikes_cnt / app.data.stats["largest_spike_cnt"]) * app.settings["max_dot_size"]
    size = np.clip(size, app.settings["min_dot_size"], app.settings["max_dot_size"])
    app.data.df.at[idx, "array_dot_size"] = size
    return color, size

on_color_bar_levels_changed(app)

called when the color bar is interacted with by the user in the viewing mode, changes the colors of the dots on the array map

Parameters:

Name Type Description Default
app

MainWindow

required

Returns:

Type Description

None

Source code in src/controller/plots/array_map.py
def on_color_bar_levels_changed(app):
    """ called when the color bar is interacted with by the user in the viewing mode, changes the
    colors of the dots on the array map

    Args:
        app: MainWindow

    Returns:
        None

    """
    recalculate_all_colors(app)
    elecs_points = []
    color_map = app.array_map_color_bar.colorMap()
    for row in range(NUM_TOTAL_ROWS):
        for col in range(NUM_TOTAL_COLS):
            from src.model.data_loading_mat import map2idx
            idx = map2idx(row, col)
            color = color_map.map(app.data.df.at[idx, "array_dot_color"])


            default_elec_dict = {'pos': (col, row), 'size': app.data.df.at[idx, "array_dot_size"],
                                 'pen': color,
                                 'brush': color,
                                 'symbol': 'o'}
            elecs_points.append(default_elec_dict)
    elecs_plot = pg.ScatterPlotItem(pxMode=False)
    elecs_plot.addPoints(elecs_points)
    app.update_subplot_element("arrayMap", 'default_elecs_plot', elecs_plot)

recalculate_all_colors(app)

Parameters:

Name Type Description Default
app

MainWindow

required

Returns:

Type Description

None

Source code in src/controller/plots/array_map.py
def recalculate_all_colors(app):
    """

    Args:
        app: MainWindow

    Returns:
        None

    """
    spikes_avg_amp = np.array(app.data.df["spikes_avg_amp"])
    levels = app.array_map_color_bar.levels()
    spikes_avg_amp = np.clip(np.abs(spikes_avg_amp), levels[0], levels[1])
    spikes_avg_amp = (spikes_avg_amp - levels[0]) / (levels[1] - levels[0])

    app.data.df["array_dot_color"] = spikes_avg_amp

recalculate_all_sizes(app)

Parameters:

Name Type Description Default
app

MainWindow

required

Returns:

Type Description

None

Source code in src/controller/plots/array_map.py
def recalculate_all_sizes(app):
    """

    Args:
        app: MainWindow

    Returns:
        None

    """
    spikes_cnt = np.array(app.data.df["spikes_cnt"])
    sizes = (spikes_cnt / app.data.stats["largest_spike_cnt"]) * app.settings["max_dot_size"]
    sizes = np.clip(sizes, app.settings["min_dot_size"], app.settings["max_dot_size"])
    app.data.df["array_dot_size"] = sizes

    if app.arrayMapHoverCoords is not None:
        x, y = app.arrayMapHoverCoords
        if 0 <= x <= 31 and 0 <= y <= 31:
            from src.model.DC1DataContainer import map2idx
            idx = map2idx(x, y)
            spike_cnt = app.data.df.at[idx, "spikes_cnt"]
            spike_amp = app.data.df.at[idx, "spikes_avg_amp"]
            # spike_cnt = app.model.array_indexed['stats_spikes+cnt'][y][x + 1]
            # spike_amp = app.model.array_indexed['stats_spikes+avg+amp'][y][x + 1]
            from src.model.data_loading_mat import map2idx
            channel_idx = map2idx(y, x)
            tooltip_text = "<html>" + "Electrode Channel #" + str(channel_idx) + "<br>" + \
                           "Row " + str(y) + ", Column " + str(x) + "<br>" + \
                           "Spike Count: " + str(round(spike_cnt)) + "<br>" + \
                           "Spike Amplitude: " + str(round(spike_amp, 3)) + "<\html>"
            app.charts["arrayMap"].setToolTip(str(tooltip_text))

setupArrayMap(app, plot_widget, CURRENT_THEME, themes)

Parameters:

Name Type Description Default
app

MainWindow

required
plot_widget

reference to pyqtgraph widget

required
CURRENT_THEME str

current GUI theme

required
themes dict

dictionary of theme colors

required

Returns:

Type Description

None

Source code in src/controller/plots/array_map.py
def setupArrayMap(app, plot_widget, CURRENT_THEME: str, themes: dict):
    """

    Args:
        app: MainWindow
        plot_widget: reference to pyqtgraph widget
        CURRENT_THEME: current GUI theme
        themes: dictionary of theme colors

    Returns:
        None

    """
    plot_widget.showGrid(x=False, y=False, alpha=0)
    plot_widget.setAspectLocked()

    plot_widget.setLimits(xMin=-7, xMax=39,
                          yMin=-7, yMax=39,
                          minXRange=5, maxXRange=100,
                          minYRange=5, maxYRange=100)

    plot_widget.getPlotItem().hideAxis('top')
    plot_widget.getPlotItem().hideAxis('bottom')
    plot_widget.getPlotItem().hideAxis('left')
    plot_widget.getPlotItem().hideAxis('right')

    cm = pg.colormap.get('plasma', source='matplotlib')

    colors = np.array(app.data.df["spikes_avg_amp"]).reshape((32, 32))

    # the pyqtgraph color bar REQUIRES it to be set to an image
    # however, we don't want to use an image, we want it to be linked with our scatter plot
    # so we make it, and set it off the screen
    tr = QtGui.QTransform()  # prepare ImageItem transformation:
    tr.translate(200, 200)  # scoot image out of view
    image = pg.ImageItem(colors.T)  # for old pixel-based model
    image.setTransform(tr)

    # bound the LinearRegionItem to the plotted model
    app.charts["arrayMap"].addItem(image)
    # TODO check if average spike amplitude makes sense w/ colors
    app.array_map_color_bar = app.charts["arrayMap"].addColorBar(image, colorMap=cm, label="Spike Amplitude",
                                                                 values=(-10, 20))  # values=(0, np.max(model)))
    app.array_map_color_bar.sigLevelsChanged.connect(lambda: on_color_bar_levels_changed(app))
    #app.charts["arrayMapHover"].region.setClipItem(image)

    update_minimap_indicator(app, CURRENT_THEME, themes)

    elecs_points = []
    for row in range(NUM_TOTAL_ROWS):
        for col in range(NUM_TOTAL_COLS):
            default_elec_dict = {'pos': (row, col), 'size': 0.1,
                         'pen': {'color': 'w'},
                         'brush': QColor(0, 0, 0, 0),
                         'symbol': 'o'}
            elecs_points.append(default_elec_dict)

    elecs_plot = pg.ScatterPlotItem(pxMode=False)
    elecs_plot.addPoints(elecs_points)
    app.update_subplot_element("arrayMap", 'default_elecs_plot', elecs_plot)

update_array_map_plot(app, next_packet, CURRENT_THEME, themes, extra_params)

Parameters:

Name Type Description Default
app

MainWindow

required
next_packet

data from the chip on the next buffer to be displayed

required
CURRENT_THEME str

current GUI theme

required
themes dict

dictionary of theme colors

required
extra_params required

Returns:

Type Description

None

Source code in src/controller/plots/array_map.py
def update_array_map_plot(app, next_packet, CURRENT_THEME: str, themes: dict, extra_params):
    """

    Args:
        app: MainWindow
        next_packet: data from the chip on the next buffer to be displayed
        CURRENT_THEME: current GUI theme
        themes: dictionary of theme colors
        extra_params:

    Returns:
        None

    """
    # CURRENT ELECTRODE BOX INDICATOR
    curr_rec_elecs_box = []
    dot_scaling_changed = False

    for i in range(len(next_packet['packet_data'])):
        # get packet info
        chan_idx = next_packet['packet_data'][i]['channel_idx']
        from src.model.data_loading_mat import idx2map
        row, col = idx2map(chan_idx)

        # add squares around electrodes currently being recorded from + visualized in spike trace
        spot_dict = {'pos': (col, row), 'size': 1,
                     'pen': {'color': pg.mkColor(themes[CURRENT_THEME]['light1']), 'width': 2},
                     'brush': QColor(255, 0, 255, 0),
                     'symbol': 's'}
        curr_rec_elecs_box.append(spot_dict)

        # check if scaling needs to be changed
        if app.data.stats['largest_spike_cnt'] < app.settings['spike_cnt_for_dot_size_saturation']:
            if app.data.df.at[chan_idx, "spikes_cnt"] > app.data.stats["largest_spike_cnt"]:
                if app.data.df.at[chan_idx, "spikes_cnt"] > app.settings['spike_cnt_for_dot_size_saturation']:
                    app.data.stats['largest_spikes_cnt'] = app.settings['spike_cnt_for_dot_size_saturation']
                else:
                    app.data.stats["largest_spike_cnt"] = app.data.df.at[chan_idx, "spikes_cnt"]
                dot_scaling_changed = True

    current_recording_elecs_indicator = pg.ScatterPlotItem(pxMode=False)
    current_recording_elecs_indicator.addPoints(curr_rec_elecs_box)
    app.update_subplot_element("arrayMap", "current_recording_elecs_indicator", current_recording_elecs_indicator)

    # update the dot information (color and size)
    idxs_to_change = []
    if dot_scaling_changed:  # all recalculate colors and sizes
        recalculate_all_sizes(app)
    else:  # calculate only for specific elecs in current buffer
        for i in range(len(next_packet['packet_data'])):
            chan_idx = next_packet['packet_data'][i]['channel_idx']
            idxs_to_change.append(chan_idx)
            calculate_one_elec_color_and_size(app, chan_idx)

    # render all the points
    elecs_points = []
    color_map = app.array_map_color_bar.colorMap()
    for row in range(NUM_TOTAL_ROWS):
        for col in range(NUM_TOTAL_COLS):
            from src.model.data_loading_mat import map2idx
            idx = map2idx(row, col)

            array_dot_color_idx = app.data.df.at[idx, "array_dot_color"]
            #if array_dot_color_idx > 0:
            #    print('ar dot color idx:', 'r', row, 'c', col, '/', array_dot_color_idx)
            color = color_map.map(array_dot_color_idx)

            default_elec_dict = {'pos': (col, row), 'size': app.data.df.at[idx, "array_dot_size"],
                                 'pen': color,
                                 'brush': color,
                                 'symbol': 'o'}
            elecs_points.append(default_elec_dict)
    elecs_plot = pg.ScatterPlotItem(pxMode=False)
    elecs_plot.addPoints(elecs_points)
    app.update_subplot_element("arrayMap", 'default_elecs_plot', elecs_plot)

update_minimap_indicator(app, CURRENT_THEME, themes)

add a square around electrodes displayed in the minimap

Parameters:

Name Type Description Default
app

MainWindow

required
CURRENT_THEME str

current GUI theme

required
themes dict

dictionary of theme colors

required

Returns:

Type Description

None

Source code in src/controller/plots/array_map.py
def update_minimap_indicator(app, CURRENT_THEME: str, themes: dict): # this is called on cursor click + on setup
    """add a square around electrodes displayed in the minimap

    Args:
        app: MainWindow
        CURRENT_THEME: current GUI theme
        themes: dictionary of theme colors

    Returns:
        None

    """

    minimap_square_indicator = pg.QtGui.QGraphicsRectItem(app.settings['cursor_row'] - 4.5,
                                                          app.settings['cursor_col'] - 2.5, 8, 4)
    minimap_square_indicator.setPen(pg.mkPen(themes[CURRENT_THEME]['blue3']))
    minimap_square_indicator.setBrush(QColor(255, 0, 255, 0))
    app.update_subplot_element("arrayMap", 'minimap_square_indicator', minimap_square_indicator)