Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Interactive viewer for microscopy. Displays 2D/3D/4D arrays as Image, Labels, Points, Shapes, Tracks layers; supports annotation, plugin analysis, headless screenshots. Core visualization for Python bioimage workflows. Use ImageJ/FIJI for macro processing; napari for Python-native interactive visualization and DL segmentation review.
.claude/skills/jaechang-hits-napari-image-viewer/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-05 | ✗→✓ | ▲ Improved | 241% | 0% |
| case-01 | ✓→✓ | = Same ✓ | 243% | 0% |
| case-02 | ✓→✓ | = Same ✓ | 612% | 0% |
| case-03 | ✓→✓ | = Same ✓ | 839% | 0% |
| case-04 | ✓→✓ | = Same ✓ | 462% | 0% |
napari is a fast, interactive multi-dimensional viewer for scientific data built on PyQt5 and VisPy. It displays NumPy arrays and zarr arrays as layered visualizations — Image layers for raw data, Labels layers for segmentation masks, Points layers for cell centroids, and Shapes layers for ROI annotations. napari integrates with scikit-image, Cellpose, and StarDist via plugins, making it the standard visualization and annotation tool in Python bioimage analysis pipelines. For headless environments (HPC, CI), napari supports offscreen rendering and viewer.screenshot() for automated figure generation.
napari, numpy, scikit-imageQT_QPA_PLATFORM=offscreennapari-cellpose, napari-stardist, napari-animationbash# Install with all backends pip install "napari[all]" # Or minimal install pip install napari pyqt5 # Verify python -c "import napari; print(napari.__version__)" # 0.5.5 # Install useful plugins pip install napari-cellpose napari-animation
pythonimport napari import numpy as np from skimage import data # Open viewer with a sample image viewer = napari.Viewer() viewer.add_image(data.cells3d()[:, 1, :, :], name="DAPI", colormap="blue") napari.run() # blocks until viewer closed (use in scripts)
Add and configure multi-channel image layers.
pythonimport napari import numpy as np from skimage import io viewer = napari.Viewer() # Add single grayscale image img = io.imread("cells.tif") # shape: (H, W) viewer.add_image(img, name="phase contrast", colormap="gray", contrast_limits=[0, img.max()]) # Add multichannel image (3 channels) img_mc = io.imread("multichannel.tif") # shape: (H, W, 3) viewer.add_image(img_mc[..., 0], name="DAPI", colormap="blue", blending="additive") viewer.add_image(img_mc[..., 1], name="GFP", colormap="green", blending="additive") viewer.add_image(img_mc[..., 2], name="mCherry", colormap="red", blending="additive") print(f"Layers: {[l.name for l in viewer.layers]}")
Display and edit integer label masks from Cellpose, StarDist, or scikit-image.
pythonimport napari import numpy as np from skimage import io viewer = napari.Viewer() img = io.imread("cells.tif") masks = np.load("masks.npy") # integer label array: 0=background, 1..N=cells # Add raw image viewer.add_image(img, name="raw", colormap="gray") # Add label mask (each cell gets a unique random color) label_layer = viewer.add_labels(masks, name="cell_masks", opacity=0.5) # Access labels for editing print(f"Unique cells: {len(np.unique(masks)) - 1}") print(f"Label layer data shape: {label_layer.data.shape}")
Add and style point markers for centroids, landmarks, or detected features.
pythonimport napari import numpy as np import pandas as pd from skimage.measure import regionprops_table viewer = napari.Viewer() # Compute centroids from label mask masks = np.load("masks.npy") props = regionprops_table(masks, properties=["centroid", "label"]) centroids = np.column_stack([props["centroid-0"], props["centroid-1"]]) # Add centroids as Points layer viewer.add_points( centroids, name=f"centroids ({len(centroids)} cells)", size=8, face_color="yellow", edge_color="black", edge_width=0.5, ) print(f"Cells marked: {len(centroids)}")
Add bounding boxes, polygons, and line annotations.
pythonimport napari import numpy as np viewer = napari.Viewer() # Add rectangles as ROIs (format: [[y1, x1], [y2, x2]]) rois = [ np.array([[50, 100], [200, 300]]), # ROI 1 np.array([[300, 150], [450, 350]]), # ROI 2 ] shapes_layer = viewer.add_shapes( rois, shape_type="rectangle", name="ROIs", edge_color="cyan", face_color="transparent", edge_width=2, ) # Retrieve shapes data for analysis for i, shape in enumerate(shapes_layer.data): y_min, x_min = shape.min(axis=0) y_max, x_max = shape.max(axis=0) print(f"ROI {i+1}: y={y_min:.0f}-{y_max:.0f}, x={x_min:.0f}-{x_max:.0f}")
Display z-stacks and time series with sliders.
pythonimport napari import numpy as np from skimage import data viewer = napari.Viewer() # 3D z-stack: shape (Z, H, W) zstack = data.cells3d()[:, 1, :, :] # nuclei channel viewer.add_image(zstack, name="z-stack nuclei", colormap="cyan", blending="additive") # 4D time-lapse: shape (T, H, W) or (T, Z, H, W) timelapse = np.random.randint(0, 65535, (10, 256, 256), dtype=np.uint16) viewer.add_image(timelapse, name="timelapse", colormap="gray") # napari shows axis sliders automatically for ndim > 2 print(f"z-stack shape: {zstack.shape} → slider for Z axis") print(f"timelapse shape: {timelapse.shape} → sliders for T axis")
Export screenshots without a display (for HPC and CI environments).
pythonimport os os.environ["QT_QPA_PLATFORM"] = "offscreen" # must be set BEFORE importing napari import napari import numpy as np from skimage import io, data import matplotlib matplotlib.use("Agg") # also set matplotlib backend viewer = napari.Viewer(show=False) img = data.cells3d()[30, 1, :, :] # single z-slice masks = (img > img.mean()).astype(int) # simple threshold mask viewer.add_image(img, name="DAPI", colormap="blue", blending="additive") viewer.add_labels(masks.astype(np.int32), name="masks", opacity=0.5) # Export screenshot screenshot = viewer.screenshot(path="napari_export.png", canvas_only=True) print(f"Screenshot saved: napari_export.png ({screenshot.shape})") viewer.close()
| Parameter | Module | Default | Effect | |-----------|--------|---------|--------| | colormap | add_image | "gray" | Colormap name (matplotlib cmaps + napari built-ins: "green", "blue", "cyan") | | contrast_limits | add_image | auto | [min, max] intensity clipping for display | | blending | add_image | "translucent" | "additive" for multichannel overlay; "opaque" for solid | | opacity | add_labels | 0.7 | 0–1 transparency of label layer over image | | face_color | add_points | "white" | Point fill color (name, hex, or RGBA) | | size | add_points | 10 | Point radius in data coordinates (pixels) | | edge_width | add_shapes | 1 | Shape outline width in pixels | | show | Viewer() | True | False for headless/offscreen mode | | ndisplay | Viewer() | 2 | 3 for 3D OpenGL rendering mode | | canvas_only | screenshot() | False | True to exclude the napari toolbar from export |
pythonimport os os.environ["QT_QPA_PLATFORM"] = "offscreen" import napari import numpy as np from cellpose import models from skimage import io from skimage.measure import regionprops_table # Segment with Cellpose img = io.imread("cells.tif") model = models.Cellpose(model_type="cyto3", gpu=False) masks, _, _, diams = model.eval(img, diameter=0, channels=[0, 0]) # Visualize in napari (headless for export) viewer = napari.Viewer(show=False) viewer.add_image(img, name="raw", colormap="gray") viewer.add_labels(masks, name=f"masks ({masks.max()} cells)", opacity=0.6) # Add centroids props = regionprops_table(masks, properties=["centroid"]) centroids = np.column_stack([props["centroid-0"], props["centroid-1"]]) viewer.add_points(centroids, name="centroids", size=6, face_color="yellow") viewer.screenshot(path="segmentation_review.png", canvas_only=True) viewer.close() print(f"QC export: segmentation_review.png — {masks.max()} cells detected")
pythonimport napari import numpy as np from skimage import io # Load 4-channel FISH image: DAPI + 3 RNA probes img = io.imread("fish_4channel.tif") # shape: (H, W, 4) viewer = napari.Viewer() channels = [ ("DAPI", "blue", img[..., 0]), ("probe_A_cy3", "yellow", img[..., 1]), ("probe_B_cy5", "red", img[..., 2]), ("probe_C_gfp", "green", img[..., 3]), ] for name, colormap, channel in channels: viewer.add_image(channel, name=name, colormap=colormap, blending="additive", contrast_limits=[channel.min(), np.percentile(channel, 99.5)]) napari.run()
pythonimport os os.environ["QT_QPA_PLATFORM"] = "offscreen" import napari import numpy as np from skimage import io import matplotlib.pyplot as plt viewer = napari.Viewer(show=False) img = io.imread("cells.tif") masks = np.load("masks.npy") viewer.add_image(img, name="raw", colormap="gray") viewer.add_labels(masks, name="segmentation", opacity=0.5) # Set camera zoom and position viewer.camera.zoom = 1.5 viewer.camera.center = (img.shape[0] // 2, img.shape[1] // 2) screenshot = viewer.screenshot(path="figure_panel.png", canvas_only=True) viewer.close() # Add scalebar with matplotlib fig, ax = plt.subplots(figsize=(6, 6)) ax.imshow(screenshot) ax.axis("off") plt.tight_layout() plt.savefig("figure_final.pdf", dpi=300, bbox_inches="tight") print("Exported: figure_final.pdf")
pythonimport os os.environ["QT_QPA_PLATFORM"] = "offscreen" import napari import numpy as np from skimage import io from pathlib import Path output_dir = Path("projections") output_dir.mkdir(exist_ok=True) for img_path in sorted(Path("zstacks").glob("*.tif")): zstack = io.imread(img_path) # shape: (Z, H, W) max_proj = zstack.max(axis=0) viewer = napari.Viewer(show=False) viewer.add_image(max_proj, name="max_projection", colormap="gray") viewer.screenshot(path=str(output_dir / f"{img_path.stem}_maxproj.png"), canvas_only=True) viewer.close() print(f"Exported: {img_path.stem}_maxproj.png") print("All z-stack projections exported.")
| Problem | Cause | Solution | |---------|-------|----------| | qt.qpa.plugin: Could not load the Qt platform plugin "xcb" | Missing display or Qt platform plugin | Set QT_QPA_PLATFORM=offscreen before importing napari; install libxcb-util-dev | | napari window does not open | Running in SSH without X forwarding | Use viewer = napari.Viewer(show=False) and export via screenshot() | | Slow rendering of large images | Image too large for GPU VRAM | Use viewer.add_image(img, multiscale=True) for pyramidal rendering | | Labels layer shows wrong colors | Mask dtype overflow | Ensure masks are int32 not uint8 (overflow at 255 cells) | | napari.run() blocks Jupyter notebook | Qt event loop conflict | Use %gui qt magic in Jupyter; or use viewer.show() without napari.run() | | Screenshot is black/empty | Viewer not fully rendered before screenshot | Add viewer.update() or slight delay before screenshot() | | Plugin not appearing in menu | Plugin not installed or wrong napari version | pip install napari-<plugin>; check napari version compatibility on napari-hub | | 3D rendering slow | Complex geometry or large volume | Switch viewer.dims.ndisplay = 2; reduce z-stack depth |
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | pass→pass | 8,785 | 9,079 | +3% | 1 | 1 | 0% | 1,416 | 4,852 | +243% | 0 | 0 | — |
case-02 | pass→pass | 3,964 | 2,814 | -29% | 1 | 1 | 0% | 602 | 4,284 | +612% | 0 | 0 | — |
case-03 | pass→pass | 3,079 | 2,185 | -29% | 1 | 1 | 0% | 444 | 4,170 | +839% | 0 | 0 | — |
case-04 | pass→pass | 5,137 | 4,559 | -11% | 1 | 1 | 0% | 806 | 4,530 | +462% | 0 | 0 | — |
case-05 | fail→pass | 8,205 | 5,786 | -29% | 1 | 1 | 0% | 1,425 | 4,866 | +241% | 0 | 0 | — |
case-06 | pass→pass | 5,532 | 6,581 | +19% | 1 | 1 | 0% | 833 | 4,935 | +492% | 0 | 0 | — |
case-07 | pass→pass | 6,060 | 5,193 | -14% | 1 | 1 | 0% | 919 | 4,672 | +408% | 0 | 0 | — |
case-08 | pass→pass | 7,747 | 4,717 | -39% | 1 | 1 | 0% | 1,423 | 4,706 | +231% | 0 | 0 | — |
case-09 | pass→pass | 4,077 | 3,110 | -24% | 1 | 1 | 0% | 621 | 4,372 | +604% | 0 | 0 | — |
case-10 | pass→pass | 6,593 | 3,526 | -47% | 1 | 1 | 0% | 1,226 | 4,437 | +262% | 0 | 0 | — |
case-11 | pass→pass | 4,326 | 3,704 | -14% | 1 | 1 | 0% | 713 | 4,469 | +527% | 0 | 0 | — |
case-12 | pass→pass | 4,961 | 2,769 | -44% | 1 | 1 | 0% | 864 | 4,240 | +391% | 0 | 0 | — |
case-13 | pass→pass | 3,036 | 2,571 | -15% | 1 | 1 | 0% | 509 | 4,312 | +747% | 0 | 0 | — |
case-14 | fail→fail | 15,485 | 10,248 | -34% | 1 | 1 | 0% | 2,647 | 5,639 | +113% | 0 | 0 | — |
case-15 | pass→pass | 9,039 | 6,176 | -32% | 1 | 1 | 0% | 1,640 | 4,983 | +204% | 0 | 0 | — |
case-16 | pass→pass | 3,206 | 4,716 | +47% | 1 | 1 | 0% | 484 | 4,590 | +848% | 0 | 0 | — |
case-17 | pass→pass | 10,915 | 4,333 | -60% | 1 | 1 | 0% | 1,952 | 4,647 | +138% | 0 | 0 | — |
case-18 | pass→pass | 9,049 | 5,419 | -40% | 1 | 1 | 0% | 1,609 | 4,901 | +205% | 0 | 0 | — |
case-19 | pass→pass | 7,570 | 11,081 | +46% | 1 | 1 | 0% | 1,073 | 4,666 | +335% | 0 | 0 | — |
case-20 | pass→pass | 7,688 | 5,926 | -23% | 1 | 1 | 0% | 1,174 | 4,813 | +310% | 0 | 0 | — |
case-21 | pass→pass | 15,164 | 15,000 | -1% | 1 | 1 | 0% | 2,909 | 6,719 | +131% | 0 | 0 | — |
case-22 | pass→pass | 10,604 | 7,751 | -27% | 1 | 1 | 0% | 1,965 | 5,260 | +168% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +5 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.