Skip to content

Commit 40cdbc1

Browse files
authored
Added crop functionality (#926)
1 parent 508d6ea commit 40cdbc1

File tree

9 files changed

+289
-5
lines changed

9 files changed

+289
-5
lines changed
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
package edu.wpi.grip.core.operations.composite;
2+
3+
import edu.wpi.grip.annotation.operation.Description;
4+
import edu.wpi.grip.annotation.operation.OperationCategory;
5+
import edu.wpi.grip.core.MatWrapper;
6+
import edu.wpi.grip.core.Operation;
7+
import edu.wpi.grip.core.sockets.InputSocket;
8+
import edu.wpi.grip.core.sockets.OutputSocket;
9+
import edu.wpi.grip.core.sockets.SocketHints;
10+
11+
import com.google.common.collect.ImmutableList;
12+
import com.google.inject.Inject;
13+
14+
import java.util.List;
15+
16+
import static org.bytedeco.javacpp.opencv_core.Rect;
17+
18+
19+
/**
20+
* Crop an image to an exact width and height using one of several origin modes. Cropping
21+
* images down can be a useful optimization.
22+
*/
23+
@Description(name = "Crop",
24+
summary = "Crop an image to an exact size",
25+
category = OperationCategory.IMAGE_PROCESSING,
26+
iconName = "crop")
27+
public class CropOperation implements Operation {
28+
29+
private final InputSocket<MatWrapper> inputSocket;
30+
private final InputSocket<Number> xSocket;
31+
private final InputSocket<Number> ySocket;
32+
33+
private final InputSocket<Number> widthSocket;
34+
private final InputSocket<Number> heightSocket;
35+
private final InputSocket<Origin> originSocket;
36+
37+
private final OutputSocket<MatWrapper> outputSocket;
38+
39+
@Inject
40+
@SuppressWarnings("JavadocMethod")
41+
public CropOperation(InputSocket.Factory inputSocketFactory, OutputSocket.Factory
42+
outputSocketFactory) {
43+
this.inputSocket = inputSocketFactory.create(SocketHints
44+
.createImageSocketHint("Input"));
45+
this.xSocket = inputSocketFactory.create(SocketHints.Inputs
46+
.createNumberSpinnerSocketHint("X", 100));
47+
this.ySocket = inputSocketFactory.create(SocketHints.Inputs
48+
.createNumberSpinnerSocketHint("Y", 100));
49+
this.widthSocket = inputSocketFactory.create(SocketHints.Inputs
50+
.createNumberSpinnerSocketHint("Width", 50));
51+
this.heightSocket = inputSocketFactory.create(SocketHints.Inputs
52+
.createNumberSpinnerSocketHint("Height", 50));
53+
this.originSocket = inputSocketFactory
54+
.create(SocketHints.createEnumSocketHint("Origin", Origin.CENTER));
55+
56+
this.outputSocket = outputSocketFactory.create(SocketHints
57+
.createImageSocketHint("Output"));
58+
}
59+
60+
@Override
61+
public List<InputSocket> getInputSockets() {
62+
return ImmutableList.of(
63+
inputSocket,
64+
xSocket,
65+
ySocket,
66+
widthSocket,
67+
heightSocket,
68+
originSocket
69+
);
70+
}
71+
72+
@Override
73+
public List<OutputSocket> getOutputSockets() {
74+
return ImmutableList.of(
75+
outputSocket
76+
);
77+
}
78+
79+
@Override
80+
public void perform() {
81+
final MatWrapper input = inputSocket.getValue().get();
82+
final MatWrapper output = outputSocket.getValue().get();
83+
final Number x = xSocket.getValue().get();
84+
final Number y = ySocket.getValue().get();
85+
final Number width = widthSocket.getValue().get();
86+
final Number height = heightSocket.getValue().get();
87+
88+
final Origin origin = originSocket.getValue().get();
89+
90+
final Rect regionOfInterest = new Rect(
91+
x.intValue() + (int) (origin.xOffsetMultiplier * width.intValue()),
92+
y.intValue() + (int) (origin.yOffsetMultiplier * height.intValue()),
93+
width.intValue(),
94+
height.intValue()
95+
);
96+
97+
//apply() returns a sub-matrix; It does not modify the input Mat: https://github.com/WPIRoboticsProjects/GRIP/pull/926
98+
if (input.isCpu()) {
99+
output.set(input.getCpu().apply(regionOfInterest));
100+
} else {
101+
output.set(input.getGpu().apply(regionOfInterest));
102+
}
103+
104+
outputSocket.setValue(output);
105+
}
106+
107+
private enum Origin {
108+
TOP_LEFT("Top Left", 0, 0),
109+
TOP_RIGHT("Top Right", -1, 0),
110+
BOTTOM_LEFT("Bottom Left", 0, -1),
111+
BOTTOM_RIGHT("Bottom Right", -1, -1),
112+
CENTER("Center", -.5, -.5);
113+
114+
final String label;
115+
final double xOffsetMultiplier;
116+
final double yOffsetMultiplier;
117+
118+
Origin(String label, double xOffsetMultiplier, double yOffsetMultiplier) {
119+
this.label = label;
120+
this.xOffsetMultiplier = xOffsetMultiplier;
121+
this.yOffsetMultiplier = yOffsetMultiplier;
122+
}
123+
124+
@Override
125+
public String toString() {
126+
return label;
127+
}
128+
}
129+
}

ui/src/main/resources/edu/wpi/grip/ui/codegeneration/cpp/macros.vm

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ int#elseif($type.equalsIgnoreCase("MaskSize"))
1414
std::string#elseif($type.equalsIgnoreCase("Interpolation"))
1515
int#elseif($type.equalsIgnoreCase("FlipCode"))
1616
FlipCode#elseif($type.equalsIgnoreCase("List"))
17-
List#else
17+
List#elseif($type.equalsIgnoreCase("Origin"))
18+
Origin#else
1819
cv::$type#end#end
1920

2021
#macro(funPassType $baseType)
@@ -68,7 +69,7 @@ $name(#foreach($inp in $step.getInputs())#param($inp $names[$count])#set($count
6869
int ${tMeth.name($input.name())} = $input.value(); // ENUM $input.type()
6970
#elseif ($input.type().equals("MaskSize") )
7071
std::string ${tMeth.name($input.name())} = "$input.value()";
71-
#elseif ($input.type().equals("FlipCode") || $input.type().equals("BlurType") )
72+
#elseif ($input.type().equals("FlipCode") || $input.type().equals("BlurType") || $input.type().equals("Origin"))
7273
$input.type() ${tMeth.name($input.name())} = $input.type()::#cvVal($input.value());
7374
#elseif ($input.type().contains("Type") || $input.type().equals("Interpolation"))
7475
int ${tMeth.name($input.name())} = #cvVal($input.value());
@@ -100,7 +101,12 @@ cv::INTER_AREA#elseif($value.equals("Box Blur"))
100101
BOX#elseif($value.equals("Gaussian Blur"))
101102
GAUSSIAN#elseif($value.equals("Median Filter"))
102103
MEDIAN#elseif($value.equals("Bilateral Filter"))
103-
BILATERAL#else
104+
BILATERAL#elseif($value.equals("Top Left"))
105+
TOP_LEFT#elseif($value.equals("Top Right"))
106+
TOP_RIGHT#elseif($value.equals("Bottom Left"))
107+
BOTTOM_LEFT#elseif($value.equals("Bottom Right"))
108+
BOTTOM_RIGHT#elseif($value.equals("Center"))
109+
CENTER#else
104110
$value#end#end
105111

106112
#macro(enumType $uniStep)
@@ -113,6 +119,14 @@ $value#end#end
113119
enum BlurType {
114120
BOX, GAUSSIAN, MEDIAN, BILATERAL
115121
};
122+
#elseif($uniStep.name().equalsIgnoreCase("Crop"))
123+
/**
124+
* A representation of the different origins that can be used.
125+
*
126+
*/
127+
enum Origin {
128+
TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT, CENTER
129+
};
116130
#elseif($name.contains("Flip"))
117131
/**
118132
* Code used for CV_flip.
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/**
2+
* Crops an image.
3+
*
4+
* @param input The image on which to perform the crop.
5+
* @param x The x (horiontal) location of the crop.
6+
* @param y The y (vertical) location of the crop.
7+
* @param width The width(horizontal length) of the crop.
8+
* @param height The height(vertical length) of the crop.
9+
* @param origin The Origin of the crop.
10+
* @param output The image in which to store the output.
11+
*/
12+
void $className::#func($step, ["input", "x", "y", "width", "height", "origin", "output"]) {
13+
double xOffsetMultiplier = 0;
14+
double yOffsetMultiplier = 0;
15+
switch(origin) {
16+
case TOP_RIGHT:
17+
xOffsetMultiplier = -1;
18+
break;
19+
case BOTTOM_LEFT:
20+
yOffsetMultiplier = -1;
21+
break;
22+
case BOTTOM_RIGHT:
23+
xOffsetMultiplier = -1;
24+
yOffsetMultiplier = -1;
25+
break;
26+
case CENTER:
27+
xOffsetMultiplier = -.5;
28+
yOffsetMultiplier = -.5;
29+
break;
30+
default: //origin == TOP_LEFT
31+
break;
32+
}
33+
cv::Rect regionOfInterest = cv::Rect(
34+
(int) (x + xOffsetMultiplier * width),
35+
(int) (y + yOffsetMultiplier * height),
36+
(int) width,
37+
(int) height
38+
);
39+
output = input(regionOfInterest);
40+
}

ui/src/main/resources/edu/wpi/grip/ui/codegeneration/java/macros.vm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ Ref<$obj>#end
2424
int ${tMeth.name($input.name())} = Imgproc.$input.value();
2525
#elseif ($input.type().equals("MaskSize"))
2626
int ${tMeth.name($input.name())} = $input.value().substring(0,1);
27-
#elseif ($input.type().equals("BlurType"))
27+
#elseif ($input.type().equals("BlurType") || $input.type().equals("Origin"))
2828
$input.type() ${tMeth.name($input.name())} = ${input.type()}.get("${input.value()}");
2929
#elseif ($input.type().contains("Type") || $input.type().equals("Interpolation"))
3030
int ${tMeth.name($input.name())} = #enum($input.value());
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/**
2+
* An indication of which type of which point on the box is used as the origin.
3+
* Choices are TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT, CENTER
4+
*/
5+
enum Origin {
6+
TOP_LEFT("Top Left", 0, 0),
7+
TOP_RIGHT("Top Right", -1, 0),
8+
BOTTOM_LEFT("Bottom Left", 0, -1),
9+
BOTTOM_RIGHT("Bottom Right", -1, -1),
10+
CENTER("Center", -.5, -.5);
11+
12+
private final String label;
13+
private final double xOffsetMultiplier;
14+
private final double yOffsetMultiplier;
15+
16+
Origin(String label, double xOffsetMultiplier, double yOffsetMultiplier) {
17+
this.label = label;
18+
this.xOffsetMultiplier = xOffsetMultiplier;
19+
this.yOffsetMultiplier = yOffsetMultiplier;
20+
}
21+
22+
public static Origin get(String label){
23+
switch(label){
24+
case "Top Left":
25+
return TOP_LEFT;
26+
case "Top Right":
27+
return TOP_RIGHT;
28+
case "Bottom Left":
29+
return BOTTOM_LEFT;
30+
case "Bottom Right":
31+
return BOTTOM_RIGHT;
32+
default:
33+
return CENTER;
34+
}
35+
}
36+
37+
@Override
38+
public String toString() {
39+
return label;
40+
}
41+
}
42+
43+
/**
44+
* Crops an image.
45+
* @param input The image on which to perform the crop.
46+
* @param x The x (horiontal) location of the crop.
47+
* @param y The y (vertical) location of the crop.
48+
* @param width The width(horizontal length) of the crop.
49+
* @param height The height(vertical length) of the crop.
50+
* @param origin The Origin of the crop.
51+
* @param output The image in which to store the output.
52+
*/
53+
private void $tMeth.name($step.name())(Mat input, double x, double y, double width, double height, Origin origin, Mat output) {
54+
55+
Rect regionOfInterest = new Rect(
56+
(int) (x + origin.xOffsetMultiplier * width),
57+
(int) (y + origin.yOffsetMultiplier * height),
58+
(int) width,
59+
(int) height
60+
);
61+
input.submat(regionOfInterest).copyTo(output);
62+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Origin = Enum('Origin', 'Top_Left Top_Right Bottom_Left Bottom_Right Center')

ui/src/main/resources/edu/wpi/grip/ui/codegeneration/python/macros.vm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ self.$tMeth.name($inp.name())#end
44

55
#macro(newInput $inp)
66
#if($inp.hasValue())
7-
#if($inp.type().equals("BlurType") || $inp.type().equals("FlipCode"))
7+
#if($inp.type().equals("BlurType") || $inp.type().equals("FlipCode") || $inp.type().equals("Origin"))
88
#input($inp) = ${inp.type()}.$inp.value().replaceAll(' ','_')#elseif($inp.type().equals("MaskSize"))
99
#input($inp) = $inp.value().substring(0,1)#elseif($inp.value().contains("source"))
1010
#input($inp) = $inp.value()#elseif ($inp.type().contains("Enum") ||
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#needs("Origin")
2+
@staticmethod
3+
def $tMeth.name($step.name())(src, x, y, width, height, origin):
4+
"""Crops an image.
5+
Args:
6+
src: The source mat (numpy.ndarray).
7+
x: The x (horiontal) location of the crop.
8+
y: The y (vertical) location of the crop.
9+
width: The width(horizontal length) of the crop.
10+
height: The height(vertical length) of the crop.
11+
origin: The Origin of the crop.
12+
Returns:
13+
A numpy.ndarray that has been cropped.
14+
"""
15+
16+
# origin is Top_Left
17+
x_offset_multiplier = 0
18+
y_offset_multiplier = 0
19+
20+
if origin is Origin.Top_Right:
21+
x_offset_multiplier = -1
22+
elif origin is Origin.Bottom_Left:
23+
y_offset_multiplier = -1
24+
elif origin is Origin.Bottom_Right:
25+
x_offset_multiplier = -1
26+
y_offset_multiplier = -1
27+
elif origin is Origin.Center:
28+
x_offset_multiplier = -.5
29+
y_offset_multiplier = -.5
30+
31+
# Flooring to keep consistency with Java
32+
x_start = int(x + x_offset_multiplier * width)
33+
x_end = int(x_start + width)
34+
35+
y_start = int(y + y_offset_multiplier * height)
36+
y_end = int(y_start + height)
37+
38+
return src[y_start: y_end, x_start : x_end]
25.6 KB
Loading

0 commit comments

Comments
 (0)