diff --git a/buildSrc/src/main/java/edu/wpi/gripgenerator/FileParser.java b/buildSrc/src/main/java/edu/wpi/gripgenerator/FileParser.java index 7450d531f8..7ec36f87ac 100644 --- a/buildSrc/src/main/java/edu/wpi/gripgenerator/FileParser.java +++ b/buildSrc/src/main/java/edu/wpi/gripgenerator/FileParser.java @@ -10,11 +10,7 @@ import com.github.javaparser.ast.type.PrimitiveType; import edu.wpi.gripgenerator.defaults.DefaultValueCollector; import edu.wpi.gripgenerator.defaults.EnumDefaultValue; -import edu.wpi.gripgenerator.defaults.ObjectDefaultValue; import edu.wpi.gripgenerator.defaults.PrimitiveDefaultValue; -import edu.wpi.gripgenerator.settings.DefinedMethod; -import edu.wpi.gripgenerator.settings.DefinedMethodCollection; -import edu.wpi.gripgenerator.settings.DefinedParamType; import edu.wpi.gripgenerator.templates.OperationList; import java.io.ByteArrayInputStream; @@ -113,9 +109,6 @@ public Expression getDefaultValue(String defaultValue) { returnMap.putAll(parseOpenImgprc(compilationUnit, collector, operationList)); } - - // Generate the Operation List class last - returnMap.put(operationList.getClassName(), operationList.getDeclaration()); return returnMap; } @@ -126,70 +119,6 @@ public static Map parseOpenImgprc(CompilationUnit imgpr OpenCVEnumVisitor enumVisitor = new OpenCVEnumVisitor(baseClassName, collector); enumVisitor.visit(imgprocDeclaration, compilationUnits); compilationUnits.putAll(enumVisitor.generateCompilationUnits()); - - DefinedMethodCollection collection = new DefinedMethodCollection(baseClassName, - new DefinedMethod("Sobel", false, "Mat", "Mat" - ).addDescription("Find edges by calculating the requested derivative order for the given image."), - new DefinedMethod("medianBlur", false, "Mat", "Mat" - ).addDescription("Apply a Median blur to an image."), - new DefinedMethod("GaussianBlur", false, - new DefinedParamType("Mat"), - new DefinedParamType("Mat"), - new DefinedParamType("Size").setDefaultValue(new ObjectDefaultValue("Size", "1", "1")) - ).addDescription("Apply a Gaussian blur to an image."), - new DefinedMethod("Laplacian", "Mat", "Mat" - ).addDescription("Find edges by calculating the Laplacian for the given image."), - new DefinedMethod("dilate", false, - new DefinedParamType("Mat"), - new DefinedParamType("Mat"), - new DefinedParamType("Mat").setDefaultValue(new ObjectDefaultValue("Mat")) - ).addDescription("Expands areas of higher values in an image."), - new DefinedMethod("Canny", false, - new DefinedParamType("Mat"), - new DefinedParamType("Mat", DefinedParamType.DefinedParamDirection.OUTPUT) - ).addDescription("Apply a \\\"canny edge detection\\\" algorithm to an image."), - new DefinedMethod("threshold", false, - new DefinedParamType("Mat"), - new DefinedParamType("Mat"), - new DefinedParamType("double"), - new DefinedParamType("double"), - new DefinedParamType("int").setLiteralDefaultValue("THRESH_BINARY") - ).addDescription("Apply a fixed-level threshold to each array element in an image."), - new DefinedMethod("adaptiveThreshold", false, - new DefinedParamType("Mat"), - new DefinedParamType("Mat"), - new DefinedParamType("double"), - new DefinedParamType("int").setLiteralDefaultValue("ADAPTIVE_THRESH_MEAN_C"), - new DefinedParamType("int").setLiteralDefaultValue("THRESH_BINARY") - ).addDescription("Transforms a grayscale image to a binary image)."), - new DefinedMethod("erode", false, - new DefinedParamType("Mat"), - new DefinedParamType("Mat"), - new DefinedParamType("Mat").setDefaultValue(new ObjectDefaultValue("Mat")) - ).addDescription("Expands areas of lower values in an image."), - new DefinedMethod("cvtColor", false, - new DefinedParamType("Mat"), - new DefinedParamType("Mat"), - new DefinedParamType("int").setLiteralDefaultValue("COLOR_BGR2BGRA") - ).addDescription("Convert an image from one color space to another."), - new DefinedMethod("applyColorMap", true, - new DefinedParamType("Mat"), - new DefinedParamType("Mat"), - new DefinedParamType("int").setLiteralDefaultValue("COLORMAP_AUTUMN") - ).addDescription("Apply a MATLAB equivalent colormap to an image."), - new DefinedMethod("resize", false, - new DefinedParamType("Mat"), - new DefinedParamType("Mat"), - new DefinedParamType("Size").setDefaultValue(new ObjectDefaultValue("Size")) - ).addDescription("Resize the image to the specified size."), - new DefinedMethod("rectangle", false, - new DefinedParamType("Mat", DefinedParamType.DefinedParamDirection.INPUT_AND_OUTPUT), - new DefinedParamType("Point") - ).addDescription("Draw a rectangle (outline or filled) on an image.") - ).setDirectionDefaults(DefinedParamType.DefinedParamDirection.OUTPUT, "dst") - .setIgnoreDefaults("dtype", "ddepth"); - new OpenCVMethodVisitor(collection).visit(imgprocDeclaration, compilationUnits); - collection.generateCompilationUnits(collector, compilationUnits, operations); return compilationUnits; } @@ -201,62 +130,6 @@ public static Map parseOpenCVCore(CompilationUnit coreD enumVisitor.visit(coreDeclaration, compilationUnits); compilationUnits.putAll(enumVisitor.generateCompilationUnits()); - DefinedMethodCollection collection = new DefinedMethodCollection(baseClassName, - new DefinedMethod("add", true, "Mat", "Mat", "Mat") - .addDescription("Calculate the per-pixel sum of two images."), - new DefinedMethod("subtract", true, "Mat", "Mat", "Mat") - .addDescription("Calculate the per-pixel difference between two images."), - new DefinedMethod("multiply", false, "Mat", "Mat", "Mat") - .addDescription("Calculate the per-pixel scaled product of two images."), - new DefinedMethod("divide", false, "Mat", "Mat", "Mat") - .addDescription("Perform per-pixel division of two images."), - new DefinedMethod("scaleAdd", false, "Mat", "double", "Mat", "Mat") - .addDescription("Calculate the sum of two images where one image is multiplied by a scalar."), -// new DefinedMethod("normalize", false, "Mat", "Mat"), - new DefinedMethod("addWeighted", false, "Mat") - .addDescription("Calculate the weighted sum of two images."), - new DefinedMethod("flip", false, - new DefinedParamType("Mat"), - new DefinedParamType("Mat"), - new DefinedParamType("int").setLiteralDefaultValue("Y_AXIS")) - .addDescription("Flip image around vertical, horizontal, or both axes."), - new DefinedMethod("bitwise_and", true, "Mat", "Mat", "Mat") - .addDescription("Calculate the per-element bitwise conjunction of two images."), - new DefinedMethod("bitwise_or", true, "Mat", "Mat", "Mat") - .addDescription("Calculate the per-element bit-wise disjunction of two images."), - new DefinedMethod("bitwise_xor", true, "Mat", "Mat", "Mat") - .addDescription("Calculate the per-element bit-wise \\\"exclusive or\\\" on two images."), - new DefinedMethod("bitwise_not", true, "Mat", "Mat") - .addDescription("Calculate per-element bit-wise inversion of an image."), - new DefinedMethod("absdiff", false, "Mat", "Mat") - .addDescription("Calculate the per-element absolute difference of two images."), - new DefinedMethod("compare", true, - new DefinedParamType("Mat"), - new DefinedParamType("Mat"), - new DefinedParamType("Mat"), - new DefinedParamType("int").setLiteralDefaultValue("CMP_EQ") - ).addDescription("Compare each pixel in two images using a given rule."), - new DefinedMethod("max", false, "Mat", "Mat") - .addDescription("Calculate per-element maximum of two images."), - new DefinedMethod("min", false, "Mat", "Mat") - .addDescription("Calculate the per-element minimum of two images."), - new DefinedMethod("extractChannel", false, "Mat", "Mat") - .addDescription("Extract a single channel from a image."), - new DefinedMethod("transpose", false, "Mat", "Mat") - .addDescription("Calculate the transpose of an image.") -// new DefinedMethod("sqrt", false, "Mat", "Mat"), -// new DefinedMethod("pow", false, -// new DefinedParamType("Mat"), -// new DefinedParamType("double") -// .setDefaultValue(new PrimitiveDefaultValue(new PrimitiveType(PrimitiveType.Primitive.Double), "1")) -// ) - ).setDirectionDefaults(DefinedParamType.DefinedParamDirection.OUTPUT, "dst") - .setIgnoreDefaults("dtype", "ddepth"); - new OpenCVMethodVisitor(collection).visit(coreDeclaration, compilationUnits); - - collection.generateCompilationUnits(collector, compilationUnits, operations); - - return compilationUnits; } diff --git a/core/src/main/java/edu/wpi/grip/core/Connection.java b/core/src/main/java/edu/wpi/grip/core/Connection.java index cef5e94990..acb1bdfecc 100644 --- a/core/src/main/java/edu/wpi/grip/core/Connection.java +++ b/core/src/main/java/edu/wpi/grip/core/Connection.java @@ -58,7 +58,7 @@ public void onConnectionAdded(ConnectionAddedEvent event) { @Subscribe public void onOutputChanged(SocketChangedEvent e) { - if (e.getSocket() == outputSocket) { + if (e.isRegarding(outputSocket)) { inputSocket.setValueOptional(outputSocket.getValue()); } } @@ -76,14 +76,14 @@ public void removeConnection(StepRemovedEvent e) { // Remove this connection if one of the steps it was connected to was removed for (OutputSocket socket : e.getStep().getOutputSockets()) { if (socket == this.outputSocket) { - this.eventBus.post(new ConnectionRemovedEvent(this)); + remove(); return; } } for (InputSocket socket : e.getStep().getInputSockets()) { if (socket == this.inputSocket) { - this.eventBus.post(new ConnectionRemovedEvent(this)); + remove(); return; } } @@ -94,9 +94,16 @@ public void removeConnection(SourceRemovedEvent e) { // Remove this connection if it's from a source that was removed for (OutputSocket socket : e.getSource().getOutputSockets()) { if (socket == this.outputSocket) { - this.eventBus.post(new ConnectionRemovedEvent(this)); + remove(); return; } } } + + /** + * Removes this connection from the pipeline. + */ + public void remove() { + eventBus.post(new ConnectionRemovedEvent(this)); + } } diff --git a/core/src/main/java/edu/wpi/grip/core/GRIPCoreModule.java b/core/src/main/java/edu/wpi/grip/core/GRIPCoreModule.java index fda27d7fef..44b9a40700 100644 --- a/core/src/main/java/edu/wpi/grip/core/GRIPCoreModule.java +++ b/core/src/main/java/edu/wpi/grip/core/GRIPCoreModule.java @@ -12,6 +12,10 @@ import edu.wpi.grip.core.events.UnexpectedThrowableEvent; import edu.wpi.grip.core.serialization.Project; import edu.wpi.grip.core.settings.SettingsProvider; +import edu.wpi.grip.core.sockets.InputSocket; +import edu.wpi.grip.core.sockets.InputSocketImpl; +import edu.wpi.grip.core.sockets.OutputSocket; +import edu.wpi.grip.core.sockets.OutputSocketImpl; import edu.wpi.grip.core.sources.CameraSource; import edu.wpi.grip.core.sources.ImageFileSource; import edu.wpi.grip.core.sources.MultiImageFileSource; @@ -109,6 +113,9 @@ public void hear(TypeLiteral type, TypeEncounter encounter) { bind(ConnectionValidator.class).to(Pipeline.class); bind(Source.SourceFactory.class).to(Source.SourceFactoryImpl.class); + + bind(InputSocket.Factory.class).to(InputSocketImpl.FactoryImpl.class); + bind(OutputSocket.Factory.class).to(OutputSocketImpl.FactoryImpl.class); install(new FactoryModuleBuilder() .implement(CameraSource.class, CameraSource.class) .build(CameraSource.Factory.class)); diff --git a/core/src/main/java/edu/wpi/grip/core/Main.java b/core/src/main/java/edu/wpi/grip/core/Main.java index ebbaef8a26..3bbe07f6a3 100644 --- a/core/src/main/java/edu/wpi/grip/core/Main.java +++ b/core/src/main/java/edu/wpi/grip/core/Main.java @@ -6,11 +6,11 @@ import com.google.inject.Injector; import edu.wpi.grip.core.events.ExceptionClearedEvent; import edu.wpi.grip.core.events.ExceptionEvent; +import edu.wpi.grip.core.operations.CVOperations; import edu.wpi.grip.core.operations.Operations; import edu.wpi.grip.core.operations.network.GRIPNetworkModule; import edu.wpi.grip.core.serialization.Project; import edu.wpi.grip.core.sources.GRIPSourcesHardwareModule; -import edu.wpi.grip.generated.CVOperations; import javax.inject.Inject; import java.io.File; @@ -27,6 +27,7 @@ public class Main { @Inject private PipelineRunner pipelineRunner; @Inject private EventBus eventBus; @Inject private Operations operations; + @Inject private CVOperations cvOperations; @Inject private Logger logger; @SuppressWarnings("PMD.SystemPrintln") @@ -45,7 +46,7 @@ public void start(String[] args) throws IOException, InterruptedException { } operations.addOperations(); - CVOperations.addOperations(eventBus); + cvOperations.addOperations(); final String projectPath = args[0]; diff --git a/core/src/main/java/edu/wpi/grip/core/Operation.java b/core/src/main/java/edu/wpi/grip/core/Operation.java index c57a8cc16b..a319e7a3a4 100644 --- a/core/src/main/java/edu/wpi/grip/core/Operation.java +++ b/core/src/main/java/edu/wpi/grip/core/Operation.java @@ -1,112 +1,40 @@ package edu.wpi.grip.core; -import com.google.common.collect.ImmutableSet; -import com.google.common.eventbus.EventBus; import edu.wpi.grip.core.sockets.InputSocket; import edu.wpi.grip.core.sockets.OutputSocket; -import edu.wpi.grip.core.sockets.SocketsProvider; -import java.io.InputStream; -import java.util.Optional; +import java.util.List; /** - * The common interface used by Steps in a pipeline to call various operations. There is usually only one - * instance of any class that implements Operation, which is called whenever that operation is used. + * The common interface used by Steps in a pipeline to call various operations. Each instance of an + * operation in the pipeline is handled by a unique instance of that {@code Operation} class. */ public interface Operation { - enum Category { - IMAGE_PROCESSING, - FEATURE_DETECTION, - NETWORK, - LOGICAL, - OPENCV, - MISCELLANEOUS, - } - - /** - * @return The unique user-facing name of the operation, such as "Gaussian Blur" - */ - String getName(); - - /** - * @return Any old unique user-facing names of the operation. This is used to preserve compatibility with - * old versions of GRIP if the operation name changes. - */ - default ImmutableSet getAliases() { - return ImmutableSet.of(); - } - - - /** - * @return A description of the operation. - */ - String getDescription(); - - /** - * @return What category the operation falls under. This is used to organize them in the GUI - */ - default Category getCategory() { - return Category.MISCELLANEOUS; - } - /** - * @return An {@link InputStream} of a 128x128 image to show the user as a representation of the operation. - */ - default Optional getIcon() { - return Optional.empty(); - } - - default SocketsProvider createSockets(EventBus eventBus) { - return new SocketsProvider(createInputSockets(eventBus), createOutputSockets(eventBus)); - } - /** - * @param eventBus The Guava {@link EventBus} used by the application. - * @return An array of sockets for the inputs that the operation expects. - */ - InputSocket[] createInputSockets(EventBus eventBus); - - /** - * @param eventBus The Guava {@link EventBus} used by the application. - * @return An array of sockets for the outputs that the operation produces. + * @return A list of sockets for the inputs that the operation expects. + * + * @implNote The returned list should be immutable (i.e. read-only) */ - OutputSocket[] createOutputSockets(EventBus eventBus); + List getInputSockets(); /** - * Override this to provide persistent per-step data + * @return A list of sockets for the outputs that the operation produces. + * + * @implNote The returned list should be immutable (i.e. read-only) */ - default Optional createData() { - return Optional.empty(); - } + List getOutputSockets(); /** - * Perform the operation on the specified inputs, storing the results in the specified outputs. - * - * @param inputs An array obtained from {@link #createInputSockets(EventBus)}. The caller can set the value of - * each socket to an actual parameter for the operation. - * @param outputs An array obtained from {@link #createOutputSockets(EventBus)}. The outputs of the operation will - * be stored in these sockets. - * @param data Optional data to be passed to the operation + * Performs this {@code Operation}. */ - default void perform(InputSocket[] inputs, OutputSocket[] outputs, Optional data) { - perform(inputs, outputs); - } - - default void perform(InputSocket[] inputs, OutputSocket[] outputs) { - throw new UnsupportedOperationException("Perform was not overridden"); - } + void perform(); /** * Allows the step to clean itself up when removed from the pipeline. * This should only be called by {@link Step#setRemoved()} to ensure correct synchronization. - * - * @param inputs An array obtained from {@link #createInputSockets(EventBus)}. The caller can set the value of - * each socket to an actual parameter for the operation. - * @param outputs An array obtained from {@link #createOutputSockets(EventBus)}. The outputs of the operation will - * be stored in these sockets. - * @param data Optional data to be passed to the operation */ - default void cleanUp(InputSocket[] inputs, OutputSocket[] outputs, Optional data) { + default void cleanUp() { /* no-op */ } } diff --git a/core/src/main/java/edu/wpi/grip/core/OperationDescription.java b/core/src/main/java/edu/wpi/grip/core/OperationDescription.java new file mode 100644 index 0000000000..49cb269829 --- /dev/null +++ b/core/src/main/java/edu/wpi/grip/core/OperationDescription.java @@ -0,0 +1,207 @@ +package edu.wpi.grip.core; + +import com.google.common.base.MoreObjects; +import com.google.common.collect.ImmutableSet; +import edu.wpi.grip.core.util.Icon; + +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; +import java.io.InputStream; +import java.util.Optional; +import java.util.Set; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * An interface describing how an operation should be displayed in the {@link Palette} to the user. + */ +@Immutable +public class OperationDescription { + + /** + * The categories that entries can be in. + */ + public enum Category { + IMAGE_PROCESSING, + FEATURE_DETECTION, + NETWORK, + LOGICAL, + OPENCV, + MISCELLANEOUS, + } + + private final String name; + private final String summary; + private final Category category; + private final Icon icon; + private final ImmutableSet aliases; + + /** + * Private constructor - use {@link #builder} to instantiate this class. + */ + private OperationDescription(String name, + String summary, + Category category, + Icon icon, + Set aliases) { + this.name = checkNotNull(name, "Name cannot be null"); + this.summary = checkNotNull(summary, "Summary cannot be null"); + this.category = checkNotNull(category, "Category cannot be null"); + this.icon = icon; // This is allowed to be null + this.aliases = ImmutableSet.copyOf(checkNotNull(aliases, "Aliases cannot be null")); + } + + /** + * @return The unique user-facing name of the operation, such as "Gaussian Blur" + */ + public String name() { + return name; + } + + /** + * @return A summary of the operation. + */ + public String summary() { + return summary; + } + + /** + * @return What category the operation falls under. This is used to organize them in the GUI + */ + public Category category() { + return category; + } + + /** + * @return An {@link InputStream} of a 128x128 image to show the user as a representation of the operation. + */ + public Optional icon() { + return Optional.ofNullable(icon).map(Icon::getStream); + } + + /** + * @return Any old unique user-facing names of the operation. This is used to preserve compatibility with + * old versions of GRIP if the operation name changes. + */ + public ImmutableSet aliases() { + return aliases; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (!(o instanceof OperationDescription)) return false; + + OperationDescription that = (OperationDescription) o; + + if (name() != null ? !name().equals(that.name()) : that.name() != null) return false; + if (summary() != null ? !summary().equals(that.summary()) : that.summary() != null) return false; + if (category() != that.category()) return false; + if (icon() != null ? !icon().equals(that.icon()) : that.icon() != null) return false; + return aliases() != null ? aliases().equals(that.aliases()) : that.aliases() == null; + + } + + @Override + public int hashCode() { + int result = name() != null ? name().hashCode() : 0; + result = 31 * result + (summary() != null ? summary().hashCode() : 0); + result = 31 * result + (category() != null ? category().hashCode() : 0); + result = 31 * result + (icon() != null ? icon().hashCode() : 0); + result = 31 * result + (aliases() != null ? aliases().hashCode() : 0); + return result; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("name", name()) + .add("summary", summary()) + .add("aliases", aliases()) + .add("category", category()) + .toString(); + } + + /** + * Creates a new {@link Builder} instance to create a new {@code OperationDescription} object. + *

+ * The created descriptor has a default category of {@link Category#MISCELLANEOUS MISCELLANEOUS} and no icon; use + * the {@link Builder#category(Category) .category()} and {@link Builder#icon(Icon) .icon()} methods to + * override the default values. + */ + public static Builder builder() { + return new Builder() + .category(Category.MISCELLANEOUS) + .icon(null); + } + + /** + * Builder class for {@code OperationDescription} + */ + public static final class Builder { + private String name; + private String summary = "PLEASE PROVIDE A DESCRIPTION TO THE OPERATION DESCRIPTION!"; + private Category category; + private Icon icon; + private ImmutableSet aliases = ImmutableSet.of(); // default to empty Set to avoid NPE if not assigned + + /** + * Private constructor; use {@link OperationDescription#builder()} to create a builder. + */ + private Builder() { + } + + /** + * Sets the name + */ + public Builder name(String name) { + this.name = checkNotNull(name); + return this; + } + + /** + * Sets the summary + */ + public Builder summary(String summary) { + this.summary = checkNotNull(summary); + return this; + } + + /** + * Sets the category + */ + public Builder category(Category category) { + this.category = checkNotNull(category); + return this; + } + + /** + * Sets the icon + */ + public Builder icon(Icon icon) { + this.icon = icon; + return this; + } + + /** + * Sets the aliases + */ + public Builder aliases(String... aliases) { + this.aliases = ImmutableSet.copyOf(checkNotNull(aliases)); + return this; + } + + /** + * Builds a new {@code OperationDescription} + */ + public OperationDescription build() { + return new OperationDescription( + name, + summary, + category, + icon, + aliases); + } + } + +} diff --git a/core/src/main/java/edu/wpi/grip/core/OperationMetaData.java b/core/src/main/java/edu/wpi/grip/core/OperationMetaData.java new file mode 100644 index 0000000000..b4fb2ad777 --- /dev/null +++ b/core/src/main/java/edu/wpi/grip/core/OperationMetaData.java @@ -0,0 +1,41 @@ +package edu.wpi.grip.core; + +import javax.annotation.concurrent.Immutable; +import java.util.function.Supplier; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Holds metadata for an operation. + */ +@Immutable +public class OperationMetaData { + + private final OperationDescription description; + private final Supplier operationSupplier; + + /** + * Creates a metadata object for an {@link Operation}. + * + * @param description the summary for the {@code Operation} + * @param operationSupplier a supplier for the {@code Operation}. This should return a new instance each time it's called. + */ + public OperationMetaData(OperationDescription description, Supplier operationSupplier) { + this.description = checkNotNull(description); + this.operationSupplier = checkNotNull(operationSupplier); + } + + /** + * Gets the summary of the operation. + */ + public OperationDescription getDescription() { + return description; + } + + /** + * Gets a {@code Supplier} for the operation. This should return a new instance each time it's called. + */ + public Supplier getOperationSupplier() { + return operationSupplier; + } +} diff --git a/core/src/main/java/edu/wpi/grip/core/Palette.java b/core/src/main/java/edu/wpi/grip/core/Palette.java index ea0ed354fd..537a63fbf6 100644 --- a/core/src/main/java/edu/wpi/grip/core/Palette.java +++ b/core/src/main/java/edu/wpi/grip/core/Palette.java @@ -1,10 +1,8 @@ package edu.wpi.grip.core; -import com.google.common.eventbus.EventBus; import com.google.common.eventbus.Subscribe; import edu.wpi.grip.core.events.OperationAddedEvent; -import javax.inject.Inject; import javax.inject.Singleton; import java.util.Collection; import java.util.LinkedHashMap; @@ -20,20 +18,13 @@ @Singleton public class Palette { - private final EventBus eventBus; - - @Inject - Palette(EventBus eventBus) { - this.eventBus = eventBus; - } - - private final Map operations = new LinkedHashMap<>(); + private final Map operations = new LinkedHashMap<>(); @Subscribe public void onOperationAdded(OperationAddedEvent event) { - final Operation operation = event.getOperation(); - map(operation.getName(), operation); - for(String alias : operation.getAliases()) { + final OperationMetaData operation = event.getOperation(); + map(operation.getDescription().name(), operation); + for(String alias : operation.getDescription().aliases()) { map(alias, operation); } } @@ -44,7 +35,7 @@ public void onOperationAdded(OperationAddedEvent event) { * @param operation The operation to map the key to * @throws IllegalArgumentException if the key is already in the {@link #operations} map. */ - private void map(String key, Operation operation) { + private void map(String key, OperationMetaData operation) { checkArgument(!operations.containsKey(key), "Operation name or alias already exists: " + key); operations.put(key, operation); } @@ -52,14 +43,14 @@ private void map(String key, Operation operation) { /** * @return A collection of all available operations */ - public Collection getOperations() { + public Collection getOperations() { return this.operations.values(); } /** * @return The operation with the specified unique name */ - public Optional getOperationByName(String name) { + public Optional getOperationByName(String name) { return Optional.ofNullable(this.operations.get(checkNotNull(name, "name cannot be null"))); } } diff --git a/core/src/main/java/edu/wpi/grip/core/Source.java b/core/src/main/java/edu/wpi/grip/core/Source.java index d8d46ac91f..ce8509ac12 100644 --- a/core/src/main/java/edu/wpi/grip/core/Source.java +++ b/core/src/main/java/edu/wpi/grip/core/Source.java @@ -9,6 +9,7 @@ import edu.wpi.grip.core.util.ExceptionWitness; import java.io.IOException; +import java.util.List; import java.util.Optional; import java.util.Properties; import java.util.logging.Level; @@ -61,7 +62,7 @@ protected Source(ExceptionWitness.Factory exceptionWitnessFactory) { * @return @return An array of {@link OutputSocket}s for the outputs that the source produces. */ public final ImmutableList getOutputSockets() { - final OutputSocket[] outputSockets = this.createOutputSockets(); + final List outputSockets = createOutputSockets(); for (OutputSocket socket : outputSockets) { socket.setSource(Optional.of(this)); } @@ -69,7 +70,7 @@ public final ImmutableList getOutputSockets() { return ImmutableList.copyOf(outputSockets); } - protected abstract OutputSocket[] createOutputSockets(); + protected abstract List createOutputSockets(); /** * This method will check if there are any pending updates to diff --git a/core/src/main/java/edu/wpi/grip/core/Step.java b/core/src/main/java/edu/wpi/grip/core/Step.java index e874f17677..3d2cab11d4 100644 --- a/core/src/main/java/edu/wpi/grip/core/Step.java +++ b/core/src/main/java/edu/wpi/grip/core/Step.java @@ -1,16 +1,15 @@ package edu.wpi.grip.core; import com.google.common.collect.ImmutableList; -import com.google.common.eventbus.EventBus; import com.google.inject.Inject; import com.google.inject.Singleton; import com.thoughtworks.xstream.annotations.XStreamAlias; import edu.wpi.grip.core.sockets.InputSocket; import edu.wpi.grip.core.sockets.OutputSocket; import edu.wpi.grip.core.sockets.Socket; -import edu.wpi.grip.core.sockets.SocketsProvider; import edu.wpi.grip.core.util.ExceptionWitness; +import java.util.List; import java.util.Optional; import java.util.logging.Level; import java.util.logging.Logger; @@ -29,35 +28,33 @@ public class Step { private final ExceptionWitness witness; private final Operation operation; - private final InputSocket[] inputSockets; - private final OutputSocket[] outputSockets; - private final Optional data; + private final OperationDescription description; + private final List inputSockets; + private final List outputSockets; private final Object removedLock = new Object(); private boolean removed = false; @Singleton public static class Factory { - private final EventBus eventBus; private final ExceptionWitness.Factory exceptionWitnessFactory; @Inject - public Factory(EventBus eventBus, ExceptionWitness.Factory exceptionWitnessFactory) { - this.eventBus = eventBus; + public Factory(ExceptionWitness.Factory exceptionWitnessFactory) { this.exceptionWitnessFactory = exceptionWitnessFactory; } - public Step create(Operation operation) { - checkNotNull(operation, "The operation can not be null"); + public Step create(OperationMetaData operationData) { + checkNotNull(operationData, "The operationMetaData cannot be null"); + final Operation operation = operationData.getOperationSupplier().get(); // Create the list of input and output sockets, and mark this step as their owner. - final SocketsProvider socketsProvider = operation.createSockets(eventBus); - final InputSocket[] inputSockets = socketsProvider.inputSockets(); - final OutputSocket[] outputSockets = socketsProvider.outputSockets(); + final List inputSockets = operation.getInputSockets(); + final List outputSockets = operation.getOutputSockets(); final Step step = new Step( operation, + operationData.getDescription(), inputSockets, outputSockets, - operation.createData(), exceptionWitnessFactory ); @@ -74,41 +71,41 @@ public Step create(Operation operation) { /** * @param operation The operation that is performed at this step. + * @param description The description of the operation * @param inputSockets The input sockets from the operation. * @param outputSockets The output sockets provided by the operation. - * @param data The data provided by the operation. * @param exceptionWitnessFactory A factory used to create an {@link ExceptionWitness} */ Step(Operation operation, - InputSocket[] inputSockets, - OutputSocket[] outputSockets, - Optional data, + OperationDescription description, + List inputSockets, + List outputSockets, ExceptionWitness.Factory exceptionWitnessFactory) { this.operation = operation; + this.description = description; this.inputSockets = inputSockets; this.outputSockets = outputSockets; - this.data = data; this.witness = exceptionWitnessFactory.create(this); } /** - * @return The underlying Operation that this step performs + * @return The description for the step */ - public Operation getOperation() { - return this.operation; + public OperationDescription getOperationDescription() { + return this.description; } /** * @return An array of {@link InputSocket InputSockets} that hold the inputs to this step */ - public ImmutableList> getInputSockets() { + public ImmutableList getInputSockets() { return ImmutableList.copyOf(inputSockets); } /** * @return A list of {@link OutputSocket OutputSockets} that hold the outputs of this step */ - public ImmutableList> getOutputSockets() { + public ImmutableList getOutputSockets() { return ImmutableList.copyOf(outputSockets); } @@ -129,6 +126,7 @@ private void resetOutputSockets() { */ protected final void runPerformIfPossible() { boolean anyDirty = false; // Keeps track of if there are sockets that are dirty + for (InputSocket inputSocket : inputSockets) { // If there is a socket that isn't present then we have a problem. if (!inputSocket.getValue().isPresent()) { @@ -138,6 +136,7 @@ protected final void runPerformIfPossible() { } // If one value is true then this will stay true anyDirty |= inputSocket.dirtied(); + } if (!anyDirty) { // If there aren't any dirty inputs // Don't clear the exceptions just return @@ -149,13 +148,13 @@ protected final void runPerformIfPossible() { // while that is happening. synchronized (removedLock) { if (!removed) { - this.operation.perform(inputSockets, outputSockets, data); + this.operation.perform(); } } } catch (RuntimeException e) { // We do not want to catch all exceptions, only runtime exceptions. // This is especially important when it comes to InterruptedExceptions - final String operationFailedMessage = "The " + operation.getName() + " operation did not perform correctly."; + final String operationFailedMessage = String.format("The %s operation did not perform correctly.", getOperationDescription().name()); logger.log(Level.WARNING, operationFailedMessage, e); witness.flagException(e, operationFailedMessage); resetOutputSockets(); @@ -169,7 +168,7 @@ public final void setRemoved() { // if we don't wait then the perform method could end up being run concurrently with the perform methods execution synchronized (removedLock) { removed = true; - operation.cleanUp(inputSockets, outputSockets, data); + operation.cleanUp(); } } diff --git a/core/src/main/java/edu/wpi/grip/core/events/OperationAddedEvent.java b/core/src/main/java/edu/wpi/grip/core/events/OperationAddedEvent.java index e463f22f00..2d6acf01d7 100644 --- a/core/src/main/java/edu/wpi/grip/core/events/OperationAddedEvent.java +++ b/core/src/main/java/edu/wpi/grip/core/events/OperationAddedEvent.java @@ -1,7 +1,7 @@ package edu.wpi.grip.core.events; import com.google.common.base.MoreObjects; -import edu.wpi.grip.core.Operation; +import edu.wpi.grip.core.OperationMetaData; import static com.google.common.base.Preconditions.checkNotNull; @@ -10,19 +10,19 @@ * script, or at startup for the built-in operations. This is NOT the event for adding a new step to the pipeline. */ public class OperationAddedEvent { - private final Operation operation; + private final OperationMetaData operation; /** * @param operation The operation being added */ - public OperationAddedEvent(Operation operation) { + public OperationAddedEvent(OperationMetaData operation) { this.operation = checkNotNull(operation, "Operation cannot be null"); } /** - * @return The operation being added.. + * @return The operation being added. */ - public Operation getOperation() { + public OperationMetaData getOperation() { return this.operation; } diff --git a/core/src/main/java/edu/wpi/grip/core/events/SocketChangedEvent.java b/core/src/main/java/edu/wpi/grip/core/events/SocketChangedEvent.java index 295e2ae08a..a7d38a7b17 100644 --- a/core/src/main/java/edu/wpi/grip/core/events/SocketChangedEvent.java +++ b/core/src/main/java/edu/wpi/grip/core/events/SocketChangedEvent.java @@ -19,12 +19,15 @@ public SocketChangedEvent(Socket socket) { this.socket = checkNotNull(socket, "Socket can not be null"); } - /** - * @return The socket that changed, with its new value. + * Queries the event to determine if this event is about this socket. + * + * @param socket The socket to check to see if it is related to. + * @return True if this socket is with regards to this event. */ - public Socket getSocket() { - return this.socket; + public boolean isRegarding(Socket socket) { + // This is necessary as some of the sockets are just decorators for other sockets. + return socket.equals(this.socket); } @Override @@ -38,7 +41,7 @@ public boolean pipelineShouldRun() { * If the connections are empty then the change must have come from the UI so we need to run the pipeline * with the new values. */ - return getSocket().getDirection().equals(Socket.Direction.INPUT) && getSocket().getConnections().isEmpty(); + return socket.getDirection().equals(Socket.Direction.INPUT) && socket.getConnections().isEmpty(); } @Override diff --git a/core/src/main/java/edu/wpi/grip/core/events/SocketPreviewChangedEvent.java b/core/src/main/java/edu/wpi/grip/core/events/SocketPreviewChangedEvent.java index 2ce165c858..c06428ef35 100644 --- a/core/src/main/java/edu/wpi/grip/core/events/SocketPreviewChangedEvent.java +++ b/core/src/main/java/edu/wpi/grip/core/events/SocketPreviewChangedEvent.java @@ -19,6 +19,17 @@ public SocketPreviewChangedEvent(OutputSocket socket) { this.socket = checkNotNull(socket, "Socket cannot be null"); } + /** + * Queries the event to determine if this event is about this socket. + * + * @param socket The socket to check to see if it is related to. + * @return True if this socket is with regards to this event. + */ + public boolean isRegarding(OutputSocket socket) { + // This is necessary as some of the sockets are just decorators for other sockets. + return socket.equals(this.socket); + } + /** * @return The socket being previewed */ diff --git a/core/src/main/java/edu/wpi/grip/core/operations/CVOperations.java b/core/src/main/java/edu/wpi/grip/core/operations/CVOperations.java new file mode 100644 index 0000000000..c727a6d848 --- /dev/null +++ b/core/src/main/java/edu/wpi/grip/core/operations/CVOperations.java @@ -0,0 +1,325 @@ +package edu.wpi.grip.core.operations; + + +import com.google.common.collect.ImmutableList; +import com.google.common.eventbus.EventBus; +import com.google.inject.Inject; +import edu.wpi.grip.core.OperationMetaData; +import edu.wpi.grip.core.events.OperationAddedEvent; +import edu.wpi.grip.core.operations.opencv.CVOperation; +import edu.wpi.grip.core.operations.opencv.enumeration.FlipCode; +import edu.wpi.grip.core.operations.templated.TemplateFactory; +import edu.wpi.grip.core.sockets.InputSocket; +import edu.wpi.grip.core.sockets.OutputSocket; +import edu.wpi.grip.core.sockets.SocketHint; +import edu.wpi.grip.core.sockets.SocketHints; +import edu.wpi.grip.generated.opencv_core.enumeration.BorderTypesEnum; +import edu.wpi.grip.generated.opencv_core.enumeration.CmpTypesEnum; +import edu.wpi.grip.generated.opencv_core.enumeration.LineTypesEnum; +import edu.wpi.grip.generated.opencv_imgproc.enumeration.*; +import org.bytedeco.javacpp.opencv_core; +import org.bytedeco.javacpp.opencv_core.Point; +import org.bytedeco.javacpp.opencv_core.Scalar; +import org.bytedeco.javacpp.opencv_core.Size; +import org.bytedeco.javacpp.opencv_imgproc; + +/** + * A list of all of the raw opencv operations + */ +@SuppressWarnings("PMD.AvoidDuplicateLiterals") +public class CVOperations { + private final EventBus eventBus; + private final ImmutableList coreOperations; + private final ImmutableList imgprocOperation; + + @Inject + CVOperations(EventBus eventBus, InputSocket.Factory isf, OutputSocket.Factory osf) { + this.eventBus = eventBus; + final TemplateFactory templateFactory = new TemplateFactory(isf, osf); + this.coreOperations = ImmutableList.of( + new OperationMetaData(CVOperation.defaults("CV absdiff", "Calculate the per-element absolute difference of two images."), + templateFactory.createAllMatTwoSource(opencv_core::absdiff)), + + new OperationMetaData(CVOperation.defaults("CV add", "Calculate the per-pixel sum of two images."), + templateFactory.createAllMatTwoSource(opencv_core::add)), + + new OperationMetaData(CVOperation.defaults("CV addWeighted", "Calculate the weighted sum of two images."), + templateFactory.create( + SocketHints.Inputs.createMatSocketHint("src1", false), + SocketHints.Inputs.createNumberSpinnerSocketHint("alpha", 0), + SocketHints.Inputs.createMatSocketHint("src2", false), + SocketHints.Inputs.createNumberSpinnerSocketHint("beta", 0), + SocketHints.Inputs.createNumberSpinnerSocketHint("gamma", 0), + SocketHints.Outputs.createMatSocketHint("dst"), + (src1, alpha, src2, beta, gamma, dst) -> { + opencv_core.addWeighted(src1, alpha.doubleValue(), src2, beta.doubleValue(), gamma.doubleValue(), dst); + } + )), + + new OperationMetaData(CVOperation.defaults("CV bitwise_and", "Calculate the per-element bitwise conjunction of two images."), + templateFactory.createAllMatTwoSource(opencv_core::bitwise_and)), + + new OperationMetaData(CVOperation.defaults("CV bitwise_not", "Calculate per-element bit-wise inversion of an image."), + templateFactory.createAllMatTwoSource(opencv_core::bitwise_not)), + + new OperationMetaData(CVOperation.defaults("CV bitwise_or", "Calculate the per-element bit-wise disjunction of two images."), + templateFactory.createAllMatTwoSource(opencv_core::bitwise_or)), + + new OperationMetaData(CVOperation.defaults("CV bitwise_xor", "Calculate the per-element bit-wise \"exclusive or\" on two images."), + templateFactory.createAllMatTwoSource(opencv_core::bitwise_xor)), + + new OperationMetaData(CVOperation.defaults("CV compare", "Compare each pixel in two images using a given rule."), + templateFactory.create( + SocketHints.Inputs.createMatSocketHint("src1", false), + SocketHints.Inputs.createMatSocketHint("src2", false), + SocketHints.createEnumSocketHint("cmpop", CmpTypesEnum.CMP_EQ), + SocketHints.Outputs.createMatSocketHint("dst"), + (src1, src2, cmp, dst) -> { + opencv_core.compare(src1, src2, dst, cmp.value); + } + )), + + new OperationMetaData(CVOperation.defaults("CV divide", "Perform per-pixel division of two images."), + templateFactory.create( + SocketHints.Inputs.createMatSocketHint("src1", false), + SocketHints.Inputs.createMatSocketHint("src2", false), + SocketHints.Inputs.createNumberSpinnerSocketHint("scale", 1.0, -Double.MAX_VALUE, Double.MAX_VALUE), + SocketHints.Outputs.createMatSocketHint("dst"), + (src1, src2, scale, dst) -> { + opencv_core.divide(src1, src2, dst, scale.doubleValue(), -1); + } + )), + + new OperationMetaData(CVOperation.defaults("CV extractChannel", "Extract a single channel from a image."), + templateFactory.create( + SocketHints.Inputs.createMatSocketHint("src", false), + SocketHints.Inputs.createNumberSpinnerSocketHint("channel", 0, 0, Integer.MAX_VALUE), + SocketHints.Outputs.createMatSocketHint("dst"), + (src1, coi, dst) -> { + opencv_core.extractChannel(src1, dst, coi.intValue()); + } + )), + + new OperationMetaData(CVOperation.defaults("CV flip", "Flip image around vertical, horizontal, or both axes."), + templateFactory.create( + SocketHints.Inputs.createMatSocketHint("src", false), + SocketHints.createEnumSocketHint("flipCode", FlipCode.Y_AXIS), + SocketHints.Outputs.createMatSocketHint("dst"), + (src, flipCode, dst) -> { + opencv_core.flip(src, dst, flipCode.value); + } + )), + + new OperationMetaData(CVOperation.defaults("CV max", "Calculate per-element maximum of two images."), + templateFactory.createAllMatTwoSource(opencv_core::max)), + + new OperationMetaData(CVOperation.defaults("CV min", "Calculate the per-element minimum of two images."), + templateFactory.createAllMatTwoSource(opencv_core::min)), + + new OperationMetaData(CVOperation.defaults("CV multiply", "Calculate the per-pixel scaled product of two images."), + templateFactory.create( + SocketHints.Inputs.createMatSocketHint("src1", false), + SocketHints.Inputs.createMatSocketHint("src2", false), + SocketHints.Inputs.createNumberSpinnerSocketHint("scale", 1.0, Integer.MIN_VALUE, Integer.MAX_VALUE), + SocketHints.Outputs.createMatSocketHint("dst"), + (src1, src2, scale, dst) -> { + opencv_core.multiply(src1, src2, dst, scale.doubleValue(), -1); + } + )), + + new OperationMetaData(CVOperation.defaults("CV scaleAdd", "Calculate the sum of two images where one image is multiplied by a scalar."), + templateFactory.create( + SocketHints.Inputs.createMatSocketHint("src1", false), + SocketHints.Inputs.createNumberSpinnerSocketHint("scale", 1.0), + SocketHints.Inputs.createMatSocketHint("src2", false), + SocketHints.Outputs.createMatSocketHint("dst"), + (src1, alpha, src2, dst) -> { + opencv_core.scaleAdd(src1, alpha.doubleValue(), src2, dst); + } + )), + + new OperationMetaData(CVOperation.defaults("CV subtract", "Calculate the per-pixel difference between two images."), + templateFactory.createAllMatTwoSource(opencv_core::subtract)), + + new OperationMetaData(CVOperation.defaults("CV transpose", "Calculate the transpose of an image."), + templateFactory.createAllMatOneSource(opencv_core::transpose)) + ); + + this.imgprocOperation = ImmutableList.of( + new OperationMetaData(CVOperation.defaults("CV adaptiveThreshold", "Transforms a grayscale image to a binary image)."), + templateFactory.create( + SocketHints.Inputs.createMatSocketHint("src", false), + SocketHints.Inputs.createNumberSpinnerSocketHint("maxValue", 0.0), + SocketHints.createEnumSocketHint("adaptiveMethod", AdaptiveThresholdTypesEnum.ADAPTIVE_THRESH_MEAN_C), + SocketHints.createEnumSocketHint("thresholdType", ThresholdTypesEnum.THRESH_BINARY), + SocketHints.Inputs.createNumberSpinnerSocketHint("blockSize", 0.0), + SocketHints.Inputs.createNumberSpinnerSocketHint("C", 0.0), + SocketHints.Outputs.createMatSocketHint("dst"), + (src, maxValue, adaptiveMethod, thresholdType, blockSize, C, dst) -> { + opencv_imgproc.adaptiveThreshold(src, dst, maxValue.doubleValue(), adaptiveMethod.value, thresholdType.value, blockSize.intValue(), C.doubleValue()); + } + )), + + new OperationMetaData(CVOperation.defaults("CV applyColorMap", "Apply a MATLAB equivalent colormap to an image."), + templateFactory.create( + SocketHints.Inputs.createMatSocketHint("src", false), + SocketHints.createEnumSocketHint("colormap", ColormapTypesEnum.COLORMAP_AUTUMN), + SocketHints.Outputs.createMatSocketHint("dst"), + (src, colormap, dst) -> { + opencv_imgproc.applyColorMap(src, dst, colormap.value); + } + )), + + new OperationMetaData(CVOperation.defaults("CV Canny", "Apply a \"canny edge detection\" algorithm to an image."), + templateFactory.create( + SocketHints.Inputs.createMatSocketHint("image", false), + SocketHints.Inputs.createNumberSpinnerSocketHint("threshold1", 0.0), + SocketHints.Inputs.createNumberSpinnerSocketHint("threshold2", 0.0), + SocketHints.Inputs.createNumberSpinnerSocketHint("apertureSize", 3), + SocketHints.Inputs.createCheckboxSocketHint("L2gradient", false), + SocketHints.Outputs.createMatSocketHint("edges"), + (image, threshold1, threshold2, apertureSize, L2gradient, edges) -> { + opencv_imgproc.Canny(image, edges, threshold1.doubleValue(), threshold2.doubleValue(), apertureSize.intValue(), L2gradient); + } + )), + + new OperationMetaData(CVOperation.defaults("CV cvtColor", "Convert an image from one color space to another."), + templateFactory.create( + SocketHints.Inputs.createMatSocketHint("src", false), + SocketHints.createEnumSocketHint("code", ColorConversionCodesEnum.COLOR_BGR2BGRA), + SocketHints.Outputs.createMatSocketHint("dst"), + (src, code, dst) -> { + opencv_imgproc.cvtColor(src, dst, code.value); + } + )), + + new OperationMetaData(CVOperation.defaults("CV dilate", "Expands areas of higher values in an image."), + templateFactory.create( + SocketHints.Inputs.createMatSocketHint("src", false), + SocketHints.Inputs.createMatSocketHint("kernel", true), + new SocketHint.Builder<>(Point.class).identifier("anchor").initialValueSupplier(() -> new Point(-1, -1)).build(), + SocketHints.Inputs.createNumberSpinnerSocketHint("iterations", 1), + SocketHints.createEnumSocketHint("borderType", BorderTypesEnum.BORDER_CONSTANT), + new SocketHint.Builder<>(Scalar.class).identifier("borderValue").initialValueSupplier(opencv_imgproc::morphologyDefaultBorderValue).build(), + SocketHints.Outputs.createMatSocketHint("dst"), + (src, kernel, anchor, iterations, borderType, borderValue, dst) -> { + opencv_imgproc.dilate(src, kernel, dst, anchor, iterations.intValue(), borderType.value, borderValue); + } + )), + + new OperationMetaData(CVOperation.defaults("CV erode", "Expands areas of lower values in an image."), + templateFactory.create( + SocketHints.Inputs.createMatSocketHint("src", false), + SocketHints.Inputs.createMatSocketHint("kernel", true), + new SocketHint.Builder<>(Point.class).identifier("anchor").initialValueSupplier(() -> new Point(-1, -1)).build(), + SocketHints.Inputs.createNumberSpinnerSocketHint("iterations", 1), + SocketHints.createEnumSocketHint("borderType", BorderTypesEnum.BORDER_CONSTANT), + new SocketHint.Builder<>(Scalar.class).identifier("borderValue").initialValueSupplier(opencv_imgproc::morphologyDefaultBorderValue).build(), + SocketHints.Outputs.createMatSocketHint("dst"), + (src, kernel, anchor, iterations, borderType, borderValue, dst) -> { + opencv_imgproc.erode(src, kernel, dst, anchor, iterations.intValue(), borderType.value, borderValue); + } + )), + + new OperationMetaData(CVOperation.defaults("CV GaussianBlur", "Apply a Gaussian blur to an image."), + templateFactory.create( + SocketHints.Inputs.createMatSocketHint("src", true), + new SocketHint.Builder<>(Size.class).identifier("ksize").initialValueSupplier(() -> new Size(1, 1)).build(), + SocketHints.Inputs.createNumberSpinnerSocketHint("sigmaX", 0.0), + SocketHints.Inputs.createNumberSpinnerSocketHint("sigmaY", 0.0), SocketHints.createEnumSocketHint("borderType", BorderTypesEnum.BORDER_DEFAULT), + SocketHints.Outputs.createMatSocketHint("dst"), + (src, ksize, sigmaX, sigmaY, borderType, dst) -> { + opencv_imgproc.GaussianBlur(src, dst, ksize, sigmaX.doubleValue(), sigmaY.doubleValue(), borderType.value); + } + )), + + new OperationMetaData(CVOperation.defaults("CV Laplacian", "Find edges by calculating the Laplacian for the given image."), + templateFactory.create( + SocketHints.Inputs.createMatSocketHint("src", false), + SocketHints.Inputs.createNumberSpinnerSocketHint("ksize", 1), + SocketHints.Inputs.createNumberSpinnerSocketHint("scale", 1.0), + SocketHints.Inputs.createNumberSpinnerSocketHint("delta", 0.0), + SocketHints.createEnumSocketHint("borderType", BorderTypesEnum.BORDER_DEFAULT), + SocketHints.Outputs.createMatSocketHint("dst"), + (src, ksize, scale, delta, borderType, dst) -> { + opencv_imgproc.Laplacian(src, dst, 0, ksize.intValue(), scale.doubleValue(), delta.doubleValue(), borderType.value); + } + )), + + new OperationMetaData(CVOperation.defaults("CV medianBlur", "Apply a Median blur to an image."), + templateFactory.create( + SocketHints.Inputs.createMatSocketHint("src", false), + SocketHints.Inputs.createNumberSpinnerSocketHint("ksize", 1, 1, Integer.MAX_VALUE), + SocketHints.Outputs.createMatSocketHint("dst"), + (src, ksize, dst) -> { + opencv_imgproc.medianBlur(src, dst, ksize.intValue()); + } + )), + + new OperationMetaData(CVOperation.defaults("CV rectangle", "Draw a rectangle (outline or filled) on an image."), + templateFactory.create( + SocketHints.Inputs.createMatSocketHint("src", false), + SocketHints.Inputs.createPointSocketHint("pt1", 0, 0), + SocketHints.Inputs.createPointSocketHint("pt2", 0, 0), + new SocketHint.Builder<>(Scalar.class).identifier("color").initialValueSupplier(() -> Scalar.BLACK).build(), + SocketHints.Inputs.createNumberSpinnerSocketHint("thickness", 0, Integer.MIN_VALUE, Integer.MAX_VALUE), + SocketHints.createEnumSocketHint("lineType", LineTypesEnum.LINE_8), + SocketHints.Inputs.createNumberSpinnerSocketHint("shift", 0), + SocketHints.Outputs.createMatSocketHint("dst"), + (src, pt1, pt2, color, thickness, lineType, shift, dst) -> { + // Rectangle only has one input and it modifies it so we have to copy the input image to the dst + src.copyTo(dst); + opencv_imgproc.rectangle(dst, pt1, pt2, color, thickness.intValue(), lineType.value, shift.intValue()); + } + )), + + new OperationMetaData(CVOperation.defaults("CV resize", "Resizes the image to the specified size."), + templateFactory.create( + SocketHints.Inputs.createMatSocketHint("src", false), + new SocketHint.Builder<>(Size.class).identifier("dsize").initialValueSupplier(() -> new Size(0, 0)).build(), + SocketHints.Inputs.createNumberSpinnerSocketHint("fx", .25), SocketHints.Inputs.createNumberSpinnerSocketHint("fx", .25), + SocketHints.createEnumSocketHint("interpolation", InterpolationFlagsEnum.INTER_LINEAR), + SocketHints.Outputs.createMatSocketHint("dst"), + (src, dsize, fx, fy, interpolation, dst) -> { + opencv_imgproc.resize(src, dst, dsize, fx.doubleValue(), fy.doubleValue(), interpolation.value); + } + )), + + new OperationMetaData(CVOperation.defaults("CV Sobel", "Find edges by calculating the requested derivative order for the given image."), + templateFactory.create( + SocketHints.Inputs.createMatSocketHint("src", false), + SocketHints.Inputs.createNumberSpinnerSocketHint("dx", 0), + SocketHints.Inputs.createNumberSpinnerSocketHint("dy", 0), + SocketHints.Inputs.createNumberSpinnerSocketHint("ksize", 3), + SocketHints.Inputs.createNumberSpinnerSocketHint("scale", 1), + SocketHints.Inputs.createNumberSpinnerSocketHint("delta", 0), + SocketHints.createEnumSocketHint("borderType", BorderTypesEnum.BORDER_DEFAULT), + SocketHints.Outputs.createMatSocketHint("dst"), + (src, dx, dy, ksize, scale, delta, borderType, dst) -> { + opencv_imgproc.Sobel(src, dst, 0, dx.intValue(), dy.intValue(), ksize.intValue(), scale.doubleValue(), delta.doubleValue(), borderType.value); + } + )), + + new OperationMetaData(CVOperation.defaults("CV Threshold", "Apply a fixed-level threshold to each array element in an image."), + templateFactory.create( + SocketHints.Inputs.createMatSocketHint("src", false), + SocketHints.Inputs.createNumberSpinnerSocketHint("thresh", 0), + SocketHints.Inputs.createNumberSpinnerSocketHint("maxval", 0), + SocketHints.createEnumSocketHint("type", ThresholdTypesEnum.THRESH_BINARY), + SocketHints.Outputs.createMatSocketHint("dst"), + (src, thresh, maxval, type, dst) -> { + opencv_imgproc.threshold(src, dst, thresh.doubleValue(), maxval.doubleValue(), type.value); + } + )) + ); + } + + public void addOperations() { + coreOperations.stream() + .map(OperationAddedEvent::new) + .forEach(eventBus::post); + imgprocOperation.stream() + .map(OperationAddedEvent::new) + .forEach(eventBus::post); + } +} diff --git a/core/src/main/java/edu/wpi/grip/core/operations/Operations.java b/core/src/main/java/edu/wpi/grip/core/operations/Operations.java index 859bd2d58f..ed009f9cef 100644 --- a/core/src/main/java/edu/wpi/grip/core/operations/Operations.java +++ b/core/src/main/java/edu/wpi/grip/core/operations/Operations.java @@ -5,7 +5,7 @@ import com.google.inject.Inject; import com.google.inject.Singleton; import com.google.inject.name.Named; -import edu.wpi.grip.core.Operation; +import edu.wpi.grip.core.OperationMetaData; import edu.wpi.grip.core.events.OperationAddedEvent; import edu.wpi.grip.core.operations.composite.*; import edu.wpi.grip.core.operations.network.BooleanPublishable; @@ -20,82 +20,90 @@ import edu.wpi.grip.core.operations.opencv.MinMaxLoc; import edu.wpi.grip.core.operations.opencv.NewPointOperation; import edu.wpi.grip.core.operations.opencv.NewSizeOperation; - -import java.util.function.Supplier; +import edu.wpi.grip.core.sockets.InputSocket; +import edu.wpi.grip.core.sockets.OutputSocket; +import org.bytedeco.javacpp.opencv_core.Point; +import org.bytedeco.javacpp.opencv_core.Size; import static com.google.common.base.Preconditions.checkNotNull; -import static org.bytedeco.javacpp.opencv_core.Point; -import static org.bytedeco.javacpp.opencv_core.Size; @Singleton public class Operations { + private final EventBus eventBus; - private final ImmutableList> operations; + + private final ImmutableList operations; @Inject - Operations(EventBus eventBus, @Named("ntManager") MapNetworkPublisherFactory ntPublisherFactory, @Named("rosManager") ROSNetworkPublisherFactory rosPublishFactory) { + Operations(EventBus eventBus, + @Named("ntManager") MapNetworkPublisherFactory ntPublisherFactory, + @Named("rosManager") ROSNetworkPublisherFactory rosPublishFactory, + InputSocket.Factory isf, + OutputSocket.Factory osf) { this.eventBus = checkNotNull(eventBus, "EventBus cannot be null"); checkNotNull(ntPublisherFactory, "ntPublisherFactory cannot be null"); checkNotNull(rosPublishFactory, "rosPublishFactory cannot be null"); this.operations = ImmutableList.of( - ResizeOperation::new, - BlurOperation::new, - DesaturateOperation::new, - RGBThresholdOperation::new, - HSVThresholdOperation::new, - HSLThresholdOperation::new, - FindContoursOperation::new, - FilterContoursOperation::new, - ConvexHullsOperation::new, - FindBlobsOperation::new, - FindLinesOperation::new, - FilterLinesOperation::new, - MaskOperation::new, - MinMaxLoc::new, - MatFieldAccessor::new, - NewPointOperation::new, - NewSizeOperation::new, - () -> new NTPublishAnnotatedOperation(ntPublisherFactory, NumberPublishable::new) { - }, - () -> new NTPublishAnnotatedOperation(ntPublisherFactory, BooleanPublishable::new) { - }, - () -> new NTPublishAnnotatedOperation(ntPublisherFactory, Vector2D::new) { - }, - () -> new NTPublishAnnotatedOperation(ntPublisherFactory, Vector2D::new) { - }, - () -> new NTPublishAnnotatedOperation(ntPublisherFactory) { - }, - () -> new NTPublishAnnotatedOperation(ntPublisherFactory) { - }, - () -> new NTPublishAnnotatedOperation(ntPublisherFactory) { - }, - () -> new ROSPublishOperation(rosPublishFactory, JavaToMessageConverter.FLOAT) { - }, - () -> new ROSPublishOperation(rosPublishFactory, JavaToMessageConverter.BOOL) { - }, -// () -> new ROSPublishOperation(rosManager, Vector2D::new) { -// }, -// () -> new ROSPublishOperation(rosManager, Vector2D::new) { -// }, - () -> new ROSPublishOperation(rosPublishFactory, JavaToMessageConverter.CONTOURS) { - }, - () -> new ROSPublishOperation(rosPublishFactory, JavaToMessageConverter.BLOBS) { - }, - () -> new ROSPublishOperation(rosPublishFactory, JavaToMessageConverter.LINES) { - }, - PublishVideoOperation::new, - DistanceTransformOperation::new, - NormalizeOperation::new, - WatershedOperation::new, - SwitchOperation::new, - ValveOperation::new, - ThresholdMoving::new + // Composite operations + new OperationMetaData(BlurOperation.DESCRIPTION, () -> new BlurOperation(isf, osf)), + new OperationMetaData(ConvexHullsOperation.DESCRIPTION, () -> new ConvexHullsOperation(isf, osf)), + new OperationMetaData(DesaturateOperation.DESCRIPTION, () -> new DesaturateOperation(isf, osf)), + new OperationMetaData(DistanceTransformOperation.DESCRIPTION, () -> new DistanceTransformOperation(isf, osf)), + new OperationMetaData(FilterContoursOperation.DESCRIPTION, () -> new FilterContoursOperation(isf, osf)), + new OperationMetaData(FilterLinesOperation.DESCRIPTION, () -> new FilterLinesOperation(isf, osf)), + new OperationMetaData(FindBlobsOperation.DESCRIPTION, () -> new FindBlobsOperation(isf, osf)), + new OperationMetaData(FindContoursOperation.DESCRIPTION, () -> new FindContoursOperation(isf, osf)), + new OperationMetaData(FindLinesOperation.DESCRIPTION, () -> new FindLinesOperation(isf, osf)), + new OperationMetaData(HSLThresholdOperation.DESCRIPTION, () -> new HSLThresholdOperation(isf, osf)), + new OperationMetaData(HSVThresholdOperation.DESCRIPTION, () -> new HSVThresholdOperation(isf, osf)), + new OperationMetaData(MaskOperation.DESCRIPTION, () -> new MaskOperation(isf, osf)), + new OperationMetaData(NormalizeOperation.DESCRIPTION, () -> new NormalizeOperation(isf, osf)), + new OperationMetaData(PublishVideoOperation.DESCRIPTION, () -> new PublishVideoOperation(isf)), + new OperationMetaData(ResizeOperation.DESCRIPTION, () -> new ResizeOperation(isf, osf)), + new OperationMetaData(RGBThresholdOperation.DESCRIPTION, () -> new RGBThresholdOperation(isf, osf)), + new OperationMetaData(SwitchOperation.DESCRIPTION, () -> new SwitchOperation(isf, osf)), + new OperationMetaData(ValveOperation.DESCRIPTION, () -> new ValveOperation(isf, osf)), + new OperationMetaData(WatershedOperation.DESCRIPTION, () -> new WatershedOperation(isf, osf)), + new OperationMetaData(ThresholdMoving.DESCRIPTION, () -> new ThresholdMoving(isf, osf)), + + // OpenCV operations + new OperationMetaData(MatFieldAccessor.DESCRIPTION, () -> new MatFieldAccessor(isf, osf)), + new OperationMetaData(MinMaxLoc.DESCRIPTION, () -> new MinMaxLoc(isf, osf)), + new OperationMetaData(NewPointOperation.DESCRIPTION, () -> new NewPointOperation(isf, osf)), + new OperationMetaData(NewSizeOperation.DESCRIPTION, () -> new NewSizeOperation(isf, osf)), + + // NetworkTables publishing operations + new OperationMetaData(NTPublishAnnotatedOperation.descriptionFor(ContoursReport.class), + () -> new NTPublishAnnotatedOperation<>(isf, ContoursReport.class, ntPublisherFactory)), + new OperationMetaData(NTPublishAnnotatedOperation.descriptionFor(LinesReport.class), + () -> new NTPublishAnnotatedOperation<>(isf, LinesReport.class, ntPublisherFactory)), + new OperationMetaData(NTPublishAnnotatedOperation.descriptionFor(BlobsReport.class), + () -> new NTPublishAnnotatedOperation<>(isf, BlobsReport.class, ntPublisherFactory)), + new OperationMetaData(NTPublishAnnotatedOperation.descriptionFor(Size.class), + () -> new NTPublishAnnotatedOperation<>(isf, Size.class, Vector2D.class, Vector2D::new, ntPublisherFactory)), + new OperationMetaData(NTPublishAnnotatedOperation.descriptionFor(Point.class), + () -> new NTPublishAnnotatedOperation<>(isf, Point.class, Vector2D.class, Vector2D::new, ntPublisherFactory)), + new OperationMetaData(NTPublishAnnotatedOperation.descriptionFor(Number.class), + () -> new NTPublishAnnotatedOperation<>(isf, Number.class, NumberPublishable.class, NumberPublishable::new, ntPublisherFactory)), + new OperationMetaData(NTPublishAnnotatedOperation.descriptionFor(Boolean.class), + () -> new NTPublishAnnotatedOperation<>(isf, Boolean.class, BooleanPublishable.class, BooleanPublishable::new, ntPublisherFactory)), + + // ROS publishing operations + new OperationMetaData(ROSPublishOperation.descriptionFor(Number.class), + () -> new ROSPublishOperation<>(isf, Number.class, rosPublishFactory, JavaToMessageConverter.FLOAT)), + new OperationMetaData(ROSPublishOperation.descriptionFor(Boolean.class), + () -> new ROSPublishOperation<>(isf, Boolean.class, rosPublishFactory, JavaToMessageConverter.BOOL)), + new OperationMetaData(ROSPublishOperation.descriptionFor(ContoursReport.class), + () -> new ROSPublishOperation<>(isf, ContoursReport.class, rosPublishFactory, JavaToMessageConverter.CONTOURS)), + new OperationMetaData(ROSPublishOperation.descriptionFor(BlobsReport.class), + () -> new ROSPublishOperation<>(isf, BlobsReport.class, rosPublishFactory, JavaToMessageConverter.BLOBS)), + new OperationMetaData(ROSPublishOperation.descriptionFor(LinesReport.class), + () -> new ROSPublishOperation<>(isf, LinesReport.class, rosPublishFactory, JavaToMessageConverter.LINES)) ); } public void addOperations() { operations.stream() - .map(s -> s.get()) .map(OperationAddedEvent::new) .forEach(eventBus::post); } diff --git a/core/src/main/java/edu/wpi/grip/core/operations/PythonScriptFile.java b/core/src/main/java/edu/wpi/grip/core/operations/PythonScriptFile.java new file mode 100644 index 0000000000..b3c959d298 --- /dev/null +++ b/core/src/main/java/edu/wpi/grip/core/operations/PythonScriptFile.java @@ -0,0 +1,81 @@ +package edu.wpi.grip.core.operations; + + +import com.google.auto.value.AutoValue; +import edu.wpi.grip.core.OperationMetaData; +import edu.wpi.grip.core.sockets.InputSocket; +import edu.wpi.grip.core.sockets.OutputSocket; +import edu.wpi.grip.core.sockets.SocketHint; +import org.python.core.PyFunction; +import org.python.core.PyObject; +import org.python.core.PyString; +import org.python.core.PySystemState; +import org.python.util.PythonInterpreter; + +import java.io.IOException; +import java.net.URL; +import java.util.List; +import java.util.Properties; + +/** + * Converts a string of Python Code or a Python File into something the {@link PythonScriptOperation} can handle + */ +@AutoValue +public abstract class PythonScriptFile { + + static { + Properties pythonProperties = new Properties(); + pythonProperties.setProperty("python.import.site", "false"); + PySystemState.initialize(pythonProperties, null); + } + + public static PythonScriptFile create(URL url) throws IOException { + final String path = url.getPath(); + final String alternativeName = path.substring(1 + Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\'))); + final PythonInterpreter interpreter = new PythonInterpreter(); + interpreter.execfile(url.openStream()); + return create(interpreter, alternativeName); + } + + public static PythonScriptFile create(String code) { + final PythonInterpreter interpreter = new PythonInterpreter(); + interpreter.exec(code); + return create(interpreter, null); + } + + @SuppressWarnings("unchecked") + private static PythonScriptFile create(PythonInterpreter interpreter, String alternativeName) { + final PyString name = interpreter.get("name", PyString.class); + final PyString summary = interpreter.get("summary", PyString.class); + final List> inputSocketHints = interpreter.get("inputs", List.class); + final List> outputSocketHints = interpreter.get("outputs", List.class); + final PyFunction performFunction = interpreter.get("perform", PyFunction.class); + return new AutoValue_PythonScriptFile( + name == null ? alternativeName : name.toString(), + summary == null ? "" : summary.toString(), + inputSocketHints, + outputSocketHints, + performFunction); + } + + public abstract String name(); + + public abstract String summary(); + + public abstract List> inputSocketHints(); + + public abstract List> outputSocketHints(); + + public abstract PyFunction performFunction(); + + /** + * Converts this file into a {@link PythonScriptOperation} + * + * @param isf Input Socket Factory + * @param osf Output Socket Factory + * @return The meta data for a {@link PythonScriptOperation} + */ + public final OperationMetaData toOperationMetaData(InputSocket.Factory isf, OutputSocket.Factory osf) { + return new OperationMetaData(PythonScriptOperation.descriptionFor(this), () -> new PythonScriptOperation(isf, osf, this)); + } +} diff --git a/core/src/main/java/edu/wpi/grip/core/operations/PythonScriptOperation.java b/core/src/main/java/edu/wpi/grip/core/operations/PythonScriptOperation.java index 12b3188122..05667c01e3 100644 --- a/core/src/main/java/edu/wpi/grip/core/operations/PythonScriptOperation.java +++ b/core/src/main/java/edu/wpi/grip/core/operations/PythonScriptOperation.java @@ -1,28 +1,28 @@ package edu.wpi.grip.core.operations; -import com.google.common.eventbus.EventBus; -import edu.wpi.grip.core.*; +import com.google.common.collect.ImmutableList; +import edu.wpi.grip.core.Operation; +import edu.wpi.grip.core.OperationDescription; import edu.wpi.grip.core.sockets.InputSocket; import edu.wpi.grip.core.sockets.OutputSocket; -import edu.wpi.grip.core.sockets.SocketHint; -import org.python.core.*; -import org.python.util.PythonInterpreter; - -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.util.Optional; +import edu.wpi.grip.core.util.Icon; +import org.python.core.Py; +import org.python.core.PyObject; +import org.python.core.PySequence; + import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; -import java.util.Properties; +import java.util.stream.Collectors; + +import static com.google.common.base.Preconditions.checkNotNull; /** * A class that implements an operation using Jython. This enables users to write plugins for the application as * Python scripts. Python script plugins should have global lists of SocketHints called "inputs" and "outputs" that * declare what parameters the script accepts and what outputs in produces. For example, - * + *

*

{@code
  *    import edu.wpi.grip.core as grip
  *    import java.lang.Integer
@@ -36,137 +36,68 @@
  *        grip.SocketHint("c", java.lang.Integer),
  *    ]
  * }
- * + *

* The script should also define a function "perform", which takes the same number of parameters as there are inputs * and returns the values for the outputs. It can return a single value if there's one output, or a sequence type for * any number of values. - * + *

*

{@code
  * def perform(a, b):
  * return a + b
  * }
- * - * Lastly, the script can optionally have global "name" and "description" strings to provide the user with more + *

+ * Lastly, the script can optionally have global "name" and "summary" strings to provide the user with more * information about what the operation does. */ public class PythonScriptOperation implements Operation { - - static { - Properties pythonProperties = new Properties(); - pythonProperties.setProperty("python.import.site", "false"); - PySystemState.initialize(pythonProperties, null); - } - private static final String DEFAULT_NAME = "Python Operation"; - private static final String DEFAULT_DESCRIPTION = ""; - private static final Logger logger = Logger.getLogger(PythonScriptOperation.class.getName()); - - - // Either a URL or a String of literal source code is stored in this field. This allows a PythonScriptOperation to - // be serialized as a reference to some code rather than trying to save a bunch of Jython internal structures to a - // file, which is what would automatically happen otherwise. - private final Optional sourceURL; - private final Optional sourceCode; - - private final PythonInterpreter interpreter = new PythonInterpreter(); - - private List> inputSocketHints; - private List> outputSocketHints; - private PyFunction performFunction; - private PyString name; - private PyString description; - - public PythonScriptOperation(URL url) throws PyException, IOException { - this.sourceURL = Optional.of(url); - this.sourceCode = Optional.empty(); - this.interpreter.execfile(url.openStream()); - this.getPythonVariables(); - - if (this.name == null) { - // If a name of the operation wasn't specified in the script, use the basename of the URL - final String path = url.getPath(); - this.name = new PyString(path.substring(1 + Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\")))); - } + private static final Logger logger = Logger.getLogger(PythonScriptOperation.class.getName()); - if (this.description == null) { - this.description = new PyString(DEFAULT_DESCRIPTION); - } - } - public PythonScriptOperation(String code) throws PyException { - this.sourceURL = Optional.empty(); - this.sourceCode = Optional.of(code); - this.interpreter.exec(code); - this.getPythonVariables(); + private final PythonScriptFile scriptFile; + private List inputSockets; // intentionally using raw types + private List outputSockets; // intentionally using raw types - if (this.name == null) { - this.name = new PyString(DEFAULT_NAME); - } - - if (this.description == null) { - this.description = new PyString(DEFAULT_DESCRIPTION); - } + public static OperationDescription descriptionFor(PythonScriptFile pythonScriptFile) { + return OperationDescription.builder() + .name(pythonScriptFile.name()) + .summary(pythonScriptFile.summary()) + .icon(Icon.iconStream("python")) + .category(OperationDescription.Category.MISCELLANEOUS) + .build(); } - private void getPythonVariables() throws PyException { - this.inputSocketHints = this.interpreter.get("inputs", List.class); - this.outputSocketHints = this.interpreter.get("outputs", List.class); - this.performFunction = this.interpreter.get("perform", PyFunction.class); - this.name = this.interpreter.get("name", PyString.class); - this.description = this.interpreter.get("description", PyString.class); - } - public Optional getSourceURL() { - return this.sourceURL; - } + public PythonScriptOperation(InputSocket.Factory isf, OutputSocket.Factory osf, PythonScriptFile scriptFile) { + checkNotNull(isf); + checkNotNull(osf); - public Optional getSourceCode() { - return this.sourceCode; - } + this.scriptFile = checkNotNull(scriptFile); - @Override - public String getName() { - return this.name.getString(); - } + this.inputSockets = scriptFile.inputSocketHints().stream() + .map(isf::create) + .collect(Collectors.toList()); - @Override - public String getDescription() { - return this.description.getString(); + this.outputSockets = scriptFile.outputSocketHints().stream() + .map(osf::create) + .collect(Collectors.toList()); } - @Override - public Optional getIcon() { - return Optional.of(getClass().getResourceAsStream("/edu/wpi/grip/ui/icons/python.png")); - } /** - * @param eventBus The Guava {@link EventBus} used by the application. * @return An array of Sockets, based on the global "inputs" list in the Python script */ @Override - public InputSocket[] createInputSockets(EventBus eventBus) { - InputSocket[] sockets = new InputSocket[this.inputSocketHints.size()]; - - for (int i = 0; i < sockets.length; i++) { - sockets[i] = new InputSocket<>(eventBus, this.inputSocketHints.get(i)); - } - - return sockets; + public List getInputSockets() { + return ImmutableList.copyOf(inputSockets); } /** - * @param eventBus The Guava {@link EventBus} used by the application. * @return An array of Sockets, based on the global "outputs" list in the Python script */ @Override - public OutputSocket[] createOutputSockets(EventBus eventBus) { - OutputSocket[] sockets = new OutputSocket[this.outputSocketHints.size()]; - - for (int i = 0; i < sockets.length; i++) { - sockets[i] = new OutputSocket<>(eventBus, this.outputSocketHints.get(i)); - } - - return sockets; + public List getOutputSockets() { + return ImmutableList.copyOf(outputSockets); } /** @@ -178,20 +109,16 @@ public OutputSocket[] createOutputSockets(EventBus eventBus) { * The Python function should return a tuple, list, or other sequence containing the outputs. If there is only * one output, it can just return a value. Either way, the number of inputs and outputs should match up with the * number of parameters and return values of the function. - * - * @param inputs An array obtained from {@link #createInputSockets(EventBus)}. The caller can set the value of - * each socket to an actual parameter for the operation. - * @param outputs An array obtained from {@link #createOutputSockets(EventBus)}. The outputs of the operation will */ @Override - public void perform(InputSocket[] inputs, OutputSocket[] outputs) { - PyObject[] pyInputs = new PyObject[inputs.length]; - for (int i = 0; i < inputs.length; i++) { - pyInputs[i] = Py.java2py(inputs[i].getValue().get()); + public void perform() { + PyObject[] pyInputs = new PyObject[inputSockets.size()]; + for (int i = 0; i < inputSockets.size(); i++) { + pyInputs[i] = Py.java2py(inputSockets.get(i).getValue().get()); } try { - PyObject pyOutput = this.performFunction.__call__(pyInputs); + PyObject pyOutput = this.scriptFile.performFunction().__call__(pyInputs); if (pyOutput.isSequenceType()) { /* If the Python function returned a sequence type, there must be multiple outputs for this step. @@ -199,23 +126,23 @@ public void perform(InputSocket[] inputs, OutputSocket[] outputs) { PySequence pySequence = (PySequence) pyOutput; Object[] javaOutputs = Py.tojava(pySequence, Object[].class); - if (outputs.length != javaOutputs.length) { - throw new RuntimeException(wrongNumberOfArgumentsMsg(outputs.length, javaOutputs.length)); + if (outputSockets.size() != javaOutputs.length) { + throw new IllegalArgumentException(wrongNumberOfArgumentsMsg(outputSockets.size(), javaOutputs.length)); } for (int i = 0; i < javaOutputs.length; i++) { - outputs[i].setValue(javaOutputs[i]); + outputSockets.get(i).setValue(javaOutputs[i]); } } else { /* If the Python script did not return a sequence, there should only be one output socket. */ - if (outputs.length != 1) { - throw new RuntimeException(wrongNumberOfArgumentsMsg(outputs.length, 1)); + if (outputSockets.size() != 1) { + throw new IllegalArgumentException(wrongNumberOfArgumentsMsg(outputSockets.size(), 1)); } - Object javaOutput = Py.tojava(pyOutput, outputs[0].getSocketHint().getType()); - outputs[0].setValue(javaOutput); + Object javaOutput = Py.tojava(pyOutput, outputSockets.get(0).getSocketHint().getType()); + outputSockets.get(0).setValue(javaOutput); } - } catch (Exception e) { + } catch (RuntimeException e) { /* Exceptions can happen if there's a mistake in a Python script, so just print a stack trace and leave the * current state of the output sockets alone. * diff --git a/core/src/main/java/edu/wpi/grip/core/operations/composite/BlobsReport.java b/core/src/main/java/edu/wpi/grip/core/operations/composite/BlobsReport.java index 3d3ab22ada..fc44ec3824 100644 --- a/core/src/main/java/edu/wpi/grip/core/operations/composite/BlobsReport.java +++ b/core/src/main/java/edu/wpi/grip/core/operations/composite/BlobsReport.java @@ -1,9 +1,9 @@ package edu.wpi.grip.core.operations.composite; import com.google.common.base.MoreObjects; -import edu.wpi.grip.core.sockets.NoSocketTypeLabel; import edu.wpi.grip.core.operations.network.PublishValue; import edu.wpi.grip.core.operations.network.Publishable; +import edu.wpi.grip.core.sockets.NoSocketTypeLabel; import java.util.Collections; import java.util.List; diff --git a/core/src/main/java/edu/wpi/grip/core/operations/composite/BlurOperation.java b/core/src/main/java/edu/wpi/grip/core/operations/composite/BlurOperation.java index f11081bfc5..da8049f5b2 100644 --- a/core/src/main/java/edu/wpi/grip/core/operations/composite/BlurOperation.java +++ b/core/src/main/java/edu/wpi/grip/core/operations/composite/BlurOperation.java @@ -1,14 +1,15 @@ package edu.wpi.grip.core.operations.composite; -import com.google.common.eventbus.EventBus; -import edu.wpi.grip.core.*; +import com.google.common.collect.ImmutableList; +import edu.wpi.grip.core.Operation; +import edu.wpi.grip.core.OperationDescription; import edu.wpi.grip.core.sockets.InputSocket; import edu.wpi.grip.core.sockets.OutputSocket; import edu.wpi.grip.core.sockets.SocketHint; import edu.wpi.grip.core.sockets.SocketHints; +import edu.wpi.grip.core.util.Icon; -import java.io.InputStream; -import java.util.Optional; +import java.util.List; import static org.bytedeco.javacpp.opencv_core.Mat; import static org.bytedeco.javacpp.opencv_core.Size; @@ -19,6 +20,17 @@ */ public class BlurOperation implements Operation { + /** + * Describes this operation. This is used by the 'Operations' class to add operations to GRIP. + */ + public static final OperationDescription DESCRIPTION = + OperationDescription.builder() + .name("Blur") + .summary("Blurs an image to remove noise") + .category(OperationDescription.Category.IMAGE_PROCESSING) + .icon(Icon.iconStream("blur")) + .build(); + private enum Type { BOX("Box Blur"), GAUSSIAN("Gaussian Blur"), MEDIAN("Median Filter"), BILATERAL_FILTER("Bilateral Filter"); @@ -40,50 +52,42 @@ public String toString() { private final SocketHint outputHint = SocketHints.Inputs.createMatSocketHint("Output", true); - @Override - public String getName() { - return "Blur"; - } + private final InputSocket inputSocket; + private final InputSocket typeSocket; + private final InputSocket radiusSocket; - @Override - public String getDescription() { - return "Soften the details of an image to remove noise."; - } + private final OutputSocket outputSocket; - @Override - public Category getCategory() { - return Category.IMAGE_PROCESSING; - } + public BlurOperation(InputSocket.Factory inputSocketFactory, OutputSocket.Factory outputSocketFactory) { + this.inputSocket = inputSocketFactory.create(inputHint); + this.typeSocket = inputSocketFactory.create(typeHint); + this.radiusSocket = inputSocketFactory.create(radiusHint); - @Override - public Optional getIcon() { - return Optional.of(getClass().getResourceAsStream("/edu/wpi/grip/ui/icons/blur.png")); + this.outputSocket = outputSocketFactory.create(outputHint); } @Override - public InputSocket[] createInputSockets(EventBus eventBus) { - return new InputSocket[]{ - new InputSocket<>(eventBus, inputHint), - new InputSocket<>(eventBus, typeHint), - new InputSocket<>(eventBus, radiusHint), - }; + public List getInputSockets() { + return ImmutableList.of( + inputSocket, + typeSocket, + radiusSocket + ); } @Override - public OutputSocket[] createOutputSockets(EventBus eventBus) { - return new OutputSocket[]{ - new OutputSocket<>(eventBus, outputHint) - }; + public List getOutputSockets() { + return ImmutableList.of( + outputSocket + ); } @Override - @SuppressWarnings("unchecked") - public void perform(InputSocket[] inputs, OutputSocket[] outputs) { - final Mat input = ((InputSocket) inputs[0]).getValue().get(); - final Type type = ((InputSocket) inputs[1]).getValue().get(); - final Number radius = ((InputSocket) inputs[2]).getValue().get(); + public void perform() { + final Mat input = inputSocket.getValue().get(); + final Type type = typeSocket.getValue().get(); + final Number radius = radiusSocket.getValue().get(); - final OutputSocket outputSocket = (OutputSocket) outputs[0]; final Mat output = outputSocket.getValue().get(); int kernelSize; diff --git a/core/src/main/java/edu/wpi/grip/core/operations/composite/ConvexHullsOperation.java b/core/src/main/java/edu/wpi/grip/core/operations/composite/ConvexHullsOperation.java index c3cb088bc8..893ceae62e 100644 --- a/core/src/main/java/edu/wpi/grip/core/operations/composite/ConvexHullsOperation.java +++ b/core/src/main/java/edu/wpi/grip/core/operations/composite/ConvexHullsOperation.java @@ -1,13 +1,13 @@ package edu.wpi.grip.core.operations.composite; -import com.google.common.eventbus.EventBus; -import edu.wpi.grip.core.sockets.InputSocket; +import com.google.common.collect.ImmutableList; import edu.wpi.grip.core.Operation; +import edu.wpi.grip.core.OperationDescription; +import edu.wpi.grip.core.sockets.InputSocket; import edu.wpi.grip.core.sockets.OutputSocket; import edu.wpi.grip.core.sockets.SocketHint; -import java.io.InputStream; -import java.util.Optional; +import java.util.List; import static org.bytedeco.javacpp.opencv_core.MatVector; import static org.bytedeco.javacpp.opencv_imgproc.convexHull; @@ -18,44 +18,43 @@ * This can help remove holes in detected shapes, making them easier to analyze. */ public class ConvexHullsOperation implements Operation { + + public static final OperationDescription DESCRIPTION = + OperationDescription.builder() + .name("Convex Hulls") + .summary("Compute the convex hulls of contours") + .category(OperationDescription.Category.FEATURE_DETECTION) + .build(); + private final SocketHint contoursHint = new SocketHint.Builder<>(ContoursReport.class) .identifier("Contours").initialValueSupplier(ContoursReport::new).build(); - @Override - public String getName() { - return "Convex Hulls"; - } + private final InputSocket inputSocket; + private final OutputSocket outputSocket; - @Override - public String getDescription() { - return "Compute the convex hulls of contours."; - } - - @Override - public Category getCategory() { - return Category.FEATURE_DETECTION; - } + public ConvexHullsOperation(InputSocket.Factory inputSocketFactory, OutputSocket.Factory outputSocketFactory) { + this.inputSocket = inputSocketFactory.create(contoursHint); - @Override - public Optional getIcon() { - return Optional.of(getClass().getResourceAsStream("/edu/wpi/grip/ui/icons/convex-hulls.png")); + this.outputSocket = outputSocketFactory.create(contoursHint); } @Override - public InputSocket[] createInputSockets(EventBus eventBus) { - return new InputSocket[]{new InputSocket<>(eventBus, contoursHint)}; + public List getInputSockets() { + return ImmutableList.of( + inputSocket + ); } @Override - public OutputSocket[] createOutputSockets(EventBus eventBus) { - return new OutputSocket[]{new OutputSocket<>(eventBus, contoursHint)}; + public List getOutputSockets() { + return ImmutableList.of( + outputSocket + ); } @Override @SuppressWarnings("unchecked") - public void perform(InputSocket[] inputs, OutputSocket[] outputs) { - final InputSocket inputSocket = (InputSocket) inputs[0]; - + public void perform() { final MatVector inputContours = inputSocket.getValue().get().getContours(); final MatVector outputContours = new MatVector(inputContours.size()); @@ -63,7 +62,6 @@ public void perform(InputSocket[] inputs, OutputSocket[] outputs) { convexHull(inputContours.get(i), outputContours.get(i)); } - final OutputSocket outputSocket = (OutputSocket) outputs[0]; outputSocket.setValue(new ContoursReport(outputContours, inputSocket.getValue().get().getRows(), inputSocket.getValue().get().getCols())); } diff --git a/core/src/main/java/edu/wpi/grip/core/operations/composite/DesaturateOperation.java b/core/src/main/java/edu/wpi/grip/core/operations/composite/DesaturateOperation.java index 95cd825f41..dcad1ef8f7 100644 --- a/core/src/main/java/edu/wpi/grip/core/operations/composite/DesaturateOperation.java +++ b/core/src/main/java/edu/wpi/grip/core/operations/composite/DesaturateOperation.java @@ -1,62 +1,65 @@ package edu.wpi.grip.core.operations.composite; -import com.google.common.eventbus.EventBus; -import edu.wpi.grip.core.*; +import com.google.common.collect.ImmutableList; + +import edu.wpi.grip.core.Operation; +import edu.wpi.grip.core.OperationDescription; import edu.wpi.grip.core.sockets.InputSocket; import edu.wpi.grip.core.sockets.OutputSocket; import edu.wpi.grip.core.sockets.SocketHint; import edu.wpi.grip.core.sockets.SocketHints; +import edu.wpi.grip.core.util.Icon; -import java.io.InputStream; -import java.util.Optional; +import java.util.List; import static org.bytedeco.javacpp.opencv_core.Mat; -import static org.bytedeco.javacpp.opencv_imgproc.*; +import static org.bytedeco.javacpp.opencv_imgproc.COLOR_BGR2GRAY; +import static org.bytedeco.javacpp.opencv_imgproc.COLOR_BGRA2GRAY; +import static org.bytedeco.javacpp.opencv_imgproc.cvtColor; /** * An {@link Operation} that converts a color image into shades of gray */ public class DesaturateOperation implements Operation { + public static final OperationDescription DESCRIPTION = + OperationDescription.builder() + .name("Desaturate") + .summary("Convert a color image into shades of gray.") + .category(OperationDescription.Category.IMAGE_PROCESSING) + .icon(Icon.iconStream("desaturate")) + .build(); + private final SocketHint inputHint = SocketHints.Inputs.createMatSocketHint("Input", false); private final SocketHint outputHint = SocketHints.Outputs.createMatSocketHint("Output"); - @Override - public String getName() { - return "Desaturate"; - } - - @Override - public String getDescription() { - return "Convert a color image into shades of gray."; - } + private final InputSocket inputSocket; + private final OutputSocket outputSocket; - @Override - public Category getCategory() { - return Category.IMAGE_PROCESSING; + public DesaturateOperation(InputSocket.Factory inputSocketFactory, OutputSocket.Factory outputSocketFactory) { + this.inputSocket = inputSocketFactory.create(inputHint); + this.outputSocket = outputSocketFactory.create(outputHint); } - @Override - public Optional getIcon() { - return Optional.of(getClass().getResourceAsStream("/edu/wpi/grip/ui/icons/desaturate.png")); - } @Override - public InputSocket[] createInputSockets(EventBus eventBus) { - return new InputSocket[]{new InputSocket<>(eventBus, inputHint)}; + public List getInputSockets() { + return ImmutableList.of( + inputSocket + ); } @Override - public OutputSocket[] createOutputSockets(EventBus eventBus) { - return new OutputSocket[]{new OutputSocket<>(eventBus, outputHint)}; + public List getOutputSockets() { + return ImmutableList.of( + outputSocket + ); } @Override - @SuppressWarnings("unchecked") - public void perform(InputSocket[] inputs, OutputSocket[] outputs) { - final Mat input = ((InputSocket) inputs[0]).getValue().get(); + public void perform() { + final Mat input = inputSocket.getValue().get(); - final OutputSocket outputSocket = (OutputSocket) outputs[0]; Mat output = outputSocket.getValue().get(); diff --git a/core/src/main/java/edu/wpi/grip/core/operations/composite/DistanceTransformOperation.java b/core/src/main/java/edu/wpi/grip/core/operations/composite/DistanceTransformOperation.java index c40a5261cc..a55d91d4f9 100644 --- a/core/src/main/java/edu/wpi/grip/core/operations/composite/DistanceTransformOperation.java +++ b/core/src/main/java/edu/wpi/grip/core/operations/composite/DistanceTransformOperation.java @@ -1,20 +1,25 @@ package edu.wpi.grip.core.operations.composite; -import com.google.common.eventbus.EventBus; +import com.google.common.collect.ImmutableList; -import edu.wpi.grip.core.sockets.InputSocket; import edu.wpi.grip.core.Operation; +import edu.wpi.grip.core.OperationDescription; +import edu.wpi.grip.core.sockets.InputSocket; import edu.wpi.grip.core.sockets.OutputSocket; import edu.wpi.grip.core.sockets.SocketHint; import edu.wpi.grip.core.sockets.SocketHints; - -import java.io.InputStream; -import java.util.Optional; +import edu.wpi.grip.core.util.Icon; import org.bytedeco.javacpp.opencv_core.Mat; -import static org.bytedeco.javacpp.opencv_core.*; -import static org.bytedeco.javacpp.opencv_imgproc.*; + +import java.util.List; + +import static org.bytedeco.javacpp.opencv_core.CV_8U; +import static org.bytedeco.javacpp.opencv_imgproc.CV_DIST_C; +import static org.bytedeco.javacpp.opencv_imgproc.CV_DIST_L1; +import static org.bytedeco.javacpp.opencv_imgproc.CV_DIST_L2; +import static org.bytedeco.javacpp.opencv_imgproc.distanceTransform; /** * GRIP {@link Operation} for @@ -22,6 +27,14 @@ */ public class DistanceTransformOperation implements Operation { + public static final OperationDescription DESCRIPTION = + OperationDescription.builder() + .name("Distance Transform") + .summary("Sets the values of pixels in a binary image to their distance to the nearest black pixel.") + .category(OperationDescription.Category.IMAGE_PROCESSING) + .icon(Icon.iconStream("opencv")) + .build(); + private enum Type { DIST_L1("CV_DIST_L1", CV_DIST_L1), @@ -42,7 +55,9 @@ public String toString() { } } - /** Masks are either 0x0, 3x3, or 5x5 */ + /** + * Masks are either 0x0, 3x3, or 5x5 + */ private enum MaskSize { ZERO("0x0", 0), @@ -69,54 +84,49 @@ public String toString() { private final SocketHint outputHint = SocketHints.Inputs.createMatSocketHint("Output", true); - @Override - public String getName() { - return "Distance Transform"; - } - @Override - public String getDescription() { - return "Sets the values of pixels in a binary image to their distance to the nearest black pixel."; - } + private final InputSocket srcSocket; + private final InputSocket typeSocket; + private final InputSocket maskSizeSocket; - @Override - public Category getCategory() { - return Category.IMAGE_PROCESSING; - } + private final OutputSocket outputSocket; - @Override - public Optional getIcon() { - return Optional.of(getClass().getResourceAsStream("/edu/wpi/grip/ui/icons/opencv.png")); + public DistanceTransformOperation(InputSocket.Factory inputSocketFactory, OutputSocket.Factory outputSocketFactory) { + this.srcSocket = inputSocketFactory.create(srcHint); + this.typeSocket = inputSocketFactory.create(typeHint); + this.maskSizeSocket = inputSocketFactory.create(maskSizeHint); + + this.outputSocket = outputSocketFactory.create(outputHint); } + @Override - public InputSocket[] createInputSockets(EventBus eventBus) { - return new InputSocket[]{ - new InputSocket<>(eventBus, srcHint), - new InputSocket<>(eventBus, typeHint), - new InputSocket<>(eventBus, maskSizeHint) - }; + public List getInputSockets() { + return ImmutableList.of( + srcSocket, + typeSocket, + maskSizeSocket + ); } @Override - public OutputSocket[] createOutputSockets(EventBus eventBus) { - return new OutputSocket[]{ - new OutputSocket<>(eventBus, outputHint) - }; + public List getOutputSockets() { + return ImmutableList.of( + outputSocket + ); } @Override - public void perform(InputSocket[] inputs, OutputSocket[] outputs) { - final Mat input = (Mat) inputs[0].getValue().get(); + public void perform() { + final Mat input = srcSocket.getValue().get(); if (input.type() != CV_8U) { throw new IllegalArgumentException("Distance transform only works on 8-bit binary images"); } - final Type type = (Type) inputs[1].getValue().get(); - final MaskSize maskSize = (MaskSize) inputs[2].getValue().get(); + final Type type = typeSocket.getValue().get(); + final MaskSize maskSize = maskSizeSocket.getValue().get(); - final OutputSocket outputSocket = (OutputSocket) outputs[0]; final Mat output = outputSocket.getValue().get(); distanceTransform(input, output, type.value, maskSize.value); 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 8807e94731..b7aba5430c 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 @@ -1,18 +1,23 @@ package edu.wpi.grip.core.operations.composite; -import com.google.common.eventbus.EventBus; -import edu.wpi.grip.core.*; +import com.google.common.collect.ImmutableList; +import edu.wpi.grip.core.Operation; +import edu.wpi.grip.core.OperationDescription; import edu.wpi.grip.core.sockets.InputSocket; import edu.wpi.grip.core.sockets.OutputSocket; import edu.wpi.grip.core.sockets.SocketHint; import edu.wpi.grip.core.sockets.SocketHints; +import edu.wpi.grip.core.util.Icon; -import java.io.InputStream; import java.util.List; -import java.util.Optional; -import static org.bytedeco.javacpp.opencv_core.*; -import static org.bytedeco.javacpp.opencv_imgproc.*; +import static org.bytedeco.javacpp.opencv_core.Mat; +import static org.bytedeco.javacpp.opencv_core.MatVector; +import static org.bytedeco.javacpp.opencv_core.Rect; +import static org.bytedeco.javacpp.opencv_imgproc.arcLength; +import static org.bytedeco.javacpp.opencv_imgproc.boundingRect; +import static org.bytedeco.javacpp.opencv_imgproc.contourArea; +import static org.bytedeco.javacpp.opencv_imgproc.convexHull; /** * An {@link Operation} that takes in a list of contours and outputs a list of any contours in the input that match @@ -25,6 +30,14 @@ */ public class FilterContoursOperation implements Operation { + public static final OperationDescription DESCRIPTION = + OperationDescription.builder() + .name("Filter Contours") + .summary("Find contours matching certain criteria") + .category(OperationDescription.Category.FEATURE_DETECTION) + .icon(Icon.iconStream("find-contours")) + .build(); + private final SocketHint contoursHint = new SocketHint.Builder<>(ContoursReport.class) .identifier("Contours").initialValueSupplier(ContoursReport::new).build(); @@ -46,7 +59,7 @@ public class FilterContoursOperation implements Operation { private final SocketHint maxHeightHint = SocketHints.Inputs.createNumberSpinnerSocketHint("Max Height", 1000, 0, Integer.MAX_VALUE); - private final SocketHint solidityHint = + private final SocketHint> solidityHint = SocketHints.Inputs.createNumberListRangeSocketHint("Solidity", 0, 100); private final SocketHint minVertexHint = @@ -62,65 +75,75 @@ public class FilterContoursOperation implements Operation { SocketHints.Inputs.createNumberSpinnerSocketHint("Max Ratio", 1000, 0, Integer.MAX_VALUE); - @Override - public String getName() { - return "Filter Contours"; - } - - @Override - public String getDescription() { - return "Find contours matching certain criteria."; - } - - @Override - public Category getCategory() { - return Category.FEATURE_DETECTION; - } - - @Override - public Optional getIcon() { - return Optional.of(getClass().getResourceAsStream("/edu/wpi/grip/ui/icons/find-contours.png")); + private final InputSocket contoursSocket; + private final InputSocket minAreaSocket; + private final InputSocket minPerimeterSocket; + private final InputSocket minWidthSocket, maxWidthSocket; + private final InputSocket minHeightSocket, maxHeightSocket; + private final InputSocket> soliditySocket; + private final InputSocket minVertexSocket, maxVertexSocket; + private final InputSocket minRatioSocket, maxRatioSocket; + + private final OutputSocket outputSocket; + + public FilterContoursOperation(InputSocket.Factory inputSocketFactory, OutputSocket.Factory outputSocketFactory) { + this.contoursSocket = inputSocketFactory.create(contoursHint); + this.minAreaSocket = inputSocketFactory.create(minAreaHint); + this.minPerimeterSocket = inputSocketFactory.create(minPerimeterHint); + this.minWidthSocket = inputSocketFactory.create(minWidthHint); + this.maxWidthSocket = inputSocketFactory.create(maxWidthHint); + this.minHeightSocket = inputSocketFactory.create(minHeightHint); + this.maxHeightSocket = inputSocketFactory.create(maxHeightHint); + this.soliditySocket = inputSocketFactory.create(solidityHint); + this.minVertexSocket = inputSocketFactory.create(minVertexHint); + this.maxVertexSocket = inputSocketFactory.create(maxVertexHint); + this.minRatioSocket = inputSocketFactory.create(minRatioHint); + this.maxRatioSocket = inputSocketFactory.create(maxRatioHint); + + this.outputSocket = outputSocketFactory.create(contoursHint); } @Override - public InputSocket[] createInputSockets(EventBus eventBus) { - return new InputSocket[]{ - new InputSocket<>(eventBus, contoursHint), - new InputSocket<>(eventBus, minAreaHint), - new InputSocket<>(eventBus, minPerimeterHint), - new InputSocket<>(eventBus, minWidthHint), - new InputSocket<>(eventBus, maxWidthHint), - new InputSocket<>(eventBus, minHeightHint), - new InputSocket<>(eventBus, maxHeightHint), - new InputSocket<>(eventBus, solidityHint), - new InputSocket<>(eventBus, minVertexHint), - new InputSocket<>(eventBus, maxVertexHint), - new InputSocket<>(eventBus, minRatioHint), - new InputSocket<>(eventBus, maxRatioHint), - }; + public List getInputSockets() { + return ImmutableList.of( + contoursSocket, + minAreaSocket, + minPerimeterSocket, + minWidthSocket, + maxWidthSocket, + minHeightSocket, + maxHeightSocket, + soliditySocket, + maxVertexSocket, + minVertexSocket, + minRatioSocket, + maxRatioSocket + ); } @Override - public OutputSocket[] createOutputSockets(EventBus eventBus) { - return new OutputSocket[]{new OutputSocket<>(eventBus, contoursHint)}; + public List getOutputSockets() { + return ImmutableList.of( + outputSocket + ); } @Override @SuppressWarnings("unchecked") - public void perform(InputSocket[] inputs, OutputSocket[] outputs) { - final InputSocket inputSocket = (InputSocket) inputs[0]; - final double minArea = ((Number) inputs[1].getValue().get()).doubleValue(); - final double minPerimeter = ((Number) inputs[2].getValue().get()).doubleValue(); - final double minWidth = ((Number) inputs[3].getValue().get()).doubleValue(); - final double maxWidth = ((Number) inputs[4].getValue().get()).doubleValue(); - final double minHeight = ((Number) inputs[5].getValue().get()).doubleValue(); - final double maxHeight = ((Number) inputs[6].getValue().get()).doubleValue(); - final double minSolidity = ((List) inputs[7].getValue().get()).get(0).doubleValue(); - final double maxSolidity = ((List) inputs[7].getValue().get()).get(1).doubleValue(); - final double minVertexCount = ((Number) inputs[8].getValue().get()).doubleValue(); - final double maxVertexCount = ((Number) inputs[9].getValue().get()).doubleValue(); - final double minRatio = ((Number) inputs[10].getValue().get()).doubleValue(); - final double maxRatio = ((Number) inputs[11].getValue().get()).doubleValue(); + public void perform() { + final InputSocket inputSocket = contoursSocket; + final double minArea = minAreaSocket.getValue().get().doubleValue(); + final double minPerimeter = minPerimeterSocket.getValue().get().doubleValue(); + final double minWidth = minWidthSocket.getValue().get().doubleValue(); + 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 minVertexCount = minVertexSocket.getValue().get().doubleValue(); + final double maxVertexCount = maxVertexSocket.getValue().get().doubleValue(); + final double minRatio = minRatioSocket.getValue().get().doubleValue(); + final double maxRatio = maxRatioSocket.getValue().get().doubleValue(); final MatVector inputContours = inputSocket.getValue().get().getContours(); @@ -145,7 +168,7 @@ public void perform(InputSocket[] inputs, OutputSocket[] outputs) { final double solidity = 100 * area / contourArea(hull); if (solidity < minSolidity || solidity > maxSolidity) continue; - if(contour.rows() < minVertexCount || contour.rows() > maxVertexCount) continue; + if (contour.rows() < minVertexCount || contour.rows() > maxVertexCount) continue; final double ratio = bb.width() / bb.height(); if (ratio < minRatio || ratio > maxRatio) continue; @@ -155,7 +178,6 @@ public void perform(InputSocket[] inputs, OutputSocket[] outputs) { outputContours.resize(filteredContourCount); - final OutputSocket outputSocket = (OutputSocket) outputs[0]; outputSocket.setValue(new ContoursReport(outputContours, inputSocket.getValue().get().getRows(), inputSocket.getValue().get().getCols())); } 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 0de625c040..430fab685b 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 @@ -1,15 +1,15 @@ package edu.wpi.grip.core.operations.composite; -import com.google.common.eventbus.EventBus; -import edu.wpi.grip.core.*; +import com.google.common.collect.ImmutableList; +import edu.wpi.grip.core.Operation; +import edu.wpi.grip.core.OperationDescription; import edu.wpi.grip.core.sockets.InputSocket; import edu.wpi.grip.core.sockets.OutputSocket; import edu.wpi.grip.core.sockets.SocketHint; import edu.wpi.grip.core.sockets.SocketHints; +import edu.wpi.grip.core.util.Icon; -import java.io.InputStream; import java.util.List; -import java.util.Optional; import java.util.stream.Collectors; /** @@ -17,60 +17,64 @@ * to only relevant ones. */ public class FilterLinesOperation implements Operation { + + public static final OperationDescription DESCRIPTION = + OperationDescription.builder() + .name("Filter Lines") + .summary("Filter only lines from a Find Lines operation that fit certain criteria") + .category(OperationDescription.Category.FEATURE_DETECTION) + .icon(Icon.iconStream("filter-lines")) + .build(); + private final SocketHint inputHint = new SocketHint.Builder<>(LinesReport.class).identifier("Lines").build(); private final SocketHint minLengthHint = SocketHints.Inputs.createNumberSpinnerSocketHint("Min Length", 20); - private final SocketHint angleHint = SocketHints.Inputs.createNumberListRangeSocketHint("Angle", 0, 360); + private final SocketHint> angleHint = SocketHints.Inputs.createNumberListRangeSocketHint("Angle", 0, 360); private final SocketHint outputHint = new SocketHint.Builder<>(LinesReport.class) .identifier("Lines").initialValueSupplier(LinesReport::new).build(); - @Override - public String getName() { - return "Filter Lines"; - } - @Override - public String getDescription() { - return "Filter only lines from a Find Lines operation that fit certain criteria."; - } + private final InputSocket inputSocket; + private final InputSocket minLengthSocket; + private final InputSocket> angleSocket; - @Override - public Category getCategory() { - return Category.FEATURE_DETECTION; - } + private final OutputSocket linesOutputSocket; - @Override - public Optional getIcon() { - return Optional.of(getClass().getResourceAsStream("/edu/wpi/grip/ui/icons/filter-lines.png")); + public FilterLinesOperation(InputSocket.Factory inputSocketFactory, OutputSocket.Factory outputSocketFactory) { + this.inputSocket = inputSocketFactory.create(inputHint); + this.minLengthSocket = inputSocketFactory.create(minLengthHint); + this.angleSocket = inputSocketFactory.create(angleHint); + + this.linesOutputSocket = outputSocketFactory.create(outputHint); } @Override - public InputSocket[] createInputSockets(EventBus eventBus) { - return new InputSocket[]{ - new InputSocket<>(eventBus, inputHint), - new InputSocket<>(eventBus, minLengthHint), - new InputSocket<>(eventBus, angleHint), - }; + public List getInputSockets() { + return ImmutableList.of( + inputSocket, + minLengthSocket, + angleSocket + ); } @Override - public OutputSocket[] createOutputSockets(EventBus eventBus) { - return new OutputSocket[]{new OutputSocket<>(eventBus, outputHint)}; + public List getOutputSockets() { + return ImmutableList.of( + linesOutputSocket + ); } @Override @SuppressWarnings("unchecked") - public void perform(InputSocket[] inputs, OutputSocket[] outputs) { - final LinesReport inputLines = (LinesReport) inputs[0].getValue().get(); - final double minLengthSquared = Math.pow(((Number) inputs[1].getValue().get()).doubleValue(), 2); - final double minAngle = ((InputSocket>) inputs[2]).getValue().get().get(0).doubleValue(); - final double maxAngle = ((InputSocket>) inputs[2]).getValue().get().get(1).doubleValue(); - - final OutputSocket linesOutputSocket = (OutputSocket) outputs[0]; + 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(); List 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 e6473bcd3f..3e4d169313 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 @@ -1,18 +1,20 @@ package edu.wpi.grip.core.operations.composite; -import com.google.common.eventbus.EventBus; -import edu.wpi.grip.core.*; +import com.google.common.collect.ImmutableList; +import edu.wpi.grip.core.Operation; +import edu.wpi.grip.core.OperationDescription; import edu.wpi.grip.core.sockets.InputSocket; import edu.wpi.grip.core.sockets.OutputSocket; import edu.wpi.grip.core.sockets.SocketHint; import edu.wpi.grip.core.sockets.SocketHints; +import edu.wpi.grip.core.util.Icon; -import java.io.InputStream; import java.util.ArrayList; import java.util.List; -import java.util.Optional; -import static org.bytedeco.javacpp.opencv_core.*; +import static org.bytedeco.javacpp.opencv_core.KeyPoint; +import static org.bytedeco.javacpp.opencv_core.KeyPointVector; +import static org.bytedeco.javacpp.opencv_core.Mat; import static org.bytedeco.javacpp.opencv_features2d.SimpleBlobDetector; /** @@ -20,58 +22,64 @@ */ public class FindBlobsOperation implements Operation { + public static final OperationDescription DESCRIPTION = + OperationDescription.builder() + .name("Find Blobs") + .summary("Detects groups of pixels in an image.") + .category(OperationDescription.Category.FEATURE_DETECTION) + .icon(Icon.iconStream("find-blobs")) + .build(); + private final SocketHint inputHint = SocketHints.Inputs.createMatSocketHint("Input", false); private final SocketHint minAreaHint = SocketHints.Inputs.createNumberSpinnerSocketHint("Min Area", 1); - private final SocketHint circularityHint = SocketHints.Inputs.createNumberListRangeSocketHint("Circularity", 0.0, 1.0); + private final SocketHint> circularityHint = SocketHints.Inputs.createNumberListRangeSocketHint("Circularity", 0.0, 1.0); private final SocketHint colorHint = SocketHints.createBooleanSocketHint("Dark Blobs", false); - private final SocketHint blobsHint = new SocketHint.Builder(BlobsReport.class) + private final SocketHint blobsHint = new SocketHint.Builder<>(BlobsReport.class) .identifier("Blobs") .initialValueSupplier(BlobsReport::new) .build(); - @Override - public String getName() { - return "Find Blobs"; - } + private final InputSocket inputSocket; + private final InputSocket minAreaSocket; + private final InputSocket> circularitySocket; + private final InputSocket colorSocket; - @Override - public String getDescription() { - return "Detect groups of pixels in an image."; - } + private final OutputSocket outputSocket; - @Override - public Category getCategory() { - return Category.FEATURE_DETECTION; - } + public FindBlobsOperation(InputSocket.Factory inputSocketFactory, OutputSocket.Factory outputSocketFactory) { + this.inputSocket = inputSocketFactory.create(inputHint); + this.minAreaSocket = inputSocketFactory.create(minAreaHint); + this.circularitySocket = inputSocketFactory.create(circularityHint); + this.colorSocket = inputSocketFactory.create(colorHint); - @Override - public Optional getIcon() { - return Optional.of(getClass().getResourceAsStream("/edu/wpi/grip/ui/icons/find-blobs.png")); + this.outputSocket = outputSocketFactory.create(blobsHint); } @Override - public InputSocket[] createInputSockets(EventBus eventBus) { - return new InputSocket[]{ - new InputSocket<>(eventBus, inputHint), - new InputSocket<>(eventBus, minAreaHint), - new InputSocket<>(eventBus, circularityHint), - new InputSocket<>(eventBus, colorHint), - }; + public List getInputSockets() { + return ImmutableList.of( + inputSocket, + minAreaSocket, + circularitySocket, + colorSocket + ); } @Override - public OutputSocket[] createOutputSockets(EventBus eventBus) { - return new OutputSocket[]{new OutputSocket<>(eventBus, blobsHint)}; + public List getOutputSockets() { + return ImmutableList.of( + outputSocket + ); } @Override @SuppressWarnings("unchecked") - public void perform(InputSocket[] inputs, OutputSocket[] outputs) { - final Mat input = (Mat) inputs[0].getValue().get(); - final Number minArea = (Number) inputs[1].getValue().get(); - final List circularity = (List) inputs[2].getValue().get(); - final Boolean darkBlobs = (Boolean) inputs[3].getValue().get(); + public void perform() { + final Mat input = inputSocket.getValue().get(); + final Number minArea = minAreaSocket.getValue().get(); + final List circularity = circularitySocket.getValue().get(); + final Boolean darkBlobs = colorSocket.getValue().get(); final SimpleBlobDetector blobDetector = SimpleBlobDetector.create(new SimpleBlobDetector.Params() @@ -96,6 +104,6 @@ public void perform(InputSocket[] inputs, OutputSocket[] outputs) { blobs.add(new BlobsReport.Blob(keyPoint.pt().x(), keyPoint.pt().y(), keyPoint.size())); } - ((OutputSocket) outputs[0]).setValue(new BlobsReport(input, blobs)); + outputSocket.setValue(new BlobsReport(input, blobs)); } } diff --git a/core/src/main/java/edu/wpi/grip/core/operations/composite/FindContoursOperation.java b/core/src/main/java/edu/wpi/grip/core/operations/composite/FindContoursOperation.java index e364009e4d..dcaed282da 100644 --- a/core/src/main/java/edu/wpi/grip/core/operations/composite/FindContoursOperation.java +++ b/core/src/main/java/edu/wpi/grip/core/operations/composite/FindContoursOperation.java @@ -1,23 +1,36 @@ package edu.wpi.grip.core.operations.composite; -import com.google.common.eventbus.EventBus; -import edu.wpi.grip.core.*; +import com.google.common.collect.ImmutableList; +import edu.wpi.grip.core.Operation; +import edu.wpi.grip.core.OperationDescription; import edu.wpi.grip.core.sockets.InputSocket; import edu.wpi.grip.core.sockets.OutputSocket; import edu.wpi.grip.core.sockets.SocketHint; import edu.wpi.grip.core.sockets.SocketHints; +import edu.wpi.grip.core.util.Icon; -import java.io.InputStream; -import java.util.Optional; +import java.util.List; -import static org.bytedeco.javacpp.opencv_core.*; -import static org.bytedeco.javacpp.opencv_imgproc.*; +import static org.bytedeco.javacpp.opencv_core.Mat; +import static org.bytedeco.javacpp.opencv_core.MatVector; +import static org.bytedeco.javacpp.opencv_imgproc.CV_CHAIN_APPROX_TC89_KCOS; +import static org.bytedeco.javacpp.opencv_imgproc.CV_RETR_EXTERNAL; +import static org.bytedeco.javacpp.opencv_imgproc.CV_RETR_LIST; +import static org.bytedeco.javacpp.opencv_imgproc.findContours; /** * An {@link Operation} that, given a binary image, produces a list of contours of all of the shapes in the image */ public class FindContoursOperation implements Operation { + public static final OperationDescription DESCRIPTION = + OperationDescription.builder() + .name("Find Contours") + .summary("Detects contours in a binary image.") + .category(OperationDescription.Category.FEATURE_DETECTION) + .icon(Icon.iconStream("find-contours")) + .build(); + private final SocketHint inputHint = new SocketHint.Builder<>(Mat.class).identifier("Input").build(); @@ -27,50 +40,39 @@ public class FindContoursOperation implements Operation { private final SocketHint contoursHint = new SocketHint.Builder<>(ContoursReport.class) .identifier("Contours").initialValueSupplier(ContoursReport::new).build(); - @Override - public String getName() { - return "Find Contours"; - } - @Override - public String getDescription() { - return "Detect contours in a binary image."; - } + private final InputSocket inputSocket; + private final InputSocket externalSocket; - @Override - public Category getCategory() { - return Category.FEATURE_DETECTION; - } + private final OutputSocket contoursSocket; - @Override - public Optional getIcon() { - return Optional.of(getClass().getResourceAsStream("/edu/wpi/grip/ui/icons/find-contours.png")); - } + public FindContoursOperation(InputSocket.Factory inputSocketFactory, OutputSocket.Factory outputSocketFactory) { + this.inputSocket = inputSocketFactory.create(inputHint); + this.externalSocket = inputSocketFactory.create(externalHint); - @Override - public InputSocket[] createInputSockets(EventBus eventBus) { - return new InputSocket[]{ - new InputSocket<>(eventBus, inputHint), - new InputSocket<>(eventBus, externalHint), - }; + this.contoursSocket = outputSocketFactory.create(contoursHint); } @Override - public OutputSocket[] createOutputSockets(EventBus eventBus) { - return new OutputSocket[]{new OutputSocket<>(eventBus, contoursHint)}; + public List getInputSockets() { + return ImmutableList.of( + inputSocket, + externalSocket + ); } @Override - public Optional createData() { - return Optional.of(new Mat()); + public List getOutputSockets() { + return ImmutableList.of( + contoursSocket + ); } @Override - @SuppressWarnings("unchecked") - public void perform(InputSocket[] inputs, OutputSocket[] outputs, Optional data) { - final Mat input = ((InputSocket) inputs[0]).getValue().get(); - final Mat tmp = ((Optional) data).get(); - final boolean externalOnly = ((InputSocket) inputs[1]).getValue().get(); + public void perform() { + final Mat input = inputSocket.getValue().get(); + final Mat tmp = new Mat(); + final boolean externalOnly = externalSocket.getValue().get(); if (input.empty()) { return; @@ -86,7 +88,6 @@ public void perform(InputSocket[] inputs, OutputSocket[] outputs, Optional findContours(tmp, contours, externalOnly ? CV_RETR_EXTERNAL : CV_RETR_LIST, CV_CHAIN_APPROX_TC89_KCOS); - final OutputSocket contoursSocket = (OutputSocket) outputs[0]; contoursSocket.setValue(new ContoursReport(contours, input.rows(), input.cols())); } } diff --git a/core/src/main/java/edu/wpi/grip/core/operations/composite/FindLinesOperation.java b/core/src/main/java/edu/wpi/grip/core/operations/composite/FindLinesOperation.java index f530f25672..ee9252c627 100644 --- a/core/src/main/java/edu/wpi/grip/core/operations/composite/FindLinesOperation.java +++ b/core/src/main/java/edu/wpi/grip/core/operations/composite/FindLinesOperation.java @@ -1,70 +1,68 @@ package edu.wpi.grip.core.operations.composite; -import com.google.common.eventbus.EventBus; -import edu.wpi.grip.core.*; +import com.google.common.collect.ImmutableList; +import edu.wpi.grip.core.Operation; +import edu.wpi.grip.core.OperationDescription; import edu.wpi.grip.core.sockets.InputSocket; import edu.wpi.grip.core.sockets.OutputSocket; import edu.wpi.grip.core.sockets.SocketHint; import edu.wpi.grip.core.sockets.SocketHints; +import edu.wpi.grip.core.util.Icon; import org.bytedeco.javacpp.indexer.FloatIndexer; -import java.io.InputStream; import java.util.ArrayList; import java.util.List; -import java.util.Optional; import static org.bytedeco.javacpp.opencv_core.Mat; -import static org.bytedeco.javacpp.opencv_imgproc.*; +import static org.bytedeco.javacpp.opencv_imgproc.COLOR_BGR2GRAY; +import static org.bytedeco.javacpp.opencv_imgproc.LineSegmentDetector; +import static org.bytedeco.javacpp.opencv_imgproc.cvtColor; /** * Find line segments in a color or grayscale image */ public class FindLinesOperation implements Operation { + public static final OperationDescription DESCRIPTION = + OperationDescription.builder() + .name("Find Lines") + .summary("Detects line segments in an image.") + .category(OperationDescription.Category.FEATURE_DETECTION) + .icon(Icon.iconStream("find-lines")) + .build(); + private final SocketHint inputHint = SocketHints.Inputs.createMatSocketHint("Input", false); private final SocketHint linesHint = new SocketHint.Builder<>(LinesReport.class) .identifier("Lines").initialValueSupplier(LinesReport::new).build(); - @Override - public String getName() { - return "Find Lines"; - } - @Override - public String getDescription() { - return "Detect line segments in an image."; - } + private final InputSocket inputSocket; - @Override - public Category getCategory() { - return Category.FEATURE_DETECTION; - } + private final OutputSocket linesReportSocket; - @Override - public Optional getIcon() { - return Optional.of(getClass().getResourceAsStream("/edu/wpi/grip/ui/icons/find-lines.png")); - } - - @Override - public InputSocket[] createInputSockets(EventBus eventBus) { - return new InputSocket[]{new InputSocket<>(eventBus, inputHint)}; + public FindLinesOperation(InputSocket.Factory inputSocketFactory, OutputSocket.Factory outputSocketFactory) { + this.inputSocket = inputSocketFactory.create(inputHint); + this.linesReportSocket = outputSocketFactory.create(linesHint); } @Override - public OutputSocket[] createOutputSockets(EventBus eventBus) { - return new OutputSocket[]{new OutputSocket<>(eventBus, linesHint)}; + public List getInputSockets() { + return ImmutableList.of( + inputSocket + ); } @Override - public Optional createData() { - return Optional.of(new Mat()); + public List getOutputSockets() { + return ImmutableList.of( + linesReportSocket + ); } @Override @SuppressWarnings("unchecked") - public void perform(InputSocket[] inputs, OutputSocket[] outputs, Optional data) { - final Mat input = (Mat) inputs[0].getValue().get(); - final OutputSocket linesReportSocket = (OutputSocket) outputs[0]; + public void perform() { + final Mat input = inputSocket.getValue().get(); final LineSegmentDetector lsd = linesReportSocket.getValue().get().getLineSegmentDetector(); final Mat lines = new Mat(); @@ -73,7 +71,7 @@ public void perform(InputSocket[] inputs, OutputSocket[] outputs, Optional } else { // The line detector works on a single channel. If the input is a color image, we can just give the line // detector a grayscale version of it - final Mat tmp = (Mat) data.get(); + final Mat tmp = new Mat(); cvtColor(input, tmp, COLOR_BGR2GRAY); lsd.detect(tmp, lines); } 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 e556daba10..1853af8053 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 @@ -1,74 +1,91 @@ package edu.wpi.grip.core.operations.composite; -import com.google.common.eventbus.EventBus; +import com.google.common.collect.ImmutableList; +import edu.wpi.grip.core.Operation; +import edu.wpi.grip.core.OperationDescription; import edu.wpi.grip.core.sockets.InputSocket; import edu.wpi.grip.core.sockets.OutputSocket; import edu.wpi.grip.core.sockets.SocketHint; import edu.wpi.grip.core.sockets.SocketHints; +import edu.wpi.grip.core.util.Icon; import java.util.List; -import java.util.Optional; import java.util.logging.Level; import java.util.logging.Logger; -import static org.bytedeco.javacpp.opencv_core.*; +import static org.bytedeco.javacpp.opencv_core.Mat; +import static org.bytedeco.javacpp.opencv_core.Scalar; +import static org.bytedeco.javacpp.opencv_core.inRange; import static org.bytedeco.javacpp.opencv_imgproc.COLOR_BGR2HLS; import static org.bytedeco.javacpp.opencv_imgproc.cvtColor; /** - * An {@link edu.wpi.grip.core.Operation} that converts a color image into a binary image based on the HSL threshold ranges + * An {@link Operation} that converts a color image into a binary image based on the HSL threshold ranges */ public class HSLThresholdOperation extends ThresholdOperation { + + public static final OperationDescription DESCRIPTION = + OperationDescription.builder() + .name("HSL Threshold") + .summary("Segment an image based on hue, saturation, and luminance ranges.") + .category(OperationDescription.Category.IMAGE_PROCESSING) + .icon(Icon.iconStream("threshold")) + .build(); + private static final Logger logger = Logger.getLogger(HSLThresholdOperation.class.getName()); private final SocketHint inputHint = SocketHints.Inputs.createMatSocketHint("Input", false); - private final SocketHint hueHint = SocketHints.Inputs.createNumberListRangeSocketHint("Hue", 0.0, 180.0); - private final SocketHint saturationHint = SocketHints.Inputs.createNumberListRangeSocketHint("Saturation", 0.0, 255.0); - private final SocketHint luminanceHint = SocketHints.Inputs.createNumberListRangeSocketHint("Luminance", 0.0, 255.0); + private final SocketHint> hueHint = SocketHints.Inputs.createNumberListRangeSocketHint("Hue", 0.0, 180.0); + private final SocketHint> saturationHint = SocketHints.Inputs.createNumberListRangeSocketHint("Saturation", 0.0, 255.0); + private final SocketHint> luminanceHint = SocketHints.Inputs.createNumberListRangeSocketHint("Luminance", 0.0, 255.0); private final SocketHint outputHint = SocketHints.Outputs.createMatSocketHint("Output"); - @Override - public String getName() { - return "HSL Threshold"; - } + private final InputSocket inputSocket; + private final InputSocket> hueSocket; + private final InputSocket> saturationSocket; + private final InputSocket> luminanceSocket; - @Override - public String getDescription() { - return "Segment an image based on hue, saturation, and luminance ranges."; + private final OutputSocket outputSocket; + + public HSLThresholdOperation(InputSocket.Factory inputSocketFactory, OutputSocket.Factory outputSocketFactory) { + this.inputSocket = inputSocketFactory.create(inputHint); + this.hueSocket = inputSocketFactory.create(hueHint); + this.saturationSocket = inputSocketFactory.create(saturationHint); + this.luminanceSocket = inputSocketFactory.create(luminanceHint); + + this.outputSocket = outputSocketFactory.create(outputHint); } @Override - public InputSocket[] createInputSockets(EventBus eventBus) { - return new InputSocket[]{ - new InputSocket<>(eventBus, inputHint), - new InputSocket<>(eventBus, hueHint), - new InputSocket<>(eventBus, saturationHint), - new InputSocket<>(eventBus, luminanceHint), - }; + public List getInputSockets() { + return ImmutableList.of( + inputSocket, + hueSocket, + saturationSocket, + luminanceSocket + ); } @Override - public OutputSocket[] createOutputSockets(EventBus eventBus) { - return new OutputSocket[]{ - new OutputSocket<>(eventBus, outputHint) - }; + public List getOutputSockets() { + return ImmutableList.of( + outputSocket + ); } @Override - public void perform(InputSocket[] inputs, OutputSocket[] outputs, Optional data) { - final Mat[] dataArray = (Mat[]) data.orElseThrow(() -> new IllegalStateException("Data was not provided")); - - final Mat input = ((InputSocket) inputs[0]).getValue().get(); - final List channel1 = ((InputSocket>) inputs[1]).getValue().get(); - final List channel2 = ((InputSocket>) inputs[2]).getValue().get(); - final List channel3 = ((InputSocket>) inputs[3]).getValue().get(); + @SuppressWarnings("unchecked") + public void perform() { + final Mat input = inputSocket.getValue().get(); + final List channel1 = hueSocket.getValue().get(); + final List channel2 = saturationSocket.getValue().get(); + final List channel3 = luminanceSocket.getValue().get(); if (input.channels() != 3) { throw new IllegalArgumentException("HSL Threshold needs a 3-channel input"); } - final OutputSocket outputSocket = (OutputSocket) outputs[0]; final Mat output = outputSocket.getValue().get(); // Intentionally 1, 3, 2. This maps to the HLS open cv expects 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 d9ad0a9622..94d94a4e1e 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 @@ -1,15 +1,17 @@ package edu.wpi.grip.core.operations.composite; -import com.google.common.eventbus.EventBus; +import com.google.common.collect.ImmutableList; +import edu.wpi.grip.core.Operation; +import edu.wpi.grip.core.OperationDescription; import edu.wpi.grip.core.sockets.InputSocket; import edu.wpi.grip.core.sockets.OutputSocket; import edu.wpi.grip.core.sockets.SocketHint; import edu.wpi.grip.core.sockets.SocketHints; +import edu.wpi.grip.core.util.Icon; import org.bytedeco.javacpp.opencv_core.Mat; import org.bytedeco.javacpp.opencv_core.Scalar; import java.util.List; -import java.util.Optional; import java.util.logging.Level; import java.util.logging.Logger; @@ -18,60 +20,71 @@ import static org.bytedeco.javacpp.opencv_imgproc.cvtColor; /** - * An {@link edu.wpi.grip.core.Operation} that converts a color image into a binary image based on the HSV threshold ranges for each channel + * An {@link Operation} that converts a color image into a binary image based on the HSV threshold ranges for each channel */ public class HSVThresholdOperation extends ThresholdOperation { + public static final OperationDescription DESCRIPTION = + OperationDescription.builder() + .name("HSV Threshold") + .summary("Segment an image based on hue, saturation, and value ranges.") + .category(OperationDescription.Category.IMAGE_PROCESSING) + .icon(Icon.iconStream("threshold")) + .build(); + private static final Logger logger = Logger.getLogger(HSVThresholdOperation.class.getName()); private final SocketHint inputHint = SocketHints.Inputs.createMatSocketHint("Input", false); - private final SocketHint hueHint = SocketHints.Inputs.createNumberListRangeSocketHint("Hue", 0.0, 180.0); - private final SocketHint saturationHint = SocketHints.Inputs.createNumberListRangeSocketHint("Saturation", 0.0, 255.0); - private final SocketHint valueHint = SocketHints.Inputs.createNumberListRangeSocketHint("Value", 0.0, 255.0); + private final SocketHint> hueHint = SocketHints.Inputs.createNumberListRangeSocketHint("Hue", 0.0, 180.0); + private final SocketHint> saturationHint = SocketHints.Inputs.createNumberListRangeSocketHint("Saturation", 0.0, 255.0); + private final SocketHint> valueHint = SocketHints.Inputs.createNumberListRangeSocketHint("Value", 0.0, 255.0); private final SocketHint outputHint = SocketHints.Outputs.createMatSocketHint("Output"); - @Override - public String getName() { - return "HSV Threshold"; - } + private final InputSocket inputSocket; + private final InputSocket> hueSocket; + private final InputSocket> saturationSocket; + private final InputSocket> valueSocket; - @Override - public String getDescription() { - return "Segment an image based on hue, saturation and value ranges."; + private final OutputSocket outputSocket; + + public HSVThresholdOperation(InputSocket.Factory inputSocketFactory, OutputSocket.Factory outputSocketFactory) { + this.inputSocket = inputSocketFactory.create(inputHint); + this.hueSocket = inputSocketFactory.create(hueHint); + this.saturationSocket = inputSocketFactory.create(saturationHint); + this.valueSocket = inputSocketFactory.create(valueHint); + + this.outputSocket = outputSocketFactory.create(outputHint); } @Override - public InputSocket[] createInputSockets(EventBus eventBus) { - return new InputSocket[]{ - new InputSocket<>(eventBus, inputHint), - new InputSocket<>(eventBus, hueHint), - new InputSocket<>(eventBus, saturationHint), - new InputSocket<>(eventBus, valueHint), - }; + public List getInputSockets() { + return ImmutableList.of( + inputSocket, + hueSocket, + saturationSocket, + valueSocket + ); } @Override - public OutputSocket[] createOutputSockets(EventBus eventBus) { - return new OutputSocket[]{ - new OutputSocket<>(eventBus, outputHint) - }; + public List getOutputSockets() { + return ImmutableList.of( + outputSocket + ); } @Override - public void perform(InputSocket[] inputs, OutputSocket[] outputs, Optional data) { - final Mat[] dataArray = (Mat[]) data.orElseThrow(() -> new IllegalStateException("Data was not provided")); - - final Mat input = ((InputSocket) inputs[0]).getValue().get(); - final List channel1 = ((InputSocket>) inputs[1]).getValue().get(); - final List channel2 = ((InputSocket>) inputs[2]).getValue().get(); - final List channel3 = ((InputSocket>) inputs[3]).getValue().get(); + public void perform() { + final Mat input = inputSocket.getValue().get(); + final List channel1 = hueSocket.getValue().get(); + final List channel2 = saturationSocket.getValue().get(); + final List channel3 = valueSocket.getValue().get(); if (input.channels() != 3) { throw new IllegalArgumentException("HSV Threshold needs a 3-channel input"); } - final OutputSocket outputSocket = (OutputSocket) outputs[0]; final Mat output = outputSocket.getValue().get(); final Scalar lowScalar = new Scalar( diff --git a/core/src/main/java/edu/wpi/grip/core/operations/composite/MaskOperation.java b/core/src/main/java/edu/wpi/grip/core/operations/composite/MaskOperation.java index 5a5ceafb84..d534fcd883 100644 --- a/core/src/main/java/edu/wpi/grip/core/operations/composite/MaskOperation.java +++ b/core/src/main/java/edu/wpi/grip/core/operations/composite/MaskOperation.java @@ -1,14 +1,15 @@ package edu.wpi.grip.core.operations.composite; -import com.google.common.eventbus.EventBus; -import edu.wpi.grip.core.*; +import com.google.common.collect.ImmutableList; +import edu.wpi.grip.core.Operation; +import edu.wpi.grip.core.OperationDescription; import edu.wpi.grip.core.sockets.InputSocket; import edu.wpi.grip.core.sockets.OutputSocket; import edu.wpi.grip.core.sockets.SocketHint; import edu.wpi.grip.core.sockets.SocketHints; +import edu.wpi.grip.core.util.Icon; -import java.io.InputStream; -import java.util.Optional; +import java.util.List; import static org.bytedeco.javacpp.opencv_core.Mat; import static org.bytedeco.javacpp.opencv_core.bitwise_xor; @@ -18,53 +19,52 @@ */ public class MaskOperation implements Operation { + public static final OperationDescription DESCRIPTION = + OperationDescription.builder() + .name("Mask") + .summary("Filter out an area of interest in an image using a binary mask.") + .category(OperationDescription.Category.IMAGE_PROCESSING) + .icon(Icon.iconStream("mask")) + .build(); + private final SocketHint inputHint = SocketHints.Inputs.createMatSocketHint("Input", false); private final SocketHint maskHint = SocketHints.Inputs.createMatSocketHint("Mask", false); private final SocketHint outputHint = SocketHints.Outputs.createMatSocketHint("Output"); - @Override - public String getName() { - return "Mask"; - } - @Override - public String getDescription() { - return "Filter out an area of interest in an image using a binary mask."; - } + private final InputSocket inputSocket; + private final InputSocket maskSocket; - @Override - public Category getCategory() { - return Category.IMAGE_PROCESSING; - } + private final OutputSocket outputSocket; - @Override - public Optional getIcon() { - return Optional.of(getClass().getResourceAsStream("/edu/wpi/grip/ui/icons/mask.png")); + public MaskOperation(InputSocket.Factory inputSocketFactory, OutputSocket.Factory outputSocketFactory) { + this.inputSocket = inputSocketFactory.create(inputHint); + this.maskSocket = inputSocketFactory.create(maskHint); + + this.outputSocket = outputSocketFactory.create(outputHint); } @Override - public InputSocket[] createInputSockets(EventBus eventBus) { - return new InputSocket[]{ - new InputSocket<>(eventBus, inputHint), - new InputSocket<>(eventBus, maskHint), - }; + public List getInputSockets() { + return ImmutableList.of( + inputSocket, + maskSocket + ); } @Override - public OutputSocket[] createOutputSockets(EventBus eventBus) { - return new OutputSocket[]{ - new OutputSocket<>(eventBus, outputHint) - }; + public List getOutputSockets() { + return ImmutableList.of( + outputSocket + ); } @Override - @SuppressWarnings("unchecked") - public void perform(InputSocket[] inputs, OutputSocket[] outputs) { - final Mat input = ((InputSocket) inputs[0]).getValue().get(); - final Mat mask = ((InputSocket) inputs[1]).getValue().get(); + public void perform() { + final Mat input = inputSocket.getValue().get(); + final Mat mask = maskSocket.getValue().get(); - final OutputSocket outputSocket = (OutputSocket) outputs[0]; final Mat output = outputSocket.getValue().get(); // Clear the output to black, then copy the input to it with the mask diff --git a/core/src/main/java/edu/wpi/grip/core/operations/composite/NormalizeOperation.java b/core/src/main/java/edu/wpi/grip/core/operations/composite/NormalizeOperation.java index 5d30c0fc54..00a75a9c48 100644 --- a/core/src/main/java/edu/wpi/grip/core/operations/composite/NormalizeOperation.java +++ b/core/src/main/java/edu/wpi/grip/core/operations/composite/NormalizeOperation.java @@ -1,18 +1,24 @@ package edu.wpi.grip.core.operations.composite; -import com.google.common.eventbus.EventBus; +import com.google.common.collect.ImmutableList; -import edu.wpi.grip.core.sockets.InputSocket; import edu.wpi.grip.core.Operation; +import edu.wpi.grip.core.OperationDescription; +import edu.wpi.grip.core.sockets.InputSocket; import edu.wpi.grip.core.sockets.OutputSocket; import edu.wpi.grip.core.sockets.SocketHint; import edu.wpi.grip.core.sockets.SocketHints; +import edu.wpi.grip.core.util.Icon; -import java.io.InputStream; -import java.util.Optional; +import java.util.List; -import static org.bytedeco.javacpp.opencv_core.*; +import static org.bytedeco.javacpp.opencv_core.Mat; +import static org.bytedeco.javacpp.opencv_core.NORM_INF; +import static org.bytedeco.javacpp.opencv_core.NORM_L1; +import static org.bytedeco.javacpp.opencv_core.NORM_L2; +import static org.bytedeco.javacpp.opencv_core.NORM_MINMAX; +import static org.bytedeco.javacpp.opencv_core.normalize; /** * GRIP {@link Operation} for @@ -20,6 +26,14 @@ */ public class NormalizeOperation implements Operation { + public static final OperationDescription DESCRIPTION = + OperationDescription.builder() + .name("Normalize") + .summary("Normalizes or remaps the values of pixels in an image.") + .category(OperationDescription.Category.IMAGE_PROCESSING) + .icon(Icon.iconStream("opencv")) + .build(); + private enum Type { INF("NORM_INF", NORM_INF), @@ -49,51 +63,47 @@ public String toString() { private final SocketHint dstHint = SocketHints.Inputs.createMatSocketHint("Output", true); - @Override - public String getName() { - return "Normalize"; - } - @Override - public String getDescription() { - return "Normalizes or remaps the pixel values in an image."; - } + private final InputSocket srcSocket; + private final InputSocket typeSocket; + private final InputSocket alphaSocket; + private final InputSocket betaSocket; - @Override - public Category getCategory() { - return Category.IMAGE_PROCESSING; - } + private final OutputSocket outputSocket; - @Override - public Optional getIcon() { - return Optional.of(getClass().getResourceAsStream("/edu/wpi/grip/ui/icons/opencv.png")); + public NormalizeOperation(InputSocket.Factory inputSocketFactory, OutputSocket.Factory outputSocketFactory) { + this.srcSocket = inputSocketFactory.create(srcHint); + this.typeSocket = inputSocketFactory.create(typeHint); + this.alphaSocket = inputSocketFactory.create(aHint); + this.betaSocket = inputSocketFactory.create(bHint); + + this.outputSocket = outputSocketFactory.create(dstHint); } @Override - public InputSocket[] createInputSockets(EventBus eventBus) { - return new InputSocket[]{ - new InputSocket<>(eventBus, srcHint), - new InputSocket<>(eventBus, typeHint), - new InputSocket<>(eventBus, aHint), - new InputSocket<>(eventBus, bHint) - }; + public List getInputSockets() { + return ImmutableList.of( + srcSocket, + typeSocket, + alphaSocket, + betaSocket + ); } @Override - public OutputSocket[] createOutputSockets(EventBus eventBus) { - return new OutputSocket[]{ - new OutputSocket<>(eventBus, dstHint) - }; + public List getOutputSockets() { + return ImmutableList.of( + outputSocket + ); } @Override - public void perform(InputSocket[] inputs, OutputSocket[] outputs) { - final Mat input = (Mat) inputs[0].getValue().get(); - final Type type = (Type) inputs[1].getValue().get(); - final Number a = (Number) inputs[2].getValue().get(); - final Number b = (Number) inputs[3].getValue().get(); + public void perform() { + final Mat input = srcSocket.getValue().get(); + final Type type = typeSocket.getValue().get(); + final Number a = alphaSocket.getValue().get(); + final Number b = betaSocket.getValue().get(); - final OutputSocket outputSocket = (OutputSocket) outputs[0]; final Mat output = outputSocket.getValue().get(); normalize(input, output, a.doubleValue(), b.doubleValue(), type.value, -1, null); diff --git a/core/src/main/java/edu/wpi/grip/core/operations/composite/PublishVideoOperation.java b/core/src/main/java/edu/wpi/grip/core/operations/composite/PublishVideoOperation.java index 43630983d9..716c6def83 100644 --- a/core/src/main/java/edu/wpi/grip/core/operations/composite/PublishVideoOperation.java +++ b/core/src/main/java/edu/wpi/grip/core/operations/composite/PublishVideoOperation.java @@ -1,20 +1,21 @@ package edu.wpi.grip.core.operations.composite; -import com.google.common.eventbus.EventBus; -import edu.wpi.grip.core.sockets.InputSocket; +import com.google.common.collect.ImmutableList; import edu.wpi.grip.core.Operation; +import edu.wpi.grip.core.OperationDescription; +import edu.wpi.grip.core.sockets.InputSocket; import edu.wpi.grip.core.sockets.OutputSocket; import edu.wpi.grip.core.sockets.SocketHints; +import edu.wpi.grip.core.util.Icon; import org.bytedeco.javacpp.BytePointer; import org.bytedeco.javacpp.IntPointer; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; -import java.io.InputStream; import java.net.ServerSocket; import java.net.Socket; -import java.util.Optional; +import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; @@ -31,6 +32,14 @@ */ public class PublishVideoOperation implements Operation { + public static final OperationDescription DESCRIPTION = + OperationDescription.builder() + .name("Publish Video") + .summary("Publish an M_JPEG stream to the dashboard.") + .category(OperationDescription.Category.NETWORK) + .icon(Icon.iconStream("publish-video")) + .build(); + private final Logger logger = Logger.getLogger(PublishVideoOperation.class.getName()); private static final int PORT = 1180; @@ -38,9 +47,25 @@ public class PublishVideoOperation implements Operation { private final Object imageLock = new Object(); private final BytePointer imagePointer = new BytePointer(); - private Optional serverThread = Optional.empty(); private volatile boolean connected = false; - private int numSteps = 0; + private final Thread serverThread; + private static int numSteps = 0; + + private final InputSocket inputSocket; + private final InputSocket qualitySocket; + + public PublishVideoOperation(InputSocket.Factory inputSocketFactory) { + if (numSteps != 0) { + throw new IllegalStateException("Only one instance of PublishVideoOperation may exist"); + } + this.inputSocket = inputSocketFactory.create(SocketHints.Inputs.createMatSocketHint("Image", false)); + this.qualitySocket = inputSocketFactory.create(SocketHints.Inputs.createNumberSliderSocketHint("Quality", 80, 0, 100)); + numSteps = numSteps + 1; + + serverThread = new Thread(runServer, "Camera Server"); + serverThread.setDaemon(true); + serverThread.start(); + } /** * Listens for incoming connections on port 1180 and writes JPEG data whenever there's a new frame. @@ -105,7 +130,6 @@ public class PublishVideoOperation implements Operation { } catch (InterruptedException e) { Thread.currentThread().interrupt(); // This is really unnecessary since the thread is about to exit logger.info("Shutting down camera server"); - serverThread = Optional.empty(); return; } finally { connected = false; @@ -114,53 +138,22 @@ public class PublishVideoOperation implements Operation { }; @Override - public String getName() { - return "Publish Video"; - } - - @Override - public String getDescription() { - return "Publish an M-JPEG stream to the dashboard"; + public List getInputSockets() { + return ImmutableList.of( + inputSocket, + qualitySocket + ); } @Override - public Category getCategory() { - return Category.NETWORK; + public List getOutputSockets() { + return ImmutableList.of(); } @Override - public Optional getIcon() { - return Optional.of(getClass().getResourceAsStream("/edu/wpi/grip/ui/icons/publish-video.png")); - } - - @Override - public InputSocket[] createInputSockets(EventBus eventBus) { - return new InputSocket[]{ - new InputSocket<>(eventBus, SocketHints.Inputs.createMatSocketHint("Image", false)), - new InputSocket<>(eventBus, SocketHints.Inputs.createNumberSliderSocketHint("Quality", 80, 0, 100)), - }; - } - - @Override - public OutputSocket[] createOutputSockets(EventBus eventBus) { - return new OutputSocket[0]; - } - - @Override - public synchronized Optional createData() { - numSteps++; - if (!serverThread.isPresent()) { - serverThread = Optional.of(new Thread(runServer, "Camera Server")); - serverThread.get().setDaemon(true); - serverThread.get().start(); - } - return Optional.empty(); - } - - @Override - public void perform(InputSocket[] inputs, OutputSocket[] outputs) { - Mat input = (Mat) inputs[0].getValue().get(); - Number quality = (Number) inputs[1].getValue().get(); + public void perform() { + Mat input = inputSocket.getValue().get(); + Number quality = qualitySocket.getValue().get(); if (!connected) { return; // Don't waste any time converting images if there's no dashboard connected @@ -177,10 +170,9 @@ public void perform(InputSocket[] inputs, OutputSocket[] outputs) { } @Override - public synchronized void cleanUp(InputSocket[] inputs, OutputSocket[] outputs, Optional data) { + public synchronized void cleanUp() { // Stop the video server if there are no Publish Video steps left - if (--numSteps == 0) { - serverThread.ifPresent(Thread::interrupt); - } + serverThread.interrupt(); + numSteps --; } } 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 27e21f1e45..f4f037b3c3 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 @@ -1,79 +1,88 @@ package edu.wpi.grip.core.operations.composite; -import com.google.common.eventbus.EventBus; -import edu.wpi.grip.core.*; +import com.google.common.collect.ImmutableList; +import edu.wpi.grip.core.Operation; +import edu.wpi.grip.core.OperationDescription; import edu.wpi.grip.core.sockets.InputSocket; import edu.wpi.grip.core.sockets.OutputSocket; import edu.wpi.grip.core.sockets.SocketHint; import edu.wpi.grip.core.sockets.SocketHints; +import edu.wpi.grip.core.util.Icon; import java.util.List; -import java.util.Optional; import java.util.logging.Level; import java.util.logging.Logger; -import static org.bytedeco.javacpp.opencv_core.*; +import static org.bytedeco.javacpp.opencv_core.Mat; +import static org.bytedeco.javacpp.opencv_core.Scalar; +import static org.bytedeco.javacpp.opencv_core.inRange; /** * An {@link Operation} that converts a color image into a binary image based on threshold ranges for each channel */ public class RGBThresholdOperation extends ThresholdOperation { + public static final OperationDescription DESCRIPTION = + OperationDescription.builder() + .name("RGB Threshold") + .summary("Segment an image based on color ranges") + .category(OperationDescription.Category.IMAGE_PROCESSING) + .icon(Icon.iconStream("threshold")) + .build(); + private static final Logger logger = Logger.getLogger(RGBThresholdOperation.class.getName()); private final SocketHint inputHint = SocketHints.Inputs.createMatSocketHint("Input", false); - private final SocketHint redHint = SocketHints.Inputs.createNumberListRangeSocketHint("Red", 0.0, 255.0); - private final SocketHint greenHint = SocketHints.Inputs.createNumberListRangeSocketHint("Green", 0.0, 255.0); - private final SocketHint blueHint = SocketHints.Inputs.createNumberListRangeSocketHint("Blue", 0.0, 255.0); + private final SocketHint> redHint = SocketHints.Inputs.createNumberListRangeSocketHint("Red", 0.0, 255.0); + private final SocketHint> greenHint = SocketHints.Inputs.createNumberListRangeSocketHint("Green", 0.0, 255.0); + private final SocketHint> blueHint = SocketHints.Inputs.createNumberListRangeSocketHint("Blue", 0.0, 255.0); private final SocketHint outputHint = SocketHints.Outputs.createMatSocketHint("Output"); - @Override - public String getName() { - return "RGB Threshold"; - } - @Override - public String getDescription() { - return "Segment an image based on color ranges."; - } + private final InputSocket inputSocket; + private final InputSocket> redSocket; + private final InputSocket> greenSocket; + private final InputSocket> blueSocket; - @Override - public InputSocket[] createInputSockets(EventBus eventBus) { - return new InputSocket[]{ - new InputSocket<>(eventBus, inputHint), - new InputSocket<>(eventBus, redHint), - new InputSocket<>(eventBus, greenHint), - new InputSocket<>(eventBus, blueHint), - }; + private final OutputSocket outputSocket; + + public RGBThresholdOperation(InputSocket.Factory inputSocketFactory, OutputSocket.Factory outputSocketFactory) { + this.inputSocket = inputSocketFactory.create(inputHint); + this.redSocket = inputSocketFactory.create(redHint); + this.greenSocket = inputSocketFactory.create(greenHint); + this.blueSocket = inputSocketFactory.create(blueHint); + + this.outputSocket = outputSocketFactory.create(outputHint); } @Override - public OutputSocket[] createOutputSockets(EventBus eventBus) { - return new OutputSocket[]{ - new OutputSocket<>(eventBus, outputHint) - }; + public List getInputSockets() { + return ImmutableList.of( + inputSocket, + redSocket, + greenSocket, + blueSocket + ); } @Override - public Optional createData() { - return Optional.of(new Mat[]{new Mat(), new Mat()}); + public List getOutputSockets() { + return ImmutableList.of( + outputSocket + ); } @Override - @SuppressWarnings("unchecked") - public void perform(InputSocket[] inputs, OutputSocket[] outputs, Optional data) { - final Mat[] dataArray = (Mat[]) data.orElseThrow(() -> new IllegalStateException("Data was not provided")); - - final Mat input = ((InputSocket) inputs[0]).getValue().get(); - final List channel1 = ((InputSocket>) inputs[1]).getValue().get(); - final List channel2 = ((InputSocket>) inputs[2]).getValue().get(); - final List channel3 = ((InputSocket>) inputs[3]).getValue().get(); + public void perform() { + final Mat input = inputSocket.getValue().get(); + final List channel1 = redSocket.getValue().get(); + final List channel2 = greenSocket.getValue().get(); + final List channel3 = blueSocket.getValue().get(); if (input.channels() != 3) { throw new IllegalArgumentException("RGB Threshold needs a 3-channel input"); } - final OutputSocket outputSocket = (OutputSocket) outputs[0]; final Mat output = outputSocket.getValue().get(); diff --git a/core/src/main/java/edu/wpi/grip/core/operations/composite/ResizeOperation.java b/core/src/main/java/edu/wpi/grip/core/operations/composite/ResizeOperation.java index c1d442c594..58f9d6bb39 100644 --- a/core/src/main/java/edu/wpi/grip/core/operations/composite/ResizeOperation.java +++ b/core/src/main/java/edu/wpi/grip/core/operations/composite/ResizeOperation.java @@ -1,17 +1,22 @@ package edu.wpi.grip.core.operations.composite; -import com.google.common.eventbus.EventBus; -import edu.wpi.grip.core.sockets.InputSocket; +import com.google.common.collect.ImmutableList; import edu.wpi.grip.core.Operation; +import edu.wpi.grip.core.OperationDescription; +import edu.wpi.grip.core.sockets.InputSocket; import edu.wpi.grip.core.sockets.OutputSocket; import edu.wpi.grip.core.sockets.SocketHints; +import edu.wpi.grip.core.util.Icon; -import java.io.InputStream; -import java.util.Optional; +import java.util.List; import static org.bytedeco.javacpp.opencv_core.Mat; import static org.bytedeco.javacpp.opencv_core.Size; -import static org.bytedeco.javacpp.opencv_imgproc.*; +import static org.bytedeco.javacpp.opencv_imgproc.INTER_AREA; +import static org.bytedeco.javacpp.opencv_imgproc.INTER_CUBIC; +import static org.bytedeco.javacpp.opencv_imgproc.INTER_LANCZOS4; +import static org.bytedeco.javacpp.opencv_imgproc.INTER_LINEAR; +import static org.bytedeco.javacpp.opencv_imgproc.INTER_NEAREST; import static org.bytedeco.javacpp.opencv_imgproc.resize; /** @@ -21,6 +26,30 @@ */ public class ResizeOperation implements Operation { + public static final OperationDescription DESCRIPTION = + OperationDescription.builder() + .name("Resize Image") + .summary("Scale an image to an exact size") + .category(OperationDescription.Category.IMAGE_PROCESSING) + .icon(Icon.iconStream("resize")) + .build(); + + private final InputSocket inputSocket; + private final InputSocket widthSocket; + private final InputSocket heightSocket; + private final InputSocket interpolationSocket; + + private final OutputSocket outputSocket; + + public ResizeOperation(InputSocket.Factory inputSocketFactory, OutputSocket.Factory outputSocketFactory) { + this.inputSocket = inputSocketFactory.create(SocketHints.Inputs.createMatSocketHint("Input", false)); + this.widthSocket = inputSocketFactory.create(SocketHints.Inputs.createNumberSpinnerSocketHint("Width", 640)); + this.heightSocket = inputSocketFactory.create(SocketHints.Inputs.createNumberSpinnerSocketHint("Height", 480)); + this.interpolationSocket = inputSocketFactory.create(SocketHints.createEnumSocketHint("Interpolation", Interpolation.CUBIC)); + + this.outputSocket = outputSocketFactory.create(SocketHints.Outputs.createMatSocketHint("Output")); + } + private enum Interpolation { NEAREST("None", INTER_NEAREST), LINEAR("Linear", INTER_LINEAR), @@ -43,51 +72,29 @@ public String toString() { } @Override - public String getName() { - return "Resize Image"; + public List getInputSockets() { + return ImmutableList.of( + inputSocket, + widthSocket, + heightSocket, + interpolationSocket + ); } @Override - public String getDescription() { - return "Scale an image to an exact size"; + public List getOutputSockets() { + return ImmutableList.of( + outputSocket + ); } @Override - public Category getCategory() { - return Category.IMAGE_PROCESSING; - } + public void perform() { + final Mat input = inputSocket.getValue().get(); + final Number width = widthSocket.getValue().get(); + final Number height = heightSocket.getValue().get(); + final Interpolation interpolation = interpolationSocket.getValue().get(); - @Override - public Optional getIcon() { - return Optional.of(getClass().getResourceAsStream("/edu/wpi/grip/ui/icons/resize.png")); - } - - @Override - public InputSocket[] createInputSockets(EventBus eventBus) { - return new InputSocket[]{ - new InputSocket<>(eventBus, SocketHints.Inputs.createMatSocketHint("Input", false)), - new InputSocket<>(eventBus, SocketHints.Inputs.createNumberSpinnerSocketHint("Width", 640)), - new InputSocket<>(eventBus, SocketHints.Inputs.createNumberSpinnerSocketHint("Height", 480)), - new InputSocket<>(eventBus, SocketHints.createEnumSocketHint("Interpolation", Interpolation.CUBIC)), - }; - } - - @Override - public OutputSocket[] createOutputSockets(EventBus eventBus) { - return new OutputSocket[]{ - new OutputSocket<>(eventBus, SocketHints.Outputs.createMatSocketHint("Output")), - }; - } - - @Override - @SuppressWarnings("unchecked") - public void perform(InputSocket[] inputs, OutputSocket[] outputs) { - final Mat input = ((InputSocket) inputs[0]).getValue().get(); - final Number width = ((InputSocket) inputs[1]).getValue().get(); - final Number height = ((InputSocket) inputs[2]).getValue().get(); - final Interpolation interpolation = ((InputSocket) inputs[3]).getValue().get(); - - final OutputSocket outputSocket = (OutputSocket) outputs[0]; final Mat output = outputSocket.getValue().get(); resize(input, output, new Size(width.intValue(), height.intValue()), 0.0, 0.0, interpolation.value); diff --git a/core/src/main/java/edu/wpi/grip/core/operations/composite/SwitchOperation.java b/core/src/main/java/edu/wpi/grip/core/operations/composite/SwitchOperation.java index d599e9f5e4..bbefb552f6 100644 --- a/core/src/main/java/edu/wpi/grip/core/operations/composite/SwitchOperation.java +++ b/core/src/main/java/edu/wpi/grip/core/operations/composite/SwitchOperation.java @@ -1,66 +1,72 @@ package edu.wpi.grip.core.operations.composite; -import com.google.common.eventbus.EventBus; -import edu.wpi.grip.core.*; -import edu.wpi.grip.core.sockets.*; +import com.google.common.collect.ImmutableList; +import edu.wpi.grip.core.Operation; +import edu.wpi.grip.core.OperationDescription; +import edu.wpi.grip.core.sockets.InputSocket; +import edu.wpi.grip.core.sockets.LinkedSocketHint; +import edu.wpi.grip.core.sockets.OutputSocket; +import edu.wpi.grip.core.sockets.Socket; +import edu.wpi.grip.core.sockets.SocketHint; +import edu.wpi.grip.core.sockets.SocketHints; + +import java.util.List; /** * Allows for switching between two arbitrary typed {@link Socket} using a * boolean {@link InputSocket} */ public class SwitchOperation implements Operation { - @Override - public String getName() { - return "Switch"; - } - @Override - public String getDescription() { - return "Switch between two possible input sockets using a boolean"; - } + public static final OperationDescription DESCRIPTION = + OperationDescription.builder() + .name("Switch") + .summary("Switch between two possible input sockets using a boolean") + .category(OperationDescription.Category.LOGICAL) + .build(); - @Override - public Category getCategory() { - return Category.LOGICAL; - } + private final InputSocket switcherSocket; + private final InputSocket inputSocket1; // Intentionally using raw types + private final InputSocket inputSocket2; - @Override - public SocketsProvider createSockets(EventBus eventBus) { - // This hint toggles the switch between using the true and false sockets + private final OutputSocket outputSocket; + + public SwitchOperation(InputSocket.Factory inputSocketFactory, OutputSocket.Factory outputSocketFactory) { final SocketHint switcherHint = SocketHints.createBooleanSocketHint("switch", true); + final LinkedSocketHint linkedSocketHint = new LinkedSocketHint(inputSocketFactory, outputSocketFactory); + + this.switcherSocket = inputSocketFactory.create(switcherHint); + this.inputSocket1 = linkedSocketHint.linkedInputSocket("If True"); + this.inputSocket2 = linkedSocketHint.linkedInputSocket("If False"); - final LinkedSocketHint linkedSocketHint = new LinkedSocketHint(eventBus); - final InputSocket[] inputs = new InputSocket[]{ - new InputSocket<>(eventBus, switcherHint), - linkedSocketHint.linkedInputSocket("If True"), - linkedSocketHint.linkedInputSocket("If False") - }; - final OutputSocket[] outputs = new OutputSocket[]{ - linkedSocketHint.linkedOutputSocket("Result") - }; - return new SocketsProvider(inputs, outputs); + this.outputSocket = linkedSocketHint.linkedOutputSocket("Result"); } @Override - public InputSocket[] createInputSockets(EventBus eventBus) { - throw new UnsupportedOperationException("This method should not be used"); + public List getInputSockets() { + return ImmutableList.of( + switcherSocket, + inputSocket1, + inputSocket2 + ); } @Override - public OutputSocket[] createOutputSockets(EventBus eventBus) { - throw new UnsupportedOperationException("This method should not be used"); + public List getOutputSockets() { + return ImmutableList.of( + outputSocket + ); } @Override @SuppressWarnings("unchecked") - public void perform(InputSocket[] inputs, OutputSocket[] outputs) { - final InputSocket switchHint = (InputSocket) inputs[0]; + public void perform() { // If the input is true pass one value through - if (switchHint.getValue().get()) { - outputs[0].setValueOptional(((InputSocket) inputs[1]).getValue()); + if (switcherSocket.getValue().get()) { + outputSocket.setValueOptional(inputSocket1.getValue()); } else { // Otherwise pass the other one through - outputs[0].setValueOptional(((InputSocket) inputs[2]).getValue()); + outputSocket.setValueOptional(inputSocket2.getValue()); } } } diff --git a/core/src/main/java/edu/wpi/grip/core/operations/composite/ThresholdMoving.java b/core/src/main/java/edu/wpi/grip/core/operations/composite/ThresholdMoving.java index 2eca25d5a1..08f1c871fd 100644 --- a/core/src/main/java/edu/wpi/grip/core/operations/composite/ThresholdMoving.java +++ b/core/src/main/java/edu/wpi/grip/core/operations/composite/ThresholdMoving.java @@ -1,7 +1,8 @@ package edu.wpi.grip.core.operations.composite; -import com.google.common.eventbus.EventBus; +import com.google.common.collect.ImmutableList; import edu.wpi.grip.core.Operation; +import edu.wpi.grip.core.OperationDescription; import edu.wpi.grip.core.sockets.InputSocket; import edu.wpi.grip.core.sockets.OutputSocket; import edu.wpi.grip.core.sockets.SocketHints; @@ -9,56 +10,43 @@ import org.bytedeco.javacpp.opencv_core.Mat; import org.bytedeco.javacpp.opencv_core.Size; -import java.util.Optional; - -import static edu.wpi.grip.core.Operation.Category.IMAGE_PROCESSING; +import java.util.List; /** * Finds the absolute difference between the current image and the previous image. */ public class ThresholdMoving implements Operation { - - - @Override - public String getName() { - return "Threshold Moving"; - } - - @Override - public String getDescription() { - return "Thresholds off parts of the image that have moved or changed between the previous and next image."; + public static final OperationDescription DESCRIPTION = OperationDescription + .builder().name("Threshold Moving") + .summary("Thresholds off parts of the image that have moved or changed between the previous and next image.") + .build(); + private final InputSocket imageSocket; + private final OutputSocket outputSocket; + private final Mat lastImage; + + public ThresholdMoving(InputSocket.Factory inputSocketFactory, OutputSocket.Factory outputSocketFactory) { + imageSocket = inputSocketFactory.create(SocketHints.Inputs.createMatSocketHint("image", false)); + outputSocket = outputSocketFactory.create(SocketHints.Outputs.createMatSocketHint("moved")); + lastImage = new Mat(); } @Override - public InputSocket[] createInputSockets(EventBus eventBus) { - return new InputSocket[]{ - new InputSocket<>(eventBus, SocketHints.Inputs.createMatSocketHint("image", false)) - }; + public List getInputSockets() { + return ImmutableList.of( + imageSocket + ); } @Override - public OutputSocket[] createOutputSockets(EventBus eventBus) { - return new OutputSocket[]{ - new OutputSocket<>(eventBus, SocketHints.Outputs.createMatSocketHint("moved")) - }; + public List getOutputSockets() { + return ImmutableList.of( + outputSocket + ); } @Override - public Category getCategory() { - return IMAGE_PROCESSING; - } - - @Override - public Optional createData() { - return Optional.of(new Mat()); - } - - @Override - public void perform(InputSocket[] inputs, OutputSocket[] outputs, Optional data) { - final Mat input = ((InputSocket) inputs[0]).getValue().get(); - final OutputSocket outputSocket = (OutputSocket) outputs[0]; - final Mat lastImage = (Mat) data.get(); - + public void perform() { + final Mat input = imageSocket.getValue().get(); final Size lastSize = lastImage.size(); final Size inputSize = input.size(); if (!lastImage.empty() && lastSize.height() == inputSize.height() && lastSize.width() == inputSize.width()) { diff --git a/core/src/main/java/edu/wpi/grip/core/operations/composite/ThresholdOperation.java b/core/src/main/java/edu/wpi/grip/core/operations/composite/ThresholdOperation.java index de35dc57ca..d53a718b37 100644 --- a/core/src/main/java/edu/wpi/grip/core/operations/composite/ThresholdOperation.java +++ b/core/src/main/java/edu/wpi/grip/core/operations/composite/ThresholdOperation.java @@ -4,25 +4,9 @@ import org.bytedeco.javacpp.opencv_core.Mat; import org.bytedeco.javacpp.opencv_core.Scalar; -import java.io.InputStream; -import java.util.Optional; +public abstract class ThresholdOperation> implements Operation { -public abstract class ThresholdOperation implements Operation { - - @Override - public Optional getIcon() { - return Optional.of(getClass().getResourceAsStream("/edu/wpi/grip/ui/icons/threshold.png")); - } - - @Override - public Category getCategory() { - return Category.IMAGE_PROCESSING; - } - - @Override - public Optional createData() { - return Optional.of(new Mat[]{new Mat(), new Mat(), new Mat()}); - } + protected Mat[] dataArray = {new Mat(), new Mat(), new Mat()}; /** * @param dataArray The array with the element that should be re-allocated diff --git a/core/src/main/java/edu/wpi/grip/core/operations/composite/ValveOperation.java b/core/src/main/java/edu/wpi/grip/core/operations/composite/ValveOperation.java index ec9ac9ad0f..2547ad5e9c 100644 --- a/core/src/main/java/edu/wpi/grip/core/operations/composite/ValveOperation.java +++ b/core/src/main/java/edu/wpi/grip/core/operations/composite/ValveOperation.java @@ -1,63 +1,64 @@ package edu.wpi.grip.core.operations.composite; -import com.google.common.eventbus.EventBus; -import edu.wpi.grip.core.*; -import edu.wpi.grip.core.sockets.*; +import com.google.common.collect.ImmutableList; +import edu.wpi.grip.core.Operation; +import edu.wpi.grip.core.OperationDescription; +import edu.wpi.grip.core.sockets.InputSocket; +import edu.wpi.grip.core.sockets.LinkedSocketHint; +import edu.wpi.grip.core.sockets.OutputSocket; +import edu.wpi.grip.core.sockets.SocketHint; +import edu.wpi.grip.core.sockets.SocketHints; +import java.util.List; import java.util.Optional; public class ValveOperation implements Operation { - @Override - public String getName() { - return "Valve"; - } - @Override - public String getDescription() { - return "Toggle an output socket on or off using a boolean"; - } + public static OperationDescription DESCRIPTION = + OperationDescription.builder() + .name("Valve") + .summary("Toggle an output socket on or off using a boolean") + .category(OperationDescription.Category.LOGICAL) + .build(); - @Override - public Category getCategory() { - return Category.LOGICAL; - } + private final InputSocket switcherSocket; + private final InputSocket inputSocket; // Intentionally using raw types - @Override - public SocketsProvider createSockets(EventBus eventBus) { - // This hint toggles the switch between using the true and false sockets + private final OutputSocket outputSocket; + + public ValveOperation(InputSocket.Factory inputSocketFactory, OutputSocket.Factory outputSocketFactory) { + final LinkedSocketHint linkedSocketHint = new LinkedSocketHint(inputSocketFactory, outputSocketFactory); final SocketHint switcherHint = SocketHints.createBooleanSocketHint("valve", true); - final LinkedSocketHint linkedSocketHint = new LinkedSocketHint(eventBus); - final InputSocket[] inputs = new InputSocket[]{ - new InputSocket<>(eventBus, switcherHint), - linkedSocketHint.linkedInputSocket("Input"), - }; - final OutputSocket[] outputs = new OutputSocket[]{ - linkedSocketHint.linkedOutputSocket("Output") - }; - return new SocketsProvider(inputs, outputs); + this.switcherSocket = inputSocketFactory.create(switcherHint); + this.inputSocket = linkedSocketHint.linkedInputSocket("Input"); + + this.outputSocket = linkedSocketHint.linkedOutputSocket("Output"); } @Override - public InputSocket[] createInputSockets(EventBus eventBus) { - throw new UnsupportedOperationException("This method should not be used"); + public List getInputSockets() { + return ImmutableList.of( + switcherSocket, + inputSocket + ); } @Override - public OutputSocket[] createOutputSockets(EventBus eventBus) { - throw new UnsupportedOperationException("This method should not be used"); + public List getOutputSockets() { + return ImmutableList.of( + outputSocket + ); } @Override - @SuppressWarnings("unchecked") - public void perform(InputSocket[] inputs, OutputSocket[] outputs) { - final InputSocket switchHint = (InputSocket) inputs[0]; + public void perform() { // If the input is true pass the value through - if (switchHint.getValue().get()) { - outputs[0].setValueOptional(((InputSocket) inputs[1]).getValue()); + if (switcherSocket.getValue().get()) { + outputSocket.setValueOptional(inputSocket.getValue()); } else { - outputs[0].setValueOptional((Optional) Optional.empty()); + outputSocket.setValueOptional(Optional.empty()); } } } diff --git a/core/src/main/java/edu/wpi/grip/core/operations/composite/WatershedOperation.java b/core/src/main/java/edu/wpi/grip/core/operations/composite/WatershedOperation.java index ac336ed3c2..ce4d12754f 100644 --- a/core/src/main/java/edu/wpi/grip/core/operations/composite/WatershedOperation.java +++ b/core/src/main/java/edu/wpi/grip/core/operations/composite/WatershedOperation.java @@ -1,13 +1,16 @@ package edu.wpi.grip.core.operations.composite; -import com.google.common.eventbus.EventBus; - -import edu.wpi.grip.core.sockets.InputSocket; +import com.google.common.collect.ImmutableList; import edu.wpi.grip.core.Operation; +import edu.wpi.grip.core.OperationDescription; +import edu.wpi.grip.core.sockets.InputSocket; import edu.wpi.grip.core.sockets.OutputSocket; import edu.wpi.grip.core.sockets.SocketHint; import edu.wpi.grip.core.sockets.SocketHints; +import edu.wpi.grip.core.util.Icon; + +import java.util.List; import static org.bytedeco.javacpp.opencv_core.*; import static org.bytedeco.javacpp.opencv_imgproc.*; @@ -18,6 +21,14 @@ */ public class WatershedOperation implements Operation { + public static final OperationDescription DESCRIPTION = + OperationDescription.builder() + .name("Watershed") + .summary("Isolates overlapping objects from the background and each other") + .category(OperationDescription.Category.FEATURE_DETECTION) + .icon(Icon.iconStream("opencv")) + .build(); + private final SocketHint srcHint = SocketHints.Inputs.createMatSocketHint("Input", false); private final SocketHint contoursHint = new SocketHint.Builder<>(ContoursReport.class) .identifier("Contours") @@ -26,42 +37,41 @@ public class WatershedOperation implements Operation { private final SocketHint outputHint = SocketHints.Inputs.createMatSocketHint("Output", true); - @Override - public String getName() { - return "Watershed"; - } + private final InputSocket srcSocket; + private final InputSocket contoursSocket; + private final OutputSocket outputSocket; - @Override - public String getDescription() { - return "Isolates overlapping objects from the background and each other"; + public WatershedOperation(InputSocket.Factory inputSocketFactory, OutputSocket.Factory outputSocketFactory) { + srcSocket = inputSocketFactory.create(srcHint); + contoursSocket = inputSocketFactory.create(contoursHint); + outputSocket = outputSocketFactory.create(outputHint); } @Override - public InputSocket[] createInputSockets(EventBus eventBus) { - return new InputSocket[]{ - new InputSocket<>(eventBus, srcHint), - new InputSocket<>(eventBus, contoursHint) - }; + public List getInputSockets() { + return ImmutableList.of( + srcSocket, + contoursSocket + ); } @Override - public OutputSocket[] createOutputSockets(EventBus eventBus) { - return new OutputSocket[]{ - new OutputSocket<>(eventBus, outputHint) - }; + public List getOutputSockets() { + return ImmutableList.of( + outputSocket + ); } @Override - public void perform(InputSocket[] inputs, OutputSocket[] outputs) { - final Mat input = (Mat) inputs[0].getValue().get(); + public void perform() { + final Mat input = srcSocket.getValue().get(); if (input.type() != CV_8UC3) { throw new IllegalArgumentException("Watershed only works on 8-bit, 3-channel images"); } - final ContoursReport contourReport = (ContoursReport) inputs[1].getValue().get(); + final ContoursReport contourReport = contoursSocket.getValue().get(); final MatVector contours = contourReport.getContours(); - final OutputSocket outputSocket = (OutputSocket) outputs[0]; final Mat markers = new Mat(input.size(), CV_32SC1, new Scalar(0.0)); final Mat output = new Mat(markers.size(), CV_8UC1, new Scalar(0.0)); diff --git a/core/src/main/java/edu/wpi/grip/core/operations/network/NetworkPublishOperation.java b/core/src/main/java/edu/wpi/grip/core/operations/network/NetworkPublishOperation.java new file mode 100644 index 0000000000..f7e1976970 --- /dev/null +++ b/core/src/main/java/edu/wpi/grip/core/operations/network/NetworkPublishOperation.java @@ -0,0 +1,78 @@ +package edu.wpi.grip.core.operations.network; + +import com.google.common.collect.ImmutableList; +import com.google.common.reflect.TypeToken; +import edu.wpi.grip.core.Operation; +import edu.wpi.grip.core.OperationDescription; +import edu.wpi.grip.core.sockets.InputSocket; +import edu.wpi.grip.core.sockets.OutputSocket; +import edu.wpi.grip.core.sockets.SocketHint; +import edu.wpi.grip.core.sockets.SocketHints; +import edu.wpi.grip.core.util.Icon; + +import java.util.List; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Abstract superclass for operations that publish data. + */ +public abstract class NetworkPublishOperation implements Operation { + + /** + * OperationDescription builder that has the icon default to "publish" and the category to "NETWORK". + */ + protected static final OperationDescription.Builder defaultBuilder = + OperationDescription.builder() + .icon(Icon.iconStream("publish")) + .category(OperationDescription.Category.NETWORK); + + + protected final Class dataType; + + private final SocketHint dataHint = + new SocketHint.Builder<>((Class)new TypeToken(getClass()){}.getRawType()) + .identifier("Data") + .build(); + private final SocketHint nameHint = SocketHints.Inputs.createTextSocketHint("Name", ""); + + protected final InputSocket dataSocket; + protected final InputSocket nameSocket; + + protected NetworkPublishOperation(InputSocket.Factory isf, Class dataType) { + checkNotNull(isf); + checkNotNull(dataType); + this.dataType = dataType; + this.dataSocket = isf.create(dataHint); + this.nameSocket = isf.create(nameHint); + } + + @Override + public List getInputSockets() { + return ImmutableList.builder() + .add(dataSocket) + .add(nameSocket) + .addAll(createFlagSockets()) + .build(); + } + + /** + * Creates a list of input sockets that control which items to publish. + */ + protected abstract List> createFlagSockets(); + + @Override + public List getOutputSockets() { + return ImmutableList.of(); + } + + /** + * Publishes the data. + */ + protected abstract void doPublish(); + + @Override + public void perform() { + doPublish(); + } +} diff --git a/core/src/main/java/edu/wpi/grip/core/operations/network/NetworkPublisher.java b/core/src/main/java/edu/wpi/grip/core/operations/network/NetworkPublisher.java index 9b630d3d06..c681ca87a0 100644 --- a/core/src/main/java/edu/wpi/grip/core/operations/network/NetworkPublisher.java +++ b/core/src/main/java/edu/wpi/grip/core/operations/network/NetworkPublisher.java @@ -30,7 +30,7 @@ protected NetworkPublisher() { * * @param name The new/existing name for the publisher. */ - final void setName(String name) { + public final void setName(String name) { checkArgument(!name.isEmpty(), "Name cannot be an empty string"); if (this.name.equals(Optional.of(name))) { // The old name and the new name are the same diff --git a/core/src/main/java/edu/wpi/grip/core/operations/network/PublishAnnotatedOperation.java b/core/src/main/java/edu/wpi/grip/core/operations/network/PublishAnnotatedOperation.java index 78672e5409..b6334745b4 100644 --- a/core/src/main/java/edu/wpi/grip/core/operations/network/PublishAnnotatedOperation.java +++ b/core/src/main/java/edu/wpi/grip/core/operations/network/PublishAnnotatedOperation.java @@ -1,189 +1,159 @@ package edu.wpi.grip.core.operations.network; -import com.google.common.base.Throwables; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Maps; -import com.google.common.collect.Ordering; -import com.google.common.eventbus.EventBus; -import com.google.common.reflect.Invokable; -import com.google.common.reflect.TypeToken; import edu.wpi.grip.core.sockets.InputSocket; import edu.wpi.grip.core.sockets.SocketHints; +import org.apache.commons.lang3.tuple.Pair; import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.lang.reflect.Modifier; -import java.util.*; +import java.util.Comparator; +import java.util.List; +import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; +import java.util.stream.Stream; import static com.google.common.base.Preconditions.checkNotNull; /** * Publishes data to a specific network protocol. - * This class does not actually perform the publishing, that is the role of the {@link NetworkPublisher}. - * Its exclusive responsibility is resolving the reflection required to get the value that should be published. - * - * @param The socketType of socket that can be connected to this step - * @param A class implementing {@link Publishable} that determines what data is Published - * @param

The type that each function in the {@link Publishable} should return + *

+ * This looks at {@link PublishValue} annotations on accessor methods in a class to generate the data to publish. */ -public abstract class PublishAnnotatedOperation extends PublishOperation> { - private final MapNetworkPublisherFactory factory; - private final TypeToken socketType; - private final TypeToken

publishType; - private final Function converter; - private final ImmutableList> valueMethods; - private final ImmutableSet keys; - - /** - * Create a new publish operation for a socket socketType that implements {@link Publishable} directly - */ - @SuppressWarnings("unchecked") - public PublishAnnotatedOperation(MapNetworkPublisherFactory factory) { - this(factory, value -> (T) value); - } - - /** - * Create a new publish operation where the socket socketType and Publishable socketType are different. This is useful for - * classes that we don't create, such as JavaCV's {@link org.bytedeco.javacpp.opencv_core.Size} class, since we - * can't make them implement additional interfaces. - * - * @param converter A function to convert socket values into publishable values - */ - public PublishAnnotatedOperation(MapNetworkPublisherFactory factory, Function converter) { - this.factory = checkNotNull(factory, "MapNetworkPublisherFactory was null"); - this.socketType = new TypeToken(getClass()) { - }; - this.publishType = new TypeToken

(getClass()) { - }; - final TypeToken reportType = new TypeToken(getClass()) { - }; - this.converter = checkNotNull(converter, "Converter was null"); - - final Comparator> byWeight = Comparator.comparing(method -> method.getAnnotation(PublishValue.class).weight()); - - // Any accessor method with an @PublishValue annotation can be published to NetworkTables. We sort them by their - // "weights" in order to avoid the issue of different JVM versions returning methods in a different order. - this.valueMethods = ImmutableList.copyOf(Arrays.asList(reportType.getRawType().getDeclaredMethods()).stream() - .filter(method -> method.getAnnotation(PublishValue.class) != null) - .map(m -> reportType.method(m).returning(publishType.unwrap())) - .sorted(byWeight) - .iterator()); - if (this.valueMethods.size() == 0) { - throw new IllegalArgumentException("Must be at least one @PublishValue method on " + reportType + " to be published"); +public abstract class PublishAnnotatedOperation extends NetworkPublishOperation { + + private final InputSocket.Factory isf; + private final Class

publishType; + private final Function converter; + private final MapNetworkPublisher publisher; + + protected PublishAnnotatedOperation(InputSocket.Factory isf, + Class dataType, + Class

publishType, + Function converter, + MapNetworkPublisherFactory publisherFactory) { + super(isf, dataType); + checkNotNull(publishType); + checkNotNull(converter); + checkNotNull(publisherFactory); + + if (!Modifier.isPublic(publishType.getModifiers())) { + throw new IllegalAccessError("Cannot access methods in non-public Publishable class"); } - if (!Modifier.isPublic(reportType.getRawType().getModifiers())) { - throw new IllegalAccessError(reportType.getRawType().getName() + " must be public"); + if (!Modifier.isStatic(publishType.getModifiers()) && publishType.isMemberClass()) { + throw new IllegalAccessError("Cannot access methods in non-static inner class"); } - if (reportType.getRawType().getEnclosingClass() != null && !Modifier.isStatic(reportType.getRawType().getModifiers())) { - throw new IllegalAccessError(reportType.getRawType().getName() + " must be static if declared as an inner class"); - } + this.isf = isf; + this.publishType = publishType; + this.converter = converter; + this.publisher = publisherFactory.create(valueMethodStream() + .map(m -> m.getAnnotation(PublishValue.class).key()) + .filter(k -> !k.isEmpty()) + .collect(Collectors.toSet())); - // In order for PublishAnnotatedOperation to call the accessor methods, they must all be public - this.valueMethods.stream() - .filter(m -> !m.isPublic()) + // Make sure there's at least one method to call + valueMethodStream() .findAny() - .ifPresent(m -> { - throw new IllegalAccessError("@PublishValue method must be public: " + m); + .orElseThrow(() -> new IllegalArgumentException("A Publishable type must have at least one method annotated with @PublishValue")); + + // Make sure keys and weights are distinct + valueMethodStream() + .map(m -> m.getAnnotation(PublishValue.class)) + .filter(a -> valueMethodStream() + .map(m -> m.getAnnotation(PublishValue.class)) + .anyMatch(a0 -> !a.equals(a0) && (a.key().equals(a0.key()) || a.weight() == a0.weight()))) + .findAny() + .ifPresent(x -> { + throw new IllegalArgumentException("Keys and weights must be distinct"); }); + // Make sure all methods are non-static + valueMethodStream() + .filter(m -> Modifier.isStatic(m.getModifiers())) + .findAny() + .ifPresent(x -> { + throw new IllegalArgumentException("Methods annotated with @PublishValue must be non-static"); + }); - // In order for PublishAnnotatedOperation to call the accessor methods, they must all have no parameters - this.valueMethods.stream() - .filter(method -> method.getParameters().size() > 0) + // Make sure all methods are public + valueMethodStream() + .filter(m -> !Modifier.isPublic(m.getModifiers())) .findAny() - .ifPresent(method -> { - throw new IllegalArgumentException("@PublishValue method must have 0 parameters: " + method); + .ifPresent(x -> { + throw new IllegalArgumentException("Methods annotated with @PublishValue must be public"); }); - final long uniqueKeyCount = this.valueMethods - .stream().map(method -> method.getAnnotation(PublishValue.class).key()).distinct().count(); - if (uniqueKeyCount != this.valueMethods.size()) { - throw new IllegalArgumentException("@PublishValue methods must have distinct keys: " + reportType); - } - this.keys = ImmutableSet - .copyOf(this.valueMethods - .stream() - .map(method -> method.getAnnotation(PublishValue.class).key()) - .filter(k -> !k.isEmpty()) // Check that the key is not empty - .iterator()); - - // Count the number of method annotated without a key - final long count = this.valueMethods.stream() - .filter(method -> method.getAnnotation(PublishValue.class).key().isEmpty()) - .count(); - // If there is more than one method without a key - // or, if there is one method without a key, it must be the only method - if (count > 1 || (count != 0 && this.valueMethods.size() > 1)) { - throw new IllegalArgumentException("If there is more than one @PublishValue method all need keys: " + reportType); - } + // Make sure annotated methods don't take parameters + valueMethodStream() + .filter(m -> m.getParameterCount() > 0) + .findAny() + .ifPresent(x -> { + throw new IllegalArgumentException("Methods annotated with @PublishValue cannot take parameters"); + }); - // The weight thing doesn't help us if two methods have the same weight, since the JVM could put them in either - // order. - if (!Ordering.from(byWeight).isStrictlyOrdered(this.valueMethods)) { - throw new IllegalArgumentException("@PublishValue methods must have distinct weights: " + reportType); + // Make sure all methods have keys + if (valueMethodStream() + .map(m -> m.getAnnotation(PublishValue.class)) + .filter(a -> a.key().isEmpty()) + .count() > 0 + && + valueMethodStream() + .map(m -> m.getAnnotation(PublishValue.class)) + .filter(a -> !a.key().isEmpty()) + .count() > 0) { + throw new IllegalArgumentException("If a method has no key, it can be the only one annotated with @PublishValue in the class"); } } - protected final TypeToken getSocketType() { - return socketType; + /** + * Gets a stream of all valid methods annotated with {@link PublishValue} in the class of the data to publish. + * The methods are sorted by weight. + */ + protected Stream valueMethodStream() { + return Stream.of(publishType.getMethods()) + .filter(m -> m.isAnnotationPresent(PublishValue.class)) + .sorted(Comparator.comparing(m -> m.getAnnotation(PublishValue.class).weight())); } @Override - public List> provideRemainingInputSockets(EventBus eventBus) { - // Create a checkbox for every property of the object that might be published. For example, for a - // ContourReport, the user might wish to publish the x and y coordinates of the center of each contour. - return valueMethods - .stream() - .map(method -> new InputSocket<>(eventBus, - SocketHints.createBooleanSocketHint("Publish " + method.getAnnotation(PublishValue.class).key(), true))) + protected List> createFlagSockets() { + return valueMethodStream() + .map(m -> m.getAnnotation(PublishValue.class).key()) + .map(name -> SocketHints.createBooleanSocketHint("Publish " + name, true)) + .map(isf::create) .collect(Collectors.toList()); } @Override - public MapNetworkPublisher

createPublisher() { - return factory.create(keys); + protected void doPublish() { + publisher.setName(nameSocket.getValue().get()); + D data = dataSocket.getValue().get(); + Map dataMap = valueMethodStream() + .map(m -> Pair.of(m.getAnnotation(PublishValue.class).key(), get(m, converter.apply(data)))) + .collect(Collectors.toMap(Pair::getLeft, Pair::getRight)); + publisher.publish(dataMap); } - @Override - protected void performPublish(S socketValue, MapNetworkPublisher

publisher, List> restOfInputSockets) { - final T value = converter.apply(socketValue); - // For each PublishValue method in the object being published, put it in the table if the the corresponding - // checkbox is selected. - final Map publishMap = Maps.newHashMapWithExpectedSize(valueMethods.size()); - try { - int i = 0; - for (final Invokable method : valueMethods) { - final String key = method.getAnnotation(PublishValue.class).key(); - final boolean publish = (Boolean) restOfInputSockets.get(i++).getValue().get(); - - if (publish) { - publishMap.put(key, method.invoke(value)); - } - } - } catch (IllegalAccessException | InvocationTargetException e) { - Throwables.propagate(e); - } - - publisher.publish(publishMap); + public Class getSocketType() { + return dataType; } /** - * @return The network protocol's acronym (eg. ROS for Robot Operating System) - */ - protected abstract String getNetworkProtocolNameAcronym(); - - /** - * @return The network protocol's name (eg. Robot Operating System) - */ - protected abstract String getNetworkProtocolName(); - - /** - * @return The hint to indicate what you will be publishing to (eg. ROS to a Topic, Network Tables to a Table) + * Helper method for invoking an accessor method on an object. + * + * @param accessor the accessor method to invoke + * @param instance the object to invoke the accessor on + * @return the value returned by the accessor method, or {@code null} if the method could not be invoked. */ - protected abstract String getSocketHintStringPrompt(); + protected Object get(Method accessor, Object instance) { + try { + return accessor.invoke(instance); + } catch (IllegalAccessException | InvocationTargetException e) { + return null; + } + } } diff --git a/core/src/main/java/edu/wpi/grip/core/operations/network/PublishOperation.java b/core/src/main/java/edu/wpi/grip/core/operations/network/PublishOperation.java deleted file mode 100644 index 1174ad8493..0000000000 --- a/core/src/main/java/edu/wpi/grip/core/operations/network/PublishOperation.java +++ /dev/null @@ -1,141 +0,0 @@ -package edu.wpi.grip.core.operations.network; - - -import com.google.common.eventbus.EventBus; -import com.google.common.reflect.TypeToken; -import edu.wpi.grip.core.*; -import edu.wpi.grip.core.sockets.InputSocket; -import edu.wpi.grip.core.sockets.OutputSocket; -import edu.wpi.grip.core.sockets.SocketHint; -import edu.wpi.grip.core.sockets.SocketHints; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Optional; - -/** - * - * @param The type of the socket that will be published - * @param

The type of the publisher that will be used to publish values - */ -public abstract class PublishOperation implements Operation { - private final TypeToken socketType; - - protected PublishOperation() { - this.socketType = new TypeToken(getClass()) { - }; - } - - @Override - public final String getName() { - return getNetworkProtocolNameAcronym() + "Publish " + socketType.getRawType().getSimpleName(); - } - - @Override - public final String getDescription() { - return "Publish a " + socketType.getRawType().getSimpleName() + " to " + getNetworkProtocolName(); - } - - - @Override - public final InputSocket[] createInputSockets(EventBus eventBus) { - final List> customSockets = provideRemainingInputSockets(eventBus); - final InputSocket[] sockets = new InputSocket[2 + customSockets.size()]; - int i = 0; - // Create an input for the actual object being published - sockets[i++] = new InputSocket<>(eventBus, - new SocketHint.Builder<>(socketType.getRawType()).identifier("Value").build()); - - // Create a string input for the key used by the network protocol - sockets[i++] = new InputSocket<>(eventBus, - SocketHints.Inputs.createTextSocketHint(getSocketHintStringPrompt(), "my" + socketType.getRawType().getSimpleName())); - for (InputSocket socket : customSockets) { - sockets[i++] = socket; - } - return sockets; - } - - @Override - public final OutputSocket[] createOutputSockets(EventBus eventBus) { - return new OutputSocket[0]; - } - - /** - * There should be a different instance of {@link NetworkPublisher} for each step. - * The NetworkPublisher will be closed by the close function when this step is removed. - * - * @return The publisher that will be used for this step. - */ - @Override - public final Optional createData() { - return Optional.of(createPublisher()); - } - - @Override - @SuppressWarnings("unchecked") - public final void perform(InputSocket[] inputs, OutputSocket[] outputs, Optional data) { - // Get the socket value that should be published - final S socketValue = (S) socketType.getRawType().cast(inputs[0].getValue().get()); - // Get the subfield - final String subField = (String) inputs[1].getValue().get(); - - if (subField.isEmpty()) { - throw new IllegalArgumentException("Need key to publish to " + getNetworkProtocolName()); - } - - // The publisher that the data will be published with - final P publisher = (P) data.get(); - final List> remainingSockets = inputs.length > 2 ? Arrays.asList(inputs).subList(2, inputs.length) : Collections.emptyList(); - publisher.setName(subField); - performPublish(socketValue, publisher, remainingSockets); - } - - /** - * Performs the publish action. Provides the implementer with the resolved type of the socket being published. - * @param socketValue The resolved socket value that has been provided as an input. - * @param publisher The publisher to be used to publish the socket value - * @param restOfInputSockets The remainder of the input sockets that were provided by {@link #provideRemainingInputSockets} - */ - protected abstract void performPublish(S socketValue, P publisher, List> restOfInputSockets); - - @Override - public final void cleanUp(InputSocket[] inputs, OutputSocket[] outputs, Optional data) { - final NetworkPublisher networkPublisher = (NetworkPublisher) data.get(); - networkPublisher.close(); - } - - @Override - public final Category getCategory() { - return Category.NETWORK; - } - - /** - * Creates a new publisher to be used for publishing data to the network service - * - * @return The publisher to be used. - */ - protected abstract P createPublisher(); - - /** - * Provide any additional input sockets to be used in addition to the default ones - */ - protected List> provideRemainingInputSockets(EventBus eventBus) { - return Collections.emptyList(); - } - - /** - * @return The network protocol's acronym (eg. ROS for Robot Operating System) - */ - protected abstract String getNetworkProtocolNameAcronym(); - - /** - * @return The network protocol's name (eg. Robot Operating System) - */ - protected abstract String getNetworkProtocolName(); - - /** - * @return The hint to indicate what you will be publishing to (eg. ROS to a Topic, Network Tables to a Table) - */ - protected abstract String getSocketHintStringPrompt(); -} diff --git a/core/src/main/java/edu/wpi/grip/core/operations/network/networktables/NTPublishAnnotatedOperation.java b/core/src/main/java/edu/wpi/grip/core/operations/network/networktables/NTPublishAnnotatedOperation.java index fd12d49071..89f3a52d5f 100644 --- a/core/src/main/java/edu/wpi/grip/core/operations/network/networktables/NTPublishAnnotatedOperation.java +++ b/core/src/main/java/edu/wpi/grip/core/operations/network/networktables/NTPublishAnnotatedOperation.java @@ -1,62 +1,76 @@ package edu.wpi.grip.core.operations.network.networktables; -import com.google.common.collect.ImmutableSet; -import edu.wpi.grip.core.operations.network.*; +import edu.wpi.grip.core.OperationDescription; +import edu.wpi.grip.core.operations.network.MapNetworkPublisherFactory; +import edu.wpi.grip.core.operations.network.PublishAnnotatedOperation; +import edu.wpi.grip.core.operations.network.PublishValue; +import edu.wpi.grip.core.operations.network.Publishable; +import edu.wpi.grip.core.sockets.InputSocket; +import edu.wpi.grip.core.util.Icon; -import java.io.InputStream; -import java.util.Optional; import java.util.function.Function; +import static com.google.common.base.Preconditions.checkNotNull; + /** * An operation that publishes any type that implements {@link Publishable} to NetworkTables. *

* To be publishable, a type should have one or more accessor methods annotated with {@link PublishValue}. This is done * with annotations instead of methods + * + * @param the type of the data to publish (e.g. {@link Number}) + * @param

the publishable analog of the data (e.g. {@link edu.wpi.grip.core.operations.network.NumberPublishable NumberPublishable}) */ -public abstract class NTPublishAnnotatedOperation extends PublishAnnotatedOperation { +public class NTPublishAnnotatedOperation extends PublishAnnotatedOperation { /** - * Create a new publish operation for a socket type that implements {@link Publishable} directly + * Creates an {@code OperationDescription} for an {@link NTPublishAnnotatedOperation} that publishes data of the given type. + * + * @param dataType the type of the data to publish */ - @SuppressWarnings("unchecked") - protected NTPublishAnnotatedOperation(MapNetworkPublisherFactory factory) { - super(factory); + public static OperationDescription descriptionFor(Class dataType) { + checkNotNull(dataType); + final String name = dataType.getSimpleName(); + return OperationDescription.builder() + .name(String.format("NTPublish %s", name)) + .summary(String.format("Publishes a %s to a network table", name)) + .aliases(String.format("Publish %s", name)) + .icon(Icon.iconStream("first")) + .category(OperationDescription.Category.NETWORK) + .build(); } + /** - * Create a new publish operation where the socket type and Publishable type are different. This is useful for - * classes that we don't create, such as JavaCV's {@link org.bytedeco.javacpp.opencv_core.Size} class, since we - * can't make them implement additional interfaces. + * Creates an NTPublishAnnotatedOperation for a type that already implements {@link Publishable} (e.g. + * {@link edu.wpi.grip.core.operations.composite.ContoursReport ContoursReport}). * - * @param converter A function to convert socket values into publishable values + * @param inputSocketFactory factory for creating the input sockets + * @param dataType the type of the data to publish + * @param publisherFactory factory for creating the publisher */ - protected NTPublishAnnotatedOperation(MapNetworkPublisherFactory factory, Function converter) { - super(factory, converter); - } - - @Override - public ImmutableSet getAliases() { - return ImmutableSet.of("Publish " + getSocketType().getRawType().getSimpleName()); - } - - @Override - public String getNetworkProtocolNameAcronym() { - return "NT"; + public NTPublishAnnotatedOperation(InputSocket.Factory inputSocketFactory, + Class

dataType, + MapNetworkPublisherFactory publisherFactory) { + this(inputSocketFactory, (Class) dataType, dataType, d -> (P) d, publisherFactory); } - @Override - public String getNetworkProtocolName() { - return "NetworkTables"; - } - - @Override - protected String getSocketHintStringPrompt() { - return "Subtable Name"; - } - - @Override - public Optional getIcon() { - return Optional.of(getClass().getResourceAsStream("/edu/wpi/grip/ui/icons/first.png")); + /** + * Creates an NTPublishAnnotatedOperation. + * + * @param inputSocketFactory factory for creating the input sockets + * @param dataType the type of the data to publish + * @param publishType the publishable analog of the data type + * @param converter function for converting instances of the input type to publishable objects + * @param publisherFactory factory for creating the publisher + */ + public NTPublishAnnotatedOperation(InputSocket.Factory inputSocketFactory, + Class dataType, + Class

publishType, + Function converter, + MapNetworkPublisherFactory publisherFactory) { + super(inputSocketFactory, dataType, publishType, converter, publisherFactory); + super.nameSocket.setValue("my" + dataType.getSimpleName()); } } diff --git a/core/src/main/java/edu/wpi/grip/core/operations/network/ros/ROSPublishOperation.java b/core/src/main/java/edu/wpi/grip/core/operations/network/ros/ROSPublishOperation.java index 8e5da5ac83..3d377ede10 100644 --- a/core/src/main/java/edu/wpi/grip/core/operations/network/ros/ROSPublishOperation.java +++ b/core/src/main/java/edu/wpi/grip/core/operations/network/ros/ROSPublishOperation.java @@ -1,57 +1,48 @@ package edu.wpi.grip.core.operations.network.ros; +import edu.wpi.grip.core.OperationDescription; +import edu.wpi.grip.core.operations.network.NetworkPublishOperation; import edu.wpi.grip.core.sockets.InputSocket; -import edu.wpi.grip.core.operations.network.PublishOperation; +import edu.wpi.grip.core.util.Icon; -import java.io.InputStream; +import java.util.Collections; import java.util.List; -import java.util.Optional; /** * An operation that can publish a type to ROS using the java to message converter - * @param The type of the socket that is taken as an input to be published + * @param The type of the socket that is taken as an input to be published */ -public abstract class ROSPublishOperation extends PublishOperation { - private final ROSNetworkPublisherFactory rosNetworkPublisherFactory; - private final JavaToMessageConverter converter; - - /* - * Protected so type resolution can happen - */ - protected ROSPublishOperation(ROSNetworkPublisherFactory rosNetworkPublisherFactory, JavaToMessageConverter converter) { - super(); - this.rosNetworkPublisherFactory = rosNetworkPublisherFactory; - this.converter = converter; +public class ROSPublishOperation extends NetworkPublishOperation { + + public static OperationDescription descriptionFor(Class dataType) { + return OperationDescription.builder() + .name("ROSPublish " + dataType.getSimpleName()) + .summary("Publishes a " + dataType.getSimpleName() + " to a ROS node") + .icon(Icon.iconStream("rosorg-logo")) + .category(OperationDescription.Category.NETWORK) + .build(); } - @Override - protected final String getNetworkProtocolNameAcronym() { - return "ROS"; - } + private final JavaToMessageConverter converter; + private final ROSMessagePublisher publisher; - @Override - protected final String getNetworkProtocolName() { - return "Robot Operating System"; - } - @Override - protected final String getSocketHintStringPrompt() { - return "Topic"; + public ROSPublishOperation(InputSocket.Factory inputSocketFactory, Class dataType, ROSNetworkPublisherFactory rosNetworkPublisherFactory, JavaToMessageConverter converter) { + super(inputSocketFactory, dataType); + this.converter = converter; + this.publisher = rosNetworkPublisherFactory.create(converter); } @Override - protected ROSMessagePublisher createPublisher() { - return rosNetworkPublisherFactory.create(converter); + protected List> createFlagSockets() { + return Collections.emptyList(); } @Override - protected final void performPublish(S socketValue, ROSMessagePublisher publisher, List> restOfInputSockets) { - publisher.publish((message, mFactory) -> converter.convert(socketValue, message, mFactory)); + @SuppressWarnings("OptionalGetWithoutIsPresent") + protected void doPublish() { + publisher.publish((m, f) -> converter.convert(dataSocket.getValue().get(), m, f)); } - @Override - public Optional getIcon() { - return Optional.of(getClass().getResourceAsStream("/edu/wpi/grip/ui/icons/rosorg-logo.png")); - } } diff --git a/core/src/main/java/edu/wpi/grip/core/operations/opencv/CVOperation.java b/core/src/main/java/edu/wpi/grip/core/operations/opencv/CVOperation.java index 0a6330b5e4..433f650a94 100644 --- a/core/src/main/java/edu/wpi/grip/core/operations/opencv/CVOperation.java +++ b/core/src/main/java/edu/wpi/grip/core/operations/opencv/CVOperation.java @@ -1,16 +1,18 @@ package edu.wpi.grip.core.operations.opencv; import edu.wpi.grip.core.Operation; - -import java.io.InputStream; -import java.util.Optional; +import edu.wpi.grip.core.OperationDescription; +import edu.wpi.grip.core.util.Icon; public interface CVOperation extends Operation { - @Override - default Optional getIcon(){ - return Optional.of( - getClass().getResourceAsStream("/edu/wpi/grip/ui/icons/opencv.png") - ); + static OperationDescription.Builder defaultBuilder() { + return OperationDescription.builder() + .category(OperationDescription.Category.OPENCV) + .icon(Icon.iconStream("opencv")); + } + + static OperationDescription defaults(String name, String description) { + return defaultBuilder().name(name).summary(description).build(); } } diff --git a/core/src/main/java/edu/wpi/grip/core/operations/opencv/MatFieldAccessor.java b/core/src/main/java/edu/wpi/grip/core/operations/opencv/MatFieldAccessor.java index 1aa1890e34..262f65d8a1 100644 --- a/core/src/main/java/edu/wpi/grip/core/operations/opencv/MatFieldAccessor.java +++ b/core/src/main/java/edu/wpi/grip/core/operations/opencv/MatFieldAccessor.java @@ -1,6 +1,7 @@ package edu.wpi.grip.core.operations.opencv; -import com.google.common.eventbus.EventBus; +import com.google.common.collect.ImmutableList; +import edu.wpi.grip.core.OperationDescription; import edu.wpi.grip.core.sockets.InputSocket; import edu.wpi.grip.core.sockets.OutputSocket; import edu.wpi.grip.core.sockets.SocketHint; @@ -8,56 +9,71 @@ import org.bytedeco.javacpp.opencv_core.Mat; import org.bytedeco.javacpp.opencv_core.Size; +import java.util.List; + public class MatFieldAccessor implements CVOperation { + + public static final OperationDescription DESCRIPTION = + CVOperation.defaultBuilder() + .name("Get Mat Info") + .summary("Provide access to the various elements and properties of an image.") + .build(); + private static final Mat defaultsMat = new Mat(); - private final SocketHint matHint = SocketHints.Inputs.createMatSocketHint("Input", false); - private final SocketHint sizeHint = SocketHints.Inputs.createSizeSocketHint("size", true); - private final SocketHint emptyHint = SocketHints.Outputs.createBooleanSocketHint("empty", defaultsMat.empty()); - private final SocketHint channelsHint = SocketHints.Outputs.createNumberSocketHint("channels", defaultsMat.channels()); - private final SocketHint colsHint = SocketHints.Outputs.createNumberSocketHint("cols", defaultsMat.rows()); - private final SocketHint rowsHint = SocketHints.Outputs.createNumberSocketHint("rows", defaultsMat.rows()); - private final SocketHint highValueHint = SocketHints.Outputs.createNumberSocketHint("high value", defaultsMat.highValue()); + private final SocketHint matHint = SocketHints.Inputs.createMatSocketHint("Input", false); + private final SocketHint sizeHint = SocketHints.Inputs.createSizeSocketHint("size", true); + private final SocketHint emptyHint = SocketHints.Outputs.createBooleanSocketHint("empty", defaultsMat.empty()); + private final SocketHint channelsHint = SocketHints.Outputs.createNumberSocketHint("channels", defaultsMat.channels()); + private final SocketHint colsHint = SocketHints.Outputs.createNumberSocketHint("cols", defaultsMat.rows()); + private final SocketHint rowsHint = SocketHints.Outputs.createNumberSocketHint("rows", defaultsMat.rows()); + private final SocketHint highValueHint = SocketHints.Outputs.createNumberSocketHint("high value", defaultsMat.highValue()); - @Override - public String getName() { - return "Get Mat Info"; - } + private final InputSocket inputSocket; - @Override - public String getDescription() { - return "Provide access to the various elements and properties of an image."; + private final OutputSocket sizeSocket; + private final OutputSocket emptySocket; + private final OutputSocket channelsSocket; + private final OutputSocket colsSocket; + private final OutputSocket rowsSocket; + private final OutputSocket highValueSocket; + + public MatFieldAccessor(InputSocket.Factory inputSocketFactory, OutputSocket.Factory outputSocketFactory) { + this.inputSocket = inputSocketFactory.create(matHint); + + this.sizeSocket = outputSocketFactory.create(sizeHint); + this.emptySocket = outputSocketFactory.create(emptyHint); + this.channelsSocket = outputSocketFactory.create(channelsHint); + this.colsSocket = outputSocketFactory.create(colsHint); + this.rowsSocket = outputSocketFactory.create(rowsHint); + this.highValueSocket = outputSocketFactory.create(highValueHint); } @Override - public InputSocket[] createInputSockets(EventBus eventBus) { - return new InputSocket[]{new InputSocket(eventBus, matHint)}; + public List getInputSockets() { + return ImmutableList.of( + inputSocket + ); } @Override - public OutputSocket[] createOutputSockets(EventBus eventBus) { - return new OutputSocket[]{ - new OutputSocket(eventBus, sizeHint), - new OutputSocket(eventBus, emptyHint), - new OutputSocket(eventBus, channelsHint), - new OutputSocket(eventBus, colsHint), - new OutputSocket(eventBus, rowsHint), - new OutputSocket(eventBus, highValueHint) - }; + public List getOutputSockets() { + return ImmutableList.of( + sizeSocket, + emptySocket, + channelsSocket, + colsSocket, + rowsSocket, + highValueSocket + ); } @Override - public void perform(InputSocket[] inputs, OutputSocket[] outputs) { - final Mat inputMat = (Mat) inputs[0].getValue().get(); - final OutputSocket sizeSocket = (OutputSocket) outputs[0]; - final OutputSocket isEmptySocket = (OutputSocket) outputs[1]; - final OutputSocket channelsSocket = (OutputSocket) outputs[2]; - final OutputSocket colsSocket = (OutputSocket) outputs[3]; - final OutputSocket rowsSocket = (OutputSocket) outputs[4]; - final OutputSocket highValueSocket = (OutputSocket) outputs[5]; + public void perform() { + final Mat inputMat = inputSocket.getValue().get(); sizeSocket.setValue(inputMat.size()); - isEmptySocket.setValue(inputMat.empty()); + emptySocket.setValue(inputMat.empty()); channelsSocket.setValue(inputMat.channels()); colsSocket.setValue(inputMat.cols()); rowsSocket.setValue(inputMat.rows()); diff --git a/core/src/main/java/edu/wpi/grip/core/operations/opencv/MinMaxLoc.java b/core/src/main/java/edu/wpi/grip/core/operations/opencv/MinMaxLoc.java index 01db1f0994..f4cde02b16 100644 --- a/core/src/main/java/edu/wpi/grip/core/operations/opencv/MinMaxLoc.java +++ b/core/src/main/java/edu/wpi/grip/core/operations/opencv/MinMaxLoc.java @@ -1,6 +1,7 @@ package edu.wpi.grip.core.operations.opencv; -import com.google.common.eventbus.EventBus; +import com.google.common.collect.ImmutableList; +import edu.wpi.grip.core.OperationDescription; import edu.wpi.grip.core.sockets.InputSocket; import edu.wpi.grip.core.sockets.OutputSocket; import edu.wpi.grip.core.sockets.SocketHint; @@ -9,12 +10,19 @@ import org.bytedeco.javacpp.opencv_core.Mat; import org.bytedeco.javacpp.opencv_core.Point; -import java.util.logging.Level; -import java.util.logging.Logger; +import java.util.List; -/** Operation to call {@link opencv_core#minMaxLoc} */ +/** + * Operation to call {@link opencv_core#minMaxLoc} + */ public class MinMaxLoc implements CVOperation { + public static final OperationDescription DESCRIPTION = + CVOperation.defaultBuilder() + .name("Find Min and Max") + .summary("Find the global minimum and manimum in a single channel grayscale image.") + .build(); + private final SocketHint srcInputHint = SocketHints.Inputs.createMatSocketHint("Image", false), maskInputHint = SocketHints.Inputs.createMatSocketHint("Mask", true); @@ -26,53 +34,57 @@ public class MinMaxLoc implements CVOperation { private final SocketHint minLocOutputHint = SocketHints.Outputs.createPointSocketHint("Min Loc"), maxLocOutputHint = SocketHints.Outputs.createPointSocketHint("Max Loc"); - private static Logger logger = Logger.getLogger(MinMaxLoc.class.getName()); - @Override - public String getName() { - return "Find Min and Max"; - } + private final InputSocket srcSocket; + private final InputSocket maskSocket; - @Override - public String getDescription() { - return "Find the global minimum and maximum in a single channel grayscale image."; + private final OutputSocket minValSocket; + private final OutputSocket maxValSocket; + private final OutputSocket minLocSocket; + private final OutputSocket maxLocSocket; + + public MinMaxLoc(InputSocket.Factory inputSocketFactory, OutputSocket.Factory outputSocketFactory) { + this.srcSocket = inputSocketFactory.create(srcInputHint); + this.maskSocket = inputSocketFactory.create(maskInputHint); + + this.minValSocket = outputSocketFactory.create(minValOutputHint); + this.maxValSocket = outputSocketFactory.create(maxValOutputHint); + this.minLocSocket = outputSocketFactory.create(minLocOutputHint); + this.maxLocSocket = outputSocketFactory.create(maxLocOutputHint); } @Override - @SuppressWarnings("unchecked") - public InputSocket[] createInputSockets(EventBus eventBus) { - return new InputSocket[] { new InputSocket(eventBus, srcInputHint), new InputSocket(eventBus, maskInputHint) }; + public List getInputSockets() { + return ImmutableList.of( + srcSocket, + maskSocket + ); } @Override - @SuppressWarnings("unchecked") - public OutputSocket[] createOutputSockets(EventBus eventBus) { - return new OutputSocket[] { - new OutputSocket(eventBus, minValOutputHint), - new OutputSocket(eventBus, maxValOutputHint), - new OutputSocket(eventBus, minLocOutputHint), - new OutputSocket(eventBus, maxLocOutputHint), - }; + public List getOutputSockets() { + return ImmutableList.of( + minValSocket, + maxValSocket, + minLocSocket, + maxLocSocket + ); } @Override - public void perform(InputSocket[] inputs, OutputSocket[] outputs) { - final Mat src = (Mat) inputs[0].getValue().get(); - Mat mask = (Mat) inputs[1].getValue().get(); + public void perform() { + final Mat src = srcSocket.getValue().get(); + Mat mask = maskSocket.getValue().get(); if (mask.empty()) mask = null; - final double minVal[] = new double [1]; - final double maxVal[] = new double [1]; - final Point minLoc = (Point) outputs[2].getValue().get(); - final Point maxLoc = (Point) outputs[3].getValue().get(); + final double minVal[] = new double[1]; + final double maxVal[] = new double[1]; + final Point minLoc = minLocSocket.getValue().get(); + final Point maxLoc = maxLocSocket.getValue().get(); - try { - opencv_core.minMaxLoc(src, minVal, maxVal, minLoc, maxLoc, mask); - ((OutputSocket) outputs[0]).setValue(minVal[0]); - ((OutputSocket) outputs[1]).setValue(maxVal[0]); - ((OutputSocket) outputs[2]).setValue(outputs[2].getValue().get()); - ((OutputSocket) outputs[3]).setValue(outputs[3].getValue().get()); - } catch (final Exception e) { - logger.log(Level.WARNING, e.getMessage(), e); - } + opencv_core.minMaxLoc(src, minVal, maxVal, minLoc, maxLoc, mask); + minValSocket.setValue(minVal[0]); + maxValSocket.setValue(maxVal[0]); + minLocSocket.setValue(minLocSocket.getValue().get()); + maxLocSocket.setValue(maxLocSocket.getValue().get()); } } \ No newline at end of file diff --git a/core/src/main/java/edu/wpi/grip/core/operations/opencv/NewPointOperation.java b/core/src/main/java/edu/wpi/grip/core/operations/opencv/NewPointOperation.java index bd7c17c2ad..fd379e9b22 100644 --- a/core/src/main/java/edu/wpi/grip/core/operations/opencv/NewPointOperation.java +++ b/core/src/main/java/edu/wpi/grip/core/operations/opencv/NewPointOperation.java @@ -1,56 +1,64 @@ package edu.wpi.grip.core.operations.opencv; -import com.google.common.eventbus.EventBus; +import com.google.common.collect.ImmutableList; +import edu.wpi.grip.core.OperationDescription; import edu.wpi.grip.core.sockets.InputSocket; import edu.wpi.grip.core.sockets.OutputSocket; import edu.wpi.grip.core.sockets.SocketHint; import edu.wpi.grip.core.sockets.SocketHints; +import edu.wpi.grip.core.util.Icon; import org.bytedeco.javacpp.opencv_core.Point; -import java.io.InputStream; -import java.util.Optional; +import java.util.List; public class NewPointOperation implements CVOperation { + public static final OperationDescription DESCRIPTION = + CVOperation.defaultBuilder() + .name("New Point") + .summary("Create a point by (x,y) value.") + .icon(Icon.iconStream("point")) + .build(); + private final SocketHint xHint = SocketHints.Inputs.createNumberSpinnerSocketHint("x", -1, Integer.MIN_VALUE, Integer.MAX_VALUE); private final SocketHint yHint = SocketHints.Inputs.createNumberSpinnerSocketHint("y", -1, Integer.MIN_VALUE, Integer.MAX_VALUE); private final SocketHint outputHint = SocketHints.Outputs.createPointSocketHint("point"); - @Override - public String getName() { - return "New Point"; - } - @Override - public String getDescription() { - return "Create a point by (x,y) value."; - } + private final InputSocket xSocket; + private final InputSocket ySocket; - @Override - public Optional getIcon() { - return Optional.of(getClass().getResourceAsStream("/edu/wpi/grip/ui/icons/point.png")); + private final OutputSocket outputSocket; + + public NewPointOperation(InputSocket.Factory inputSocketFactory, OutputSocket.Factory outputSocketFactory) { + this.xSocket = inputSocketFactory.create(xHint); + this.ySocket = inputSocketFactory.create(yHint); + + this.outputSocket = outputSocketFactory.create(outputHint); } @Override - public InputSocket[] createInputSockets(EventBus eventBus) { - return new InputSocket[]{new InputSocket(eventBus, xHint), new InputSocket(eventBus, yHint)}; + public List getInputSockets() { + return ImmutableList.of( + xSocket, + ySocket + ); } @Override - public OutputSocket[] createOutputSockets(EventBus eventBus) { - return new OutputSocket[]{new OutputSocket(eventBus, outputHint)}; + public List getOutputSockets() { + return ImmutableList.of( + outputSocket + ); } @Override - public void perform(InputSocket[] inputs, OutputSocket[] outputs) { - final InputSocket xSocket = (InputSocket) inputs[0]; - final InputSocket ySocket = (InputSocket) inputs[1]; + public void perform() { final int xValue = xSocket.getValue().get().intValue(); final int yValue = ySocket.getValue().get().intValue(); - final OutputSocket outputSocket = (OutputSocket) outputs[0]; outputSocket.setValue(new Point(xValue, yValue)); } } diff --git a/core/src/main/java/edu/wpi/grip/core/operations/opencv/NewSizeOperation.java b/core/src/main/java/edu/wpi/grip/core/operations/opencv/NewSizeOperation.java index 12c3f54014..4df0db38eb 100644 --- a/core/src/main/java/edu/wpi/grip/core/operations/opencv/NewSizeOperation.java +++ b/core/src/main/java/edu/wpi/grip/core/operations/opencv/NewSizeOperation.java @@ -1,54 +1,62 @@ package edu.wpi.grip.core.operations.opencv; -import com.google.common.eventbus.EventBus; +import edu.wpi.grip.core.OperationDescription; import edu.wpi.grip.core.sockets.InputSocket; import edu.wpi.grip.core.sockets.OutputSocket; import edu.wpi.grip.core.sockets.SocketHint; import edu.wpi.grip.core.sockets.SocketHints; +import edu.wpi.grip.core.util.Icon; import org.bytedeco.javacpp.opencv_core.Size; +import org.python.google.common.collect.ImmutableList; -import java.io.InputStream; -import java.util.Optional; +import java.util.List; public class NewSizeOperation implements CVOperation { + public static final OperationDescription DESCRIPTION = + CVOperation.defaultBuilder() + .name("New Size") + .summary("Create a size.") + .icon(Icon.iconStream("size")) + .build(); + private final SocketHint widthHint = SocketHints.Inputs.createNumberSpinnerSocketHint("width", -1, -1, Integer.MAX_VALUE); private final SocketHint heightHint = SocketHints.Inputs.createNumberSpinnerSocketHint("height", -1, -1, Integer.MAX_VALUE); private final SocketHint outputHint = SocketHints.Outputs.createSizeSocketHint("size"); - @Override - public String getName() { - return "New Size"; - } - @Override - public String getDescription() { - return "Create a size."; - } + private final InputSocket widthSocket; + private final InputSocket heightSocket; - @Override - public Optional getIcon() { - return Optional.of(getClass().getResourceAsStream("/edu/wpi/grip/ui/icons/size.png")); + private final OutputSocket outputSocket; + + public NewSizeOperation(InputSocket.Factory inputSocketFactory, OutputSocket.Factory outputSocketFactory) { + this.widthSocket = inputSocketFactory.create(widthHint); + this.heightSocket = inputSocketFactory.create(heightHint); + + this.outputSocket = outputSocketFactory.create(outputHint); } @Override - public InputSocket[] createInputSockets(EventBus eventBus) { - return new InputSocket[]{new InputSocket(eventBus, widthHint), new InputSocket(eventBus, heightHint)}; + public List getInputSockets() { + return ImmutableList.of( + widthSocket, + heightSocket + ); } @Override - public OutputSocket[] createOutputSockets(EventBus eventBus) { - return new OutputSocket[]{new OutputSocket(eventBus, outputHint)}; + public List getOutputSockets() { + return ImmutableList.of( + outputSocket + ); } @Override - public void perform(InputSocket[] inputs, OutputSocket[] outputs) { - final InputSocket widthSocket = (InputSocket) inputs[0]; - final InputSocket heightSocket = (InputSocket) inputs[1]; + public void perform() { final int widthValue = widthSocket.getValue().get().intValue(); final int heightValue = heightSocket.getValue().get().intValue(); - final OutputSocket outputSocket = (OutputSocket) outputs[0]; outputSocket.setValue(new Size(widthValue, heightValue)); } } diff --git a/core/src/main/java/edu/wpi/grip/core/operations/templated/FiveSourceOneDestinationOperation.java b/core/src/main/java/edu/wpi/grip/core/operations/templated/FiveSourceOneDestinationOperation.java new file mode 100644 index 0000000000..e564f2cca8 --- /dev/null +++ b/core/src/main/java/edu/wpi/grip/core/operations/templated/FiveSourceOneDestinationOperation.java @@ -0,0 +1,61 @@ +package edu.wpi.grip.core.operations.templated; + +import com.google.common.collect.ImmutableList; +import edu.wpi.grip.core.Operation; +import edu.wpi.grip.core.sockets.InputSocket; +import edu.wpi.grip.core.sockets.OutputSocket; +import edu.wpi.grip.core.sockets.SocketHint; + +import java.util.List; + +@SuppressWarnings("PMD.GenericsNaming") +final class FiveSourceOneDestinationOperation implements Operation { + private final InputSocket input1; + private final InputSocket input2; + private final InputSocket input3; + private final InputSocket input4; + private final InputSocket input5; + private final OutputSocket output; + private final Performer performer; + + @FunctionalInterface + public interface Performer { + void perform(T1 src1, T2 src2, T3 src3, T4 src4, T5 src5, R dst); + } + + FiveSourceOneDestinationOperation( + InputSocket.Factory inputSocketFactory, + OutputSocket.Factory outputSocketFactory, + SocketHint t1SocketHint, SocketHint t2SocketHint, SocketHint t3SocketHint, SocketHint t4SocketHint, SocketHint t5SocketHint, SocketHint rSocketHint, Performer performer) { + this.performer = performer; + this.input1 = inputSocketFactory.create(t1SocketHint); + this.input2 = inputSocketFactory.create(t2SocketHint); + this.input3 = inputSocketFactory.create(t3SocketHint); + this.input4 = inputSocketFactory.create(t4SocketHint); + this.input5 = inputSocketFactory.create(t5SocketHint); + this.output = outputSocketFactory.create(rSocketHint); + } + + @Override + public List getInputSockets() { + return ImmutableList.of(input1, input2, input3, input4, input5); + } + + @Override + public List getOutputSockets() { + return ImmutableList.of(output); + } + + @Override + @SuppressWarnings("OptionalGetWithoutIsPresent") + public void perform() { + performer.perform( + input1.getValue().get(), + input2.getValue().get(), + input3.getValue().get(), + input4.getValue().get(), + input5.getValue().get(), + output.getValue().get()); + output.setValue(output.getValue().get()); + } +} diff --git a/core/src/main/java/edu/wpi/grip/core/operations/templated/FourSourceOneDestinationOperation.java b/core/src/main/java/edu/wpi/grip/core/operations/templated/FourSourceOneDestinationOperation.java new file mode 100644 index 0000000000..2c1010e674 --- /dev/null +++ b/core/src/main/java/edu/wpi/grip/core/operations/templated/FourSourceOneDestinationOperation.java @@ -0,0 +1,73 @@ +package edu.wpi.grip.core.operations.templated; + +import com.google.common.collect.ImmutableList; +import edu.wpi.grip.core.Operation; +import edu.wpi.grip.core.sockets.InputSocket; +import edu.wpi.grip.core.sockets.OutputSocket; +import edu.wpi.grip.core.sockets.SocketHint; + +import java.util.List; + +@SuppressWarnings("PMD.GenericsNaming") +final class FourSourceOneDestinationOperation implements Operation { + private final InputSocket input1; + private final InputSocket input2; + private final InputSocket input3; + private final InputSocket input4; + private final OutputSocket output; + private final Performer performer; + + @FunctionalInterface + public interface Performer { + void perform(T1 src1, T2 src2, T3 src3, T4 src4, R dst); + } + + FourSourceOneDestinationOperation( + InputSocket.Factory inputSocketFactory, + OutputSocket.Factory outputSocketFactory, + SocketHint t1SocketHint, SocketHint t2SocketHint, SocketHint t3SocketHint, SocketHint t4SocketHint, SocketHint rSocketHint, Performer performer) { + this.performer = performer; + this.input1 = inputSocketFactory.create(t1SocketHint); + this.input2 = inputSocketFactory.create(t2SocketHint); + this.input3 = inputSocketFactory.create(t3SocketHint); + this.input4 = inputSocketFactory.create(t4SocketHint); + this.output = outputSocketFactory.create(rSocketHint); + } + + + public FourSourceOneDestinationOperation( + InputSocket.Factory inputSocketFactory, + OutputSocket.Factory outputSocketFactory, + Class t1, Class t2, Class t3, Class t4, Class r, Performer performer) { + this( + inputSocketFactory, + outputSocketFactory, + new SocketHint.Builder<>(t1).identifier("src1").build(), new SocketHint.Builder<>(t2).identifier("src2").build(), new SocketHint.Builder<>(t3).identifier("src3").build(), new SocketHint.Builder<>(t4).identifier("src4").build(), new SocketHint.Builder<>(r).identifier("dst").build(), performer + ); + + + } + + @Override + public List getInputSockets() { + return ImmutableList.of(input1, input2, input3, input4); + } + + @Override + public List getOutputSockets() { + return ImmutableList.of(output); + } + + @Override + @SuppressWarnings("OptionalGetWithoutIsPresent") + public void perform() { + performer.perform( + input1.getValue().get(), + input2.getValue().get(), + input3.getValue().get(), + input4.getValue().get(), + output.getValue().get() + ); + output.setValue(output.getValue().get()); + } +} diff --git a/core/src/main/java/edu/wpi/grip/core/operations/templated/OneSourceOneDestinationOperation.java b/core/src/main/java/edu/wpi/grip/core/operations/templated/OneSourceOneDestinationOperation.java new file mode 100644 index 0000000000..fbc40a9d28 --- /dev/null +++ b/core/src/main/java/edu/wpi/grip/core/operations/templated/OneSourceOneDestinationOperation.java @@ -0,0 +1,49 @@ +package edu.wpi.grip.core.operations.templated; + +import autovalue.shaded.com.google.common.common.collect.ImmutableList; +import edu.wpi.grip.core.Operation; +import edu.wpi.grip.core.sockets.InputSocket; +import edu.wpi.grip.core.sockets.OutputSocket; +import edu.wpi.grip.core.sockets.SocketHint; + +import java.util.List; + +@SuppressWarnings("PMD.GenericsNaming") +final class OneSourceOneDestinationOperation implements Operation { + private final InputSocket input1; + private final OutputSocket output; + private final Performer performer; + + @FunctionalInterface + public interface Performer { + void perform(T1 src1, R dst); + } + + OneSourceOneDestinationOperation(InputSocket.Factory inputSocketFactory, + OutputSocket.Factory outputSocketFactory, + Performer performer, + SocketHint t1SocketHint, + SocketHint rSocketHint) { + this.input1 = inputSocketFactory.create(t1SocketHint); + this.output = outputSocketFactory.create(rSocketHint); + this.performer = performer; + } + + @Override + public List getInputSockets() { + return ImmutableList.of(input1); + } + + @Override + public List getOutputSockets() { + return ImmutableList.of(output); + } + + @Override + @SuppressWarnings("OptionalGetWithoutIsPresent") + public void perform() { + performer.perform(input1.getValue().get(), output.getValue().get()); + output.setValue(output.getValue().get()); + } + +} diff --git a/core/src/main/java/edu/wpi/grip/core/operations/templated/SevenSourceOneDestinationOperation.java b/core/src/main/java/edu/wpi/grip/core/operations/templated/SevenSourceOneDestinationOperation.java new file mode 100644 index 0000000000..0324972549 --- /dev/null +++ b/core/src/main/java/edu/wpi/grip/core/operations/templated/SevenSourceOneDestinationOperation.java @@ -0,0 +1,77 @@ +package edu.wpi.grip.core.operations.templated; + +import com.google.common.collect.ImmutableList; +import edu.wpi.grip.core.Operation; +import edu.wpi.grip.core.sockets.InputSocket; +import edu.wpi.grip.core.sockets.OutputSocket; +import edu.wpi.grip.core.sockets.SocketHint; + +import java.util.List; + +@SuppressWarnings("PMD.GenericsNaming") +final class SevenSourceOneDestinationOperation implements Operation { + private final InputSocket input1; + private final InputSocket input2; + private final InputSocket input3; + private final InputSocket input4; + private final InputSocket input5; + private final InputSocket input6; + private final InputSocket input7; + private final OutputSocket output; + private final Performer performer; + + @FunctionalInterface + public interface Performer { + void perform(T1 src1, T2 src2, T3 src3, T4 src4, T5 src5, T6 src6, T7 src7, R dst); + } + + @SuppressWarnings("PMD.ExcessiveParameterList") + SevenSourceOneDestinationOperation( + InputSocket.Factory inputSocketFactory, + OutputSocket.Factory outputSocketFactory, + SocketHint t1SocketHint, + SocketHint t2SocketHint, + SocketHint t3SocketHint, + SocketHint t4SocketHint, + SocketHint t5SocketHint, + SocketHint t6SocketHint, + SocketHint t7SocketHint, + SocketHint rSocketHint, + Performer performer) { + this.performer = performer; + this.input1 = inputSocketFactory.create(t1SocketHint); + this.input2 = inputSocketFactory.create(t2SocketHint); + this.input3 = inputSocketFactory.create(t3SocketHint); + this.input4 = inputSocketFactory.create(t4SocketHint); + this.input5 = inputSocketFactory.create(t5SocketHint); + this.input6 = inputSocketFactory.create(t6SocketHint); + this.input7 = inputSocketFactory.create(t7SocketHint); + this.output = outputSocketFactory.create(rSocketHint); + } + + @Override + public List getInputSockets() { + return ImmutableList.of(input1, input2, input3, input4, input5, input6, input7); + } + + @Override + public List getOutputSockets() { + return ImmutableList.of(output); + } + + @Override + @SuppressWarnings("OptionalGetWithoutIsPresent") + public void perform() { + performer.perform( + input1.getValue().get(), + input2.getValue().get(), + input3.getValue().get(), + input4.getValue().get(), + input5.getValue().get(), + input6.getValue().get(), + input7.getValue().get(), + output.getValue().get() + ); + output.setValue(output.getValue().get()); + } +} diff --git a/core/src/main/java/edu/wpi/grip/core/operations/templated/SixSourceOneDestinationOperation.java b/core/src/main/java/edu/wpi/grip/core/operations/templated/SixSourceOneDestinationOperation.java new file mode 100644 index 0000000000..7bf11922fb --- /dev/null +++ b/core/src/main/java/edu/wpi/grip/core/operations/templated/SixSourceOneDestinationOperation.java @@ -0,0 +1,74 @@ +package edu.wpi.grip.core.operations.templated; + +import com.google.common.collect.ImmutableList; +import edu.wpi.grip.core.Operation; +import edu.wpi.grip.core.sockets.InputSocket; +import edu.wpi.grip.core.sockets.OutputSocket; +import edu.wpi.grip.core.sockets.SocketHint; + +import java.util.List; + +@SuppressWarnings("PMD.GenericsNaming") +final class SixSourceOneDestinationOperation implements Operation { + private final InputSocket input1; + private final InputSocket input2; + private final InputSocket input3; + private final InputSocket input4; + private final InputSocket input5; + private final InputSocket input6; + private final OutputSocket output; + private final Performer performer; + + @FunctionalInterface + public interface Performer { + void perform(T1 src1, T2 src2, T3 src3, T4 src4, T5 src5, T6 src6, R dst); + } + + @SuppressWarnings("PMD.ExcessiveParameterList") + SixSourceOneDestinationOperation( + InputSocket.Factory inputSocketFactory, + OutputSocket.Factory outputSocketFactory, + SocketHint t1SocketHint, + SocketHint t2SocketHint, + SocketHint t3SocketHint, + SocketHint t4SocketHint, + SocketHint t5SocketHint, + SocketHint t6SocketHint, + SocketHint rSocketHint, + Performer performer) { + this.performer = performer; + this.input1 = inputSocketFactory.create(t1SocketHint); + this.input2 = inputSocketFactory.create(t2SocketHint); + this.input3 = inputSocketFactory.create(t3SocketHint); + this.input4 = inputSocketFactory.create(t4SocketHint); + this.input5 = inputSocketFactory.create(t5SocketHint); + this.input6 = inputSocketFactory.create(t6SocketHint); + this.output = outputSocketFactory.create(rSocketHint); + } + + + @Override + public List getInputSockets() { + return ImmutableList.of(input1, input2, input3, input4, input5, input6); + } + + @Override + public List getOutputSockets() { + return ImmutableList.of(output); + } + + @Override + @SuppressWarnings("OptionalGetWithoutIsPresent") + public void perform() { + performer.perform( + input1.getValue().get(), + input2.getValue().get(), + input3.getValue().get(), + input4.getValue().get(), + input5.getValue().get(), + input6.getValue().get(), + output.getValue().get() + ); + output.setValue(output.getValue().get()); + } +} diff --git a/core/src/main/java/edu/wpi/grip/core/operations/templated/TemplateFactory.java b/core/src/main/java/edu/wpi/grip/core/operations/templated/TemplateFactory.java new file mode 100644 index 0000000000..ffd3f4063a --- /dev/null +++ b/core/src/main/java/edu/wpi/grip/core/operations/templated/TemplateFactory.java @@ -0,0 +1,130 @@ +package edu.wpi.grip.core.operations.templated; + + +import com.google.inject.Singleton; +import edu.wpi.grip.core.Operation; +import edu.wpi.grip.core.sockets.InputSocket; +import edu.wpi.grip.core.sockets.OutputSocket; +import edu.wpi.grip.core.sockets.SocketHint; +import org.bytedeco.javacpp.opencv_core.Mat; + +import java.util.function.Supplier; + +/** + * Allows you to easily create {@link Supplier} using only {@link SocketHint SocketHints} and a lambda that + * will be run as part of the {@link Operation#perform()} method. + */ +@Singleton +@SuppressWarnings("PMD.GenericsNaming") +public final class TemplateFactory { + private final InputSocket.Factory isf; + private final OutputSocket.Factory osf; + + public TemplateFactory(InputSocket.Factory inputSocketFactory, + OutputSocket.Factory outputSocketFactory) { + this.isf = inputSocketFactory; + this.osf = outputSocketFactory; + } + + public Supplier create( + SocketHint t1SocketHint, + SocketHint rSocketHint, + OneSourceOneDestinationOperation.Performer performer) { + return () -> new OneSourceOneDestinationOperation<>(isf, osf, performer, t1SocketHint, rSocketHint); + } + + public Supplier create( + SocketHint t1SocketHint, + SocketHint t2SocketHint, + SocketHint rSocketHint, + TwoSourceOneDestinationOperation.Performer performer) { + return () -> new TwoSourceOneDestinationOperation<>(isf, osf, t1SocketHint, t2SocketHint, rSocketHint, performer); + } + + public Supplier create( + SocketHint t1SocketHint, + SocketHint t2SocketHint, + SocketHint t3SocketHint, + SocketHint rSocketHint, + ThreeSourceOneDestinationOperation.Performer performer) { + return () -> new ThreeSourceOneDestinationOperation<>(isf, osf, t1SocketHint, t2SocketHint, t3SocketHint, rSocketHint, performer); + } + + public Supplier create( + SocketHint t1SocketHint, + SocketHint t2SocketHint, + SocketHint t3SocketHint, + SocketHint t4SocketHint, + SocketHint rSocketHint, + FourSourceOneDestinationOperation.Performer performer) { + return () -> new FourSourceOneDestinationOperation<>(isf, osf, t1SocketHint, t2SocketHint, t3SocketHint, t4SocketHint, rSocketHint, performer); + } + + public Supplier create( + SocketHint t1SocketHint, + SocketHint t2SocketHint, + SocketHint t3SocketHint, + SocketHint t4SocketHint, + SocketHint t5SocketHint, + SocketHint rSocketHint, + FiveSourceOneDestinationOperation.Performer performer) { + return () -> new FiveSourceOneDestinationOperation<>(isf, osf, t1SocketHint, t2SocketHint, t3SocketHint, t4SocketHint, t5SocketHint, rSocketHint, performer); + } + + public Supplier create( + SocketHint t1SocketHint, + SocketHint t2SocketHint, + SocketHint t3SocketHint, + SocketHint t4SocketHint, + SocketHint t5SocketHint, + SocketHint t6SocketHint, + SocketHint rSocketHint, + SixSourceOneDestinationOperation.Performer performer) { + return () -> new SixSourceOneDestinationOperation<>(isf, osf, t1SocketHint, t2SocketHint, t3SocketHint, t4SocketHint, t5SocketHint, t6SocketHint, rSocketHint, performer); + } + + public Supplier create( + SocketHint t1SocketHint, + SocketHint t2SocketHint, + SocketHint t3SocketHint, + SocketHint t4SocketHint, + SocketHint t5SocketHint, + SocketHint t6SocketHint, + SocketHint t7SocketHint, + SocketHint rSocketHint, + SevenSourceOneDestinationOperation.Performer performer) { + return () -> new SevenSourceOneDestinationOperation<>(isf, osf, t1SocketHint, t2SocketHint, t3SocketHint, t4SocketHint, t5SocketHint, t6SocketHint, t7SocketHint, rSocketHint, performer); + } + + public Supplier createAllMatTwoSource( + SocketHint matSocketHint, + SocketHint matSocketHint2, + SocketHint matSocketHint3, + TwoSourceOneDestinationOperation.Performer performer) { + return create(matSocketHint, matSocketHint2, matSocketHint3, performer); + } + + public Supplier createAllMatTwoSource(TwoSourceOneDestinationOperation.Performer performer) { + return createAllMatTwoSource(srcSocketHint(Mat.class, 1), srcSocketHint(Mat.class, 2), dstSocketHint(Mat.class), performer); + } + + public Supplier createAllMatOneSource( + SocketHint matSocketHint, + SocketHint matSocketHint2, + OneSourceOneDestinationOperation.Performer performer) { + return create(matSocketHint, matSocketHint2, performer); + } + + public Supplier createAllMatOneSource(OneSourceOneDestinationOperation.Performer performer) { + return createAllMatOneSource(srcSocketHint(Mat.class, 1), dstSocketHint(Mat.class), performer); + } + + + private SocketHint srcSocketHint(Class srcType, int index) { + return new SocketHint.Builder<>(srcType).identifier("src" + index).build(); + } + + private SocketHint dstSocketHint(Class returnType) { + return new SocketHint.Builder<>(returnType).identifier("dst").build(); + } +} diff --git a/core/src/main/java/edu/wpi/grip/core/operations/templated/ThreeSourceOneDestinationOperation.java b/core/src/main/java/edu/wpi/grip/core/operations/templated/ThreeSourceOneDestinationOperation.java new file mode 100644 index 0000000000..0272b3f3b2 --- /dev/null +++ b/core/src/main/java/edu/wpi/grip/core/operations/templated/ThreeSourceOneDestinationOperation.java @@ -0,0 +1,52 @@ +package edu.wpi.grip.core.operations.templated; + + +import com.google.common.collect.ImmutableList; +import edu.wpi.grip.core.Operation; +import edu.wpi.grip.core.sockets.InputSocket; +import edu.wpi.grip.core.sockets.OutputSocket; +import edu.wpi.grip.core.sockets.SocketHint; + +import java.util.List; + +@SuppressWarnings("PMD.GenericsNaming") +final class ThreeSourceOneDestinationOperation implements Operation { + private final InputSocket input1; + private final InputSocket input2; + private final InputSocket input3; + private final OutputSocket output; + private final Performer performer; + + @FunctionalInterface + public interface Performer { + void perform(T1 src1, T2 src2, T3 src3, R dst); + } + + ThreeSourceOneDestinationOperation( + InputSocket.Factory inputSocketFactory, + OutputSocket.Factory outputSocketFactory, + SocketHint t1SocketHint, SocketHint t2SocketHint, SocketHint t3SocketHint, SocketHint rSocketHint, Performer performer) { + this.performer = performer; + this.input1 = inputSocketFactory.create(t1SocketHint); + this.input2 = inputSocketFactory.create(t2SocketHint); + this.input3 = inputSocketFactory.create(t3SocketHint); + this.output = outputSocketFactory.create(rSocketHint); + } + + @Override + public List getInputSockets() { + return ImmutableList.of(input1, input2, input3); + } + + @Override + public List getOutputSockets() { + return ImmutableList.of(output); + } + + @Override + @SuppressWarnings("OptionalGetWithoutIsPresent") + public void perform() { + performer.perform(input1.getValue().get(), input2.getValue().get(), input3.getValue().get(), output.getValue().get()); + output.setValue(output.getValue().get()); + } +} diff --git a/core/src/main/java/edu/wpi/grip/core/operations/templated/TwoSourceOneDestinationOperation.java b/core/src/main/java/edu/wpi/grip/core/operations/templated/TwoSourceOneDestinationOperation.java new file mode 100644 index 0000000000..b24318403d --- /dev/null +++ b/core/src/main/java/edu/wpi/grip/core/operations/templated/TwoSourceOneDestinationOperation.java @@ -0,0 +1,51 @@ +package edu.wpi.grip.core.operations.templated; + + +import com.google.common.collect.ImmutableList; +import edu.wpi.grip.core.Operation; +import edu.wpi.grip.core.sockets.InputSocket; +import edu.wpi.grip.core.sockets.OutputSocket; +import edu.wpi.grip.core.sockets.SocketHint; + +import java.util.List; + + +@SuppressWarnings("PMD.GenericsNaming") +final class TwoSourceOneDestinationOperation implements Operation { + private final InputSocket input1; + private final InputSocket input2; + private final OutputSocket output; + private final Performer performer; + + @FunctionalInterface + public interface Performer { + void perform(T1 src1, T2 src2, R dst); + } + + TwoSourceOneDestinationOperation( + InputSocket.Factory inputSocketFactory, + OutputSocket.Factory outputSocketFactory, + SocketHint t1SocketHint, SocketHint t2SocketHint, SocketHint rSocketHint, Performer performer) { + this.performer = performer; + this.input1 = inputSocketFactory.create(t1SocketHint); + this.input2 = inputSocketFactory.create(t2SocketHint); + this.output = outputSocketFactory.create(rSocketHint); + } + + @Override + public List getInputSockets() { + return ImmutableList.of(input1, input2); + } + + @Override + public List getOutputSockets() { + return ImmutableList.of(output); + } + + @Override + @SuppressWarnings("OptionalGetWithoutIsPresent") + public void perform() { + performer.perform(input1.getValue().get(), input2.getValue().get(), output.getValue().get()); + output.setValue(output.getValue().get()); + } +} diff --git a/core/src/main/java/edu/wpi/grip/core/operations/templated/package-info.java b/core/src/main/java/edu/wpi/grip/core/operations/templated/package-info.java new file mode 100644 index 0000000000..e319b6f9b0 --- /dev/null +++ b/core/src/main/java/edu/wpi/grip/core/operations/templated/package-info.java @@ -0,0 +1,6 @@ +/** + * Templates for various {@link edu.wpi.grip.core.Operation Operations}. + * Many of these classes contain duplicated code because there is no way to create types at runtime. + * The best place to start looking at this package is the {@link edu.wpi.grip.core.operations.templated.TemplateFactory}. + */ +package edu.wpi.grip.core.operations.templated; diff --git a/core/src/main/java/edu/wpi/grip/core/serialization/Project.java b/core/src/main/java/edu/wpi/grip/core/serialization/Project.java index 196984e9be..1cd898faf9 100644 --- a/core/src/main/java/edu/wpi/grip/core/serialization/Project.java +++ b/core/src/main/java/edu/wpi/grip/core/serialization/Project.java @@ -1,14 +1,11 @@ package edu.wpi.grip.core.serialization; import com.google.common.annotations.VisibleForTesting; +import com.google.common.reflect.ClassPath; import com.thoughtworks.xstream.XStream; +import com.thoughtworks.xstream.annotations.XStreamAlias; import com.thoughtworks.xstream.converters.reflection.PureJavaReflectionProvider; -import edu.wpi.grip.core.*; -import edu.wpi.grip.core.sockets.InputSocket; -import edu.wpi.grip.core.sockets.OutputSocket; -import edu.wpi.grip.core.sources.CameraSource; -import edu.wpi.grip.core.sources.ImageFileSource; -import edu.wpi.grip.core.sources.MultiImageFileSource; +import edu.wpi.grip.core.Pipeline; import javax.inject.Inject; import javax.inject.Singleton; @@ -40,8 +37,24 @@ public void initialize(StepConverter stepConverter, xstream.registerConverter(socketConverter); xstream.registerConverter(connectionConverter); xstream.registerConverter(projectSettingsConverter); - xstream.processAnnotations(new Class[]{Pipeline.class, Step.class, Connection.class, InputSocket.class, - OutputSocket.class, ImageFileSource.class, MultiImageFileSource.class, CameraSource.class}); + try { + ClassPath cp = ClassPath.from(getClass().getClassLoader()); + cp.getAllClasses() + .stream() + .filter(ci -> ci.getPackageName().startsWith("edu.wpi.grip")) + .map(ClassPath.ClassInfo::load) + .filter(clazz -> clazz.isAnnotationPresent(XStreamAlias.class)) + .forEach(clazz -> { + try { + xstream.processAnnotations(clazz); + } catch (InternalError e) { + throw new AssertionError("Failed to load class: " + clazz.getName(), e); + } + + }); + } catch (IOException e) { + throw new AssertionError("Could not load classes for XStream annotation processing", e); + } } /** diff --git a/core/src/main/java/edu/wpi/grip/core/serialization/SocketConverter.java b/core/src/main/java/edu/wpi/grip/core/serialization/SocketConverter.java index 6fb7376f65..1e96f985c4 100644 --- a/core/src/main/java/edu/wpi/grip/core/serialization/SocketConverter.java +++ b/core/src/main/java/edu/wpi/grip/core/serialization/SocketConverter.java @@ -91,15 +91,15 @@ public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext co try { reader.moveDown(); - final String nodeName = reader.getNodeName(); + final Class nodeClass = project.xstream.getMapper().realClass(reader.getNodeName()); Socket.Direction direction; - if (nodeName.equals(project.xstream.getMapper().serializedClass(InputSocket.class))) { + if (InputSocket.class.isAssignableFrom(nodeClass)) { direction = Socket.Direction.INPUT; - } else if (nodeName.equals(project.xstream.getMapper().serializedClass(OutputSocket.class))) { + } else if (OutputSocket.class.isAssignableFrom(nodeClass)) { direction = Socket.Direction.OUTPUT; } else { - throw new IllegalArgumentException("Unexpected socket node name: " + nodeName); + throw new IllegalArgumentException("Unexpected socket type: " + nodeClass.getName()); } // Look up the socket using the saved indexes. Serializing sockets this way makes it so different things diff --git a/core/src/main/java/edu/wpi/grip/core/serialization/StepConverter.java b/core/src/main/java/edu/wpi/grip/core/serialization/StepConverter.java index e8de4150ca..6e60ae14a6 100644 --- a/core/src/main/java/edu/wpi/grip/core/serialization/StepConverter.java +++ b/core/src/main/java/edu/wpi/grip/core/serialization/StepConverter.java @@ -7,7 +7,10 @@ import com.thoughtworks.xstream.converters.UnmarshallingContext; import com.thoughtworks.xstream.io.HierarchicalStreamReader; import com.thoughtworks.xstream.io.HierarchicalStreamWriter; -import edu.wpi.grip.core.*; +import edu.wpi.grip.core.Palette; +import edu.wpi.grip.core.Pipeline; +import edu.wpi.grip.core.Step; +import edu.wpi.grip.core.OperationMetaData; import edu.wpi.grip.core.sockets.InputSocket; import edu.wpi.grip.core.sockets.OutputSocket; import edu.wpi.grip.core.sockets.Socket; @@ -38,7 +41,7 @@ public class StepConverter implements Converter { public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) { final Step step = ((Step) source); - writer.addAttribute(NAME_ATTRIBUTE, step.getOperation().getName()); + writer.addAttribute(NAME_ATTRIBUTE, step.getOperationDescription().name()); // Also save any sockets in the step for (InputSocket socket : step.getInputSockets()) { @@ -53,15 +56,15 @@ public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingC @Override public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { final String operationName = reader.getAttribute(NAME_ATTRIBUTE); - final Optional operation = this.palette.getOperationByName(operationName); + final Optional operationMetaData = this.palette.getOperationByName(operationName); - if (!operation.isPresent()) { + if (!operationMetaData.isPresent()) { throw new ConversionException("Unknown operation: " + operationName); } // Instead of simply returning the step and having XStream insert it into the pipeline using reflection, send a // StepAddedEvent. This allows other interested classes (such as PipelineView) to also know when steps are added. - pipeline.addStep(stepFactory.create(operation.get())); + pipeline.addStep(stepFactory.create(operationMetaData.get())); while (reader.hasMoreChildren()) { context.convertAnother(this, Socket.class); diff --git a/core/src/main/java/edu/wpi/grip/core/sockets/InputSocket.java b/core/src/main/java/edu/wpi/grip/core/sockets/InputSocket.java index 12ec268a23..afa6a9c940 100644 --- a/core/src/main/java/edu/wpi/grip/core/sockets/InputSocket.java +++ b/core/src/main/java/edu/wpi/grip/core/sockets/InputSocket.java @@ -1,50 +1,128 @@ package edu.wpi.grip.core.sockets; -import com.google.common.eventbus.EventBus; -import com.thoughtworks.xstream.annotations.XStreamAlias; +import edu.wpi.grip.core.Connection; import edu.wpi.grip.core.Operation; +import edu.wpi.grip.core.Source; +import edu.wpi.grip.core.Step; -import java.util.concurrent.atomic.AtomicBoolean; +import java.util.Optional; +import java.util.Set; /** * Represents the input into an {@link Operation}. * * @param The type of the value that this socket stores */ -@XStreamAlias(value = "grip:Input") -public class InputSocket extends Socket { - private final AtomicBoolean dirty = new AtomicBoolean(false); +public interface InputSocket extends Socket { - /** - * @param eventBus The Guava {@link EventBus} used by the application. - * @param socketHint {@link #getSocketHint} - */ - public InputSocket(EventBus eventBus, SocketHint socketHint) { - super(eventBus, socketHint, Direction.INPUT); + interface Factory { + InputSocket create(SocketHint hint); } - @Override - protected void onValueChanged() { - dirty.set(true); - } /** * Checks if the socket has been dirtied and rests it to false. * * @return True if the socket has been dirtied */ - public boolean dirtied() { - return dirty.compareAndSet(true, false); - } + boolean dirtied(); /** - * {@inheritDoc} + * Should be only called by parent classes. */ - @Override - protected void onDisconnected() { - super.onDisconnected(); - if (this.getConnections().isEmpty()) { - this.setValue(this.getSocketHint().createInitialValue().orElse(null)); + void onValueChanged(); + + + /** + * A decorator for the {@link InputSocket} + * + * @param The type of the value that this socket stores + */ + abstract class Decorator implements InputSocket { + + private final InputSocket decorated; + + /** + * @param socket the decorated socket + */ + public Decorator(InputSocket socket) { + this.decorated = socket; + } + + @Override + public Direction getDirection() { + return decorated.getDirection(); + } + + @Override + public Optional getSource() { + return decorated.getSource(); + } + + @Override + public Optional getStep() { + return decorated.getStep(); + } + + @Override + public Optional getValue() { + return decorated.getValue(); + } + + @Override + public void addConnection(Connection connection) { + decorated.addConnection(connection); + } + + @Override + public void removeConnection(Connection connection) { + decorated.removeConnection(connection); + } + + @Override + public Set getConnections() { + return decorated.getConnections(); + } + + @Override + public SocketHint getSocketHint() { + return decorated.getSocketHint(); + } + + @Override + public void setSource(Optional source) { + decorated.setSource(source); + } + + @Override + public void setStep(Optional step) { + decorated.setStep(step); + } + + @Override + public void setValueOptional(Optional optionalValue) { + decorated.setValueOptional(optionalValue); + } + + @Override + public boolean dirtied() { + return decorated.dirtied(); + } + + @Override + public void onValueChanged() { + decorated.onValueChanged(); + } + + @Override + public boolean equals(Object o) { + return o instanceof InputSocket && decorated.equals(o); + } + + @Override + public int hashCode() { + return decorated.hashCode(); } } + } diff --git a/core/src/main/java/edu/wpi/grip/core/sockets/InputSocketImpl.java b/core/src/main/java/edu/wpi/grip/core/sockets/InputSocketImpl.java new file mode 100644 index 0000000000..a65cdd0aca --- /dev/null +++ b/core/src/main/java/edu/wpi/grip/core/sockets/InputSocketImpl.java @@ -0,0 +1,71 @@ +package edu.wpi.grip.core.sockets; + + +import com.google.common.eventbus.EventBus; +import com.google.inject.Inject; +import com.google.inject.Singleton; +import com.thoughtworks.xstream.annotations.XStreamAlias; + +import edu.wpi.grip.core.Connection; + +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Concrete implementation of the {@link InputSocket} + * @param The type of the value that this socket stores + */ +@XStreamAlias("grip:Input") +public class InputSocketImpl extends SocketImpl implements InputSocket { + private final AtomicBoolean dirty = new AtomicBoolean(false); + + /** + * Needed to get around Guice's inability to inject a generic typed factory + */ + @Singleton + public static class FactoryImpl implements Factory { + private final EventBus eventBus; + + @Inject + FactoryImpl(EventBus eventBus) { + this.eventBus = eventBus; + } + + @Override + public InputSocket create(SocketHint hint) { + return new InputSocketImpl<>(eventBus, hint); + } + } + + /** + * @param eventBus The Guava {@link EventBus} used by the application. + * @param socketHint {@link #getSocketHint} + */ + InputSocketImpl(EventBus eventBus, SocketHint socketHint) { + super(eventBus, socketHint, Socket.Direction.INPUT); + } + + /** + * Reset the socket to its default value when it's no longer connected to anything. This prevents removed + * connections from continuing to have an effect on steps because they still hold references to the values they + * were connected to. + */ + @Override + public void removeConnection(Connection connection) { + super.removeConnection(connection); + if (this.getConnections().isEmpty()) { + this.setValue(this.getSocketHint().createInitialValue().orElse(null)); + } + } + + @Override + public boolean dirtied() { + return dirty.compareAndSet(true, false); + } + + @Override + public void onValueChanged() { + dirty.set(true); + } + + +} diff --git a/core/src/main/java/edu/wpi/grip/core/sockets/LinkedSocketHint.java b/core/src/main/java/edu/wpi/grip/core/sockets/LinkedSocketHint.java index d9b302f2cd..cb231ed224 100644 --- a/core/src/main/java/edu/wpi/grip/core/sockets/LinkedSocketHint.java +++ b/core/src/main/java/edu/wpi/grip/core/sockets/LinkedSocketHint.java @@ -1,8 +1,8 @@ package edu.wpi.grip.core.sockets; -import com.google.common.eventbus.EventBus; +import com.thoughtworks.xstream.annotations.XStreamAlias; + import edu.wpi.grip.core.Connection; -import edu.wpi.grip.core.events.ConnectionRemovedEvent; import java.util.HashSet; import java.util.Optional; @@ -20,13 +20,58 @@ public final class LinkedSocketHint extends SocketHint.SocketHintDecorator { */ private final Set controllingSockets = new HashSet<>(); private final Set controlledOutputSockets = new HashSet<>(); - private final EventBus eventBus; private Optional connectedType = Optional.empty(); + private final InputSocket.Factory inputSocketFactory; + private final OutputSocket.Factory outputSocketFactory; @SuppressWarnings("unchecked") - public LinkedSocketHint(EventBus eventBus) { - super(new Builder<>(Object.class).identifier("").build()); - this.eventBus = checkNotNull(eventBus, "EventBus cannot be null"); + public LinkedSocketHint(InputSocket.Factory inputSocketFactory, OutputSocket.Factory outputSocketFactory) { + super(new Builder(Object.class).identifier("").build()); + this.inputSocketFactory = checkNotNull(inputSocketFactory); + this.outputSocketFactory = checkNotNull(outputSocketFactory); + } + + /** + * Our own custom implementation of socket hint that interacts on this class when connections are added and removed. + */ + @XStreamAlias("grip:LinkedInput") + private class LinkedInputSocket extends InputSocket.Decorator { + + /** + * @param socket the decorated socket + */ + LinkedInputSocket(InputSocket socket) { + super(socket); + } + + @Override + public void addConnection(Connection connection) { + synchronized (this) { + controllingSockets.add(this); + connectedType = Optional.of(connection.getOutputSocket().getSocketHint().getType()); + } + super.addConnection(connection); + } + + @Override + @SuppressWarnings("unchecked") + public void removeConnection(Connection connection) { + synchronized (this) { + // Remove this socket because it is no longer controlling the type of socket + controllingSockets.remove(this); + if (controllingSockets.isEmpty()) { // When the set is empty, the socket can support any type again + connectedType = Optional.empty(); + // XXX: TODO: This is breaking the law of Demeter fix this + controlledOutputSockets.forEach(outputSocket -> { + final Set> connections = outputSocket.getConnections(); + connections.stream().forEach(Connection::remove); + outputSocket.setPreviewed(false); + outputSocket.setValueOptional(Optional.empty()); + }); + } + } + super.removeConnection(connection); + } } /** @@ -36,42 +81,12 @@ public LinkedSocketHint(EventBus eventBus) { * @return A socket hint that's socket type is determined by this SocketHint */ @SuppressWarnings("unchecked") - public InputSocket linkedInputSocket(String hintIdentifier) { - // Our own custom implementation of socket hint that interacts on this class when connections are - // added and removed - return new InputSocket(eventBus, new IdentiferOverridingSocketHintDecorator(this, hintIdentifier)) { - @Override - public void addConnection(Connection connection) { - synchronized (this) { - controllingSockets.add(this); - connectedType = Optional.of(connection.getOutputSocket().getSocketHint().getType()); - } - super.addConnection(connection); - } - - @Override - public void onDisconnected() { - synchronized (this) { - // Remove this socket because it is no longer controlling the type of socket - controllingSockets.remove(this); - if (controllingSockets.isEmpty()) { // When the set is empty, the socket can support any type again - connectedType = Optional.empty(); - // XXX: TODO: This is breaking the law of Demeter fix this - controlledOutputSockets.forEach(outputSocket -> { - final Set> connections = outputSocket.getConnections(); - connections.stream().map(ConnectionRemovedEvent::new).forEach(this.eventBus::post); - outputSocket.setPreviewed(false); - outputSocket.setValueOptional(Optional.empty()); - }); - } - } - super.onDisconnected(); - } - }; + public InputSocket linkedInputSocket(String hintIdentifier) { + return new LinkedInputSocket<>(inputSocketFactory.create(new IdentiferOverridingSocketHintDecorator(this, hintIdentifier))); } /** - * Creates an input socket that is linked to this SocketHint. + * Creates an input socket that is linked to this Socket * This output socket will automatically be disconnected when there is no longer an input socket to guarantee the type * of this SocketHint * @@ -80,7 +95,7 @@ public void onDisconnected() { */ @SuppressWarnings("unchecked") public OutputSocket linkedOutputSocket(String hintIdentifier) { - final OutputSocket outSocket = new OutputSocket(eventBus, new IdentiferOverridingSocketHintDecorator(this, hintIdentifier)); + final OutputSocket outSocket = outputSocketFactory.create(new IdentiferOverridingSocketHintDecorator(this, hintIdentifier)); controlledOutputSockets.add(outSocket); return outSocket; } diff --git a/core/src/main/java/edu/wpi/grip/core/sockets/OutputSocket.java b/core/src/main/java/edu/wpi/grip/core/sockets/OutputSocket.java index faca8f8f3b..501d9ecdd5 100644 --- a/core/src/main/java/edu/wpi/grip/core/sockets/OutputSocket.java +++ b/core/src/main/java/edu/wpi/grip/core/sockets/OutputSocket.java @@ -1,71 +1,32 @@ package edu.wpi.grip.core.sockets; -import com.google.common.base.MoreObjects; -import com.google.common.eventbus.EventBus; -import com.thoughtworks.xstream.annotations.XStreamAlias; import edu.wpi.grip.core.Operation; -import edu.wpi.grip.core.events.SocketPreviewChangedEvent; /** * Represents the output of an {@link Operation}. * * @param The type of the value that this socket stores. */ -@XStreamAlias(value = "grip:Output") -public class OutputSocket extends Socket { +public interface OutputSocket extends Socket { - /** - * Indicates if the socket is being previewed - */ - private boolean previewed = false; - - /** - * @param eventBus The Guava {@link EventBus} used by the application. - * @param socketHint {@link #getSocketHint} - */ - public OutputSocket(EventBus eventBus, SocketHint socketHint) { - super(eventBus, socketHint, Direction.OUTPUT); + interface Factory { + OutputSocket create(SocketHint hint); } /** * @param previewed If true, this socket will be shown in a preview in the GUI. */ - public void setPreviewed(boolean previewed) { - boolean changed = previewed != this.previewed; - this.previewed = previewed; - - // Only send an event if the field was actually changed - if (changed) { - eventBus.post(new SocketPreviewChangedEvent(this)); - } - } + void setPreviewed(boolean previewed); /** * @return Whether or not this socket is shown in a preview in the GUI * @see #setPreviewed(boolean) d(boolean) */ - public boolean isPreviewed() { - return this.previewed; - } + boolean isPreviewed(); /** - * Resets the value of this socket to its initial value. + * Resets the value of this socket to its initial value. */ - public void resetValueToInitial() { - this.setValue(this.getSocketHint() - .createInitialValue() - .orElse(null)); - } - - - @Override - public String toString() { - return MoreObjects.toStringHelper(this) - .add("socketHint", getSocketHint()) - .add("value", getValue()) - .add("previewed", isPreviewed()) - .add("direction", getDirection()) - .toString(); - } + void resetValueToInitial(); } diff --git a/core/src/main/java/edu/wpi/grip/core/sockets/OutputSocketImpl.java b/core/src/main/java/edu/wpi/grip/core/sockets/OutputSocketImpl.java new file mode 100644 index 0000000000..29dcb7c6c8 --- /dev/null +++ b/core/src/main/java/edu/wpi/grip/core/sockets/OutputSocketImpl.java @@ -0,0 +1,82 @@ +package edu.wpi.grip.core.sockets; + + +import com.google.common.base.MoreObjects; +import com.google.common.eventbus.EventBus; +import com.google.inject.Inject; +import com.thoughtworks.xstream.annotations.XStreamAlias; + +import edu.wpi.grip.core.events.SocketPreviewChangedEvent; + +/** + * A concrete implementation of the {@link OutputSocket} + * + * @param The type that that this socket holds. + */ +@XStreamAlias("grip:Output") +public class OutputSocketImpl extends SocketImpl implements OutputSocket { + private final EventBus eventBus; + /** + * Indicates if the socket is being previewed + */ + private boolean previewed = false; + + public static class FactoryImpl implements OutputSocket.Factory { + private final EventBus eventBus; + + @Inject + FactoryImpl(EventBus eventBus) { + this.eventBus = eventBus; + } + + @Override + public OutputSocket create(SocketHint hint) { + return new OutputSocketImpl<>(eventBus, hint); + } + } + + /** + * @param eventBus The Guava {@link EventBus} used by the application. + * @param socketHint {@link #getSocketHint} + */ + OutputSocketImpl(EventBus eventBus, SocketHint socketHint) { + super(eventBus, socketHint, Direction.OUTPUT); + this.eventBus = eventBus; + } + + + + @Override + public void setPreviewed(boolean previewed) { + boolean changed = previewed != this.previewed; + this.previewed = previewed; + + // Only send an event if the field was actually changed + if (changed) { + eventBus.post(new SocketPreviewChangedEvent(this)); + } + } + + @Override + public boolean isPreviewed() { + return this.previewed; + } + + @Override + public void resetValueToInitial() { + this.setValue(this.getSocketHint() + .createInitialValue() + .orElse(null)); + } + + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("socketHint", getSocketHint()) + .add("value", getValue()) + .add("previewed", isPreviewed()) + .add("direction", getDirection()) + .toString(); + } +} diff --git a/core/src/main/java/edu/wpi/grip/core/sockets/Socket.java b/core/src/main/java/edu/wpi/grip/core/sockets/Socket.java index ad72c998c4..12ff445900 100644 --- a/core/src/main/java/edu/wpi/grip/core/sockets/Socket.java +++ b/core/src/main/java/edu/wpi/grip/core/sockets/Socket.java @@ -1,22 +1,14 @@ package edu.wpi.grip.core.sockets; -import com.google.common.base.MoreObjects; -import com.google.common.collect.ImmutableSet; -import com.google.common.eventbus.EventBus; + import edu.wpi.grip.core.Connection; import edu.wpi.grip.core.Source; import edu.wpi.grip.core.Step; -import edu.wpi.grip.core.events.SocketChangedEvent; -import edu.wpi.grip.core.events.SocketConnectedChangedEvent; import javax.annotation.Nullable; -import java.util.HashSet; import java.util.Optional; import java.util.Set; -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Preconditions.checkState; - /** * A Socket is an abstract wrapper for a value that can be updated and passed around operations. Sockets contain a set of hints * about the data they contain, as well as an actual value. @@ -26,164 +18,84 @@ * * @param The type of the value that this socket stores */ -public abstract class Socket { - public enum Direction {INPUT, OUTPUT} - - protected final EventBus eventBus; - private Optional step = Optional.empty(); - private Optional source = Optional.empty(); - private final Direction direction; - private final Set connections = new HashSet<>(); - private final SocketHint socketHint; - private Optional value = Optional.empty(); - - - /** - * @param eventBus The Guava {@link EventBus} used by the application. - * @param socketHint {@link #getSocketHint} - * @param direction The direction that this socket represents - */ - public Socket(EventBus eventBus, SocketHint socketHint, Direction direction) { - this.eventBus = checkNotNull(eventBus, "EventBus can not be null"); - this.socketHint = checkNotNull(socketHint, "Socket Hint can not be null"); - this.direction = checkNotNull(direction, "Direction can not be null"); - } +public interface Socket { + enum Direction {INPUT, OUTPUT} /** * @return A hint at what sort of data is in this socket. */ - public SocketHint getSocketHint() { - return socketHint; - } + SocketHint getSocketHint(); /** * Set the value of the socket using an {@link Optional}, and fire off a {@link edu.wpi.grip.core.events.SocketChangedEvent}. * * @param optionalValue The optional value to assign this socket to. */ - public final synchronized void setValueOptional(Optional optionalValue) { - checkNotNull(optionalValue, "The optional value can not be null"); - if (optionalValue.isPresent()) { - getSocketHint().getType().cast(optionalValue.get()); - } - this.value = optionalValue; - onValueChanged(); - eventBus.post(new SocketChangedEvent(this)); - } + void setValueOptional(Optional optionalValue); - /** - * Set the value of the socket, and fire off a {@link edu.wpi.grip.core.events.SocketChangedEvent}. - * - * @param value The value to store in this socket. Nullable. - */ - public final void setValue(@Nullable T value) { - setValueOptional(Optional.ofNullable(this.getSocketHint().getType().cast(value))); - } /** * Called when the value for the socket is reassigned. * Can be used by an implementing class to change behaviour when the value is changed. */ - protected void onValueChanged() { + default void onValueChanged() { /* no-op */ } /** - * @return The value currently stored in this socket. + * Set the value of the socket, and fire off a {@link edu.wpi.grip.core.events.SocketChangedEvent}. + * + * @param value The value to store in this socket. Nullable. */ - public Optional getValue() { - if (!this.value.isPresent()) { - this.value = socketHint.createInitialValue(); - } - return (Optional) this.value; + default void setValue(@Nullable T value) { + setValueOptional(Optional.ofNullable(getSocketHint().getType().cast(value))); } + /** + * @return The value currently stored in this socket. + */ + Optional getValue(); + /** * @param step The step that this socket is part of, if it's in a step. */ - public void setStep(Optional step) { - step.ifPresent(s -> checkState(!this.source.isPresent(), "Socket cannot be both in a step and a source")); - this.step = step; - } + void setStep(Optional step); /** * @return The step that this socket is part of * @see #getSource() */ - public Optional getStep() { - return step; - } + Optional getStep(); /** * @param source The source that this socket is part of, if it's in a source. */ - public void setSource(Optional source) { - source.ifPresent(s -> checkState(!this.step.isPresent(), "Socket cannot be both in a step and a source")); - this.source = source; - } + void setSource(Optional source); /** * @return The source that this socket is part of * @see #getStep() */ - public Optional getSource() { - return source; - } + Optional getSource(); /** * @return INPUT if this is the input to a step, OUTPUT if this is the output of a step * or source. */ - public Direction getDirection() { - return this.direction; - } + Direction getDirection(); /** * @return The set of connections that have this socket as an input or output */ - public Set getConnections() { - return ImmutableSet.copyOf(this.connections); - } + Set getConnections(); /** * @param connection The connection to add to this socket. */ - public void addConnection(Connection connection) { - checkNotNull(connection, "Can not remove null connection"); - this.connections.add(connection); - - if (this.connections.size() == 1) { - this.eventBus.post(new SocketConnectedChangedEvent(this)); - } - } + void addConnection(Connection connection); /** * @param connection The connection to remove from this socket. */ - public void removeConnection(Connection connection) { - checkNotNull(connection, "Can not remove null connection"); - onDisconnected(); - this.connections.remove(connection); - - if (this.connections.isEmpty()) { - this.eventBus.post(new SocketConnectedChangedEvent(this)); - } - } - - /** - * Reset the socket to its default value when it's no longer connected to anything. This prevents removed - * connections from continuing to have an effect on steps because they still hold references to the values they - * were connected to. - */ - protected void onDisconnected() { - } - - @Override - public String toString() { - return MoreObjects.toStringHelper(this) - .add("socketHint", getSocketHint()) - .add("value", getValue()) - .add("direction", getDirection()) - .toString(); - } + void removeConnection(Connection connection); } 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 9bfad9520e..1440c70a18 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,6 +1,7 @@ package edu.wpi.grip.core.sockets; +import com.google.common.reflect.TypeToken; import org.bytedeco.javacpp.opencv_core.Mat; import org.bytedeco.javacpp.opencv_core.Point; import org.bytedeco.javacpp.opencv_core.Size; @@ -38,6 +39,10 @@ public static SocketHint createPointSocketHint(final String identifier, f return createObjectSocketHintBuilder(identifier, Point.class, Point::new, withDefault).build(); } + public static SocketHint createPointSocketHint(final String identifier, int x, int y) { + return createObjectSocketHintBuilder(identifier, Point.class, () -> new Point(x, y), true).build(); + } + public static SocketHint createNumberSliderSocketHint(final String identifier, final Number number, final Number low, final Number high) { return createNumberSocketHintBuilder(identifier, number, new Number[]{low, high}).view(SocketHint.View.SLIDER).build(); @@ -52,7 +57,7 @@ public static SocketHint createNumberSpinnerSocketHint(final String iden return createNumberSocketHintBuilder(identifier, number).view(SocketHint.View.TEXT).build(); } - public static SocketHint createNumberListRangeSocketHint(final String identifier, final Number low, final Number high) { + public static SocketHint> createNumberListRangeSocketHint(final String identifier, final Number low, final Number high) { return createNumberListSocketHintBuilder(identifier, new Number[]{low, high}) .view(SocketHint.View.RANGE) .build(); @@ -65,6 +70,14 @@ public static SocketHint createTextSocketHint(final String identifier, f .view(SocketHint.View.TEXT) .build(); } + + public static SocketHint createCheckboxSocketHint(final String identifier, final Boolean initialValue) { + return new SocketHint.Builder<>(Boolean.class) + .identifier(identifier) + .initialValue(initialValue) + .view(SocketHint.View.CHECKBOX) + .build(); + } } /** @@ -127,8 +140,9 @@ private static SocketHint.Builder createObjectSocketHintBuilder(final Str else return builder; } - private static SocketHint.Builder createNumberListSocketHintBuilder(final String identifier, final Number[] domain) { - return new SocketHint.Builder<>(List.class).identifier(identifier) + @SuppressWarnings("unchecked") + private static SocketHint.Builder> createNumberListSocketHintBuilder(final String identifier, final Number[] domain) { + return new SocketHint.Builder<>((Class>) new TypeToken>(){}.getRawType()).identifier(identifier) .initialValueSupplier(() -> new ArrayList<>(Arrays.asList(domain))) .domain(new List[]{Arrays.asList(domain)}); } diff --git a/core/src/main/java/edu/wpi/grip/core/sockets/SocketImpl.java b/core/src/main/java/edu/wpi/grip/core/sockets/SocketImpl.java new file mode 100644 index 0000000000..7652b9b7fc --- /dev/null +++ b/core/src/main/java/edu/wpi/grip/core/sockets/SocketImpl.java @@ -0,0 +1,129 @@ +package edu.wpi.grip.core.sockets; + + +import com.google.common.base.MoreObjects; +import com.google.common.collect.ImmutableSet; +import com.google.common.eventbus.EventBus; +import edu.wpi.grip.core.Connection; +import edu.wpi.grip.core.Source; +import edu.wpi.grip.core.Step; +import edu.wpi.grip.core.events.SocketChangedEvent; +import edu.wpi.grip.core.events.SocketConnectedChangedEvent; + +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +/** + * A concrete implementation of {@link Socket} + * @param The type of the value that this socket stores + */ +public class SocketImpl implements Socket { + private final EventBus eventBus; + private Optional step = Optional.empty(); + private Optional source = Optional.empty(); + private final Direction direction; + private final Set connections = new HashSet<>(); + private final SocketHint socketHint; + private Optional value = Optional.empty(); + + + /** + * @param eventBus The Guava {@link EventBus} used by the application. + * @param socketHint {@link #getSocketHint} + * @param direction The direction that this socket represents + */ + SocketImpl(EventBus eventBus, SocketHint socketHint, Direction direction) { + this.eventBus = checkNotNull(eventBus, "EventBus can not be null"); + this.socketHint = checkNotNull(socketHint, "Socket Hint can not be null"); + this.direction = checkNotNull(direction, "Direction can not be null"); + } + + @Override + public SocketHint getSocketHint() { + return socketHint; + } + + @Override + public synchronized void setValueOptional(Optional optionalValue) { + checkNotNull(optionalValue, "The optional value can not be null"); + if (optionalValue.isPresent()) { + getSocketHint().getType().cast(optionalValue.get()); + } + this.value = optionalValue; + onValueChanged(); + eventBus.post(new SocketChangedEvent(this)); + } + + @Override + public Optional getValue() { + if (!this.value.isPresent()) { + this.value = socketHint.createInitialValue(); + } + return (Optional) this.value; + } + + @Override + public void setStep(Optional step) { + step.ifPresent(s -> checkState(!this.source.isPresent(), "Socket cannot be both in a step and a source")); + this.step = step; + } + + @Override + public void setSource(Optional source) { + source.ifPresent(s -> checkState(!this.step.isPresent(), "Socket cannot be both in a step and a source")); + this.source = source; + } + + @Override + public Optional getStep() { + return step; + } + + @Override + public Optional getSource() { + return source; + } + + @Override + public Direction getDirection() { + return this.direction; + } + + @Override + public Set getConnections() { + return ImmutableSet.copyOf(this.connections); + } + + @Override + public void addConnection(Connection connection) { + checkNotNull(connection, "Can not remove null connection"); + this.connections.add(connection); + + if (this.connections.size() == 1) { + this.eventBus.post(new SocketConnectedChangedEvent(this)); + } + } + + @Override + public void removeConnection(Connection connection) { + checkNotNull(connection, "Can not remove null connection"); + this.connections.remove(connection); + + if (this.connections.isEmpty()) { + this.eventBus.post(new SocketConnectedChangedEvent(this)); + } + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("socketHint", getSocketHint()) + .add("value", getValue()) + .add("direction", getDirection()) + .toString(); + } +} diff --git a/core/src/main/java/edu/wpi/grip/core/sockets/SocketsProvider.java b/core/src/main/java/edu/wpi/grip/core/sockets/SocketsProvider.java deleted file mode 100644 index 21bde169b6..0000000000 --- a/core/src/main/java/edu/wpi/grip/core/sockets/SocketsProvider.java +++ /dev/null @@ -1,28 +0,0 @@ -package edu.wpi.grip.core.sockets; - -import edu.wpi.grip.core.Operation; -import edu.wpi.grip.core.Step; - -import static com.google.common.base.Preconditions.checkNotNull; - -/** - * Provides a pairing of a list of {@link InputSocket} and {@link OutputSocket} - * that an {@link Operation} produces when a {@link Step} is initialized. - */ -public final class SocketsProvider { - private final InputSocket[] inputSockets; - private final OutputSocket[] outputSockets; - - public SocketsProvider(InputSocket[] inputSockets, OutputSocket[] outputSockets) { - this.inputSockets = checkNotNull(inputSockets, "InputSockets cannot be null"); - this.outputSockets = checkNotNull(outputSockets, "OutputSockets cannot be null"); - } - - public final InputSocket[] inputSockets() { - return inputSockets; - } - - public final OutputSocket[] outputSockets() { - return outputSockets; - } -} diff --git a/core/src/main/java/edu/wpi/grip/core/sources/CameraSource.java b/core/src/main/java/edu/wpi/grip/core/sources/CameraSource.java index b978b5594f..ee234f5db2 100644 --- a/core/src/main/java/edu/wpi/grip/core/sources/CameraSource.java +++ b/core/src/main/java/edu/wpi/grip/core/sources/CameraSource.java @@ -2,18 +2,19 @@ import com.google.common.base.StandardSystemProperty; +import com.google.common.collect.ImmutableList; import com.google.common.eventbus.EventBus; import com.google.common.eventbus.Subscribe; import com.google.common.util.concurrent.MoreExecutors; import com.google.inject.assistedinject.Assisted; import com.google.inject.assistedinject.AssistedInject; import com.thoughtworks.xstream.annotations.XStreamAlias; -import edu.wpi.grip.core.sockets.OutputSocket; -import edu.wpi.grip.core.sockets.SocketHint; -import edu.wpi.grip.core.sockets.SocketHints; import edu.wpi.grip.core.Source; import edu.wpi.grip.core.events.SourceHasPendingUpdateEvent; import edu.wpi.grip.core.events.SourceRemovedEvent; +import edu.wpi.grip.core.sockets.OutputSocket; +import edu.wpi.grip.core.sockets.SocketHint; +import edu.wpi.grip.core.sockets.SocketHints; import edu.wpi.grip.core.util.ExceptionWitness; import edu.wpi.grip.core.util.service.AutoRestartingService; import edu.wpi.grip.core.util.service.LoggingListener; @@ -26,6 +27,7 @@ import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; +import java.util.List; import java.util.Optional; import java.util.Properties; import java.util.concurrent.Executor; @@ -137,10 +139,11 @@ public FrameGrabber create(String addressProperty) throws MalformedURLException @AssistedInject CameraSource( final EventBus eventBus, + final OutputSocket.Factory outputSocketFactory, final FrameGrabberFactory grabberFactory, final ExceptionWitness.Factory exceptionWitnessFactory, @Assisted final int deviceNumber) throws IOException { - this(eventBus, grabberFactory, exceptionWitnessFactory, createProperties(deviceNumber)); + this(eventBus, outputSocketFactory, grabberFactory, exceptionWitnessFactory, createProperties(deviceNumber)); } /** @@ -152,10 +155,11 @@ public FrameGrabber create(String addressProperty) throws MalformedURLException @AssistedInject CameraSource( final EventBus eventBus, + final OutputSocket.Factory outputSocketFactory, final FrameGrabberFactory grabberFactory, final ExceptionWitness.Factory exceptionWitnessFactory, @Assisted final String address) throws IOException { - this(eventBus, grabberFactory, exceptionWitnessFactory, createProperties(address)); + this(eventBus, outputSocketFactory, grabberFactory, exceptionWitnessFactory, createProperties(address)); } /** @@ -164,13 +168,14 @@ public FrameGrabber create(String addressProperty) throws MalformedURLException @AssistedInject CameraSource( final EventBus eventBus, + final OutputSocket.Factory outputSocketFactory, final FrameGrabberFactory grabberFactory, final ExceptionWitness.Factory exceptionWitnessFactory, @Assisted final Properties properties) throws MalformedURLException { super(exceptionWitnessFactory); this.eventBus = eventBus; - this.frameOutputSocket = new OutputSocket<>(eventBus, imageOutputHint); - this.frameRateOutputSocket = new OutputSocket<>(eventBus, frameRateOutputHint); + this.frameOutputSocket = outputSocketFactory.create(imageOutputHint); + this.frameRateOutputSocket = outputSocketFactory.create(frameRateOutputHint); this.properties = properties; final String deviceNumberProperty = properties.getProperty(DEVICE_NUMBER_PROPERTY); @@ -237,8 +242,11 @@ public String getName() { } @Override - public OutputSocket[] createOutputSockets() { - return new OutputSocket[]{frameOutputSocket, frameRateOutputSocket}; + public List createOutputSockets() { + return ImmutableList.of( + frameOutputSocket, + frameRateOutputSocket + ); } @Override diff --git a/core/src/main/java/edu/wpi/grip/core/sources/ImageFileSource.java b/core/src/main/java/edu/wpi/grip/core/sources/ImageFileSource.java index b3efa34a3e..87d0bbec07 100644 --- a/core/src/main/java/edu/wpi/grip/core/sources/ImageFileSource.java +++ b/core/src/main/java/edu/wpi/grip/core/sources/ImageFileSource.java @@ -1,14 +1,15 @@ package edu.wpi.grip.core.sources; +import com.google.common.collect.ImmutableList; import com.google.common.eventbus.EventBus; import com.google.common.io.Files; import com.google.inject.assistedinject.Assisted; import com.google.inject.assistedinject.AssistedInject; import com.thoughtworks.xstream.annotations.XStreamAlias; +import edu.wpi.grip.core.Source; import edu.wpi.grip.core.sockets.OutputSocket; import edu.wpi.grip.core.sockets.SocketHint; import edu.wpi.grip.core.sockets.SocketHints; -import edu.wpi.grip.core.Source; import edu.wpi.grip.core.util.ExceptionWitness; import edu.wpi.grip.core.util.ImageLoadingUtility; import org.bytedeco.javacpp.opencv_core.Mat; @@ -18,6 +19,7 @@ import java.io.IOException; import java.net.URLDecoder; import java.nio.file.Paths; +import java.util.List; import java.util.Properties; import static com.google.common.base.Preconditions.checkNotNull; @@ -51,29 +53,32 @@ public interface Factory { @AssistedInject ImageFileSource( final EventBus eventBus, + final OutputSocket.Factory outputSocketFactory, final ExceptionWitness.Factory exceptionWitnessFactory, @Assisted final File file) { - this(eventBus, exceptionWitnessFactory, URLDecoder.decode(Paths.get(file.toURI()).toString())); + this(eventBus, outputSocketFactory, exceptionWitnessFactory, URLDecoder.decode(Paths.get(file.toURI()).toString())); } @AssistedInject ImageFileSource( final EventBus eventBus, + final OutputSocket.Factory outputSocketFactory, final ExceptionWitness.Factory exceptionWitnessFactory, @Assisted final Properties properties) { - this(eventBus, exceptionWitnessFactory, properties.getProperty(PATH_PROPERTY)); + this(eventBus, outputSocketFactory, exceptionWitnessFactory, properties.getProperty(PATH_PROPERTY)); } private ImageFileSource( final EventBus eventBus, + final OutputSocket.Factory outputSocketFactory, final ExceptionWitness.Factory exceptionWitnessFactory, final String path) { super(exceptionWitnessFactory); this.eventBus = checkNotNull(eventBus, "Event Bus was null."); this.path = checkNotNull(path, "Path can not be null"); this.name = Files.getNameWithoutExtension(this.path); - this.outputSocket = new OutputSocket<>(eventBus, imageOutputHint); + this.outputSocket = outputSocketFactory.create(imageOutputHint); } /** @@ -93,8 +98,10 @@ public String getName() { } @Override - public OutputSocket[] createOutputSockets() { - return new OutputSocket[]{outputSocket}; + public List createOutputSockets() { + return ImmutableList.of( + outputSocket + ); } @Override diff --git a/core/src/main/java/edu/wpi/grip/core/sources/MultiImageFileSource.java b/core/src/main/java/edu/wpi/grip/core/sources/MultiImageFileSource.java index 59d607b265..75abd00a1a 100644 --- a/core/src/main/java/edu/wpi/grip/core/sources/MultiImageFileSource.java +++ b/core/src/main/java/edu/wpi/grip/core/sources/MultiImageFileSource.java @@ -1,11 +1,13 @@ package edu.wpi.grip.core.sources; +import com.google.common.collect.ImmutableList; import com.google.common.eventbus.EventBus; import com.google.common.math.IntMath; import com.google.inject.assistedinject.Assisted; import com.google.inject.assistedinject.AssistedInject; import com.thoughtworks.xstream.annotations.XStreamAlias; -import edu.wpi.grip.core.*; +import edu.wpi.grip.core.PreviousNext; +import edu.wpi.grip.core.Source; import edu.wpi.grip.core.events.SourceHasPendingUpdateEvent; import edu.wpi.grip.core.sockets.OutputSocket; import edu.wpi.grip.core.sockets.SocketHint; @@ -62,10 +64,11 @@ public interface Factory { @AssistedInject MultiImageFileSource( final EventBus eventBus, + final OutputSocket.Factory outputSocketFactory, final ExceptionWitness.Factory exceptionWitnessFactory, @Assisted final List files, @Assisted final int index) { - this(eventBus, exceptionWitnessFactory, files.stream() + this(eventBus, outputSocketFactory, exceptionWitnessFactory, files.stream() .map(file -> URLDecoder.decode(Paths.get(file.toURI()).toString())) .collect(Collectors.toList()).toArray(new String[files.size()]), index); } @@ -73,9 +76,10 @@ public interface Factory { @AssistedInject MultiImageFileSource( final EventBus eventBus, + final OutputSocket.Factory outputSocketFactory, final ExceptionWitness.Factory exceptionWitnessFactory, @Assisted final List files) { - this(eventBus, exceptionWitnessFactory, files, 0); + this(eventBus, outputSocketFactory, exceptionWitnessFactory, files, 0); } /** @@ -83,19 +87,21 @@ public interface Factory { */ @AssistedInject MultiImageFileSource(final EventBus eventBus, + final OutputSocket.Factory outputSocketFactory, final ExceptionWitness.Factory exceptionWitnessFactory, @Assisted final Properties properties) { - this(eventBus, exceptionWitnessFactory, pathsFromProperties(properties), indexFromProperties(properties)); + this(eventBus, outputSocketFactory, exceptionWitnessFactory, pathsFromProperties(properties), indexFromProperties(properties)); } private MultiImageFileSource( final EventBus eventBus, + final OutputSocket.Factory outputSocketFactory, final ExceptionWitness.Factory exceptionWitnessFactory, final String[] paths, final int index) { super(exceptionWitnessFactory); this.eventBus = eventBus; - this.outputSocket = new OutputSocket(eventBus, imageOutputHint); + this.outputSocket = outputSocketFactory.create(imageOutputHint); this.index = new AtomicInteger(checkElementIndex(index, paths.length, "File List Index")); this.paths = Arrays.asList(paths); } @@ -113,8 +119,10 @@ public String getName() { } @Override - protected OutputSocket[] createOutputSockets() { - return new OutputSocket[]{outputSocket}; + protected List createOutputSockets() { + return ImmutableList.of( + outputSocket + ); } @Override diff --git a/core/src/main/java/edu/wpi/grip/core/util/Icon.java b/core/src/main/java/edu/wpi/grip/core/util/Icon.java new file mode 100644 index 0000000000..6760eb1f5d --- /dev/null +++ b/core/src/main/java/edu/wpi/grip/core/util/Icon.java @@ -0,0 +1,64 @@ +package edu.wpi.grip.core.util; + +import java.io.InputStream; +import java.util.function.Supplier; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Utility class for fetching icon streams. + */ +public final class Icon { + private final Supplier streamSupplier; + + private Icon(Supplier streamSupplier) { + this.streamSupplier = streamSupplier; + } + + /** + * Get a newly constructed stream. + */ + public InputStream getStream() { + return streamSupplier.get(); + } + + /** + * Gets an image stream for an icon. + * + * @param path the directory where the icon is located + * @param name the name of the icon + * @param type the type of the icon (".png", ".jpg", etc.) + * @return a stream for the given icon, or {@code null} if no image by that name exists + */ + public static Icon iconStream(String path, String name, String type) { + checkNotNull(path); + checkNotNull(name); + checkNotNull(type); + return new Icon(() -> Icon.class.getResourceAsStream(path + name + type)); + } + + + /** + * Gets an image stream for an icon. + * Will look for the image at /edu/wpi/grip/ui/icons/{name}.{type} + * + * @param name the name of the icon + * @param type the type of the icon (".png", ".jpg", etc.) + * @return a stream for the given icon, or {@code null} if no image by that name exists + */ + public static Icon iconStream(String name, String type) { + return iconStream("/edu/wpi/grip/ui/icons/", name, type); + } + + /** + * Gets an image stream for an icon. + * Will look for the image at /edu/wpi/grip/ui/icons/{name}.png + * + * @param name the name of the icon + * @return a stream for the given icon, or {@code null} if no image by that name exists + */ + public static Icon iconStream(String name) { + return iconStream(name, ".png"); + } + +} diff --git a/core/src/test/java/edu/wpi/grip/core/AddOperation.java b/core/src/test/java/edu/wpi/grip/core/AddOperation.java index 1308fd7ac6..8eb086d795 100644 --- a/core/src/test/java/edu/wpi/grip/core/AddOperation.java +++ b/core/src/test/java/edu/wpi/grip/core/AddOperation.java @@ -1,48 +1,54 @@ package edu.wpi.grip.core; +import com.google.common.collect.ImmutableList; import com.google.common.eventbus.EventBus; import edu.wpi.grip.core.sockets.*; import org.bytedeco.javacpp.opencv_core; import org.bytedeco.javacpp.opencv_core.Mat; +import java.util.List; + /** * Performs the opencv add operation */ public class AddOperation implements Operation { + public static final OperationDescription DESCRIPTION = OperationDescription + .builder().name("OpenCV Add").summary("Compute the per-pixel sum of two images.").build(); private SocketHint aHint = SocketHints.Inputs.createMatSocketHint("a", false), bHint = SocketHints.Inputs.createMatSocketHint("b", false), sumHint = SocketHints.Inputs.createMatSocketHint("sum", true); - @Override - public String getName() { - return "OpenCV Add"; + private InputSocket a, b; + private OutputSocket sum; + + public AddOperation(EventBus eventBus) { + this(new MockInputSocketFactory(eventBus), new MockOutputSocketFactory(eventBus)); } - @Override - public String getDescription() { - return "Compute the per-pixel sum of two images."; + public AddOperation(InputSocket.Factory isf, OutputSocket.Factory osf) { + a = isf.create(aHint); + b = isf.create(bHint); + sum = osf.create(sumHint); } @Override - public InputSocket[] createInputSockets(EventBus eventBus) { - return new InputSocket[]{ - new InputSocket(eventBus, aHint), - new InputSocket(eventBus, bHint) - }; + public List getInputSockets() { + return ImmutableList.of( + a, b + ); } @Override - public OutputSocket[] createOutputSockets(EventBus eventBus) { - return new OutputSocket[]{ - new OutputSocket(eventBus, sumHint) - }; + public List getOutputSockets() { + return ImmutableList.of( + sum + ); } @Override - public void perform(InputSocket[] inputs, OutputSocket[] outputs) { - Socket a = inputs[0], b = inputs[1], sum = outputs[0]; + public void perform() { opencv_core.add(a.getValue().get(), b.getValue().get(), sum.getValue().get()); } } diff --git a/core/src/test/java/edu/wpi/grip/core/AdditionOperation.java b/core/src/test/java/edu/wpi/grip/core/AdditionOperation.java index d3af0e7d0d..ccc8137e77 100644 --- a/core/src/test/java/edu/wpi/grip/core/AdditionOperation.java +++ b/core/src/test/java/edu/wpi/grip/core/AdditionOperation.java @@ -1,42 +1,53 @@ package edu.wpi.grip.core; -import com.google.common.eventbus.EventBus; +import com.google.common.collect.ImmutableList; import edu.wpi.grip.core.sockets.InputSocket; import edu.wpi.grip.core.sockets.OutputSocket; import edu.wpi.grip.core.sockets.SocketHint; import edu.wpi.grip.core.sockets.SocketHints; +import java.util.List; + public class AdditionOperation implements Operation { + public static final OperationDescription DESCRIPTION = + OperationDescription.builder() + .name("Add") + .summary("Compute the sum of two doubles") + .build(); private SocketHint aHint = SocketHints.createNumberSocketHint("a", 0.0), bHint = SocketHints.createNumberSocketHint("b", 0.0), cHint = SocketHints.Outputs.createNumberSocketHint("c", 0.0); - @Override - public String getName() { - return "Add"; - } + private InputSocket a, b; + private OutputSocket c; - @Override - public String getDescription() { - return "Compute the sum of two doubles"; + public AdditionOperation(InputSocket.Factory isf, OutputSocket.Factory osf) { + a = isf.create(aHint); + b = isf.create(bHint); + c = osf.create(cHint); } @Override - public InputSocket[] createInputSockets(EventBus eventBus) { - return new InputSocket[]{new InputSocket<>(eventBus, aHint), new InputSocket<>(eventBus, bHint)}; + public List getInputSockets() { + return ImmutableList.of( + a, + b + ); } @Override - public OutputSocket[] createOutputSockets(EventBus eventBus) { - return new OutputSocket[]{new OutputSocket<>(eventBus, cHint)}; + public List getOutputSockets() { + return ImmutableList.of( + c + ); } @Override - public void perform(InputSocket[] inputs, OutputSocket[] outputs) { - InputSocket a = inputs[0], b = inputs[1]; - OutputSocket c = outputs[0]; - - c.setValue(a.getValue().get().doubleValue() + b.getValue().get().doubleValue()); + public void perform() { + double val_a = a.getValue().get().doubleValue(); + double val_b = b.getValue().get().doubleValue(); + double val_c = val_a + val_b; + c.setValue(val_c); } } diff --git a/core/src/test/java/edu/wpi/grip/core/ConnectionTest.java b/core/src/test/java/edu/wpi/grip/core/ConnectionTest.java index d59676d50c..94f1103589 100644 --- a/core/src/test/java/edu/wpi/grip/core/ConnectionTest.java +++ b/core/src/test/java/edu/wpi/grip/core/ConnectionTest.java @@ -2,10 +2,7 @@ import com.google.common.eventbus.EventBus; import edu.wpi.grip.core.events.ConnectionRemovedEvent; -import edu.wpi.grip.core.sockets.InputSocket; -import edu.wpi.grip.core.sockets.OutputSocket; -import edu.wpi.grip.core.sockets.SocketHint; -import edu.wpi.grip.core.sockets.SocketHints; +import edu.wpi.grip.core.sockets.*; import org.junit.Before; import org.junit.Test; @@ -27,14 +24,16 @@ private class MockPipeline extends Pipeline { @Before public void setUp() { eventBus = new EventBus(); + InputSocket.Factory isf = new MockInputSocketFactory(eventBus); + OutputSocket.Factory osf = new MockOutputSocketFactory(eventBus); fooHint = SocketHints.createNumberSocketHint("foo", 0.0); barHint = SocketHints.createNumberSocketHint("bar", 0.0); - foo = new OutputSocket<>(eventBus, fooHint); + foo = osf.create(fooHint); eventBus.register(foo); - bar = new InputSocket<>(eventBus, barHint); + bar = isf.create(barHint); eventBus.register(bar); } diff --git a/core/src/test/java/edu/wpi/grip/core/CoreSanityTest.java b/core/src/test/java/edu/wpi/grip/core/CoreSanityTest.java index fb244b0edd..506975e4e5 100644 --- a/core/src/test/java/edu/wpi/grip/core/CoreSanityTest.java +++ b/core/src/test/java/edu/wpi/grip/core/CoreSanityTest.java @@ -27,5 +27,9 @@ public CoreSanityTest() { setDefault(Service.Listener.class, new SingleActionListener(() -> { })); setDefault(ConnectionValidator.class, (outputSocket, inputSocket) -> true); + setDefault(OperationMetaData.class, + new OperationMetaData(OperationDescription.builder().name("").summary("").build(), + () -> null)); + setDefault(OperationDescription.class, OperationDescription.builder().name("").summary("").build()); } } diff --git a/core/src/test/java/edu/wpi/grip/core/MockOperation.java b/core/src/test/java/edu/wpi/grip/core/MockOperation.java index 53c572a791..084e277ec1 100644 --- a/core/src/test/java/edu/wpi/grip/core/MockOperation.java +++ b/core/src/test/java/edu/wpi/grip/core/MockOperation.java @@ -1,35 +1,31 @@ package edu.wpi.grip.core; -import com.google.common.eventbus.EventBus; +import com.google.common.collect.ImmutableList; import edu.wpi.grip.core.sockets.InputSocket; import edu.wpi.grip.core.sockets.OutputSocket; -import java.util.Optional; +import java.util.List; public class MockOperation implements Operation { - @Override - public String getName() { - return "Mock Operation"; - } - - @Override - public String getDescription() { - return "A mock operation description"; - } + public static final OperationDescription DESCRIPTION + = OperationDescription.builder() + .name("Mock Operation") + .summary("A mock operation summary") + .build(); @Override - public InputSocket[] createInputSockets(EventBus eventBus) { - return new InputSocket[0]; + public List getInputSockets() { + return ImmutableList.of(); } @Override - public OutputSocket[] createOutputSockets(EventBus eventBus) { - return new OutputSocket[0]; + public List getOutputSockets() { + return ImmutableList.of(); } @Override - public void perform(InputSocket[] inputs, OutputSocket[] outputs, Optional data) { + public void perform() { } } diff --git a/core/src/test/java/edu/wpi/grip/core/MockSource.java b/core/src/test/java/edu/wpi/grip/core/MockSource.java index 03fca7c9b5..390ce4d69d 100644 --- a/core/src/test/java/edu/wpi/grip/core/MockSource.java +++ b/core/src/test/java/edu/wpi/grip/core/MockSource.java @@ -1,9 +1,11 @@ package edu.wpi.grip.core; +import com.google.common.collect.ImmutableList; import edu.wpi.grip.core.sockets.OutputSocket; import java.io.IOException; +import java.util.List; import java.util.Properties; public class MockSource extends Source { @@ -18,8 +20,8 @@ public String getName() { } @Override - protected OutputSocket[] createOutputSockets() { - return new OutputSocket[0]; + protected List createOutputSockets() { + return ImmutableList.of(); } @Override diff --git a/core/src/test/java/edu/wpi/grip/core/MockStep.java b/core/src/test/java/edu/wpi/grip/core/MockStep.java index c379804386..d43d471a59 100644 --- a/core/src/test/java/edu/wpi/grip/core/MockStep.java +++ b/core/src/test/java/edu/wpi/grip/core/MockStep.java @@ -1,20 +1,19 @@ package edu.wpi.grip.core; import com.google.common.eventbus.EventBus; -import edu.wpi.grip.core.sockets.InputSocket; -import edu.wpi.grip.core.sockets.OutputSocket; import edu.wpi.grip.core.util.MockExceptionWitness; -import java.util.Optional; +import java.util.Collections; public class MockStep extends Step { public MockStep() { - super(null, new InputSocket[0], new OutputSocket[0], Optional.empty(), origin -> null); + super(null, MockOperation.DESCRIPTION, Collections.emptyList(), Collections.emptyList(), origin -> null); } public static Step createMockStepWithOperation() { final EventBus eventBus = new EventBus(); - return new Step.Factory(eventBus, origin -> new MockExceptionWitness(eventBus, origin)).create(new MockOperation()); + return new Step.Factory(origin -> new MockExceptionWitness(eventBus, origin)).create( + new OperationMetaData(MockOperation.DESCRIPTION, MockOperation::new)); } } diff --git a/core/src/test/java/edu/wpi/grip/core/OperationTest.java b/core/src/test/java/edu/wpi/grip/core/OperationTest.java index f3c4e8bd2d..b8a5bdfd89 100644 --- a/core/src/test/java/edu/wpi/grip/core/OperationTest.java +++ b/core/src/test/java/edu/wpi/grip/core/OperationTest.java @@ -2,25 +2,38 @@ import com.google.common.eventbus.EventBus; import edu.wpi.grip.core.sockets.InputSocket; +import edu.wpi.grip.core.sockets.MockInputSocketFactory; +import edu.wpi.grip.core.sockets.MockOutputSocketFactory; import edu.wpi.grip.core.sockets.OutputSocket; +import org.junit.Before; import org.junit.Test; +import java.util.List; + import static org.junit.Assert.assertEquals; public class OperationTest { - EventBus eventBus = new EventBus(); - Operation addition = new AdditionOperation(); + private Operation addition; + + @Before + public void setUp() { + EventBus eventBus = new EventBus(); + InputSocket.Factory isf = new MockInputSocketFactory(eventBus); + OutputSocket.Factory osf = new MockOutputSocketFactory(eventBus); + addition = new AdditionOperation(isf, osf); + } @Test public void testOperation() throws Exception { - InputSocket[] inputs = addition.createInputSockets(eventBus); - OutputSocket[] outputs = addition.createOutputSockets(eventBus); - InputSocket a = inputs[0], b = inputs[1]; - OutputSocket c = outputs[0]; + List inputs = addition.getInputSockets(); + List outputs = addition.getOutputSockets(); + InputSocket a = inputs.get(0); + InputSocket b = inputs.get(1); + OutputSocket c = outputs.get(0); a.setValue(1234.0); b.setValue(5678.0); - addition.perform(inputs, outputs); + addition.perform(); assertEquals((Double) (1234.0 + 5678.0), c.getValue().get()); } diff --git a/core/src/test/java/edu/wpi/grip/core/PaletteTest.java b/core/src/test/java/edu/wpi/grip/core/PaletteTest.java index caa7b70e90..bd00daacd9 100644 --- a/core/src/test/java/edu/wpi/grip/core/PaletteTest.java +++ b/core/src/test/java/edu/wpi/grip/core/PaletteTest.java @@ -2,8 +2,6 @@ import com.google.common.eventbus.EventBus; import edu.wpi.grip.core.events.OperationAddedEvent; -import edu.wpi.grip.core.sockets.InputSocket; -import edu.wpi.grip.core.sockets.OutputSocket; import org.junit.Before; import org.junit.Test; @@ -16,38 +14,18 @@ public class PaletteTest { private Palette palette; private EventBus eventBus; - private Operation operation; + private OperationMetaData operation; @Before public void setUp() { eventBus = new EventBus(); - palette = new Palette(eventBus); + palette = new Palette(); eventBus.register(palette); - operation = new Operation() { - @Override - public String getName() { - return "Find Target"; - } - - @Override - public String getDescription() { - return ""; - } - - @Override - public InputSocket[] createInputSockets(EventBus eventBus) { - return new InputSocket[0]; - } - - @Override - public OutputSocket[] createOutputSockets(EventBus eventBus) { - return new OutputSocket[0]; - } - - @Override - public void perform(InputSocket[] inputs, OutputSocket[] outputs) { - } - }; + operation = new OperationMetaData(OperationDescription.builder() + .name("Find Target") + .summary("") + .build(), + () -> null); } @Test diff --git a/core/src/test/java/edu/wpi/grip/core/PipelineRunnerTest.java b/core/src/test/java/edu/wpi/grip/core/PipelineRunnerTest.java index 7784c416e0..9dcc3dfc5e 100644 --- a/core/src/test/java/edu/wpi/grip/core/PipelineRunnerTest.java +++ b/core/src/test/java/edu/wpi/grip/core/PipelineRunnerTest.java @@ -10,8 +10,8 @@ import edu.wpi.grip.core.events.RunPipelineEvent; import edu.wpi.grip.core.events.StopPipelineEvent; import edu.wpi.grip.core.sockets.InputSocket; +import edu.wpi.grip.core.sockets.MockInputSocket; import edu.wpi.grip.core.sockets.OutputSocket; -import edu.wpi.grip.core.sockets.SocketHints; import edu.wpi.grip.core.util.MockExceptionWitness; import net.jodah.concurrentunit.Waiter; import org.junit.After; @@ -21,7 +21,7 @@ import org.junit.runner.RunWith; import java.io.IOException; -import java.util.Optional; +import java.util.List; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -92,10 +92,12 @@ public void testRunningOperationThatThrowsExceptionWillNotPropagate() throws Tim final String illegalAugmentExceptionMessage = "Kersplat!"; class OperationThatThrowsExceptionOnPerform implements SimpleOperation { @Override - public void perform(InputSocket[] inputs, OutputSocket[] outputs) { + public void perform() { throw new IllegalArgumentException(illegalAugmentExceptionMessage); } } + final Operation operation = new OperationThatThrowsExceptionOnPerform(); + final OperationMetaData operationMetaData = new OperationMetaData(OperationDescription.builder().name("OperationThatThrowsExceptionOnPerform").build(), () -> operation); class ExceptionEventReceiver { private int callCount = 0; private ExceptionEvent event; @@ -110,7 +112,7 @@ public void onException(ExceptionEvent event) { eventBus.register(exceptionEventReceiver); eventBus.register(new RenderWaiterResumer(renderWaiter)); - final Step throwingStep = new Step.Factory(eventBus, MockExceptionWitness.simpleFactory(eventBus)).create(new OperationThatThrowsExceptionOnPerform()); + final Step throwingStep = new Step.Factory(MockExceptionWitness.simpleFactory(eventBus)).create(operationMetaData); final PipelineRunner runner = new PipelineRunner(eventBus, () -> ImmutableList.of(), () -> ImmutableList.of(throwingStep)); runner.addListener(failureListener, MoreExecutors.directExecutor()); @@ -148,7 +150,7 @@ public void setUp() { renderWaiter = new Waiter(); sourceCounter = new RunSourceCounter(); operationCounter = new RunCounterOperation(); - runCounterStep = new Step.Factory(null, MockExceptionWitness.MOCK_FACTORY).create(operationCounter); + runCounterStep = new Step.Factory(MockExceptionWitness.MOCK_FACTORY).create(new OperationMetaData(RunCounterOperation.DESCRIPTION, () -> operationCounter)); failureListener = new FailureListener(); } @@ -202,7 +204,9 @@ public boolean pipelineShouldRun() { @Test @SuppressWarnings("PMD.AvoidDuplicateLiterals") public void testRemovedStepWillNotRun() { - final PipelineRunner runner = new PipelineRunner(eventBus, () -> ImmutableList.of(), () -> ImmutableList.of(runCounterStep)); + final PipelineRunner runner = new PipelineRunner(eventBus, + () -> ImmutableList.of(), + () -> ImmutableList.of(runCounterStep)); runner.addListener(failureListener, MoreExecutors.directExecutor()); runner.startAsync().awaitRunning(); @@ -309,46 +313,43 @@ protected boolean updateOutputSockets() { } static class RunCounterOperation implements SimpleOperation { + private static final OperationDescription DESCRIPTION = OperationDescription.builder().name("Simple").build(); private int performCount = 0; private int cleanUpCount = 0; @Override - public void perform(InputSocket[] inputSockets, OutputSocket[] outputSockets) { + public void perform() { performCount++; } @Override - public void cleanUp(InputSocket[] inputs, OutputSocket[] outputs, Optional data) { + public void cleanUp() { cleanUpCount++; } } interface SimpleOperation extends Operation { - @Override - default String getName() { - return null; - } - - @Override - default String getDescription() { - return null; - } + OperationDescription DESCRIPTION = OperationDescription.builder() + .name("Simple Operation") + .summary("A simple operation for testing") + .build(); @Override - default InputSocket[] createInputSockets(EventBus eventBus) { - return new InputSocket[]{ - new InputSocket(new EventBus(), SocketHints.createBooleanSocketHint("Test val", false)) { + default List getInputSockets() { + return ImmutableList.of( + new MockInputSocket("Test Socket") { @Override public boolean dirtied() { return true; } } - }; + ); } + @Override - default OutputSocket[] createOutputSockets(EventBus eventBus) { - return new OutputSocket[0]; + default List getOutputSockets() { + return ImmutableList.of(); } } @@ -369,4 +370,4 @@ public synchronized void throwIfProblemPresent() throws Throwable { } } } -} \ No newline at end of file +} diff --git a/core/src/test/java/edu/wpi/grip/core/PipelineTest.java b/core/src/test/java/edu/wpi/grip/core/PipelineTest.java index b03f681106..6fe89d8b00 100644 --- a/core/src/test/java/edu/wpi/grip/core/PipelineTest.java +++ b/core/src/test/java/edu/wpi/grip/core/PipelineTest.java @@ -26,7 +26,9 @@ public class PipelineTest { private Step.Factory stepFactory; private EventBus eventBus; private Pipeline pipeline; - private Operation addition; + private OperationMetaData additionMeta; + private InputSocket.Factory isf; + private OutputSocket.Factory osf; private class MockConnection extends Connection { @@ -47,7 +49,9 @@ public void setUp() { stepFactory = injector.getInstance(Step.Factory.class); eventBus = injector.getInstance(EventBus.class); pipeline = injector.getInstance(Pipeline.class); - addition = new AdditionOperation(); + isf = injector.getInstance(InputSocket.Factory.class); + osf = injector.getInstance(OutputSocket.Factory.class); + additionMeta = new OperationMetaData(AdditionOperation.DESCRIPTION, () -> new AdditionOperation(isf, osf)); } @After @@ -189,8 +193,9 @@ public void testRemoveConnection() { @Test @SuppressWarnings("unchecked") public void testPipeline() { - Step step1 = stepFactory.create(addition); - Step step2 = stepFactory.create(addition); + Step step1 = stepFactory.create(new OperationMetaData(AdditionOperation.DESCRIPTION, () -> new AdditionOperation(isf, osf))); + Step step2 = stepFactory.create(new OperationMetaData(AdditionOperation.DESCRIPTION, () -> new AdditionOperation(isf, osf))); + InputSocket a1 = (InputSocket) step1.getInputSockets().get(0); InputSocket b1 = (InputSocket) step1.getInputSockets().get(1); OutputSocket sum1 = (OutputSocket) step1.getOutputSockets().get(0); @@ -224,8 +229,9 @@ public void testPipeline() { @Test @SuppressWarnings("unchecked") public void testPipelineRemoved() { - Step step1 = stepFactory.create(addition); - Step step2 = stepFactory.create(addition); + Step step1 = stepFactory.create(new OperationMetaData(AdditionOperation.DESCRIPTION, () -> new AdditionOperation(isf, osf))); + Step step2 = stepFactory.create(new OperationMetaData(AdditionOperation.DESCRIPTION, () -> new AdditionOperation(isf, osf))); + InputSocket a1 = (InputSocket) step1.getInputSockets().get(0); InputSocket b1 = (InputSocket) step1.getInputSockets().get(1); OutputSocket sum1 = (OutputSocket) step1.getOutputSockets().get(0); @@ -257,8 +263,8 @@ public void testPipelineRemoved() { @Test @SuppressWarnings("unchecked") public void testCannotConnectBackwards() { - Step step1 = stepFactory.create(addition); - Step step2 = stepFactory.create(addition); + Step step1 = stepFactory.create(additionMeta); + Step step2 = stepFactory.create(additionMeta); InputSocket a1 = (InputSocket) step1.getInputSockets().get(0); OutputSocket sum2 = (OutputSocket) step2.getOutputSockets().get(0); @@ -270,8 +276,8 @@ public void testCannotConnectBackwards() { @Test @SuppressWarnings("unchecked") public void testCannotConnectIncompatibleTypes() { - InputSocket a = new InputSocket<>(eventBus, SocketHints.createNumberSocketHint("a", 0.0)); - OutputSocket b = new OutputSocket<>(eventBus, new SocketHint.Builder<>(String.class).identifier("b").initialValue("").build()); + InputSocket a = isf.create(SocketHints.createNumberSocketHint("a", 0.0)); + OutputSocket b = osf.create(new SocketHint.Builder<>(String.class).identifier("b").initialValue("").build()); assertFalse("Should not be able to connect incompatible types", pipeline.canConnect((OutputSocket) b, (InputSocket) a)); } diff --git a/core/src/test/java/edu/wpi/grip/core/PythonTest.java b/core/src/test/java/edu/wpi/grip/core/PythonTest.java index b98dd65840..10a89d694e 100644 --- a/core/src/test/java/edu/wpi/grip/core/PythonTest.java +++ b/core/src/test/java/edu/wpi/grip/core/PythonTest.java @@ -1,9 +1,15 @@ package edu.wpi.grip.core; import com.google.common.eventbus.EventBus; -import edu.wpi.grip.core.operations.PythonScriptOperation; +import com.google.inject.Guice; +import com.google.inject.Injector; +import edu.wpi.grip.core.operations.PythonScriptFile; +import edu.wpi.grip.core.sockets.InputSocket; +import edu.wpi.grip.core.sockets.OutputSocket; import edu.wpi.grip.core.sockets.Socket; import edu.wpi.grip.core.util.MockExceptionWitness; +import edu.wpi.grip.util.GRIPCoreTestModule; +import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -12,17 +18,25 @@ public class PythonTest { private static final int a = 1234, b = 5678; + private GRIPCoreTestModule testModule; private EventBus eventBus; + private InputSocket.Factory isf; + private OutputSocket.Factory osf; @Before - public void setUp () { - eventBus = new EventBus(); + public void setUp() { + testModule = new GRIPCoreTestModule(); + testModule.setUp(); + final Injector injector = Guice.createInjector(testModule); + eventBus = injector.getInstance(EventBus.class); + isf = injector.getInstance(InputSocket.Factory.class); + osf = injector.getInstance(OutputSocket.Factory.class); } @Test public void testPython() throws Exception { - Operation addition = new PythonScriptOperation(PythonTest.class.getResource("/edu/wpi/grip/scripts/addition.py")); - Step step = new Step.Factory(eventBus, (origin) -> new MockExceptionWitness(eventBus, origin)).create(addition); + PythonScriptFile pythonScriptFile = PythonScriptFile.create(PythonTest.class.getResource("/edu/wpi/grip/scripts/addition.py")); + Step step = new Step.Factory((origin) -> new MockExceptionWitness(eventBus, origin)).create(pythonScriptFile.toOperationMetaData(isf, osf)); Socket aSocket = step.getInputSockets().get(0); Socket bSocket = step.getInputSockets().get(1); Socket sumSocket = step.getOutputSockets().get(0); @@ -37,15 +51,16 @@ public void testPython() throws Exception { @Test public void testPythonAdditionFromString() throws Exception { - Operation additionFromString = new PythonScriptOperation("import edu.wpi.grip.core.sockets as grip\nimport java" + - ".lang.Integer\n\ninputs = [\n grip.SocketHints.createNumberSocketHint(\"a\", 0.0),\n grip.SocketHints.createNumberSocketHint(" + + PythonScriptFile pythonScriptFile = PythonScriptFile.create("import edu.wpi.grip.core.sockets as grip\nimport java" + + ".lang.Integer\n\nname = \"Addition Operation\"\n\ninputs = [\n grip.SocketHints.createNumberSocketHint(\"a\", 0.0),\n grip.SocketHints.createNumberSocketHint(" + "\"b\", 0.0),\n]\n\noutputs = [\n grip.SocketHints.Outputs.createNumberSocketHint(\"sum\", 0.0)," + "\n]\n\ndef perform(a, b):\n return a + b\n"); - Step step = new Step.Factory(eventBus, (origin) -> new MockExceptionWitness(eventBus, origin)).create(additionFromString); + Step step = new Step.Factory((origin) -> new MockExceptionWitness(eventBus, origin)).create(pythonScriptFile.toOperationMetaData(isf, osf)); Socket aSocket = step.getInputSockets().get(0); Socket bSocket = step.getInputSockets().get(1); Socket sumSocket = step.getOutputSockets().get(0); + aSocket.setValue(a); bSocket.setValue(b); @@ -56,8 +71,9 @@ public void testPythonAdditionFromString() throws Exception { @Test public void testPythonMultipleOutputs() throws Exception { - Operation additionSubtraction = new PythonScriptOperation(PythonTest.class.getResource("/edu/wpi/grip/scripts/addition-subtraction.py")); - Step step = new Step.Factory(eventBus, (origin) -> new MockExceptionWitness(eventBus, origin)).create(additionSubtraction); + Step step = new Step.Factory((origin) -> new MockExceptionWitness(eventBus, origin)).create( + PythonScriptFile.create(PythonTest.class.getResource("/edu/wpi/grip/scripts/addition-subtraction.py")).toOperationMetaData(isf, osf) + ); Socket aSocket = step.getInputSockets().get(0); Socket bSocket = step.getInputSockets().get(1); Socket sumSocket = step.getOutputSockets().get(0); @@ -74,8 +90,9 @@ public void testPythonMultipleOutputs() throws Exception { @Test public void testPythonWrongOutputCount() throws Exception { - Operation additionWrongOutputCount = new PythonScriptOperation(PythonTest.class.getResource("/edu/wpi/grip/scripts/addition-wrong-output-count.py")); - Step step = new Step.Factory(eventBus, (origin) -> new MockExceptionWitness(eventBus, origin)).create(additionWrongOutputCount); + Step step = new Step.Factory((origin) -> new MockExceptionWitness(eventBus, origin)).create( + PythonScriptFile.create(PythonTest.class.getResource("/edu/wpi/grip/scripts/addition-wrong-output-count.py")).toOperationMetaData(isf, osf) + ); Socket aSocket = step.getInputSockets().get(0); Socket bSocket = step.getInputSockets().get(1); Socket sumSocket = step.getOutputSockets().get(0); @@ -88,8 +105,8 @@ public void testPythonWrongOutputCount() throws Exception { @Test public void testPythonWrongOutputType() throws Exception { - Operation additionWrongOutputType = new PythonScriptOperation(PythonTest.class.getResource("/edu/wpi/grip/scripts/addition-wrong-output-type.py")); - Step step = new Step.Factory(eventBus, (origin) -> new MockExceptionWitness(eventBus, origin)).create(additionWrongOutputType); + Step step = new Step.Factory((origin) -> new MockExceptionWitness(eventBus, origin)).create( + PythonScriptFile.create(PythonTest.class.getResource("/edu/wpi/grip/scripts/addition-wrong-output-type.py")).toOperationMetaData(isf, osf)); Socket aSocket = step.getInputSockets().get(0); Socket bSocket = step.getInputSockets().get(1); Socket sumSocket = step.getOutputSockets().get(0); @@ -104,25 +121,34 @@ public void testPythonWrongOutputType() throws Exception { @Test public void testDefaultName() throws Exception { - Operation addition = new PythonScriptOperation(PythonTest.class.getResource("/edu/wpi/grip/scripts/addition.py")); - assertEquals("addition.py", addition.getName()); + OperationDescription additionDescription = PythonScriptFile.create(PythonTest.class.getResource("/edu/wpi/grip/scripts/addition.py")) + .toOperationMetaData(isf, osf).getDescription(); + assertEquals("Name incorrect", "addition.py", additionDescription.name()); } @Test public void testDefaultDescription() throws Exception { - Operation addition = new PythonScriptOperation(PythonTest.class.getResource("/edu/wpi/grip/scripts/addition.py")); - assertEquals("", addition.getDescription()); + OperationDescription additionDescription = PythonScriptFile.create(PythonTest.class.getResource("/edu/wpi/grip/scripts/addition.py")) + .toOperationMetaData(isf, osf).getDescription(); + assertEquals("Description incorrect", "", additionDescription.summary()); } @Test public void testName() throws Exception { - Operation addition = new PythonScriptOperation(PythonTest.class.getResource("/edu/wpi/grip/scripts/addition-with-name-and-description.py")); - assertEquals("Add", addition.getName()); + OperationDescription additionDescription = PythonScriptFile.create(PythonTest.class.getResource("/edu/wpi/grip/scripts/addition-with-name-and-description.py")) + .toOperationMetaData(isf, osf).getDescription(); + assertEquals("Name incorrect", "Add", additionDescription.name()); } @Test - public void testDescription() throws Exception { - Operation addition = new PythonScriptOperation(PythonTest.class.getResource("/edu/wpi/grip/scripts/addition-with-name-and-description.py")); - assertEquals("Compute the sum of two integers", addition.getDescription()); + public void testSummary() throws Exception { + OperationDescription additionDescription = PythonScriptFile.create(PythonTest.class.getResource("/edu/wpi/grip/scripts/addition-with-name-and-description.py")) + .toOperationMetaData(isf, osf).getDescription(); + assertEquals("Description incorrect", "Compute the sum of two integers", additionDescription.summary()); + } + + @After + public void tearDown() { + testModule.tearDown(); } } diff --git a/core/src/test/java/edu/wpi/grip/core/StepTest.java b/core/src/test/java/edu/wpi/grip/core/StepTest.java index 0404b10337..2ab8ac2b2e 100644 --- a/core/src/test/java/edu/wpi/grip/core/StepTest.java +++ b/core/src/test/java/edu/wpi/grip/core/StepTest.java @@ -1,8 +1,14 @@ package edu.wpi.grip.core; import com.google.common.eventbus.EventBus; +import com.google.inject.Guice; +import com.google.inject.Injector; +import edu.wpi.grip.core.sockets.InputSocket; +import edu.wpi.grip.core.sockets.OutputSocket; import edu.wpi.grip.core.sockets.Socket; import edu.wpi.grip.core.util.MockExceptionWitness; +import edu.wpi.grip.util.GRIPCoreTestModule; +import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -10,22 +16,32 @@ public class StepTest { private EventBus eventBus; - private Operation addition; + private OperationMetaData additionMeta; + private GRIPCoreTestModule testModule = new GRIPCoreTestModule(); @Before public void setUp() { - this.eventBus = new EventBus(); - this.addition = new AdditionOperation(); + testModule.setUp(); + Injector injector = Guice.createInjector(testModule); + InputSocket.Factory isf = injector.getInstance(InputSocket.Factory.class); + OutputSocket.Factory osf = injector.getInstance(OutputSocket.Factory.class); + additionMeta = new OperationMetaData(AdditionOperation.DESCRIPTION, () -> new AdditionOperation(isf, osf)); + eventBus = injector.getInstance(EventBus.class); + } + + @After + public void tearDown() { + testModule.tearDown(); } @Test(expected = NullPointerException.class) public void testOperationNotNull() { - new Step.Factory(eventBus, (origin) -> null).create(null); + new Step.Factory((origin) -> null).create(null); } @Test public void testStep() { - Step step = new Step.Factory(eventBus, (origin) -> new MockExceptionWitness(eventBus, origin)).create(addition); + Step step = new Step.Factory((origin) -> new MockExceptionWitness(eventBus, origin)).create(additionMeta); Socket a = (Socket) step.getInputSockets().get(0); Socket b = (Socket) step.getInputSockets().get(1); Socket c = (Socket) step.getOutputSockets().get(0); @@ -34,26 +50,26 @@ public void testStep() { b.setValue(5678.0); step.runPerformIfPossible(); - assertEquals((Double) (1234.0 + 5678.0), c.getValue().get()); + assertEquals("Step did not perform correctly", (Double) (1234.0 + 5678.0), c.getValue().get()); eventBus.unregister(step); } @Test public void testSocketDirection() { - Step step = new Step.Factory(eventBus, (origin) -> new MockExceptionWitness(eventBus, origin)).create(addition); + Step step = new Step.Factory((origin) -> new MockExceptionWitness(eventBus, origin)).create(additionMeta); Socket a = (Socket) step.getInputSockets().get(0); Socket b = (Socket) step.getInputSockets().get(1); Socket c = (Socket) step.getOutputSockets().get(0); - assertEquals(Socket.Direction.INPUT, a.getDirection()); - assertEquals(Socket.Direction.INPUT, b.getDirection()); - assertEquals(Socket.Direction.OUTPUT, c.getDirection()); + assertEquals("Socket was not an input", Socket.Direction.INPUT, a.getDirection()); + assertEquals("Socket was not an input", Socket.Direction.INPUT, b.getDirection()); + assertEquals("Socket was not an output", Socket.Direction.OUTPUT, c.getDirection()); } @Test public void testGetOperation() { - Step step = new Step.Factory(eventBus, (origin) -> new MockExceptionWitness(eventBus, origin)).create(addition); + Step step = new Step.Factory((origin) -> new MockExceptionWitness(eventBus, origin)).create(additionMeta); - assertEquals(addition, step.getOperation()); + assertEquals("Operation descriptions were not the same", additionMeta.getDescription(), step.getOperationDescription()); } } diff --git a/core/src/test/java/edu/wpi/grip/core/SubtractionOperation.java b/core/src/test/java/edu/wpi/grip/core/SubtractionOperation.java index be474c0a9f..d6b0cd1459 100644 --- a/core/src/test/java/edu/wpi/grip/core/SubtractionOperation.java +++ b/core/src/test/java/edu/wpi/grip/core/SubtractionOperation.java @@ -1,42 +1,52 @@ package edu.wpi.grip.core; +import com.google.common.collect.ImmutableList; import com.google.common.eventbus.EventBus; -import edu.wpi.grip.core.sockets.InputSocket; -import edu.wpi.grip.core.sockets.OutputSocket; -import edu.wpi.grip.core.sockets.SocketHint; -import edu.wpi.grip.core.sockets.SocketHints; +import edu.wpi.grip.core.sockets.*; + +import java.util.List; public class SubtractionOperation implements Operation { + public static final OperationDescription DESCRIPTION = + OperationDescription.builder() + .name("Subtract") + .summary("Computer the difference between two doubles") + .build(); private SocketHint aHint = SocketHints.createNumberSocketHint("a", 0.0), bHint = SocketHints.createNumberSocketHint("b", 0.0), cHint = SocketHints.Outputs.createNumberSocketHint("c", 0.0); - @Override - public String getName() { - return "Subtract"; + private InputSocket a, b; + private OutputSocket c; + + public SubtractionOperation(EventBus eventBus) { + this(new MockInputSocketFactory(eventBus), new MockOutputSocketFactory(eventBus)); } - @Override - public String getDescription() { - return "Compute the difference between two doubles"; + public SubtractionOperation(InputSocket.Factory isf, OutputSocket.Factory osf) { + a = isf.create(aHint); + b = isf.create(bHint); + c = osf.create(cHint); } + @Override - public InputSocket[] createInputSockets(EventBus eventBus) { - return new InputSocket[]{new InputSocket<>(eventBus, aHint), new InputSocket<>(eventBus, bHint)}; + public List getInputSockets() { + return ImmutableList.of( + a, b + ); } @Override - public OutputSocket[] createOutputSockets(EventBus eventBus) { - return new OutputSocket[]{new OutputSocket<>(eventBus, cHint)}; + public List getOutputSockets() { + return ImmutableList.of( + c + ); } @Override - public void perform(InputSocket[] inputs, OutputSocket[] outputs) { - InputSocket a = inputs[0], b = inputs[1]; - OutputSocket c = outputs[0]; - + public void perform() { c.setValue(a.getValue().get().doubleValue() - b.getValue().get().doubleValue()); } } diff --git a/core/src/test/java/edu/wpi/grip/core/operations/OperationsFactory.java b/core/src/test/java/edu/wpi/grip/core/operations/OperationsFactory.java index 599df4ac5f..865d17ec7e 100644 --- a/core/src/test/java/edu/wpi/grip/core/operations/OperationsFactory.java +++ b/core/src/test/java/edu/wpi/grip/core/operations/OperationsFactory.java @@ -7,6 +7,10 @@ import edu.wpi.grip.core.operations.network.ros.JavaToMessageConverter; import edu.wpi.grip.core.operations.network.ros.ROSMessagePublisher; import edu.wpi.grip.core.operations.network.ros.ROSNetworkPublisherFactory; +import edu.wpi.grip.core.sockets.InputSocket; +import edu.wpi.grip.core.sockets.MockInputSocketFactory; +import edu.wpi.grip.core.sockets.MockOutputSocketFactory; +import edu.wpi.grip.core.sockets.OutputSocket; import java.util.Optional; @@ -34,10 +38,20 @@ public void close() { } public static Operations create(EventBus eventBus) { - return create(eventBus, MockMapNetworkPublisher::new, MockROSMessagePublisher::new); + + return create(eventBus, MockMapNetworkPublisher::new, MockROSMessagePublisher::new, + new MockInputSocketFactory(eventBus), new MockOutputSocketFactory(eventBus)); + } + + public static Operations create(EventBus eventBus, + MapNetworkPublisherFactory mapFactory, + ROSNetworkPublisherFactory rosFactory, + InputSocket.Factory isf, + OutputSocket.Factory osf) { + return new Operations(eventBus, mapFactory, rosFactory, isf, osf); } - public static Operations create(EventBus eventBus, MapNetworkPublisherFactory mapFactory, ROSNetworkPublisherFactory rosFactory) { - return new Operations(eventBus, mapFactory, rosFactory); + public static CVOperations createCV(EventBus eventBus) { + return new CVOperations(eventBus, new MockInputSocketFactory(eventBus), new MockOutputSocketFactory(eventBus)); } } diff --git a/core/src/test/java/edu/wpi/grip/core/operations/OperationsTest.java b/core/src/test/java/edu/wpi/grip/core/operations/OperationsTest.java index ea1b0978dc..47696fd737 100644 --- a/core/src/test/java/edu/wpi/grip/core/operations/OperationsTest.java +++ b/core/src/test/java/edu/wpi/grip/core/operations/OperationsTest.java @@ -3,11 +3,10 @@ import com.google.common.base.Throwables; import com.google.common.eventbus.EventBus; import com.google.common.eventbus.Subscribe; -import edu.wpi.grip.core.Operation; +import edu.wpi.grip.core.OperationMetaData; import edu.wpi.grip.core.Step; import edu.wpi.grip.core.events.OperationAddedEvent; import edu.wpi.grip.core.util.MockExceptionWitness; -import edu.wpi.grip.generated.CVOperations; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -16,8 +15,10 @@ import java.util.List; import java.util.Optional; +//import edu.wpi.grip.generated.CVOperations; + public class OperationsTest { - private List operationList; + private List operationList; private Optional throwableOptional; private EventBus eventBus; @@ -47,10 +48,10 @@ public void afterTest() { @Test public void testCreateAllCVSteps() { - CVOperations.addOperations(eventBus); - for (Operation operation : operationList) { + OperationsFactory.createCV(eventBus).addOperations(); + for (OperationMetaData operationMeta : operationList) { final Step step = - new Step.Factory(eventBus, (origin) -> new MockExceptionWitness(eventBus, origin)).create(operation); + new Step.Factory((origin) -> new MockExceptionWitness(eventBus, origin)).create(operationMeta); step.setRemoved(); } } @@ -59,9 +60,9 @@ public void testCreateAllCVSteps() { public void testCreateAllCoreSteps() { OperationsFactory.create(eventBus) .addOperations(); - for (Operation operation : operationList) { + for (OperationMetaData operationMeta : operationList) { final Step step = - new Step.Factory(eventBus, (origin) -> new MockExceptionWitness(eventBus, origin)).create(operation); + new Step.Factory((origin) -> new MockExceptionWitness(eventBus, origin)).create(operationMeta); step.setRemoved(); } } diff --git a/core/src/test/java/edu/wpi/grip/core/operations/network/PublishAnnotatedOperationTest.java b/core/src/test/java/edu/wpi/grip/core/operations/network/PublishAnnotatedOperationTest.java index 2e1be78778..9a6f63c280 100644 --- a/core/src/test/java/edu/wpi/grip/core/operations/network/PublishAnnotatedOperationTest.java +++ b/core/src/test/java/edu/wpi/grip/core/operations/network/PublishAnnotatedOperationTest.java @@ -1,13 +1,16 @@ package edu.wpi.grip.core.operations.network; import com.google.common.eventbus.EventBus; -import com.google.common.reflect.TypeToken; +import edu.wpi.grip.core.Operation; import edu.wpi.grip.core.sockets.InputSocket; +import edu.wpi.grip.core.sockets.MockInputSocketFactory; import org.junit.Test; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.function.Function; import static junit.framework.TestCase.assertTrue; import static org.junit.Assert.*; @@ -123,104 +126,71 @@ public double getFoo() { } } - abstract static class TestPublishAnnotatedOperation extends PublishAnnotatedOperation { + static class TestPublishAnnotatedOperation extends PublishAnnotatedOperation { - public TestPublishAnnotatedOperation(MapNetworkPublisherFactory factory) { - super(factory); + public TestPublishAnnotatedOperation(Class type, MapNetworkPublisherFactory factory) { + super(new MockInputSocketFactory(new EventBus()), type, type, Function.identity(), factory); } - public TestPublishAnnotatedOperation() { - this(MockMapNetworkPublisher::new); - } - - @Override - protected String getNetworkProtocolNameAcronym() { - return "TP"; - } - - @Override - protected String getNetworkProtocolName() { - return "Test Protocol"; - } - - @Override - protected String getSocketHintStringPrompt() { - return "Test Name"; + public TestPublishAnnotatedOperation(Class type) { + this(type, MockMapNetworkPublisher::new); } } @Test public void testNTValueOrder() { - TestPublishAnnotatedOperation ntPublishOperation = new TestPublishAnnotatedOperation() { - }; - InputSocket[] sockets = ntPublishOperation.createInputSockets(new EventBus()); + Operation ntPublishOperation = new TestPublishAnnotatedOperation<>(SimpleReport.class); + List sockets = ntPublishOperation.getInputSockets(); - assertEquals("Unexpected number of sockets", 4, sockets.length); - assertEquals("Wrong publish name", "Publish bar", sockets[2].getSocketHint().getIdentifier()); - assertEquals("Wrong publish name", "Publish foo", sockets[3].getSocketHint().getIdentifier()); + assertEquals("Unexpected number of sockets", 4, sockets.size()); + assertEquals("Wrong publish name", "Publish bar", sockets.get(2).getSocketHint().getIdentifier()); + assertEquals("Wrong publish name", "Publish foo", sockets.get(3).getSocketHint().getIdentifier()); } @Test(expected = IllegalArgumentException.class) public void testNonDistinctWeights() { - new TestPublishAnnotatedOperation() { - }; + new TestPublishAnnotatedOperation<>(ReportWithNonDistinctWeights.class); } @Test(expected = IllegalArgumentException.class) public void testPublishableWithMethodThatHasParameters() { - new TestPublishAnnotatedOperation() { - - }; + new TestPublishAnnotatedOperation<>(ReportWithParameters.class); } @Test(expected = IllegalArgumentException.class) public void testPublishableWithNonDistinctKeys() { - new TestPublishAnnotatedOperation() { - }; + new TestPublishAnnotatedOperation<>(ReportWithNonDistinctKeys.class); } @Test(expected = IllegalArgumentException.class) public void testPublishableWithMultipleEmptyKeys() { - new TestPublishAnnotatedOperation() { - }; + new TestPublishAnnotatedOperation<>(ReportWithMultipleEmptyKeys.class); } @Test(expected = IllegalArgumentException.class) public void testPublishableWithMixedEmptyAndSuppliedKeys() { - new TestPublishAnnotatedOperation() { - }; + new TestPublishAnnotatedOperation<>(ReportWithMixedEmptyAndSuppliedKeys.class); } - @Test(expected = IllegalAccessError.class) + @Test(expected = IllegalArgumentException.class) public void testPublishableWithPrivateMethod() { - new TestPublishAnnotatedOperation() { - }; + new TestPublishAnnotatedOperation<>(ReportWithPrivateMethod.class); } @Test(expected = IllegalAccessError.class) public void testNonPublicPublishable() { - new TestPublishAnnotatedOperation() { - }; + new TestPublishAnnotatedOperation<>(StaticNonPublicReport.class); } @Test(expected = IllegalAccessError.class) public void testNonStaticInnerPublishable() { - new TestPublishAnnotatedOperation() { - }; + new TestPublishAnnotatedOperation<>(NonStaticPublicReport.class); } @Test(expected = IllegalArgumentException.class) public void testPublishableWithNoAnnoatedMethods() { - new TestPublishAnnotatedOperation() { - }; - } - - @Test(expected = IllegalArgumentException.class) - public void testUnresolvableTypesFails() { - // This should not be able to resolve the type and as such should fail - new TestPublishAnnotatedOperation() { - }; + new TestPublishAnnotatedOperation<>(NoAnnotatedMethodReport.class); } @Test @@ -263,21 +233,20 @@ protected void doPublish() { fail("This should not have run"); } } + // Cannot be method reference due to JDK/javac bug 8144673 final MapNetworkPublisherFactory factory = new MapNetworkPublisherFactory() { @Override public MapNetworkPublisher create(Set keys) { return new TestMapNetworkPublisher<>(keys); } }; - final TestPublishAnnotatedOperation testPublishAnnotatedOperation = new TestPublishAnnotatedOperation(factory) { - }; + final TestPublishAnnotatedOperation testPublishAnnotatedOperation = new TestPublishAnnotatedOperation<>(SimpleReport.class, factory); - final InputSocket[] inputSockets = testPublishAnnotatedOperation.createInputSockets(new EventBus()); - inputSockets[0].setValue(new SimpleReport()); - inputSockets[1].setValue(PUBLISHER_NAME); - final Optional data = testPublishAnnotatedOperation.createData(); + final List inputSockets = testPublishAnnotatedOperation.getInputSockets(); + inputSockets.get(0).setValue(new SimpleReport()); + inputSockets.get(1).setValue(PUBLISHER_NAME); - testPublishAnnotatedOperation.perform(inputSockets, null, data); + testPublishAnnotatedOperation.perform(); assertTrue("publishNameChanged never ran", publishNameChangedRan[0]); assertTrue("doPublish never ran", doPublishRan[0]); @@ -285,11 +254,10 @@ public MapNetworkPublisher create(Set keys) { @Test public void testPublishProperlyResolvesSocketType() { - TestPublishAnnotatedOperation testPublishAnnotatedOperation - = new TestPublishAnnotatedOperation() { - }; + TestPublishAnnotatedOperation testPublishAnnotatedOperation + = new TestPublishAnnotatedOperation<>(SimpleReport.class); assertEquals("Socket types were not the same", testPublishAnnotatedOperation.getSocketType(), - TypeToken.of(SimpleReport.class)); + SimpleReport.class); } -} \ No newline at end of file +} diff --git a/core/src/test/java/edu/wpi/grip/core/operations/network/ros/ROSPackageSanityTest.java b/core/src/test/java/edu/wpi/grip/core/operations/network/ros/ROSPackageSanityTest.java index a63f9b709a..6d3f2cb8da 100644 --- a/core/src/test/java/edu/wpi/grip/core/operations/network/ros/ROSPackageSanityTest.java +++ b/core/src/test/java/edu/wpi/grip/core/operations/network/ros/ROSPackageSanityTest.java @@ -7,5 +7,6 @@ public class ROSPackageSanityTest extends AbstractPackageSanityTests { public ROSPackageSanityTest() { super(); ignoreClasses(c -> c.equals(ROSLoader.class)); + setDefault(JavaToMessageConverter.class, JavaToMessageConverter.BLOBS); } } diff --git a/core/src/test/java/edu/wpi/grip/core/operations/opencv/AddOperationTest.java b/core/src/test/java/edu/wpi/grip/core/operations/opencv/AddOperationTest.java index 2f4e3ca940..b52d1f814d 100644 --- a/core/src/test/java/edu/wpi/grip/core/operations/opencv/AddOperationTest.java +++ b/core/src/test/java/edu/wpi/grip/core/operations/opencv/AddOperationTest.java @@ -2,26 +2,26 @@ import com.google.common.eventbus.EventBus; import edu.wpi.grip.core.AddOperation; -import edu.wpi.grip.core.sockets.InputSocket; -import edu.wpi.grip.core.Operation; -import edu.wpi.grip.core.sockets.OutputSocket; +import edu.wpi.grip.core.sockets.*; import org.bytedeco.javacpp.opencv_core; import org.bytedeco.javacpp.opencv_core.Mat; import org.bytedeco.javacpp.opencv_core.Scalar; import org.junit.Before; import org.junit.Test; +import java.util.List; + import static org.junit.Assert.assertTrue; public class AddOperationTest { EventBus eventBus; - Operation addition; + AddOperation addition; @Before public void setUp() throws Exception { this.eventBus = new EventBus(); - this.addition = new AddOperation(); + this.addition = new AddOperation(eventBus); } /** @@ -50,10 +50,10 @@ private boolean isMatEqual(Mat mat1, Mat mat2) { @Test public void testAddMatrixOfOnesToMatrixOfTwosEqualsMatrixOfThrees() { // Given - InputSocket[] inputs = addition.createInputSockets(eventBus); - OutputSocket[] outputs = addition.createOutputSockets(eventBus); - InputSocket a = inputs[0], b = inputs[1]; - OutputSocket c = outputs[0]; + List inputs = addition.getInputSockets(); + List outputs = addition.getOutputSockets(); + InputSocket a = inputs.get(0), b = inputs.get(1); + OutputSocket c = outputs.get(0); int sz[] = {256, 256}; @@ -63,13 +63,13 @@ public void testAddMatrixOfOnesToMatrixOfTwosEqualsMatrixOfThrees() { //When long startTime = System.currentTimeMillis(); for (int i = 0; i < 1000; i++) { - addition.perform(inputs, outputs); + addition.perform(); } long endTime = System.currentTimeMillis(); System.out.println("Run time: " + (endTime - startTime)); //Then Mat expectedResult = new Mat(2, sz, opencv_core.CV_8U, Scalar.all(3)); - assertTrue(isMatEqual(c.getValue().get(), expectedResult)); + assertTrue(isMatEqual((Mat) c.getValue().get(), expectedResult)); } -} \ No newline at end of file +} diff --git a/core/src/test/java/edu/wpi/grip/core/serialization/CompatibilityTest.java b/core/src/test/java/edu/wpi/grip/core/serialization/CompatibilityTest.java index 06286cd29b..900a66b385 100644 --- a/core/src/test/java/edu/wpi/grip/core/serialization/CompatibilityTest.java +++ b/core/src/test/java/edu/wpi/grip/core/serialization/CompatibilityTest.java @@ -3,9 +3,9 @@ import com.google.common.eventbus.EventBus; import com.google.inject.Guice; import com.google.inject.Injector; + import edu.wpi.grip.core.Pipeline; import edu.wpi.grip.core.operations.OperationsFactory; -import edu.wpi.grip.generated.CVOperations; import edu.wpi.grip.util.Files; import edu.wpi.grip.util.GRIPCoreTestModule; import org.junit.After; @@ -15,12 +15,18 @@ import java.io.*; import java.net.URI; +import org.junit.After; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; + import static junit.framework.TestCase.assertEquals; /** * This tests for backwards compatibility by opening a project file containing ALL the steps of GRIP * at the time of this file's creation. Please note that this test requires a webcam at position 0. */ +@Ignore("OpenCV operations were removed. Remove this annotation after they are added back.") public class CompatibilityTest { private static final URI testphotoURI = Files.testphotoURI; //The location of the photo source for the test @@ -42,7 +48,7 @@ public void setUp() throws Exception { //Add the operations so that GRIP will recognize them OperationsFactory.create(eventBus).addOperations(); - CVOperations.addOperations(eventBus); +// CVOperations.addOperations(eventBus); //Set up the test project file to work with this machine String fileName = testprojectURI.toString().substring(5); @@ -75,8 +81,9 @@ public void tearDown() { testModule.tearDown(); } + @Test - public void testCompatibilityIsMaintained() throws Exception { + public void testCompatibilityIsPreserved() throws Exception { assertEquals("The expected number of steps were not found", 50, pipeline.getSteps().size()); assertEquals("The expected number of sources were not found", 2, pipeline.getSources().size()); pipeline.clear(); diff --git a/core/src/test/java/edu/wpi/grip/core/serialization/ProjectTest.java b/core/src/test/java/edu/wpi/grip/core/serialization/ProjectTest.java index 4fe97c6c30..d742ff00a6 100644 --- a/core/src/test/java/edu/wpi/grip/core/serialization/ProjectTest.java +++ b/core/src/test/java/edu/wpi/grip/core/serialization/ProjectTest.java @@ -10,7 +10,7 @@ import edu.wpi.grip.core.events.OperationAddedEvent; import edu.wpi.grip.core.events.ProjectSettingsChangedEvent; import edu.wpi.grip.core.events.SourceAddedEvent; -import edu.wpi.grip.core.operations.PythonScriptOperation; +import edu.wpi.grip.core.operations.PythonScriptFile; import edu.wpi.grip.core.settings.ProjectSettings; import edu.wpi.grip.core.sockets.InputSocket; import edu.wpi.grip.core.sockets.OutputSocket; @@ -41,8 +41,8 @@ public class ProjectTest { private Project project; private ManualPipelineRunner pipelineRunner; - private Operation additionOperation, opencvAddOperation, pythonAdditionOperationFromURL, - pythonAdditionOperationFromSource; + private OperationMetaData pythonAdditionOperationFromURL, pythonAdditionOperationFromSource; + private OperationMetaData additionOperation, opencvAddOperation; @Before public void setUp() throws Exception { @@ -57,24 +57,27 @@ public void setUp() throws Exception { eventBus = injector.getInstance(EventBus.class); project = injector.getInstance(Project.class); stepFactory = injector.getInstance(Step.Factory.class); + final InputSocket.Factory isf = injector.getInstance(InputSocket.Factory.class); + final OutputSocket.Factory osf = injector.getInstance(OutputSocket.Factory.class); pipeline = injector.getInstance(Pipeline.class); pipelineRunner = new ManualPipelineRunner(eventBus, pipeline); - additionOperation = new AdditionOperation(); - pythonAdditionOperationFromURL = new PythonScriptOperation( - ProjectTest.class.getResource("/edu/wpi/grip/scripts/addition.py")); - pythonAdditionOperationFromSource = new PythonScriptOperation("import edu.wpi.grip.core.sockets as grip\nimport java" + - ".lang.Integer\n\ninputs = [\n grip.SocketHints.createNumberSocketHint(\"a\", 0.0),\n grip.SocketHints.createNumberSocketHint(" + + additionOperation = new OperationMetaData(AdditionOperation.DESCRIPTION, () -> new AdditionOperation(isf, osf)); + pythonAdditionOperationFromURL = PythonScriptFile.create( + ProjectTest.class.getResource("/edu/wpi/grip/scripts/addition.py")).toOperationMetaData(isf, osf); + + pythonAdditionOperationFromSource = PythonScriptFile.create("import edu.wpi.grip.core.sockets as grip\nimport java" + + ".lang.Integer\n\nname = \"Addition Operation\"\n\ninputs = [\n grip.SocketHints.createNumberSocketHint(\"a\", 0.0),\n grip.SocketHints.createNumberSocketHint(" + "\"b\", 0.0),\n]\n\noutputs = [\n grip.SocketHints.Outputs.createNumberSocketHint(\"sum\", 0.0)," + - "\n]\n\ndef perform(a, b):\n return a + b\n"); - opencvAddOperation = new AddOperation(); + "\n]\n\ndef perform(a, b):\n return a + b\n").toOperationMetaData(isf, osf); + opencvAddOperation = new OperationMetaData(AddOperation.DESCRIPTION, () -> new AddOperation(isf, osf)); - eventBus.post(new OperationAddedEvent(additionOperation)); eventBus.post(new OperationAddedEvent(pythonAdditionOperationFromURL)); eventBus.post(new OperationAddedEvent(pythonAdditionOperationFromSource)); + eventBus.post(new OperationAddedEvent(additionOperation)); eventBus.post(new OperationAddedEvent(opencvAddOperation)); } @@ -274,7 +277,7 @@ public void testSerializePipelineWithSource() throws Exception { serializeAndDeserialize(); final ImageFileSource sourceDeserialized = (ImageFileSource) pipeline.getSources().get(0); - Files.gompeiJpegFile.assertSameImage((Mat) sourceDeserialized.createOutputSockets()[0].getValue().get()); + Files.gompeiJpegFile.assertSameImage((Mat) sourceDeserialized.createOutputSockets().get(0).getValue().get()); } @Test diff --git a/core/src/test/java/edu/wpi/grip/core/sockets/LinkedSocketHintTest.java b/core/src/test/java/edu/wpi/grip/core/sockets/LinkedSocketHintTest.java index 6e0fd0a323..e25bf56c93 100644 --- a/core/src/test/java/edu/wpi/grip/core/sockets/LinkedSocketHintTest.java +++ b/core/src/test/java/edu/wpi/grip/core/sockets/LinkedSocketHintTest.java @@ -14,15 +14,21 @@ public class LinkedSocketHintTest { private SocketHint booleanSocketHint; private SocketHint numberSocketHint; + private InputSocket.Factory isf; + private OutputSocket.Factory osf; + @Before public void setUp() { + EventBus eventBus = new EventBus(); booleanSocketHint = SocketHints.createBooleanSocketHint("testBooleanHint", true); numberSocketHint = SocketHints.createNumberSocketHint("testNumberHint", 30); + isf = new MockInputSocketFactory(eventBus); + osf = new MockOutputSocketFactory(eventBus); } @Test public void testConnectingAnyType() { - final LinkedSocketHint linkedSocketHint = new LinkedSocketHint(new EventBus()); + final LinkedSocketHint linkedSocketHint = new LinkedSocketHint(isf, osf); assertTrue("Boolean should be compatible with linkedSocketHint", linkedSocketHint.isCompatibleWith(booleanSocketHint)); assertTrue("Number should be compatible with linkedSocketHint", linkedSocketHint.isCompatibleWith(numberSocketHint)); @@ -33,8 +39,8 @@ public void testConnectingAnyType() { public void testMakingConnectionOfTypeBooleanPreventsConnectionOfTypeNumber() { // Given final EventBus eventBus = new EventBus(); - final OutputSocket booleanOutputSocket = new OutputSocket(eventBus, booleanSocketHint); - final LinkedSocketHint linkedSocketHint = new LinkedSocketHint(eventBus); + final OutputSocket booleanOutputSocket = osf.create(booleanSocketHint); + final LinkedSocketHint linkedSocketHint = new LinkedSocketHint(isf, osf); // When final InputSocket connectedLinkedInputSocket = linkedSocketHint.linkedInputSocket("A"); @@ -48,4 +54,4 @@ public void testMakingConnectionOfTypeBooleanPreventsConnectionOfTypeNumber() { assertFalse("Linked Socket Hint should no longer support number types", linkedSocketHint.isCompatibleWith(numberSocketHint)); assertTrue("Linked Socket Hint should still accept boolean types", linkedSocketHint.isCompatibleWith(booleanSocketHint)); } -} \ No newline at end of file +} diff --git a/core/src/test/java/edu/wpi/grip/core/sockets/MockInputSocket.java b/core/src/test/java/edu/wpi/grip/core/sockets/MockInputSocket.java index c1ac709392..ca6ee16314 100644 --- a/core/src/test/java/edu/wpi/grip/core/sockets/MockInputSocket.java +++ b/core/src/test/java/edu/wpi/grip/core/sockets/MockInputSocket.java @@ -3,7 +3,7 @@ import com.google.common.eventbus.EventBus; -public class MockInputSocket extends InputSocket { +public class MockInputSocket extends InputSocketImpl { public MockInputSocket(String name) { super(new EventBus(), SocketHints.Outputs.createBooleanSocketHint(name, false)); } diff --git a/core/src/test/java/edu/wpi/grip/core/sockets/MockInputSocketFactory.java b/core/src/test/java/edu/wpi/grip/core/sockets/MockInputSocketFactory.java new file mode 100644 index 0000000000..985af526d2 --- /dev/null +++ b/core/src/test/java/edu/wpi/grip/core/sockets/MockInputSocketFactory.java @@ -0,0 +1,11 @@ +package edu.wpi.grip.core.sockets; + + +import com.google.common.eventbus.EventBus; + +public class MockInputSocketFactory extends InputSocketImpl.FactoryImpl { + + public MockInputSocketFactory(EventBus eventBus) { + super(eventBus); + } +} diff --git a/core/src/test/java/edu/wpi/grip/core/sockets/MockOutputSocket.java b/core/src/test/java/edu/wpi/grip/core/sockets/MockOutputSocket.java index 34b7a585c6..d80b4b4428 100644 --- a/core/src/test/java/edu/wpi/grip/core/sockets/MockOutputSocket.java +++ b/core/src/test/java/edu/wpi/grip/core/sockets/MockOutputSocket.java @@ -2,7 +2,7 @@ import com.google.common.eventbus.EventBus; -public class MockOutputSocket extends OutputSocket { +public class MockOutputSocket extends OutputSocketImpl { public MockOutputSocket(String socketName) { super(new EventBus(), SocketHints.Outputs.createBooleanSocketHint(socketName, false)); } diff --git a/core/src/test/java/edu/wpi/grip/core/sockets/MockOutputSocketFactory.java b/core/src/test/java/edu/wpi/grip/core/sockets/MockOutputSocketFactory.java new file mode 100644 index 0000000000..1095d12140 --- /dev/null +++ b/core/src/test/java/edu/wpi/grip/core/sockets/MockOutputSocketFactory.java @@ -0,0 +1,9 @@ +package edu.wpi.grip.core.sockets; + +import com.google.common.eventbus.EventBus; + +public class MockOutputSocketFactory extends OutputSocketImpl.FactoryImpl { + public MockOutputSocketFactory(EventBus eventBus) { + super(eventBus); + } +} diff --git a/core/src/test/java/edu/wpi/grip/core/sockets/SocketTest.java b/core/src/test/java/edu/wpi/grip/core/sockets/SocketTest.java index 99fbd5c9ce..d4aa611cf3 100644 --- a/core/src/test/java/edu/wpi/grip/core/sockets/SocketTest.java +++ b/core/src/test/java/edu/wpi/grip/core/sockets/SocketTest.java @@ -19,7 +19,7 @@ public class SocketTest { @Before public void initialize() { sh = SocketHints.Inputs.createNumberSliderSocketHint("foo", 0.0, 0.0, 1.0); - socket = new OutputSocket(eventBus, sh); + socket = new OutputSocketImpl<>(eventBus, sh); eventBus.register(socket); } @@ -39,7 +39,7 @@ public void testSetValue() throws Exception { @Test public void testDefaultValue() throws Exception { sh = SocketHints.Inputs.createNumberSliderSocketHint("foo", testValue, 0.0, 1.0); - socket = new OutputSocket(eventBus, sh); + socket = new OutputSocketImpl<>(eventBus, sh); assertEquals(testValue, socket.getValue().get()); } @@ -52,7 +52,7 @@ public void testSocketChangedEvent() throws Exception { @Subscribe public void onSocketChanged(SocketChangedEvent e) { handled[0] = true; - value[0] = (Double) e.getSocket().getValue().get(); + value[0] = (Double) socket.getValue().get(); } }; @@ -67,7 +67,7 @@ public void onSocketChanged(SocketChangedEvent e) { @Test public void testSocketPreview() { SocketHint sh = SocketHints.createNumberSocketHint("foo", 0); - OutputSocket socket = new OutputSocket(eventBus, sh); + OutputSocket socket = new OutputSocketImpl<>(eventBus, sh); final boolean[] handled = new boolean[]{false}; Object eventHandler = new Object() { @@ -75,7 +75,7 @@ public void testSocketPreview() { public void onSocketPreviewed(SocketPreviewChangedEvent e) { handled[0] = true; assertTrue("A preview event fired for a socket but the socket was not labeled as able to be previewed", - e.getSocket().isPreviewed()); + socket.isPreviewed()); } }; @@ -88,28 +88,28 @@ public void onSocketPreviewed(SocketPreviewChangedEvent e) { @Test(expected = NullPointerException.class) public void testSocketHintNotNullInput() throws Exception { - new InputSocket(eventBus, null); + new InputSocketImpl<>(eventBus, null); } @Test(expected = NullPointerException.class) public void testSocketHintNotNullOutput() throws Exception { - new OutputSocket(eventBus, null); + new OutputSocketImpl<>(eventBus, null); } @Test(expected = NullPointerException.class) public void testSocketEventBusNotNullInput() throws Exception { - new InputSocket(null, sh); + new InputSocketImpl<>(null, sh); } @Test(expected = NullPointerException.class) public void testSocketEventBusNotNullOutput() throws Exception { - new OutputSocket(null, sh); + new OutputSocketImpl<>(null, sh); } @Test(expected = ClassCastException.class) @SuppressWarnings("unchecked") public void testSocketValueWrongType() throws Exception { - InputSocket socket = new InputSocket(eventBus, sh); + InputSocket socket = new InputSocketImpl(eventBus, sh); socket.setValue("I am not a Double"); } diff --git a/core/src/test/java/edu/wpi/grip/core/sockets/SocketsSanityTest.java b/core/src/test/java/edu/wpi/grip/core/sockets/SocketsSanityTest.java index 1f779e60e9..e2e73aeb94 100644 --- a/core/src/test/java/edu/wpi/grip/core/sockets/SocketsSanityTest.java +++ b/core/src/test/java/edu/wpi/grip/core/sockets/SocketsSanityTest.java @@ -8,5 +8,6 @@ enum TestEnum {A, B, C} public SocketsSanityTest() { super(); setDefault(Enum.class, TestEnum.A); + ignoreClasses(c -> c.getName().contains("Mock")); } } diff --git a/core/src/test/java/edu/wpi/grip/core/sources/CameraSourceTest.java b/core/src/test/java/edu/wpi/grip/core/sources/CameraSourceTest.java index 50bb1ba4b3..7587d699b2 100644 --- a/core/src/test/java/edu/wpi/grip/core/sources/CameraSourceTest.java +++ b/core/src/test/java/edu/wpi/grip/core/sources/CameraSourceTest.java @@ -8,6 +8,7 @@ import com.google.inject.Guice; import com.google.inject.Injector; import edu.wpi.grip.core.events.UnexpectedThrowableEvent; +import edu.wpi.grip.core.sockets.OutputSocket; import edu.wpi.grip.core.util.ImageLoadingUtility; import edu.wpi.grip.core.util.MockExceptionWitness; import edu.wpi.grip.util.Files; @@ -32,13 +33,18 @@ import java.util.concurrent.TimeoutException; import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; public class CameraSourceTest { private GRIPCoreTestModule testModule; private CameraSource.Factory cameraSourceFactory; private CameraSource cameraSourceWithMockGrabber; private MockFrameGrabberFactory mockFrameGrabberFactory; + private OutputSocket.Factory osf; @Rule public final Timeout timeout = Timeout.seconds(3); @@ -120,6 +126,7 @@ public void setUp() throws Exception { testModule.setUp(); final Injector injector = Guice.createInjector(testModule); this.cameraSourceFactory = injector.getInstance(CameraSource.Factory.class); + this.osf = injector.getInstance(OutputSocket.Factory.class); final EventBus eventBus = new EventBus(); class UnhandledExceptionWitness { @@ -134,6 +141,7 @@ public void onUnexpectedThrowableEvent(UnexpectedThrowableEvent event) { this.mockFrameGrabberFactory = new MockFrameGrabberFactory(); this.cameraSourceWithMockGrabber = new CameraSource( eventBus, + osf, mockFrameGrabberFactory, origin -> new MockExceptionWitness(eventBus, origin), 0); @@ -216,7 +224,7 @@ public void testEnsureThatGrabberIsReinitializedWhenStartThrowsException() throw Waiter waiter3 = new Waiter(); Queue waiterQueue = new LinkedList<>(Arrays.asList(waiter1, waiter2, waiter3)); - CameraSource source = new CameraSource(new EventBus(), new CameraSource.FrameGrabberFactory() { + CameraSource source = new CameraSource(new EventBus(), osf, new CameraSource.FrameGrabberFactory() { @Override public FrameGrabber create(int deviceNumber) { return new SimpleMockFrameGrabber() { @@ -258,7 +266,7 @@ public void testFrameRateUpdatesWithGrabSpeed() throws IOException, InterruptedE Mat image = new Mat(); ImageLoadingUtility.loadImage(Files.gompeiJpegFile.file.getPath(), image); - CameraSource source = new CameraSource(new EventBus(), new CameraSource.FrameGrabberFactory() { + CameraSource source = new CameraSource(new EventBus(), osf, new CameraSource.FrameGrabberFactory() { @Override public FrameGrabber create(int deviceNumber) { return new SimpleMockFrameGrabber() { @@ -292,7 +300,7 @@ public FrameGrabber create(String addressProperty) throws MalformedURLException source.updateOutputSockets(); assertNotEquals("The frame rate was not updated when the camera was running", - Double.valueOf(0), source.createOutputSockets()[1].getValue().get()); + Double.valueOf(0), source.createOutputSockets().get(1).getValue().get()); try { source.stopAndAwait(); diff --git a/core/src/test/java/edu/wpi/grip/core/sources/ImageFileSourceTest.java b/core/src/test/java/edu/wpi/grip/core/sources/ImageFileSourceTest.java index b7e5a1d28b..961bf7174c 100644 --- a/core/src/test/java/edu/wpi/grip/core/sources/ImageFileSourceTest.java +++ b/core/src/test/java/edu/wpi/grip/core/sources/ImageFileSourceTest.java @@ -1,6 +1,7 @@ package edu.wpi.grip.core.sources; import com.google.common.eventbus.EventBus; +import edu.wpi.grip.core.sockets.MockOutputSocketFactory; import edu.wpi.grip.core.sockets.OutputSocket; import edu.wpi.grip.util.Files; import edu.wpi.grip.util.ImageWithData; @@ -13,9 +14,7 @@ import java.net.MalformedURLException; import java.net.URISyntaxException; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.Assert.*; /** * @@ -23,18 +22,20 @@ public class ImageFileSourceTest { private final ImageWithData imageFile = Files.imageFile; private final File textFile = Files.textFile; - private static EventBus eventBus; + private EventBus eventBus; + private OutputSocket.Factory osf; @Before public void setUp() throws URISyntaxException { this.eventBus = new EventBus(); + osf = new MockOutputSocketFactory(eventBus); } @Test public void testLoadImageToMat() throws IOException { // Given above setup // When - final ImageFileSource fileSource = new ImageFileSource(eventBus, origin -> null, this.imageFile.file); + final ImageFileSource fileSource = new ImageFileSource(eventBus, osf, origin -> null, this.imageFile.file); fileSource.initialize(); OutputSocket outputSocket = fileSource.getOutputSockets().get(0); @@ -46,7 +47,7 @@ public void testLoadImageToMat() throws IOException { @Test(expected = IOException.class) public void testReadInTextFile() throws IOException { - final ImageFileSource fileSource = new ImageFileSource(eventBus, origin -> null, this.textFile); + final ImageFileSource fileSource = new ImageFileSource(eventBus, osf, origin -> null, this.textFile); fileSource.initialize(); OutputSocket outputSocket = fileSource.getOutputSockets().get(0); assertTrue("No matrix should have been returned.", outputSocket.getValue().get().empty()); @@ -56,18 +57,18 @@ public void testReadInTextFile() throws IOException { public void testReadInFileWithoutExtension() throws MalformedURLException, IOException { final File testFile = new File("temp" + File.separator + "fdkajdl3eaf"); - final ImageFileSource fileSource = new ImageFileSource(eventBus, origin -> null, testFile); + final ImageFileSource fileSource = new ImageFileSource(eventBus, osf, origin -> null, testFile); fileSource.initialize(); fail("initialize() should have thrown an IOException"); } @Test public void testCallingInitializeAfterGetOutputSocketUpdatesOutputSocket() throws IOException { - final ImageFileSource source = new ImageFileSource(eventBus, origin -> null, this.imageFile.file); + final ImageFileSource source = new ImageFileSource(eventBus, osf, origin -> null, this.imageFile.file); // Calling this before loading the image should throw an exception final OutputSocket imageSource = source.getOutputSockets().get(0); assertTrue("The value should not be present if the source hasn't been initialized", imageSource.getValue().get().empty()); source.initialize(); assertFalse("The value should now be present since the source has been initialized", imageSource.getValue().get().empty()); } -} \ No newline at end of file +} diff --git a/core/src/test/java/edu/wpi/grip/core/sources/MockCameraSource.java b/core/src/test/java/edu/wpi/grip/core/sources/MockCameraSource.java index af0c2f3f59..d381d04ebe 100644 --- a/core/src/test/java/edu/wpi/grip/core/sources/MockCameraSource.java +++ b/core/src/test/java/edu/wpi/grip/core/sources/MockCameraSource.java @@ -1,6 +1,7 @@ package edu.wpi.grip.core.sources; import com.google.common.eventbus.EventBus; +import edu.wpi.grip.core.sockets.MockOutputSocketFactory; import edu.wpi.grip.core.util.MockExceptionWitness; import java.io.IOException; @@ -10,11 +11,11 @@ public class MockCameraSource extends CameraSource { private boolean started = false; public MockCameraSource(EventBus eventBus, String address) throws IOException { - super(eventBus, new MockFrameGrabberFactory(), MockExceptionWitness.MOCK_FACTORY, address); + super(eventBus, new MockOutputSocketFactory(eventBus), new MockFrameGrabberFactory(), MockExceptionWitness.MOCK_FACTORY, address); } public MockCameraSource(EventBus eventBus, int deviceNumber) throws IOException { - super(eventBus, new MockFrameGrabberFactory(), MockExceptionWitness.MOCK_FACTORY, deviceNumber); + super(eventBus, new MockOutputSocketFactory(eventBus), new MockFrameGrabberFactory(), MockExceptionWitness.MOCK_FACTORY, deviceNumber); } @Override diff --git a/core/src/test/java/edu/wpi/grip/core/sources/MultiImageFileSourceTest.java b/core/src/test/java/edu/wpi/grip/core/sources/MultiImageFileSourceTest.java index 179a6ce39b..3f661f434e 100644 --- a/core/src/test/java/edu/wpi/grip/core/sources/MultiImageFileSourceTest.java +++ b/core/src/test/java/edu/wpi/grip/core/sources/MultiImageFileSourceTest.java @@ -1,6 +1,7 @@ package edu.wpi.grip.core.sources; import com.google.common.eventbus.EventBus; +import edu.wpi.grip.core.sockets.MockOutputSocketFactory; import edu.wpi.grip.core.sockets.OutputSocket; import edu.wpi.grip.util.Files; import edu.wpi.grip.util.ImageWithData; @@ -19,15 +20,19 @@ public class MultiImageFileSourceTest { private static final File textFile = Files.textFile; private MultiImageFileSource source; private MultiImageFileSource sourceWithIndexSet; + private OutputSocket.Factory osf; @Before public void setUp() throws IOException { + osf = new MockOutputSocketFactory(new EventBus()); source = new MultiImageFileSource( new EventBus(), + osf, origin -> null, Arrays.asList(imageFile.file, gompeiJpegFile.file)); sourceWithIndexSet = new MultiImageFileSource( new EventBus(), + osf, origin -> null, Arrays.asList(imageFile.file, gompeiJpegFile.file), 1); source.initialize(); @@ -38,6 +43,7 @@ public void setUp() throws IOException { public void createMultiImageFileSourceWithTextFile() throws IOException { new MultiImageFileSource( new EventBus(), + osf, origin -> null, Arrays.asList(imageFile.file, gompeiJpegFile.file, textFile)).initialize(); } @@ -71,6 +77,7 @@ public void testLoadFromProperties() throws Exception { final Properties properties = sourceWithIndexSet.getProperties(); final MultiImageFileSource newSource = new MultiImageFileSource( new EventBus(), + osf, origin -> null, properties); newSource.initialize(); @@ -78,4 +85,4 @@ public void testLoadFromProperties() throws Exception { OutputSocket outputSocket = newSource.getOutputSockets().get(0); gompeiJpegFile.assertSameImage(outputSocket.getValue().get()); } -} \ No newline at end of file +} diff --git a/core/src/test/java/edu/wpi/grip/generated/opencv_core/SubtractTest.java b/core/src/test/java/edu/wpi/grip/generated/opencv_core/SubtractTest.java deleted file mode 100644 index 5461dfb3ff..0000000000 --- a/core/src/test/java/edu/wpi/grip/generated/opencv_core/SubtractTest.java +++ /dev/null @@ -1,63 +0,0 @@ -package edu.wpi.grip.generated.opencv_core; - -import com.google.common.eventbus.EventBus; -import edu.wpi.grip.core.sockets.InputSocket; -import edu.wpi.grip.core.sockets.OutputSocket; -import org.bytedeco.javacpp.opencv_core; -import org.bytedeco.javacpp.opencv_core.Mat; -import org.bytedeco.javacpp.opencv_core.Scalar; -import org.junit.Before; -import org.junit.Test; - -import static org.junit.Assert.assertTrue; - - -public class SubtractTest { - private EventBus eventBus; - private Subtract subtractOperation; - - @Before - public void setUp() throws Exception { - this.eventBus = new EventBus(); - this.subtractOperation = new Subtract(); - } - - /** - * This method was found here http://stackoverflow.com/a/32235744/3708426 - * Converted from C++ to Java - * - * @param mat1 The first Mat - * @param mat2 The second Mat - * @return true if the two Matrices are the same - */ - private boolean isMatEqual(Mat mat1, Mat mat2) { - // treat two empty mat as identical as well - if (mat1.empty() && mat2.empty()) { - return true; - } - // if dimensionality of two mat is not identical, these two mat is not identical - if (mat1.cols() != mat2.cols() || mat1.rows() != mat2.rows() || mat1.dims() != mat2.dims()) { - return false; - } - Mat diff = new Mat(); - opencv_core.compare(mat1, mat2, diff, opencv_core.CMP_NE); - int nz = opencv_core.countNonZero(diff); - return nz == 0; - } - - @Test - public void testSubtract() { - InputSocket[] inputs = (InputSocket[]) subtractOperation.createInputSockets(eventBus); - OutputSocket[] outputs = (OutputSocket[]) subtractOperation.createOutputSockets(eventBus); - - int sz[] = {256, 256}; - - inputs[0].setValue(new Mat(2, sz, opencv_core.CV_8U, Scalar.all(9))); - inputs[1].setValue(new Mat(2, sz, opencv_core.CV_8U, Scalar.all(4))); - subtractOperation.perform(inputs, outputs); - - //Then - Mat expectedResult = new Mat(2, sz, opencv_core.CV_8U, Scalar.all(5)); - assertTrue(isMatEqual(outputs[0].getValue().get(), expectedResult)); - } -} \ No newline at end of file diff --git a/core/src/test/resources/edu/wpi/grip/scripts/addition-with-name-and-description.py b/core/src/test/resources/edu/wpi/grip/scripts/addition-with-name-and-description.py index 273a6b005e..1e3090ad45 100644 --- a/core/src/test/resources/edu/wpi/grip/scripts/addition-with-name-and-description.py +++ b/core/src/test/resources/edu/wpi/grip/scripts/addition-with-name-and-description.py @@ -2,7 +2,7 @@ name = "Add" -description = "Compute the sum of two integers" +summary = "Compute the sum of two integers" inputs = [ grip.SocketHints.createNumberSocketHint("a", 0.0), diff --git a/ui/src/main/java/edu/wpi/grip/ui/Main.java b/ui/src/main/java/edu/wpi/grip/ui/Main.java index d5d45748a3..f0cdeee16e 100644 --- a/ui/src/main/java/edu/wpi/grip/ui/Main.java +++ b/ui/src/main/java/edu/wpi/grip/ui/Main.java @@ -10,12 +10,12 @@ import edu.wpi.grip.core.GRIPCoreModule; import edu.wpi.grip.core.PipelineRunner; import edu.wpi.grip.core.events.UnexpectedThrowableEvent; +import edu.wpi.grip.core.operations.CVOperations; import edu.wpi.grip.core.operations.Operations; import edu.wpi.grip.core.operations.network.GRIPNetworkModule; import edu.wpi.grip.core.serialization.Project; import edu.wpi.grip.core.sources.GRIPSourcesHardwareModule; import edu.wpi.grip.core.util.SafeShutdown; -import edu.wpi.grip.generated.CVOperations; import edu.wpi.grip.ui.util.DPIUtility; import javafx.application.Application; import javafx.application.Platform; @@ -39,6 +39,7 @@ public class Main extends Application { @Inject private PipelineRunner pipelineRunner; @Inject private Project project; @Inject private Operations operations; + @Inject private CVOperations cvOperations; @Inject private Logger logger; /** @@ -62,7 +63,7 @@ public void start(Stage stage) throws Exception { if (parameters.contains("--headless")) { // If --headless was specified on the command line, run in headless mode (only use the core module) - injector = Guice.createInjector(new GRIPCoreModule(), new GRIPNetworkModule()); + injector = Guice.createInjector(new GRIPCoreModule(), new GRIPNetworkModule(), new GRIPSourcesHardwareModule()); injector.injectMembers(this); parameters.remove("--headless"); @@ -83,7 +84,7 @@ public void start(Stage stage) throws Exception { } operations.addOperations(); - CVOperations.addOperations(eventBus); + cvOperations.addOperations(); // If there was a file specified on the command line, open it immediately if (!parameters.isEmpty()) { diff --git a/ui/src/main/java/edu/wpi/grip/ui/OperationController.java b/ui/src/main/java/edu/wpi/grip/ui/OperationController.java index 5c63451fdf..0b7043a6f6 100644 --- a/ui/src/main/java/edu/wpi/grip/ui/OperationController.java +++ b/ui/src/main/java/edu/wpi/grip/ui/OperationController.java @@ -2,9 +2,7 @@ import com.google.inject.Inject; import com.google.inject.assistedinject.Assisted; -import edu.wpi.grip.core.Operation; -import edu.wpi.grip.core.Pipeline; -import edu.wpi.grip.core.Step; +import edu.wpi.grip.core.*; import edu.wpi.grip.ui.annotations.ParametrizedController; import edu.wpi.grip.ui.dragging.OperationDragService; import edu.wpi.grip.ui.util.StyleClassNameUtility; @@ -37,43 +35,45 @@ public class OperationController implements Controller { private final Pipeline pipeline; private final Step.Factory stepFactory; private final OperationDragService operationDragService; - private final Operation operation; + private final OperationMetaData operationMetaData; public interface Factory { - OperationController create(Operation operation); + OperationController create(OperationMetaData operationMetaData); } @Inject - OperationController(Pipeline pipeline, Step.Factory stepFactory, OperationDragService operationDragService, @Assisted Operation operation) { + OperationController(Pipeline pipeline, + Step.Factory stepFactory, + OperationDragService operationDragService, + @Assisted OperationMetaData operationMetaData) { this.pipeline = pipeline; this.stepFactory = stepFactory; this.operationDragService = operationDragService; - this.operation = operation; + this.operationMetaData = operationMetaData; } @FXML public void initialize() { - root.setId(StyleClassNameUtility.idNameFor(this.operation)); - this.name.setText(this.operation.getName()); - this.description.setText(this.operation.getDescription()); + final OperationDescription description = operationMetaData.getDescription(); + root.setId(StyleClassNameUtility.idNameFor(description)); + this.name.setText(description.name()); + this.description.setText(description.summary()); - final Tooltip tooltip = new Tooltip(this.operation.getDescription()); + final Tooltip tooltip = new Tooltip(description.summary()); tooltip.setPrefWidth(400.0); tooltip.setWrapText(true); Tooltip.install(root, tooltip); - this.description.setAccessibleHelp(this.operation.getDescription()); + this.description.setAccessibleHelp(description.summary()); - this.operation.getIcon().ifPresent(icon -> this.icon.setImage(new Image(icon))); + description.icon().ifPresent(icon -> this.icon.setImage(new Image(icon))); // Ensures that when this element is hidden that it also removes its size calculations root.managedProperty().bind(root.visibleProperty()); - root.setOnDragDetected(mouseEvent -> { // Tell the drag service that this is the operation that will be received - operationDragService.beginDrag(operation, root, operation.getName()); - + operationDragService.beginDrag(operationMetaData, root, operationMetaData.getDescription().name()); mouseEvent.consume(); }); @@ -84,14 +84,14 @@ public void initialize() { @FXML public void addStep() { - this.pipeline.addStep(stepFactory.create(this.operation)); + this.pipeline.addStep(stepFactory.create(operationMetaData)); } public GridPane getRoot() { return root; } - public Operation getOperation() { - return operation; + public OperationDescription getOperationDescription() { + return operationMetaData.getDescription(); } } diff --git a/ui/src/main/java/edu/wpi/grip/ui/OperationListController.java b/ui/src/main/java/edu/wpi/grip/ui/OperationListController.java index 43cef6099e..7be28cb9c9 100644 --- a/ui/src/main/java/edu/wpi/grip/ui/OperationListController.java +++ b/ui/src/main/java/edu/wpi/grip/ui/OperationListController.java @@ -2,8 +2,9 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.eventbus.Subscribe; +import com.google.inject.Inject; import com.sun.javafx.application.PlatformImpl; -import edu.wpi.grip.core.Operation; +import edu.wpi.grip.core.OperationMetaData; import edu.wpi.grip.core.events.OperationAddedEvent; import edu.wpi.grip.ui.annotations.ParametrizedController; import edu.wpi.grip.ui.util.ControllerMap; @@ -17,13 +18,11 @@ import javafx.scene.control.Tab; import javafx.scene.layout.VBox; -import javax.inject.Inject; - /** * Controller for a VBox of {@link OperationController}s. The user data for this should be what category it shows, * and the filterText property allows for fuzzy searching the operations. * - * @see edu.wpi.grip.core.Operation.Category + * @see edu.wpi.grip.core.OperationDescription.Category */ @ParametrizedController(url = "OperationList.fxml") public class OperationListController { @@ -53,8 +52,8 @@ public void initialize() { String filter = filterText.getValue(); long numMatches = operationsMapManager.keySet().stream() .filter(key -> { - boolean visible = SearchUtility.fuzzyContains(key.getOperation().getName(), filter) - || SearchUtility.fuzzyContains(key.getOperation().getDescription(), filter); + boolean visible = SearchUtility.fuzzyContains(key.getOperationDescription().name(), filter) + || SearchUtility.fuzzyContains(key.getOperationDescription().summary(), filter); operationsMapManager.get(key).setVisible(visible); return visible; }).count(); @@ -83,11 +82,11 @@ public void initialize() { @Subscribe public void onOperationAdded(OperationAddedEvent event) { - Operation operation = event.getOperation(); + OperationMetaData operationMetaData = event.getOperation(); - if (root.getUserData() == null || operation.getCategory() == root.getUserData()) { + if (root.getUserData() == null || operationMetaData.getDescription().category() == root.getUserData()) { PlatformImpl.runAndWait(() -> - operationsMapManager.add(operationControllerFactory.create(event.getOperation()))); + operationsMapManager.add(operationControllerFactory.create(operationMetaData))); } } diff --git a/ui/src/main/java/edu/wpi/grip/ui/PaletteController.java b/ui/src/main/java/edu/wpi/grip/ui/PaletteController.java index 0d2307b7a9..02968e1190 100644 --- a/ui/src/main/java/edu/wpi/grip/ui/PaletteController.java +++ b/ui/src/main/java/edu/wpi/grip/ui/PaletteController.java @@ -1,6 +1,6 @@ package edu.wpi.grip.ui; -import edu.wpi.grip.core.Operation; +import edu.wpi.grip.core.OperationDescription; import javafx.beans.property.ObjectProperty; import javafx.fxml.FXML; import javafx.scene.control.Tab; @@ -41,12 +41,12 @@ public void initialize() { throw new RuntimeException(e); } - imgprocOperations.setUserData(Operation.Category.IMAGE_PROCESSING); - featureOperations.setUserData(Operation.Category.FEATURE_DETECTION); - networkOperations.setUserData(Operation.Category.NETWORK); - logicalOperations.setUserData(Operation.Category.LOGICAL); - opencvOperations.setUserData(Operation.Category.OPENCV); - miscellaneousOperations.setUserData(Operation.Category.MISCELLANEOUS); + imgprocOperations.setUserData(OperationDescription.Category.IMAGE_PROCESSING); + featureOperations.setUserData(OperationDescription.Category.FEATURE_DETECTION); + networkOperations.setUserData(OperationDescription.Category.NETWORK); + logicalOperations.setUserData(OperationDescription.Category.LOGICAL); + opencvOperations.setUserData(OperationDescription.Category.OPENCV); + miscellaneousOperations.setUserData(OperationDescription.Category.MISCELLANEOUS); // Bind the filterText of all of the individual tabs to the search field operationSearch.textProperty().addListener(observable -> { diff --git a/ui/src/main/java/edu/wpi/grip/ui/dragging/OperationDragService.java b/ui/src/main/java/edu/wpi/grip/ui/dragging/OperationDragService.java index 14ba54fac4..40ce584d4b 100644 --- a/ui/src/main/java/edu/wpi/grip/ui/dragging/OperationDragService.java +++ b/ui/src/main/java/edu/wpi/grip/ui/dragging/OperationDragService.java @@ -3,13 +3,14 @@ import com.google.inject.Singleton; import edu.wpi.grip.core.Operation; +import edu.wpi.grip.core.OperationMetaData; /** * Service for dragging an {@link Operation} from the {@link edu.wpi.grip.ui.pipeline.PipelineController} * to the {@link edu.wpi.grip.ui.pipeline.PipelineController}. */ @Singleton -public class OperationDragService extends DragService { +public class OperationDragService extends DragService { public OperationDragService() { super("operation"); diff --git a/ui/src/main/java/edu/wpi/grip/ui/pipeline/OutputSocketController.java b/ui/src/main/java/edu/wpi/grip/ui/pipeline/OutputSocketController.java index 31639c74b1..fc734ea943 100644 --- a/ui/src/main/java/edu/wpi/grip/ui/pipeline/OutputSocketController.java +++ b/ui/src/main/java/edu/wpi/grip/ui/pipeline/OutputSocketController.java @@ -94,12 +94,12 @@ public SocketHandleView getHandle() { @Subscribe public void onSocketPreviewChangedEvent(SocketPreviewChangedEvent event) { - if (event.getSocket().equals(socket)) { + if (event.isRegarding(socket)) { // Only try to update the button if the two aren't the same // This really should only happen we deserialize the pipeline - if (event.getSocket().isPreviewed() != preview.isSelected()) { + if (socket.isPreviewed() != preview.isSelected()) { preview.selectedProperty().removeListener(previewListener); - preview.setSelected(event.getSocket().isPreviewed()); + preview.setSelected(socket.isPreviewed()); preview.selectedProperty().addListener(previewListener); } } diff --git a/ui/src/main/java/edu/wpi/grip/ui/pipeline/SocketHandleView.java b/ui/src/main/java/edu/wpi/grip/ui/pipeline/SocketHandleView.java index 9ec43e0ff4..d314f84d3e 100644 --- a/ui/src/main/java/edu/wpi/grip/ui/pipeline/SocketHandleView.java +++ b/ui/src/main/java/edu/wpi/grip/ui/pipeline/SocketHandleView.java @@ -174,7 +174,7 @@ public BooleanProperty connectedProperty() { @Subscribe public void onSocketConnectedChanged(SocketConnectedChangedEvent event) { - if (event.getSocket() == this.socket) { + if (this.socket.equals(event.getSocket())) { // Set the handle as "selected" whenever there is at least one connection connected to it this.connectedProperty().set(!this.socket.getConnections().isEmpty()); } diff --git a/ui/src/main/java/edu/wpi/grip/ui/pipeline/StepController.java b/ui/src/main/java/edu/wpi/grip/ui/pipeline/StepController.java index 1a97c6ff63..b4f4711f57 100644 --- a/ui/src/main/java/edu/wpi/grip/ui/pipeline/StepController.java +++ b/ui/src/main/java/edu/wpi/grip/ui/pipeline/StepController.java @@ -1,10 +1,10 @@ package edu.wpi.grip.ui.pipeline; import com.google.inject.assistedinject.Assisted; -import edu.wpi.grip.core.sockets.InputSocket; -import edu.wpi.grip.core.sockets.OutputSocket; import edu.wpi.grip.core.Pipeline; import edu.wpi.grip.core.Step; +import edu.wpi.grip.core.sockets.InputSocket; +import edu.wpi.grip.core.sockets.OutputSocket; import edu.wpi.grip.ui.Controller; import edu.wpi.grip.ui.annotations.ParametrizedController; import edu.wpi.grip.ui.components.ExceptionWitnessResponderButton; @@ -22,6 +22,7 @@ import javafx.scene.layout.VBox; import javax.inject.Inject; +import java.io.InputStream; import java.util.Collection; /** @@ -85,8 +86,8 @@ private void initialize() { outputSocketMapManager = new ControllerMap<>(outputs.getChildren()); root.getStyleClass().add(StyleClassNameUtility.classNameFor(step)); - title.setText(step.getOperation().getName()); - step.getOperation().getIcon().ifPresent(icon -> this.icon.setImage(new Image(icon))); + title.setText(step.getOperationDescription().name()); + step.getOperationDescription().icon().ifPresent(icon -> this.icon.setImage(new Image(InputStream.class.cast(icon)))); buttons.getChildren().add(0, exceptionWitnessResponderButtonFactory.create(step, "Step Error")); // Add a SocketControlView for each input socket and output socket diff --git a/ui/src/main/java/edu/wpi/grip/ui/pipeline/input/CheckboxInputSocketController.java b/ui/src/main/java/edu/wpi/grip/ui/pipeline/input/CheckboxInputSocketController.java index 22a519d658..9197fe13ac 100644 --- a/ui/src/main/java/edu/wpi/grip/ui/pipeline/input/CheckboxInputSocketController.java +++ b/ui/src/main/java/edu/wpi/grip/ui/pipeline/input/CheckboxInputSocketController.java @@ -53,8 +53,8 @@ private void assignSocketValue(final Optional value) { @Subscribe public void updateCheckboxFromSocket(SocketChangedEvent event) { - if (event.getSocket() == this.getSocket()) { - platform.runAsSoonAsPossible(() -> assignSocketValue(event.getSocket().getValue())); + if (event.isRegarding(getSocket())) { + platform.runAsSoonAsPossible(() -> assignSocketValue(getSocket().getValue())); } } } diff --git a/ui/src/main/java/edu/wpi/grip/ui/pipeline/input/ListSpinnerInputSocketController.java b/ui/src/main/java/edu/wpi/grip/ui/pipeline/input/ListSpinnerInputSocketController.java index 9b3b30ca77..37ec62f64f 100644 --- a/ui/src/main/java/edu/wpi/grip/ui/pipeline/input/ListSpinnerInputSocketController.java +++ b/ui/src/main/java/edu/wpi/grip/ui/pipeline/input/ListSpinnerInputSocketController.java @@ -69,7 +69,7 @@ public void initialize() { @Subscribe public void updateSpinnerFromSocket(SocketChangedEvent event) { - if (event.getSocket() == this.getSocket()) { + if (event.isRegarding(this.getSocket())) { platform.runAsSoonAsPossible(() -> { // Remove the invalidation listener when we set the value. This listener is useful for updating the socket value // when the user changes the spinner, but since we're setting the spinner value from the socket value, calling it diff --git a/ui/src/main/java/edu/wpi/grip/ui/pipeline/input/NumberSpinnerInputSocketController.java b/ui/src/main/java/edu/wpi/grip/ui/pipeline/input/NumberSpinnerInputSocketController.java index 9a264a51f5..87b19160ca 100644 --- a/ui/src/main/java/edu/wpi/grip/ui/pipeline/input/NumberSpinnerInputSocketController.java +++ b/ui/src/main/java/edu/wpi/grip/ui/pipeline/input/NumberSpinnerInputSocketController.java @@ -66,7 +66,7 @@ public void initialize() { @Subscribe public void updateSpinnerFromSocket(SocketChangedEvent event) { - if (event.getSocket() == this.getSocket()) { + if (event.isRegarding(this.getSocket())) { platform.runAsSoonAsPossible(() -> { // Remove the invalidation listener when we set the value. This listener is useful for updating the socket value // when the user changes the spinner, but since we're setting the spinner value from the socket value, calling it 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 63c06f8578..f27e5c8b9d 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 @@ -102,7 +102,7 @@ private String getLowHighLabelText() { @Subscribe public void updateSliderValue(SocketChangedEvent event) { - if (event.getSocket() == this.getSocket()) { + 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()); } diff --git a/ui/src/main/java/edu/wpi/grip/ui/pipeline/input/SelectInputSocketController.java b/ui/src/main/java/edu/wpi/grip/ui/pipeline/input/SelectInputSocketController.java index 8cdf4f837e..4f8dfa59b1 100644 --- a/ui/src/main/java/edu/wpi/grip/ui/pipeline/input/SelectInputSocketController.java +++ b/ui/src/main/java/edu/wpi/grip/ui/pipeline/input/SelectInputSocketController.java @@ -60,7 +60,7 @@ public void initialize() { @Subscribe public void updateChoiceBoxFromSocket(SocketChangedEvent event) { - if (event.getSocket() == this.getSocket()) { + if (event.isRegarding(this.getSocket())) { platform.runAsSoonAsPossible(() -> { // Remove the invalidation listener when we set the value. This listener is useful for updating the socket value // when the user changes the spinner, but since we're setting the spinner value from the socket value, calling it diff --git a/ui/src/main/java/edu/wpi/grip/ui/pipeline/input/SliderInputSocketController.java b/ui/src/main/java/edu/wpi/grip/ui/pipeline/input/SliderInputSocketController.java index f80d4549b0..1ca4ce4934 100644 --- a/ui/src/main/java/edu/wpi/grip/ui/pipeline/input/SliderInputSocketController.java +++ b/ui/src/main/java/edu/wpi/grip/ui/pipeline/input/SliderInputSocketController.java @@ -70,7 +70,7 @@ public void initialize() { @Subscribe public void updateSliderValue(SocketChangedEvent event) { - if (event.getSocket() == this.getSocket()) { + if (event.isRegarding(this.getSocket())) { platform.runAsSoonAsPossible(()-> this.slider.setValue(this.getSocket().getValue().get().doubleValue())); } } diff --git a/ui/src/main/java/edu/wpi/grip/ui/pipeline/input/TextFieldInputSocketController.java b/ui/src/main/java/edu/wpi/grip/ui/pipeline/input/TextFieldInputSocketController.java index fbf0073454..de3db4fb3c 100644 --- a/ui/src/main/java/edu/wpi/grip/ui/pipeline/input/TextFieldInputSocketController.java +++ b/ui/src/main/java/edu/wpi/grip/ui/pipeline/input/TextFieldInputSocketController.java @@ -45,7 +45,7 @@ public void initialize() { @Subscribe public void updateTextFieldFromSocket(SocketChangedEvent event) { - if (event.getSocket() == getSocket()) { + if (event.isRegarding(getSocket())) { final String text = getSocket().getValue().get(); Platform.runLater(() -> textField.setText(text)); } diff --git a/ui/src/main/java/edu/wpi/grip/ui/preview/PreviewsController.java b/ui/src/main/java/edu/wpi/grip/ui/preview/PreviewsController.java index f83f53d47f..e4d4095050 100644 --- a/ui/src/main/java/edu/wpi/grip/ui/preview/PreviewsController.java +++ b/ui/src/main/java/edu/wpi/grip/ui/preview/PreviewsController.java @@ -64,7 +64,7 @@ public synchronized void onSocketPreviewChanged(SocketPreviewChangedEvent event) } else { // When a socket is no longer marked as previewed, find and remove the view associated with it previews.stream() - .filter(view -> view.getSocket() == socket) + .filter(view -> event.isRegarding(view.getSocket())) .findFirst() .ifPresent(preview -> { previews.remove(preview); diff --git a/ui/src/main/java/edu/wpi/grip/ui/preview/SocketPreviewView.java b/ui/src/main/java/edu/wpi/grip/ui/preview/SocketPreviewView.java index cdce809c7e..e2fbaefa11 100644 --- a/ui/src/main/java/edu/wpi/grip/ui/preview/SocketPreviewView.java +++ b/ui/src/main/java/edu/wpi/grip/ui/preview/SocketPreviewView.java @@ -31,7 +31,7 @@ private String getTitle() { String title = ""; if (this.socket.getStep().isPresent()) { - title += this.socket.getStep().get().getOperation().getName() + " -> "; + title += this.socket.getStep().get().getOperationDescription().name() + " -> "; } title += this.socket.getSocketHint().getIdentifier(); diff --git a/ui/src/main/java/edu/wpi/grip/ui/util/StyleClassNameUtility.java b/ui/src/main/java/edu/wpi/grip/ui/util/StyleClassNameUtility.java index 8b71feef3e..4b44f113d5 100644 --- a/ui/src/main/java/edu/wpi/grip/ui/util/StyleClassNameUtility.java +++ b/ui/src/main/java/edu/wpi/grip/ui/util/StyleClassNameUtility.java @@ -2,6 +2,7 @@ import edu.wpi.grip.core.Connection; import edu.wpi.grip.core.Operation; +import edu.wpi.grip.core.OperationDescription; import edu.wpi.grip.core.Step; /** @@ -17,11 +18,11 @@ private StyleClassNameUtility() { /** * Returns the CSS id name for an operation * - * @param operation The operation to get the class name for + * @param operationDescription the description of the operation * @return The CSS id for the operation. To use as a css selector then prepend the string with a '#' */ - public static String idNameFor(Operation operation) { - return shortNameFor(operation).append("-operation").toString(); + public static String idNameFor(OperationDescription operationDescription) { + return shortNameFor(operationDescription).append("-operation").toString(); } /** @@ -31,7 +32,7 @@ public static String idNameFor(Operation operation) { * @return The CSS class for the step in the pipeline. To use as a css selector then prepend the string with a '.' */ public static String classNameFor(Step step) { - return classNameForStepHolding(step.getOperation()); + return classNameForStepHolding(step.getOperationDescription()); } /** @@ -40,25 +41,25 @@ public static String classNameFor(Step step) { * @param operation The operation to get the step's class name for * @return The CSS class for this step. To use as a css selector then prepend the string with a '.' */ - public static String classNameForStepHolding(Operation operation) { + public static String classNameForStepHolding(OperationDescription operation) { return shortNameFor(operation).append("-step").toString(); } - public static String cssSelectorForOutputSocketHandleOnStepHolding(Operation operation) { + public static String cssSelectorForOutputSocketHandleOnStepHolding(OperationDescription operation) { return ".pipeline ." + classNameForStepHolding(operation) + " .socket-handle.output"; } - public static String cssSelectorForInputSocketHandleOnStepHolding(Operation operation) { + public static String cssSelectorForInputSocketHandleOnStepHolding(OperationDescription operation) { return ".pipeline ." + classNameForStepHolding(operation) + " .socket-handle.input"; } public static String cssSelectorForOutputSocketHandleOn(Step step) { - return cssSelectorForOutputSocketHandleOnStepHolding(step.getOperation()); + return cssSelectorForOutputSocketHandleOnStepHolding(step.getOperationDescription()); } public static String cssSelectorForInputSocketHandleOn(Step step) { - return cssSelectorForInputSocketHandleOnStepHolding(step.getOperation()); + return cssSelectorForInputSocketHandleOnStepHolding(step.getOperationDescription()); } /** @@ -74,7 +75,7 @@ public static String classNameFor(Connection connection) { connection.getInputSocket().getSocketHint().getIdentifier(); } - private static StringBuilder shortNameFor(Operation operation) { - return new StringBuilder(operation.getName().toLowerCase().replace(" ", "-")); + private static StringBuilder shortNameFor(OperationDescription operationDescription) { + return new StringBuilder(operationDescription.name().toLowerCase().replace(" ", "-")); } } diff --git a/ui/src/test/java/edu/wpi/grip/ui/MainWindowTest.java b/ui/src/test/java/edu/wpi/grip/ui/MainWindowTest.java index 3e0d56c765..fb67c75267 100644 --- a/ui/src/test/java/edu/wpi/grip/ui/MainWindowTest.java +++ b/ui/src/test/java/edu/wpi/grip/ui/MainWindowTest.java @@ -7,6 +7,8 @@ import com.google.inject.util.Modules; import edu.wpi.grip.core.*; import edu.wpi.grip.core.events.OperationAddedEvent; +import edu.wpi.grip.core.sockets.InputSocket; +import edu.wpi.grip.core.sockets.OutputSocket; import edu.wpi.grip.ui.util.DPIUtility; import edu.wpi.grip.ui.util.StyleClassNameUtility; import edu.wpi.grip.util.GRIPCoreTestModule; @@ -29,8 +31,8 @@ public class MainWindowTest extends ApplicationTest { private final GRIPCoreTestModule testModule = new GRIPCoreTestModule(); private Pipeline pipeline; private PipelineRunner pipelineRunner; - private Operation addOperation; - private AdditionOperation additionOperation; + private OperationMetaData addOperation; + private OperationMetaData additionOperation; @Override @SuppressWarnings("PMD.SignatureDeclareThrowsException") @@ -47,8 +49,8 @@ public void start(Stage stage) throws Exception { pipelineRunner = injector.getInstance(PipelineRunner.class); final EventBus eventBus = injector.getInstance(EventBus.class); - addOperation = new AddOperation(); - additionOperation = new AdditionOperation(); + addOperation = new OperationMetaData(AddOperation.DESCRIPTION, () -> new AddOperation(eventBus)); + additionOperation = new OperationMetaData(AdditionOperation.DESCRIPTION, () -> new AdditionOperation(injector.getInstance(InputSocket.Factory.class), injector.getInstance(OutputSocket.Factory.class))); eventBus.post(new OperationAddedEvent(addOperation)); eventBus.post(new OperationAddedEvent(additionOperation)); @@ -66,34 +68,34 @@ public void tearDown() { @Test public void testShouldCreateNewOperationInPipelineView() { // Given: - clickOn(addOperation.getName()); + clickOn(addOperation.getDescription().name()); WaitForAsyncUtils.waitForFxEvents(); // Then: - final String cssSelector = "." + StyleClassNameUtility.classNameForStepHolding(addOperation); + final String cssSelector = "." + StyleClassNameUtility.classNameForStepHolding(addOperation.getDescription()); verifyThat(cssSelector, NodeMatchers.isNotNull()); verifyThat(cssSelector, NodeMatchers.isVisible()); assertEquals(STEP_NOT_ADDED_MSG, 1, pipeline.getSteps().size()); - assertEquals("Step added was not this addOperation", addOperation, pipeline.getSteps().get(0).getOperation()); + assertEquals("Step added was not this addOperation", AddOperation.DESCRIPTION, pipeline.getSteps().get(0).getOperationDescription()); } @Test public void testDragOperationFromPaletteToPipeline() { // Given: - drag(addOperation.getName()) + drag(addOperation.getDescription().name()) .dropTo(".steps"); WaitForAsyncUtils.waitForFxEvents(); // Then: - final String cssSelector = "." + StyleClassNameUtility.classNameForStepHolding(addOperation); + final String cssSelector = "." + StyleClassNameUtility.classNameForStepHolding(addOperation.getDescription()); verifyThat(cssSelector, NodeMatchers.isNotNull()); verifyThat(cssSelector, NodeMatchers.isVisible()); assertEquals(STEP_NOT_ADDED_MSG, 1, pipeline.getSteps().size()); - assertEquals("Step added was not this addOperation", addOperation, pipeline.getSteps().get(0).getOperation()); + assertEquals("Step added was not this addOperation", AddOperation.DESCRIPTION, pipeline.getSteps().get(0).getOperationDescription()); } @Test @@ -102,21 +104,21 @@ public void testDragOperationFromPaletteToLeftOfExistingStep() { testDragOperationFromPaletteToPipeline(); // Now add a second step before it - drag(additionOperation.getName()) + drag(additionOperation.getDescription().name()) // We drag to the input socket hint handle because this will always be on the left side of the // step. This should cause the UI to put the new step on the left side - .dropTo(StyleClassNameUtility.cssSelectorForInputSocketHandleOnStepHolding(addOperation)); + .dropTo(StyleClassNameUtility.cssSelectorForInputSocketHandleOnStepHolding(addOperation.getDescription())); WaitForAsyncUtils.waitForFxEvents(); // Then: - final String cssSelector = "." + StyleClassNameUtility.classNameForStepHolding(additionOperation); + final String cssSelector = "." + StyleClassNameUtility.classNameForStepHolding(additionOperation.getDescription()); verifyThat(cssSelector, NodeMatchers.isNotNull()); verifyThat(cssSelector, NodeMatchers.isVisible()); assertEquals(STEP_NOT_ADDED_MSG, 2, pipeline.getSteps().size()); assertEquals("Step added was not added in the right place in the pipeline", - additionOperation, pipeline.getSteps().get(0).getOperation()); + AdditionOperation.DESCRIPTION, pipeline.getSteps().get(0).getOperationDescription()); } @Test @@ -125,22 +127,22 @@ public void testDragOperationFromPaletteToRightOfExistingStep() { testDragOperationFromPaletteToPipeline(); // Now add a second step after it - drag(additionOperation.getName()) + drag(additionOperation.getDescription().name()) // We drag to the output socket hint handle because this will always be on the right side of the // step. This should cause the UI to put the new step on the right side - .dropTo(StyleClassNameUtility.cssSelectorForOutputSocketHandleOnStepHolding(addOperation)); + .dropTo(StyleClassNameUtility.cssSelectorForOutputSocketHandleOnStepHolding(addOperation.getDescription())); WaitForAsyncUtils.waitForFxEvents(); // Then: - final String cssSelector = "." + StyleClassNameUtility.classNameForStepHolding(additionOperation); + final String cssSelector = "." + StyleClassNameUtility.classNameForStepHolding(additionOperation.getDescription()); verifyThat(cssSelector, NodeMatchers.isNotNull()); verifyThat(cssSelector, NodeMatchers.isVisible()); assertEquals(STEP_NOT_ADDED_MSG, 2, pipeline.getSteps().size()); assertEquals("Step added was not added in the right place in the pipeline", - additionOperation, pipeline.getSteps().get(1).getOperation()); + AdditionOperation.DESCRIPTION, pipeline.getSteps().get(1).getOperationDescription()); } -} \ No newline at end of file +} diff --git a/ui/src/test/java/edu/wpi/grip/ui/PaletteTest.java b/ui/src/test/java/edu/wpi/grip/ui/PaletteTest.java index 376fc485cc..ea0b26f34a 100644 --- a/ui/src/test/java/edu/wpi/grip/ui/PaletteTest.java +++ b/ui/src/test/java/edu/wpi/grip/ui/PaletteTest.java @@ -5,11 +5,14 @@ import com.google.inject.Guice; import com.google.inject.Injector; import com.google.inject.util.Modules; -import edu.wpi.grip.core.AdditionOperation; import edu.wpi.grip.core.Operation; +import edu.wpi.grip.core.OperationDescription; +import edu.wpi.grip.core.OperationMetaData; import edu.wpi.grip.core.Step; import edu.wpi.grip.core.events.OperationAddedEvent; import edu.wpi.grip.core.events.StepAddedEvent; +import edu.wpi.grip.core.sockets.InputSocket; +import edu.wpi.grip.core.sockets.OutputSocket; import edu.wpi.grip.util.GRIPCoreTestModule; import javafx.fxml.FXMLLoader; import javafx.scene.Scene; @@ -19,6 +22,8 @@ import org.testfx.util.WaitForAsyncUtils; import java.io.IOException; +import java.util.Collections; +import java.util.List; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -49,8 +54,8 @@ public void stop() { @Test public void testPalette() { // Given a single operation... - Operation addition = new AdditionOperation(); - eventBus.post(new OperationAddedEvent(addition)); + Operation operation = new TestOperation(); + eventBus.post(new OperationAddedEvent(new OperationMetaData(TestOperation.DESCRIPTION, () -> operation))); // Record when a a StepAddedEvent happens Step[] step = new Step[]{null}; @@ -62,13 +67,32 @@ public void onStepAdded(StepAddedEvent event) { }); // If we click on the operation button in the palette... - clickOn("#add-operation"); + clickOn("#test-operation"); WaitForAsyncUtils.waitForFxEvents(); // Then there should be a step added assertNotNull("Clicking on palette did not add a new step", step[0]); - assertEquals("Clicking on palette did not add the correct step", addition, step[0].getOperation()); + assertEquals("Clicking on palette did not add the correct step", TestOperation.DESCRIPTION, step[0].getOperationDescription()); } + private static class TestOperation implements Operation { + public static final OperationDescription DESCRIPTION + = OperationDescription.builder().name("test").summary("test").build(); + + @Override + public List getInputSockets() { + return Collections.emptyList(); + } + + @Override + public List getOutputSockets() { + return Collections.emptyList(); + } + + @Override + public void perform() { + //no-op + } + } } diff --git a/ui/src/test/java/edu/wpi/grip/ui/pipeline/PipelineUITest.java b/ui/src/test/java/edu/wpi/grip/ui/pipeline/PipelineUITest.java index 45069f4a16..9e856ac39b 100644 --- a/ui/src/test/java/edu/wpi/grip/ui/pipeline/PipelineUITest.java +++ b/ui/src/test/java/edu/wpi/grip/ui/pipeline/PipelineUITest.java @@ -38,8 +38,8 @@ public class PipelineUITest extends ApplicationTest { private GRIPCoreTestModule testModule; private EventBus eventBus; - private AdditionOperation additionOperation; - private SubtractionOperation subtractionOperation; + private OperationMetaData additionOperation; + private OperationMetaData subtractionOperation; private PipelineController pipelineController; private Pipeline pipeline; @@ -50,8 +50,10 @@ public void start(Stage stage) { final Injector injector = Guice.createInjector(Modules.override(testModule).with(new GRIPUIModule())); eventBus = injector.getInstance(EventBus.class); pipeline = injector.getInstance(Pipeline.class); - additionOperation = new AdditionOperation(); - subtractionOperation = new SubtractionOperation(); + InputSocket.Factory isf = injector.getInstance(InputSocket.Factory.class); + OutputSocket.Factory osf = injector.getInstance(OutputSocket.Factory.class); + additionOperation = new OperationMetaData(AdditionOperation.DESCRIPTION, () ->new AdditionOperation(isf, osf)); + subtractionOperation = new OperationMetaData(SubtractionOperation.DESCRIPTION, () -> new SubtractionOperation(isf, osf)); pipelineController = injector.getInstance(PipelineController.class); final Scene scene = new Scene(TestAnnotationFXMLLoader.load(pipelineController), 800, 600); stage.setScene(scene); @@ -139,8 +141,8 @@ private void addAdditionOperation() { addOperation(1, additionOperation); } - private Step addOperation(int count, Operation operation) { - final Step step = new Step.Factory(eventBus, origin -> new MockExceptionWitness(eventBus, origin)).create(operation); + private Step addOperation(int count, OperationMetaData operationMetaData) { + final Step step = new Step.Factory(origin -> new MockExceptionWitness(eventBus, origin)).create(operationMetaData); pipeline.addStep(step); // Wait for the event to propagate to the UI @@ -151,4 +153,4 @@ private Step addOperation(int count, Operation operation) { } -} \ No newline at end of file +} diff --git a/ui/src/test/java/edu/wpi/grip/ui/pipeline/input/InputSocketControllerFactoryTest.java b/ui/src/test/java/edu/wpi/grip/ui/pipeline/input/InputSocketControllerFactoryTest.java index 73de0f5cb9..1cb5d9bcb4 100644 --- a/ui/src/test/java/edu/wpi/grip/ui/pipeline/input/InputSocketControllerFactoryTest.java +++ b/ui/src/test/java/edu/wpi/grip/ui/pipeline/input/InputSocketControllerFactoryTest.java @@ -4,12 +4,11 @@ import com.google.inject.Guice; import com.google.inject.Injector; import com.google.inject.util.Modules; -import edu.wpi.grip.core.sockets.InputSocket; -import edu.wpi.grip.core.Operation; +import edu.wpi.grip.core.OperationMetaData; import edu.wpi.grip.core.Palette; import edu.wpi.grip.core.Step; import edu.wpi.grip.core.operations.OperationsFactory; -import edu.wpi.grip.generated.CVOperations; +import edu.wpi.grip.core.sockets.InputSocket; import edu.wpi.grip.ui.GRIPUIModule; import edu.wpi.grip.util.GRIPCoreTestModule; import javafx.scene.Scene; @@ -27,6 +26,7 @@ import static org.testfx.api.FxAssert.verifyThat; + @RunWith(Parameterized.class) public class InputSocketControllerFactoryTest extends ApplicationTest { @@ -35,7 +35,7 @@ public class InputSocketControllerFactoryTest extends ApplicationTest { private InputSocketControllerFactory inputSocketControllerFactory; private GridPane gridPane; - private final Operation operation; + private final OperationMetaData operationMeta; @SuppressWarnings({"PMD.UnusedPrivateField", "PMD.SingularField"}) private final String name; @@ -48,14 +48,14 @@ public static Collection data() { final Palette palette = injector.getInstance(Palette.class); final EventBus eventBus = injector.getInstance(EventBus.class); OperationsFactory.create(eventBus).addOperations(); - CVOperations.addOperations(eventBus); - Collection operations = palette.getOperations(); + OperationsFactory.createCV(eventBus).addOperations(); + Collection operationMetas = palette.getOperations(); - Object[][] params = new Object[operations.size()][2]; + Object[][] params = new Object[operationMetas.size()][2]; final int[] index = {0}; - operations.forEach(operation -> { - params[index[0]][0] = operation; - params[index[0]][1] = operation.getName(); + operationMetas.forEach(operationMeta -> { + params[index[0]][0] = operationMeta; + params[index[0]][1] = operationMeta.getDescription().name(); index[0]++; }); @@ -65,12 +65,12 @@ public static Collection data() { } /** - * @param operation The operation under test - * @param name The name. This is used for logging if the tests fail + * @param operationMeta The operation under test + * @param name The name. This is used for logging if the tests fail */ - public InputSocketControllerFactoryTest(Operation operation, String name) { + public InputSocketControllerFactoryTest(OperationMetaData operationMeta, String name) { super(); - this.operation = operation; + this.operationMeta = operationMeta; this.name = name; } @@ -96,7 +96,7 @@ public void tearDown() { @Test @SuppressWarnings("PMD.JUnitTestsShouldIncludeAssert") public void testCreateAllKnownInputSocketControllers() throws Exception { - final Step step = stepFactory.create(operation); + final Step step = stepFactory.create(operationMeta); interact(() -> { for (int i = 0; i < step.getInputSockets().size(); i++) { final InputSocket inputSocket = step.getInputSockets().get(i); @@ -107,4 +107,4 @@ public void testCreateAllKnownInputSocketControllers() throws Exception { } }); } -} \ No newline at end of file +} diff --git a/ui/src/test/java/edu/wpi/grip/ui/preview/ImageSocketPreviewViewTest.java b/ui/src/test/java/edu/wpi/grip/ui/preview/ImageSocketPreviewViewTest.java index 2d30641c1d..0e4627776e 100644 --- a/ui/src/test/java/edu/wpi/grip/ui/preview/ImageSocketPreviewViewTest.java +++ b/ui/src/test/java/edu/wpi/grip/ui/preview/ImageSocketPreviewViewTest.java @@ -1,36 +1,56 @@ package edu.wpi.grip.ui.preview; import com.google.common.eventbus.EventBus; +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.util.Modules; + import edu.wpi.grip.core.sockets.OutputSocket; import edu.wpi.grip.core.sockets.SocketHint; +import edu.wpi.grip.ui.GRIPUIModule; import edu.wpi.grip.ui.util.MockGripPlatform; import edu.wpi.grip.util.Files; -import javafx.scene.Scene; -import javafx.stage.Stage; +import edu.wpi.grip.util.GRIPCoreTestModule; + import org.bytedeco.javacpp.opencv_core.Mat; +import org.junit.After; import org.junit.Test; import org.testfx.framework.junit.ApplicationTest; import org.testfx.matcher.base.NodeMatchers; import org.testfx.util.WaitForAsyncUtils; +import javafx.scene.Scene; +import javafx.stage.Stage; + import static org.testfx.api.FxAssert.verifyThat; public class ImageSocketPreviewViewTest extends ApplicationTest { + private GRIPCoreTestModule testModule; private static final String identifier = "image"; + @Override public void start(Stage stage) { + testModule = new GRIPCoreTestModule(); + testModule.setUp(); + + final Injector injector = Guice.createInjector(Modules.override(testModule).with(new GRIPUIModule())); final ImageSocketPreviewView imageSocketPreviewView = new ImageSocketPreviewView(new MockGripPlatform(new EventBus()), - new OutputSocket<>( - new EventBus(), new SocketHint.Builder<>(Mat.class) - .identifier(identifier) - .initialValueSupplier(Files.gompeiJpegFile::createMat) - .build())); + injector.getInstance(OutputSocket.Factory.class) + .create(new SocketHint.Builder<>(Mat.class) + .identifier(identifier) + .initialValueSupplier(Files.gompeiJpegFile::createMat) + .build())); final Scene scene = new Scene(imageSocketPreviewView); stage.setScene(scene); stage.show(); } + @After + public void tearDown() { + testModule.tearDown(); + } + @Test @SuppressWarnings("PMD.JUnitTestsShouldIncludeAssert") public void testIfImageRenders() { diff --git a/ui/src/test/java/edu/wpi/grip/ui/preview/PointSizeSocketPreviewViewTest.java b/ui/src/test/java/edu/wpi/grip/ui/preview/PointSizeSocketPreviewViewTest.java index 577f2ab6b0..c33bd2aa3d 100644 --- a/ui/src/test/java/edu/wpi/grip/ui/preview/PointSizeSocketPreviewViewTest.java +++ b/ui/src/test/java/edu/wpi/grip/ui/preview/PointSizeSocketPreviewViewTest.java @@ -1,13 +1,19 @@ package edu.wpi.grip.ui.preview; import com.google.common.eventbus.EventBus; +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.util.Modules; + import edu.wpi.grip.core.sockets.OutputSocket; import edu.wpi.grip.core.sockets.SocketHint; +import edu.wpi.grip.ui.GRIPUIModule; import edu.wpi.grip.ui.util.MockGripPlatform; -import javafx.scene.Scene; -import javafx.stage.Stage; +import edu.wpi.grip.util.GRIPCoreTestModule; + import org.bytedeco.javacpp.opencv_core.Point; import org.bytedeco.javacpp.opencv_core.Size; +import org.junit.After; import org.junit.Test; import org.junit.experimental.runners.Enclosed; import org.junit.runner.RunWith; @@ -15,19 +21,27 @@ import org.testfx.matcher.base.NodeMatchers; import org.testfx.util.WaitForAsyncUtils; +import javafx.scene.Scene; +import javafx.stage.Stage; + import static org.testfx.api.FxAssert.verifyThat; @RunWith(Enclosed.class) public class PointSizeSocketPreviewViewTest { public static class PointSocketPreviewViewTest extends ApplicationTest { + private GRIPCoreTestModule testModule; private static final String identifier = "testPoint"; private static final int value1 = 1, value2 = 2; + @Override public void start(Stage stage) { + testModule = new GRIPCoreTestModule(); + testModule.setUp(); + final Injector injector = Guice.createInjector(Modules.override(testModule).with(new GRIPUIModule())); final OutputSocket pointOutputSocket = - new OutputSocket<>(new EventBus(), - new SocketHint.Builder<>(Point.class) + injector.getInstance(OutputSocket.Factory.class) + .create(new SocketHint.Builder<>(Point.class) .identifier(identifier) .initialValueSupplier(() -> new Point(value1, value2)) .build()); @@ -46,17 +60,27 @@ public void testForExpectedValues() { verifyThat(Integer.toString(value1), NodeMatchers.isVisible()); verifyThat(Integer.toString(value2), NodeMatchers.isVisible()); } + + + @After + public void tearDown() { + testModule.tearDown(); + } } public static class SizeSocketPreviewViewTest extends ApplicationTest { + private GRIPCoreTestModule testModule; private static final String identifier = "testSize"; private static final int value1 = 1, value2 = 2; @Override public void start(Stage stage) { + testModule = new GRIPCoreTestModule(); + testModule.setUp(); + final Injector injector = Guice.createInjector(Modules.override(testModule).with(new GRIPUIModule())); final OutputSocket sizeOutputSocket = - new OutputSocket<>(new EventBus(), - new SocketHint.Builder<>(Size.class) + injector.getInstance(OutputSocket.Factory.class) + .create(new SocketHint.Builder<>(Size.class) .identifier(identifier) .initialValueSupplier(() -> new Size(value1, value2)) .build()); @@ -76,6 +100,11 @@ public void testForExpectedValues() { verifyThat(Integer.toString(value1), NodeMatchers.isVisible()); verifyThat(Integer.toString(value2), NodeMatchers.isVisible()); } + + @After + public void tearDown() { + testModule.tearDown(); + } }