Skip to content

Commit 6bb940f

Browse files
committed
Merge pull request #194 from ThomasJClark/master
Add initial headless mode support
2 parents 55d8acc + 9e51025 commit 6bb940f

File tree

6 files changed

+267
-7
lines changed

6 files changed

+267
-7
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ crashlytics-build.properties
7373

7474
# Package Files #
7575
*.jar
76+
!*/libs/*.jar
7677
*.war
7778
*.ear
7879

build.gradle

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,15 +70,22 @@ project(":core") {
7070
shadow
7171
}
7272

73+
repositories {
74+
flatDir {
75+
dirs 'libs'
76+
}
77+
}
78+
7379
dependencies {
7480
compile group: 'org.bytedeco', name: 'javacv', version: '1.1'
7581
compile group: 'org.bytedeco.javacpp-presets', name: 'opencv', version: '3.0.0-1.1'
82+
compile group: 'org.bytedeco.javacpp-presets', name: 'opencv', version: '3.0.0-1.1', classifier: os
83+
compile group: 'org.bytedeco.javacpp-presets', name: 'opencv-3.0.0-1.1', classifier: 'linux-frc'
84+
compile group: 'org.bytedeco.javacpp-presets', name: 'videoinput', version: '0.200-1.1', classifier: os
7685
compile group: 'org.python', name: 'jython', version: '2.7.0'
7786
compile group: 'com.thoughtworks.xstream', name: 'xstream', version: '1.4.8'
7887
compile group: 'org.apache.commons', name: 'commons-lang3', version: '3.4'
7988
compile group: 'com.google.guava', name: 'guava', version: '18.0'
80-
testCompile group: 'org.bytedeco.javacpp-presets', name: 'opencv', version: '3.0.0-1.1', classifier: os
81-
testCompile group: 'org.bytedeco.javacpp-presets', name: 'videoinput', version: '0.200-1.1', classifier: os
8289
}
8390

8491
mainClassName = 'edu.wpi.grip.core.Main'
@@ -146,8 +153,6 @@ project(":ui") {
146153
compile project(path: ':core', configuration: 'shadow')
147154
ideProvider project(path: ':core', configuration: 'compile')
148155
compile group: 'org.controlsfx', name: 'controlsfx', version: '8.40.10'
149-
compile group: 'org.bytedeco.javacpp-presets', name: 'opencv', version: '3.0.0-1.1', classifier: os
150-
compile group: 'org.bytedeco.javacpp-presets', name: 'videoinput', version: '0.200-1.1', classifier: os
151156
testCompile files(project(':core').sourceSets.test.output.classesDir)
152157
testCompile group: 'org.testfx', name: 'testfx-core', version: '4.0.+'
153158
testCompile group: 'org.testfx', name: 'testfx-junit', version: '4.0.+'
@@ -157,7 +162,7 @@ project(":ui") {
157162
compileTestJava.dependsOn tasks.getByPath(':core:testClasses')
158163

159164
idea.module {
160-
scopes.PROVIDED.plus += [ configurations.ideProvider ]
165+
scopes.PROVIDED.plus += [configurations.ideProvider]
161166
}
162167

163168
javafx {
6.63 MB
Binary file not shown.
Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,38 @@
11
package edu.wpi.grip.core;
22

3+
import com.google.common.eventbus.EventBus;
4+
import edu.wpi.grip.core.operations.Operations;
5+
import edu.wpi.grip.core.serialization.Project;
6+
import edu.wpi.grip.generated.CVOperations;
7+
8+
import java.io.File;
9+
10+
/**
11+
* Main driver class for headless mode
12+
*/
313
public class Main {
4-
public static void main(String[] args) {
14+
public static void main(String[] args) throws Exception {
15+
if (args.length != 1) {
16+
System.err.println("Usage: GRIP.jar project.grip");
17+
return;
18+
}
19+
20+
final String projectPath = args[0];
21+
22+
final EventBus eventBus = new EventBus((exception, context) -> exception.printStackTrace());
23+
final Pipeline pipeline = new Pipeline(eventBus);
24+
final Palette palette = new Palette(eventBus);
25+
final Project project = new Project(eventBus, pipeline, palette);
26+
27+
Operations.addOperations(eventBus);
28+
CVOperations.addOperations(eventBus);
29+
30+
// Open a project from a .grip file specified on the command line
31+
project.open(new File(projectPath));
32+
33+
// There's nothing more to do in the main thread since we're in headless mode - sleep forever
34+
for (; ; ) {
35+
Thread.sleep(Integer.MAX_VALUE);
36+
}
537
}
638
}

core/src/main/java/edu/wpi/grip/core/sources/CameraSource.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ public Properties getProperties() {
103103

104104
@Override
105105
public void createFromProperties(EventBus eventBus, Properties properties) throws IOException {
106+
this.properties = properties;
107+
106108
final String deviceNumberProperty = properties.getProperty(DEVICE_NUMBER_PROPERTY);
107109
final String addressProperty = properties.getProperty(ADDRESS_PROPERTY);
108110

@@ -167,7 +169,7 @@ private synchronized void startVideo(FrameGrabber grabber) throws IOException {
167169
frameRateOutputSocket.setValue(1000 / (thisMoment - lastFrame));
168170
lastFrame = thisMoment;
169171
}
170-
});
172+
}, "Camera");
171173
frameExecutor.setUncaughtExceptionHandler(
172174
(thread, exception) -> {
173175
// TODO Pass Exception to the UI.
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
/*
2+
* Copyright (C) 2013 Greg Perry
3+
* Modified for use in GRIP to reduce heap allocations
4+
*
5+
* Licensed either under the Apache License, Version 2.0, or (at your option)
6+
* under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation (subject to the "Classpath" exception),
8+
* either version 2, or any later version (collectively, the "License");
9+
* you may not use this file except in compliance with the License.
10+
* You may obtain a copy of the License at
11+
*
12+
* http://www.apache.org/licenses/LICENSE-2.0
13+
* http://www.gnu.org/licenses/
14+
* http://www.gnu.org/software/classpath/license.html
15+
*
16+
* or as provided in the LICENSE.txt file that accompanied this code.
17+
* Unless required by applicable law or agreed to in writing, software
18+
* distributed under the License is distributed on an "AS IS" BASIS,
19+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20+
* See the License for the specific language governing permissions and
21+
* limitations under the License.
22+
*/
23+
24+
package edu.wpi.grip.core.sources;
25+
26+
import org.bytedeco.javacpp.BytePointer;
27+
import org.bytedeco.javacpp.Loader;
28+
import org.bytedeco.javacv.Frame;
29+
import org.bytedeco.javacv.FrameConverter;
30+
import org.bytedeco.javacv.FrameGrabber;
31+
import org.bytedeco.javacv.OpenCVFrameConverter;
32+
33+
import javax.imageio.ImageIO;
34+
import java.awt.image.BufferedImage;
35+
import java.io.ByteArrayInputStream;
36+
import java.io.IOException;
37+
import java.io.InputStream;
38+
import java.net.MalformedURLException;
39+
import java.net.URL;
40+
import java.net.URLConnection;
41+
import java.util.List;
42+
import java.util.Map;
43+
44+
import static org.bytedeco.javacpp.opencv_core.*;
45+
import static org.bytedeco.javacpp.opencv_imgcodecs.cvDecodeImage;
46+
47+
public class IPCameraFrameGrabber extends FrameGrabber {
48+
49+
/*
50+
* excellent reference - http://www.jpegcameras.com/ foscam url
51+
* http://host/videostream.cgi?user=username&pwd=password
52+
* http://192.168.0.59:60/videostream.cgi?user=admin&pwd=password android ip
53+
* cam http://192.168.0.57:8080/videofeed
54+
*/
55+
56+
private static Exception loadingException = null;
57+
58+
public static void tryLoad() throws Exception {
59+
if (loadingException != null) {
60+
throw loadingException;
61+
} else {
62+
try {
63+
Loader.load(org.bytedeco.javacpp.opencv_highgui.class);
64+
} catch (Throwable t) {
65+
throw loadingException = new Exception("Failed to load " + IPCameraFrameGrabber.class, t);
66+
}
67+
}
68+
}
69+
70+
private URL url;
71+
72+
private URLConnection connection;
73+
private InputStream input;
74+
private byte[] pixelBuffer = new byte[1024];
75+
private Map<String, List<String>> headerfields;
76+
private String boundryKey;
77+
private IplImage decoded = null;
78+
private FrameConverter<IplImage> converter = new OpenCVFrameConverter.ToIplImage();
79+
80+
public IPCameraFrameGrabber(String urlstr) throws MalformedURLException {
81+
url = new URL(urlstr);
82+
}
83+
84+
@Override
85+
public void start() {
86+
87+
try {
88+
connection = url.openConnection();
89+
headerfields = connection.getHeaderFields();
90+
if (headerfields.containsKey("Content-Type")) {
91+
List<String> ct = headerfields.get("Content-Type");
92+
for (int i = 0; i < ct.size(); ++i) {
93+
String key = ct.get(i);
94+
int j = key.indexOf("boundary=");
95+
if (j != -1) {
96+
boundryKey = key.substring(j + 9); // FIXME << fragile
97+
}
98+
}
99+
}
100+
input = connection.getInputStream();
101+
} catch (IOException e) {
102+
e.printStackTrace();
103+
}
104+
}
105+
106+
@Override
107+
public void stop() throws Exception {
108+
try {
109+
input.close();
110+
input = null;
111+
connection = null;
112+
url = null;
113+
if (decoded != null) {
114+
cvReleaseImage(decoded);
115+
}
116+
} catch (IOException e) {
117+
throw new Exception(e.getMessage(), e);
118+
}
119+
}
120+
121+
@Override
122+
public void trigger() throws Exception {
123+
}
124+
125+
@Override
126+
public Frame grab() throws Exception {
127+
try {
128+
byte[] b = readImage();
129+
CvMat mat = cvMat(1, b.length, CV_8UC1, new BytePointer(b));
130+
if (decoded != null) {
131+
cvReleaseImage(decoded);
132+
}
133+
return converter.convert(decoded = cvDecodeImage(mat));
134+
} catch (IOException e) {
135+
throw new Exception(e.getMessage(), e);
136+
}
137+
}
138+
139+
public BufferedImage grabBufferedImage() throws IOException {
140+
BufferedImage bi = ImageIO.read(new ByteArrayInputStream(readImage()));
141+
return bi;
142+
}
143+
144+
byte[] readImage() throws IOException {
145+
StringBuffer sb = new StringBuffer();
146+
int c;
147+
// read http subheader
148+
while ((c = input.read()) != -1) {
149+
if (c > 0) {
150+
sb.append((char) c);
151+
if (c == 13) {
152+
sb.append((char) input.read());// '10'+
153+
c = input.read();
154+
sb.append((char) c);
155+
if (c == 13) {
156+
sb.append((char) input.read());// '10'
157+
break; // done with subheader
158+
}
159+
160+
}
161+
}
162+
}
163+
// find embedded jpeg in stream
164+
String subheader = sb.toString();
165+
//log.debug(subheader);
166+
int contentLength = -1;
167+
// if (boundryKey == null)
168+
// {
169+
// Yay! - server was nice and sent content length
170+
int c0 = subheader.indexOf("Content-Length: ");
171+
int c1 = subheader.indexOf('\r', c0);
172+
173+
if (c0 < 0) {
174+
//log.info("no content length returning null");
175+
return null;
176+
}
177+
178+
c0 += 16;
179+
contentLength = Integer.parseInt(subheader.substring(c0, c1).trim());
180+
//log.debug("Content-Length: " + contentLength);
181+
182+
// adaptive size - careful - don't want a 2G jpeg
183+
ensureBufferCapacity(contentLength);
184+
185+
while (input.available() < contentLength) ;
186+
input.read(pixelBuffer, 0, contentLength);
187+
input.read();// \r
188+
input.read();// \n
189+
input.read();// \r
190+
input.read();// \n
191+
192+
return pixelBuffer;
193+
}
194+
195+
/**
196+
* Grow the pixel buffer if necessary. Using this method instead of allocating a new buffer every time a frame
197+
* is grabbed improves performance by reducing the frequency of garbage collections. In a simple test, the
198+
* unmodified version of IPCameraFrameGrabber caused about 200MB of allocations within 13 seconds. In this
199+
* version, almost no additional heap space is typically allocated per frame.
200+
* <p>
201+
* The downside to this is that the returned frames can't really be modified, so this probably won't go upstream,
202+
* but it's useful for us because in GRIP we don't operate on images in-place.
203+
*/
204+
private void ensureBufferCapacity(int desiredCapacity) {
205+
int capacity = pixelBuffer.length;
206+
207+
while (capacity < desiredCapacity) {
208+
capacity *= 2;
209+
}
210+
211+
if (capacity > pixelBuffer.length) {
212+
pixelBuffer = new byte[capacity];
213+
}
214+
}
215+
216+
@Override
217+
public void release() throws Exception {
218+
}
219+
220+
}

0 commit comments

Comments
 (0)