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.

_images/ROIselection.jpg

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:

_images/plots.jpg

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.

_images/timeIntervalExample.jpg

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.

_images/sliceMethod.jpg

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 and channelsAvgIntensArr. 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 the slice object above.

  • timeScdsArr: The array of timestamps that correspond to the period wherein the CRT phenomenon occurs. In practice, this is fullTimeScdsArr indexed by the slice 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 as timeScdsArr, 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:

_images/propertiesIllustration.jpg

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")