Skip to content

Latest commit

Β 

History

History
1244 lines (829 loc) Β· 22.9 KB

File metadata and controls

1244 lines (829 loc) Β· 22.9 KB
theme the-unnamed
background https://images.unsplash.com/photo-1451187580459-43490279c0fa?q=80&w=2072
title Appose Workshop
info ## Appose: Interprocess Cooperation with Shared Memory I2K 2025 Workshop Teaching participants how to use Appose with Fiji for Python integration
class text-center
drawings
persist
transition slide-left
mdc true
duration 120min

Appose Workshop

Interprocess Cooperation with Shared Memory

Curtis Rueden @ UW-Madison LOCI
"Halfway to I2K" 2025

Press Space to begin

SLIDES: https://fiji.github.io/i2k-2025-appose/
VIDEO: https://youtu.be/9Rf4ynSDozc

--- layout: default ---

What is Appose?

"Interprocess cooperation with shared memory"

  • πŸ”„ Interprocess - Multiple processes connected via communication protocol
  • 🀝 Cooperation - Build environments, start services, run tasks
  • πŸ’Ύ Shared Memory - Cross-platform, cross-language memory buffers

layout: default

What Appose is NOT

Appose β‰  Fiji's Python Mode

Python Mode: Runs Python in the same process as Java

  • Powered by PyImageJ (imglyb, scyjava, jgo, jpype) and enabled by Jaunch

Pros βœ…

  • Memory can be shared directly
  • Java objects wrapped as Python objects via JPype
  • Write true Python (CPython, not Jython) scripts in Fiji's Script Editor

Cons ❌

  • Fiji can only run in Python mode with one environment at a time
  • No switching between deep learning tools with incompatible dependencies
  • All tools must share the same Python environment

Appose solves this!

Multiple isolated environments, each with their own dependencies, running simultaneously.


layout: default

Interprocess

Multiple processes (programs) connected via communication protocol

Examples

  • Fiji (Java) β†’ Python program
  • napari (Python) β†’ Java program
  • Java ↔ Java
  • Python ↔ Python
  • Python ↔ R

::right::

Pros βœ…

  • Called program doesn't need to "know about" caller
  • Clean separation of concerns
  • Use best tool for each job

Cons ❌

  • Each process has own memory space
  • No direct memory sharing (traditionally)

layout: default

Cooperation

How Appose facilitates
interprocess cooperation:

graph TD
    Env[Environment] -->|contains| S1[Service/Worker 1]
    Env -->|contains| S2[Service/Worker 2]
    Env -->|contains| S3[Service/Worker ...]
    S1 -->|executes| T1[Task 1]
    S1 -->|executes| T2[Task 2]
    S1 -->|executes| T3[Task ...]
    S2 -->|executes| T4[Task 1]
    S2 -->|executes| T5[Task 2]

    style Env fill:#4a9eff,stroke:#333,stroke-width:3px,color:#fff
    style S1 fill:#6bc95f,stroke:#333,stroke-width:2px,color:#fff
    style S2 fill:#6bc95f,stroke:#333,stroke-width:2px,color:#fff
    style S3 fill:#6bc95f,stroke:#333,stroke-width:2px,color:#fff
    style T1 fill:#ffa726,stroke:#333,stroke-width:1px,color:#fff
    style T2 fill:#ffa726,stroke:#333,stroke-width:1px,color:#fff
    style T3 fill:#ffa726,stroke:#333,stroke-width:1px,color:#fff
    style T4 fill:#ffa726,stroke:#333,stroke-width:1px,color:#fff
    style T5 fill:#ffa726,stroke:#333,stroke-width:1px,color:#fff
Loading

  1. Build Environment
    • Using pixi, uv, or micromamba
    • No pre-installation needed
    • Everything downloaded on demand

  1. Start Service
    • Worker process running in that environment
    • Stays alive for multiple tasks
  1. Run Tasks
    • Feed inputs, receive outputs
    • Via JSON serialization
    • Through the service/worker

layout: default

Shared Memory

Cross-platform, cross-language support for named shared memory buffers

Built into Python

from multiprocessing.shared_memory import SharedMemory

Appose Extension

from appose import SharedMemory

Java Implementation

import org.apposed.appose.SharedMemory;

New class modeled closely after Python's implementation

Result: Efficient sharing of large data (images!) without serialization overhead


layout: default

N-dimensional Arrays

Shared memory with structural metadata (images!)

NDArray = SharedMemory + dtype + shape

Python: NumPy-compatible

import appose

# Create shared memory NDArray
data = appose.NDArray("uint16", [2, 20, 25])

# Access as NumPy array
numpy_array = data.ndarray()

πŸ€” How does that work under the hood?

numpy.ndarray(
    prod(data.shape), dtype=data.dtype, buffer=data.shm.buf
).reshape(data.shape)

Java: ImgLib2-compatible

import org.apposed.appose.NDArray;
import net.imglib2.appose.*; // ShmImg, NDArrays

// Receive NDArray from Python (via task.outputs)
ShmImg<FloatType> img = new ShmImg<>(ndarray);

// Or create and send to Python (as a task input)
NDArray ndarray = NDArrays.ndArray(new FloatType(), 4, 3, 2);
Img<FloatType> img = NDArrays.asArrayImg(ndarray, new FloatType());

// Or copy to ShmImg from existing (presumably non-shm) Img
Img<FloatType> sharedCopy = ShmImg.copyOf(someOtherImg);
// See also net.imglib2.util.ImgUtil.copy(srcImg, destImg)

layout: center class: text-center

Live Demos

Real-world Appose integrations in action


layout: center

Demo 1: SAMJ

One-click live segmentation

  • Segment Anything Model integration
  • Python AI model called from Java
  • Interactive segmentation UI

https://github.com/segment-anything-models-java/SAMJ-IJ

Help β€Ί Update... β€Ί Manage Update Sites β€Ί SAMJ


layout: center

Demo 2: TrackMate

Deep learning spot detectors

  • v9-appose branch
  • Python-based detection in Java application
  • Real-time particle tracking

https://github.com/trackmate-sc/TrackMate/tree/v9-appose

Run fiji/plugin/trackmate/TrackMatePlugIn.java in IDE


layout: center

Demo 3: Mastodon

Large-scale tracking capabilities

  • Cell detection and tracking with Python deep learning models
  • Powered by Appose + Cellpose3 + TrackAstra
  • Proof-of-concept stage: best to use Linux

https://github.com/mastodon-sc/mastodon-deep-lineage/

Help β€Ί Update... β€Ί Manage Update Sites β€Ί Mastodon + Mastodon-DeepLineage


layout: default

Mastodon Demo: Dataset

Using TGMM mini example dataset

Download from:
https://github.com/mastodon-sc/mastodon-example-data/tree/master/tgmm-mini

Files needed:

  • datasethdf5.h5 - Image data
  • datasethdf5.xml - BDV metadata

Create new Mastodon project based on these two files


layout: default

Mastodon Demo: Install Python Environments

Install the required Python environments via Appose

Menu: Plugins β€Ί Tracking β€Ί Python environments for detectors/linkers

  • Update/Install cellpose3 - Cell segmentation model
  • Update/Install trackastra - Deep learning-based tracking
error    libmamba Could not solve for environment specs
    The following packages are incompatible
    └─ pytorch-cuda =* * is not installable because there are no viable options
       β”œβ”€ pytorch-cuda 11.6 would require
       β”‚  └─ cuda =11.6 *, which does not exist (perhaps a missing channel);
       β”œβ”€ pytorch-cuda 11.7 would require
       β”‚  └─ cuda =11.7 *, which does not exist (perhaps a missing channel);
       └─ pytorch-cuda 11.8 would require
          └─ cuda =11.8 *, which does not exist (perhaps a missing channel).
critical libmamba Could not solve for environment specs
[ERROR] Installation failed for cellpose3

layout: default

Mastodon Demo: Detection with Cellpose3

Menu: Plugins β€Ί Tracking β€Ί Detection β€Ί Cellpose3

Settings to adjust:

Parameter Value Notes
Estimated diameter 25 Cell size in pixels
GPU usage 1.0 Use full GPU (if available)

Run detection

  • Takes 3-5 minutes on laptop, or less than 1 minute on workstation
  • Cellpose runs in Python via Appose, results sent back to Mastodon

layout: default

Mastodon Demo: Linking with TrackAstra

Menu: Plugins β€Ί Tracking β€Ί Linking β€Ί TrackAstra

Settings:

  • Leave default parameters
  • TrackAstra uses deep learning for tracking

Run linking:

  • Links detected cells across timepoints
  • Creates complete cell lineage tracks
  • All computation happens in Python via Appose

Platform note: Tested on Linux and Windows. Detection works on both; linking works best on Linux.


layout: center class: text-center

Hands-On Workshop

Build your own Appose-powered tool in Fiji

Goal: Integrate UNSEG with Fiji via Appose
⏱️ ~90 minutes

layout: default

Workshop Overview

What we'll build:

Fiji plugin (Groovy script) that calls UNSEG for nucleus/cell segmentation

UNSEG: Unsupervised segmentation of cells and their nuclei in tissue
https://github.com/uttamLab/UNSEG

What you'll learn:

  • Setting up Python environments with pixi
  • Adapting Python code for Appose
  • Writing Fiji scripts that call Python
  • Debugging cross-language integration

layout: default

Prerequisites

Required:

  • Fiji installed

Recommended:

  • bash terminal
  • pixi
  • git
  • vim (or your favorite text editor)
  • claude (or your favorite AI assistant)

Can follow along without codingβ€”just watch!


layout: center class: text-center

πŸ‘©β€πŸ’» Let's Code! πŸ‘¨β€πŸ’»

Follow along or just watch

We'll go through all the steps together!

πŸ’‘ Stuck? Clone reference repo:

git clone https://github.com/ctrueden/unseg-fiji

Use tags to jump to any step:

git checkout step03

Or to view changes for a particular step:

git show step04

layout: default

Step 0: Initialize Project

Create a dedicated folder for this project:

mkdir ~/Desktop/unseg-fiji
cd ~/Desktop/unseg-fiji

Initialize an empty pixi project skeleton:

pixi init

πŸ’‘ Install pixi from: https://pixi.sh/latest/installation/

This creates pixi.toml: the environment specification

🎯 Checkpoint: git init && git add . && git commit -m 'Add initial project skeleton'


layout: default

Step 1: Clone UNSEG

Get the UNSEG repository from GitHub:

git clone https://github.com/uttamLab/UNSEG
cp UNSEG/unseg.py .
unzip UNSEG/image.zip

What's in here?

  • unseg.py - The segmentation algorithm
  • run_unseg.ipynb - Jupyter notebook showing how to use it
  • requirements.txt - Python dependencies
  • image.zip - Sample image data

🎯 Checkpoint: git add unseg.py && git commit -m 'Add unseg script'


layout: default

Step 2: Create Test Script

Extract code from the Jupyter notebook into a Python script:

Manual approach:

  1. Go to https://github.com/uttamLab/UNSEG in browser
  2. Click into run_unseg.ipynb
  3. Copy code blocks into text editor
  4. Save as run.py

Or use your AI assistant! πŸ€–

🎯 Checkpoint: git add run.py && git commit -m 'Add test script'


layout: default

Step 3: Import Dependencies

Import dependencies into pixi project:

pixi import UNSEG/requirements.txt --format pypi-txt --environment default

This updates pixi.toml environment specification.

🎯 Checkpoint: git commit -a -m 'Add unseg dependencies from requirements.txt'


layout: default

Step 4: Adjust Dependencies

1. Add needed dependencies: pixi add python=3.9 appose==0.7.2

2. Move packages from pypi-dependencies to dependencies

3. Test it: pixi run python run.py

Common issues to fix:

  1. opencv-python fails β†’ move back to pypi-dependencies
  2. Missing images β†’ run unzip UNSEG/image.zip
  3. Qt errors β†’ change opencv-python to opencv-python-headless

🎯 Checkpoint: git commit -a -m 'Update dependencies to first working version'


layout: default

Step 5: Adapt unseg.py for Appose

Make the library "listenable" by adding a callback mechanism:

Add at the top of unseg.py:

report = print
def listen(callback):
    global report
    report = callback

Then replace all print( with report(.

πŸ€” Why? Allows calling code to capture progress updates via callback

🎯 Checkpoint: git commit -m 'Change print statements to callback invocations' unseg.py


layout: default

Step 6: Append Running Code

Paste the contents of run.py at the bottom of unseg.py:

Now we have a single file that:

  • Defines the library functions
  • Includes example usage code
  • Can be called as a script

πŸ’‘ This makes it easy to test the code standalone before integrating with Appose

Test it again:

pixi run python unseg.py

🎯 Checkpoint: git rm -f run.py && git commit -a -m 'Unify all code into one script'


layout: two-cols

Step 7: Refactor Image Loading Logic

  • Simplify open_img to only open the image
  • Move intensity channel selection to main script

πŸ€” Why? Prepares for images coming from Appose

🎯 Checkpoint:

git commit \
    -m 'Split out channel selection logic' \
    unseg.py

::right::

def open_img(path_to_img):
    """Returns the RGB image (img)"""
    return io.imread(path_to_img, plugin="tifffile")

def plot_img(img, tlt='', cmp='gray'):
    ...

# Path to image
path_to_img = './image/Gallbladder_Normal_Tissue.tif'

# Open and plot the original image
img = open_img(path_to_img)
plot_img(img, tlt='Image')

# Select intensity channels for processing: two channels
# with nuclei (DAPI) and cell membrane (Na+K+ATPase) markers
h = img.shape[0]
w = img.shape[1]
intensity = np.zeros((h,w,2), dtype='float64')
intensity[:,:,0] = img[:,:,2] # Nuclei Marker
intensity[:,:,1] = img[:,:,0] # Cell Membrane Marker

layout: default

Step 8: Add "Appose Mode" to Script

Add Appose task handling to beginning of the main section:

appose_mode = 'task' in globals()
if appose_mode:
    listen(task.update)
else:
    from appose.python_worker import Task
    task = Task()

What this does:

  • Checks if running in Appose context (task exists) or standalone
  • Connects our listen callback to Appose's task.update method
  • Creates a dummy task object when running outside Appose

🎯 Checkpoint: git commit -m 'Make script Appose-aware' unseg.py


layout: default

Step 9: Integrate with Appose

Modify unseg.py to work in "Appose mode":

  1. Get input image from task instead of file:

    if appose_mode:
        image = task.inputs['image'].ndarray()
    else:
        image = open_img(path_to_img)
  2. Comment out plot calls (or make conditional)

  1. Set outputs:

    if appose_mode:
        task.outputs['nuclei'] = mask_nuclei
        task.outputs['cells'] = mask_cells

🎯 Checkpoint: git commit -m 'Add case logic for appose mode' unseg.py


layout: default

Step 10: Create the Fiji Script

Now the exciting part: call Python from Java! 🐍

Launch Fiji β†’ File β€Ί New β€Ί Script... β†’ Language β€Ί Groovy

πŸ€” Why Groovy?

  • Full Java compatibility
  • Works seamlessly with Fiji
  • Simpler syntax than pure Java

Declare inputs & outputs as SciJava script parameters:

#@ Img image
#@output Img nuclei
#@output Img cells

println(image)

Save the script as Unseg_Fiji.groovy

🎯 Checkpoint: git add Unseg_Fiji.groovy && git commit -m 'Start writing the Groovy script'


layout: default

Step 11: Build Environment

Embed the pixi.toml configuration:

import org.apposed.appose.Appose

println("== BUILDING ENVIRONMENT ==")
pixiToml = """
# ... paste entire pixi.toml contents here ...
"""

env = Appose.pixi().content(pixiToml).logDebug().build()
println("Environment build complete: ${env.base()}")

🎯 Checkpoint: git commit -m 'Build the Appose environment' Unseg_Fiji.groovy


layout: default

Step 12: Read Python Script

Load the adapted unseg.py:

// Read in the Python script (TODO: load as resource instead of hardcoding path)
unsegPath = System.getProperty("user.home") + "/Desktop/unseg-fiji/unseg.py"
unsegScript = new File(unsegPath).text
println("Loaded unseg script of length ${unsegScript.length()}")

πŸ’‘ Later: You could embed the Python code directly in the Groovy script, or package it as a resource

🎯 Checkpoint: git commit -m 'Load the unseg Python script' Unseg_Fiji.groovy


layout: default

Step 13: Add Shared Memory Utility Functions

For copying an ImgLib2 Img to Appose NDArray:

import net.imglib2.appose.ShmImg
imgToAppose = { img ->
    ndArray = ShmImg.copyOf(image).ndArray()
    println("Copied image into shared memory: ${ndArray.shape()}")
    return ndArray
}

For wrapping an Appose NDArray as ImgLib2 Img:

import net.imglib2.appose.NDArrays
apposeToImg = { ndarray ->
    NDArrays.asArrayImg(ndarray)
}

🎯 Checkpoint: git commit -m 'Add NDArray utility functions' Unseg_Fiji.groovy


layout: default

Step 14: Execute Task πŸš€

Invoke the Python code via an Appose task:

println("== STARTING PYTHON SERVICE ==")
try (python = env.python()) {
    inputs = ["ndarray": imgToAppose(image)]
    task = python.task(unsegScript, inputs)
        .listen { if (it.message) println("[UNSEG] ${it.message}") }
        .waitFor()

    println("TASK FINISHED: ${task.status}")
    if (task.error) println(task.error)
    nuclei = apposeToImg(task.outputs["nuclei"])
    cells = apposeToImg(task.outputs["cells"])
}
finally {
    println("== TERMINATING PYTHON SERVICE ==")
}

🎯 Checkpoint: git commit -m 'Call unseg code via Appose' Unseg_Fiji.groovy


layout: default

Step 15: Fix Bugs! πŸͺ²

TASK FINISHED: FAILED
Traceback (most recent call last):
  File "/home/curtis/.local/share/appose/unseg-fiji/.pixi/envs/default/lib/python3.9/site-packages/appose/python_worker.py", line 145, in _run
    exec(compile(block, "<string>", mode="exec"), _globals, binding)
  File "<string>", line 1644, in <module>
AttributeError: 'Task' object has no attribute 'inputs'

πŸ’‘ HINTS:

  • Task inputs are available as variables directly, not task.inputs
  • The Groovy script passes a var named ndarray, not image
  • Use appose.NDArray.ndarray() to obtain a numpy.ndarray
  • To transform input & output images, these functions are needed ➑️
def flip_img(img):
    """Flips a NumPy array between Java (F_ORDER) and NumPy-friendly (C_ORDER)"""
    return np.transpose(img, tuple(reversed(range(img.ndim))))

def share_as_ndarray(img):
    """Copies a NumPy array into a same-sized newly allocated block of shared memory"""
    from appose import NDArray
    shared = NDArray(str(img.dtype), img.shape)
    shared.ndarray()[:] = img
    return shared

🎯 Checkpoint: git commit -m 'Fix Appose-related bugs in Python code' unseg.py


layout: default

Troubleshooting Tips

Common issues:

  • Environment build fails β†’ Test build on command line; use logDebug function
  • Service won't start or Tasks won't run β†’ Use Service#debug function
  • Python script errors β†’ Test standalone first with pixi run python unseg.py
  • Input/output types β†’ Verify Appose can serialize your data types

Debug strategy:

  1. Test Python code standalone
  2. Test environment with simple script
  3. Add Appose integration incrementally

layout: default

Next Steps

After this workshop:

Ideas for experimentation:

  • Different deep learning models
  • R-based statistical analysis
  • Custom Python algorithms
  • Bidirectional workflows (Python calling Java)

layout: center class: text-center

Questions?

Thank you for participating! πŸ™