Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for python3 #332

Merged
merged 12 commits into from
Jun 11, 2019
20 changes: 8 additions & 12 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
# Caches
# Caches, build artifacts
.DS_Store
.cache/
*.egg*
.gradle/
.idea/
*.py[cod]
.pypirc
.tox/
.*version
.venv*/
__pycache__/

# Build artifacts
activate
build/

# Code coverage reports
.coverage*
coverage.xml
htmlcov/

*.egg-info
*.pyc
dist/
14 changes: 6 additions & 8 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,22 +1,20 @@
language: python
python:
- "2.7"
- "3.6"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Travis now exercises tests under both 2.7 and 3.6
All tests pass like before this change under 2.7
Under 3.6, a few tests are currently skipped because I'm not sure how to fix them

So end result after this commit should be:

  • 2.7 should behave the same as before
  • 3.6 has probably a few bugs to iron out, no tests should be skipped because of 3.6 before we can call it really ready for python3


before_install:
- sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y
- sudo apt-get update -qq
- sudo apt-get install -qq g++-4.8
- export CXX="g++-4.8" CC="gcc-4.8"
- sudo apt-get install git

install:
- pip install --upgrade pip
- pip install --upgrade numpy scipy matplotlib pandas
- pip install --upgrade scikit-learn scikit-image
- pip install --upgrade h5py
- pip install --upgrade sureal
- pip install tox-travis

script:
- make
- sudo make install
- make testlib
- export PYTHONPATH=$PWD/python/src:$PYTHONPATH
- python -m unittest discover -s python/test -p '*_test.py'
- python -m unittest discover -s python/test/lib -p '*_libtest.py'
- tox -c python/ -- -v -p no:warnings -m 'main or lib' --doctest-modules
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ Files in the repository follow the structure below:

The core class architecture can be depicted in the diagram below:

![UML](/resource/images/uml.png)
![UML](resource/images/uml.png)

#### Asset

Expand Down
2 changes: 0 additions & 2 deletions python/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
*.pyc
externals.py
.pypirc
42 changes: 42 additions & 0 deletions python/DEVELOP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Notes on python2 / python3 support

A `tox.ini` was added to help provide support for python3. Before running tests using tox, be sure to first run `make` under the root directory.

First draft allows to do this:

- python 3:
- `tox -e venv`, to get a python3 venv in `./.venv`
- `tox -e py37` to exercise all tests with python 3.7
- python 2:
- `tox -e venv2`, to get a python2 venv in `./.venv2`
- `tox -e py27` to exercise all tests with python 2.7
- you can also run `tox` to exercise all tests with both python 2.7 and 3.7


# Support for python3 status

Search for `TODO python3` to spot which places in code still need attention regarding python3 support.

All tests pass with both python2 and python3, however, a few test cases are disabled currently for python3, spot then with:

```bash
grep -r 'reason="TODO python3' .
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Search for TODO python3 to see what still needs to be examined for python3 (all relevant parts of the code were marked with this, everything else I'm pretty sure is good to go)

```

Here's the current list:

- `scipy` v1.3.0 removed some deprecated functions tha vmaf still uses -> need to adapt those before we can upgrade to latest scipy
- tests that rely on `random` do not yield the same results in python2 and python3
- vmaf uses `pickle` to serialize some objects, however "pickle is fickle", and a few objects fail to deserialize in python3
- `YuvReader` needs to be reviewed, it doesn't work in python3
- `map()` and `filter()` yield a generator (instead of list) in python3 -> this implies an extra call `to_list()` throughout the code
(search for `to_list` to find all the spots where this is done).
All usages of `map()`/`filter()` should be reviewed to leverage the speed generators offer


# Test coverage

If you run `tox`, a summary test coverage report will be shown in terminal,
you can also see a full HTML report by looking at `.tox/coverage/index.html`.

This report can be useful to spot any key part of the code that was not exercised (and thus possibly likely to fail under python3)
15 changes: 15 additions & 0 deletions python/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
VMAF - Video Multimethod Assessment Fusion
==========================================

.. image:: https://travis-ci.org/Netflix/vmaf.svg?branch=master
:target: https://travis-ci.org/Netflix/vmaf
:alt: Travis Build Status

.. image:: https://ci.appveyor.com/api/projects/status/68i57b8ssasttngg?svg=true
:target: https://ci.appveyor.com/project/li-zhi/vmaf
:alt: AppVeyor Build Status

VMAF is a perceptual video quality assessment algorithm developed by Netflix.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just a placeholder README, there was no README before in this folder, I added one to make setup.py be closer to what it would eventually need to be if we one day publish this to pypi.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good.


VMAF Development Kit (VDK) is a software package that contains the VMAF algorithm implementation,
as well as a set of tools that allows a user to train and test a custom VMAF model.
9 changes: 9 additions & 0 deletions python/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
numpy
# TODO python3: scipy 1.3.0 removed some previously deprecated functions, still used by vmaf
scipy>=0.17.1,<1.3.0
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

scipy change is indirectly related to py3
Their latest version 1.3.0 is python3 only (they dropped support for python2).
See https://github.com/scipy/scipy/blob/master/setup.py#L490 (python_requires='>=3.5')

This makes it so that vmaf picks up scipy 1.2.1 (because that's the latest version it can pick up...), but python3 ends up getting 1.3.0, where they dropped some previously deprecated functions.

matplotlib>=2.0.0
pandas>=0.19.2
scikit-learn>=0.18.1
scikit-image>=0.13.1
h5py>=2.6.0
sureal>=0.1.0
29 changes: 14 additions & 15 deletions python/script/ffmpeg2vmaf.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@


def print_usage():
print "usage: " + os.path.basename(sys.argv[0]) \
print("usage: " + os.path.basename(sys.argv[0]) \
+ " quality_width quality_height ref_path dis_path [--model model_path] " \
"[--out-fmt out_fmt] [--work-dir work_dir] [--phone-model] [--ci] " \
"[--ref-fmt ref_fmt --ref-width ref_width --ref-height ref_height] " \
"[--dis-fmt dis_fmt --dis-width dis_width --dis-height dis_height] " \
"[--save-plot plot_dir]\n"
print "ref_fmt/dis_fmt:\n\t" + "\n\t".join(FMTS) + "\n"
print "out_fmt:\n\t" + "\n\t".join(OUT_FMTS) + "\n"
"[--save-plot plot_dir]\n")
print("ref_fmt/dis_fmt:\n\t" + "\n\t".join(FMTS) + "\n")
print("out_fmt:\n\t" + "\n\t".join(OUT_FMTS) + "\n")


def main():
Expand All @@ -49,7 +49,7 @@ def main():
return 2

if q_width < 0 or q_height < 0:
print "quality_width and quality_height must be non-negative, but are {w} and {h}".format(w=q_width, h=q_height)
print("quality_width and quality_height must be non-negative, but are {w} and {h}".format(w=q_width, h=q_height))
print_usage()
return 2

Expand All @@ -66,7 +66,7 @@ def main():
ref_fmt = get_cmd_option(sys.argv, 5, len(sys.argv), '--ref-fmt')
if not (ref_fmt is None
or ref_fmt in FMTS):
print '--ref-fmt can only have option among {}'.format(', '.join(FMTS))
print('--ref-fmt can only have option among {}'.format(', '.join(FMTS)))

ref_width = get_cmd_option(sys.argv, 5, len(sys.argv), '--ref-width')
ref_height = get_cmd_option(sys.argv, 5, len(sys.argv), '--ref-height')
Expand All @@ -76,14 +76,14 @@ def main():
dis_fmt = get_cmd_option(sys.argv, 5, len(sys.argv), '--dis-fmt')
if not (dis_fmt is None
or dis_fmt in FMTS):
print '--dis-fmt can only have option among {}'.format(', '.join(FMTS))
print('--dis-fmt can only have option among {}'.format(', '.join(FMTS)))

work_dir = get_cmd_option(sys.argv, 5, len(sys.argv), '--work-dir')

pool_method = get_cmd_option(sys.argv, 5, len(sys.argv), '--pool')
if not (pool_method is None
or pool_method in POOL_METHODS):
print '--pool can only have option among {}'.format(', '.join(POOL_METHODS))
print('--pool can only have option among {}'.format(', '.join(POOL_METHODS)))
return 2

show_local_explanation = cmd_option_exists(sys.argv, 5, len(sys.argv), '--local-explain')
Expand All @@ -103,7 +103,7 @@ def main():
asset_dict['ref_yuv_type'] = 'notyuv'
else:
if ref_width is None or ref_height is None:
print 'if --ref-fmt is specified, both --ref-width and --ref-height must be specified'
print('if --ref-fmt is specified, both --ref-width and --ref-height must be specified')
return 2
else:
asset_dict['ref_yuv_type'] = ref_fmt
Expand All @@ -114,15 +114,15 @@ def main():
asset_dict['dis_yuv_type'] = 'notyuv'
else:
if dis_width is None or dis_height is None:
print 'if --dis-fmt is specified, both --dis-width and --dis-height must be specified'
print('if --dis-fmt is specified, both --dis-width and --dis-height must be specified')
return 2
else:
asset_dict['dis_yuv_type'] = dis_fmt
asset_dict['dis_width'] = dis_width
asset_dict['dis_height'] = dis_height

if show_local_explanation and enable_conf_interval:
print 'cannot set both --local-explain and --ci flags'
print('cannot set both --local-explain and --ci flags')
return 2

asset = Asset(dataset="cmd",
Expand Down Expand Up @@ -184,15 +184,14 @@ def main():

# output
if out_fmt == 'xml':
print result.to_xml()
print(result.to_xml())
elif out_fmt == 'json':
print result.to_json()
print(result.to_json())
else: # None or 'text'
print str(result)
print(str(result))

# local explanation
if show_local_explanation:
import matplotlib.pyplot as plt
runner.show_local_explanations([result])

if save_plot_dir is None:
Expand Down
8 changes: 4 additions & 4 deletions python/script/run_cleaning_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@

def print_usage():
quality_runner_types = ['VMAF', 'PSNR', 'SSIM', 'MS_SSIM']
print "usage: " + os.path.basename(sys.argv[0]) + \
" quality_type dataset_filepath\n"
print "quality_type:\n\t" + "\n\t".join(quality_runner_types) +"\n"
print("usage: " + os.path.basename(sys.argv[0]) + \
" quality_type dataset_filepath\n")
print("quality_type:\n\t" + "\n\t".join(quality_runner_types) +"\n")


def main():
Expand All @@ -32,7 +32,7 @@ def main():
try:
dataset = import_python_file(dataset_filepath)
except Exception as e:
print "Error: " + str(e)
print("Error: " + str(e))
return 1

try:
Expand Down
20 changes: 9 additions & 11 deletions python/script/run_psnr.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#!/usr/bin/env python
from functools import partial

import sys
import os
Expand All @@ -21,10 +20,9 @@


def print_usage():
print "usage: " + os.path.basename(sys.argv[0]) \
+ " fmt width height ref_path dis_path [--out-fmt out_fmt]\n"
print "fmt:\n\t" + "\n\t".join(FMTS) + "\n"
print "out_fmt:\n\t" + "\n\t".join(OUT_FMTS) + "\n"
print("usage: %s fmt width height ref_path dis_path [--out-fmt out_fmt]\n" % os.path.basename(sys.argv[0]))
print("fmt:\n\t" + "\n\t".join(FMTS) + "\n")
print("out_fmt:\n\t" + "\n\t".join(OUT_FMTS) + "\n")


def main():
Expand All @@ -43,7 +41,7 @@ def main():
return 2

if width < 0 or height < 0:
print "width and height must be non-negative, but are {w} and {h}".format(w=width, h=height)
print("width and height must be non-negative, but are {w} and {h}".format(w=width, h=height))
print_usage()
return 2

Expand All @@ -62,7 +60,7 @@ def main():
pool_method = get_cmd_option(sys.argv, 6, len(sys.argv), '--pool')
if not (pool_method is None
or pool_method in POOL_METHODS):
print '--pool can only have option among {}'.format(', '.join(POOL_METHODS))
print('--pool can only have option among {}'.format(', '.join(POOL_METHODS)))
return 2

asset = Asset(dataset="cmd", content_id=0, asset_id=0,
Expand Down Expand Up @@ -105,11 +103,11 @@ def main():

# output
if out_fmt == 'xml':
print result.to_xml()
print(result.to_xml())
elif out_fmt == 'json':
print result.to_json()
else: # None or 'text'
print str(result)
print(result.to_json())
else: # None or 'text'
print(str(result))

return 0

Expand Down
26 changes: 13 additions & 13 deletions python/script/run_testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@

def print_usage():
quality_runner_types = ['VMAF', 'PSNR', 'SSIM', 'MS_SSIM']
print "usage: " + os.path.basename(sys.argv[0]) + \
print("usage: " + os.path.basename(sys.argv[0]) + \
" quality_type test_dataset_filepath [--vmaf-model VMAF_model_path] " \
"[--vmaf-phone-model] [--subj-model subjective_model] [--cache-result] " \
"[--parallelize] [--print-result] [--save-plot plot_dir]\n"
print "quality_type:\n\t" + "\n\t".join(quality_runner_types) +"\n"
print "subjective_model:\n\t" + "\n\t".join(SUBJECTIVE_MODELS) + "\n"
"[--parallelize] [--print-result] [--save-plot plot_dir]\n")
print("quality_type:\n\t" + "\n\t".join(quality_runner_types) +"\n")
print("subjective_model:\n\t" + "\n\t".join(SUBJECTIVE_MODELS) + "\n")


def main():
Expand All @@ -56,7 +56,7 @@ def main():
pool_method = get_cmd_option(sys.argv, 3, len(sys.argv), '--pool')
if not (pool_method is None
or pool_method in POOL_METHODS):
print '--pool can only have option among {}'.format(', '.join(POOL_METHODS))
print('--pool can only have option among {}'.format(', '.join(POOL_METHODS)))
return 2

subj_model = get_cmd_option(sys.argv, 3, len(sys.argv), '--subj-model')
Expand All @@ -68,33 +68,33 @@ def main():
else:
subj_model_class = None
except Exception as e:
print "Error: " + str(e)
print("Error: " + str(e))
return 1

save_plot_dir = get_cmd_option(sys.argv, 3, len(sys.argv), '--save-plot')

try:
runner_class = QualityRunner.find_subclass(quality_type)
except Exception as e:
print "Error: " + str(e)
print("Error: " + str(e))
return 1

if vmaf_model_path is not None and runner_class != VmafQualityRunner and \
runner_class != BootstrapVmafQualityRunner:
print "Input error: only quality_type of VMAF accepts --vmaf-model."
print("Input error: only quality_type of VMAF accepts --vmaf-model.")
print_usage()
return 2

if vmaf_phone_model and runner_class != VmafQualityRunner and \
runner_class != BootstrapVmafQualityRunner:
print "Input error: only quality_type of VMAF accepts --vmaf-phone-model."
print("Input error: only quality_type of VMAF accepts --vmaf-phone-model.")
print_usage()
return 2

try:
test_dataset = import_python_file(test_dataset_filepath)
except Exception as e:
print "Error: " + str(e)
print("Error: " + str(e))
return 1

if cache_result:
Expand Down Expand Up @@ -127,7 +127,7 @@ def main():
if suppress_plot:
raise AssertionError

import matplotlib.pyplot as plt
from vmaf import plt
fig, ax = plt.subplots(figsize=(5, 5), nrows=1, ncols=1)

assets, results = run_test_on_dataset(test_dataset, runner_class, ax,
Expand Down Expand Up @@ -171,8 +171,8 @@ def main():

if print_result:
for result in results:
print result
print ''
print(result)
print('')

return 0

Expand Down