Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Python bridge to ImageJ2/Fiji for macros, plugins (Bio-Formats, TrackMate, Analyze Particles), NumPy↔ImagePlus/ImgLib2 exchange, and ImageJ Ops. Automates Fiji headlessly from Python. Use scikit-image for pure Python without Fiji plugins; napari for visualization.
.claude/skills/jaechang-hits-pyimagej-fiji-bridge/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-11 | ✗→✓ | ▲ Improved | 270% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 320% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 334% | 0% |
| case-12 | ✗→✓ | ▲ Improved | 384% | 0% |
| case-14 | ✗→✓ | ▲ Improved | 132% | 0% |
PyImageJ provides a Python interface to ImageJ2 and Fiji through PyJNIus and scyjava, embedding a full Java Virtual Machine inside a Python process. It enables bidirectional data exchange between NumPy arrays and ImageJ's ImagePlus/ImgLib2 data structures, so you can preprocess images in Python, pass them into Fiji plugins (Bio-Formats, TrackMate, Analyze Particles, Weka segmentation), and return results back to pandas DataFrames. The library supports headless operation for scripting and batch processing, as well as GUI mode for interactive Fiji sessions.
.ijm macro files as part of a Python workflow without rewriting themscikit-image instead when you need pure Python processing without Fiji plugins — scikit-image is faster to install and avoids JVM overheadnapari instead for interactive multi-dimensional image visualization and annotation; PyImageJ does not replace a viewerpyimagej, scyjava, numpy, pandasbash# Recommended: conda installation conda create -n pyimagej -c conda-forge pyimagej openjdk=11 conda activate pyimagej # Install additional dependencies pip install pandas tifffile # Verify python -c "import imagej; ij = imagej.init('sc.fiji:fiji', mode='headless'); print(ij.getVersion())"
pythonimport imagej import numpy as np # Initialize Fiji in headless mode (downloads on first run, ~500 MB) ij = imagej.init("sc.fiji:fiji", mode="headless") print(f"ImageJ version: {ij.getVersion()}") # Create a test image, process with Gaussian blur via Ops, convert back arr = np.random.randint(0, 1000, (256, 256), dtype=np.uint16) imp = ij.py.to_imageplus(arr) blurred = ij.op().filter().gauss(imp.getProcessor(), 2.0) result = ij.py.from_imageplus(imp) print(f"Processed array shape: {result.shape}, dtype: {result.dtype}")
PyImageJ must be initialized once per Python session. The mode and endpoint determine which ImageJ distribution and GUI behavior to use.
pythonimport imagej # Headless Fiji — most common for scripts and batch jobs ij = imagej.init("sc.fiji:fiji", mode="headless") # GUI mode — opens the Fiji window (requires a display) ij = imagej.init("sc.fiji:fiji", mode="gui") # Local Fiji installation — faster startup, no download ij = imagej.init("/path/to/Fiji.app", mode="headless") # Specific Fiji version ij = imagej.init("sc.fiji:fiji:2.14.0", mode="headless") # Bare ImageJ2 without Fiji plugins ij = imagej.init("net.imagej:imagej", mode="headless") print(f"ImageJ version: {ij.getVersion()}") print(f"Headless: {ij.ui().isHeadless()}")
Open and save images using ImageJ's I/O layer (which includes Bio-Formats for proprietary formats) and convert between ImageJ and NumPy representations.
pythonimport imagej import numpy as np ij = imagej.init("sc.fiji:fiji", mode="headless") # Open any format Bio-Formats supports: CZI, LIF, ND2, ICS, TIFF, etc. imp = ij.io().open("/data/experiment.czi") print(f"Dimensions: {imp.getDimensions()}") # [W, H, C, Z, T] print(f"nSlices: {imp.getNSlices()}, nFrames: {imp.getNFrames()}") # Save image ij.io().save(imp, "/data/output.tif") print("Saved output.tif")
python# NumPy ↔ ImageJ conversion arr = np.zeros((100, 100), dtype=np.uint16) arr[30:70, 30:70] = 1000 # bright square # NumPy → ImagePlus imp = ij.py.to_imageplus(arr) print(f"ImagePlus: {imp.getWidth()}×{imp.getHeight()}, type={imp.getType()}") # ImagePlus → NumPy (returns a view where possible) arr_back = ij.py.from_imageplus(imp) print(f"NumPy array: shape={arr_back.shape}, dtype={arr_back.dtype}") # Multi-channel array: shape (C, H, W) rgb = np.random.randint(0, 255, (3, 256, 256), dtype=np.uint8) imp_rgb = ij.py.to_imageplus(rgb) print(f"Channels: {imp_rgb.getNChannels()}")
Run ImageJ macro language (IJM) snippets or macro files. Macros execute inside the ImageJ environment and can call any built-in ImageJ command.
pythonimport imagej ij = imagej.init("sc.fiji:fiji", mode="headless") # Run an inline macro string ij.macro.run("print('Hello from ImageJ macro');") # Run a macro with options string (key=value pairs) # Options string mirrors the dialog parameters of ImageJ commands macro_code = """ run("Gaussian Blur...", "sigma=2"); run("Auto Threshold", "method=Otsu white"); """ ij.macro.run(macro_code) # Run a macro file from disk ij.macro.runMacroFile("/scripts/my_analysis.ijm") # Run macro that returns a value via getResult or output string result = ij.macro.run(""" x = 42 * 2; return x; """) print(f"Macro returned: {result}")
python# Macro with current image: open → process → measure ij.io().open("/data/cells.tif") # sets current active image measure_macro = """ run("Set Measurements...", "area mean min integrated redirect=None decimal=3"); run("Analyze Particles...", "size=50-Infinity display clear summarize"); """ ij.macro.run(measure_macro) print("Analyze Particles complete; results in Results table")
ImageJ Ops is a framework of 150+ image processing operations with type-safe dispatch. Ops work on ImgLib2 Img objects and are the preferred way to call image processing algorithms programmatically.
pythonimport imagej import numpy as np ij = imagej.init("sc.fiji:fiji", mode="headless") arr = np.random.randint(100, 900, (512, 512), dtype=np.uint16) img = ij.py.to_java(arr) # converts to ImgLib2 RandomAccessibleInterval # Gaussian blur blurred = ij.op().filter().gauss(img, 2.0) blurred_np = ij.py.from_java(blurred) print(f"Blurred: {blurred_np.shape}") # Otsu threshold → binary image binary = ij.op().threshold().otsu(img) binary_np = ij.py.from_java(binary) print(f"Binary unique values: {np.unique(binary_np)}") # Morphological operations from jnius import autoclass BitType = autoclass("net.imglib2.type.logic.BitType") opened = ij.op().morphology().open(binary, [3, 3]) opened_np = ij.py.from_java(opened) print(f"After opening: {opened_np.shape}")
python# Statistics ops mean_val = ij.op().stats().mean(img) std_val = ij.op().stats().stdDev(img) print(f"Mean intensity: {mean_val:.1f}, StdDev: {std_val:.1f}") # Math ops: multiply image by scalar scaled = ij.op().math().multiply(img, ij.py.to_java(2.0)) print(f"Scaled max: {ij.py.from_java(scaled).max()}")
SciJava commands are the primary way to invoke Fiji plugins programmatically. Commands accept a dict of named parameters mirroring the plugin dialog.
pythonimport imagej ij = imagej.init("sc.fiji:fiji", mode="headless") # Open a file using Bio-Formats opener command future = ij.command().run( "loci.plugins.LociImporter", True, {"id": "/data/image.lif", "open_files": True, "autoscale": True} ) module = future.get() imp = module.getOutput("imp") print(f"Opened via Bio-Formats: {imp.getDimensions()}")
python# Run Analyze Particles as a SciJava command ij.io().open("/data/binary_mask.tif") future = ij.command().run( "ij.plugin.filter.ParticleAnalyzer", True, { "minSize": 50.0, "maxSize": float("inf"), "options": 0, # SHOW_NONE "measurements": 1, # AREA } ) future.get() print("Analyze Particles command complete") # Alternatively, run via macro string for simpler plugin invocation ij.macro.run(""" run("Analyze Particles...", "size=50-Infinity display clear summarize"); """)
Retrieve measurement results from ImageJ's Results table and ROI Manager after running Analyze Particles or other measurement commands.
pythonimport imagej import pandas as pd ij = imagej.init("sc.fiji:fiji", mode="headless") # After running Analyze Particles, read the Results table def results_to_dataframe(ij) -> pd.DataFrame: """Convert ImageJ Results table to pandas DataFrame.""" rt = ij.ResultsTable.getResultsTable() if rt is None or rt.size() == 0: return pd.DataFrame() headings = list(rt.getHeadings()) data = {col: [rt.getValue(col, i) for i in range(rt.size())] for col in headings} return pd.DataFrame(data) # Run segmentation + measurement macro ij.io().open("/data/cells.tif") ij.macro.run(""" run("Gaussian Blur...", "sigma=1.5"); setAutoThreshold("Otsu dark"); run("Convert to Mask"); run("Analyze Particles...", "size=20-Infinity display clear"); """) df = results_to_dataframe(ij) print(f"Found {len(df)} objects") print(df[["Area", "Mean", "IntDen"]].describe()) df.to_csv("particle_measurements.csv", index=False) print("Saved particle_measurements.csv")
python# Access the ROI Manager def get_roi_manager(ij): """Return the ImageJ ROI Manager instance, creating if needed.""" RoiManager = ij.py.jclass("ij.plugin.frame.RoiManager") rm = RoiManager.getInstance() if rm is None: rm = RoiManager(False) # headless=False means no GUI window return rm rm = get_roi_manager(ij) roi_count = rm.getCount() print(f"ROIs in manager: {roi_count}") # Extract bounding boxes for all ROIs rois = [] for i in range(roi_count): roi = rm.getRoi(i) bounds = roi.getBounds() rois.append({"index": i, "x": bounds.x, "y": bounds.y, "width": bounds.width, "height": bounds.height}) roi_df = pd.DataFrame(rois) print(roi_df.head())
Goal: Open a multi-channel TIFF stack, apply Gaussian blur, threshold nuclei channel, run Analyze Particles, and export per-cell measurements as CSV.
pythonimport imagej import pandas as pd import numpy as np from pathlib import Path ij = imagej.init("sc.fiji:fiji", mode="headless") def quantify_nuclei(tiff_path: str, output_csv: str, channel: int = 1, sigma: float = 1.5, min_size: int = 50) -> pd.DataFrame: """ Segment and measure nuclei in a fluorescence TIFF. Parameters ---------- tiff_path : path to single- or multi-channel TIFF output_csv : where to save results channel : 1-based channel index for nuclear stain (e.g., DAPI) sigma : Gaussian blur radius in pixels min_size : minimum nucleus area in pixels """ # Step 1: Open image imp = ij.io().open(tiff_path) print(f"Loaded: {Path(tiff_path).name} dims={imp.getDimensions()}") # Step 2: Extract channel if multi-channel if imp.getNChannels() > 1: imp.setC(channel) # Step 3: Apply Gaussian blur and threshold via macro ij.macro.run(f""" selectWindow("{imp.getTitle()}"); run("Gaussian Blur...", "sigma={sigma}"); setAutoThreshold("Otsu dark"); run("Convert to Mask"); run("Fill Holes"); run("Watershed"); """) # Step 4: Measure ij.macro.run(f""" run("Set Measurements...", "area mean min centroid integrated shape redirect=None decimal=3"); run("Analyze Particles...", "size={min_size}-Infinity display clear include summarize"); """) # Step 5: Collect results rt = ij.ResultsTable.getResultsTable() if rt is None or rt.size() == 0: print("No objects detected") return pd.DataFrame() headings = list(rt.getHeadings()) df = pd.DataFrame( {col: [rt.getValue(col, i) for i in range(rt.size())] for col in headings} ) df["source_file"] = Path(tiff_path).stem df.to_csv(output_csv, index=False) print(f"Saved {len(df)} measurements → {output_csv}") return df df = quantify_nuclei( tiff_path="/data/experiment_dapi.tif", output_csv="nuclei_measurements.csv", channel=1, sigma=1.5, min_size=50 ) print(df[["Area", "Mean", "Circ."]].describe())
Goal: Process a folder of images with an existing Fiji .ijm macro file, collect the Results table from each image into a single DataFrame.
pythonimport imagej import pandas as pd from pathlib import Path ij = imagej.init("sc.fiji:fiji", mode="headless") def run_macro_on_image(ij, image_path: str, macro_file: str) -> pd.DataFrame: """Open one image, run a macro file, return its Results table.""" ij.io().open(image_path) # Clear any previous results before running ij.macro.run("run(\"Clear Results\");") # Run macro file (macro must operate on the active image) ij.macro.runMacroFile(macro_file) rt = ij.ResultsTable.getResultsTable() if rt is None or rt.size() == 0: return pd.DataFrame() headings = list(rt.getHeadings()) return pd.DataFrame( {col: [rt.getValue(col, i) for i in range(rt.size())] for col in headings} ) input_dir = Path("/data/images") macro_file = "/scripts/measure_cells.ijm" output_csv = "batch_results.csv" all_results = [] image_files = sorted(input_dir.glob("*.tif")) for img_path in image_files: print(f"Processing: {img_path.name}") df = run_macro_on_image(ij, str(img_path), macro_file) if not df.empty: df["filename"] = img_path.name all_results.append(df) # Close all windows to free memory between images ij.macro.run("close('*');") if all_results: combined = pd.concat(all_results, ignore_index=True) combined.to_csv(output_csv, index=False) print(f"Batch complete: {len(image_files)} images, " f"{len(combined)} total measurements → {output_csv}") else: print("No results collected from any image")
| Parameter | Module | Default | Range / Options | Effect | |-----------|--------|---------|-----------------|--------| | mode | Initialization | "headless" | "headless", "gui", "interactive" | Controls whether Fiji GUI window opens; use "headless" for scripts | | endpoint | Initialization | "sc.fiji:fiji" | Maven coordinate or /path/to/Fiji.app | Selects ImageJ2 distribution; sc.fiji:fiji includes all Fiji plugins | | sigma (Gaussian blur) | Macro / Ops | 2.0 | 0.5–10.0 | Spatial smoothing radius in pixels; higher reduces noise but blurs edges | | minSize (Analyze Particles) | Plugin / Macro | 0 | pixels² or 0–Infinity | Smallest object area to include; eliminates noise particles | | method (Auto Threshold) | Macro / Ops | "Otsu" | "Otsu", "Triangle", "MaxEntropy", "Huang", "Li" | Threshold algorithm; Otsu works well for bimodal histograms | | measurements bitmask | Results / Macro | varies | OR combination of AREA=1, MEAN=2, CENTROID=4, etc. | Selects which columns appear in the Results table | | Java heap size | Initialization (env) | JVM default (~25% RAM) | set via JAVA_TOOL_OPTIONS | Limits memory for large stacks; set -Xmx8g for big images |
ij: Starting a new JVM is expensive (5–15 seconds). Create ij at module level or pass it as a parameter rather than calling imagej.init() inside a loop.python # At module top level — initialized once import imagej ij = imagej.init("sc.fiji:fiji", mode="headless")
def process(path): imp = ij.io().open(path) # reuse ij ...
JAVA_HOME is not set or the wrong JDK version is on PATH. A conda environment with openjdk=11 avoids 90% of JVM-not-found errors.run("Clear Results") and rm.reset() before each image to prevent row contamination.python ij.macro.run("run('Clear Results');") rm = get_roi_manager(ij) rm.reset()
ij.macro.run() for simple commands over ij.command().run(): Macro strings are shorter, easier to read, and use the same syntax as the Fiji Macro Recorder. Use ij.command().run() only when you need programmatic access to command outputs (module return values).ij.py.from_imageplus() copies data from the JVM to Python. For multi-step processing inside ImageJ, keep data in ImagePlus or ImgLib2 form and convert only at the end to minimize memory-copy overhead.ij.py.to_java() / ij.py.from_java() for ImgLib2 Ops: The to_imageplus() / from_imageplus() pair works with the classic ImageProcessor; to_java() / from_java() target the modern ImgLib2 type system required by ij.op().sc.fiji:fiji:2.14.0) rather than sc.fiji:fiji (latest) so your pipeline behavior does not change when Fiji releases new plugin updates.When to use: Run TrackMate particle tracking programmatically and retrieve detected spots as a DataFrame without opening the TrackMate GUI.
pythonimport imagej import pandas as pd ij = imagej.init("sc.fiji:fiji", mode="headless") # TrackMate headless via macro (scripting interface) trackmate_macro = """ run("TrackMate", ""); // For fully scripted TrackMate, use the Scripting Interface // documented at https://imagej.net/plugins/trackmate/scripting """ # Scripted TrackMate via Jython-style Java interop def run_trackmate_headless(ij, imp, radius=3.0, threshold=100.0): """Detect spots with LoG detector; return DataFrame of spot coordinates.""" from jnius import autoclass # Import TrackMate classes Model = autoclass("fiji.plugin.trackmate.Model") Settings = autoclass("fiji.plugin.trackmate.Settings") TrackMate = autoclass("fiji.plugin.trackmate.TrackMate") LogDetectorFactory = autoclass( "fiji.plugin.trackmate.detection.LogDetectorFactory") model = Model() settings = Settings(imp) # Configure LoG spot detector settings.detectorFactory = LogDetectorFactory() settings.detectorSettings = { "DO_SUBPIXEL_LOCALIZATION": True, "RADIUS": radius, "TARGET_CHANNEL": 1, "THRESHOLD": threshold, "DO_MEDIAN_FILTERING": False, } tm = TrackMate(model, settings) tm.process() # Extract spot table spots = model.getSpots() spots.setVisible(True) records = [] for spot in spots.iterable(True): records.append({ "id": spot.ID(), "x": spot.getDoublePosition(0), "y": spot.getDoublePosition(1), "z": spot.getDoublePosition(2), "frame": spot.getFeature("FRAME"), "quality": spot.getFeature("QUALITY"), }) return pd.DataFrame(records) imp = ij.io().open("/data/timelapse.tif") df = run_trackmate_headless(ij, imp, radius=3.0, threshold=50.0) print(f"Detected {len(df)} spots across {df['frame'].nunique()} frames") df.to_csv("spots.csv", index=False) print(df.head())
When to use: Import a multi-dimensional Fiji hyperstack into Python as a 5D NumPy array with the standard TZCYX axis order used by most scientific image analysis libraries.
pythonimport imagej import numpy as np ij = imagej.init("sc.fiji:fiji", mode="headless") def hyperstack_to_numpy(ij, imp) -> np.ndarray: """ Convert an ImageJ hyperstack to a NumPy array with shape (T, Z, C, Y, X). ImageJ internal order is C-Z-T (slowest to fastest in stack index). This function reorders to the TZCYX convention used by tifffile, OME, etc. """ nC = imp.getNChannels() nZ = imp.getNSlices() nT = imp.getNFrames() H = imp.getHeight() W = imp.getWidth() arr = np.zeros((nT, nZ, nC, H, W), dtype=np.uint16) for t in range(1, nT + 1): for z in range(1, nZ + 1): for c in range(1, nC + 1): idx = imp.getStackIndex(c, z, t) imp.setSlice(idx) arr[t-1, z-1, c-1] = ij.py.from_imageplus(imp) return arr imp = ij.io().open("/data/4d_experiment.tif") print(f"ImageJ dims (W,H,C,Z,T): {imp.getDimensions()}") stack = hyperstack_to_numpy(ij, imp) print(f"NumPy TZCYX shape: {stack.shape}") # e.g., (10, 15, 2, 512, 512) print(f"dtype: {stack.dtype}, max: {stack.max()}") # Save as OME-TIFF with correct axis metadata import tifffile tifffile.imwrite( "output_TZCYX.ome.tif", stack, imagej=True, metadata={"axes": "TZCYX"}, photometric="minisblack" ) print("Saved output_TZCYX.ome.tif")
| Problem | Cause | Solution | |---------|-------|----------| | JVMNotFoundException on init | JAVA_HOME not set or wrong JDK version | Install via conda: conda install -c conda-forge openjdk=11; avoid Java 17 | | RuntimeError: Fiji download failed | No internet or corporate proxy | Download Fiji manually from fiji.sc, then use imagej.init("/path/to/Fiji.app") | | java.lang.OutOfMemoryError on large stacks | JVM default heap is too small | Set export JAVA_TOOL_OPTIONS="-Xmx8g" before importing imagej | | Macro run(...) silently does nothing | No active image when macro expects one | Call ij.io().open(path) before running processing macros; check ij.WindowManager.getImageCount() | | Results table empty after Analyze Particles | Threshold not applied, or mask not binary | Verify mask is 8-bit binary (0/255) with imp.getType() == 0; run Convert to Mask before Analyze Particles | | AttributeError: 'NoneType' object on ij.ResultsTable.getResultsTable() | No measurements run yet in this session | Confirm Analyze Particles macro completed; run ij.macro.run("print(nResults);") to check count | | Plugin class not found (ClassNotFoundException) | Plugin not in this Fiji installation | Add the Fiji update site (e.g., TrackMate) or use sc.fiji:fiji endpoint which includes all default plugins | | gui mode crashes with HeadlessException | No display available (SSH/cluster) | Use mode="headless" for remote environments; GUI mode requires DISPLAY or X11 forwarding |
ij.io().open()ij.macro.run() strings| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-04 | pass→pass | 13,467 | 11,526 | -14% | 1 | 1 | 0% | 2,244 | 9,108 | +306% | 0 | 0 | — |
case-05 | pass→pass | 8,251 | 6,681 | -19% | 1 | 1 | 0% | 1,350 | 8,252 | +511% | 0 | 0 | — |
case-11 | fail→pass | 13,652 | 10,704 | -22% | 1 | 1 | 0% | 2,467 | 9,129 | +270% | 0 | 0 | — |
case-02 | fail→pass | 13,131 | 6,841 | -48% | 1 | 1 | 0% | 1,961 | 8,230 | +320% | 0 | 0 | — |
case-03 | pass→pass | 8,715 | 6,151 | -29% | 1 | 1 | 0% | 1,448 | 8,264 | +471% | 0 | 0 | — |
case-01 | fail→fail | 28,511 | 16,294 | -43% | 1 | 1 | 0% | 5,099 | 9,967 | +95% | 0 | 0 | — |
case-06 | fail→fail | 12,836 | 10,228 | -20% | 1 | 1 | 0% | 2,314 | 9,043 | +291% | 0 | 0 | — |
case-07 | fail→pass | 10,748 | 6,692 | -38% | 1 | 1 | 0% | 1,914 | 8,314 | +334% | 0 | 0 | — |
case-08 | pass→pass | 9,581 | 3,978 | -58% | 1 | 1 | 0% | 1,522 | 7,787 | +412% | 0 | 0 | — |
case-09 | pass→pass | 11,429 | 7,368 | -36% | 1 | 1 | 0% | 2,163 | 8,597 | +297% | 0 | 0 | — |
case-10 | pass→pass | 16,266 | 7,740 | -52% | 1 | 1 | 0% | 2,914 | 8,491 | +191% | 0 | 0 | — |
case-12 | fail→pass | 9,992 | 5,998 | -40% | 1 | 1 | 0% | 1,700 | 8,224 | +384% | 0 | 0 | — |
case-13 | pass→pass | 13,828 | 7,079 | -49% | 1 | 1 | 0% | 2,169 | 8,284 | +282% | 0 | 0 | — |
case-14 | fail→pass | 21,458 | 9,056 | -58% | 1 | 1 | 0% | 3,752 | 8,713 | +132% | 0 | 0 | — |
case-15 | pass→pass | 30,830 | 12,151 | -61% | 1 | 1 | 0% | 2,070 | 9,283 | +348% | 0 | 0 | — |
case-16 | pass→pass | 14,547 | 10,403 | -28% | 1 | 1 | 0% | 2,782 | 9,226 | +232% | 0 | 0 | — |
case-17 | fail→pass | 8,899 | 5,222 | -41% | 1 | 1 | 0% | 1,562 | 8,121 | +420% | 0 | 0 | — |
case-18 | pass→pass | 15,152 | 23,358 | +54% | 1 | 1 | 0% | 2,739 | 8,434 | +208% | 0 | 0 | — |
case-19 | fail→pass | 13,284 | 8,269 | -38% | 1 | 1 | 0% | 2,465 | 8,786 | +256% | 0 | 0 | — |
case-20 | pass→pass | 9,144 | 4,400 | -52% | 1 | 1 | 0% | 1,790 | 8,006 | +347% | 0 | 0 | — |
case-21 | pass→pass | 6,512 | 4,444 | -32% | 1 | 1 | 0% | 1,178 | 7,951 | +575% | 0 | 0 | — |
case-22 | pass→pass | 17,421 | 12,586 | -28% | 1 | 1 | 0% | 2,651 | 9,290 | +250% | 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 +32 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.