The AS7262 is a 6 Channel Spectral Sensor by Pimoroni that can be programmed in Python. This guide shows you how to setup the sensor on a Raspberry Pi and then how to use python to get the results.
A Spectral Sensor is a device that can show the intensity of different wave lengths of colour across the visible and invisible light spectrum. In Astronomy these types of devices can detect what elements are present in stars and planets by looking for low readings in the entire spectral wavelengths. As different elements absorb different wave lengths of light the scientist can look for these low reading and know what elements are present. These devices will be expensive and a bit advanced for hobby electronics so if you want to dabble in spectral scanning Pimoroni offer the AS7262 Spectral Sensor designed for the Raspberry Pi. It is capable of readings 6 wave lengths of light, so no planet detecting with this device. Though you can detect the intensity of roughly the most vibrant shades of Red, Orange, Yellow, Green, Blue and Violet.
The as7262 Spectral Sensor
The as7262 board contains the colour sensor and two 4000k leds to illuminate an object with. The colour sensor contains two photo diodes, the first one can detect the colours Yellow, Green, Blue & Violet only. The second one detects colours Red, Orange, Yellow and Green. Each colour sensor can be accessed separately using mode 0 or mode 1, these are continuous scanning modes. Mode 2 gets continuous readings from both photo diodes and gives results for all 6 channels. Mode 3 also reads all 6 channels but is for single scan on command.
The exposure time can be set with the integration setting. This can be set in a range of 1 to 255 which is then mutiplyed by 0.0028 sec to have a total integration time. That is a range from 0.0028 second to 0.71 second.
Different light sources have different spectral profiles which affect how colours are seen. So it is important to make sure your scans are done in consistent lighting conditions. Even the distance the sensor is from the object will affect the intensity of the results. So it would be a good idea to create a housing for the sensor so you can make sure the environment is the same for each of the scans.
As the sensor board has two 4000k LED's onboard and midday light is above 5500k I wanted to see what the difference was when the LED's and daylight was used to illuminate an A4 sheet of paper. These graphs show the results from three different light conditions. The sensor was located 5cm above the paper with integration set to 20, gain set to 1. For the first test with the LEDs on, the current was set at 25 otherwise the LED was off.
- The first one is using the two LED's on the sensor in a dark room to scan a white A4 sheet of paper.
- The second is the exactly same setup and settings but outside at noon in non-direct sunlight.
- The third is again at noon but indoors next to a window, about 2 meters from test two.
Here you can see the onboard LED's show stronger orange and green wave lengths with low red and violet but the overall intensity was 5.
Day light has a different spectral range than the led with blue and violet being the stronger wave lengths. The intensity is well above the LED's at nearly 300.
The effect of a double glazed window on daylight is to reduce the intensity by two thirds. The spectral scale across the wave lengths is the same.
So from this example you can see that objects illuminated with the built in LED's will give you different results compared to daylight.
Setting up a Pimoroni AS7262 6 Channel Spectral Sensor on the Raspberry Pi:
The AS7262 uses the i2c gpio pins to communicate to the Raspberry Pi. If you haven't already, you will need i2c enabled in the interfaces section of raspi-config or Raspberry Pi Configuration.
In a terminal window enter sudo raspi-config or on the desktop goto the menu and select Preferences and Raspberry Pi Configuration. Then go to the Interfaces section and enable i2c.
The AS7262 module has been designed so it can be connected straight to the left side of the GPIO connector at pin positions 1.3.5.7.& 9.
To check everything is setup use the command: i2cdetect -y 1
This should show 49 like this if it is all working ok. If not recheck your connections.
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- 49 -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
With the Sensor connected, next install the python library with sudo pip3 install as7262
Now the Raspberry Pi is setup and the Spectral Sensor is connected, we can try it out. This python3 script will get readings for all 6 colour channels and display the results as intensity of each wave length. The results are in the order of Red, Orange, Yellow, Green , Blue & Violet.
Example 1: Output the 6 channel values
#!/usr/bin/python3 #This script uses a as7262 6 colour spectral scanner from Pimoroni and displays the resulting values. #raspberryconnect.com from as7262 import AS7262 as7262 = AS7262() as7262.set_gain(1) # 1, 3.7, 16, 64 as7262.set_integration_time(10) #1 to 255 x 2.8ms exposure #mode 0 - bank 1 only continuous, mode 1 - bank 2 only continuous, mode 2 - both banks continuous, mode 3 - both banks single read as7262.set_measurement_mode(2) #2 all colours continuous as7262.set_illumination_led_current(12.5) #12.5mA 25mA 50mA 100mA as7262.set_illumination_led(1) # led on def main(): try: while 1: values = as7262.get_calibrated_values() #get values from scan spec = [float(i) for i in list(values)] #convert results from string to float print(spec) except KeyboardInterrupt: as7262.set_measurement_mode(3) #Switch to scan on demand as7262.set_illumination_led(0) #light off if __name__ == '__main__': main()
This will turn on the led's and continuously scan and display the results like this. Red, Orange, Yellow, Green , Blue & Violet. (ctrl + c to exit)
Also available at as7262-example1-basicscan.txt
[7.560032844543457, 10.0905179977417, 5.567821502685547, 3.222388505935669, 1.1915769577026367, 1.1165746450424194] [7.560032844543457, 10.0905179977417, 6.495791435241699, 3.222388505935669, 1.1915769577026367, 1.1165746450424194] [7.560032844543457, 10.0905179977417, 6.495791435241699, 3.222388505935669, 1.1915769577026367, 1.1165746450424194] [7.560032844543457, 10.0905179977417, 5.567821502685547, 3.222388505935669, 1.1915769577026367, 1.1165746450424194]
Example 2: Display the results on a graph
The next example shows how to add a graph which represents the results of the scan
for this you will need to install the python library matplotlib and numpy
sudo pip3 install matplotlib numpy
#!/usr/bin/python3 #This script uses a as7262 6 colour spectral scanner from Pimoroni and displays a colour bar graph. #raspberryconnect.com from as7262 import AS7262 import matplotlib.pyplot as plt import numpy as np as7262 = AS7262() as7262.set_gain(1) # 1, 3.7, 16, 64 as7262.set_integration_time(10) #1 to 255 x 2.8ms exposure #mode 0 - bank 1 only continuous, mode 1 - bank 2 only continuous, mode 2 - both banks continuous, mode 3 - both banks single read as7262.set_measurement_mode(2) #2 all colours continuous as7262.set_illumination_led_current(12.5) #12.5mA 25mA 50mA 100mA as7262.set_illumination_led(1) # led on def bargraph(self): label = ("Red","Ora","Yel","Gre","Blu","Vio") y_pos = np.arange(len(label)) barcol = ('red','orange','yellow','green','blue','violet') #Graph bar colours plt.bar(y_pos,self,color=(barcol)) plt.xticks(y_pos,label) plt.show() try: while True: values = as7262.get_calibrated_values() #get values from scan spec = [float(i) for i in list(values)] #convert results from string to float print(spec) bargraph(spec) except KeyboardInterrupt: as7262.set_measurement_mode(3) #switch to single scan mode as7262.set_illumination_led(0) 'led off
Also available at as7262-example2-scangraphs.txt
Example 3: Convert Spectral Scan values to CIE XYZ colour space
If you want to represent the results of the scan as a colour then the spectral data needs to be converted to a RGB colour space. This will only give you a representation of the scan result as a computer screen cannot show the colour shades available in the spectrum, First you need to convert the scan to the CIE XYZ colour space as this has the closes match to the range of human colour vision.
The conversion used the values; observer='10', illuminant='D65'
Observer 10 refers to the colour standard. The alternate option is 2 but 10 is the recommended standard. Illuminant D65 refers to the standard illumination of the scene in Noon Daylight. As the as7262 scanner does not sense illumination D65 is used for colour conversions so will also cause an error in the colour representation.
#!/usr/bin/python3 #This script uses a as7262 6 colour spectral scanner from Pimoroni and displays the #Spectral results and XYZ colour RGB colour co-coordinates. #raspberryconnect.com from as7262 import AS7262 from colormath.color_objects import SpectralColor,XYZColor from colormath.color_conversions import convert_color as7262 = AS7262() as7262.set_gain(1) # 1, 3.7, 16, 64 as7262.set_integration_time(10) #1 to 255 x 2.8ms exposure #mode 0 - bank 1 only continuous, mode 1 - bank 2 only continuous, mode 2 - both banks continuous, mode 3 - both banks single read as7262.set_measurement_mode(2) #2 all colours continuous as7262.set_illumination_led_current(12.5) #12.5mA 25mA 50mA 100mA as7262.set_illumination_led(1) # led on def spectral_to_xyz(self): """Convert Scan to RGB Colour""" spc = SpectralColor( observer='10', illuminant='D65', spec_650nm=str(self[0]), spec_600nm=str(self[1]), spec_570nm=str(self[2]), spec_550nm=str(self[3]), spec_500nm=str(self[4]), spec_450nm=str(self[5])) xyz = convert_color(spc, XYZColor) #convert spectral signals to XYZ colour space return xyz def cleanup(): as7262.set_measurement_mode(3) #deactivate scanner to scan on command as7262.set_illumination_led(0) #switch off led def main(): try: a = input("Press a key when ready, enter q to quit") while a != "q": values = as7262.get_calibrated_values() #get values from scan spec = [float(i) for i in list(values)] #convert results from string to float xyzcol = spectral_to_xyz(spec) #convert spectral scan to xyz colour space values print("Spectral Scan ROYGBV:",spec) print("XYZ Color",xyzcol) a = input() #wait for user cleanup() except KeyboardInterrupt: cleanup() if __name__ == '__main__': main()
Also available at as7262-example3-scan2xyz.txt
The results will look like this
Spectral Scan ROYGBV: [5.880025863647461, 7.0633625984191895, 6.495791435241699, 6.444777011871338, 3.57473087310791,1.1165746450424194] XYZ Color XYZColor (xyz_x:1.6150 xyz_y:1.7803 xyz_z:0.2601)
Example 4: Convert Spectral Scan values to sRGB
Now if you do want to represent the colour on screen you will need to convert it to sRGB before the Red, Green, Blue values can be used.
The problem is RGB is in a range of 0-1 or 0-255. As you can see from the XYZ colour values above, they are higher than 1. This means the sRGB colours need to be scaled down which will add more error in to the results. This example shows you how to convert from the xyz colours to sRGB. There is an option to scale values over 255 to 255.
print("RGB:",convert_to_rgb(xyzcol,1)) if you don't want sRGB to be scaled then change 1 to 0 . print("RGB:",convert_to_rgb(xyzcol,0))
#!/usr/bin/python3 #This script uses a as7262 6 colour spectral scanner from Pimoroni and displays the #Spectral results and XYZ colour RGB colour co-coordinates and sRGB values. #raspberryconnect.com from as7262 import AS7262 from colormath.color_objects import SpectralColor,XYZColor,sRGBColor from colormath.color_conversions import convert_color as7262 = AS7262() as7262.set_gain(1) # 1, 3.7, 16, 64 as7262.set_integration_time(10) #1 to 255 x 2.8ms exposure #mode 0 - bank 1 only continuous, mode 1 - bank 2 only continuous, mode 2 - both banks continuous, mode 3 - both banks single read as7262.set_measurement_mode(2) #2 all colours continuous as7262.set_illumination_led_current(12.5) #12.5mA 25mA 50mA 100mA as7262.set_illumination_led(1) # led on def spectral_to_xyz(self): """Convert Scan to RGB Colour""" spc = SpectralColor( observer='10', illuminant='D65', spec_650nm=str(self[0]), spec_600nm=str(self[1]), spec_570nm=str(self[2]), spec_550nm=str(self[3]), spec_500nm=str(self[4]), spec_450nm=str(self[5])) xyz = convert_color(spc, XYZColor) #convert spectral signals to XYZ colour space return xyz def convert_to_rgb(self,clip): """Convert colour object to RGB for Screen Colours as r,g,b""" #Colour object can be XYZColor, sRGBColor or other colormath color object #clip of 1 will restrict the max value to 255, possibly alter the colour. rgbcol = convert_color(self, sRGBColor,is_upscaled=False) #convert to sRGB screen colours if clip == 1: #Clip RB Values to 255 rgbcol = sRGBColor(rgbcol.clamped_rgb_r,rgbcol.clamped_rgb_g,rgbcol.clamped_rgb_b) #limit RGB max to 1 rgbcol = rgbcol.get_upscaled_value_tuple() # convert from 0-1 to 0-255 return rgbcol[0],rgbcol[1],rgbcol[2] #return r,g,b values 0-255 def cleanup(): as7262.set_measurement_mode(3) #deactivate scanner to scan on command as7262.set_illumination_led(0) #switch off led def main(): try: a = input("Press a key when ready, enter q to quit") while a != "q": values = as7262.get_calibrated_values() #get values from scan spec = [float(i) for i in list(values)] #convert results from string to float xyzcol = spectral_to_xyz(spec) #convert spectral scan to xyz colour space values print("Spectral Scan ROYGBV:",spec) print("XYZ Color",xyzcol) print("RGB:",convert_to_rgb(xyzcol,1)) a = input() #wait for user cleanup() except KeyboardInterrupt: cleanup() if __name__ == '__main__': main()
Also available at as7262-example4-scan2rgb.txt
The results look like this
Spectral Scan ROYGBV: [5.880025863647461, 4.03620719909668, 0.9279702305793762, 1.0741294622421265, 1.1915769577026367, 1.1165746450424194] XYZ Color XYZColor (xyz_x:0.6209 xyz_y:0.4801 xyz_z:0.2491) RGB: (255, 151, 124)
Use a Spectral Sensor for Identification
Not quite the intended use for a spectral scanner but as the results of a spectral scan of an object are the same under the same lighting conditions, you can use it to identify objects based on the colour signature.
This means you could create a set of cards for different actions. When one is placed in front of the scanner, it could trigger an action. For example you could print out your favorite albumn covers. Then place one in front of the spectral scanner, then with a little bit of programming you could get that album to play. You could also use it as a security feature by only allowing access when the correct image is presented to the spectral scanner. Though this is very sensitive so it needs to be done in some type of housing so the same lighting and picture position is used for the scan to match.
This is an example of a program that can scan an items and log the results in a text file with a reference name. Then when that item is scanned again, in exactly the same conditions as it was recorded, it will identify the object.
#!/usr/bin/python3 #This script uses a as7262 6 colour spectral scanner from Pimoroni #It will log scans in a file and then will match new scans against logged scans #RaspberryConnect.com from as7262 import AS7262 import csv as7262 = AS7262() as7262.set_gain(1) # 1 3.7 16 64 as7262.set_integration_time(30) #(1 to 255 x 2.8ms) as7262.set_measurement_mode(2) #all colours continous as7262.set_illumination_led_current(12.5) #12.5mA 25mA 50mA 100mA as7262.set_illumination_led(1) # led on def log(self,value): """save Log to current folder""" with open('./ScanLog-Items.txt', 'a') as f: wr = csv.writer(f) self.append(value) wr.writerow(self) def scan(): multiscan=[] for i in range(5): #scan 5 times, use most common results values = as7262.get_calibrated_values() spec = [str(i) for i in list(values)] multiscan.append(spec) r = checkscan(multiscan) return r def checkscan(self): """Take 5 scan results and use the most common scan values""" cnt=0 for x in range(len(self)): for i in range(len(self)): if self[x] == self[i]: cnt +=1 if cnt >= 4: return self[x] cnt=0 return 0 def comparescan(self): """Check log for a match with the scan and return the name""" m = "non" with open('./ScanLog-Items.txt', 'r') as f: r = csv.reader(f) for row in r: if row[:6] == self: m = row[6] return m try: mode = "" sc = 0 print("Enter m to match items") print("Enter s to scan and log new items") print("Enter q to quit") a = input() if a == "m": mode = "m" a = input("\nPress enter to start") elif a == "s": mode = "s" a = input("Enter a name for the item to be scanned: ") while a != "q": #Match Mode if mode == "m": while sc == 0: sc = scan() x = comparescan(sc) if x != "non": print("\nMatch with: ",x) else: print("No Match Found") sc = 0 a = input("\nPress Enter to Scan for Match: ") #Scan Mode elif mode == "s": while sc == 0: sc = scan() log(sc,a) print(a," Logged") a = "" sc = 0 a = input("\nEnter a name for the item to be scanned: ") as7262.set_measurement_mode(3) as7262.set_illumination_led(0) except KeyboardInterrupt: as7262.set_measurement_mode(3) as7262.set_illumination_led(0)
Also available at as7262-scan_and_match.txt
To use this script first scan the items you wish to log. Select s from the menu. Enter a name for the item you are scanning. Then enter q when done.
Next run the program again but use m from the menu. Now when you scan an item it will compare the results with what is in the log and displays the name of the matched item if one is found.
Reference:
https://github.com/pimoroni/as7262-python
https://www.colourphil.co.uk/xyz_colour_space.shtml
https://en.wikipedia.org/wiki/SRGB
https://videocide.com/glossary/color-space/
https://www.waveformlighting.com/color-matching/what-is-d50-for-graphic-arts-printing
https://python-colormath.readthedocs.io/en/latest/illuminants.html