imagej / imagej/pyimagej

ImageJ Ops threshold results do not work with `3D Object Counter`

Open
#216 1 comment 0 reactions 0 assignees View on GitHub
bug
Dominant language
Python
Stars
534
Forks
95
PR merge metrics
No merged PRs in 30d

Description

While working on some workflows using the `3D Object Counter` I discovered a bug where 8-bit binary images created from ImageJ Ops (_i.e._ the output from a threshold op) and converted to `ImagePlus` type (needed for the plugin) fail to produce any object detections. If the threshold is applied via the original ImageJ thresholder the plugin works as expected.

As I see it there are at least two potential sources of this bug.

1. The `3D Object Counter` plugin is looking/doing something unusual with the `ImagePlus` passed to it that the converted `ImagePlus` from ImageJ Ops is missing.
2. 8-bit binary images are not converting properly to `ImagePlus` type (_i.e._ something larger is wrong).

If option (1) is at play here, then I’d like to know what the `3D Object Counter` is looking for. I’ve viewed the source a few times and I can’t see anything obviously unusual about what its doing. This plugin is super useful and it would be great to get it to work correctly with ImageJ Ops outputs.

If its option (2), the bug could be impacting other operations that use `ImagePlus` thresholds. This makes using ImageJ Ops to apply thresholds tricky if some plugins act like the `3D Object Counter` with converted `ImagePlus` images. I haven’t tried yet, but the next thing to do is perhaps compare the `ImagePlus` object produced by both approaches.

Also I’m not exactly sure if this repository is the best place for this issue, it might better belong in `SciJava` where the `ConvertService` lives.

## Minimal example

Here is a minimal example demonstrating this behavior. The `process_imagej_thres` method works as expected, producing a results table and several output images. The `process_op_thres` fails with an empty table and empty image outputs (of the correct shape).

You can download the sample data for this minimal example from my sample-data repository here:
[sample data](https://github.com/elevans/sample-data/tree/main/microscopy/puncta)

You can probably use other data, but this workflow was intended for zstacks of dots/puncta.

```python
import imagej
import code

# initialize PyImageJ
ij = imagej.init("sc.fiji:fiji", mode="interactive")
print(f"ImageJ version: {ij.getVersion()}")

# load data
dataset = ij.io().open("data/vif_zstack_100x.tif")

def pre_process(dataset: "net.imagej.Dataset"):
dataset = ij.op().convert().int32(dataset)
dataset_blur = ij.op().run("filter.gauss", dataset, 3)
return dataset - dataset_blur

def process_imagej_thres(dataset: "net.imagej.Dataset"):
# apply triangle threshold and fill holes
imp = ij.py.to_imageplus(dataset)
ij.IJ.setAutoThreshold(imp, "Triangle")
ij.IJ.run(imp, "Convert to Mask", "method=Triangle background=Light calculate black")
imp = ij.WindowManager.getCurrentImage()
imp.setTitle("mask-imagej")
ij.IJ.run(imp, "Fill Holes", "stack")

# reorder stack and run plugin
ij.IJ.run("Re-order Hyperstack ...", "channels=[Slices (z)] slices=[Channels (c)] frames=[Frames (t)]")
ij.IJ.run(ij.WindowManager.getCurrentImage(), "3D Objects Counter", "threshold=128 slice=30 min.=25 max.=15250000 exclude_objects_on_edges objects surfaces statistics")

def process_ops_thres(dataset: "net.imagej.Dataset"):
# apply triangle threshold and fill holes
dataset_mask = ij.op().run("threshold.triangle", dataset)
dataset_mask = ij.op().run("morphology.fillHoles", dataset_mask)
dataset_mask_i = ij.dataset().create(dataset_mask)
ij.op().run("image.invert", dataset_mask_i, dataset_mask)
# convert to ImagePlus and activate image
imp = ij.py.to_imageplus(dataset_mask_i)
imp.show()
imp = ij.WindowManager.getCurrentImage()
imp.setTitle("mask-imglib")

# set threshold on imp
ip = imp.getProcessor()
ip.setAutoThreshold("Triangle", True, ip.RED_LUT)
imp.updateAndDraw()

# reorder stack and run plugin
ij.IJ.run("Re-order Hyperstack ...", "channels=[Slices (z)] slices=[Channels (c)] frames=[Frames (t)]")
ij.IJ.run(ij.WindowManager.getCurrentImage(), "3D Objects Counter", "threshold=128 slice=30 min.=25 max.=15250000 exclude_objects_on_edges objects surfaces statistics")

# launch
image = pre_process(dataset)
process_imagej_thres(image)
process_ops_thres(image)

# return REPL
code.interact(local=locals())
```

Contributor guide

No contributing guide indexed for this repository

Research direction

Start with the minimal Python example, comparing process_imagej_thres and process_ops_thres around ij.py.to_imageplus and the 3D Objects Counter entry point. Use the linked puncta sample data and inspect the ImagePlus objects produced by both workflows. Done means identifying whether conversion or the plugin causes the empty results and documenting or fixing the affected behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
computer-vision
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.