├── .gitignore ├── LICENSE.md ├── README.md ├── docs ├── MODULE_LAYOUT.md ├── README.md ├── USB_CMD_INTERFACE.md ├── USB_SPECTRUM_FUNCTION_DETAILS.md └── download_docs.sh ├── examples ├── CIE1931and1964.txt ├── colour.py ├── liveview.py └── tectest.py ├── misc └── 10-oceanoptics.rules ├── oceanoptics ├── __init__.py ├── base.py ├── defines.py ├── spectrometers │ ├── APEX.py │ ├── MAYA.py │ ├── MAYA2000pro.py │ ├── QE65xxx.py │ ├── STS.py │ ├── TORUS.py │ ├── XXX2000.py │ ├── XXX2000plus.py │ ├── XXX4000.py │ └── __init__.py └── utils.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.pdf 3 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (C) 2012-2014 Andreas Poehlmann 2 | 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 | of the Software, and to permit persons to whom the Software is furnished to do 9 | so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **This software is not associated with Ocean Optics. Use it at your own risk.** 2 | 3 | # DISCONTINUED 4 | 5 | **I do not work on this module anymore. Please switch to its successor [python-seabreeze](https://github.com/ap--/python-seabreeze).** 6 | 7 | # INFO 8 | 9 | The complete python-oceanoptics **PyUSB** interface is now merged into the **pyseabreeze** backend from [python-seabreeze](https://github.com/ap--/python-seabreeze). As soon as all spectrometers are tested with python-seabreeze, the python-oceanoptics module will be obsolete. So if you own any of the untested spectrometers, please test it with python-seabreeze and let me know if it works. Thanks. 10 | 11 | 12 | ## Ocean Optics python module ## 13 | 14 | This python module provides access to some basic functionality of some [Ocean 15 | Optics](http://www.oceanoptics.com/) spectrometers. The lack of availability of 16 | a python interface for their spectrometers lead to the development of this 17 | module. The specifications of the USB communication layer used in their 18 | spectrometers are freely available in the OEM manuals on their website. 19 | 20 | This software is a community effort to get platform independent python support 21 | for these spectrometers. If you are not 100% sure what you are doing, stick with 22 | the - also platform independent - SpectraSuite and OceanView software. 23 | 24 | **Currently working with:** 25 | 26 | * USB2000+ 27 | * USB4000 28 | * QE65000 29 | * STS (Note: The rewritten class supports protocol version _0x1100_) 30 | * USB2000 31 | * USB650 32 | 33 | **Might work, untested:** 34 | 35 | * HR4000 36 | * QE65pro 37 | * HR2000+ 38 | * HR2000 39 | * apex 40 | * maya 41 | * maya2000pro 42 | * Torus 43 | 44 | **Not yet supported:** 45 | 46 | * Jaz 47 | * NIR 48 | * NIRQUEST 49 | 50 | Contributions are welcome. 51 | 52 | 53 | ## Installing ## 54 | 55 | ### Requirements ### 56 | 57 | I'm using python 2.7.x, so this is not tested on Python 3.x. 58 | 59 | The core libraries require: 60 | - pyusb-1.0.0b1 (this is available on pypi. Newer versions break parts of the API...) 61 | - numpy 62 | 63 | For the example you'll need: 64 | - gtk3 python bindings 65 | - matplotlib 66 | 67 | ### Linux ### 68 | 69 | To install the python module download this repository and run: 70 | 71 | ``` 72 | python setup.py install 73 | ``` 74 | 75 | You should also install the udev rules: 76 | 77 | ``` 78 | sudo cp misc/10-oceanoptics.rules /etc/udev/rules.d/ 79 | sudo udevadm control --reload-rules 80 | ``` 81 | 82 | make sure that your user is in the _plugdev_ group. 83 | 84 | ### Windows and OSX ### 85 | 86 | If anyone wants to volunteer to write instructions how to use this library on Windows and Mac, feel free to contact me. 87 | 88 | 89 | ## Quickstart ## 90 | 91 | After installing test if it's working by: 92 | 93 | ``` 94 | import oceanoptics 95 | spec = oceanoptics.get_a_random_spectrometer() 96 | print spec.spectrum() 97 | ``` 98 | 99 | or run the example program _liveview.py_ 100 | 101 | 102 | ## Contributing ## 103 | 104 | If you own any of the spectrometers listed below, feel free to contribute. 105 | 106 | 107 | ## License ## 108 | 109 | Python files in this repository are released under the [MIT license](LICENSE.md). 110 | -------------------------------------------------------------------------------- /docs/MODULE_LAYOUT.md: -------------------------------------------------------------------------------- 1 | ### planned module layout 2 | 3 | To turn this thing into a universal python module for oceanoptics spectrometers, I suggest the following approach: 4 | 5 | There is one base class for USB-communication shared by all classes: 6 | OceanOpticsUSBComm 7 | It's init function needs two be called with a specific model-string, that is used two set up the USB-communication. 8 | It also provides three functions called \_usb\_send, \_usb\_read, \_usb\_query . 9 | 10 | The OceanOpticsBase class is shared by most spectrometers. It inherits from USBComm and implements the following lowlevel functionality. The lowlevel function names start with a underscore, and they basically implement the stuff thats in the dev-manual provided by OceanOptics. 11 | 12 | | cmd | function | 13 | |:----|:---------------------| 14 | | 0x01| initialize USB 15 | | 0x02| set integration time 16 | | 0x05| query information 17 | | 0x06| write information 18 | | 0xFE| query status 19 | | 0x09| request spectra 20 | 21 | 22 | If a class inherits from USBComm and implements lowlevel functions, all highlevel functions in that class should only use the available lowlevel functions and their names should match \[a\-z\]\[a\-z\_\]\*\[a\-z\]. 23 | 24 | The common high-level interface for all spectrometers will be defined in OceanOpticsSpectrometer. Inherit from this, and implement the functionality. 25 | 26 | That way we can assure a common base highlevel interface for all spectrometers, and if some extra functionality is shared among spectrometers it can be easily tested for, by checking if they inherit from the specific class. 27 | 28 | The classes could be grouped like that. All will be implemented in \_base.py. Each spectrometer then inherits from the base classes that it supports. 29 | 30 | So this is free for discussion. Because some of the classes below are only supported by a single spectrometer model. Maybe those should be put in the Spectrometer class... Also the high-level interface might still change... 31 | 32 | 33 | **class** OceanOpticsTrigger 34 | * 0x03 set strobe enable status 35 | * 0x0A set trigger mode 36 | 37 | **class** OceanOpticsPlugin 38 | * 0x0C query plugin id 39 | * 0x0B query number od plugin 40 | * 0x0D detect plugins 41 | 42 | **class** OceanOpticsRegister 43 | * 0x6A write register info 44 | * 0x6B read register indo 45 | 46 | **class** OceanOpticsIrradiance 47 | * 0x6D read irradiance calib 48 | * 0x6E write irradiance calib 49 | 50 | **class** OceanOpticsI2C 51 | * 0x60 i2c read 52 | * 0x61 i2c write 53 | 54 | **class** OceanOpticsSPI 55 | * 0x62 spi io 56 | 57 | **class** OceanOpticsTemperature 58 | * 0x6C red pcb temperature 59 | 60 | **class** OceanOpticsPSOC 61 | * 0x68 psoc read 62 | * 0x69 psoc write 63 | 64 | **class** OceanOpticsShutdown 65 | * 0x04 set shutdown mode 66 | 67 | **class** OceanOpticsSerial 68 | * 0x07 wirte serial number 69 | * 0x08 get serial number 70 | 71 | **class** OceanOpticsTEC 72 | * 0x70 set fan state 73 | * 0x71 set tec controller state 74 | * 0x72 tec controller read 75 | * 0x73 tec controller write 76 | 77 | **class** ??? 78 | * 0x20 read temperature 79 | * 0x21 set led mode 80 | * 0x23 query calib constant 81 | * 0x24 send calib constant 82 | * 0x25 set analog output 83 | * 0x26 load all calib < eeprom 84 | * 0x27 write all calib > eeprom 85 | 86 | **class** OceanOpticsPotentiometer 87 | * 0x40 set digital poti 88 | * 0x41 set powerup potu val 89 | * 0x42 read poti val 90 | 91 | 92 | Feel free to implement any of those. 93 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | 2 | These files collect some information regarding supported commandsets on 3 | different spectrometers. They may be outdated. Double check with the manuals if 4 | you implement some functionality for your spectrometer. 5 | 6 | The **download_docs.sh** downloads the required OEM manuals. Please check online if these are the newest versions. 7 | -------------------------------------------------------------------------------- /docs/USB_CMD_INTERFACE.md: -------------------------------------------------------------------------------- 1 | ## USB Command interface 2 | 3 | Most of the OceanOptics spectrometer share a relatively similar usb-interface. The following tables show the supported commandset for each model. 4 | 5 | * The STS spectrometer is not listed here, because it has a special commandset. 6 | 7 | #### mapping for the cmdset table 8 | 9 | | id| Spectrometer | 10 | |:--|--------------| 11 | | A | USB2000 12 | | B | USB-LS450 13 | | C | USB-ISS-UVVIS 14 | | D | USB2000+ 15 | | E | USB4000 16 | | F | HR2000 17 | | G | HR2000+ 18 | | H | HR4000 19 | | I | Jaz 20 | | J | apex 21 | | K | maya 22 | | L | maya2000pro 23 | | M | QE65pro 24 | | N | QE65000 25 | | O | NIR 26 | | P | NIRQUEST 27 | | Q | Torus 28 | 29 | #### supported cmdsets 30 | 31 | 32 | | cmd | function | A | B | C | D | E | F | G | H | I | J | K | L | M | N | O | P | Q | 33 | |:----|:-------------------------|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---| 34 | | 0x01| initialize USB | x | x | x | x | x | x | x | x | x | x | x | x | x | x | x | x | x | 35 | | 0x02| set integration time | x | x | x | x | x | x | x | x | x | x | x | x | x | x | x | x | x | 36 | | 0x03| set strobe enable status | x | x | x | x | x | x | x | x | x | | x | x | x | x | x | x | x | 37 | | 0x04| set shutdown mode | | | | x | | | x | x | x | | | | | | | | x | 38 | | 0x05| query information | x | x | x | x | x | x | x | x | x | x | x | x | x | x | x | x | x | 39 | | 0x06| write information | x | x | x | x | x | x | x | x | x | x | x | x | x | x | x | x | x | 40 | | 0x07| wirte serial number | x | x | x | | | x | | | | | | | | | x | x | | 41 | | 0x08| get serial number | x | x | x | | | x | | | | | | | | | x | x | | 42 | | 0x09| request spectra | x | x | x | x | x | x | x | x | x | x | x | x | x | x | x | x | x | 43 | | 0x0A| set trigger mode | x | x | x | x | x | x | x | x | | | x | x | x | x | x | x | x | 44 | | 0x0B| query number od plugin | x | x | x | x | x | x | x | x | | | x | x | x | x |x\*| | x | 45 | | 0x0C| query plugin id | x | x | x | x | x | x | x | x | | | x | x | x | x |x\*|x\*| x | 46 | | 0x0D| detect plugins | x | x | x | x | x | x | x | x | | | x | x | x | x |x\*| | x | 47 | | | | | | | | | | | | | | | | | | | | | 48 | | 0x1E| stop spectral acquisition| | | | | | | | | | | | | | | x | | | 49 | | | | | | | | | | | | | | | | | | | | | 50 | | 0x20| read temperature | | x | | | | | | | | | | | | | | | | 51 | | 0x21| set led mode | | x | | | | | | | | | | | | | | | | 52 | | 0x23| query calib constant | | x | | | | | | | | | | | | | | | | 53 | | 0x24| send calib constant | | x | | | | | | | | | | | | | | | | 54 | | 0x25| set analog output | | x | | | | | | | | | | | | | | | | 55 | | 0x26| load all calib < eeprom | | x | | | | | | | | | | | | | | | | 56 | | 0x27| write all calib > eeprom | | x | | | | | | | | | | | | | | | | 57 | | | | | | | | | | | | | | | | | | | | | 58 | | 0x3E| tec controller write | | | | | | | | | | | | | | | x | | | 59 | | 0x3F| tec controller read | | | | | | | | | | | | | | | x | | | 60 | | | | | | | | | | | | | | | | | | | | | 61 | | 0x40| set digital poti | | | x | | | | | | | | | | | | | | | 62 | | 0x41| set powerup potu val | | | x | | | | | | | | | | | | | | | 63 | | 0x42| read poti val | | | x | | | | | | | | | | | | | | | 64 | | | | | | | | | | | | | | | | | | | | | 65 | | 0x60| i2c read | | | | x | x | | x | x | | | x | x | x | x | | | x | 66 | | 0x61| i2c write | | | | x | x | | x | x | | | x | x | x | x | | | x | 67 | | 0x62| spi io | | | | x | x | | x | x | | | | | x | x | | | x | 68 | | 0x68| psoc read | | | | | x | | x | x | | | | | x | x | | | x | 69 | | 0x69| psoc write | | | | | x | | x | x | | | | | x | x | | | x | 70 | | 0x6A| write register info | | | | x | x | | x | x | | x | x | x | x | x | | | x | 71 | | 0x6B| read register indo | | | | x | x | | x | x | | x | x | x | x | x | | | x | 72 | | 0x6C| red pcb temperature | | | | x | x | | x | x | | | | | x | x | | x | x | 73 | | 0x6D| read irradiance calib | | | | x | x | | x | x | | x | x | x | x | x | | | x | 74 | | 0x6E| write irradiance calib | | | | x | x | | x | x | | x | x | x | x | x | | | x | 75 | | | | | | | | | | | | | | | | | | | | | 76 | | 0x70| set fan state | | | | | | | | | | | | | x | x | | x | | 77 | | 0x71| set tec controller state | | | | | | | | | | | | | x | x | | x | | 78 | | 0x72| tec controller read | | | | | | | | | | | | | x | x | | x | | 79 | | 0x73| tec controller write | | | | | | | | | | | | | x | x | | x | | 80 | | | | | | | | | | | | | | | | | | | | | 81 | | 0xC0| get # of spectrometers | | | | | | | | | x | | | | | | | | | 82 | | 0xC1| set current channel | | | | | | | | | x | | | | | | | | | 83 | | 0xC6| get jaz info | | | | | | | | | x | | | | | | | | | 84 | | 0xC7| set jaz info | | | | | | | | | x | | | | | | | | | 85 | | | | | | | | | | | | | | | | | | | | | 86 | | 0xFE| query status | x | x | x | x | x | | x | x | | x | x | x | x | x | x | x | x | 87 | 88 | 89 | \*) Theese commands mean something different for the specific spectrometer 90 | 91 | 92 | 93 | ### near term planned support 94 | 95 | To keep everything simple we'll exclude the following models at the beginning: 96 | 97 | * HR2000, Jaz because they don't support the query status cmd 98 | * NIR, NIRQUEST because they differ in some commands 99 | 100 | 101 | | cmd | function | A | B | C | D | E | G | H | J | K | L | M | N | Q | 102 | |:----|:-------------------------|---|---|---|---|---|---|---|---|---|---|---|---|---| 103 | | 0x01| initialize USB | x | x | x | x | x | x | x | x | x | x | x | x | x | 104 | | 0x02| set integration time | x | x | x | x | x | x | x | x | x | x | x | x | x | 105 | | 0x05| query information | x | x | x | x | x | x | x | x | x | x | x | x | x | 106 | | 0x06| write information | x | x | x | x | x | x | x | x | x | x | x | x | x | 107 | | 0xFE| query status | x | x | x | x | x | x | x | x | x | x | x | x | x | 108 | | 0x09| request spectra | x | x | x | x | x | x | x | x | x | x | x | x | x | 109 | | | | | | | | | | | | | | | | | 110 | | 0x03| set strobe enable status | x | x | x | x | x | x | x | | x | x | x | x | x | 111 | | 0x0A| set trigger mode | x | x | x | x | x | x | x | | x | x | x | x | x | 112 | | 0x0C| query plugin id | x | x | x | x | x | x | x | | x | x | x | x | x | 113 | | 0x0B| query number od plugin | x | x | x | x | x | x | x | | x | x | x | x | x | 114 | | 0x0D| detect plugins | x | x | x | x | x | x | x | | x | x | x | x | x | 115 | | | | | | | | | | | | | | | | | 116 | | 0x6A| write register info | | | | x | x | x | x | x | x | x | x | x | x | 117 | | 0x6B| read register indo | | | | x | x | x | x | x | x | x | x | x | x | 118 | | 0x6D| read irradiance calib | | | | x | x | x | x | x | x | x | x | x | x | 119 | | 0x6E| write irradiance calib | | | | x | x | x | x | x | x | x | x | x | x | 120 | | 0x60| i2c read | | | | x | x | x | x | | x | x | x | x | x | 121 | | 0x61| i2c write | | | | x | x | x | x | | x | x | x | x | x | 122 | | 0x62| spi io | | | | x | x | x | x | | | | x | x | x | 123 | | 0x6C| red pcb temperature | | | | x | x | x | x | | | | x | x | x | 124 | | 0x68| psoc read | | | | | x | x | x | | | | x | x | x | 125 | | 0x69| psoc write | | | | | x | x | x | | | | x | x | x | 126 | | 0x04| set shutdown mode | | | | x | | x | x | | | | | | x | 127 | | | | | | | | | | | | | | | | | 128 | | 0x07| wirte serial number | x | x | x | | | | | | | | | | | 129 | | 0x08| get serial number | x | x | x | | | | | | | | | | | 130 | | | | | | | | | | | | | | | | | 131 | | 0x70| set fan state | | | | | | | | | | | x | x | | 132 | | 0x71| set tec controller state | | | | | | | | | | | x | x | | 133 | | 0x72| tec controller read | | | | | | | | | | | x | x | | 134 | | 0x73| tec controller write | | | | | | | | | | | x | x | | 135 | | | | | | | | | | | | | | | | | 136 | | 0x20| read temperature | | x | | | | | | | | | | | | 137 | | 0x21| set led mode | | x | | | | | | | | | | | | 138 | | 0x23| query calib constant | | x | | | | | | | | | | | | 139 | | 0x24| send calib constant | | x | | | | | | | | | | | | 140 | | 0x25| set analog output | | x | | | | | | | | | | | | 141 | | 0x26| load all calib < eeprom | | x | | | | | | | | | | | | 142 | | 0x27| write all calib > eeprom | | x | | | | | | | | | | | | 143 | | | | | | | | | | | | | | | | | 144 | | 0x40| set digital poti | | | x | | | | | | | | | | | 145 | | 0x41| set powerup potu val | | | x | | | | | | | | | | | 146 | | 0x42| read poti val | | | x | | | | | | | | | | | 147 | -------------------------------------------------------------------------------- /docs/USB_SPECTRUM_FUNCTION_DETAILS.md: -------------------------------------------------------------------------------- 1 | 2 | ##### implement... 3 | 4 | | Spectrometer | HS 480 | FS 12 | fmt | pixels | extra | 5 | |:-------------|:-------------|:-------------|------|--------|-----------| 6 | | QE65Pro | 5 x 512 +1B | 40 x 64 +1B | _(24./116.)**3: 236 | return r**0.3 237 | else: 238 | return 841./108.*r + 16./116. 239 | 240 | def set_white_point(self, widget, data=None): 241 | self.empty_buffer() 242 | self.get_white = True 243 | 244 | def set_dark_point(self, widget, data=None): 245 | self.empty_buffer() 246 | self.get_dark = True 247 | self.sp_dark = np.zeros(self.wl.shape) 248 | print "Get dark" 249 | 250 | def update_plot(self): 251 | if self.get_dark: return 252 | 253 | # Redraw the spectrum graph 254 | sp = self.average_spectra[self.wl_300nm:self.wl_830nm] 255 | scale = 1./max(1e-5,np.max(sp[self.wl_300nm:self.wl_830nm])) 256 | self.line.set_ydata(scale*sp) 257 | self.canvas.draw() 258 | 259 | def update_colour(self): 260 | if self.get_dark: return 261 | 262 | # Calculate the colour in XYZ space 263 | dw = self.deltawl[self.wl_300nm:self.wl_830nm] 264 | sp = self.average_spectra[self.wl_300nm:self.wl_830nm] 265 | X = self.scaleFactor*np.sum(self.interp_cie1964_x(self.wl[self.wl_300nm:self.wl_830nm])*sp*dw) 266 | Y = self.scaleFactor*np.sum(self.interp_cie1964_y(self.wl[self.wl_300nm:self.wl_830nm])*sp*dw) 267 | Z = self.scaleFactor*np.sum(self.interp_cie1964_z(self.wl[self.wl_300nm:self.wl_830nm])*sp*dw) 268 | 269 | # Relative tri-stimulus values 270 | rX = X/self.Xn 271 | rY = Y/self.Yn 272 | rZ = Z/self.Zn 273 | 274 | # Mapping for CIE1976 L,a*,b* coordinates 275 | fX = self.cie1976(rX) 276 | fY = self.cie1976(rY) 277 | fZ = self.cie1976(rZ) 278 | L = 116.*fY - 16. 279 | a = 500.*(fX-fY) 280 | b = 200.*(fY-fZ) 281 | 282 | self.Lab = [L,a,b] 283 | 284 | def update_temperature(self): 285 | # Ask the spectrometer how hot it is. 286 | try: 287 | self.temperature = self.spectrometer._read_pcb_temperature() 288 | except _OOError: 289 | pass 290 | 291 | def empty_buffer(self): 292 | self.n_sample = 0 293 | self.sp = np.zeros( (len(self.wl), self.oversample) ) 294 | self.buffer_mask = [False]*self.oversample 295 | 296 | def get_spectra(self): 297 | if not self.lock_get_spectra.acquire(False): 298 | return 299 | 300 | self.waiting_for_spectra = True 301 | 302 | #Try to read the next sample 303 | try: 304 | sample = np.array(self.spectrometer.intensities(raw=self.raw)) 305 | sample -= self.sp_dark 306 | 307 | except (USBError, oceanoptics.defines.OceanOpticsError), e: 308 | print e 309 | print "Reconnect the OceanOptics spectrometer to the USB port..." 310 | self.statusbar.push(self.status_context, "Reconnect the OceanOptics spectrometer to the USB port...") 311 | if self.spectrometer!=None: 312 | del self.spectrometer 313 | self.connected = False 314 | self.waiting_for_spectra = False 315 | self.lock_connect.release() 316 | self.lock_get_spectra.release() 317 | return 318 | 319 | 320 | if self.connected: 321 | self.n_sample += 1 322 | if self.n_sample >= self.oversample: 323 | self.n_sample=0 324 | 325 | self.sp[:,self.n_sample] = sample 326 | self.buffer_mask[self.n_sample] = True 327 | 328 | good_sp = np.array([self.sp[:,i] for i in xrange(len(self.buffer_mask)) if self.buffer_mask[i]]).T 329 | if len(good_sp.shape)>1: 330 | self.max_spectra = np.max(good_sp, axis=1) 331 | self.average_spectra = np.median(good_sp, axis=1) 332 | else: 333 | self.max_spectra = sample 334 | self.average_spectra = sample 335 | 336 | self.max_spectra[self.max_spectra<1.e-10] = 1.e-10 337 | self.average_spectra[self.average_spectra<1.e-10] = 1.e-10 338 | 339 | self.waiting_for_spectra = False 340 | self.spectra_ready = True 341 | 342 | self.lock_get_spectra.release() 343 | 344 | def tick(self): 345 | if self.spectra_ready: 346 | self.spectra_ready = False 347 | # Spectra has been received so now process it. 348 | if self.get_dark: 349 | if np.all(self.buffer_mask): 350 | self.get_dark = False 351 | self.sp_dark = self.max_spectra 352 | np.savetxt("dark_point.txt", zip(self.wl,self.sp_dark)) 353 | else: 354 | self.text.set_text('Setting dark-point: %d/%d\n\n' % ((self.n_sample+1), self.oversample)) 355 | self.statusbar.push(self.status_context,'Setting dark-point: %d/%d\n\n' % ((self.n_sample+1), self.oversample)) 356 | 357 | elif self.get_white: 358 | if self.n_sample==0: 359 | self.get_white = False 360 | dw = self.deltawl[self.wl_300nm:self.wl_830nm] 361 | sp = self.average_spectra[self.wl_300nm:self.wl_830nm] 362 | self.scaleFactor = 100./np.sum(self.interp_cie1964_y(self.wl[self.wl_300nm:self.wl_830nm])*sp*dw) 363 | self.Xn = self.scaleFactor*np.sum(self.interp_cie1964_x(self.wl[self.wl_300nm:self.wl_830nm])*sp*dw) 364 | self.Yn = self.scaleFactor*np.sum(self.interp_cie1964_y(self.wl[self.wl_300nm:self.wl_830nm])*sp*dw) 365 | self.Zn = self.scaleFactor*np.sum(self.interp_cie1964_z(self.wl[self.wl_300nm:self.wl_830nm])*sp*dw) 366 | np.savetxt("white_point.txt", [self.scaleFactor, self.Xn, self.Yn, self.Zn]) 367 | else: 368 | self.text.set_text('Setting white-point: %d/%d\n\n' % ((self.n_sample+1), self.oversample)) 369 | 370 | else: 371 | self.update_colour() 372 | self.text.set_text('L*=%.2f\na*=%.2f\nb*=%.2f\n' % (self.Lab[0],self.Lab[1],self.Lab[2])) 373 | oversample_time = self.sample_interval_sec * self.oversample 374 | self.statusbar.push(self.status_context, "Temperature=%.1fC The delay due to averaging is %ds" % (self.temperature,oversample_time)) 375 | 376 | 377 | self.update_plot() 378 | self.update_temperature() 379 | return True 380 | 381 | def tick_spectrometer(self): 382 | if not self.connected: 383 | # Reconnect to the spectrometer 384 | t1 = threading.Thread(target=self.connect_spectrometer()) 385 | t1.start() 386 | 387 | elif not self.waiting_for_spectra: 388 | # Request the next spectra from the spectrometer 389 | t1 = threading.Thread(target=self.get_spectra) 390 | t1.start() 391 | 392 | return True 393 | 394 | def run(self): 395 | GObject.threads_init() 396 | GObject.idle_add(self.tick_spectrometer) 397 | GObject.idle_add(self.tick) 398 | Gtk.main() 399 | 400 | if __name__ == '__main__': 401 | m = DynamicPlotter(sample_interval_sec=1.0) 402 | m.run() 403 | 404 | -------------------------------------------------------------------------------- /examples/liveview.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ File: example_liveview.py 3 | Author: Andreas Poehlmann 4 | Last change: 2013/02/27 5 | 6 | Liveview example 7 | """ 8 | 9 | import oceanoptics 10 | import time 11 | import numpy as np 12 | 13 | from gi.repository import Gtk, GLib 14 | 15 | class mpl: 16 | from matplotlib.figure import Figure 17 | from matplotlib.backends.backend_gtk3agg import FigureCanvasGTK3Agg as FigureCanvas 18 | 19 | 20 | class DynamicPlotter(Gtk.Window): 21 | 22 | def __init__(self, sample_interval_sec=0.1, smoothing=1, oversampling=1, raw=False, size=(600,350), outfile=None, 23 | lower=None, upper=None): 24 | # Gtk stuff 25 | Gtk.Window.__init__(self, title='Ocean Optics Spectrometer') 26 | self.connect("destroy", lambda x : Gtk.main_quit()) 27 | self.set_default_size(*size) 28 | # Data stuff 29 | self.sample_interval_ms = int(sample_interval_sec*1000) 30 | self.smoothing = int(smoothing) 31 | self._sample_n = 0 32 | self.raw = bool(raw) 33 | self.spectrometer = oceanoptics.get_a_random_spectrometer() 34 | self.spectrometer.integration_time(time_sec=(sample_interval_sec * 0.8)) 35 | self.wl = self.spectrometer.wavelengths() 36 | self.sp = self.spectrometer.intensities() 37 | self.sp = np.zeros((len(self.sp), int(oversampling))) 38 | # MPL stuff 39 | self.figure = mpl.Figure() 40 | self.ax = self.figure.add_subplot(1, 1, 1) 41 | self.ax.set_xlim(left=lower, right=upper, auto=True) 42 | self.ax.grid(True) 43 | self.canvas = mpl.FigureCanvas(self.figure) 44 | self.line, = self.ax.plot(self.wl, self.sp[:,0]) 45 | # Logging 46 | self.outfile = outfile 47 | if self.outfile is not None: 48 | self.outfile.write('# Spectrum File\n' 49 | '# initial table of wavelengths\n' 50 | '# column_number, wavelength\n') 51 | self.outfile.write("\n".join('# %d, %f' % x for x in enumerate(self.wl))) 52 | self.outfile.write("\n#--------------------------\n# time, ") 53 | self.outfile.write(", ".join("%d" % i for i in range(len(self.wl)))) 54 | self.outfile.write("\n") 55 | # Gtk stuff 56 | self.add(self.canvas) 57 | self.canvas.show() 58 | self.show_all() 59 | 60 | def update_plot(self): 61 | # -> redraw on new spectrum 62 | # -> average over self.sample_n spectra 63 | # -> smooth if self.smoothing 64 | 65 | # remark: 66 | # > smoothing can be done after averaging 67 | 68 | # get spectrum 69 | sp = np.array(self.spectrometer.intensities(raw=self.raw)) 70 | 71 | self.sp[:,self._sample_n] = sp 72 | self._sample_n += 1 73 | self._sample_n %= self.sp.shape[1] 74 | if self._sample_n != 0: # do not draw or average 75 | return 76 | # average! 77 | sp = np.mean(self.sp, axis=1) 78 | 79 | if self.smoothing > 1: 80 | n = self.smoothing 81 | kernel = np.ones((n,)) / n 82 | sp = np.convolve(sp, kernel)[(n-1):] 83 | 84 | self.line.set_ydata(sp) 85 | if self.outfile is not None: 86 | self.outfile.write("%f, " % time.time()) 87 | self.outfile.write(", ".join("%f" % s for s in sp) + "\n") 88 | self.ax.relim() 89 | self.ax.autoscale_view(False, False, True) 90 | self.canvas.draw() 91 | return True 92 | 93 | 94 | def run(self): 95 | GLib.timeout_add(self.sample_interval_ms, self.update_plot) 96 | Gtk.main() 97 | 98 | 99 | if __name__ == '__main__': 100 | 101 | import argparse 102 | 103 | parser = argparse.ArgumentParser() 104 | parser.add_argument('-r', '--raw', action='store_true', help='Show raw detector values') 105 | parser.add_argument('-i', '--interval', type=float, default=0.1, metavar='SECONDS', 106 | help='Update interval') 107 | parser.add_argument('-s', '--smooth', type=int, default=1, metavar='N', 108 | help='Number of spectrum points to average over') 109 | parser.add_argument('-O', '--oversample', type=int, default=1, metavar='N', 110 | help='Average together successive spectra') 111 | parser.add_argument('-l', '--lower', type=float, default=None, metavar='WAVELENGTH', 112 | help='Lower bound of plot') 113 | parser.add_argument('-u', '--upper', type=float, default=None, metavar='WAVELENGTH', 114 | help='Upper bound of plot') 115 | parser.add_argument('--out', type=argparse.FileType('w'), default=None, 116 | help='save to text-file, please implement your own way for saving... this is really just an example (and completely inefficient!!!)') 117 | 118 | args = parser.parse_args() 119 | 120 | m = DynamicPlotter(sample_interval_sec=args.interval, raw=args.raw, smoothing=args.smooth, 121 | oversampling=args.oversample, outfile=args.out, 122 | lower=args.lower, upper=args.upper) 123 | m.run() 124 | 125 | -------------------------------------------------------------------------------- /examples/tectest.py: -------------------------------------------------------------------------------- 1 | import oceanoptics 2 | import time 3 | 4 | spec = oceanoptics.QE65000() 5 | 6 | spec.integration_time(0.1) 7 | sp = spec.spectrum() 8 | 9 | spec.set_TEC_temperature(0) 10 | time.sleep(30) 11 | 12 | setpoints = [-1,-3,-5,-8,-10,-12,-15,-18] 13 | temps = [] 14 | 15 | for s in setpoints: 16 | spec.set_TEC_temperature(s) 17 | print(s) 18 | time.sleep(30) 19 | temps.append(spec.get_TEC_temperature()) 20 | 21 | print(setpoints) 22 | print(temps) 23 | 24 | # >Bh 25 | # [-1, -3, -5, -8, -10, -12, -15, -18] 26 | #[-20, -22, -22, -22, -21, -20, -22, -22] 27 | 28 | # = 0, "the received message was shorter than 64 bytes: %d" % payload_length 423 | payload_fmt = "%ds" % payload_length 424 | FMT = self._const.HEADER_FMT + payload_fmt + self._const.FOOTER_FMT 425 | 426 | data = struct.unpack(FMT, msg) 427 | 428 | msgtype = data[4] 429 | 430 | immediate_length = data[8] 431 | immediate_data = data[9] 432 | payload = data[11] 433 | 434 | if (immediate_length > 0) and len(payload) > 0: 435 | raise _OOError("the device returned immediate data and payload data? cmd: %d" % msgtype) 436 | elif immediate_length > 0: 437 | return immediate_data[:immediate_length] 438 | elif payload_length > 0: 439 | return payload 440 | else: 441 | return "" 442 | -------------------------------------------------------------------------------- /oceanoptics/spectrometers/TORUS.py: -------------------------------------------------------------------------------- 1 | # XXX: untested 2 | #---------------------------------------------------------- 3 | from oceanoptics.base import OceanOpticsBase as _OOBase 4 | #---------------------------------------------------------- 5 | 6 | 7 | class TORUS(_OOBase): 8 | 9 | def __init__(self): 10 | super(TORUS, self).__init__('Torus') 11 | 12 | 13 | -------------------------------------------------------------------------------- /oceanoptics/spectrometers/XXX2000.py: -------------------------------------------------------------------------------- 1 | # XXX: USB2000 tested, HR2000 untested 2 | #---------------------------------------------------------- 3 | from oceanoptics.base import OceanOpticsBase as _OOBase 4 | from oceanoptics.defines import OceanOpticsError as _OOError 5 | import time 6 | import struct 7 | #---------------------------------------------------------- 8 | 9 | 10 | class _XXX2000(_OOBase): 11 | 12 | def _request_spectrum(self): 13 | self._usb_send(struct.pack('HHBBBBBBBBBBBB', ret[:])) 60 | ret = { 'pixels' : data[0], 61 | 'integration_time' : data[1] * 1000, # ms to us 62 | 'lamp_enable' : data[2], 63 | 'trigger_mode' : data[3], 64 | 'acquisition_status' : data[4], 65 | 'timer_swap' : data[5], 66 | 'spectral_data_density' : data[6], 67 | 'packets_in_endpoint' : 64, 68 | 'usb_speed' : 0x80 # This is a workaround to leave 69 | # OOBase unchanged. This way 70 | # self._EPspec gets set to 0x82 71 | # TODO: change abstraction layer 72 | } 73 | return ret 74 | 75 | 76 | 77 | class HR2000(_XXX2000): 78 | 79 | def __init__(self): 80 | super(HR2000, self).__init__('HR2000') 81 | 82 | def _set_integration_time(self, time_us): 83 | """ send command 0x02 """ 84 | # XXX: The HR2000 requires the time set in Milliseconds! 85 | # This overides the provided function of OOBase 86 | # XXX: We also need to save the integration time internally 87 | # because it does not support the _qeury_status command 88 | time_ms = int(time_us/1000) 89 | self._integration_time = time_ms / 1000. 90 | self._usb_send(struct.pack('HHBBBBBBBBBBBB', ret[:])) 143 | ret = { 'pixels' : data[0], 144 | 'integration_time' : data[1] * 1000, # ms to us 145 | 'lamp_enable' : data[2], 146 | 'trigger_mode' : data[3], 147 | 'acquisition_status' : data[4], 148 | 'timer_swap' : data[5], 149 | 'spectral_data_density' : data[6], 150 | 'packets_in_endpoint' : 64, 151 | 'usb_speed' : 0x80 # This is a workaround to leave 152 | # OOBase unchanged. This way 153 | # self._EPspec gets set to 0x82 154 | # TODO: change abstraction layer 155 | } 156 | return ret 157 | -------------------------------------------------------------------------------- /oceanoptics/spectrometers/XXX2000plus.py: -------------------------------------------------------------------------------- 1 | # tested 2 | #---------------------------------------------------------- 3 | from oceanoptics.base import OceanOpticsBase as _OOBase 4 | #---------------------------------------------------------- 5 | 6 | 7 | class USB2000plus(_OOBase): 8 | 9 | def __init__(self): 10 | super(USB2000plus, self).__init__('USB2000+') 11 | 12 | 13 | class HR2000plus(_OOBase): 14 | 15 | def __init__(self): 16 | super(HR2000plus, self).__init__('HR2000+') 17 | 18 | 19 | -------------------------------------------------------------------------------- /oceanoptics/spectrometers/XXX4000.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ File: USB4000.py 3 | Author: Ian Ross Williams 4 | Last change: 2014/08/08 5 | 6 | Python Interface for USB4000 and HR4000 OceanOptics Spectometers. 7 | Current device classes: 8 | * USB4000 9 | * HR4000 10 | """ 11 | 12 | #---------------------------------------------------------- 13 | import struct 14 | import time 15 | 16 | from oceanoptics.base import OceanOpticsBase as _OOBase 17 | from oceanoptics.defines import OceanOpticsError as _OOError 18 | from oceanoptics.defines import OceanOpticsModelConfig as _OOModelConfig 19 | #---------------------------------------------------------- 20 | 21 | class _XXX4000(_OOBase): 22 | 23 | 24 | def _request_spectrum(self): 25 | self._usb_send(struct.pack(' found:' 49 | else: 50 | raise _OOError('no supported spectrometers found') 51 | for d in devices: 52 | print '> - %s' % ProductId[d.idProduct] 53 | 54 | mod = ProductId[devices[0].idProduct] 55 | print '>' 56 | print '> returning first %s as OceanOpticsSpectrometer' % mod 57 | 58 | spec_class = _models[mod] 59 | return spec_class() 60 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from distutils.core import setup 3 | 4 | setup( 5 | name='oceanoptics', 6 | version='0.3.2', 7 | author='Andreas Poehlmann, Jose A. Jimenez-Berni, Ben Gamari, Simon Dickreuter, Ian Ross Williams', 8 | author_email='mail@andreaspoehlmann.de', 9 | packages=['oceanoptics', 'oceanoptics.spectrometers'], 10 | description='Community-coded Python module for oceanoptics spectrometers. This software is not associated with Ocean Optics. Use it at your own risk.', 11 | long_description=open('README.md').read(), 12 | requires=['python (>= 2.7)', 'pyusb (>= 1.0)', 'numpy'], 13 | ) 14 | --------------------------------------------------------------------------------