001package edu.wpi.first.wpilibj.vision;
002
003import java.nio.ByteBuffer;
004import java.util.regex.Matcher;
005import java.util.regex.Pattern;
006
007import com.ni.vision.NIVision;
008import com.ni.vision.VisionException;
009
010import static com.ni.vision.NIVision.Image;
011import static edu.wpi.first.wpilibj.Timer.delay;
012
013public class USBCamera {
014        public static String kDefaultCameraName = "cam0";
015
016        private static String ATTR_VIDEO_MODE = "AcquisitionAttributes::VideoMode";
017        private static String ATTR_WB_MODE = "CameraAttributes::WhiteBalance::Mode";
018        private static String ATTR_WB_VALUE = "CameraAttributes::WhiteBalance::Value";
019        private static String ATTR_EX_MODE = "CameraAttributes::Exposure::Mode";
020        private static String ATTR_EX_VALUE = "CameraAttributes::Exposure::Value";
021        private static String ATTR_BR_MODE = "CameraAttributes::Brightness::Mode";
022        private static String ATTR_BR_VALUE = "CameraAttributes::Brightness::Value";
023
024        public class WhiteBalance {
025                public static final int kFixedIndoor = 3000;
026                public static final int kFixedOutdoor1 = 4000;
027                public static final int kFixedOutdoor2 = 5000;
028                public static final int kFixedFluorescent1 = 5100;
029                public static final int kFixedFlourescent2 = 5200;
030        }
031
032        private Pattern m_reMode = Pattern.compile("(?<width>[0-9]+)\\s*x\\s*(?<height>[0-9]+)\\s+(?<format>.*?)\\s+(?<fps>[0-9.]+)\\s*fps");
033
034        private String m_name = kDefaultCameraName;
035        private int m_id = -1;
036        private boolean m_active = false;
037        private boolean m_useJpeg = true;
038        private int m_width = 320;
039        private int m_height = 240;
040        private int m_fps = 30;
041        private String m_whiteBalance = "auto";
042        private int m_whiteBalanceValue = -1;
043        private String m_exposure = "auto";
044        private int m_exposureValue = -1;
045        private int m_brightness = 50;
046        private boolean m_needSettingsUpdate = true;
047
048        public USBCamera() {
049                openCamera();
050        }
051
052        public USBCamera(String name) {
053                m_name = name;
054                openCamera();
055        }
056
057        public synchronized void openCamera() {
058                if (m_id != -1) return; // Camera is already open
059                for (int i=0; i<3; i++) {
060                        try {
061                                m_id = NIVision.IMAQdxOpenCamera(m_name,
062                                        NIVision.IMAQdxCameraControlMode.CameraControlModeController);
063                        } catch (VisionException e) {
064                                if (i == 2)
065                                        throw e;
066                                delay(2.0);
067                                continue;
068                        }
069                        break;
070                }
071        }
072
073        public synchronized void closeCamera() {
074                if (m_id == -1)
075                        return;
076                NIVision.IMAQdxCloseCamera(m_id);
077                m_id = -1;
078        }
079
080        public synchronized void startCapture() {
081                if (m_id == -1 || m_active)
082                        return;
083                NIVision.IMAQdxConfigureGrab(m_id);
084                NIVision.IMAQdxStartAcquisition(m_id);
085                m_active = true;
086        }
087
088        public synchronized void stopCapture() {
089                if (m_id == -1 || !m_active)
090                        return;
091                NIVision.IMAQdxStopAcquisition(m_id);
092                NIVision.IMAQdxUnconfigureAcquisition(m_id);
093                m_active = false;
094        }
095
096        public synchronized void updateSettings() {
097                boolean wasActive = m_active;
098                // Stop acquistion, close and reopen camera
099                if (wasActive)
100                        stopCapture();
101                if (m_id != -1)
102                        closeCamera();
103                openCamera();
104
105                // Video Mode
106                NIVision.dxEnumerateVideoModesResult enumerated = NIVision.IMAQdxEnumerateVideoModes(m_id);
107                NIVision.IMAQdxEnumItem foundMode = null;
108                int foundFps = 1000;
109                for (NIVision.IMAQdxEnumItem mode : enumerated.videoModeArray) {
110                        Matcher m = m_reMode.matcher(mode.Name);
111                        if (!m.matches())
112                                continue;
113                        if (Integer.parseInt(m.group("width")) != m_width)
114                                continue;
115                        if (Integer.parseInt(m.group("height")) != m_height)
116                                continue;
117                        double fps = Double.parseDouble(m.group("fps"));
118                        if (fps < m_fps)
119                                continue;
120                        if (fps > foundFps)
121                                continue;
122                        String format = m.group("format");
123                        boolean isJpeg = format.equals("jpeg") || format.equals("JPEG");
124                        if ((m_useJpeg && !isJpeg) || (!m_useJpeg && isJpeg))
125                                continue;
126                        foundMode = mode;
127                        foundFps = (int)fps;
128                }
129                if (foundMode != null) {
130                        System.out.println("found mode " + foundMode.Value + ": " + foundMode.Name);
131                        if (foundMode.Value != enumerated.currentMode)
132                                NIVision.IMAQdxSetAttributeU32(m_id, ATTR_VIDEO_MODE, foundMode.Value);
133                }
134
135                // White Balance
136                if (m_whiteBalance == "auto")
137                        NIVision.IMAQdxSetAttributeString(m_id, ATTR_WB_MODE, "Auto");
138                else {
139                        NIVision.IMAQdxSetAttributeString(m_id, ATTR_WB_MODE, "Manual");
140                        if (m_whiteBalanceValue != -1)
141                                NIVision.IMAQdxSetAttributeI64(m_id, ATTR_WB_VALUE, m_whiteBalanceValue);
142                }
143
144                // Exposure
145                if (m_exposure == "auto")
146                        NIVision.IMAQdxSetAttributeString(m_id, ATTR_EX_MODE, "AutoAperaturePriority");
147                else {
148                        NIVision.IMAQdxSetAttributeString(m_id, ATTR_EX_MODE, "Manual");
149                        if (m_exposureValue != -1) {
150                                long minv = NIVision.IMAQdxGetAttributeMinimumI64(m_id, ATTR_EX_VALUE);
151                                long maxv = NIVision.IMAQdxGetAttributeMaximumI64(m_id, ATTR_EX_VALUE);
152                                long val = minv + (long)(((double)(maxv - minv)) * (((double)m_exposureValue) / 100.0));
153                                NIVision.IMAQdxSetAttributeI64(m_id, ATTR_EX_VALUE, val);
154                        }
155                }
156
157                // Brightness
158                NIVision.IMAQdxSetAttributeString(m_id, ATTR_BR_MODE, "Manual");
159                long minv = NIVision.IMAQdxGetAttributeMinimumI64(m_id, ATTR_BR_VALUE);
160                long maxv = NIVision.IMAQdxGetAttributeMaximumI64(m_id, ATTR_BR_VALUE);
161                long val = minv + (long)(((double)(maxv - minv)) * (((double)m_brightness) / 100.0));
162                NIVision.IMAQdxSetAttributeI64(m_id, ATTR_BR_VALUE, val);
163
164                // Restart acquisition
165                if (wasActive)
166                        startCapture();
167        }
168
169        public synchronized void setFPS(int fps) {
170                if (fps != m_fps) {
171                        m_needSettingsUpdate = true;
172                        m_fps = fps;
173                }
174        }
175
176        public synchronized void setSize(int width, int height) {
177                if (width != m_width || height != m_height) {
178                        m_needSettingsUpdate = true;
179                        m_width = width;
180                        m_height = height;
181                }
182        }
183
184        /** Set the brightness, as a percentage (0-100). */
185        public synchronized void setBrightness(int brightness) {
186                if (brightness > 100)
187                        m_brightness = 100;
188                else if (brightness < 0)
189                        m_brightness = 0;
190                else
191                        m_brightness = brightness;
192                m_needSettingsUpdate = true;
193        }
194
195        /** Get the brightness, as a percentage (0-100). */
196        public synchronized int getBrightness() {
197                return m_brightness;
198        }
199
200        /** Set the white balance to auto. */
201        public synchronized void setWhiteBalanceAuto() {
202                m_whiteBalance = "auto";
203                m_whiteBalanceValue = -1;
204                m_needSettingsUpdate = true;
205        }
206
207        /** Set the white balance to hold current. */
208        public synchronized void setWhiteBalanceHoldCurrent() {
209                m_whiteBalance = "manual";
210                m_whiteBalanceValue = -1;
211                m_needSettingsUpdate = true;
212        }
213
214        /** Set the white balance to manual, with specified color temperature. */
215        public synchronized void setWhiteBalanceManual(int value) {
216                m_whiteBalance = "manual";
217                m_whiteBalanceValue = value;
218                m_needSettingsUpdate = true;
219        }
220
221        /** Set the exposure to auto aperature. */
222        public synchronized void setExposureAuto() {
223                m_exposure = "auto";
224                m_exposureValue = -1;
225                m_needSettingsUpdate = true;
226        }
227
228        /** Set the exposure to hold current. */
229        public synchronized void setExposureHoldCurrent() {
230                m_exposure = "manual";
231                m_exposureValue = -1;
232                m_needSettingsUpdate = true;
233        }
234
235        /** Set the exposure to manual, as a percentage (0-100). */
236        public synchronized void setExposureManual(int value) {
237                m_exposure = "manual";
238                if (value > 100)
239                        m_exposureValue = 100;
240                else if (value < 0)
241                        m_exposureValue = 0;
242                else
243                        m_exposureValue = value;
244                m_needSettingsUpdate = true;
245        }
246
247        public synchronized void getImage(Image image) {
248                if (m_needSettingsUpdate || m_useJpeg) {
249                        m_needSettingsUpdate = false;
250                        m_useJpeg = false;
251                        updateSettings();
252                }
253
254                NIVision.IMAQdxGrab(m_id, image, 1);
255        }
256
257        public synchronized void getImageData(ByteBuffer data) {
258                if (m_needSettingsUpdate || !m_useJpeg) {
259                        m_needSettingsUpdate = false;
260                        m_useJpeg = true;
261                        updateSettings();
262                }
263
264                NIVision.IMAQdxGetImageData(m_id, data, NIVision.IMAQdxBufferNumberMode.BufferNumberModeLast, 0);
265                data.limit(data.capacity() - 1);
266                data.limit(getJpegSize(data));
267        }
268
269        private static int getJpegSize(ByteBuffer data) {
270                if (data.get(0) != (byte) 0xff || data.get(1) != (byte) 0xd8)
271                        throw new VisionException("invalid image");
272                int pos = 2;
273                while (true) {
274                        try {
275                                byte b = data.get(pos);
276                                if (b != (byte) 0xff)
277                                        throw new VisionException("invalid image at pos " + pos + " (" + data.get(pos) + ")");
278                                b = data.get(pos+1);
279                                if (b == (byte) 0x01 || (b >= (byte) 0xd0 && b <= (byte) 0xd7)) // various
280                                        pos += 2;
281                                else if (b == (byte) 0xd9) // EOI
282                                        return pos + 2;
283                                else if (b == (byte) 0xd8) // SOI
284                                        throw new VisionException("invalid image");
285                                else if (b == (byte) 0xda) { // SOS
286                                        int len = ((data.get(pos+2) & 0xff) << 8) | (data.get(pos+3) & 0xff);
287                                        pos += len + 2;
288                                        // Find next marker.  Skip over escaped and RST markers.
289                                        while (data.get(pos) != (byte) 0xff || data.get(pos+1) == (byte) 0x00 || (data.get(pos+1) >= (byte) 0xd0 && data.get(pos+1) <= (byte) 0xd7))
290                                                pos += 1;
291                                } else { // various
292                                        int len = ((data.get(pos+2) & 0xff) << 8) | (data.get(pos+3) & 0xff);
293                                        pos += len + 2;
294                                }
295                        } catch (IndexOutOfBoundsException ex) {
296                                throw new VisionException("invalid image: could not find jpeg end " + ex.getMessage());
297                        }
298                }
299        }
300}