[ImageJ-devel] [fiji-devel] Significant change to ImgOpener - or - why ImgOpener.openImg() cannot return a T

Curtis Rueden ctrueden at wisc.edu
Wed Aug 1 12:24:41 CDT 2012


Hi Steffi,

> I do not see why <? extends RealType> would have any advantage.

Here is an example of a practical difference between the two constructs:

    ImgOpener imgOpener = new ImgOpener();
    ImgPlus<? extends RealType> img =
imgOpener.openImg("/Users/curtis/data/toucan.png");
    ImgPlus<RealType> realImg = (ImgPlus<RealType>) img;
    ImgPlus<UnsignedByteType> ubyteImg = (ImgPlus<UnsignedByteType>) img;
// this works
    ImgPlus<UnsignedByteType> ubyteImg2 = (ImgPlus<UnsignedByteType>)
realImg; // illegal
    ImgPlus<UnsignedByteType> ubyteImg3 = (ImgPlus) realImg; // also works

In the above example, the compiler allows casting a "ImgPlus<? extends
RealType>" to a "ImgPlus<UnsignedByteType>" (ubyteImg) but does not allow
doing so from a "ImgPlus<RealType>" (ubyteImg2). You instead must go
through a completely raw type to make it work (ubyteImg3).

I think "? extends RealType" is more correct, and also behaves better in
cases such as the above. However, I agree it is more of a pain to write, so
if you think the tradeoff is worth it, we can stick with ImgPlus<RealType>
instead and just document why we did it in the ImgOpener javadoc.

What do you think?

Regards,
Curtis


On Tue, Jul 31, 2012 at 5:18 PM, Stephan Preibisch <preibisch at mpi-cbg.de>wrote:

> Hi Barry, hi Curtis,
>
> the example you give would give you a warning, and, yes, - a runtime
> error. The problem is, there is no right way to do it as the type is not
> defined at compile time but instead depends on the image that is opened.
>
> However, I do not see why <? extends RealType> would have any advantage.
> It crashes on the exactly same thing, but is more tough to write, see here:
>
> Img< RealType >img = ImgOpener.open(
> "/Users/preibischs/Documents/Microscopy/SPIM/HisYFP-SPIM/spim_TL18_Angle0.lsm");
>  img.firstElement().add( new FloatType() );
>
> -> fails because cannot cast to ByteType (what the image is)
>
> Img< ? extends RealType >img = ImgOpener.open(
> "/Users/preibischs/Documents/Microscopy/SPIM/HisYFP-SPIM/spim_TL18_Angle0.lsm"
>  );
>  img.firstElement().add( new FloatType() );
>
> -> fails as well, but is more to write
>
> And the construct <? extends RealType<?>> would be correct but cannot be
> used in any generic method as it is not ensured that the first and the
> second "?" are the same.
>
> We still have to choose one of them, I still vote for Img< RealType >,
> because it is at least easy to write.
>
> The best is to open it as FloatType or as ByteType, but this is not always
> possible ...
>
> What do you think?
>
> Bye bye,
> Steffi
>
>
> On Jul 31, 2012, at 17:59 , Curtis Rueden wrote:
>
> Hi Barry,
>
>
>> This is just an example I've pulled from the web but it seems to imply
>> that an Img<UnsignedByteType> is not safely considered an Img<RealType>.
>
>
> Good point! We should probably use "Img<? extends RealType>" instead. I
> tested this with ImageJ2, and it still works as expected. And I believe all
> of Steffi's example cases will still work too. So I pushed these changes to
> the newimgopener branch.
>
> What do you think, Steffi? Anyone else?
>
> Regards,
> Curtis
>
>
> On Tue, Jul 31, 2012 at 4:28 PM, Barry DeZonia <bdezonia at gmail.com> wrote:
>
>> Steffi,
>>
>> I remember running into this when originally interfacing IJ2 to Imglib.
>> We did decided to go with Img<? extends RealType<?> as that seemed the most
>> safe at the time.
>>
>> What I'm wondering about with your solution is that it seems to
>> contradict other people's description as to what is legal to do in Java.
>>
>> As a contrived example think of the animal cage example:
>>
>> We have a Animal class and a Lion class derived from it. And a Cage class
>> that is defined Cage<T extends Animal>.
>>
>> Though Lion is instanceof Animal its not the case that Cage<Lion>
>> instanceof Cage<Animal>. This is counterintuitive but many people mention
>> it.
>>
>> If it was the case you could write code like this:
>>
>> Cage<Animal> animals;
>>
>>
>>
>> Cage<Lion> lions;
>>
>>
>>
>>
>> animals = lions; // This assignment is not allowed
>>
>>
>>
>>
>> animals.add(rat); // If this executed, we'd have a Rat in a Cage<Lion>
>>
>>
>> This is just an example I've pulled from the web but it seems to imply
>> that an Img<UnsignedByteType> is not safely considered an Img<RealType>. We
>> may be avoiding all such cases where it could be a problem but is it safe
>> going forward? I can't say I know.
>>
>>
>> On Tue, Jul 17, 2012 at 4:08 PM, Stephan Preibisch <preibisch at mpi-cbg.de>wrote:
>>
>>> Hi guys,
>>>
>>> this became a massive explanation. I suggest this as as solution to a
>>> problem we have been only partly aware of. I hope you enjoy reading ;)
>>>
>>> Steffi
>>>
>>>
>>> As the ImageJ conference is approaching I was talking with Johannes and
>>> we agreed that the ImgOpener needs to be finished. However, since its first
>>> version there has been a major design fault.
>>>
>>> But first the good news:
>>> --------------------------------
>>> It works perfectly fine if you say "open me an Image as float" or "...as
>>> UnsignedByte" or whatever, classically called by the method
>>> -> new ImgOpener.openImg( String id, ImgFactory<T> factory, T type );
>>> It can, without any problems, return you an Img<T>, and it requires that
>>> T is RealType (and not anymore NativeType, see at the end). So far, so good.
>>> Note that "T" is NOT a return parameter, but something you give to the
>>> openAs method.
>>>
>>> Now the bad news.
>>> --------------------------
>>> If you say "open this image as whatever RealType it is", it can do that,
>>> but it cannot assign a "T" to it - because T is not a return parameter. We
>>> made an ugly hack inside so that it seems as if it would work, but it does
>>> not. Now you might ask, why change it if it worked so far? Well, here is an
>>> easy example that would cause a ClassCastException on run time
>>>
>>> public static <T extends RealType< T >> void main( String[] args )
>>> {
>>>         Img< T > img1 = new ImgOpener.openImg( "somepic_8bit.tif" ); //
>>> 8-bit unsigned
>>>         Img< T > img2 = new ImgOpener.openImg( "somepic_32bit.tif" ); //
>>> 32-bit float
>>>
>>>         img1.firstElement().set( img2.firstElement() ); // run-time crash
>>> }
>>>
>>> It will throw a ClassCastException because img1 is of UnsignedByteType
>>> and img2 of FloatType. But as we cast it in a dirty way, it compiles just
>>> fine.
>>> So, we cannot return a "T", but what we can return is Img< which is at
>>> least a RealType >. And this is for sure always true, but img1 and img2 are
>>> not necessarily the same RealType (as above).
>>>
>>> The correct way (which doesn't work)
>>> --------------------------------------------------
>>> What we should return is an Img< ? extends RealType< ? > >. However, it
>>> is not ensured that the two "?" are the same, so an Img of this type is
>>> basically incompatible with everything else, which means an unchecked cast
>>> is necessary. So although correct, maybe not a good idea. And it is really
>>> ugly to write if necessary.
>>>
>>> The still somehow correct way
>>> -----------------------------------------
>>> Instead, it returns now an Img< RealType >. This is kind of a tradeoff.
>>> On one hand, this is very easy to write and will give you compile errors
>>> where it should, for example
>>>
>>>         img1.firstElement().set( img2.firstElement() ); // COMPILE ERROR
>>> (not the same RealType realization)
>>>
>>> OR
>>>
>>>        public <T extends RealType< T >> void add( IterableInterval< T >
>>> i1, IterableInterval< T > i2 ) { .... }
>>>        add( img1, img2 ); // COMPILE ERROR (not the same RealType
>>> realization)
>>>
>>> BUT
>>>
>>>         Gauss.inFloatInPlace( 2.0 , img1 ); // FINE (just one RealType
>>> realization required, inside it will be always the same "T")
>>>         Gauss.inFloatInPlace( 2.0 , img2 ); // FINE (just one RealType
>>> realization required, inside it will be always the same "T")
>>>
>>>         public < T extends RealType< T > > void add1( Img< T > img1,
>>> double value ) { ... }
>>>         add1( img1, 5 ); // FINE (just one RealType realization
>>> required, inside it will be always the same "T")
>>>
>>>         public < T extends RealType< T >, S extends RealType< S > > void
>>> add2( Img< T > img1, Img< S > img2 ) { ... }
>>>         add2( img1, img2 ); // FINE (explicitly two different RealType
>>> realizations are allowed)
>>>
>>>         public void add3( Img< RealType > img1, Img< RealType > img2 ) {
>>> ... }
>>>         add3( img1, img2 ); // FINE (both are just some kind of
>>> RealType, but gives a warning)
>>>
>>> On the other hand it gives a lot of Warnings because RealType should be
>>> more specified.
>>>
>>> Why not Img< RealType< ? > >
>>> ------------------------------------------
>>> Similar problem as in <? extends RealType< ? > >. RealType< ? > is not a
>>> valid substitute for any construct like < T extends RealType < T > >. One
>>> would have to cast to Img< RealType >, so we can take this one right away
>>> as it is less writing.
>>>
>>> Where did NativeType go?
>>> ------------------------------------
>>> I do not see any reason why we should enforce a NativeType. There is no
>>> objection to load an image into a (hypothetical) ListImg< BigDecimalType >.
>>>
>>> What are the implications?
>>> -----------------------------------
>>> We should write NOTHING except the method opening files for Img<
>>> RealType >. And also only if it is really required - quite often it is not.
>>> But if, Img< RealType > It is a completely valid input for any generic
>>> algorithm as show above for Gauss, add, etc.
>>>
>>>
>>>
>>> I implemented the changes on a branch called 'newimgopener'. It also
>>> contains four static convenience opening methods and a speed improvement.
>>>
>>> Any comments are very welcome. If somebody has a better idea how to
>>> solve the problem ... I am all ears ...
>>>
>>> Bye bye,
>>> Steffi
>>>
>>> --
>>> Please avoid top-posting, and please make sure to reply-to-all!
>>>
>>> Mailing list web interface: http://groups.google.com/group/fiji-devel
>>>
>>
>>
>> --
>> Please avoid top-posting, and please make sure to reply-to-all!
>>
>> Mailing list web interface: http://groups.google.com/group/fiji-devel
>>
>
>
> --
> Please avoid top-posting, and please make sure to reply-to-all!
>
> Mailing list web interface: http://groups.google.com/group/fiji-devel
>
>
>  --
> Please avoid top-posting, and please make sure to reply-to-all!
>
> Mailing list web interface: http://groups.google.com/group/fiji-devel
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://imagej.net/pipermail/imagej-devel/attachments/20120801/3caf6c23/attachment-0001.html>


More information about the ImageJ-devel mailing list