Changelog
All notable changes to this project will be documented in this file.
[0.7.3] 2025-04-12
Added
- New package submodule has been defined;
moviefy
.moviefy
contains two functions,normalize_image_sizes()
andmoviefy_images()
. These functions allow you to convert a directory of images into a "movie" or video-like mp4 file. This is useful for applying video-only manipulations (such as point-light display, or temporal manipulations) to static images.moviefy_images()
creates 'movies' by repeating frames and blending frames together to create a transitioning sequence of images that can be operated on like a video. The number of frame repeats versus transitional blended frames is determined by the input parametersfps
,repeat_duration
, andblended_frames_prop
. moviefy_images()
requires its input images to all be of the same size, shape and format.normalize_image_sizes()
provides two methods of normalizing the image sizes; NORMALIZE_IMAGES_CROP and NORMALIZE_IMAGES_PAD. These methods will match all of the images sizes to the smallest, and largest image size contained ininput_dir
, respectively.
Changed
- Package import method has been reworked. Previously, users would have to import pyfame.core to access the core functions, now .core by default is exposed with a top-level import
import pyfame
. - Specific commonly used .utils components are also exposed with a top-level import, including
predefined_constants
,landmarks
, andtiming_functions
.
Removed
[0.7.2] 2025-03-10
Added
- A full pytest test suite has been implemented, covering all of the main package functionality found in pyfame.core. The test suite automatically disables file logging when running tests (via conftest.py setting an env variable) as to not bloat up the log files.
exceptions.py
has been implemented, containing several custom exceptions aimed at handling unrecognized file extensions, and image file IO errors.
Changed
- Pyfames package structure has been completely reworked into a more readable and modular format. Previously, all of the packages main functions were in a top level file pyfame.py. Pyfame's package structure is now as follows:
- The top level package pyfame has two submodules
.core
and.utils
.core
contains the packages main functions, which have been divied up intoanalysis.py
,coloring.py
,occlusion.py
,point_light_display.py
,scrambling.py
andtemporal_transforms.py
.- all of the main functions are accessible via 'import pyfame.core'
.utils
contains the packages utility functions, predefined landmark regions and global constants all previously found in a top levelpyfame_utils.py
.utils
has been divied up intodisplay_options.py
,landmarks.py
,predefined_constants.py
andtiming_functions.py
- all of the utility funtions are accessible via 'import pyfame.utils'
- The top level package pyfame has two submodules
setup_logging()
has been moved to its own filesetup_logging.py
to avoid circular import errors. Additionally, the function now checks for an os.environ variablePYTEST_RUNNING
prior to setting up the logging, in order to avoid writing file logs while running the test suite.- Testing exposed several bugs with input parameters being read in as an uncooperative integer type. There were multiple cases of integer input params being read in as int64 rather than uint8 as cv2 expects, which did not raise a TypeError but resulted in corrupt file outputs. This has been patched in
mask_face_region()
,occlude_face_region()
, andgenerate_point_light_display()
.
Removed
[0.7.1] 2025-02-27
Added
- Display functions have been implemented for all of the predefined constants, defined within pyfame_utils.py. Users can now simply call
display_shuffle_method_options()
for example, and the constants names and literal values will be printed to the terminal. - Normalised gaussian timing function has finally been implemented properly.
Changed
- Function Docstrings and error messages have been made more comprehensive, and now reference display functions in pyfame_utils when encountering ValueErrors for input parameters.
- Bug fixes to several parameter error checks implemented with parameter file-logging.
Removed
[0.7.0] 2025-02-16
Added
- New function
generate_shuffled_block_array()
has been implemented. This function was designed to be used alongsideshuffle_frame_order()
in order to abstract the block ordering from the user.generate_shuffled_block_array()
takes in a file_path, shuffle_method and block_duration (in milliseconds) and returns a tuple of (block_order, block_size). This output can now directly be fed as an input parameter toshuffle_frame_order()
, andshuffle_frame_order()
will now callgenerate_shuffled_block_array()
internally if no block_order is provided. - Several new frame shuffling methods have been added including random sampling with replacement, left and right cyclic shift, palindrome shuffling and frame interleaving.
- All functions now make use of logging. Standard logs can be found at PyFAME/logs/app.log. This file will be cycled once it reaches 5mb, and the last 5 log files will be stored in the logs folder. Error logs can be found at PyFAME/logs/error.log. This file will be rotated in a similar manner to the standard log. All functions now log their input parameters, various status updates on execution, and all parameter or execution errors that are raised.
- Logging configurations can be found in the PyFAME/config folder in
log_config.yaml
Changed
- Several internal changes to
shuffle_frame_order()
. Many of the array-randomizing functionalities have been rescoped togenerate_shuffled_block_array()
. - Shuffle_methods have been given a new naming scheme, all of the constant variable names follow the following format: FRAME_SHUFFLE_{method name}.
- Bug fixes to
get_optical_flow()
, specifically the dense optical flow implementation a bug fix was required over the writing of flow vectors to a csv file. Extract_face_color_means()
can now handle static images, on top of video files.
Removed
[0.6.7] - 2025-01-18
Added
- Function
shuffle_frame_order()
can now take an input parameterblock_order
, a list of integers. Whenblock_order
is provided,block_size
will be automatically computed based on the length of the block order list. For example, given a block order of [1,0,3,2,4,5], the function will computeblock_size
by taking the total frame count and dividing it by the length of the list.
Changed
- Function
get_optical_flow()
now takes a parameteroptical_flow_type
. The function now includes the option to compute Farneback's dense optical flow on top of Lucas-Kanade sparse optical flow. Several other Farneback control parameters have been added to the function, but have all been defined with defaults and typical values in the function documentation. - Furthermore, due to the sheer size of outputted csv files from
get_optical_flow()
, a new parametercsv_sample_freq
has been added. This value (given in milliseconds) determines how frequently the function will write out optical flow vectors to the outputted csv file. This is implemented using a rolling time window, and comparing the current video timestamp with its value at each iteration of the running loop.
Removed
[0.6.6] - 2025-01-11
Added
- New function
shuffle_frame_order()
has been implemented. The function has two running modes:SHUFFLE_FRAME_ORDER
andREVERSE_FRAME_ORDER
. Frames are read from the input video and stored in blocks, who's size is determined by input parameterblock_size
. Given an input video running at 30 fps, the defaultblock_size
of 30 will shuffle the order of roughly 1 second segments of the video file. The output order of the frame blocks is determined randomly. Users may pass arand_seed
to seed the rng for reproducable results.
Changed
Removed
[0.6.5] - 2025-01-02
Added
- Function
point_light_display()
has been further expanded. Now the function can display point displacement history, which can be drawn on the output file in one of two methods. Displacement history can be toggled on and off using the input parametershow_history
.SHOW_HISTORY_ORIGIN
will draw the displacement vector for each point relative to their original positions in frame 1.SHOW_HISTORY_RELATIVE
will draw each points path history, displaying each path segment for a set amount of time given by input parameterhistory_window_msec
.
Changed
- Previously, removing points to satisfy the
point_density
was done purely randomly. However this caused issues abstracting the shapes of the landmark regions, making them difficult to identify. Point removal has been reimplemented using a normal gaussian distribution to control which points are removed and which are retained. As a result, even at lower densities the points stay clustered as to maintain the shape of the landmark they are tracking. - Point size and color were previously hard-coded values. New parameters
point_color
,point_radius
, andhistory_color
allow the user to customize how the functions outputs appear.
Removed
[0.6.4] - 2024-12-30
Added
- New function
point_light_display()
has been implemented. The function utilises the 478 landmark points tracked by the mediapipe FaceMesh to generate a point-light display of the face. The default functionality displays all 478 landmark points. However, users may provide landmark sets or predefined landmark sets frompyfameutils
to the input parameterlandmarks_regions
to further specify which regions or landmarks will be included in the functions output. - Users may wish to manipulate the number of points without affecting the overall shape of the landmark regions. In order to do so, users may provide a floating point density to the input parameter
point_density
.
Changed
Removed
[0.6.3] - 2024-12-22
Added
- Expanded functionality for function
get_optical_flow()
. Now beyond outputing the visualised optical flow vectors, the function will output a csv containing the timestamp, previous and current (x,y) positions, vector magnitude, vector angle, status and error rate for each tracked point at every frame.
Changed
get_optical_flow()
has now been fully parameterized. The Lucas-Kanade optical flow control parameters were previously hard-coded values, but are now available to be passed as input parameters with predefined defaults. New parameters includemax_corners
,corner_quality_lvl
,min_corner_distance
,win_size
,max_pyr_lvl
,max_lk_iter
andlk_accuracy_thresh
.- Both
point_color
andvector_color
are now available as input parameters.
Removed
[0.6.2] - 2024-12-17
Added
- New function
get_optical_flow()
has been implemented. The function makes use of the Shi-Tomasi corners algorithm to find good points to track, and passes these points on to the Lucas-Kanade sparse optical flow algorithm. At each new frame, the function draws a point over the current positions of the good points list, then using inter-frame movement draws motion vector histories of each point overtop of the input file. - Alternatively to using the Shi-Tomasi good corners algorithm, the user may provide a set of FaceMesh landmark id's to track within the input parameter
landmarks_to_track
.
Changed
Removed
[0.6.1] - 2024-12-09
Added
Changed
- Bug causing grid-scramble order to be recomputed midway through file processing has been fixed.
- Bug causing jittery movement of facial landmarks when using
LANDMARK_SCRAMBLE
with video files has been fixed.
Removed
[0.6.0] - 2024-12-07
Added
- Function
facial_scramble()
now takes an input parameterscramble_method
. Three new scrambling methods have been defined withinpyfameutils
. These includeLOW_LEVEL_GRID_SCRAMBLE
,HIGH_LEVEL_GRID_SCRAMBLE
andLANDMARK_SCRAMBLE
. - Landmark based scrambling is a new addition to the function. It takes the eyes, eyebrows, nose and mouth, and randomly swaps their positions and orientation. In order to provide seamless gaps between the underlying face and the manipulated landmark regions, the landmarks are cut out and stored, then the holes in the image are filled using the Telea image inpainting algorithm. Telea inpainting fills the image holes with a smooth gradient sampled from nearest neighbouring pixels.
- In order to seamlessly paste the facial landmarks back onto the face, cv2's SeamlessClone() method was used to ensure proper blending of landmark edges into the facial skin tone.
Changed
- Grid-based scrambling has been subdivided into
LOW_LEVEL_GRID_SCRAMBLE
andHIGH_LEVEL_GRID_SCRAMBLE
. High level grid scrambling will function the same as previously implemented, with fully random shuffling of grid squares. Low level grid scrambling will make use of a new parameterGRID_SCRAMBLE_THRESHOLD
; which defines the max x or y distance that a particular grid square can be moved from its original position. - In order to reduce computation time from recomputing grid-square positions at each frame, the shuffled grid order is now precomputed prior to the functions main running loop. Beyond the grid-shuffle order, both grid-shuffling methods are implemented essentially the same. So precomputing the shuffle order allows for the removal of many lines of duplicate code.
- Many papers performing these types of facial scrambling/shuffling operate over grayscale images and video. As such
facial_scramble
now includes input parameterout_grayscale
in order to toggle grayscale and color outputs.
Removed
[0.5.9] - 2024-12-02
Added
- New function
facial_scramble
has been implemented. This function currently can perform grid-based shuffling of the face. Input parametergrid_square_size
specifies the square dimensions (in pixels) of each grid square, and is used to compute the optimal grid arrangement to encapsulate the entire face with minimal background inclusions. - The grid shuffle order is computed randomly using NumPy's
default_rng()
. A random seed may be passed as an input parameter to ensure reproducable outputs.
Changed
Removed
[0.5.8] - 2024-11-24
Added
- Bug fixes for
apply_noise()
. apply_noise()
has been further expanded to include masking capabilities, and is now compatible with all predefined mask variables available inpyfameutils
.
Changed
Removed
- Removed
random
as a dependency; all random operations are now computed using NumPy's random generator class.
[0.5.7] - 2024-11-20
Added
- New function
apply_noise()
has been implemented. This function provides three noise operations to select from; 'pixelate', 'gaussian' and 'salt and pepper'. apply_noise()
provides a variety of customization options such as specifying the noise probability, mean and standard deviation of the gaussian curve to be sampled from, as well as a random seed to be passed to the numpy random number generator.- An expanded set of masking options has been added to pyfameutils.MASK_OPTIONS for use with
mask_face_region()
.mask_face_region
now also allows the user to specify the background color of output files via a BGR integer color code.
Changed
- Previous masking options FACE_OVAL and FACE_OVAL_TIGHT have been removed and replaced by the singular FACE_OVAL_MASK in hopes to alleviate any user confusion between the two options previously.
- PsyFace has been officially renamed to PyFAME: the Python Facial Analysis and Manipulation Environment.
Removed
[0.5.6] - 2024-11-12
Added
- Bug fixes for last major feature update (v0.5.5).
CHIN_PATH
has been added as a predefined path for use with all facial manipulation functions.extract_color_channel_means
has been renamed asextract_face_color_means
. The function now will not only output full-facial means, but also regional color means in the cheek, nose and chin areas for all colour spaces.
Changed
- The Hemi-face family of landmark paths have been converted to standard paths, and no longer require in-place computation.
occlude_face_region
's implementation of bar-style occlusion has been reworked, such that now the occluding bar will track correctly with the position of the face and axis of the head (the occluding bar no longer remains paralell to the horizontal axis).face_color_shift
,face_saturation_shift
andface_brightness_shift
now only take list[list[tuple]] for input parameterlandmark_regions
. This massively reduces the ammount of duplicate code previously divided among if-else statements based on what was passed tolandmark_regions
.
Removed
[0.5.3 - 0.5.5] - 2024-10-16
Added
- Major feature updates to all facial manipulation functions.
face_color_shift
,face_saturation_shift
,face_brightness_shift
andblur_face_region
now are all compatible with timing functions, and every predefined landmark region defined withinpsyfaceutils.py
. - Some of the landmark paths have been redefined as placeholders, as they either need to be calculated in place (hemi-face regions) or require a different method to draw the landmark polygons (Cheek landmark regions form concave polygons).
FACE_SKIN_PATH
constant has been defined in order to provided easier access to facial skin colouring, leaving the lips and eyes untouched. For similar ease of use reasons, other commonly used grouped regions have been defined, includingCHEEKS_PATH
andCHEEKS_NOSE_PATH
for use in facial "blushing".
Changed
Removed
- Bad practice global variable declarations have been removed entirely.
[0.5.2] - 2024-10-09
Added
Occlude_face_region
can now perform vertical and horizontal hemi-face occlusion. Hemi-face masks rely on the facial screen coords, thus they cannot be precomputed. However, predefined placeholder constantsHEMI_FACE_TOP
,HEMI_FACE_BOTTOM
,HEMI_FACE_LEFT
, andHEMI_FACE_RIGHT
have been defined and can still be passed inlandmarks_to_occlude
as any of the other predefined landmark paths can.- Helper function
compute_line_intersection
has been created and can be found inpsyfaceutils.py
. - Additional masking option
EYES_NOSE_MOUTH_MASK
has been added tomask_face_region
.
Changed
Removed
- Predefined landmark paths
UPPER_FACE_PATH
andLOWER_FACE_PATH
have been removed, and replaced withHEMI_FACE_TOP
, andHEMI_FACE_BOTTOM
respectively.
[0.5.1] - 2024-10-03
Added
Blur_face_region
provides dynamic facial blurring functionality with several blurring methods (average, gaussian, median) over user-specified facial regions.- Added horizontal hemi-face occlusion,
UPPER_FACE_PATH
andLOWER_FACE_PATH
constants can be found in psyfaceutils.py.
Changed
Face_luminance_shift
has been replaced withface_brightness_shift
.Face_brightness_shift
will now take an integer shift value in the range [-255, 255], with -255 and 255 representing pure black and white respectively.
Removed
Face_luminance_shift
has been removed due to buggy behaviour when manipulating image luminance.
[0.5.0] - 2024-09-24
Added
- Package documentation is now built with MKDocs
Face_saturation_shift
andFace_luminance_shift
are now standalone functions, where previously saturation and luma parameters were passed to the Face_color_shift function.- Github.io hosting for documentation page, as well as refactored github landing page and readme.md.
- License.txt added to root project structure.
Changed
Shift_color_temp
was refactored to be a nested function withinFace_color_shift
, saturation and luminance shifting were relocated to their own specific functions.- Floodfilling operation involved with foreground-background seperation had some buggy behaviour if there was any discontinuity in the background. An intermediate step was added where prior to floodfilling, the thresholded image is padded with a 10 pixel border, which is removed after the floodfill. This border ensures background continuity when performing the floodfill operation.
- Parameters
max_color_shift
andmax_sat_shift
are now renamed toshift_magnitude
.
Removed
- Sphinx and readthedocs project files and dependencies
[0.4.2] - 2024-08-24
Added
- Sphinx dependency for autodocumentation.
- Rst files defining the documentation build.
Changed
- Updated readme.md with examples, licenses and link to documentation page
Removed
[0.4.1] - 2024-08-18
Added
Changed
- v0.4.1 bug fixes for processing directories of mixed file types (images and videos).
Removed
[0.4.0] - 2024-08-17
Added
Changed
- v0.4 Refactored all methods; moved repetative frame operations to nested functions for increased readability.
- Fixed buggy behaviour when working with still images over all methods. On top of video formats .MP4 and .MOV, you can now perform facial masking, occlusion and colour shifting over image formats .jpg, .jpeg, .png, and .bmp.
- Increased error handling; methods should now be able to process large directories of mixed file formats efficiently in a single call.
Removed
[0.3.1] - 2024-08-11
Added
- v0.3.1 Support for nose masking and occluding
Changed
- Added bar-style occlusion options to occlude_face_region(). You can now perform bar-style occlusion on the eyes, nose and mouth regions.
Removed
[0.3.0] - 2024-08-02
Added
- v0.3 occlude_face_region()
Changed
- Redefined the naming convention used for constants in utils.py
Removed
[0.2.2] - 2024-07-31
Added
Changed
- Changed mp4 video codec from h264 to cv2 supported mp4v.
- Mask_face_region and face_color_shift now take confidence parameters for the underlying mediapipe face landmarker model.
- Implemented otsu thresholding to isolate foreground to use as a mask. This foreground mask ensures that no background artifacts are present in the facial color shifting, or facial masking.
- Added documentation for new function parameters.
Removed
[0.2.1] - 2024-07-24
Added
- v0.2.1 transcode_video_to_mp4()
Changed
- All functions will work by default with .mp4 and .mov video files. If an older container is being used, see transcode_video_to_mp4 to convert video codecs.
- Bug fixes with facial mask in face_color_shift; removed background artifacts present in the masked facial region.
Removed
- Removed dependancy ffprobe-python.
[0.2.0] - 2024-07-21
Added
- v0.2 added dependancy ffprobe-python.
Changed
- Added input file codec sniffing, output video files will now match input type for mask_face_region and face_color_shift.
Removed
[0.1.1] - 2024-07-20
Added
Changed
- Minor bug fix for negative saturation shift.
Removed
[0.1.0] - 2024-07-17
Added
- v0.1 mask_face_region()
- v0.1 extract_color_channel_means()
- v0.1 face_color_shift()
- v0.1 shift_color_temp()
Changed
- Updated documentation and type hints for all package functions.
- Vectorized color shifting operations in shift_color_temp, massively reducing time costs.
- Restructured package into src, data and testing folders.
- Moved constants and helper functions into utils.py.