Measuring Light for Indoor Plants: Using i1Studio as a PAR PPFD Meter

April 22, 2020

I have recently planted a few plants including a few tomatoes (san marzano and cherry), capsicum/peppers (habanero) and (snack) cucumbers, and I was wondering if it will be possible to grow them indoors where they can get limited sunlight. I realized I did not know enough about the effect of light on plants and how to quantify this, and here comes this post.

This post can also be read as how to use X-Rite i1Studio Spectrometer as a PAR PPFD meter for horticulture use.


Everybody knows about Photosynthesis to some extent but I realized I actually did not know enough and actually did not know about plants in general, so I watched a few Plant Physiology courses on youtube, checked some books and articles about this topic, and I highly recommend this course. Here are some notes:

  • Photosynthesis is basically a carbon fixation process, meaning it captures the carbon from the CO2 in the air and fixes it (converts it) to a usable form for the plant. Then, it is used everywhere, around 45% of a plant’s dry mass is carbon.

  • (The amount of) Photosyntesis does not depend on the energy of light. So even if blue (smaller wavelength, higher frequency than red) has more energy than red, it does not matter for the plant. What matters is the total number of photons. (It does not mean exposing the plant with only red light is the best, other wavelengths including UV and IR/far-red have other effects)

  • The photosynthesis vs. the amount of light follows something like a logorithmic curve, meaning the amount of photosynthesis increases up to certain point linearly as the amount of light increases, but then it saturates. This saturation may have different reasons, e.g. CO2, phosphate.

  • Interestingly, the number of photons absorbed is independent of the photosyntesis, meaning even if photosynthesis is saturated, the photons are still absorbed up to a really high (much more than full sun) amount of light. Unfortunately, this is not a good thing, as it causes the generation of so called Reactive Oxygen Species which are harmful.

  • A leaf is developed differently depending on it was under shade or under sun, and this does not change after it is developed. So if you move a plant from shade to sun, its photosynthesis does not magically increase. It requires new leafs to be developed (assuming the plant is not a shade plant, in this case there is probably an evolutionary/genetic limit on many things that affects the amount of photosynthesis).

  • Not entirely true (because the range is extended to 280-800nm and it is called PBAR - Plant Biologically Active Radiation - now) but we can say the spectral band of radiation for photosynthesis is 400-700nm which is called Photosynthetically Active Radiation, PAR. Measuring PBAR is more difficult and expensive since most of the entry level spectrometers usually measure from mid 300nm up to mid 700nm.

  • You do not need a spectrometer to measure PAR or PBAR, there are devices that measures the number of photons directly without spectral information, but it is perfectly OK to use a spectrometer, and actually it is I think more accurate.

  • The amount of light arriving to an area is normally measured in energy units, but for photosynthesis the number of photons are important, so there needs to be a different unit. This unit is PPFD, Photosynthetic Photon Flux Density, which is in umol/m2/s, meaning the number of photons (in umol) arriving to a unit area of m2 in a second. If this is integrated over a day, corresponding value is called Daily Light Integral (DLI) in units of mol/m2/day, so the total number of photons (in mol) arrived to a unit area of m2 in a day.

  • PPFD of full sun (meaning the maximum amount of sunlight we can receive on planet earth, e.g. on equator at noon) is around 2500 umol/m2/s. At approx. 45deg north of equator, now in April, I can measure around 600 umol/m2/s (indoors, but I guess it does not matter, but I need to confirm).

In summary, for growing plants, you need to know PPFD/DLI of the environment you have, and you need to learn the requirement of the plant you want to grow, and see if these match. Of course the plant possibly grows even if you have less than ideal light conditions, but it is obviously not ideal in terms of growth and yield.


I have an i1Studio spectrometer, which is used for screen and printer profiling but since it is a spectrometer with (absolute) ambient light measurement capability, I decided to use it for the purpose of this post.

I am using the spotread program in Argyll CMS with -a option for ambient measurement. I am not using the high resolution mode, and this means i1Studio reports spectral data in 10nm bands, in mW/m2/nm unit.

I have a small bash script (below) that is run every minute by cron. As you see the script runs the actual procedure twice 30 seconds apart.


pr () {
  NOW="`date +%Y%m%d-%H%M%S`"
  /home/pi/argyll/bin/spotread -a -N -O /home/pi/e/$FILE
  /home/pi/e/ /home/pi/e/$FILE

sleep 30

The script (below) parses the spectral illuminant description (.sp) file written by spotread, calculates PPFD value from the spectral data and sends this to a Graphite Carbon instance running locally.

##!/usr/bin/env python3

import sys
import os.path
from datetime import datetime
import socket

## constants
h = 6.626
c = 3
Na = 6.022
## delta lambda, measurement values are 10nm apart
dL = 10
carbon_server = 'localhost'
carbon_port = 2003

def send_to_carbon(ts, value):
    sock = socket.socket()
    sock.connect((carbon_server, carbon_port))
    message = 'par.ppfd %f %d\n' % (value, ts)

def main(filepath):
    (dirname, filename) = os.path.split(filepath)
    (filenamealone, ext) = os.path.splitext(filename)
    ts = datetime.strptime(filenamealone, '%Y%m%d-%H%M%S')
    carbon_ts = int(ts.timestamp())
    data = ''
    with open(filepath) as f:
        while True:
            l = f.readline()
            # end of file ?
            if len(l) == 0:
            l = l.strip()
            if l == 'BEGIN_DATA':
                data = f.readline()
    if len(data) > 0:
        data = data.split(' ')
        if data[-1] == '\n':
            data = data[0:-1]
        data = list(map(float, data))
        print('no data found')
    # actual data is from 380-730
    # take the part 400-700 (inclusive)
    # data is spectral irradiance W/m2*nm
    data = data[2:-3]
    # 701 because 700nm is included
    wavelengths = list(range(400, 701, dL))
    PPFD = 0
    for i in range(0, len(wavelengths)):
        E = data[i] # this is mW/m2*nm
        # conversion from energy to photons
        factor = wavelengths[i] / (h * c * Na)
        # below E is converted to W/m2*nm first
        PPFD = PPFD + (E / 1000) * dL * factor
    print('PPFD (umol/m2s): ', round(PPFD, 3))
    send_to_carbon(carbon_ts, float(PPFD))

if __name__ == '__main__':

Then, I am using Grafana to plot these values.

All of above are running on a Raspberry Pi 4.

However, there is one unfortunate problem. The spectrometer requires calibration at least every hour. The -N option of spotread eliminates the need for calibration at every measurement, but the hardware does not allow this an hour later after the calibration done. Very unfortunately the calibration cannot be automated, since it requires turning the dial on the spectrometer to calibration position, and after calibration it has to be turned back to ambient measurement position. To solve actually simplify this problem, I wrote the below Python script which exposes two HTTP endpoint, one of which initiates calibration. So, every 50 minutes or so, I go next to the device, wait for 30 second periods by checking to LED on the spectrometer (LED briefly flashes at each measurement), then turn the dial to calibration position, call calibrate endpoint on my phone, if it succeeds, turn the dial back to ambient measurement mode. Sometimes it does not succeed, then I just recall the calibration endpoint again and it works the second time always.

##!/usr/bin/env python3

import tornado.ioloop
import tornado.web
import subprocess

class CalibrateHandler(tornado.web.RequestHandler):
    def get(self):
        o =["spotread", "-a", "-s", "-O"], stdout=subprocess.PIPE, text=True)

class MeasureHandler(tornado.web.RequestHandler):
    def get(self):
        o =["spotread", "-a", "-N", "-O"], stdout=subprocess.PIPE, text=True)

def make_app():
    return tornado.web.Application([
        (r"/calibrate", CalibrateHandler),
        (r"/measure", MeasureHandler),

if __name__ == "__main__":
    app = make_app()

PPFD and DLI measurements

Below is the result of my measurement on 20.04.2020 from a few minutes before sunrise (~6am) to some minutes after sunset (~9pm). The location of measurements, thus the plants, receive sunlight in late morning and early afternoon. The day measurements done was cloudy, so as you see it did not increase until ~11am, then the sunlight came until ~2pm.

PPFD on 20.04.2020

PPFD on 20.04.2020

Based on the PPFD chart, I derive an hourly light integral chart (since day is too long for this short experiment). This is similar to DLI but computed for hours. You can also see the sum value in the legend, which is actually the DLI value in mmol so it should be divided by 1000. So DLI above would be 5.3 mol/m2/day. In the night, PPFD is close to zero, so it is not important I am not using the whole 24h of the day.

Hourly Light Integral on 20.04.2020

Hourly Light Integral on 20.04.2020

Two extra measurements

Below is the spectrum of sunlight at 12:59 on 20.04.2020:

Sunlight Spectrum at 12:59 on on 20.04.2020

Sunlight Spectrum at 12:59 on on 20.04.2020

PPFD for this spectrum above is 622 umol/m2/s.

Below is the spectrum of IKEA LED light (I think 8W) for cultivation (it came with BITTERGURKA plant holder):

Spectrum of IKEA LED light for cultivation

Spectrum of IKEA LED light for cultivation

PPFD for this LED bulb is 564 umol/m2/s.

For comparison, normal indoor light levels with artificial lights alone is around 5 umol/m2/s. It is surprising (of course not totally surprising because it is sold as a plant cultivation light) to see this small IKEA LED bulb gives light like sun to its small plant holder, so it seems it is small but quite a successful product.

Is this enough for plants ?

Plants like tomatoes, pepper (capsicum) and cucumber requires a lot of light, and it is written in various places in the internet that this is in the range of 20-30 mol/m2/day (DLI). This looks like difficult if you do not have a terrace where you can see the sun throughout the day.

The light requirement for leafy greens (lettuce etc.) and microgreens are less than above. Maybe around 15 for greens and 10 mol/m2/day for microgreens.

Shade plants like orchids are obviously a good choice for indoor, they require only around 5 mol/m2/day.

What could be wrong or missing in this experiment ?

  • I did not validate my PPFD calculation since I could not find any example calculating PPFD from spectral data. However, it looks correct based on sample PPFD values I have seen on the internet.

  • My spectrometer is only factory calibrated. I actually do not know how accurate its absolute measurements are. I believe they are not very wrong but might have small errors.

  • The ambient measurement of the spectrometer is said to have “Approximated cosine receptor sensitivity”. I placed the spectrometer on the floor and the sunlight is naturally coming at an angle. I am not sure to what extent this would effect the measurements.

  • I should actually repeat this experiment in a sunny summer day to see the maximum PPFD.