Basic Usage
Installation
Install pyrCRT through pip, preferably in a virtual environment:
$ pip install pyCRT
Reading video files
PyCRT’s main user interface is its pyrCRT.RCRT
class, which
represents a single measurement. To calculate the rCRT from a video file, for
example, the following code is all that is strictly necessary:
from pyCRT import PCRT
# Path to the video in the file system
filePath = "video.wmv"
pcrt = PCRT.fromVideoFile(filePath)
At this point a window with video playback will appear. In order to select a region of interest (ROI), press the space bar at any moment and the video will pause, allowing you to drag a square around the desired ROI.
Press the space bar again to confirm the selection and the video will resume.
You’ll notice a 4 elements tuple printed on the terminal after confirming the
ROI. This tuple specifies the ROI, and can be passed to
RCRT.fromVideoFile()
method via the roi
keyword argument to
avoid having to manually select the ROI next time.
If the ROI is already specified via the keyword argument, you can disable the exhibition of the video playback, which speeds up computation:
from pyCRT import PCRT
roi = (220, 170, 224, 145)
filePath = "video.wmv"
pcrt = PCRT.fromVideoFile(filePath, roi=roi, displayVideo=False)
After the video ends, the window closes automatically and pyrCRT attempts to
calculate the rCRT. The rCRT and its 0.95 confidence interval uncertainty are
attributes of the rCRT instance created through the
RCRT.fromVideoFile()
method, which is now stored by the rcrt
variable:
>>> print(pcrt)
1.68±3.27%
>>> print(pcrt.pCRT) # the second element is the absolute uncertainty
(1.6790549329488764, 0.054861676836145)
To reduce the video processing load on slower machines or with high-resolution
videos, you can set the rescaleFactor
keyword argument to a number between
0 and 1. Of course, you can also upscale the video likewise.
Reading from a video camera
Calculating the pCRT from the live feed of a video camera (such as a webcam)
is also simple. Use RCRT.fromCaptureDevice()
:
from pyCRT import PCRT
captureDevice = 0
pcrt = PCRT.fromCaptureDevice(captureDevice)
This will open a window with video playback from the specified
captureDevice
. Each connected capture device recognized by OpenCV
corresponds to a different integer. If you have a single webcam connected, it
will most likely correspond to 0. You can also list and use the first
available device with pyrCRT.videoReading.listCaptureDevices()
:
from pyCRT.videoReading import listCaptureDevices
captureDevice = listCaptureDevices()[0]
Most options available as keyword arguments for RCRT.fromVideoFile()
are also valid for RCRT.fromCaptureDevice()
, except for
displayVideo
, as the user is required to press the q key to terminate
the video playback and enable calculation of the pCRT.
RCRT.fromVideoFile()
’s exclusive options are cameraResolution
,
recordingPath
, codecFourcc
and recordingFps
:
from pyCRT import PCRT
captureDevice = 0
roi = (220, 170, 224, 145)
cameraResolution = (640, 480)
recordingPath = "recording.mp4"
codecFourcc = "mp4v"
recordingFps = 30.0
pcrt = PCRT.fromCaptureDevice(
captureDevice,
roi,
cameraResolution,
recordingPath,
codecFourcc,
recordingFps
)
When a recordingPath
is specified, pyCRT will record the camera’s output
on that file using the codec specified by codecFourcc
and with
recordingFps
FPS (“mp4v” and 30.0 by default, respectively). The desired
codec must be identified by its FOURCC string (refer to
www.fourcc.org/codecs.php for a list of codecs and their FOURCC strings).
Note that the file extension on recordingPath
is meaningful and will
correspond to the recording’s container, which must be compatible with the
codec. In doubt, just stick to the defaults.
The cameraResolution
parameter specifies the camera’s resolution. Not
every camera can change its resolution. Also note that this argument is
unrelated the rescaleFactor
presented in the section above –
cameraResolution
changes the camera’s output resolution while
rescaleFactor
rescales each frame after they are read from the camera.
Calculating pCRT from arrays
Often the video reading methods the pyrCRT.RCRT
class provides are
not adequate for a specific problem, and calculating the CRT from average
channel intensities and times arrays obtained from some other method is
necessary. For that, pyrCRT.RCRT
’s default init method is used:
import numpy as np
from pyCRT import PCRT
# Creating some arbitrary arrays for illustration
timeScdsArr = np.linspace(0, 1, 100)
avgIntensArr = np.array([[n, n*2, n*3 for n in np.exp(-timeScdsArr)])
pcrt = PCRT(timeScdsArr, avgIntensArr)
The calculation above will most likely fail on the arbitrary arrays created for the purposes of this demonstration.
The most important arguments of this init method, which can also be used on the other init methods, are presented on the section Configuring the pCRT calculation below.
Plotting graphs
To view graphs of the pixel average intensities inside the ROI for each
channel, and of the curve fits performed on the channel with which the rCRT is
calculated (G, by default), use pyrCRT.RCRT.showAvgIntensPlot()
and
pyrCRT.RCRT.showRCRTPlot()
:
rcrt.showAvgIntensPlot()
rcrt.showRCRTPlot()
This will produce the following two graphs, respectively:
To save these graphs to a file, you can either click on the save button in
the plot display window (like the two windows shown above), or use
pyrCRT.RCRT.saveAvgIntensPlot()
and
pyrCRT.RCRT.saveRCRTPlot()
:
rcrt.saveAvgIntensPlot("avg_intens.png")
rcrt.saveRCRTPlot("g_rcrt_fit.png")
This will save each plot as PNG images in the specified path without showing the plot display window.
Configuring the pCRT calculation
pyCRT offers several options for configuring each step of the pCRT
calculation, most of which are accessible through arguments in the
RCRT
initialization methods (RCRT.fromVideoFile()
,
RCRT.fromCaptureDevice()
and RCRT.__init__()
). Most of these
options have sensible (for a some definition of “sensible”) default values,
so you won’t have to bother specifying each of them every time.
Channel of interest
By channel of interest we mean the channel with respect to which the which
the pCRT will be calculated (see Introduction to pCRT).
This channel can be specified by passing either “r”, “g” or “b” to the
channel
argument:
from pyCRT import PCRT
# Path to the video in the file system
filePath = "video.wmv"
# Uses the G channel by default
pcrt_g = PCRT.fromVideoFile(filePath)
# Uses the R channel
pcrt_r = PCRT.fromVideoFile(filePath, channel="r")
# The graph will be of the R channel
pcrt_r.showAvgIntensPlot()
If no channel is specified, PCRT
will use the G channel by
default, for no other reason than that this channel showed higher
signal-to-noise ratio than the others for our measurements in the forearm.
Time intervals
By default, pyCRT
tries to fit the exponential and polynomial curves (see
Algorithm for calculation if this isn’t clear) from the
instant of absolute maximum value of the channel of interest’s average intensities array, to the end of the array.
The figure below (on the left) exemplifies this behaviour using the same plots as shown on Plotting Graphs. The main figure is a magnified graph of the average intensities for each channel and the inset is the resulting pCRT calculation and plot on the green channel.
This is a rather crude way of determining the interval in which the capillary
refill phenomenon occurs, and it is often necessary to manually specify this
interval. This can be done with the fromTime
and toTime
arguments:
from pyCRT import PCRT
roi = (220, 170, 224, 145)
filePath = "video.wmv"
# Fitting on the interval between 5.5 and 12.5 s
pcrt = PCRT.fromVideoFile(
filePath,
roi=roi,
fromTime=5.5,
toTime=17.5,
sliceMethod="by time"
)
In this example, the interval considered will from 6.5 to 19.5 seconds in the
recording. The parameter sliceMethod
additionally determines the actual
interval that should be used, in the manner illustrated in the figure below.
The figure shows possible values for sliceMethod
and the respective
intervals, in every case using the same fromTime
and toTime
.
If either fromTime
or toTime
are not specified, pyCRT
’s default
behaviour is to use the beginning and the end of the array, respectively. For
example:
# toTime not specified
pcrt = PCRT.fromVideoFile(
filePath, roi=roi, fromTime=5.5, sliceMethod="from local max"
)
This will fit the pCRT function from the instant of the maximum average intensity that occurs after 5.5, to the end of the recording.
Note
The fromTime
and toTime
arguments refer to absolute times,
counting from the start of the recording, which is the X axis on the
Channel Average Intensities graph. The X axis on the Average Intensities
and Fitted Functions graph (wherein the pCRT is presented) is relative to
fromTime
, so it starts at zero at the peak of the channel of
interest’s average intensities.
Initial guesses
PyCRT’s initial guesses for the parameters of the two exponential functions
(of the form A*exp(b*x)+c
) are A, b, c = 1.0, -0.3, 0.0
, and for the
sixth degree polynomial function the initial guesses were 0.0 for every
parameter (see Introduction to pCRT for an explanation
of these functions).
These values worked well for pCRT measurements on the forearm for our specific
setup but will probably be inadequate for other applications. As such, pyCRT
enables specifying the initial guesses for all parameters of each function.
For this, create a dictionary with the initial guesses for every function you
wish to specify and pass it as initialGuesses
, as in the example below:
from pyCRT import PCRT
filePath = "video.wmv"
# The parameters are in order of [A, b, c], for f(x)=A*exp(b*x)+c
expGuesses = [0.8, -0.5, 0.2]
# The parameters are in order of increasing polynomial exponent
polyGuesses = [1.1, 2.3, 5.8, 13.21, 34.55, 89.144]
# This refers to the second exponential function
pcrtExpGuesses = [1.2, -0.1, -0.2]
# The keys in this dictionary must be exactly these
initialGuesses = {
"exponential" : expGuesses,
"polynomial": polyGuesses,
"pCRT": pcrtExpGuesses,
}
pcrt = PCRT.fromVideoFile(filePath, initialGuesses=initialGuesses)
You can also omit one or two functions in the initialGuesses
dictionary to
use their default initial guesses.
Exclusion criteria
The exclusion criteria is the maximum relative uncertainty a pCRT
measurement to be acceptable. When pyCRT calculates an uncertainty grater than
this, it raises a RuntimeError
.
By default the exclusion criteria is arbitrarily defined as 0.12, and a custom
criteria can be specified through the exclusionCriteria
argument:
# This will reject pcrt's with uncertainties greater than 20%
pcrt = PCRT(timeScdsArr, avgIntensArr, exclusionCriteria=0.2)
If you don’t want to use an exclusion criteria, you can set
exclusionCriteria
to an arbitrarily large value, or, better yet, use
numpy.inf.
Critical time
PyCRT’s default behaviour is to use the first peak of polynomial - exponential
function as the critical time (read Introduction to pCRT
if you need an explanation). This can be changed through the criticalTime
and exclusionMethod
arguments.
You can either pass a single value or a list of values as the criticalTime
argument. In the first case, pCRT calculation will be attempted with that
critical time regardless of the results of the exponential and polynomial fit.
When a list of critical values is passed, pyCRT will consider each value as a
candidate critical time, depending on the exclusionMethod
parameter.
PyCRT always calculates the pCRT for each candidate critical time it finds
using the exponential - polynomial method or is provided through the
criticalTime
argument. The exclusionMethod
option determines how pyCRT
chooses which candidate critical time to use. These are its possible values
and their effects:
“first that works” (default): will return the first pCRT and its associated critical time that pass the exclusion criteria. By “first” it is meant the lowest critical time, that is, the first chronologically.
“best fit”: will return the pCRT and associated critical time that gave the lowest uncertainty for the pCRT.
“strict”: will only attempt to calculate the pCRT on the first critical time and fail if the results don’t pass the exclusion criteria.
Attributes of the PCRT object
These are some of the most useful attributes and properties of a PCRT object:
fullTimeScdsArr: The entire array of timestamps, in seconds, extracted from a video file or a capture device, for example. Let’s say this array has size n to contextualize the descriptions below.
channelsAvgIntensArr: The entire array of pixel average intensities, for all 3 channels. This array has dimensions of 3 by n, as there are three values for each timestamp on
fullTimeScdsArr
.channel: The channel that was selected for pCRT calculation. Can be either b, g, or r.
B, G, and R: The arrays of pixel average intensities for the B, G and R channels respectively. Each is an n sized array.
slice: A slice object used for indexing
fullTimeScdsArr
andchannelsAvgIntensArr
. This slice corresponds to the span of time wherein the CRT phenomenon occurs, that is, the section of the aforementioned arrays used for calculating pCRT.fromTime and toTime: The timestamps on
fullTimeScdsArr
corresponding to the initial and final elements indexed by theslice
object above.timeScdsArr: The array of timestamps that correspond to the period wherein the CRT phenomenon occurs. In practice, this is
fullTimeScdsArr
indexed by theslice
object. Note that this array starts at zero, as its values are relative to the start of the CRT phenomenon (that is,fromTime
).avgIntensArr: The array of normalized pixel average intensities of the
channel
channel that correspond to the period wherein the CRT phenomenon occurs. This array has the same size astimeScdsArr
, and these arrays are the ones actually used for calculating pCRT.expTuple, polyTuple and pCRTTuple: The parameters and standard deviations of the exponential and polynomial functions used for calculating the pCRT. The parameters are in the order shown on this section.
pCRT: The pCRT and its uncertainty with a 95% confidence interval.
relativeUncertainty: The relative uncertainty. This is just the uncertainty divided by the pCRT.
criticalTime: The critical time calculated along the pCRT.
All these similarly named attributes can be somewhat confusing at first, so here’s a figure illustrating what each of these attributes represent in the Channel Average Intensities and Average Intensities and Fitted Functions graphs:
Note
The PCRT class wasn’t designed to be modified after it is instantiated. Instead of changing one or more attributes of an already existing PCRT object, it is best to create new PCRT objects for every change. The following snippet of code is a very common and useful pattern:
from pyCRT import PCRT videoPath = "video.wmv" # Using this object just to read from a file pcrt = PCRT.fromVideoFile("video.wmv") # Storing the most important arrays for later use fullTimeScdsArr = pcrt.fullTimeScdsArr channelsAvgIntensArr = pcrt.channelsAvgIntensArr # Testing different options for the same data pcrtRed = PCRT( fullTimeScdsArr, channelsAvgIntensArr, channel="r" ) pcrt2 = PCRT( fullTimeScdsArr, channelsAvgIntensArr, criticalTime=17.8 )
Storing and retrieving measurements
PyCRT natively supports storing pCRT measurements in a npz-compressed file
that is retrievable by pyCRT. For that, use RCRT.save()
and
RCRT.fromArchive()
:
from pyCRT import PCRT
pcrt = PCRT.fromVideoFile("video.wmv")
# Storing the measurement
pcrt.save("pcrt-backup.npz")
# Loading a measurement
pcrt2 = RCRT.fromArchive("pcrt-backup.npz")