This is part 6 of 6 of a mini-series of posts that discuss how to manipulate many images at once (instead of individually) using a variety of techniques. Please refer to part 1 of 6 for more information.
This post explores how to batch process images with Python using OpenCV and NumPy. We will resize and apply the sepia tone effect to a group of photos. For resizing, each image is scaled to three (3) different sizes, 75%, 50%, 25%, of the original. The sepia tone effect is achieved with image desaturation and blending using the algorithm discussed in the introductory post.
All code on this page was developed with & tested against Python 3.5 and NumPy 1.10.4.
Batch processing is accomplished by creating a function to select the jpg image files in an input directory, transforming each input image and saving the results to an output directory.
For resizing, each input image is scaled 75%, 50% and 25%, and the three (3) output files are saved. When the sepia tone effect is applied, the result is saved to the specified output directory.
We will be using OpenCV and NumPy. NumPy supports matrix manipulations and does the heavy math when creating soft light blending.
We will resize images based on size percentage of the original image and not only that, but we will create three different sizes (75%, 50%, 25%) as well.
Original Image Scaled to 75%, 50% and 25%
The following Python code snippet scales an input image into three (3) different sizes, 75%, 50% & 25%, and saves them into the output directory. This can be tested by saving the full script and changing the batchInputImageDir and batchOutputImageDir to valid input & output directories on your system.
resizePercentages = [75, 50, 25]    # Percentages to by which to resize input images
# Resize image by given percentages
for resizePercentage in resizePercentages:
                
    batchOutImageFN = os.path.join(batchOutputImageDir, imageName + "_" + str(resizePercentage) + ".jpg")
    ResizeImageByPercentAndSave(img, resizePercentage, batchOutImageFN)
# *****
# ResizeImageByPercentAndSave
#
# Description: Resizes image by specified percentage and saves the result
#
# Parameters:
#    inImage : An OpenCV image
#    inResizePercentage : Percentage by which to resize image as a non negative integer
#    inResizedImageFN : Output path and file name where the result is saved
# *****
def ResizeImageByPercentAndSave(inImage, inResizePercentage, inResizedImageFN):
    
    resizeFraction = inResizePercentage / 100
        
    imgResize = cv2.resize(inImage, (0,0), fx=resizeFraction, fy=resizeFraction)
    
    cv2.imwrite(inResizedImageFN,imgResize)
To resize multiple images at once, we include a function to find & process all jpg image files in the input directory as illustrated in the code snippet below. This can be tested by saving the full script and changing the batchInputImageDir and batchOutputImageDir to valid input & output directories on your system.
batchInputImageDir = os.path.join("..","images","in")       # Input directory where jpg files reside
batchOutputImageDir = os.path.join("..","images","out")     # Output directory where results are saves as jpg image files
resizePercentages = [75, 50, 25]    # Percentages to by which to resize input images
# Iterate through all jpgs in the input directory 
for jpgFile in os.listdir(batchInputImageDir):
    
    if jpgFile.endswith(".jpg"):    # Process jpg files only
    
        # Determine full path and filename
        imageName, imageExt = os.path.splitext(jpgFile)
        batchInImageFN = os.path.join(batchInputImageDir, jpgFile)
        print("Currently processing image: " + batchInImageFN)
        # Open the input image to process
        img = cv2.imread(batchInImageFN)
            
        # Resize image by given percentages
        for resizePercentage in resizePercentages:
                
            batchOutImageFN = os.path.join(batchOutputImageDir, imageName + "_" + str(resizePercentage) + ".jpg")
            ResizeImageByPercentAndSave(img, resizePercentage, batchOutImageFN)
                
Applying the sepia tone effect to an image will give it an old, antique-ish photograph look. As a recap of the discussion of what the sepia tone effect algorithm entails, here is a list of steps to achieve it:
Sepia Tone Effect Applied to Original Image
We will be implementing the soft light blending mode using the W3C equations which specify the functions f(bottom image, top image) and g(bottom image), where the bottom image is the desaturated & blurred original image and the top image is the reddish brown color filled image.
For more information regarding the sepia tone effect algorithm, please refer to the introductory post.
The following Python code snippet applies the sepia tone effect to an input image. All matrix math for soft light blending is through NumPy which is much faster and efficient than if we used a bunch of for loops. Other image manipulations use OpenCV. Please note that OpenCV expects RGB (red, blue, green) tuples in reversed order (BGR).
This can be tested by saving the full script and changing the batchInputImageDir and batchOutputImageDir to valid input & output directories on your system.
# Apply the sepia tone effect
batchOutImageFN = os.path.join(batchOutputImageDir, imageName + "_sepia.jpg")
SepiaToneEffectAndSave(img, batchOutImageFN)
# *****
# SepiaToneEffectAndSave
#
# Description: Applies sepia tone effect to input image and saves the result
#
# Parameters:
#    inImage : An OpenCV image
#    inSepiaImageFN : Output path and file name where the result is saved
# *****
def SepiaToneEffectAndSave(inImage, inSepiaImageFN):
    
    # Desaturate (but needs to be RGB for later operations)
    imgGrey = cv2.cvtColor(inImage,cv2.COLOR_BGR2GRAY)
    imgGrey = cv2.cvtColor(imgGrey,cv2.COLOR_GRAY2RGB)  # Need RGB for matrix math
    
    # Apply a slight blur
    imgSmooth = cv2.GaussianBlur(imgGrey,(5,5),0)
    # Blend the sepia tone color with the greyscale layer using soft light
    imgWidth, imgHeight, imgChannels = imgGrey.shape
    imgSepiaColor = np.zeros((imgWidth,imgHeight,3), np.uint8)
    imgSepiaColor[:,:] = (42,89,226)  # BGR
    
    imgSepia = SoftLight(imgSepiaColor, imgSmooth)
    
    cv2.imwrite(inSepiaImageFN,imgSepia)
    
# *****
# SoftLight
#
# Description: Implements the soft light blending mode as per w3c
# https://en.wikipedia.org/wiki/Blend_modes#Soft_Light
#
# Parameters:
#    inTopImg : Open OpenCV image (top)
#    inBottomImg : Open OpenCV image (bottom)
# *****
def SoftLight(inTopImg,inBottomImg):
    
    # Normalize color values to between 0 and 1
    topImgArray = np.asarray(inTopImg) / 255.0
    bottomImgArray = np.asarray(inBottomImg) / 255.0
    
    softLightImgArray = SoftLightF(topImgArray, bottomImgArray)
    
    # Convert colors back to between 0 to 255
    softLightImgArray = softLightImgArray * 255.0
        
    return softLightImgArray
# *****
# SoftLightF
#
# Description: Implements f(bottom image, top image) portion of w3c soft light blending equation
#
# Parameters:
#    inTopImgArray : Top image as array
#    inBottomImgArray : Bottom image as array
# *****
def SoftLightF(inTopImgArray,inBottomImgArray):
    softLightFArray = np.where(inTopImgArray <= 0.5,inBottomImgArray - ((1 - (2 * inTopImgArray)) * inBottomImgArray * (1 - inBottomImgArray)),inBottomImgArray + (2 * inTopImgArray - 1) * (SoftLightG(inBottomImgArray) - inBottomImgArray))
    return softLightFArray
# *****
# SoftLightG
#
# Description: Implements f(bottom image) portion of w3c soft light blending equation
#
# Parameters:
#    inBottomImgArray : Bottom image as array
# *****
def SoftLightG(inBottomImgArray):
    
    softLightGArray = np.where(inBottomImgArray <= 0.25, ((16 * inBottomImgArray - 12) * inBottomImgArray + 4) * inBottomImgArray, np.sqrt(inBottomImgArray))
    
    return softLightGArray
To apply the sepia tone effect to multiple images at once, we include a function to find & process all jpg image files in the input directory as illustrated in the code snippet below. This can be tested by saving the full script and changing the batchInputImageDir and batchOutputImageDir to valid input & output directories on your system.
batchInputImageDir = os.path.join("..","images","in")       # Input directory where jpg files reside
batchOutputImageDir = os.path.join("..","images","out")     # Output directory where results are saves as jpg image files
resizePercentages = [75, 50, 25]    # Percentages to by which to resize input images
# Iterate through all jpgs in the input directory 
for jpgFile in os.listdir(batchInputImageDir):
    
    if jpgFile.endswith(".jpg"):    # Process jpg files only
    
        # Determine full path and filename
        imageName, imageExt = os.path.splitext(jpgFile)
        batchInImageFN = os.path.join(batchInputImageDir, jpgFile)
        print("Currently processing image: " + batchInImageFN)
        # Open the input image to process
        img = cv2.imread(batchInImageFN)
            
        # Apply the sepia tone effect
        batchOutImageFN = os.path.join(batchOutputImageDir, imageName + "_sepia.jpg")
        SepiaToneEffectAndSave(img, batchOutImageFN)
Our mini-series exploring image batch processing wraps up with this post. Hope you enjoyed it!
More image processing fun!
		                              
	                        This is part 5 of 6 of a mini-series of posts that discuss how to manipulate many images at once (instead of individually) using a variety of techniques. Please refer to part 1 of 6 for more information.
This post explores how to batch process images with Python using a fork of PIL (Python Imaging Library) called Pillow, NumPy and SciPy. We will resize and apply the sepia tone effect to a group of photos. For resizing, each image is scaled to three (3) different sizes, 75%, 50%, 25%, of the original. The sepia tone effect is achieved with image desaturation and blending using the algorithm discussed in the introductory post.
All code on this page was developed with & tested against Python 3.5, Pillow 3.2.0, NumPy 1.10.4 and SciPy 0.17.0.
Batch processing is accomplished by creating a function to select the jpg image files in an input directory, transforming each input image and saving the results to an output directory.
For resizing, each input image is scaled 75%, 50% and 25%, and the three (3) output files are saved. When the sepia tone effect is applied, the result is saved to the specified output directory.
We will be using Pillow (a fork of PIL), NumPy and SciPy. NumPy supports matrix manipulations while SciPy is needed for filtering (ie gaussian blur).
We will resize images based on size percentage of the original image and not only that, but we will create three different sizes (75%, 50%, 25%) as well.
Original Image Scaled to 75%, 50% and 25%
The following Python code snippet scales an input image into three (3) different sizes, 75%, 50% & 25%, and saves them into the output directory. This can be tested by saving the full script and changing the batchInputImageDir and batchOutputImageDir to valid input & output directories on your system.
resizePercentages = [75, 50, 25]    # Percentages to by which to resize input images
# Resize image by given percentages
for resizePercentage in resizePercentages:
                
    batchOutImageFN = os.path.join(batchOutputImageDir, imageName + "_" + str(resizePercentage) + ".jpg")
    ResizeImageByPercentAndSave(img, resizePercentage, batchOutImageFN)
    
# *****
# ResizeImageByPercentAndSave
#
# Description: Resizes image by specified percentage and saves the result
#
# Parameters:
#    inImage :  A PIL image
#    inResizePercentage : Percentage by which to resize image as a non negative integer
#    inResizedImageFN : Output path and file name where the result is saved
# *****
def ResizeImageByPercentAndSave(inImage, inResizePercentage, inResizedImageFN):
    
    # Clone input image
    imgClone  = inImage.copy()
    
    imgWidth, imgHeight = imgClone.size
    resizeHeight = int(inResizePercentage * imgHeight / 100)
    resizeWidth = int(inResizePercentage * imgWidth / 100)
        
    imgClone.resize((resizeWidth,resizeHeight), Image.ANTIALIAS)
    
    imsave(inResizedImageFN, imgClone)
To resize multiple images at once, we include a function to find & process all jpg image files in the input directory as illustrated in the code snippet below. This can be tested by saving the full script and changing the batchInputImageDir and batchOutputImageDir to valid input & output directories on your system.
batchInputImageDir = os.path.join("..","images","in")       # Input directory where jpg files reside
batchOutputImageDir = os.path.join("..","images","out")     # Output directory where results are saves as jpg image files
resizePercentages = [75, 50, 25]    # Percentages to by which to resize input images
# Iterate through all jpgs in the input directory 
for jpgFile in os.listdir(batchInputImageDir):
    
    if jpgFile.endswith(".jpg"):    # Process jpg files only
    
        # Determine full path and filename
        imageName, imageExt = os.path.splitext(jpgFile)
        batchInImageFN = os.path.join(batchInputImageDir, jpgFile)
        # Open the input image to process
        img = Image.open(batchInImageFN)
            
        # Resize image by given percentages
        for resizePercentage in resizePercentages:
                
            batchOutImageFN = os.path.join(batchOutputImageDir, imageName + "_" + str(resizePercentage) + ".jpg")
            ResizeImageByPercentAndSave(img, resizePercentage, batchOutImageFN)
Applying the sepia tone effect to an image will give it an old, antique-ish photograph look. As a recap of the discussion of what the sepia tone effect algorithm entails, here is a list of steps to achieve it:
Sepia Tone Effect Applied to Original Image
We will be implementing the soft light blending mode using the W3C equations which specify the functions f(bottom image, top image) and g(bottom image), where the bottom image is the desaturated & blurred original image and the top image is the reddish brown color filled image.
For more information regarding the sepia tone effect algorithm, please refer to the introductory post.
The following Python code snippet applies the sepia tone effect to an input image. The gaussian filter for blurring is provided by SciPy. Please note that the last value of the tuple for sigma of the gaussian filter is for smoothing color channels. Since we don’t want to smooth colors (resulting in a greyscale looking image), it is 0. All matrix math for soft light blending is through NumPy which is much faster & efficient than if we used a bunch of for loops. Other basic image manipulations use PIL (Pillow).
This can be tested by saving the full script and changing the batchInputImageDir and batchOutputImageDir to valid input & output directories on your system.
# Apply the sepia tone effect
batchOutImageFN = os.path.join(batchOutputImageDir, imageName + "_sepia.jpg")
SepiaToneEffectAndSave(img, batchOutImageFN)
# *****
# SepiaToneEffectAndSave
#
# Description: Applies sepia tone effect to input image and saves the result
#
# Parameters:
#    inImage : A PIL image
#    inSepiaImageFN : Output path and file name where the result is saved
# *****
def SepiaToneEffectAndSave(inImage, inSepiaImageFN):
    
    # Desaturate (but needs to be RGB for later operations)
    imgGrey = inImage.convert('L')
    imgGrey = imgGrey.convert('RGB')
    
    # Apply a slight blur
    imgGreySmooth = filters.gaussian_filter(imgGrey,sigma=[1,1,0]) # Do not smooth color channels - results in grayscale
    
    # Blend the sepia tone color with the greyscale layer using soft light
    imgWidth, imgHeight = imgGrey.size
    imgSepiaColor = Image.new('RGB', (imgWidth, imgHeight), (226, 89, 42, 0))
    
    imgSepiaArray = SoftLight(imgSepiaColor, imgGreySmooth)
    imgSepia = Image.fromarray(imgSepiaArray.astype('uint8'), 'RGB')
    
    imsave(inSepiaImageFN, imgSepia)
# *****
# SoftLight
#
# Description: Implements the soft light blending mode as per w3c
# https://en.wikipedia.org/wiki/Blend_modes#Soft_Light
#
# Parameters:
#    inTopImg : Open PIL image (top)
#    inBottomImg : Open PIL image (bottom)
# *****
def SoftLight(inTopImg,inBottomImg):
    
    # Normalize color values to between 0 and 1
    topImgArray = np.asarray(inTopImg) / 255.0
    bottomImgArray = np.asarray(inBottomImg) / 255.0
    
    softLightImgArray = SoftLightF(topImgArray, bottomImgArray)
    
    # Convert colors back to between 0 to 255
    softLightImgArray = softLightImgArray * 255.0
        
    return softLightImgArray
# *****
# SoftLightF
#
# Description: Implements f(bottom image, top image) portion of w3c soft light blending equation
#
# Parameters:
#    inTopImgArray : Top image as array
#    inBottomImgArray : Bottom image as array
# *****
def SoftLightF(inTopImgArray,inBottomImgArray):
    softLightFArray = np.where(inTopImgArray <= 0.5,inBottomImgArray - ((1 - (2 * inTopImgArray)) * inBottomImgArray * (1 - inBottomImgArray)),inBottomImgArray + (2 * inTopImgArray - 1) * (SoftLightG(inBottomImgArray) - inBottomImgArray))
    return softLightFArray
# *****
# SoftLightG
#
# Description: Implements f(bottom image) portion of w3c soft light blending equation
#
# Parameters:
#    inBottomImgArray : Bottom image as array
# *****
def SoftLightG(inBottomImgArray):
    
    softLightGArray = np.where(inBottomImgArray <= 0.25, ((16 * inBottomImgArray - 12) * inBottomImgArray + 4) * inBottomImgArray, np.sqrt(inBottomImgArray))
    
    return softLightGArray
To apply the sepia tone effect to multiple images at once, we include a function to find & process all jpg image files in the input directory as illustrated in the code snippet below. This can be tested by saving the full script and changing the batchInputImageDir and batchOutputImageDir to valid input & output directories on your system.
batchInputImageDir = os.path.join("..","images","in")       # Input directory where jpg files reside
batchOutputImageDir = os.path.join("..","images","out")     # Output directory where results are saves as jpg image files
resizePercentages = [75, 50, 25]    # Percentages to by which to resize input images
# Iterate through all jpgs in the input directory 
for jpgFile in os.listdir(batchInputImageDir):
    
    if jpgFile.endswith(".jpg"):    # Process jpg files only
    
        # Determine full path and filename
        imageName, imageExt = os.path.splitext(jpgFile)
        batchInImageFN = os.path.join(batchInputImageDir, jpgFile)
        # Open the input image to process
        img = Image.open(batchInImageFN)
                
        # Apply the sepia tone effect
        batchOutImageFN = os.path.join(batchOutputImageDir, imageName + "_sepia.jpg")
        SepiaToneEffectAndSave(img, batchOutImageFN)
            
Our mini-series exploring image batch processing will continue with PIL (Python Imaging Library).
		                              
	                        This is part 4 of 6 of a mini-series of posts that discuss how to manipulate many images at once (instead of individually) using a variety of techniques. Please refer to part 1 of 6 for more information.
This post explores how to batch process images with Python using ImageMagick via the Python Wand library. We will resize and apply the sepia tone effect to a group of photos. For resizing, each image is scaled to three (3) different sizes, 75%, 50%, 25%, of the original. The sepia tone effect is achieved with image desaturation and blending using the algorithm discussed in the introductory post.
All code on this page was developed with & tested against Python 3.5 and Wand 0.4.2.
Batch processing is accomplished by creating a function to select the jpg image files in an input directory, transforming each input image and saving the results to an output directory.
For resizing, each input image is scaled 75%, 50% and 25%, and the three (3) output files are saved. When the sepia tone effect is applied, the result is saved to the specified output directory.
We will be using Wand, a ctypes-based simple ImageMagick binding for Python.
We will resize images based on size percentage of the original image and not only that, but we will create three different sizes (75%, 50%, 25%) as well.
Original Image Scaled to 75%, 50% and 25%
The following Python code snippet scales an input image into three (3) different sizes, 75%, 50% & 25%, and saves them into the output directory. This can be tested by saving the full script and changing the batchInputImageDir and batchOutputImageDir to valid input & output directories on your system.
resizePercentages = [75, 50, 25]    # Percentages to by which to resize input images
with Image(filename=batchInImageFN) as img:
            
       # Resize image by given percentages
       for resizePercentage in resizePercentages:
                
           batchOutImageFN = os.path.join(batchOutputImageDir, imageName + "_" + str(resizePercentage) + ".jpg")
           ResizeImageByPercentAndSave(img, resizePercentage, batchOutImageFN)
# *****
# ResizeImageByPercentAndSave
#
# Description: Resizes image by specified percentage and saves the result
#
# Parameters:
#    inImage : An image opened using Wand
#    inResizePercentage : Percentage by which to resize image as a non negative integer
#    inResizedImageFN : Output path and file name where the result is saved
# *****
def ResizeImageByPercentAndSave(inImage, inResizePercentage, inResizedImageFN):
    
    with inImage.clone() as imgClone:
        
        resizeHeight = int(inResizePercentage * imgClone.height / 100)
        resizeWidth = int(inResizePercentage * imgClone.width / 100)
        
        imgClone.resize(resizeWidth, resizeHeight)
        
        imgClone.save(filename=inResizedImageFN)
To resize multiple images at once, we include a function to find & process all jpg image files in the input directory as illustrated in the code snippet below. This can be tested by saving the full script and changing the batchInputImageDir and batchOutputImageDir to valid input & output directories on your system.
batchInputImageDir = os.path.join("..","images","in")       # Input directory where jpg files reside
batchOutputImageDir = os.path.join("..","images","out")     # Output directory where results are saves as jpg image files
resizePercentages = [75, 50, 25]    # Percentages to by which to resize input images
# Iterate through all jpgs in the input directory 
for jpgFile in os.listdir(batchInputImageDir):
    
    if jpgFile.endswith(".jpg"):    # Process jpg files only
    
        # Determine full path and filename
        imageName, imageExt = os.path.splitext(jpgFile)
        batchInImageFN = os.path.join(batchInputImageDir, jpgFile)
        # Open the input image to process
        with Image(filename=batchInImageFN) as img:
            
            # Resize image by given percentages
            for resizePercentage in resizePercentages:
                
                batchOutImageFN = os.path.join(batchOutputImageDir, imageName + "_" + str(resizePercentage) + ".jpg")
                ResizeImageByPercentAndSave(img, resizePercentage, batchOutImageFN)
Applying the sepia tone effect to an image will give it an old, antique-ish photograph look. As a recap of the discussion of what the sepia tone effect algorithm entails, here is a list of steps to achieve it:
Sepia Tone Effect Applied to Original Image
ImageMagick actually has a built-in function for creating sepia toned images called MagickSepiaToneImage. But for consistency, we will use the same algorithm for all approaches in this series.
For more information regarding the sepia tone effect algorithm, please refer to the introductory post.
The following Python code snippet applies the sepia tone effect to an input image. Soft light blending is implemented with the composite_channel function & its operator parameter specified as ‘soft_light’.
This can be tested by saving the full script and changing the batchInputImageDir and batchOutputImageDir to valid input & output directories on your system.
# Apply the sepia tone effect
batchOutImageFN = os.path.join(batchOutputImageDir, imageName + "_sepia.jpg")
SepiaToneEffectAndSave(img, batchOutImageFN)
# SepiaToneEffectAndSave
#
# Description: Applies sepia tone effect to input image and saves the result
#
# Parameters:
#    inImage : An image opened using Wand
#    inSepiaImageFN : Output path and file name where the result is saved
# *****
def SepiaToneEffectAndSave(inImage, inSepiaImageFN):
    colorStr = '#e2592a'    # Sepia tone effect color
    # Apply the effect on a copy of the input image
    with inImage.clone() as imgClone:
        
        # Convert image to greyscale
        imgClone.type = 'grayscale'
        
        # Apply a slight blur
        imgClone.gaussian_blur(0,1)
        
        # Blend the sepia tone color with the greyscale layer using soft light
        fillColor = Color(colorStr)
        with Image(width=img.width, height=img.height, background=fillColor) as fillImg:
        
            imgClone.composite_channel('default_channels', fillImg, 'soft_light', 0, 0 )
        
        imgClone.save(filename=inSepiaImageFN)
To apply the sepia tone effect to multiple images at once, we include a function to find & process all jpg image files in the input directory as illustrated in the code snippet below. This can be tested by saving the full script and changing the batchInputImageDir and batchOutputImageDir to valid input & output directories on your system.
batchInputImageDir = os.path.join("..","images","in")       # Input directory where jpg files reside
batchOutputImageDir = os.path.join("..","images","out")     # Output directory where results are saves as jpg image files
# Iterate through all jpgs in the input directory 
for jpgFile in os.listdir(batchInputImageDir):
    
    if jpgFile.endswith(".jpg"):    # Process jpg files only
    
        # Determine full path and filename
        imageName, imageExt = os.path.splitext(jpgFile)
        batchInImageFN = os.path.join(batchInputImageDir, jpgFile)
        # Open the input image to process
        with Image(filename=batchInImageFN) as img:
                
            # Apply the sepia tone effect
            batchOutImageFN = os.path.join(batchOutputImageDir, imageName + "_sepia.jpg")
            SepiaToneEffectAndSave(img, batchOutImageFN)
Our mini-series exploring image batch processing will continue with PIL (Python Imaging Library).
		                    	                    	
	                        This is part 6 of 6 of a mini-series of posts that discuss how to manipulate many images at once (instead of individually) using a variety of techniques….
		                    	                    	
	                        This is part 5 of 6 of a mini-series of posts that discuss how to manipulate many images at once (instead of individually) using a variety of techniques….
		                    	                    	
	                        This is part 4 of 6 of a mini-series of posts that discuss how to manipulate many images at once (instead of individually) using a variety of techniques….
		                    	                    	
	                        This is part 3 of 6 of a mini-series of posts that discuss how to manipulate many images at once (instead of individually) using a variety of techniques….