If you would like to contribute code to the VMAF repository, you can do so through GitHub by forking the repository and sending a pull request. When submitting code, please make every effort to follow existing conventions and style in order to keep the code as readable as possible.
By contributing your code, you agree to license your contribution under the terms of the BSD+Patent. Your contributions should also include the following header:
/**
* Copyright 2016-2020 [the original author or authors].
*
* Licensed under the BSD+Patent License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSDplusPatent
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
There are many ways you can contribute, and no contribution is too small. To name a few:
- Submitting a bugfix
- Improving documentation
- Making the code run on a new platform
- Robustifying the build system
- Improving the CI loop
- Improving code coverage by adding tests
- Optimizing the speed
- Implementing a well-known quality metric
- Implementing a custom VMAF model for a specific use case
This section focuses on algorithmic contribution, which cover two main use cases:
- Implementing a well-known quality metric that can be found in the literature
- Implementing a custom VMAF model, using new elementary features and trained on a specific dataset
For both cases, one can follow the procedure below:
- First, implement the feature extractor(s), by subclassing the
FeatureExtractor
Python class. A newFeatureExtractor
class created could be either 1) native Python implementation, or 2) calling a subprocess implemented in a different language (in C or in Matlab, for example). - Second, implement the quality runner, by:
- creating a new
QualityRunner
class as a thin wrapper around the newFeatureExtractor
created, or - using the established
VmafQualityRunner
class but training a custom VMAF model.
- creating a new
For the concepts of FeatureExtractor
, QualityRunner
and VmafQualityRunner
, please refer to the Core Classes section of the VMAF Python library documentation.
For algorithmic contribution, for a clean organization of the repo, it is advised to submit new files under directory prefixed with third_party/[orginization]
. For example, for a new model trained, it should go under model/third_party/[organization]/
. As another example, the PSNR-HVS feature extractor code sits under libvmaf/src/feature/third_party/xiph/
.
To create a subclass of FeatureExtractor
in native Python code, a minimalist example to follow is the PypsnrFeatureExtractor
class ("Py-PSNR", see the code diff). The following steps discuss the implementation strategy.
- Create a subclass of the
FeatureExtractor
. Make sure to specify theTYPE
,VERSION
andATOM_FEATURES
, which play a role in caching the features extracted inResultStore
. Optionally, one can specify aDERIVED_ATOM_FEATURES
field (refer to the_post_process_result()
method section for more details). - Implement the
_generate_result()
method, which is responsible for the heavy-lifting feature calculation. This method is called by the_run_on_asset()
method in the parentExecutor
class, which preprocesses the reference and distorted videos to prepare them in a proper YUV format. It calls twoYuvReader
to read the video frame-by-frame, and run computations on them. The result is written to thelog_file_path
file path. - Implement the
_get_feature_scores()
method, which parses the result from thelog_file_path
and put it in a dictionary to prepare for a newResult
object. - Optionally, implement the
_post_process_result()
method to compute theDERIVED_ATOM_FEATURES
from theATOM_FEATURES
. Refer to the Python Calling Matlab section for a specific example. - Create test cases to lock the numerical results.
- Notice that
FeatureExtractor
allows one to pass in optional parameters that has an effect on the numerical result. This is demonstrated by themax_db
parameter inPypsnrFeatureExtractor
(see this test case for an example use case). By default, there is a bit depth-dependent maximum PSNR value (see here for the motivation behind), but themax_db
parameter specified in theoptional_dict
input allows one to specify a maximum value.
Very often the feature extractor implementation is in the C library libvmaf
. In this case we simply create a thin Python FeatureExtractor
subclass to call the vmaf
command line executable. For more information on implementing a new feature extractor in libvmaf
, refer to this section. An example to follow is the PSNR-HVS feature extractor (see the code diff). The following steps discuss the implementation strategy.
- Add a new feature extractor implementation
vmaf_fex_psnr_hvs
in filelibvmaf/src/feature/third_party/xiph/psnr_hvs.c
. It is recommended to put the code under directorythird_party/[org]
. - In
libvmaf/src/feature/feature_extractor.c
:- Declare the new feature extractor as
extern
:extern VmafFeatureExtractor vmaf_fex_psnr_hvs;
- Add the new feature extractor to the
feature_extractor_list
:static VmafFeatureExtractor *feature_extractor_list[] = { ... &vmaf_fex_psnr_hvs, ... };
- Declare the new feature extractor as
- In
libvmaf/src/meson.build
, add the newpsnr_hvs.c
file to thelibvmaf_feature_sources
list:libvmaf_feature_sources = [ ... feature_src_dir + 'third_party/xiph/psnr_hvs.c', ... ]
- Create a Python wrapper class
PsnrhvsFeatureExtractor
inpython/vmaf/third_party/xiph/vmafexec_feature_extractor.py
(Note: you also need to makevmaf.third_party.xiph
a Python package by adding the__init__.py
files in corresponding directories.) - Add a test case for
PsnrhvsFeatureExtractor
inpython/test/third_party/xiph/vmafexec_feature_extractor_test.py
to lock the numerical values.
Oftentimes for a well-known quality metric, its Matlab implementation already exists. The VMAF Python library allows directly plugging in the Matlab code by creating a thin Python MatlabFeatureExtractor
subclass to call the Matlab script. An example to follow is the STRRED feature extractor (see implementation and test case). The following steps discuss the implementation strategy.
- First, Matlab must be pre-installed and its path specified in the
MATLAB_PATH
field in thepython/vmaf/externals.py
file. If not, a user will be prompt with the installation instructions. - Create a subclass of the
MatlabFeatureExtractor
. Make sure to specify theTYPE
,VERSION
andATOM_FEATURES
, which play a role in caching the features extracted inResultStore
. Optionally, one can specify aDERIVED_ATOM_FEATURES
field (refer to the_post_process_result()
method section for more details). - Implement the
_generate_result()
method, which is responsible for calling the Matlab command line to output the result to the file atlog_file_path
. - Implement the
_get_feature_scores()
method, which parses the result from thelog_file_path
and put it in a dictionary to prepare for a newResult
object. In the case of theStrredFeatureExtractor
, the default method provided by theFeatureExtractor
superclass can be directly used as the Matlab script's data format is compatible with it, hence the implementation is skipped. But in general, this methods needs to be implemented. - Optionally, implement the
_post_process_result()
method to compute theDERIVED_ATOM_FEATURES
from theATOM_FEATURES
. In the case of STRRED, thestrred
feature can be derived from thesrred
andtrred
features via simple computation:Therefore, we define thestrred = srred * trred
strred
feature as "derived" and skip the caching process. - Create test cases to lock the numerical results.
For the use case of implementing a well-known quality metric, after the feature extractor is created, the job is almost done. But to run tests and scripts uniformly, we need to create a thin wrapper of the QualityRunner
subclass around the new FeatureExtractor
already created. A good example of this is the SsimQualityRunner
class (see code). One simply needs to create a subclass from QualityRunnerFromFeatureExtractor
, and override the _get_feature_extractor_class()
and _get_feature_key_for_score()
methods.
For the use case of implementing a custom VMAF model, one basically follows the two-step approach of first extracting quality-inducing features, and then using a machine-learning regressor to fuse the features and align the final result with subjective scores. The first step is covered by the FeatureExtractor
subclasses; the second step is done through the TrainTestModel
subclasses.
The default VMAF model has been using the LibsvmNusvrTrainTestModel
class for training (via specifying the model_type
field in the model parameter file, see the next section for more details). To use a different regressor, one needs to first create a new TrainTestModel
class. One minimum example to follow is the Logistic5PLRegressionTrainTestModel
(see code diff). The following steps discuss the implementation strategy.
- Create a new class by subclassing
TrainTestModel
andRegressorMixin
. Specify theTYPE
andVERSION
fields. TheTYPE
is to be specified by themodel_type
field in the model parameter file. - Implement the
_train()
method, which fits the model with the input training data (preprocessed to be a 2D-array) and model parameters. - Implement the
_predict()
method, which takes the fitted model and the input data (preprocess to be a 2D-array) and generate the predicted score. - Optionally override housekeeping functions such as
_to_file()
,_delete()
,_from_info_loaded()
when needed.
Once the FeatureExtractor
and TrainTestModel
classes are ready, the actually training of a VMAF model against a dataset of subjective scores can be initiated by calling the run_vmaf_training
script. Detailed description of how to use the script can be found in the Train a New Model section of VMAF Python Library documentation.
Notice that the current run_vmaf_training
implementation does not work with FeatureExtractors
with a custom input parameter (e.g. the max_db
of PypsnrFeatureExtractor
). A workaround of this limitation is to create a subclass of the feature extractor with the hard-coded parameter (by overriding the _custom_init()
method). Refer to this code and this test for an example.