Memory allocation error - comments!?!

To date I’ve been using an OpenMV cam with no SD card. The program I’ve written started returning allocation errors, but I was able to just squeeze it in under the limit. And then I commented the code. Comments bumped me over the limit!? This doesn’t seem right. But I confirmed line by line that the only difference was adding comments. Bizzare.

So I added a SD card. When trying to load the program with comments (larger), the IDE freezes/hangs. This proceeded to brick the OpenMV cam - it was recovered through a firmware re-flash and internal file system erase. Upon attempting to re-run my script via the IDE, I got the same results.

The SD card functions as an extension of the file system, correct? In other words, adding the SD card should enable me to run larger programs, correct? If so, any idea what I am doing wrong?

This is a basic 16gb SD card formatted to FAT32.

Thanks,

Chris

Hmm, so it seems like I can save the program to the openMV cam and run it as is, but I can’t run it through the IDE. Does this make sense? Any workarounds? Debugging will be somewhat annoying if I can’t use the IDE.

Note that when I connect the OpenMV to my computer, the drive pops up with ~15gb of space, so it seems like the uSD card is being recognized.

Um, so, the script is compiled by the camera. The camera firmware parses the script line by line and turns it into a byte array that uses heap memory.

Anyway, um, have you tried aggressively “del object” and gc.collect() and you don’t need something? You have to do this once you start running out of ram so as to control heap fragmentation.

I’m not familiar with del_object and gc_collect, so no, I’m not doing this. My program is pretty simple, its just a bit long, honestly I’m surprised I’m having a memory allocation problem.

Why would it work if I save direct to the camera, but not work when using the IDE?

Saving the script to cam strips comments and white space (and yes even comments take memory, they have to be stored somewhere, right ?) using an SD card won’t help. Also, when connected to the IDE, the camera may need more memory.

Please bear with me, I am only somewhat okay at programming in C. So I am used to a precompiler - comments take no space, etc.

So, to confirm, the SD card will not help with memory allocation errors, correct?

No it will not, the script is loaded in memory, parsed, executed etc… You should avoid too many small allocations (causes memory fragmentation) and also use del , to free some memory, this may help.

Okay, so I am taking a series of small images - color analysis on defined portions of an image. Should I run a del img command? Or simply run a gc.collect() at the end of the function where I analyze the image? Apologies if this is a basic question, still trying to navigate python. Appreciate the help.

-Chris

del img just before the line that fails to alloc will help. GC will try to collect memory before failing. If all fails, post the script and I’ll try to optimize it.

Is there a way to reliably test which line is causing the allocation failure?

The exception should tell you the line number.

Really? Thanks, sorry, I completely missed that. I was focused on the bytes that couldn’t be allocated.

Hmm, this is the error I’m getting, no reference to a code line:

MemoryError: memory allocation failed, allocating 3242 bytes
MicroPython v1.9.4-4510-g23e8457de on 2018-06-29; OPENMV3 with STM32F765

Any ideas?

If you have time/are willing to take a look at my code for memory optimization opportunities, feedback will be appreciated. Please be kind, I generally code in C, and I’m not that great at it anyway.

Note that if I take this code and remove the comments, it will fit. But barely. If I add any more functionality, I hit the memory error again.

And yes, I am barely using the functionality of the OpenMV cam, but it is perfect for what I need it to do (color analysis). Unfortunately I can’t use your canned LAB-based image analytics, this is why I am averaging pixels across a ROI.

import sensor, image, time, pyb
from pyb import I2C, delay, ExtInt, Pin, Timer


global systemState #increments state
systemState = 0x00

#flag toggled in ISR when start button is pressed
startButtonPressFlag = 0
#keeps track of the start button state - used to keep tabs on a long press to handle a user-forced reset condition
isStartBtnPressed = 0
#keeps track of the time that the start button is depressed
startBtnPressDuration = 0
#used in each state to keep track of timed events
sysClockDuration = 0
#indicates if the arm motor is on or off
isArmMotorOn = 0
#indicates if the power up state is complete - the system then waits for user input
isPwrUpComplete = 0
#indicates if the cleaning pump is on or off
isClnPumpComplete = 0
#indicates if the urine pump is on or off
isUrnPumpComplete = 0
#indicates if the test chamber is filled or not
isChamberFilled = 0
#indicates if the urine hold duration (in the test chamber) is complete
isHoldComplete = 0
#indicates if the drain in the urine accumulator is open or not
isDrainOpen = 0
#indicates if the urine pre-drain interval is complete (mid-stream urine is used for analysis_
hasPreDrainOccured = 0
#indicates if the colormetric test is complete or not
isColormetricComplete = 0
#indicates if the collector arm is home or not
isArmHome = 0
#used in each state to initialize the state as needed
init = 0
#used during i2c transactions
snd_data = bytearray(1)
recv_data = bytearray(1)
#variable updated during colormetric tests to reflect the range of interest being analyzed in the image
roi = (0,0,0)

#clock used for timing button events
buttonClock = time.clock()

#interrupt handler - only used to handle button presses (currently)
def intHandler():
    global startButtonPressFlag
    global isStartBtnPressed
    global startBtnPressBuration
    global systemState
    if startButtonPressFlag == 1:
#if the start pin is high (switch is NC) and the flag is low, the button is pressed
        if startButtonPin.value() == 1 and isStartBtnPressed == 0:
            isStartBtnPressed = 1
            buttonClock.tick()
            extIntStart.enable()
#else if the pin is high and the flag is asserted, look at the button timer
        elif startButtonPin.value() == 1 and isStartBtnPressed == 1:
            startBtnPressDuration = buttonClock.avg()
#if the button is held for longer than interval, a forced reset is issued
            if startBtnPressDuration > 2000:
                systemState = 0x08
                buttonClock.reset()
                startButtonPressFlag = 0
#if the pin is low and the flag is asserted, the button is released
        elif startButtonPin.value() == 0 and isStartBtnPressed == 1:
            isStartBtnPressed = 0
            buttonClock.reset()
            startButtonPressFlag = 0
            extIntStart.enable()

#camera initialization routine
def cameraInit():
    sensor.reset()                      # Reset and initialize the sensor.
    sensor.set_pixformat(sensor.RGB565) # Set pixel format to RGB565
    sensor.set_framesize(sensor.VGA)   # Set frame size to VGA
    sensor.set_brightness(0) #+/-3
    sensor.set_saturation(0) #+/-3
    sensor.set_contrast(0) #+/-3
    sensor.set_auto_gain(False, -10) #gain_db
    sensor.set_auto_exposure(False, 15000) #exposure time
    sensor.set_quality(100)

#start button ISR
def startbuttonISR(line):
    global startButtonPressFlag
    startButtonPressFlag = 1
    extIntStart.disable()

def runArmOut():
    snd_data[0] = 0xFD
    i2c.mem_write(snd_data, 0x64, 0, timeout=2000, addr_size=8) #arm motor

#function which drives the urine collection arm back towards the home position
def runArmBack():
    snd_data[0] = 0xFE
    i2c.mem_write(snd_data, 0x64, 0, timeout=2000, addr_size=8) #arm motor

#stops the urine collection arm
def stopArm():
    snd_data[0] = 0x00
    i2c.mem_write(snd_data, 0x64, 0, timeout=2000, addr_size=8) #arm motor

#pumps urine in to the test chamber
def pumpFluidIn():
    snd_data[0] = 0xFE
    i2c.mem_write(snd_data, 0x63, 0, timeout=2000, addr_size=8) #pump motor

def pumpFluidOut():
    snd_data[0] = 0xFD
    i2c.mem_write(snd_data, 0x63, 0, timeout=2000, addr_size=8) #pump motor

#stops the urine pump
def stopPump():
    snd_data[0] = 0x00
    i2c.mem_write(snd_data, 0x63, 0, timeout=2000, addr_size=8) #pump motor

#opens the drain in the urine collection chamber (accumulator)
def openDrain():
    snd_data[0] = 0xF7
    i2c.mem_write(snd_data, 0x20, 0, timeout=2000, addr_size=8) #output expander

def closeDrain():
    snd_data[0] = 0xFF
    i2c.mem_write(snd_data, 0x20, 0, timeout=2000, addr_size=8) #output expander

#enables the cleaning pump
def cleanActive():
    cleanPumpPin.high()

def cleanInactive():
    cleanPumpPin.low()

#used to write the results of the colormetric analysis
def uartWrite(rAvg, gAvg, bAvg, result):
    uart.write("%d"%rAvg)
    uart.write(",")
    uart.write("%d"%gAvg)
    uart.write(",")
    uart.write("%d"%bAvg)
    uart.write(",")
    uart.write("%d"%result)
    uart.write("\n\r")

def analyzeVitC(gAvg):
    if gAvg>=168:
        return 0
    elif gAvg>=160:
        return 50
    elif gAvg>=150:
        return 100
    elif gAvg>=134:
        return 200
    elif gAvg>=119:
        return 300
    elif gAvg>=94:
        return 500
    elif gAvg>=76:
        return 700
    elif gAvg>=58:
        return 1000
    else:
        return 2000

def analyzeB7(gAvg):
    if gAvg>=185:
        return 20
    if gAvg>=177:
        return 15
    if gAvg>=170:
        return 10
    elif gAvg>=162:
        return 5
    else:
        return 0

def analyzeMag(gAvg):
    if gAvg>=185:
        return 0
    if gAvg>=177:
        return 50
    if gAvg>=170:
        return 250
    elif gAvg>=162:
        return 450
    else:
        return 1500

def analyzePH(gAvg):
    if gAvg>=168:
        return 0
    elif gAvg>=160:
        return 5
    elif gAvg>=150:
        return 5.5
    elif gAvg>=134:
        return 6
    elif gAvg>=119:
        return 6.5
    elif gAvg>=94:
        return 7
    elif gAvg>=76:
        return 7.5
    elif gAvg>=58:
        return 8
    else:
        return 8.5

def analyzeSG(gAvg):
    if gAvg>=168:
        return 1000
    elif gAvg>=160:
        return 1005
    elif gAvg>=150:
        return 1010
    elif gAvg>=134:
        return 1015
    elif gAvg>=119:
        return 1020
    elif gAvg>=94:
        return 1025
    else:
        return 1030


def analyzeColor(gAvg):
    if gAvg>=168:
        return 1
    elif gAvg>=160:
        return 2
    elif gAvg>=150:
        return 3
    elif gAvg>=134:
        return 4
    elif gAvg>=119:
        return 5
    elif gAvg>=94:
        return 6
    elif gAvg>=84:
        return 7
    else:
        return 8

def analyzeArea(roi):
    cameraInit() #initialize the camera
    sensor.set_windowing(roi) #specify the region of interest
    sensor.skip_frames(time = 2000)     # Wait for settings take effect.
    img = sensor.snapshot()         # Take a picture and return the image.
    delay(100)
    rAvg = 0
    gAvg = 0
    bAvg = 0
    #sum the pixels in the range of interest
    for x in range(roi[2]):
        for y in range(roi[3]):
            pixel = img.get_pixel(x, y, rgbtuple=True)
            #delay(10)
            rAvg = rAvg+pixel[0];
            gAvg = gAvg+pixel[1];
            bAvg = bAvg+pixel[2];
    #average the pixels
    rAvg = rAvg/(roi[2]*roi[3])
    gAvg = gAvg/(roi[2]*roi[3])
    bAvg = bAvg/(roi[2]*roi[3])
    del img #img is no longer needed
    gc.collect #collect the garbage
    rgbAverage = (rAvg, gAvg, bAvg)
    return rgbAverage

#Initialization
uart = pyb.UART(1, 115200, timeout_char = 1000) #pin 0 = RX, pin 1 = TX - used for comms of the results
#pin 2 - not used
portExpInt = pyb.Pin('P3', pyb.Pin.IN) #not currently used
startButtonPin = pyb.Pin('P4', pyb.Pin.IN) #start button pin - currently NC
homeButtonPin = pyb.Pin('P5', pyb.Pin.IN) #note, home switch is NC
magProxPin = pyb.Pin('P6', pyb.Pin.IN) #magnet proximity switch - hall effect sensor
i2c = I2C(4)                         # create on bus 4 - pin 7 = SCL, pin 8 = SDA
i2c = I2C(4, I2C.MASTER)             # create and init as a master
i2c.init(I2C.MASTER, baudrate=100000)
cleanPumpPin = pyb.Pin('P9', pyb.Pin.OUT_PP)
display_led = pyb.LED(1)
green_led = pyb.LED(2)
blue_led = pyb.LED(3)
red_led = pyb.LED(4)
extIntStart = pyb.ExtInt(startButtonPin, pyb.ExtInt.IRQ_RISING_FALLING, pyb.Pin.PULL_NONE, startbuttonISR)
#make sure everything is off upon startup
stopArm()
stopPump()
cleanPumpPin.low()
closeDrain()
green_led.off()
blue_led.off()
red_led.on() #swapped polarity - must confirm
display_led.off() #swapped polariy
sysClock = time.clock()  #initialize system clock

while(True):
    #if the button press flag is issued, run the interrupt handler
    if startButtonPressFlag == 1:
        intHandler()
    if systemState == 0x00: #Power up state
        if init == 0: #Initialization
            init = 1
            sysClock.tick()
            red_led.off() #red LED on, boot up in progress, note swapped polarity
        else:
            sysClockDuration = sysClock.avg()
            if isPwrUpComplete == 0:
                #if power up is not complete, look at the home pin - if 6000ms elapses and the arm still isn't home, go to an error state
                if homeButtonPin.value() == 0 and sysClockDuration<6000: #ARM_RETURN_MAX_DURATION
                    #if the home pin indicates that the arm isn't home, send it home
                    if isArmMotorOn == 0:
                        isArmMotorOn = 1
                        runArmBack()
                elif homeButtonPin.value() == 0 and sysClockDuration>=6000: #ARM_RETURN_MAX_DURATION
                    systemState = 0x08 #error state
                    init = 0
                    sysClock.reset()
                    stopArm()
                elif homeButtonPin.value() == 1:
                    isArmMotorOn = 0
                    stopArm()
                    isPwrUpComplete = 1
            else:
                red_led.on() #swapped polarity
                green_led.on() #green LED - ready for user input
                #waiting for start button press
                if isStartBtnPressed == 1:
                    systemState = 0x01 #sweep state
                    init = 0
                    isPwrUpComplete = 0
                    sysClock.reset()
    #Timing for sweeping the arm out
    elif systemState == 0x01:
        if init == 0:
            init = 1
            sysClock.tick()
            #send arm out
            runArmOut()
            red_led.on() #red and green on - amber - filling
        else:
            sysClockDuration = sysClock.avg()
            #arm is in position
            if sysClockDuration >= 1500 and homeButtonPin.value() == 0: #ARM_SWEEP_OUT_DURATION
                stopArm()
                init = 0
                sysClock.reset()
                systemState = 0x02 #urine collect state
            #arm never left - error
            elif sysClockDuration >= 1200 and homeButtonPin.value() == 1: #ARM_SWEEP_OUT_DURATION
                stopArm()
                init = 0
                sysClock.reset()
                systemState = 0x08 #error state
    #Timing for urine collection
    elif systemState == 0x02:
        if init == 0:
            init = 1
            sysClock.tick()
        else:
            sysClockDuration = sysClock.avg()
            #if 7000ms elapses without a mag sensor toggle - error
            if sysClockDuration >=7000 and hasPreDrainOccured == 0: #MAX_COLLECT_DURATION
                init = 0
                sysClock.reset()
                systemState = 0x08 #error state
            #if the mag sensor toggles and pre-draining hasn't completed yet, open the drain
            elif magProxPin.value() == 0 and hasPreDrainOccured == 0:
                sysClock.reset()
                sysClock.tick()
                openDrain()
                hasPreDrainOccured = 1
            #close the drain after time has elapsed
            elif hasPreDrainOccured == 1 and sysClockDuration >=300 and sysClockDuration < 350:
                closeDrain()
            #when the mag sensor is toggled again (or if it is maintained) - move state
            elif magProxPin.value() == 0 and hasPreDrainOccured == 1 and sysClockDuration >=350:
                init = 0
                hasPreDrainOccured = 0
                sysClock.reset()
                systemState = 0x03
                red_led.off()
                green_led.off()
                blue_led.on() #blue LED on - urine collected, pumping to chamber
    #timing to sweep the arm back while pumping urine in to the test chamber
    elif systemState == 0x03:
        if init == 0:
            init = 1
            sysClock.tick()
            runArmBack()
            pumpFluidIn()
        else:
            sysClockDuration = sysClock.avg()
            #if the arm doesn't return in 6000ms - issue an error
            if sysClockDuration >= 6000: #ARM_RETURN_MAX_DURATION
                init = 0
                sysClock.reset()
                systemState = 0x08 #error state
                stopArm()
                stopPump()
            #stop the arm once the home switch is toggled
            elif homeButtonPin.value() == 1 and isArmHome == 0:
                stopArm()
                isArmHome = 1
            #stop the pump after the pre-determined time
            if sysClockDuration >= 2500 and isUrnPumpComplete == 0: #URINE_PUMPING_DURATION
                isUrnPumpComplete = 1
                stopPump()
            #After the urine has been held in the test chamber for the allotted time, move to the next state
            if isArmHome == 1 and isUrnPumpComplete == 1 and sysClockDuration >= (4500): #URINE_PUMPING_DURATION + URINE_HOLD_DURATION
                init = 0
                isArmHome = 0
                isUrnPumpComplete = 0
                sysClock.reset()
                red_led.on()
                green_led.on() #amber - processing urine
                blue_led.off()
                #Perform color test while urine is in the chamber
                roi = (511, 443, 32, 27) #ROI COLOR - must be updated
                RGBaverage = analyzeArea (roi)
                #apply scaling factor to RGB data - placeholder
                result = analyzeColor(RGBaverage[1])
                uart.write("TEST_BEGIN\n\r")
                uart.write("COLOR,")
                uart.write(RGBaverage[0], RGBaverage[1], RGBaverage[2], result)
                systemState = 0x04
    elif systemState == 0x04: #drain urine
        if init == 0:
            init = 1
            sysClock.tick()
            pumpFluidOut()
            openDrain()
        else:
            sysClockDuration = sysClock.avg()
            #turn off pump after elapsed interval
            if sysClockDuration >= 4500 and isUrnPumpComplete == 0: #URINE_DRAINING_DURATION
                isUrnPumpComplete = 1
                stopPump()
            #close the drain after the elapsed interval
            elif sysClockDuration >= (6500) and isUrnPumpComplete == 1: #URINE_DRAINING_DURATION + ACCUMULATOR_DRAINING_DURATION
                closeDrain()
                init = 0
                isUrnPumpComplete = 0
                sysClock.reset()
                systemState = 0x05
    elif systemState == 0x05: #pre-clean state - flush the collector and the accumulator
        if init == 0:
            init = 1
            sysClock.tick()
            cleanActive()
        else:
            sysClockDuration = sysClock.avg()
            #run the cleaning pump for the alotted time
            if sysClockDuration >= 3000 and isClnPumpComplete == 0: #CLEANING_FILL_DURATION
                isClnPumpComplete = 1
                cleanInactive()
            #Let the cleaning solution sit in the accumulator for a bit, then drain
            elif sysClockDuration >= (7000) and isClnPumpComplete == 1 and isDrainOpen == 0: #CLEANING_FILL_DURATION + CLEANING_HOLD_DURATION
                isDrainOpen = 1
                openDrain()
            #close the drain after the alotted time
            elif  sysClockDuration >= (9000) and isDrainOpen == 1: #CLEANING_FILL_DURATION + CLEANING_HOLD_DURATION + ACCUMULATOR_DRAINING_DURATION
                closeDrain()
                init = 0
                isClnPumpComplete = 0
                isDrainOpen = 0
                sysClock.reset()
                systemState = 0x06
    elif systemState == 0x06: #colormetric analysis
        if init == 0:
            init = 1
            sysClock.tick()
        else:
            sysClockDuration = sysClock.avg()
            if isColormetricComplete == 0:
                for i in range (0,4): #step through the 5x regions of interest that can be analyzed right away
                    if i==0:
                        roi = (53, 391, 35, 33) #ROI ASCORBIC ACID
                    elif i==1:
                        roi = (150, 408, 37, 32) #ROI MAGNESIUM
                    elif i==2:
                        roi = (345, 417, 32, 36) #ROI PH
                    elif i==3:
                        roi = (444, 414, 27, 33) #ROI SG
                    RGBaverage = analyzeArea (roi)
                    result = 0
                    if i==0:
                        result = analyzeVitC(RGBaverage[1])
                        uart.write("Vitamin C,")
                    elif i==1:
                        result = analyzeMag(RGBaverage[1])
                        uart.write("Magnesium,")
                    elif i==2:
                        result = analyzePH(RGBaverage[1])
                        uart.write("PH,")
                    elif i==3:
                        result = analyzeSG(RGBaverage[1])
                        uart.write("SG,")
                    uartWrite(RGBaverage[0], RGBaverage[1], RGBaverage[2], result)
                isColormetricComplete = 1
                sysClock.reset()
                sysClock.tick()
            else:
                if sysClockDuration>=450000: #B7_WAIT_DURATION - this is about 13 minutes - timekeeping is not great
                    roi = (258, 80, 26, 41) #ROI B7 - may need to be tweaked
                    RGBaverage = analyzeArea (roi)
                    result = analyzeB7(RGBaverage[1])
                    uart.write("B7,")
                    uartWrite(RGBaverage[0], RGBaverage[1], RGBaverage[2], result)
                    uartWrite("TEST_END\n\r")
                    init = 0
                    isColormetricComplete = 0
                    sysClock.reset()
                    systemState = 0x07 #move to final cleaning step
                    red_led.off()
                    green_led.on() #green - processing complete
                    blue_led.off()
    elif systemState == 0x07: #final clean
        if init == 0:
            init = 1
            sysClock.tick()
            cleanActive()
        else:
            sysClockDuration = sysClock.avg()
            #run the cleaning pump for the specified duration
            if sysClockDuration >= 3000 and isClnPumpComplete == 0: #CLEANING_FILL_DURATION
                isClnPumpComplete = 1
                cleanInactive()
                pumpFluidIn()
            #use the urine pump to pump cleaning fluid in to the test chamber
            elif sysClockDuration >= (5750) and isClnPumpComplete == 1 and isChamberFilled == 0: #CLEANING_FILL_DURATION + CLEANING_PUMPING_DURATION
                isChamberFilled = 1
                stopPump()
            #hold the cleaning solution in the test chamber
            elif sysClockDuration >= (9750) and isChamberFilled == 1 and isHoldComplete == 0: #CLEANING_FILL_DURATION + CLEANING_HOLD_DURATION + CLEANING_PUMPING_DURATION
                isHoldComplete = 1
                pumpFluidOut()
                openDrain()
            #drain the fluid from the test chamber
            elif sysClockDuration >= (15750) and isHoldComplete == 1: #CLEANING_FILL_DURATION + CLEANING_HOLD_DURATION + CLEANING_PUMPING_DURATION + CLEANING_DRAINING_DURATION
                closeDrain()
                stopPump()
                init = 0
                isClnPumpComplete = 0
                isChamberFilled = 0
                isHoldComplete = 0
                sysClock.reset()
                systemState = 0x00 #return to power up
    elif systemState == 0x08: #error state
        red_led.off() #note polarity shift
        green_led.off()
        blue_led.off()
        #sweep arm back until the home switch toggles
        runArmBack()
        while homeButtonPin.value() == 0:
            delay(20)
        stopArm()
        pumpFluidOut()
        openDrain()
        delay(4000)
        closeDrain()
        stopPump()
        cleanActive()
        delay(3000)
        cleanInactive()
        pumpFluidIn()
        delay(5550)
        stopPump()
        delay(4000)
        pumpFluidOut()
        openDrain()
        delay(10000)
        stopPump()
        closeDrain()
        systemState = 0x00
    delay(20)

That’s so much code…

Um, can you point just post the lines that have issues? Where you allocate a lot of RAM?

Sorry. Honestly I’m not sure what is the root cause of the RAM allocation issue. If I had to guess, I’d say it was here:

def analyzeArea(roi):
    cameraInit() #initialize the camera
    sensor.set_windowing(roi) #specify the region of interest
    sensor.skip_frames(time = 2000)     # Wait for settings take effect.
    img = sensor.snapshot()         # Take a picture and return the image.
    delay(100)
    rAvg = 0
    gAvg = 0
    bAvg = 0
    #sum the pixels in the range of interest
    for x in range(roi[2]):
        for y in range(roi[3]):
            pixel = img.get_pixel(x, y, rgbtuple=True)
            #delay(10)
            rAvg = rAvg+pixel[0];
            gAvg = gAvg+pixel[1];
            bAvg = bAvg+pixel[2];
    #average the pixels
    rAvg = rAvg/(roi[2]*roi[3])
    gAvg = gAvg/(roi[2]*roi[3])
    bAvg = bAvg/(roi[2]*roi[3])
    del img #img is no longer needed
    gc.collect #collect the garbage
    rgbAverage = (rAvg, gAvg, bAvg)
    return rgbAverage

I call this function 6 times in the code. Should I be using gc.collect and del differently?

Try this:

def analyzeArea(roi):
    cameraInit() #initialize the camera
    sensor.set_windowing(roi) #specify the region of interest
    sensor.skip_frames(time = 2000)     # Wait for settings take effect.
    img = sensor.snapshot()         # Take a picture and return the image.
    delay(100)
    rAvg = 0
    gAvg = 0
    bAvg = 0
    #sum the pixels in the range of interest
    for x in range(roi[2]):
        for y in range(roi[3]):
            pixel = img.get_pixel(x, y, rgbtuple=True)
            #delay(10)
            rAvg = rAvg+pixel[0];
            gAvg = gAvg+pixel[1];
            bAvg = bAvg+pixel[2];
            del pixel
        gc.collect()
    #average the pixels
    rAvg = rAvg/(roi[2]*roi[3])
    gAvg = gAvg/(roi[2]*roi[3])
    bAvg = bAvg/(roi[2]*roi[3])
    del img #img is no longer needed
    gc.collect() #collect the garbage
    rgbAverage = (rAvg, gAvg, bAvg)
    return rgbAverage

Also, note that you can wrap things in try: except: blocks if the error happens randomly and just retry. You don’t have to make sure it always succeeds.

So, keep in mind in python everything is a memory alloc. So, the im.get_pixel call returns a tuple which is allocated on the heap.

Thanks, definitely a change in my way of thinking. I’ll give the del pixel a shot, and do some digging on the try: except: blocks - never heard of this.

-Chris

Note, you can just use our LAB color code and convert the returned value to RGB…

Do you have a canned conversion back to RGB? I took a look at doing this, and it seemed like much less code to just pull the RGB data and average directly (vs converting LAB to RGB). Different story if you guys already coded this.