Skip to content

Commit

Permalink
Added crop functionality (#926)
Browse files Browse the repository at this point in the history
  • Loading branch information
TheTripleV authored Aug 17, 2020
1 parent 508d6ea commit 40cdbc1
Show file tree
Hide file tree
Showing 9 changed files with 289 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package edu.wpi.grip.core.operations.composite;

import edu.wpi.grip.annotation.operation.Description;
import edu.wpi.grip.annotation.operation.OperationCategory;
import edu.wpi.grip.core.MatWrapper;
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.SocketHints;

import com.google.common.collect.ImmutableList;
import com.google.inject.Inject;

import java.util.List;

import static org.bytedeco.javacpp.opencv_core.Rect;


/**
* Crop an image to an exact width and height using one of several origin modes. Cropping
* images down can be a useful optimization.
*/
@Description(name = "Crop",
summary = "Crop an image to an exact size",
category = OperationCategory.IMAGE_PROCESSING,
iconName = "crop")
public class CropOperation implements Operation {

private final InputSocket<MatWrapper> inputSocket;
private final InputSocket<Number> xSocket;
private final InputSocket<Number> ySocket;

private final InputSocket<Number> widthSocket;
private final InputSocket<Number> heightSocket;
private final InputSocket<Origin> originSocket;

private final OutputSocket<MatWrapper> outputSocket;

@Inject
@SuppressWarnings("JavadocMethod")
public CropOperation(InputSocket.Factory inputSocketFactory, OutputSocket.Factory
outputSocketFactory) {
this.inputSocket = inputSocketFactory.create(SocketHints
.createImageSocketHint("Input"));
this.xSocket = inputSocketFactory.create(SocketHints.Inputs
.createNumberSpinnerSocketHint("X", 100));
this.ySocket = inputSocketFactory.create(SocketHints.Inputs
.createNumberSpinnerSocketHint("Y", 100));
this.widthSocket = inputSocketFactory.create(SocketHints.Inputs
.createNumberSpinnerSocketHint("Width", 50));
this.heightSocket = inputSocketFactory.create(SocketHints.Inputs
.createNumberSpinnerSocketHint("Height", 50));
this.originSocket = inputSocketFactory
.create(SocketHints.createEnumSocketHint("Origin", Origin.CENTER));

this.outputSocket = outputSocketFactory.create(SocketHints
.createImageSocketHint("Output"));
}

@Override
public List<InputSocket> getInputSockets() {
return ImmutableList.of(
inputSocket,
xSocket,
ySocket,
widthSocket,
heightSocket,
originSocket
);
}

@Override
public List<OutputSocket> getOutputSockets() {
return ImmutableList.of(
outputSocket
);
}

@Override
public void perform() {
final MatWrapper input = inputSocket.getValue().get();
final MatWrapper output = outputSocket.getValue().get();
final Number x = xSocket.getValue().get();
final Number y = ySocket.getValue().get();
final Number width = widthSocket.getValue().get();
final Number height = heightSocket.getValue().get();

final Origin origin = originSocket.getValue().get();

final Rect regionOfInterest = new Rect(
x.intValue() + (int) (origin.xOffsetMultiplier * width.intValue()),
y.intValue() + (int) (origin.yOffsetMultiplier * height.intValue()),
width.intValue(),
height.intValue()
);

//apply() returns a sub-matrix; It does not modify the input Mat: https://github.com/WPIRoboticsProjects/GRIP/pull/926
if (input.isCpu()) {
output.set(input.getCpu().apply(regionOfInterest));
} else {
output.set(input.getGpu().apply(regionOfInterest));
}

outputSocket.setValue(output);
}

private enum Origin {
TOP_LEFT("Top Left", 0, 0),
TOP_RIGHT("Top Right", -1, 0),
BOTTOM_LEFT("Bottom Left", 0, -1),
BOTTOM_RIGHT("Bottom Right", -1, -1),
CENTER("Center", -.5, -.5);

final String label;
final double xOffsetMultiplier;
final double yOffsetMultiplier;

Origin(String label, double xOffsetMultiplier, double yOffsetMultiplier) {
this.label = label;
this.xOffsetMultiplier = xOffsetMultiplier;
this.yOffsetMultiplier = yOffsetMultiplier;
}

@Override
public String toString() {
return label;
}
}
}
20 changes: 17 additions & 3 deletions ui/src/main/resources/edu/wpi/grip/ui/codegeneration/cpp/macros.vm
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ int#elseif($type.equalsIgnoreCase("MaskSize"))
std::string#elseif($type.equalsIgnoreCase("Interpolation"))
int#elseif($type.equalsIgnoreCase("FlipCode"))
FlipCode#elseif($type.equalsIgnoreCase("List"))
List#else
List#elseif($type.equalsIgnoreCase("Origin"))
Origin#else
cv::$type#end#end

#macro(funPassType $baseType)
Expand Down Expand Up @@ -68,7 +69,7 @@ $name(#foreach($inp in $step.getInputs())#param($inp $names[$count])#set($count
int ${tMeth.name($input.name())} = $input.value(); // ENUM $input.type()
#elseif ($input.type().equals("MaskSize") )
std::string ${tMeth.name($input.name())} = "$input.value()";
#elseif ($input.type().equals("FlipCode") || $input.type().equals("BlurType") )
#elseif ($input.type().equals("FlipCode") || $input.type().equals("BlurType") || $input.type().equals("Origin"))
$input.type() ${tMeth.name($input.name())} = $input.type()::#cvVal($input.value());
#elseif ($input.type().contains("Type") || $input.type().equals("Interpolation"))
int ${tMeth.name($input.name())} = #cvVal($input.value());
Expand Down Expand Up @@ -100,7 +101,12 @@ cv::INTER_AREA#elseif($value.equals("Box Blur"))
BOX#elseif($value.equals("Gaussian Blur"))
GAUSSIAN#elseif($value.equals("Median Filter"))
MEDIAN#elseif($value.equals("Bilateral Filter"))
BILATERAL#else
BILATERAL#elseif($value.equals("Top Left"))
TOP_LEFT#elseif($value.equals("Top Right"))
TOP_RIGHT#elseif($value.equals("Bottom Left"))
BOTTOM_LEFT#elseif($value.equals("Bottom Right"))
BOTTOM_RIGHT#elseif($value.equals("Center"))
CENTER#else
$value#end#end

#macro(enumType $uniStep)
Expand All @@ -113,6 +119,14 @@ $value#end#end
enum BlurType {
BOX, GAUSSIAN, MEDIAN, BILATERAL
};
#elseif($uniStep.name().equalsIgnoreCase("Crop"))
/**
* A representation of the different origins that can be used.
*
*/
enum Origin {
TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT, CENTER
};
#elseif($name.contains("Flip"))
/**
* Code used for CV_flip.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* Crops an image.
*
* @param input The image on which to perform the crop.
* @param x The x (horiontal) location of the crop.
* @param y The y (vertical) location of the crop.
* @param width The width(horizontal length) of the crop.
* @param height The height(vertical length) of the crop.
* @param origin The Origin of the crop.
* @param output The image in which to store the output.
*/
void $className::#func($step, ["input", "x", "y", "width", "height", "origin", "output"]) {
double xOffsetMultiplier = 0;
double yOffsetMultiplier = 0;
switch(origin) {
case TOP_RIGHT:
xOffsetMultiplier = -1;
break;
case BOTTOM_LEFT:
yOffsetMultiplier = -1;
break;
case BOTTOM_RIGHT:
xOffsetMultiplier = -1;
yOffsetMultiplier = -1;
break;
case CENTER:
xOffsetMultiplier = -.5;
yOffsetMultiplier = -.5;
break;
default: //origin == TOP_LEFT
break;
}
cv::Rect regionOfInterest = cv::Rect(
(int) (x + xOffsetMultiplier * width),
(int) (y + yOffsetMultiplier * height),
(int) width,
(int) height
);
output = input(regionOfInterest);
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Ref<$obj>#end
int ${tMeth.name($input.name())} = Imgproc.$input.value();
#elseif ($input.type().equals("MaskSize"))
int ${tMeth.name($input.name())} = $input.value().substring(0,1);
#elseif ($input.type().equals("BlurType"))
#elseif ($input.type().equals("BlurType") || $input.type().equals("Origin"))
$input.type() ${tMeth.name($input.name())} = ${input.type()}.get("${input.value()}");
#elseif ($input.type().contains("Type") || $input.type().equals("Interpolation"))
int ${tMeth.name($input.name())} = #enum($input.value());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/**
* An indication of which type of which point on the box is used as the origin.
* Choices are TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT, CENTER
*/
enum Origin {
TOP_LEFT("Top Left", 0, 0),
TOP_RIGHT("Top Right", -1, 0),
BOTTOM_LEFT("Bottom Left", 0, -1),
BOTTOM_RIGHT("Bottom Right", -1, -1),
CENTER("Center", -.5, -.5);

private final String label;
private final double xOffsetMultiplier;
private final double yOffsetMultiplier;

Origin(String label, double xOffsetMultiplier, double yOffsetMultiplier) {
this.label = label;
this.xOffsetMultiplier = xOffsetMultiplier;
this.yOffsetMultiplier = yOffsetMultiplier;
}

public static Origin get(String label){
switch(label){
case "Top Left":
return TOP_LEFT;
case "Top Right":
return TOP_RIGHT;
case "Bottom Left":
return BOTTOM_LEFT;
case "Bottom Right":
return BOTTOM_RIGHT;
default:
return CENTER;
}
}

@Override
public String toString() {
return label;
}
}

/**
* Crops an image.
* @param input The image on which to perform the crop.
* @param x The x (horiontal) location of the crop.
* @param y The y (vertical) location of the crop.
* @param width The width(horizontal length) of the crop.
* @param height The height(vertical length) of the crop.
* @param origin The Origin of the crop.
* @param output The image in which to store the output.
*/
private void $tMeth.name($step.name())(Mat input, double x, double y, double width, double height, Origin origin, Mat output) {

Rect regionOfInterest = new Rect(
(int) (x + origin.xOffsetMultiplier * width),
(int) (y + origin.yOffsetMultiplier * height),
(int) width,
(int) height
);
input.submat(regionOfInterest).copyTo(output);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Origin = Enum('Origin', 'Top_Left Top_Right Bottom_Left Bottom_Right Center')
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ self.$tMeth.name($inp.name())#end

#macro(newInput $inp)
#if($inp.hasValue())
#if($inp.type().equals("BlurType") || $inp.type().equals("FlipCode"))
#if($inp.type().equals("BlurType") || $inp.type().equals("FlipCode") || $inp.type().equals("Origin"))
#input($inp) = ${inp.type()}.$inp.value().replaceAll(' ','_')#elseif($inp.type().equals("MaskSize"))
#input($inp) = $inp.value().substring(0,1)#elseif($inp.value().contains("source"))
#input($inp) = $inp.value()#elseif ($inp.type().contains("Enum") ||
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#needs("Origin")
@staticmethod
def $tMeth.name($step.name())(src, x, y, width, height, origin):
"""Crops an image.
Args:
src: The source mat (numpy.ndarray).
x: The x (horiontal) location of the crop.
y: The y (vertical) location of the crop.
width: The width(horizontal length) of the crop.
height: The height(vertical length) of the crop.
origin: The Origin of the crop.
Returns:
A numpy.ndarray that has been cropped.
"""

# origin is Top_Left
x_offset_multiplier = 0
y_offset_multiplier = 0

if origin is Origin.Top_Right:
x_offset_multiplier = -1
elif origin is Origin.Bottom_Left:
y_offset_multiplier = -1
elif origin is Origin.Bottom_Right:
x_offset_multiplier = -1
y_offset_multiplier = -1
elif origin is Origin.Center:
x_offset_multiplier = -.5
y_offset_multiplier = -.5

# Flooring to keep consistency with Java
x_start = int(x + x_offset_multiplier * width)
x_end = int(x_start + width)

y_start = int(y + y_offset_multiplier * height)
y_end = int(y_start + height)

return src[y_start: y_end, x_start : x_end]
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 40cdbc1

Please sign in to comment.