DatasetIsBinary Op

Hello everyone,

I’ve started writing this small utility Op for ImageJ, which checks whether a Dataset is binary. A little background: BoneJ features many plugins that presuppose that the input image has been thresholded. That is, the image should only feature two different values: one for foreground particles (traditionally white), and one for background particles (black). In BoneJ1 an ImagePlus is considered binary if its type is GRAY8, and it has only white (255) or black (0) values.

Now a Dataset is a lot more generic than an ImagePlus. When should it be considered binary? Micheal (@mdoube ) and I think that it means that the Dataset contains only two distinct values. It doesn’t matter what those two values actually are, as long as one can be used for foreground and one for background. The current code reflects this assumption. We’d like to hear your opinions.

Thanks & best,

I think that whenever possible, you want to avoid iterating over all the values of all pixels of an image, since doing so might be very expensive for large and/or remote images. Hence, my vote would be to consider an image binary if it is of type B extends BooleanType<B> (in practice, either BoolType or BitType). Then you know it is binary. In Ops, we have converters for going between types; it should be the case that algorithms which require binary pixel(s)/image as input are always typed in this way. This is better too for reasoning about which outputs of which algorithms can be chained as inputs to other algorithms safely.

I get the general idea of this way of implementing the Op, but I didn’t get far when trying to implement it. First of the type of a Dataset can be RealType<?>, but a BoolType is not a subtype of this. Then there is an Op to convert a ComplexType to BitType, but not to Booltype. Also this Op cannot be called with a Dataset ("convert.bit", dataset)).

I also tried an approach where I checked with ConvertService.supports(dataset, BitType.class) if a Dataset is binary, but that didn’t work either. I have a difficult time grasping how to still work with Datasets, but ensure its certain type.

1 Like

@rimadoma first of all :+1: for the organization of your repo. :smile:

I think you are actually very close!

This is true, there is a BitType that is both a BooleanType and a RealType. So I think @ctrueden’s suggestion of just checking for BooleanType, and not the concrete types, is appropriate.

So your isBinaryType method would simply become:

    private boolean isBinaryType() {
        return BooleanType.class.isAssignableFrom(dataset.getType().getClass());

I like leaning on the framework like this, but the problem with this call is that the Dataset is too high-level compared to BitType and the ConvertService operates that are at the same conceptual level.

You could test supports(dataset.getType(), BooleanType.class), but this is probably overly permissive because there could be a lossy converter from IntType to BooleanType, which would give false positives by labeling IntType datasets as “binary”.

Thus I think just checking the class of the type is more appropriate here.

1 Like

Note that in the future, the ConvertService will provide a distinction between lossy/narrowing and lossless/widening conversion. But it does not exist in the API yet.

One other comment: the Dataset API is not great, and we are planning to redo it soon. Ops are currently all coded against ImgLib2 core—and to a lesser extent, imagej-common for the ImgPlus, although that latter class will also be rewritten later this year. Best is to use IterableInterval and/or RandomAccessibleInterval as appropriate from ImgLib2 core for your ops.

Thank you @ctrueden & @hinerm! Again.

I think I got my DatasetIsBinary Op working with the class type check.

The reason I chose to work with the Dataset API is that the documentation refers to it as

– the primary image data structure in ImageJ

It’s also prominently featured in the ImageJ tutorials. Still, I’ll make a branch in my repo where I try to rework the Op to work with an Interval.

1 Like

A think I got the Op to work with an IterableInterval as well. I understood that if I want to do “actual” work with an IterableInterval I have to use one of its subtypes, like a PlanarImage. That is, e.g. if I want to access pixel arrays etc. Or should I have an IterableInterval as an input @Parameter for my Op, and then try to cast it down? What’s the right way to do things?

Hi @rimadoma I took a look at your Op.
Sadly it won’t work correctly in ImageJ / BoneJ until the release of scijava-common 2.51.0, which will allow lambdas and method references in ops.
I also opened a PR to fix the project’s pom.xml allowing it to be build using the mvn command.

1 Like

@gab1one Cheers! Do you mean that SciJava will move to Java 8 in 2.51.0?

Sadly no, I think that will happen the earliest with scijva-common 3.0.0, the next scijava-common release will contain a fix for the bytecode parser which allows it to read the bytecode of classes that contain lambdas or method references: see this commit.