diff --git a/core/src/main/java/edu/wpi/grip/core/Range.java b/core/src/main/java/edu/wpi/grip/core/Range.java
new file mode 100644
index 0000000000..5cc71dd4e5
--- /dev/null
+++ b/core/src/main/java/edu/wpi/grip/core/Range.java
@@ -0,0 +1,95 @@
+package edu.wpi.grip.core;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+/**
+ * Holds the lower and upper bounds of a range of numbers.
+ */
+public class Range {
+
+  private double min;
+  private double max;
+
+  /**
+   * Creates a new range with both bounds equal to zero.
+   */
+  public Range() {
+    this(0, 0);
+  }
+
+  /**
+   * Creates a new range with the given bounds.
+   *
+   * @param min the lower end of the range
+   * @param max the upper end of the range
+   *
+   * @throws IllegalArgumentException if min > max
+   */
+  public Range(double min, double max) {
+    checkArgument(min <= max, "Min must be <= max");
+    this.min = min;
+    this.max = max;
+  }
+
+  /**
+   * Creates a new range with the given bounds. This is equivalent to
+   * {@link #Range(double, double) new Range(min, max)} .
+   *
+   * @param min the lower end of the range
+   * @param max the upper end of the range
+   *
+   * @return a new range for the given bounds
+   *
+   * @throws IllegalArgumentException if min > max
+   */
+  public static Range of(double min, double max) {
+    return new Range(min, max);
+  }
+
+  /**
+   * Sets the lower end of the range.
+   *
+   * @param min the new lower end of the range
+   *
+   * @throws IllegalArgumentException if min > max
+   */
+  public void setMin(double min) {
+    checkArgument(min <= max, "Min must be <= max");
+    this.min = min;
+  }
+
+  /**
+   * Sets the upper end of the range.
+   *
+   * @param max the new upper end of the range
+   *
+   * @throws IllegalArgumentException if max < min
+   */
+  public void setMax(double max) {
+    checkArgument(max >= min, "Max must be >= min");
+    this.max = max;
+  }
+
+  /**
+   * Gets the lower bound of the range.
+   *
+   * @return the lower bound of the range
+   */
+  public double getMin() {
+    return min;
+  }
+
+  /**
+   * Gets the upper bound of the range.
+   *
+   * @return the upper bound of the range
+   */
+  public double getMax() {
+    return max;
+  }
+
+  public String toString() {
+    return String.format("Range(%f, %f)", min, max);
+  }
+
+}
diff --git a/core/src/main/java/edu/wpi/grip/core/operations/composite/FilterContoursOperation.java b/core/src/main/java/edu/wpi/grip/core/operations/composite/FilterContoursOperation.java
index 433ccd06cb..9b76f6403c 100644
--- a/core/src/main/java/edu/wpi/grip/core/operations/composite/FilterContoursOperation.java
+++ b/core/src/main/java/edu/wpi/grip/core/operations/composite/FilterContoursOperation.java
@@ -2,6 +2,7 @@
 
 import edu.wpi.grip.core.Operation;
 import edu.wpi.grip.core.OperationDescription;
+import edu.wpi.grip.core.Range;
 import edu.wpi.grip.core.sockets.InputSocket;
 import edu.wpi.grip.core.sockets.OutputSocket;
 import edu.wpi.grip.core.sockets.SocketHint;
@@ -60,8 +61,8 @@ public class FilterContoursOperation implements Operation {
   private final SocketHint<Number> maxHeightHint =
       SocketHints.Inputs.createNumberSpinnerSocketHint("Max Height", 1000, 0, Integer.MAX_VALUE);
 
-  private final SocketHint<List<Number>> solidityHint =
-      SocketHints.Inputs.createNumberListRangeSocketHint("Solidity", 0, 100);
+  private final SocketHint<Range> solidityHint =
+      SocketHints.Inputs.createNumberRangeSocketHint("Solidity", 0, 100);
 
   private final SocketHint<Number> minVertexHint =
       SocketHints.Inputs.createNumberSpinnerSocketHint("Min Vertices", 0, 0, Integer.MAX_VALUE);
@@ -84,7 +85,7 @@ public class FilterContoursOperation implements Operation {
   private final InputSocket<Number> maxWidthSocket;
   private final InputSocket<Number> minHeightSocket;
   private final InputSocket<Number> maxHeightSocket;
-  private final InputSocket<List<Number>> soliditySocket;
+  private final InputSocket<Range> soliditySocket;
   private final InputSocket<Number> minVertexSocket;
   private final InputSocket<Number> maxVertexSocket;
   private final InputSocket<Number> minRatioSocket;
@@ -146,8 +147,8 @@ public void perform() {
     final double maxWidth = maxWidthSocket.getValue().get().doubleValue();
     final double minHeight = minHeightSocket.getValue().get().doubleValue();
     final double maxHeight = maxHeightSocket.getValue().get().doubleValue();
-    final double minSolidity = soliditySocket.getValue().get().get(0).doubleValue();
-    final double maxSolidity = soliditySocket.getValue().get().get(1).doubleValue();
+    final double minSolidity = soliditySocket.getValue().get().getMin();
+    final double maxSolidity = soliditySocket.getValue().get().getMax();
     final double minVertexCount = minVertexSocket.getValue().get().doubleValue();
     final double maxVertexCount = maxVertexSocket.getValue().get().doubleValue();
     final double minRatio = minRatioSocket.getValue().get().doubleValue();
diff --git a/core/src/main/java/edu/wpi/grip/core/operations/composite/FilterLinesOperation.java b/core/src/main/java/edu/wpi/grip/core/operations/composite/FilterLinesOperation.java
index 1f4e9f46f8..aca1e1cf28 100644
--- a/core/src/main/java/edu/wpi/grip/core/operations/composite/FilterLinesOperation.java
+++ b/core/src/main/java/edu/wpi/grip/core/operations/composite/FilterLinesOperation.java
@@ -2,6 +2,7 @@
 
 import edu.wpi.grip.core.Operation;
 import edu.wpi.grip.core.OperationDescription;
+import edu.wpi.grip.core.Range;
 import edu.wpi.grip.core.sockets.InputSocket;
 import edu.wpi.grip.core.sockets.OutputSocket;
 import edu.wpi.grip.core.sockets.SocketHint;
@@ -33,8 +34,8 @@ public class FilterLinesOperation implements Operation {
   private final SocketHint<Number> minLengthHint = SocketHints.Inputs
       .createNumberSpinnerSocketHint("Min Length", 20);
 
-  private final SocketHint<List<Number>> angleHint = SocketHints.Inputs
-      .createNumberListRangeSocketHint("Angle", 0, 360);
+  private final SocketHint<Range> angleHint = SocketHints.Inputs
+      .createNumberRangeSocketHint("Angle", 0, 360);
 
   private final SocketHint<LinesReport> outputHint =
       new SocketHint.Builder<>(LinesReport.class)
@@ -43,7 +44,7 @@ public class FilterLinesOperation implements Operation {
 
   private final InputSocket<LinesReport> inputSocket;
   private final InputSocket<Number> minLengthSocket;
-  private final InputSocket<List<Number>> angleSocket;
+  private final InputSocket<Range> angleSocket;
 
   private final OutputSocket<LinesReport> linesOutputSocket;
 
@@ -78,8 +79,8 @@ public List<OutputSocket> getOutputSockets() {
   public void perform() {
     final LinesReport inputLines = inputSocket.getValue().get();
     final double minLengthSquared = Math.pow(minLengthSocket.getValue().get().doubleValue(), 2);
-    final double minAngle = angleSocket.getValue().get().get(0).doubleValue();
-    final double maxAngle = angleSocket.getValue().get().get(1).doubleValue();
+    final double minAngle = angleSocket.getValue().get().getMin();
+    final double maxAngle = angleSocket.getValue().get().getMax();
 
     List<LinesReport.Line> lines = inputLines.getLines().stream()
         .filter(line -> line.lengthSquared() >= minLengthSquared)
diff --git a/core/src/main/java/edu/wpi/grip/core/operations/composite/FindBlobsOperation.java b/core/src/main/java/edu/wpi/grip/core/operations/composite/FindBlobsOperation.java
index 24dc7116ed..75f30ccb6a 100644
--- a/core/src/main/java/edu/wpi/grip/core/operations/composite/FindBlobsOperation.java
+++ b/core/src/main/java/edu/wpi/grip/core/operations/composite/FindBlobsOperation.java
@@ -2,6 +2,7 @@
 
 import edu.wpi.grip.core.Operation;
 import edu.wpi.grip.core.OperationDescription;
+import edu.wpi.grip.core.Range;
 import edu.wpi.grip.core.sockets.InputSocket;
 import edu.wpi.grip.core.sockets.OutputSocket;
 import edu.wpi.grip.core.sockets.SocketHint;
@@ -34,8 +35,8 @@ public class FindBlobsOperation implements Operation {
   private final SocketHint<Mat> inputHint = SocketHints.Inputs.createMatSocketHint("Input", false);
   private final SocketHint<Number> minAreaHint = SocketHints.Inputs
       .createNumberSpinnerSocketHint("Min Area", 1);
-  private final SocketHint<List<Number>> circularityHint = SocketHints.Inputs
-      .createNumberListRangeSocketHint("Circularity", 0.0, 1.0);
+  private final SocketHint<Range> circularityHint = SocketHints.Inputs
+      .createNumberRangeSocketHint("Circularity", 0.0, 1.0);
   private final SocketHint<Boolean> colorHint = SocketHints
       .createBooleanSocketHint("Dark Blobs", false);
 
@@ -46,7 +47,7 @@ public class FindBlobsOperation implements Operation {
 
   private final InputSocket<Mat> inputSocket;
   private final InputSocket<Number> minAreaSocket;
-  private final InputSocket<List<Number>> circularitySocket;
+  private final InputSocket<Range> circularitySocket;
   private final InputSocket<Boolean> colorSocket;
 
   private final OutputSocket<BlobsReport> outputSocket;
@@ -84,7 +85,7 @@ public List<OutputSocket> getOutputSockets() {
   public void perform() {
     final Mat input = inputSocket.getValue().get();
     final Number minArea = minAreaSocket.getValue().get();
-    final List<Number> circularity = circularitySocket.getValue().get();
+    final Range circularity = circularitySocket.getValue().get();
     final Boolean darkBlobs = colorSocket.getValue().get();
 
 
@@ -98,8 +99,8 @@ public void perform() {
         .blobColor(darkBlobs ? (byte) 0 : (byte) 255)
 
         .filterByCircularity(true)
-        .minCircularity(circularity.get(0).floatValue())
-        .maxCircularity(circularity.get(1).floatValue()));
+        .minCircularity((float) circularity.getMin())
+        .maxCircularity((float) circularity.getMax()));
 
     // Detect the blobs and store them in the output BlobsReport
     final KeyPointVector keyPointVector = new KeyPointVector();
diff --git a/core/src/main/java/edu/wpi/grip/core/operations/composite/HSLThresholdOperation.java b/core/src/main/java/edu/wpi/grip/core/operations/composite/HSLThresholdOperation.java
index d8de90c6ed..6c0f276f10 100644
--- a/core/src/main/java/edu/wpi/grip/core/operations/composite/HSLThresholdOperation.java
+++ b/core/src/main/java/edu/wpi/grip/core/operations/composite/HSLThresholdOperation.java
@@ -3,6 +3,7 @@
 
 import edu.wpi.grip.core.Operation;
 import edu.wpi.grip.core.OperationDescription;
+import edu.wpi.grip.core.Range;
 import edu.wpi.grip.core.sockets.InputSocket;
 import edu.wpi.grip.core.sockets.OutputSocket;
 import edu.wpi.grip.core.sockets.SocketHint;
@@ -37,19 +38,19 @@ public class HSLThresholdOperation extends ThresholdOperation {
 
   private static final Logger logger = Logger.getLogger(HSLThresholdOperation.class.getName());
   private final SocketHint<Mat> inputHint = SocketHints.Inputs.createMatSocketHint("Input", false);
-  private final SocketHint<List<Number>> hueHint = SocketHints.Inputs
-      .createNumberListRangeSocketHint("Hue", 0.0, 180.0);
-  private final SocketHint<List<Number>> saturationHint = SocketHints.Inputs
-      .createNumberListRangeSocketHint("Saturation", 0.0, 255.0);
-  private final SocketHint<List<Number>> luminanceHint = SocketHints.Inputs
-      .createNumberListRangeSocketHint("Luminance", 0.0, 255.0);
+  private final SocketHint<Range> hueHint = SocketHints.Inputs
+      .createNumberRangeSocketHint("Hue", 0.0, 180.0);
+  private final SocketHint<Range> saturationHint = SocketHints.Inputs
+      .createNumberRangeSocketHint("Saturation", 0.0, 255.0);
+  private final SocketHint<Range> luminanceHint = SocketHints.Inputs
+      .createNumberRangeSocketHint("Luminance", 0.0, 255.0);
 
   private final SocketHint<Mat> outputHint = SocketHints.Outputs.createMatSocketHint("Output");
 
   private final InputSocket<Mat> inputSocket;
-  private final InputSocket<List<Number>> hueSocket;
-  private final InputSocket<List<Number>> saturationSocket;
-  private final InputSocket<List<Number>> luminanceSocket;
+  private final InputSocket<Range> hueSocket;
+  private final InputSocket<Range> saturationSocket;
+  private final InputSocket<Range> luminanceSocket;
 
   private final OutputSocket<Mat> outputSocket;
 
@@ -91,20 +92,20 @@ public void perform() {
     }
 
     final Mat output = outputSocket.getValue().get();
-    final List<Number> channel1 = hueSocket.getValue().get();
-    final List<Number> channel2 = saturationSocket.getValue().get();
-    final List<Number> channel3 = luminanceSocket.getValue().get();
+    final Range channel1 = hueSocket.getValue().get();
+    final Range channel2 = saturationSocket.getValue().get();
+    final Range channel3 = luminanceSocket.getValue().get();
 
     // Intentionally 1, 3, 2. This maps to the HLS open cv expects
     final Scalar lowScalar = new Scalar(
-        channel1.get(0).doubleValue(),
-        channel3.get(0).doubleValue(),
-        channel2.get(0).doubleValue(), 0);
+        channel1.getMin(),
+        channel3.getMin(),
+        channel2.getMin(), 0);
 
     final Scalar highScalar = new Scalar(
-        channel1.get(1).doubleValue(),
-        channel3.get(1).doubleValue(),
-        channel2.get(1).doubleValue(), 0);
+        channel1.getMax(),
+        channel3.getMax(),
+        channel2.getMax(), 0);
 
     final Mat low = reallocateMatIfInputSizeOrWidthChanged(dataArray, 0, lowScalar, input);
     final Mat high = reallocateMatIfInputSizeOrWidthChanged(dataArray, 1, highScalar, input);
diff --git a/core/src/main/java/edu/wpi/grip/core/operations/composite/HSVThresholdOperation.java b/core/src/main/java/edu/wpi/grip/core/operations/composite/HSVThresholdOperation.java
index c7e8700b95..a4e87a1128 100644
--- a/core/src/main/java/edu/wpi/grip/core/operations/composite/HSVThresholdOperation.java
+++ b/core/src/main/java/edu/wpi/grip/core/operations/composite/HSVThresholdOperation.java
@@ -2,6 +2,7 @@
 
 import edu.wpi.grip.core.Operation;
 import edu.wpi.grip.core.OperationDescription;
+import edu.wpi.grip.core.Range;
 import edu.wpi.grip.core.sockets.InputSocket;
 import edu.wpi.grip.core.sockets.OutputSocket;
 import edu.wpi.grip.core.sockets.SocketHint;
@@ -37,19 +38,19 @@ public class HSVThresholdOperation extends ThresholdOperation {
 
   private static final Logger logger = Logger.getLogger(HSVThresholdOperation.class.getName());
   private final SocketHint<Mat> inputHint = SocketHints.Inputs.createMatSocketHint("Input", false);
-  private final SocketHint<List<Number>> hueHint = SocketHints.Inputs
-      .createNumberListRangeSocketHint("Hue", 0.0, 180.0);
-  private final SocketHint<List<Number>> saturationHint = SocketHints.Inputs
-      .createNumberListRangeSocketHint("Saturation", 0.0, 255.0);
-  private final SocketHint<List<Number>> valueHint = SocketHints.Inputs
-      .createNumberListRangeSocketHint("Value", 0.0, 255.0);
+  private final SocketHint<Range> hueHint = SocketHints.Inputs
+      .createNumberRangeSocketHint("Hue", 0.0, 180.0);
+  private final SocketHint<Range> saturationHint = SocketHints.Inputs
+      .createNumberRangeSocketHint("Saturation", 0.0, 255.0);
+  private final SocketHint<Range> valueHint = SocketHints.Inputs
+      .createNumberRangeSocketHint("Value", 0.0, 255.0);
 
   private final SocketHint<Mat> outputHint = SocketHints.Outputs.createMatSocketHint("Output");
 
   private final InputSocket<Mat> inputSocket;
-  private final InputSocket<List<Number>> hueSocket;
-  private final InputSocket<List<Number>> saturationSocket;
-  private final InputSocket<List<Number>> valueSocket;
+  private final InputSocket<Range> hueSocket;
+  private final InputSocket<Range> saturationSocket;
+  private final InputSocket<Range> valueSocket;
 
   private final OutputSocket<Mat> outputSocket;
 
@@ -91,18 +92,18 @@ public void perform() {
     }
 
     final Mat output = outputSocket.getValue().get();
-    final List<Number> channel1 = hueSocket.getValue().get();
-    final List<Number> channel2 = saturationSocket.getValue().get();
-    final List<Number> channel3 = valueSocket.getValue().get();
+    final Range channel1 = hueSocket.getValue().get();
+    final Range channel2 = saturationSocket.getValue().get();
+    final Range channel3 = valueSocket.getValue().get();
 
     final Scalar lowScalar = new Scalar(
-        channel1.get(0).doubleValue(),
-        channel2.get(0).doubleValue(),
-        channel3.get(0).doubleValue(), 0);
+        channel1.getMin(),
+        channel2.getMin(),
+        channel3.getMin(), 0);
     final Scalar highScalar = new Scalar(
-        channel1.get(1).doubleValue(),
-        channel2.get(1).doubleValue(),
-        channel3.get(1).doubleValue(), 0);
+        channel1.getMax(),
+        channel2.getMax(),
+        channel3.getMax(), 0);
 
     final Mat low = reallocateMatIfInputSizeOrWidthChanged(dataArray, 0, lowScalar, input);
     final Mat high = reallocateMatIfInputSizeOrWidthChanged(dataArray, 1, highScalar, input);
diff --git a/core/src/main/java/edu/wpi/grip/core/operations/composite/RGBThresholdOperation.java b/core/src/main/java/edu/wpi/grip/core/operations/composite/RGBThresholdOperation.java
index 965d4fea68..8c17868375 100644
--- a/core/src/main/java/edu/wpi/grip/core/operations/composite/RGBThresholdOperation.java
+++ b/core/src/main/java/edu/wpi/grip/core/operations/composite/RGBThresholdOperation.java
@@ -2,6 +2,7 @@
 
 import edu.wpi.grip.core.Operation;
 import edu.wpi.grip.core.OperationDescription;
+import edu.wpi.grip.core.Range;
 import edu.wpi.grip.core.sockets.InputSocket;
 import edu.wpi.grip.core.sockets.OutputSocket;
 import edu.wpi.grip.core.sockets.SocketHint;
@@ -34,20 +35,20 @@ public class RGBThresholdOperation extends ThresholdOperation {
 
   private static final Logger logger = Logger.getLogger(RGBThresholdOperation.class.getName());
   private final SocketHint<Mat> inputHint = SocketHints.Inputs.createMatSocketHint("Input", false);
-  private final SocketHint<List<Number>> redHint = SocketHints.Inputs
-      .createNumberListRangeSocketHint("Red", 0.0, 255.0);
-  private final SocketHint<List<Number>> greenHint = SocketHints.Inputs
-      .createNumberListRangeSocketHint("Green", 0.0, 255.0);
-  private final SocketHint<List<Number>> blueHint = SocketHints.Inputs
-      .createNumberListRangeSocketHint("Blue", 0.0, 255.0);
+  private final SocketHint<Range> redHint = SocketHints.Inputs
+      .createNumberRangeSocketHint("Red", 0.0, 255.0);
+  private final SocketHint<Range> greenHint = SocketHints.Inputs
+      .createNumberRangeSocketHint("Green", 0.0, 255.0);
+  private final SocketHint<Range> blueHint = SocketHints.Inputs
+      .createNumberRangeSocketHint("Blue", 0.0, 255.0);
 
   private final SocketHint<Mat> outputHint = SocketHints.Outputs.createMatSocketHint("Output");
 
 
   private final InputSocket<Mat> inputSocket;
-  private final InputSocket<List<Number>> redSocket;
-  private final InputSocket<List<Number>> greenSocket;
-  private final InputSocket<List<Number>> blueSocket;
+  private final InputSocket<Range> redSocket;
+  private final InputSocket<Range> greenSocket;
+  private final InputSocket<Range> blueSocket;
 
   private final OutputSocket<Mat> outputSocket;
 
@@ -88,19 +89,19 @@ public void perform() {
     }
 
     final Mat output = outputSocket.getValue().get();
-    final List<Number> channel1 = redSocket.getValue().get();
-    final List<Number> channel2 = greenSocket.getValue().get();
-    final List<Number> channel3 = blueSocket.getValue().get();
+    final Range channel1 = redSocket.getValue().get();
+    final Range channel2 = greenSocket.getValue().get();
+    final Range channel3 = blueSocket.getValue().get();
 
     final Scalar lowScalar = new Scalar(
-        channel3.get(0).doubleValue(),
-        channel2.get(0).doubleValue(),
-        channel1.get(0).doubleValue(), 0);
+        channel3.getMin(),
+        channel2.getMin(),
+        channel1.getMin(), 0);
 
     final Scalar highScalar = new Scalar(
-        channel3.get(1).doubleValue(),
-        channel2.get(1).doubleValue(),
-        channel1.get(1).doubleValue(), 0);
+        channel3.getMax(),
+        channel2.getMax(),
+        channel1.getMax(), 0);
 
     final Mat low = reallocateMatIfInputSizeOrWidthChanged(dataArray, 0, lowScalar, input);
     final Mat high = reallocateMatIfInputSizeOrWidthChanged(dataArray, 1, highScalar, input);
diff --git a/core/src/main/java/edu/wpi/grip/core/sockets/SocketHints.java b/core/src/main/java/edu/wpi/grip/core/sockets/SocketHints.java
index 695dbb0fbd..c453d8b059 100644
--- a/core/src/main/java/edu/wpi/grip/core/sockets/SocketHints.java
+++ b/core/src/main/java/edu/wpi/grip/core/sockets/SocketHints.java
@@ -1,15 +1,12 @@
 package edu.wpi.grip.core.sockets;
 
 
-import com.google.common.reflect.TypeToken;
+import edu.wpi.grip.core.Range;
 
 import org.bytedeco.javacpp.opencv_core.Mat;
 import org.bytedeco.javacpp.opencv_core.Point;
 import org.bytedeco.javacpp.opencv_core.Size;
 
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
 import java.util.function.Supplier;
 
 import static com.google.common.base.Preconditions.checkNotNull;
@@ -62,13 +59,13 @@ private static <T> SocketHint.Builder<T> createObjectSocketHintBuilder(
   }
 
   @SuppressWarnings("unchecked")
-  private static SocketHint.Builder<List<Number>> createNumberListSocketHintBuilder(
+  private static SocketHint.Builder<Range> createNumberRangeListSocketHintBuilder(
       final String identifier,
-      final Number[] domain) {
-    return new SocketHint.Builder<>((Class<List<Number>>) new TypeToken<List<Number>>() {
-    }.getRawType()).identifier(identifier)
-        .initialValueSupplier(() -> new ArrayList<>(Arrays.asList(domain)))
-        .domain(new List[]{Arrays.asList(domain)});
+      final double[] domain) {
+    return new SocketHint.Builder<>(Range.class)
+        .identifier(identifier)
+        .initialValueSupplier(() -> new Range(domain[0], domain[1]))
+        .domain(new Range[] {new Range(domain[0], domain[1])});
   }
 
 
@@ -135,10 +132,10 @@ public static SocketHint<Number> createNumberSpinnerSocketHint(final String iden
       return createNumberSocketHintBuilder(identifier, number).view(SocketHint.View.TEXT).build();
     }
 
-    public static SocketHint<List<Number>> createNumberListRangeSocketHint(final String identifier,
-                                                                           final Number low,
-                                                                           final Number high) {
-      return createNumberListSocketHintBuilder(identifier, new Number[]{low, high})
+    public static SocketHint<Range> createNumberRangeSocketHint(final String identifier,
+                                                                final double low,
+                                                                final double high) {
+      return createNumberRangeListSocketHintBuilder(identifier, new double[] {low, high})
           .view(SocketHint.View.RANGE)
           .build();
     }
diff --git a/core/src/test/java/edu/wpi/grip/core/RangeTest.java b/core/src/test/java/edu/wpi/grip/core/RangeTest.java
new file mode 100644
index 0000000000..54ffc7b809
--- /dev/null
+++ b/core/src/test/java/edu/wpi/grip/core/RangeTest.java
@@ -0,0 +1,66 @@
+package edu.wpi.grip.core;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+/**
+ * Tests for {@link Range}.
+ */
+public class RangeTest {
+
+  @Test
+  public void testDefaultConstructor() {
+    Range r = new Range();
+    assertEquals("Default min is not zero", 0, r.getMin(), 0);
+    assertEquals("Default max is not zero", 0, r.getMax(), 0);
+  }
+
+  @Test
+  public void testArgsConstructor() {
+    Range r = new Range(-1, 1);
+    assertEquals("Min was not -1", -1, r.getMin(), 0);
+    assertEquals("Max was not 1", 1, r.getMax(), 0);
+  }
+
+  @Test
+  public void testStaticConstructor() {
+    Range r = Range.of(10, 20);
+    assertEquals("Min was not 10", 10, r.getMin(), 0);
+    assertEquals("Max was not 20", 20, r.getMax(), 0);
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void testMinGreaterThanMax() {
+    new Range(1, -1);
+    fail("Exception should have been thrown if min > max");
+  }
+
+  @Test
+  public void testSetMin() {
+    Range r = new Range();
+    r.setMin(-1);
+    assertEquals("Min was not set correctly", -1, r.getMin(), 0);
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void testSetMinGreaterThanMax() {
+    Range r = new Range();
+    r.setMin(1);
+  }
+
+  @Test
+  public void testSetMax() {
+    Range r = new Range();
+    r.setMax(1);
+    assertEquals("Max was not set correctly", 1, r.getMax(), 0);
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void testSetMaxLessThanMin() {
+    Range r = new Range();
+    r.setMax(-1);
+  }
+
+}
diff --git a/ui/src/main/java/edu/wpi/grip/ui/pipeline/input/InputSocketControllerFactory.java b/ui/src/main/java/edu/wpi/grip/ui/pipeline/input/InputSocketControllerFactory.java
index 75c9dd2d5c..43f1ecea7c 100644
--- a/ui/src/main/java/edu/wpi/grip/ui/pipeline/input/InputSocketControllerFactory.java
+++ b/ui/src/main/java/edu/wpi/grip/ui/pipeline/input/InputSocketControllerFactory.java
@@ -1,5 +1,6 @@
 package edu.wpi.grip.ui.pipeline.input;
 
+import edu.wpi.grip.core.Range;
 import edu.wpi.grip.core.sockets.InputSocket;
 import edu.wpi.grip.core.sockets.SocketHint;
 
@@ -69,12 +70,12 @@ public <T> InputSocketController<T> create(InputSocket<T> socket) {
         }
 
       case RANGE:
-        if (socketHint.getType().equals(List.class)) {
+        if (socketHint.getType().equals(Range.class)) {
           return (InputSocketController<T>) rangeInputSocketControllerFactory.create(
-              (InputSocket<List<Number>>) socket);
+              (InputSocket<Range>) socket);
         } else {
           throw new IllegalArgumentException("Could not create view for socket.  RANGE views must"
-              + " be Lists. "
+              + " be Ranges. "
               + socket.toString());
         }
 
diff --git a/ui/src/main/java/edu/wpi/grip/ui/pipeline/input/RangeInputSocketController.java b/ui/src/main/java/edu/wpi/grip/ui/pipeline/input/RangeInputSocketController.java
index f1cfe9a76c..4dcd1de791 100644
--- a/ui/src/main/java/edu/wpi/grip/ui/pipeline/input/RangeInputSocketController.java
+++ b/ui/src/main/java/edu/wpi/grip/ui/pipeline/input/RangeInputSocketController.java
@@ -1,5 +1,6 @@
 package edu.wpi.grip.ui.pipeline.input;
 
+import edu.wpi.grip.core.Range;
 import edu.wpi.grip.core.events.SocketChangedEvent;
 import edu.wpi.grip.core.sockets.InputSocket;
 import edu.wpi.grip.ui.pipeline.SocketHandleView;
@@ -10,8 +11,6 @@
 
 import org.controlsfx.control.RangeSlider;
 
-import java.util.List;
-
 import javafx.fxml.FXML;
 import javafx.geometry.Pos;
 import javafx.scene.control.Label;
@@ -23,7 +22,7 @@
  * An {@link InputSocketController} that lets the user set a high and low value in a two-element
  * list.
  */
-public class RangeInputSocketController extends InputSocketController<List<Number>> {
+public class RangeInputSocketController extends InputSocketController<Range> {
 
   private final RangeSlider slider;
 
@@ -33,27 +32,21 @@ public class RangeInputSocketController extends InputSocketController<List<Numbe
    */
   @Inject
   RangeInputSocketController(SocketHandleView.Factory socketHandleViewFactory,
-                             @Assisted InputSocket<List<Number>> socket) {
+                             @Assisted InputSocket<Range> socket) {
     super(socketHandleViewFactory, socket);
 
-    final Object[] domain = socket.getSocketHint().getDomain().get();
-    final List<Number> initialValue = socket.getValue().get();
-
-    checkArgument(domain.length == 1 && domain[0] instanceof List,
-        "Sliders must have a domain with a list of two numbers (min and max)");
+    final Range[] domain = socket.getSocketHint().getDomain().get();
+    final Range initialValue = socket.getValue().get();
 
-    @SuppressWarnings("unchecked")
-    final List<Number> extremes = (List<Number>) domain[0];
-    checkArgument((extremes.size() == 2) && (extremes.get(0) instanceof Number) && (extremes.get(1)
-            instanceof Number),
-        "Sliders must have a domain with a list of two numbers (min and max)");
+    checkArgument(domain.length == 1 && domain[0] != null,
+        "Sliders must have a domain with a single range");
 
-    checkArgument(initialValue.size() == 2, "Range sliders must contain two values (low and high)");
+    final Range extremes = domain[0];
 
-    final double min = extremes.get(0).doubleValue();
-    final double max = extremes.get(1).doubleValue();
-    final double initialLow = initialValue.get(0).doubleValue();
-    final double initialHigh = initialValue.get(1).doubleValue();
+    final double min = extremes.getMin();
+    final double max = extremes.getMax();
+    final double initialLow = initialValue.getMin();
+    final double initialHigh = initialValue.getMax();
 
     this.slider = new RangeSlider(min, max, initialLow, initialHigh);
     this.slider.setShowTickMarks(true);
@@ -62,8 +55,8 @@ public class RangeInputSocketController extends InputSocketController<List<Numbe
 
     // Set the socket values whenever the range changes
     this.slider.lowValueProperty().addListener(o -> {
-      List<Number> value = socket.getValue().get();
-      value.set(0, slider.getLowValue());
+      Range value = socket.getValue().get();
+      value.setMin(slider.getLowValue());
 
       // If the high value is also changing simultaneously, don't call setValue() twice
       if (!this.slider.isHighValueChanging()) {
@@ -72,8 +65,8 @@ public class RangeInputSocketController extends InputSocketController<List<Numbe
     });
 
     this.slider.highValueProperty().addListener(o -> {
-      List<Number> range = socket.getValue().get();
-      range.set(1, slider.getHighValue());
+      Range range = socket.getValue().get();
+      range.setMax(slider.getHighValue());
       socket.setValue(range);
     });
   }
@@ -105,12 +98,12 @@ private String getLowHighLabelText() {
   @Subscribe
   public void updateSliderValue(SocketChangedEvent event) {
     if (event.isRegarding(this.getSocket())) {
-      this.slider.setLowValue(this.getSocket().getValue().get().get(0).doubleValue());
-      this.slider.setHighValue(this.getSocket().getValue().get().get(1).doubleValue());
+      this.slider.setLowValue(this.getSocket().getValue().get().getMin());
+      this.slider.setHighValue(this.getSocket().getValue().get().getMax());
     }
   }
 
   public interface Factory {
-    RangeInputSocketController create(InputSocket<List<Number>> socket);
+    RangeInputSocketController create(InputSocket<Range> socket);
   }
 }
diff --git a/ui/src/main/resources/edu/wpi/grip/ui/codegeneration/cpp/macros.vm b/ui/src/main/resources/edu/wpi/grip/ui/codegeneration/cpp/macros.vm
index 1d2923bb83..02932b4d68 100644
--- a/ui/src/main/resources/edu/wpi/grip/ui/codegeneration/cpp/macros.vm
+++ b/ui/src/main/resources/edu/wpi/grip/ui/codegeneration/cpp/macros.vm
@@ -25,7 +25,7 @@ set$source.value()(#cType($source.type()) #funPassType($source.type())$source.va
 
 #macro(param $inp $name)
 #set($type = "#cType($inp.type())")
-#if($type.equalsIgnoreCase("list"))
+#if($type.equalsIgnoreCase("range"))
 double ${name}[]#else
 $type #funPassType($inp.type())$name#end#end
 
@@ -61,7 +61,7 @@ $name(#foreach($inp in $step.getInputs())#param($inp $names[$count])#set($count
 	int ${tMeth.name($input.name())} = #cvVal($input.value());
 #elseif ($input.type().equals("String"))
 	string ${tMeth.name($input.name())} = "$input.value()";
-#elseif ($input.type().equals("List"))
+#elseif ($input.type().equalsIgnoreCase("range"))
 #set($inputEndValue = $input.value().length() - 1)
 	double ${tMeth.name($input.name())}[] = {$input.value().substring(1,$inputEndValue)};
 #elseif ($input.type().equals("Point") || $input.type().equals("Size") || $input.type().equalsIgnoreCase("Scalar"))
diff --git a/ui/src/main/resources/edu/wpi/grip/ui/codegeneration/java/macros.vm b/ui/src/main/resources/edu/wpi/grip/ui/codegeneration/java/macros.vm
index 0225fd914d..80392438b3 100644
--- a/ui/src/main/resources/edu/wpi/grip/ui/codegeneration/java/macros.vm
+++ b/ui/src/main/resources/edu/wpi/grip/ui/codegeneration/java/macros.vm
@@ -30,7 +30,7 @@ Ref<$obj>#end
 		$input.type() ${tMeth.name($input.name())} = "$input.value()";
 #elseif ($input.type().equals("Scalar") || $input.type().equals("Size") || $input.type().equals("Point"))
 		$input.type() ${tMeth.name($input.name())} = new $input.type()$input.value();
-#elseif ($input.type().equals("List"))
+#elseif ($input.type().equals("Range"))
 #set($inputEndValue = $input.value().length() - 1)
 		double[] ${tMeth.name($input.name())} = {$input.value().substring(1,$inputEndValue)};
 #elseif ($input.type().equals("Mat") && ! ($input.value().toLowerCase().contains("source") || $input.value().toLowerCase().contains("output")))
diff --git a/ui/src/main/resources/edu/wpi/grip/ui/codegeneration/python/macros.vm b/ui/src/main/resources/edu/wpi/grip/ui/codegeneration/python/macros.vm
index 1354ef5706..455a9dd6d6 100644
--- a/ui/src/main/resources/edu/wpi/grip/ui/codegeneration/python/macros.vm
+++ b/ui/src/main/resources/edu/wpi/grip/ui/codegeneration/python/macros.vm
@@ -12,7 +12,7 @@ $input.type().contains("Type") || $input.type().equals("Interpolation"))
 #input($inp) = cv2.#enum($inp.value())#elseif($inp.value().equals("null") || $inp.type().equals("Mat"))
 #input($inp) = None#elseif($inp.value().equals("false"))
 #input($inp) = False#elseif($inp.value().equals("true"))
-#input($inp) = True#elseif ($inp.type().equals("List"))
+#input($inp) = True#elseif ($inp.type().equals("Range"))
 #set($inputEndValue = $inp.value().length() - 1)
 #input($inp) = [$inp.value().substring(1,$inputEndValue)]#elseif ($inp.type().equals("String"))
 #input($inp) = "$inp.value()"#elseif($inp.type().equals("Size") || $inp.type().equals("Point") || $inp.type().equals("Scalar"))
diff --git a/ui/src/test/java/edu/wpi/grip/ui/codegeneration/ConvexHullsGenerationTesting.java b/ui/src/test/java/edu/wpi/grip/ui/codegeneration/ConvexHullsGenerationTesting.java
index 22c7090f97..f98de1aaea 100644
--- a/ui/src/test/java/edu/wpi/grip/ui/codegeneration/ConvexHullsGenerationTesting.java
+++ b/ui/src/test/java/edu/wpi/grip/ui/codegeneration/ConvexHullsGenerationTesting.java
@@ -2,6 +2,7 @@
 
 import edu.wpi.grip.core.ManualPipelineRunner;
 import edu.wpi.grip.core.OperationMetaData;
+import edu.wpi.grip.core.Range;
 import edu.wpi.grip.core.Step;
 import edu.wpi.grip.core.operations.composite.ContoursReport;
 import edu.wpi.grip.core.operations.composite.ConvexHullsOperation;
@@ -22,7 +23,6 @@
 import org.opencv.core.Scalar;
 import org.opencv.imgproc.Imgproc;
 
-import java.util.ArrayList;
 import java.util.List;
 import java.util.Optional;
 
@@ -31,17 +31,11 @@
 @Category(GenerationTesting.class)
 public class ConvexHullsGenerationTesting extends AbstractGenerationTesting {
   private static final boolean externalBool = false;
-  private final List<Number> hVal = new ArrayList<Number>();
-  private final List<Number> sVal = new ArrayList<Number>();
-  private final List<Number> lVal = new ArrayList<Number>();
+  private final Range hVal = new Range(1.2, 51);
+  private final Range sVal = new Range(2.2, 83.2);
+  private final Range lVal = new Range(1, 101);
 
   public ConvexHullsGenerationTesting() {
-    hVal.add(new Double(1.2));
-    hVal.add(new Double(51.0));
-    sVal.add(new Double(2.2));
-    sVal.add(new Double(83.2));
-    lVal.add(new Double(1.0));
-    lVal.add(new Double(101.0));
   }
 
   void generatePipeline() {
diff --git a/ui/src/test/java/edu/wpi/grip/ui/codegeneration/DistanceTransformGenerationTesting.java b/ui/src/test/java/edu/wpi/grip/ui/codegeneration/DistanceTransformGenerationTesting.java
index 7a8d3896b0..5e1c9c588d 100644
--- a/ui/src/test/java/edu/wpi/grip/ui/codegeneration/DistanceTransformGenerationTesting.java
+++ b/ui/src/test/java/edu/wpi/grip/ui/codegeneration/DistanceTransformGenerationTesting.java
@@ -2,6 +2,7 @@
 
 import edu.wpi.grip.core.ManualPipelineRunner;
 import edu.wpi.grip.core.OperationMetaData;
+import edu.wpi.grip.core.Range;
 import edu.wpi.grip.core.Step;
 import edu.wpi.grip.core.operations.composite.DistanceTransformOperation;
 import edu.wpi.grip.core.sockets.InputSocket;
@@ -16,7 +17,6 @@
 import org.opencv.core.CvType;
 import org.opencv.core.Mat;
 
-import java.util.ArrayList;
 import java.util.Optional;
 
 import static org.junit.Assert.assertFalse;
@@ -41,9 +41,7 @@ public class DistanceTransformGenerationTesting extends AbstractGenerationTestin
   }
 
   public boolean init() {
-    ArrayList<Number> lVal = new ArrayList<Number>();
-    lVal.add(new Double(0.0));
-    lVal.add(new Double(250.0));
+    Range lVal = new Range(0, 250);
     GripIconHSLSetup.setup(this, GripIconHSLSetup.getHVal(), GripIconHSLSetup.getSVal(), lVal);
     Step dist = gen.addStep(new OperationMetaData(DistanceTransformOperation.DESCRIPTION,
         () -> new DistanceTransformOperation(isf, osf)));
diff --git a/ui/src/test/java/edu/wpi/grip/ui/codegeneration/FilterContoursGenerationTesting.java b/ui/src/test/java/edu/wpi/grip/ui/codegeneration/FilterContoursGenerationTesting.java
index 3bed35863d..f0cf1cef3a 100644
--- a/ui/src/test/java/edu/wpi/grip/ui/codegeneration/FilterContoursGenerationTesting.java
+++ b/ui/src/test/java/edu/wpi/grip/ui/codegeneration/FilterContoursGenerationTesting.java
@@ -2,6 +2,7 @@
 
 import edu.wpi.grip.core.ManualPipelineRunner;
 import edu.wpi.grip.core.OperationMetaData;
+import edu.wpi.grip.core.Range;
 import edu.wpi.grip.core.Step;
 import edu.wpi.grip.core.operations.composite.ContoursReport;
 import edu.wpi.grip.core.operations.composite.FilterContoursOperation;
@@ -23,8 +24,6 @@
 import org.opencv.core.Scalar;
 import org.opencv.imgproc.Imgproc;
 
-import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 import java.util.Optional;
 
@@ -33,17 +32,11 @@
 @Category(GenerationTesting.class)
 public class FilterContoursGenerationTesting extends AbstractGenerationTesting {
   private static final boolean externalBool = false;
-  private final List<Number> hVal = new ArrayList<Number>();
-  private final List<Number> sVal = new ArrayList<Number>();
-  private final List<Number> lVal = new ArrayList<Number>();
+  private final Range hVal = new Range(1.2, 51);
+  private final Range sVal = new Range(2.2, 83.2);
+  private final Range lVal = new Range(1, 101);
 
   public FilterContoursGenerationTesting() {
-    hVal.add(new Double(1.2));
-    hVal.add(new Double(51.0));
-    sVal.add(new Double(2.2));
-    sVal.add(new Double(83.2));
-    lVal.add(new Double(1.0));
-    lVal.add(new Double(101.0));
   }
 
   void generatePipeline(String socketName, Object value) {
@@ -114,7 +107,7 @@ public void filterContoursMaxHeightTest() {
   @Test
   public void filterContoursSolidityTest() {
     test(() -> {
-      generatePipeline("Solidity", Arrays.asList(1.0, 50.0));
+      generatePipeline("Solidity", new Range(1, 50));
       return true;
     }, (pip) -> pipelineTest(pip), "FilterContoursSolidityTest");
   }
diff --git a/ui/src/test/java/edu/wpi/grip/ui/codegeneration/FilterLinesGenerationTesting.java b/ui/src/test/java/edu/wpi/grip/ui/codegeneration/FilterLinesGenerationTesting.java
index 6a378c7613..11a6dc6460 100644
--- a/ui/src/test/java/edu/wpi/grip/ui/codegeneration/FilterLinesGenerationTesting.java
+++ b/ui/src/test/java/edu/wpi/grip/ui/codegeneration/FilterLinesGenerationTesting.java
@@ -2,6 +2,7 @@
 
 import edu.wpi.grip.core.ManualPipelineRunner;
 import edu.wpi.grip.core.OperationMetaData;
+import edu.wpi.grip.core.Range;
 import edu.wpi.grip.core.Step;
 import edu.wpi.grip.core.operations.composite.FilterLinesOperation;
 import edu.wpi.grip.core.operations.composite.FindLinesOperation;
@@ -19,8 +20,6 @@
 import org.junit.Test;
 import org.junit.experimental.categories.Category;
 
-import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 import java.util.Optional;
 
@@ -29,20 +28,14 @@
 
 @Category(GenerationTesting.class)
 public class FilterLinesGenerationTesting extends AbstractGenerationTesting {
-  private final List angleVal = Arrays.asList(160.0, 200.0);
+  private final Range angleVal = new Range(160, 200);
   private static final int minLength = 30;
 
-  private final List<Number> hVal = new ArrayList<Number>();
-  private final List<Number> sVal = new ArrayList<Number>();
-  private final List<Number> lVal = new ArrayList<Number>();
+  private final Range hVal = new Range(1.2, 51);
+  private final Range sVal = new Range(2.2, 83.2);
+  private final Range lVal = new Range(1, 101);
 
   public FilterLinesGenerationTesting() {
-    hVal.add(new Double(1.2));
-    hVal.add(new Double(51.0));
-    sVal.add(new Double(2.2));
-    sVal.add(new Double(83.2));
-    lVal.add(new Double(1.0));
-    lVal.add(new Double(101.0));
   }
 
   @Before
diff --git a/ui/src/test/java/edu/wpi/grip/ui/codegeneration/FindBlobsGenerationTesting.java b/ui/src/test/java/edu/wpi/grip/ui/codegeneration/FindBlobsGenerationTesting.java
index ab6eb4337c..3d758563b9 100644
--- a/ui/src/test/java/edu/wpi/grip/ui/codegeneration/FindBlobsGenerationTesting.java
+++ b/ui/src/test/java/edu/wpi/grip/ui/codegeneration/FindBlobsGenerationTesting.java
@@ -2,6 +2,7 @@
 
 import edu.wpi.grip.core.ManualPipelineRunner;
 import edu.wpi.grip.core.OperationMetaData;
+import edu.wpi.grip.core.Range;
 import edu.wpi.grip.core.Step;
 import edu.wpi.grip.core.operations.composite.BlobsReport;
 import edu.wpi.grip.core.operations.composite.FindBlobsOperation;
@@ -17,8 +18,6 @@
 import org.opencv.core.KeyPoint;
 import org.opencv.core.MatOfKeyPoint;
 
-import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 import java.util.Optional;
 
@@ -26,20 +25,14 @@
 
 @Category(GenerationTesting.class)
 public class FindBlobsGenerationTesting extends AbstractGenerationTesting {
-  private final List<Number> hVal = new ArrayList<Number>();
-  private final List<Number> sVal = new ArrayList<Number>();
-  private final List<Number> lVal = new ArrayList<Number>();
+  private final Range hVal = new Range(1.2, 51);
+  private final Range sVal = new Range(2.2, 83.2);
+  private final Range lVal = new Range(1, 101);
 
   public FindBlobsGenerationTesting() {
-    hVal.add(new Double(1.2));
-    hVal.add(new Double(51.0));
-    sVal.add(new Double(2.2));
-    sVal.add(new Double(83.2));
-    lVal.add(new Double(1.0));
-    lVal.add(new Double(101.0));
   }
 
-  void generatePipeline(boolean darkBool, double minArea, List<Double> circularity) {
+  void generatePipeline(boolean darkBool, double minArea, Range circularity) {
     Step step0 = gen.addStep(new OperationMetaData(HSLThresholdOperation.DESCRIPTION,
         () -> new HSLThresholdOperation(isf, osf)));
     loadImage(Files.imageFile);
@@ -76,7 +69,7 @@ void generatePipeline(boolean darkBool, double minArea, List<Double> circularity
   @Test
   public void findBlobsTest() {
     test(() -> {
-      generatePipeline(false, 0, Arrays.asList(0.0, 1.0));
+      generatePipeline(false, 0, new Range(0.0, 1.0));
       return true;
     }, (pip) -> pipelineTest(pip), "FindBlobsTest");
   }
@@ -84,7 +77,7 @@ public void findBlobsTest() {
   @Test
   public void findBlackBlobsTest() {
     test(() -> {
-      generatePipeline(true, 0, Arrays.asList(0.0, 1.0));
+      generatePipeline(true, 0, new Range(0.0, 1.0));
       return true;
     }, (pip) -> pipelineTest(pip), "FindBlackBlobsTest");
   }
@@ -92,7 +85,7 @@ public void findBlackBlobsTest() {
   @Test
   public void findSomeBlobsTest() {
     test(() -> {
-      generatePipeline(false, 9, Arrays.asList(0.0, 0.9));
+      generatePipeline(false, 9, new Range(0.0, 0.9));
       return true;
     }, (pip) -> pipelineTest(pip), "FindSomeBlobsTest");
   }
diff --git a/ui/src/test/java/edu/wpi/grip/ui/codegeneration/FindContoursGenerationTesting.java b/ui/src/test/java/edu/wpi/grip/ui/codegeneration/FindContoursGenerationTesting.java
index ec339835d7..67d3012492 100644
--- a/ui/src/test/java/edu/wpi/grip/ui/codegeneration/FindContoursGenerationTesting.java
+++ b/ui/src/test/java/edu/wpi/grip/ui/codegeneration/FindContoursGenerationTesting.java
@@ -2,6 +2,7 @@
 
 import edu.wpi.grip.core.ManualPipelineRunner;
 import edu.wpi.grip.core.OperationMetaData;
+import edu.wpi.grip.core.Range;
 import edu.wpi.grip.core.Step;
 import edu.wpi.grip.core.operations.composite.ContoursReport;
 import edu.wpi.grip.core.operations.composite.FindContoursOperation;
@@ -22,7 +23,6 @@
 import org.opencv.core.Scalar;
 import org.opencv.imgproc.Imgproc;
 
-import java.util.ArrayList;
 import java.util.List;
 import java.util.Optional;
 
@@ -31,17 +31,11 @@
 
 @Category(GenerationTesting.class)
 public class FindContoursGenerationTesting extends AbstractGenerationTesting {
-  private final List<Number> hVal = new ArrayList<Number>();
-  private final List<Number> sVal = new ArrayList<Number>();
-  private final List<Number> lVal = new ArrayList<Number>();
+  private final Range hVal = new Range(1.2, 51);
+  private final Range sVal = new Range(2.2, 83.2);
+  private final Range lVal = new Range(1, 101);
 
   public FindContoursGenerationTesting() {
-    hVal.add(new Double(1.2));
-    hVal.add(new Double(51.0));
-    sVal.add(new Double(2.2));
-    sVal.add(new Double(83.2));
-    lVal.add(new Double(1.0));
-    lVal.add(new Double(101.0));
   }
 
   void generatePipeline(boolean externalBool) {
diff --git a/ui/src/test/java/edu/wpi/grip/ui/codegeneration/FindLinesGenerationTesting.java b/ui/src/test/java/edu/wpi/grip/ui/codegeneration/FindLinesGenerationTesting.java
index 25f79bc2ef..e0587835ac 100644
--- a/ui/src/test/java/edu/wpi/grip/ui/codegeneration/FindLinesGenerationTesting.java
+++ b/ui/src/test/java/edu/wpi/grip/ui/codegeneration/FindLinesGenerationTesting.java
@@ -2,6 +2,7 @@
 
 import edu.wpi.grip.core.ManualPipelineRunner;
 import edu.wpi.grip.core.OperationMetaData;
+import edu.wpi.grip.core.Range;
 import edu.wpi.grip.core.Step;
 import edu.wpi.grip.core.operations.composite.FindLinesOperation;
 import edu.wpi.grip.core.operations.composite.HSLThresholdOperation;
@@ -18,7 +19,6 @@
 import org.junit.Test;
 import org.junit.experimental.categories.Category;
 
-import java.util.ArrayList;
 import java.util.List;
 import java.util.Optional;
 import java.util.logging.Logger;
@@ -30,17 +30,11 @@
 public class FindLinesGenerationTesting extends AbstractGenerationTesting {
   private static final Logger logger = Logger.getLogger(FindLinesGenerationTesting.class.getName());
 
-  private final List<Number> hVal = new ArrayList<Number>();
-  private final List<Number> sVal = new ArrayList<Number>();
-  private final List<Number> lVal = new ArrayList<Number>();
+  private final Range hVal = new Range(1.2, 51);
+  private final Range sVal = new Range(2.2, 83.2);
+  private final Range lVal = new Range(1, 101);
 
   public FindLinesGenerationTesting() {
-    hVal.add(new Double(1.2));
-    hVal.add(new Double(51.0));
-    sVal.add(new Double(2.2));
-    sVal.add(new Double(83.2));
-    lVal.add(new Double(1.0));
-    lVal.add(new Double(101.0));
   }
 
   @Before
diff --git a/ui/src/test/java/edu/wpi/grip/ui/codegeneration/GripIconHSLSetup.java b/ui/src/test/java/edu/wpi/grip/ui/codegeneration/GripIconHSLSetup.java
index 63ebdddfe4..a708122fd4 100644
--- a/ui/src/test/java/edu/wpi/grip/ui/codegeneration/GripIconHSLSetup.java
+++ b/ui/src/test/java/edu/wpi/grip/ui/codegeneration/GripIconHSLSetup.java
@@ -1,32 +1,24 @@
 package edu.wpi.grip.ui.codegeneration;
 
 import edu.wpi.grip.core.OperationMetaData;
+import edu.wpi.grip.core.Range;
 import edu.wpi.grip.core.Step;
 import edu.wpi.grip.core.operations.composite.HSLThresholdOperation;
 import edu.wpi.grip.core.sockets.InputSocket;
 import edu.wpi.grip.core.sockets.OutputSocket;
 import edu.wpi.grip.util.Files;
 
-import java.util.Arrays;
-import java.util.List;
-
 public class GripIconHSLSetup {
-  private static final List<Number> defaultHVal;
-  private static final List<Number> defaultSVal;
-  private static final List<Number> defaultLVal;
-
-  static {
-    defaultHVal = Arrays.asList(0.0d, 49.0d);
-    defaultSVal = Arrays.asList(0.0d, 41.0d);
-    defaultLVal = Arrays.asList(0.0d, 67.0d);
-  }
+  private static final Range defaultHVal = new Range(0, 49);
+  private static final Range defaultSVal = new Range(0, 41);
+  private static final Range defaultLVal = new Range(0, 67);
 
   public static void setup(AbstractGenerationTesting caller) {
     setup(caller, defaultHVal, defaultSVal, defaultLVal);
   }
 
-  public static void setup(AbstractGenerationTesting caller, List<Number> hVal, List<Number> sVal,
-                           List<Number> lVal) {
+  public static void setup(AbstractGenerationTesting caller, Range hVal, Range sVal,
+                           Range lVal) {
     Step hsl = caller.gen.addStep(new OperationMetaData(HSLThresholdOperation.DESCRIPTION,
         () -> new HSLThresholdOperation(caller.isf, caller.osf)));
     caller.loadImage(Files.imageFile);
@@ -44,15 +36,15 @@ public static void setup(AbstractGenerationTesting caller, List<Number> hVal, Li
     }
   }
 
-  public static List<Number> getHVal() {
+  public static Range getHVal() {
     return defaultHVal;
   }
 
-  public static List<Number> getSVal() {
+  public static Range getSVal() {
     return defaultSVal;
   }
 
-  public static List<Number> getLVal() {
+  public static Range getLVal() {
     return defaultLVal;
   }
 
diff --git a/ui/src/test/java/edu/wpi/grip/ui/codegeneration/HSLThresholdGenerationTesting.java b/ui/src/test/java/edu/wpi/grip/ui/codegeneration/HSLThresholdGenerationTesting.java
index c04b639513..70f01645f2 100644
--- a/ui/src/test/java/edu/wpi/grip/ui/codegeneration/HSLThresholdGenerationTesting.java
+++ b/ui/src/test/java/edu/wpi/grip/ui/codegeneration/HSLThresholdGenerationTesting.java
@@ -1,6 +1,7 @@
 package edu.wpi.grip.ui.codegeneration;
 
 import edu.wpi.grip.core.ManualPipelineRunner;
+import edu.wpi.grip.core.Range;
 import edu.wpi.grip.ui.codegeneration.tools.GenType;
 import edu.wpi.grip.ui.codegeneration.tools.HelperTools;
 import edu.wpi.grip.ui.codegeneration.tools.PipelineInterfacer;
@@ -9,8 +10,6 @@
 import org.junit.Test;
 import org.opencv.core.Mat;
 
-import java.util.ArrayList;
-import java.util.List;
 import java.util.Optional;
 
 import static org.junit.Assert.assertFalse;
@@ -20,17 +19,11 @@ public class HSLThresholdGenerationTesting extends AbstractGenerationTesting {
   // H 0-49
   // S 0-41
   // L 0-67
-  private final List<Number> hVal = new ArrayList<Number>();
-  private final List<Number> sVal = new ArrayList<Number>();
-  private final List<Number> lVal = new ArrayList<Number>();
+  private final Range hVal = new Range(0, 49);
+  private final Range sVal = new Range(0, 41);
+  private final Range lVal = new Range(0, 67);
 
   public HSLThresholdGenerationTesting() {
-    hVal.add(new Double(0.0));
-    hVal.add(new Double(49.0));
-    sVal.add(new Double(0.0));
-    sVal.add(new Double(41.0));
-    lVal.add(new Double(0.0));
-    lVal.add(new Double(67.0));
   }
 
   @Test
diff --git a/ui/src/test/java/edu/wpi/grip/ui/codegeneration/HSVThresholdSetup.java b/ui/src/test/java/edu/wpi/grip/ui/codegeneration/HSVThresholdSetup.java
index 5124e2239d..9b2ca3d37e 100644
--- a/ui/src/test/java/edu/wpi/grip/ui/codegeneration/HSVThresholdSetup.java
+++ b/ui/src/test/java/edu/wpi/grip/ui/codegeneration/HSVThresholdSetup.java
@@ -1,20 +1,18 @@
 package edu.wpi.grip.ui.codegeneration;
 
 import edu.wpi.grip.core.OperationMetaData;
+import edu.wpi.grip.core.Range;
 import edu.wpi.grip.core.Step;
 import edu.wpi.grip.core.operations.composite.HSVThresholdOperation;
 import edu.wpi.grip.core.sockets.InputSocket;
 import edu.wpi.grip.core.sockets.OutputSocket;
 import edu.wpi.grip.util.Files;
 
-import java.util.Arrays;
-import java.util.List;
-
 public class HSVThresholdSetup {
   static void setup(AbstractGenerationTesting caller) {
-    List<Number> hVal = Arrays.asList(50.0d, 180.0d);
-    List<Number> sVal = Arrays.asList(0.0d, 255.0d);
-    List<Number> vVal = Arrays.asList(0.0d, 255.0d);
+    Range hVal = new Range(50, 180);
+    Range sVal = new Range(0, 255);
+    Range vVal = new Range(0, 255);
 
     Step hsv = caller.gen.addStep(new OperationMetaData(HSVThresholdOperation.DESCRIPTION,
         () -> new HSVThresholdOperation(caller.isf, caller.osf)));
diff --git a/ui/src/test/java/edu/wpi/grip/ui/codegeneration/WatershedGenerationTesting.java b/ui/src/test/java/edu/wpi/grip/ui/codegeneration/WatershedGenerationTesting.java
index 61d1761434..806bf77564 100644
--- a/ui/src/test/java/edu/wpi/grip/ui/codegeneration/WatershedGenerationTesting.java
+++ b/ui/src/test/java/edu/wpi/grip/ui/codegeneration/WatershedGenerationTesting.java
@@ -2,6 +2,7 @@
 
 import edu.wpi.grip.core.ManualPipelineRunner;
 import edu.wpi.grip.core.OperationMetaData;
+import edu.wpi.grip.core.Range;
 import edu.wpi.grip.core.Step;
 import edu.wpi.grip.core.operations.composite.FindContoursOperation;
 import edu.wpi.grip.core.operations.composite.HSLThresholdOperation;
@@ -17,25 +18,17 @@
 import org.junit.Test;
 import org.opencv.core.Mat;
 
-import java.util.ArrayList;
-import java.util.List;
 import java.util.Optional;
 
 import static org.junit.Assert.assertTrue;
 
 public class WatershedGenerationTesting extends AbstractGenerationTesting {
   private static final boolean externalBool = false;
-  private final List<Number> hVal = new ArrayList<Number>();
-  private final List<Number> sVal = new ArrayList<Number>();
-  private final List<Number> lVal = new ArrayList<Number>();
+  private final Range hVal = new Range(1.2, 51);
+  private final Range sVal = new Range(2.2, 83.2);
+  private final Range lVal = new Range(1, 101);
 
   public WatershedGenerationTesting() {
-    hVal.add(new Double(1.2));
-    hVal.add(new Double(51.0));
-    sVal.add(new Double(2.2));
-    sVal.add(new Double(83.2));
-    lVal.add(new Double(1.0));
-    lVal.add(new Double(101.0));
   }
 
   void generatePipeline() {