Edit on GitHub

starplast.gallery

The gallery: every embedding a walk produces, visible while the walk is still running.

A hyperparameter walk builds one map per configuration, scores it, and -- until this existed -- threw the map away and reported a number. That is the wrong way round for the question being asked. Nobody sweeps 288 configurations to find out that trustworthiness was 0.91; they sweep them to find a map where a compartment falls out as its own island, and no scalar says whether that happened. So the maps are kept and shown, and the scores travel with them as captions rather than as the whole answer.

Two modes, because two different things are being done:

  • grid -- a wall of thumbnails, for finding the one configuration that looks unlike the others. Comparison is the point, so every thumbnail is rendered at one size, from one projection, on one background: a difference between two tiles is then a difference between two maps.
  • scroll -- one large view with a slider stepping through the configurations in order, for watching what a single hyperparameter does as it changes. A grid shows which is different; a scroll shows how it got that way.

Both fill in as the walk runs. add() takes one tuning.WalkStep and is called the moment that configuration finishes, which is the difference between a tool and a progress bar -- run 12 can be opened and clicked into while run 13 is still computing. In scroll mode the view follows the newest map only while the slider is already at the end, the way a log tail does; once the user steps back to look at something, the walk stops moving the view out from under them.

Thumbnails are painted, not grabbed from the GL view. Rendering 288 offscreen framebuffers needs a live GL context per thumbnail, is slow, and fails on exactly the headless machines the test suite runs on. A thumbnail is a small orthographic projection of the same coordinates -- deterministic, fast enough to keep up with a walk, and testable without a display. It is a picture; the map it opens into is the application's own 3D view, where a gene can be clicked like any other.

Color comes from the caller (color_fn), so a thumbnail is colored by whatever the main map is colored by. Gray means unknown there and means unknown here.

THUMB = 150
LARGE = 460
DEFAULT_POINT = (0.55, 0.72, 0.9, 0.85)
def project(coords: numpy.ndarray, size: int, margin: float = 0.06):

Coordinates to pixels: an orthographic view down the third axis, framed to the box.

Framed on the data rather than on a fixed extent, because an embedding's scale depends on its feature set -- a shared fixed frame would show one walk's maps as specks and another's clipped. Both axes are scaled by the SAME factor, so a map that is genuinely elongated still looks elongated; scaling each axis to fill the box would make every configuration look equally isotropic, which is precisely the difference the gallery exists to show.

def thumbnail( coords, colors=None, size: int = 150, background=(0.06, 0.07, 0.09), point: float = 2.0) -> PyQt6.QtGui.QImage:

One configuration's map as a small image.

Points are drawn back to front along the third component so that near points cover far ones, the same way the 3D view occludes rather than blending -- overlapping points that sum would turn a dense thumbnail white and every configuration would look alike.

def caption(step) -> str:

The configuration and what it scored, in one line.

The hyperparameters lead because they are what distinguishes one tile from the next; the scores follow because they are the reason to look closer, not the answer. Missing scores are left out rather than printed as nan -- a walk run without the cluster check has no cluster count, and "clusters=nan" reads as a failure rather than as a question that was not asked.

class GalleryPanel(PyQt6.QtWidgets.QWidget):

Thumbnails of every configuration a walk has finished, in grid or scroll form.

Holds the WalkSteps themselves, not only their pictures, so that clicking a tile can hand the real coordinates back to the window rather than a bitmap. chosen is what the window connects to.

GalleryPanel(color_fn=None, background=(0.06, 0.07, 0.09), parent=None)
def chosen(unknown):

pyqtSignal(*types, name: str = ..., revision: int = ..., arguments: Sequence = ...) -> PYQT_SIGNAL

types is normally a sequence of individual types. Each type is either a type object or a string that is the name of a C++ type. Alternatively each type could itself be a sequence of types each describing a different overloaded signal. name is the optional C++ name of the signal. If it is not specified then the name of the class attribute that is bound to the signal is used. revision is the optional revision of the signal that is exported to QML. If it is not specified then 0 is used. arguments is the optional sequence of the names of the signal's arguments.

color_fn
background
steps
mode_box
count
list
big
caption
slider
open_btn
stack
hint
def clear(self):

Drop every map. Called when a new walk starts, so two walks cannot be read as one.

CLUSTER_COLORS = [(0.95, 0.75, 0.2), (0.35, 0.7, 0.95), (0.45, 0.85, 0.45), (0.95, 0.45, 0.55), (0.7, 0.55, 0.95), (0.3, 0.85, 0.8), (0.95, 0.6, 0.3), (0.6, 0.8, 0.35)]
NOISE_COLOR = (0.45, 0.45, 0.48, 0.55)
def cluster_colors(self, labels) -> numpy.ndarray:

One color per point from a clustering, noise in gray.

def colors_for(self, step):

The point colors for one step: its own clustering if it has one, else the window's.

def add(self, step):

Add one finished configuration, and show it if the view is at the end.

Following the newest map only when the slider is already at the last one is the log-tail rule: a walk that yanked the view to run 13 while run 12 was being read would make the gallery unusable during exactly the period it exists for.

def set_mode(self, mode: str):

Switch between the wall of thumbnails and the single stepped view.

The chooser is set from here as well as reading into it, so that a mode changed in code -- by a test, by a keyboard shortcut, by anything but the combo box itself -- does not leave the control claiming one mode while the panel shows the other.

def show_index(self, i: int):

Render one configuration into the large view.

def current(self):

The step the scroll view is showing, or None if nothing has arrived yet.

def expand_current(self):

Ask for the scroll view's configuration to be shown in the central 3D view.