Skip to content

receptivefield

sleap.gui.learning.receptivefield

Widget for previewing receptive field on sample image using model hyperparams.

Classes:

Name Description
ReceptiveFieldImageWidget

Widget for showing image with receptive field.

ReceptiveFieldWidget

Widget for previewing receptive field on sample image, with caption.

Functions:

Name Description
compute_rf

Computes receptive field for specified model architecture.

receptive_field_info_from_model_cfg

Gets receptive field information given specific model configuration.

ReceptiveFieldImageWidget

Bases: GraphicsView

Widget for showing image with receptive field.

Methods:

Name Description
viewportEvent

Re-draw receptive field when needed by overriding QGraphicsView method.

Source code in sleap/gui/learning/receptivefield.py
class ReceptiveFieldImageWidget(GraphicsView):
    """Widget for showing image with receptive field."""

    def __init__(self, *args, **kwargs):
        self._widget_size = 200
        self._pen_width = 4
        self._box_size = None
        self._scale = None

        box_pen = QtGui.QPen(QtGui.QColor("blue"), self._pen_width)
        box_pen.setCosmetic(True)

        self.box = QtWidgets.QGraphicsRectItem()
        self.box.setPen(box_pen)

        super(ReceptiveFieldImageWidget, self).__init__(*args, **kwargs)

        self.setFixedSize(self._widget_size, self._widget_size)
        self.scene.addItem(self.box)

        # TODO: zoom around bounding box for labeled instance
        # self.zoomToRect(QtCore.QRectF(0, 0, 1, 1))

    def viewportEvent(self, event):
        """
        Re-draw receptive field when needed by overriding QGraphicsView method.
        """
        # Update the position and visible size of field
        if isinstance(event, QtGui.QPaintEvent):
            self._set_field_size()

        # Now draw the viewport
        return super(ReceptiveFieldImageWidget, self).viewportEvent(event)

    def _set_field_size(self, size: Optional[int] = None, scale: float = 1.0):
        """Draws receptive field preview rect, updating size if needed."""
        if size is not None:
            self._box_size = size
            self._scale = scale

        if self._box_size:
            self.box.show()
        else:
            self.box.hide()
            return

        # Adjust box relative to scaling on image that will happen in training
        scaled_box_size = self._box_size // self._scale

        # Calculate offset so that box stays centered in the view
        vis_box_rect = self.mapFromScene(
            0, 0, scaled_box_size, scaled_box_size
        ).boundingRect()
        offset = self._widget_size / 2
        scene_center = self.mapToScene(
            offset - (vis_box_rect.width() / 2), offset - (vis_box_rect.height() / 2)
        )

        self.box.setRect(
            scene_center.x(), scene_center.y(), scaled_box_size, scaled_box_size
        )

viewportEvent(event)

Re-draw receptive field when needed by overriding QGraphicsView method.

Source code in sleap/gui/learning/receptivefield.py
def viewportEvent(self, event):
    """
    Re-draw receptive field when needed by overriding QGraphicsView method.
    """
    # Update the position and visible size of field
    if isinstance(event, QtGui.QPaintEvent):
        self._set_field_size()

    # Now draw the viewport
    return super(ReceptiveFieldImageWidget, self).viewportEvent(event)

ReceptiveFieldWidget

Bases: QWidget

Widget for previewing receptive field on sample image, with caption.

Parameters:

Name Type Description Default
head_name Text

If given, then used in caption to show which model the preview is for.

''
Usage

Create, then call setImage and setModelConfig methods.

Methods:

Name Description
setImage

Sets image on which receptive field box will be drawn.

setModelConfig

Updates receptive field preview from model config.

Source code in sleap/gui/learning/receptivefield.py
class ReceptiveFieldWidget(QtWidgets.QWidget):
    """
    Widget for previewing receptive field on sample image, with caption.

    Args:
        head_name: If given, then used in caption to show which model the
            preview is for.

    Usage:
        Create, then call `setImage` and `setModelConfig` methods.
    """

    def __init__(self, head_name: Text = "", *args, **kwargs):
        super(ReceptiveFieldWidget, self).__init__(*args, **kwargs)

        self.layout = QtWidgets.QVBoxLayout()

        self._field_image_widget = ReceptiveFieldImageWidget()

        self._info_text_header = (
            f"<p>Receptive Field for {head_name}:</p>"
            if head_name
            else "<p>Receptive Field:</p>"
        )

        self._info_widget = QtWidgets.QLabel("")

        self.layout.addWidget(self._field_image_widget)
        self.layout.addWidget(self._info_widget)
        self.layout.addStretch()

        self.setLayout(self.layout)

    def _get_info_text(
        self, size, scale, max_stride, down_blocks, convs_per_block, kernel_size
    ) -> Text:
        """Returns text explaining how receptive field size is determined."""
        result = self._info_text_header
        if size:
            result += f"<p><i>{size} pixels</i></p>"
        else:
            result += "<p><i>Unable to determine size</i></p>"

        result += f"""
        <p>Receptive field size is a function<br />
        of the number of down blocks ({down_blocks}), the<br />
        number of convolutions per block ({convs_per_block}),<br />
        and the convolution kernel size ({kernel_size}).</p>

        <p>You can control the number of down<br />
        blocks by setting the <b>Max Stride</b> ({max_stride}).</p>

        <p>The number of convolutions per block<br />
        and the kernel size are currently fixed<br />
        by your choice of backbone.</p>

        <p>You can also control the receptive<br />
        field size relative to the original<br />
        image by adjusting the <b>Input Scaling</b> ({scale}).</p>
        """

        return result

    def setModelConfig(self, model_cfg: OmegaConf, scale: float):
        """Updates receptive field preview from model config."""
        rf_info = receptive_field_info_from_model_cfg(model_cfg)

        self._info_widget.setText(
            self._get_info_text(
                size=rf_info["size"],
                scale=scale,
                max_stride=rf_info["max_stride"],
                down_blocks=rf_info["down_blocks"],
                convs_per_block=rf_info["convs_per_block"],
                kernel_size=rf_info["kernel_size"],
            )
        )

        self._field_image_widget._set_field_size(rf_info["size"] or 0, scale)

    def setImage(self, *args, **kwargs):
        """Sets image on which receptive field box will be drawn."""
        self._field_image_widget.setImage(*args, **kwargs)

setImage(*args, **kwargs)

Sets image on which receptive field box will be drawn.

Source code in sleap/gui/learning/receptivefield.py
def setImage(self, *args, **kwargs):
    """Sets image on which receptive field box will be drawn."""
    self._field_image_widget.setImage(*args, **kwargs)

setModelConfig(model_cfg, scale)

Updates receptive field preview from model config.

Source code in sleap/gui/learning/receptivefield.py
def setModelConfig(self, model_cfg: OmegaConf, scale: float):
    """Updates receptive field preview from model config."""
    rf_info = receptive_field_info_from_model_cfg(model_cfg)

    self._info_widget.setText(
        self._get_info_text(
            size=rf_info["size"],
            scale=scale,
            max_stride=rf_info["max_stride"],
            down_blocks=rf_info["down_blocks"],
            convs_per_block=rf_info["convs_per_block"],
            kernel_size=rf_info["kernel_size"],
        )
    )

    self._field_image_widget._set_field_size(rf_info["size"] or 0, scale)

compute_rf(down_blocks, convs_per_block=2, kernel_size=3)

Computes receptive field for specified model architecture.

Ref: https://distill.pub/2019/computing-receptive-fields/ (Eq. 2)

Source code in sleap/gui/learning/receptivefield.py
def compute_rf(down_blocks: int, convs_per_block: int = 2, kernel_size: int = 3) -> int:
    """
    Computes receptive field for specified model architecture.

    Ref: https://distill.pub/2019/computing-receptive-fields/ (Eq. 2)
    """
    # Define the strides and kernel sizes for a single down block.
    # convs have stride 1, pooling has stride 2:
    block_strides = [1] * convs_per_block + [2]

    # convs have `kernel_size` x `kernel_size` kernels, pooling has 2 x 2 kernels:
    block_kernels = [kernel_size] * convs_per_block + [2]

    # Repeat block parameters by the total number of down blocks.
    strides = np.array(block_strides * down_blocks)
    kernels = np.array(block_kernels * down_blocks)

    # L = Total number of layers
    L = len(strides)

    # Compute the product term of the RF equation.
    rf = 1
    for l in range(L):
        rf += (kernels[l] - 1) * np.prod(strides[:l])

    return int(rf)

receptive_field_info_from_model_cfg(cfg)

Gets receptive field information given specific model configuration.

Source code in sleap/gui/learning/receptivefield.py
def receptive_field_info_from_model_cfg(cfg: OmegaConf) -> dict:
    """Gets receptive field information given specific model configuration."""
    model_cfg = cfg.model_config
    model_cfg.backbone_config.unet.max_stride = int(
        model_cfg.backbone_config.unet.max_stride
    )

    rf_info = dict(
        size=None,
        max_stride=None,
        down_blocks=None,
        convs_per_block=None,
        kernel_size=None,
    )
    head_type = get_head_from_omegaconf(cfg)
    output_strides = []
    for k, head_cfg in model_cfg.head_configs[head_type].items():
        if k == "class_vectors":
            output_strides.append(int(model_cfg.backbone_config.unet.max_stride))
        else:
            output_strides.append(int(head_cfg.output_stride))
    output_stride = min(output_strides)
    # Currently, this works only for UNet backbones.
    # TODO: Add support for other backbones.
    try:
        _ = np.log2(model_cfg.backbone_config.unet.max_stride / output_stride)
    except ZeroDivisionError:
        # Unable to create model from these config parameters
        return rf_info

    if hasattr(model_cfg.backbone_config.unet, "max_stride"):
        rf_info["max_stride"] = model_cfg.backbone_config.unet.max_stride

    rf_info["convs_per_block"] = 2

    rf_info["kernel_size"] = 3

    stem_blocks = 0
    if hasattr(model_cfg.backbone_config.unet, "stem_stride"):
        cfg_stem_stride = model_cfg.backbone_config.unet.stem_stride
        if cfg_stem_stride is not None:
            stem_blocks = np.log2(cfg_stem_stride).astype(int)

    down_blocks = (
        np.log2(model_cfg.backbone_config.unet.max_stride).astype(int) - stem_blocks
    )

    rf_info["down_blocks"] = down_blocks

    if rf_info["down_blocks"] and rf_info["convs_per_block"] and rf_info["kernel_size"]:
        rf_info["size"] = compute_rf(
            down_blocks=rf_info["down_blocks"],
            convs_per_block=rf_info["convs_per_block"],
            kernel_size=rf_info["kernel_size"],
        )

    return rf_info