Skip to content

Commit b3ad0f6

Browse files
authored
Merge pull request #273 from scijava/scijava-ops-image/number-realtype-conversion
Add Number<->RealType conversion
2 parents 8d5869b + f26748d commit b3ad0f6

File tree

15 files changed

+1058
-618
lines changed

15 files changed

+1058
-618
lines changed

scijava-common3/src/main/java/org/scijava/common3/Types.java

Lines changed: 54 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1127,8 +1127,23 @@ private static boolean isAssignable(final Type type, final Any toType,
11271127
final Map<TypeVariable<?>, Type> typeVarAssigns)
11281128
{
11291129
if (type instanceof TypeVariable) {
1130-
// TODO: do we need to do here what we do with the ParameterizedType?
1130+
// If the type variable has an assignment already, we should check against that instead.
1131+
TypeVariable<?> typeVar = (TypeVariable<?>) type;
1132+
if (typeVarAssigns.containsKey(typeVar)) {{
1133+
return isAssignable(typeVarAssigns.get(typeVar), toType, typeVarAssigns);
1134+
}}
11311135
}
1136+
1137+
// For a type to be assignable to an Any, it must fit within the Any's upper and lower bounds.
1138+
for (var upperBound : toType.getUpperBounds()) {
1139+
if (!isAssignable(type, upperBound)) return false;
1140+
}
1141+
1142+
for (var lowerBound : toType.getLowerBounds()) {
1143+
if (!isAssignable(lowerBound, type)) return false;
1144+
}
1145+
1146+
// Now that we know the type is assignable to an Any, we can do some type variable mapping.
11321147
if (type instanceof ParameterizedType) {
11331148
// check if any of the type parameters are TypeVariables, and if so
11341149
// bind them to a new Any with the bounds of the TypeVariable.
@@ -1145,14 +1160,6 @@ private static boolean isAssignable(final Type type, final Any toType,
11451160
return true;
11461161
}
11471162

1148-
for (var upperBound : toType.getUpperBounds()) {
1149-
if (!isAssignable(type, upperBound)) return false;
1150-
}
1151-
1152-
for (var lowerBound : toType.getLowerBounds()) {
1153-
if (!isAssignable(lowerBound, type)) return false;
1154-
}
1155-
11561163
return true;
11571164
}
11581165

@@ -1367,10 +1374,29 @@ else if (!isAssignable(fromResolved == null ? fromTypeArg
13671374
typeVarAssigns.put(unbounded, fromResolved);
13681375
toResolved = fromResolved;
13691376
}
1370-
// bind unbounded to another type variable
1377+
// bind unbounded to a TypeVariable or Any
13711378
else {
1372-
typeVarAssigns.put((TypeVariable<?>) toTypeVarAssigns.get(var),
1373-
fromTypeVarAssigns.get(var));
1379+
TypeVariable<?> to = (TypeVariable<?>) toTypeVarAssigns.get(var);
1380+
Type from = fromTypeVarAssigns.get(var);
1381+
if (from == null) {
1382+
// IMPORTANT: Understanding the Any bounds are very important
1383+
// here for understanding type variable bounds ACROSS
1384+
// isAssignable calls. This happens often in SciJava Ops, to which
1385+
// the following description pertains:
1386+
//
1387+
// Suppose we ask for a
1388+
// Function<IntType, Any> , hoping to match some
1389+
// Computers.Arity1<I extends RealType<I>, O extends RealType<O>> via
1390+
// adaptation. Adapation requires (1) the underlying Computers.Arity1<I, O>
1391+
// Op, but also (2) some way to generate the output (in practice, either a
1392+
// Function<IntType, Any> or a Producer<Any>). If there are no bounds on
1393+
// the Any, then we can get incorrect matches, such as a Producer<Double>,
1394+
// which would produce an object unsuitable for the Computers.Arity1 op
1395+
// being adapted. Therefore it is necessary to attach bounds on the Any (as
1396+
// an example, an Any bounded by RealType) to ensure correct matching.
1397+
from = new Any(to.getBounds());
1398+
}
1399+
typeVarAssigns.put(to, from);
13741400
}
13751401
}
13761402

@@ -1379,8 +1405,20 @@ else if (!isAssignable(fromResolved == null ? fromTypeArg
13791405
// parameters of the target type.
13801406
if (fromResolved != null && !fromResolved.equals(toResolved)) {
13811407
// check for anys
1382-
if (Any.is(fromResolved) || Any.is(toResolved)) continue;
1383-
if (fromResolved instanceof ParameterizedType &&
1408+
if (Any.is(toResolved)) {
1409+
Any a = toResolved instanceof Any ? (Any) toResolved : new Any();
1410+
for (Type upper: a.getUpperBounds()) {
1411+
if (!Types.isAssignable(fromResolved, upper))
1412+
return false;
1413+
}
1414+
for (Type lower: a.getLowerBounds()) {
1415+
if (!Types.isAssignable(lower, fromResolved))
1416+
return false;
1417+
}
1418+
continue;
1419+
}
1420+
else if (Any.is(fromResolved)) continue;
1421+
else if (fromResolved instanceof ParameterizedType &&
13841422
toResolved instanceof ParameterizedType)
13851423
{
13861424
if (raw(fromResolved) != raw(toResolved)) {
@@ -1791,8 +1829,8 @@ private static boolean isAssignable(final Type type,
17911829
}
17921830

17931831
if (Any.is(type)) {
1794-
typeVarAssigns.put(toTypeVariable, new Any(toTypeVariable.getBounds()));
1795-
return true;
1832+
typeVarAssigns.putIfAbsent(toTypeVariable, new Any(toTypeVariable.getBounds()));
1833+
return isAssignable(typeVarAssigns.get(toTypeVariable), type);
17961834
}
17971835

17981836
throw new IllegalStateException("found an unhandled type: " + type);

scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/adapt/AdaptationMatchingRoutine.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@
4040
import org.scijava.ops.engine.matcher.OpCandidate.StatusCode;
4141
import org.scijava.ops.engine.matcher.OpMatcher;
4242
import org.scijava.ops.engine.matcher.impl.DefaultOpRequest;
43-
import org.scijava.ops.engine.struct.FunctionalMethodType;
4443
import org.scijava.ops.engine.struct.FunctionalParameters;
4544
import org.scijava.ops.engine.util.Infos;
4645
import org.scijava.priority.Priority;
@@ -172,10 +171,11 @@ public OpCandidate findMatch(MatchingConditions conditions, OpMatcher matcher,
172171
* @param candidate the {@link OpCandidate} matched for the adaptor input
173172
* @param map the mapping
174173
*/
175-
private void captureTypeVarsFromCandidate(Type adaptorType, OpCandidate candidate,
176-
Map<TypeVariable<?>, Type> map)
177-
{
178-
174+
private void captureTypeVarsFromCandidate(
175+
Type adaptorType,
176+
OpCandidate candidate,
177+
Map<TypeVariable<?>, Type> map
178+
) {
179179
// STEP 1: Base adaptor type variables
180180
// For example, let's say we're operating on Computer<I, O>s.
181181
// The adaptor might work on Computer<T, T>s. Which we need to resolve.

scijava-ops-image/src/main/java/org/scijava/ops/image/convert/ConvertTypes.java renamed to scijava-ops-image/src/main/java/org/scijava/ops/image/convert/ConvertComplexTypes.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858
*
5959
* @author Alison Walter
6060
*/
61-
public final class ConvertTypes<C extends ComplexType<C>, T extends IntegerType<T>> {
61+
public final class ConvertComplexTypes<C extends ComplexType<C>, T extends IntegerType<T>> {
6262

6363
/**
6464
* @input input
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
/*-
2+
* #%L
3+
* Image processing operations for SciJava Ops.
4+
* %%
5+
* Copyright (C) 2014 - 2024 SciJava developers.
6+
* %%
7+
* Redistribution and use in source and binary forms, with or without
8+
* modification, are permitted provided that the following conditions are met:
9+
*
10+
* 1. Redistributions of source code must retain the above copyright notice,
11+
* this list of conditions and the following disclaimer.
12+
* 2. Redistributions in binary form must reproduce the above copyright notice,
13+
* this list of conditions and the following disclaimer in the documentation
14+
* and/or other materials provided with the distribution.
15+
*
16+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
20+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26+
* POSSIBILITY OF SUCH DAMAGE.
27+
* #L%
28+
*/
29+
package org.scijava.ops.image.convert;
30+
31+
import net.imglib2.type.numeric.IntegerType;
32+
import net.imglib2.type.numeric.RealType;
33+
import net.imglib2.type.numeric.integer.*;
34+
import net.imglib2.type.numeric.real.DoubleType;
35+
import net.imglib2.type.numeric.real.FloatType;
36+
37+
import java.util.function.Function;
38+
39+
/**
40+
* Converters for converting between {@link RealType}s and {@link Number}s
41+
*
42+
* @author Gabriel Selzer
43+
*/
44+
public class ConvertNumbersToRealTypes<N extends Number, T extends RealType<T>, I extends IntegerType<I>> {
45+
46+
// -- Numbers to RealTypes -- //
47+
48+
// NB Combo conversion uses these to convert to any RealType
49+
50+
/**
51+
* @input num the {@link Number} to convert
52+
* @output a {@link ByteType} containing the information in {@code num}
53+
* @implNote op names='engine.convert, convert.int8'
54+
*/
55+
public final Function<N, ByteType> numberToByteType = //
56+
num -> new ByteType(num.byteValue());
57+
58+
/**
59+
* @input num the {@link Number} to convert
60+
* @output a {@link UnsignedByteType} containing the information in {@code num}
61+
* @implNote op names='engine.convert, convert.uint8'
62+
*/
63+
public final Function<N, UnsignedByteType> numberToUnsignedByteType = //
64+
num -> new UnsignedByteType(num.shortValue());
65+
66+
/**
67+
* @input num the {@link Number} to convert
68+
* @output a {@link ShortType} containing the information in {@code num}
69+
* @implNote op names='engine.convert, convert.int16'
70+
*/
71+
public final Function<N, ShortType> numberToShortType = //
72+
num -> new ShortType(num.shortValue());
73+
74+
/**
75+
* @input num the {@link Number} to convert
76+
* @output a {@link UnsignedShortType} containing the information in {@code num}
77+
* @implNote op names='engine.convert, convert.uint16'
78+
*/
79+
public final Function<N, UnsignedShortType> numberToUnsignedShortType = //
80+
num -> new UnsignedShortType(num.intValue());
81+
82+
/**
83+
* @input num the {@link Number} to convert
84+
* @output a {@link IntType} containing the information in {@code num}
85+
* @implNote op names='engine.convert, convert.int32'
86+
*/
87+
public final Function<N, IntType> numberToIntType = //
88+
num -> new IntType(num.intValue());
89+
90+
/**
91+
* @input num the {@link Number} to convert
92+
* @output a {@link UnsignedIntType} containing the information in {@code num}
93+
* @implNote op names='engine.convert, convert.uint32'
94+
*/
95+
public final Function<N, UnsignedIntType> numberToUnsignedIntType = //
96+
num -> new UnsignedIntType(num.longValue());
97+
98+
/**
99+
* @input num the {@link Number} to convert
100+
* @output a {@link LongType} containing the information in {@code num}
101+
* @implNote op names='engine.convert, convert.int64'
102+
*/
103+
public final Function<N, LongType> numberToLongType = //
104+
num -> new LongType(num.longValue());
105+
106+
/**
107+
* @input num the {@link Number} to convert
108+
* @output a {@link FloatType} containing the information in {@code num}
109+
* @implNote op names='engine.convert, convert.float32'
110+
*/
111+
public final Function<N, FloatType> numberToFloatType = //
112+
num -> new FloatType(num.floatValue());
113+
114+
/**
115+
* NB This converter wins against those above in requests for e.g.
116+
* {@code Function<N, NativeType<T>>}
117+
*
118+
* @input num the {@link Number} to convert
119+
* @output a {@link DoubleType} containing the information in {@code num}
120+
* @implNote op names='engine.convert, convert.float64', priority=100
121+
*/
122+
public final Function<N, DoubleType> numberToDoubleType = //
123+
num -> new DoubleType(num.doubleValue());
124+
125+
// -- RealTypes to Numbers -- //
126+
127+
/**
128+
* @input integerType the {@link IntegerType} to convert
129+
* @output the {@link Byte}, converted from {@code integerType}
130+
* @implNote op names='engine.convert, convert.int8', priority="10"
131+
*/
132+
public final Function<I, Byte> integerTypeToByte = i -> (byte) i.getIntegerLong();
133+
134+
/**
135+
* NB potentially lossy, so lower priority
136+
*
137+
* @input realType the {@link RealType} to convert
138+
* @output the {@link Byte}, converted from {@code realType}
139+
* @implNote op names='engine.convert, convert.int8'
140+
*/
141+
public final Function<T, Byte> realTypeToByte = i -> (byte) i.getRealDouble();
142+
143+
/**
144+
* @input integerType the {@link IntegerType} to convert
145+
* @output the {@link Short}, converted from {@code integerType}
146+
* @implNote op names='engine.convert, convert.int16', priority="10"
147+
*/
148+
public final Function<I, Short> integerTypeToShort = i -> (short) i.getInteger();
149+
150+
/**
151+
* NB potentially lossy, so lower priority
152+
*
153+
* @input realType the {@link RealType} to convert
154+
* @output the {@link Short}, converted from {@code realType}
155+
* @implNote op names='engine.convert, convert.int16'
156+
*/
157+
public final Function<T, Short> realTypeToShort = i -> (short) i.getRealDouble();
158+
159+
/**
160+
* @input integerType the {@link IntegerType} to convert
161+
* @output the {@link Integer}, converted from {@code integerType}
162+
* @implNote op names='engine.convert, convert.int32', priority="10"
163+
*/
164+
public final Function<I, Integer> integerTypeToInteger = IntegerType::getInteger;
165+
166+
/**
167+
* NB potentially lossy, so lower priority
168+
*
169+
* @input realType the {@link RealType} to convert
170+
* @output the {@link Integer}, converted from {@code realType}
171+
* @implNote op names='engine.convert, convert.int32'
172+
*/
173+
public final Function<T, Integer> realTypeToInteger = i -> (int) i.getRealDouble();
174+
175+
/**
176+
* @input integerType the {@link IntegerType} to convert
177+
* @output the {@link Long}, converted from {@code integerType}
178+
* @implNote op names='engine.convert, convert.int64', priority="10"
179+
*/
180+
public final Function<I, Long> integerTypeToLong = IntegerType::getIntegerLong;
181+
182+
/**
183+
* NB potentially lossy, so lower priority
184+
*
185+
* @input realType the {@link RealType} to convert
186+
* @output the {@link Long}, converted from {@code realType}
187+
* @implNote op names='engine.convert, convert.int64'
188+
*/
189+
public final Function<T, Long> realTypeToLong = i -> (long) i.getRealDouble();
190+
191+
/**
192+
* @input realType the {@link RealType} to convert
193+
* @output the {@link Float}, converted from {@code realType}
194+
* @implNote op names='engine.convert, convert.float32'
195+
*/
196+
public final Function<T, Float> realTypeToFloat = RealType::getRealFloat;
197+
198+
/**
199+
* @input realType the {@link RealType} to convert
200+
* @output the {@link Double}, converted from {@code realType}
201+
* @implNote op names='engine.convert, convert.float64'
202+
*/
203+
public final Function<T, Double> realTypeToDouble = RealType::getRealDouble;
204+
}

scijava-ops-image/src/test/java/org/scijava/ops/image/OpRegressionTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public class OpRegressionTest {
4242

4343
@Test
4444
public void testOpDiscoveryRegression() {
45-
long expected = 1943;
45+
long expected = 1964;
4646
long actual = ops.infos().size();
4747
assertEquals(expected, actual);
4848
}

0 commit comments

Comments
 (0)