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

making it python 3 compatible #188

Closed
wants to merge 7 commits into from
Closed

Conversation

kmader
Copy link

@kmader kmader commented Feb 9, 2017

without breaking the python 2 compatibility (#178), adding the future imports to keep others clean and behavior particularly division consistent.

The current changes should change or break nothing with python 2 execution and only make the code itself runnable on 3.

…python 2 compatibility, adding the future imports to keep others clean and behavior particularly division consistent
…, adding a new notebook for simple tests, fixing typo in safe_xrange, most tests pass now, but still differences, probably because SimpleITK, numpy, and PyWavelets have changed.
@JoostJM
Copy link
Collaborator

JoostJM commented Feb 10, 2017

@kmader Thank you for your work on making PyRadiomics compatible with Python3! I'm currently reviewing your changes. Could you also enable CircleCI testing? I don't see the results. Travis and Appveyor show failing tests, but this is due to the fact that it is not yet set up correctly (see also #183).

Additionally, I'm seeing an error in shape._calculate2DMaxDiameter, which has to be addressed before we can merge.

In python 3, the zip function returns a 'zip' object, similar to python generators. This does not change it's implementation in python for loops, but it is not evaluated if it is used to generate a numpy array. Therefore, add a cast to list, which evaluates the object, before assigning it to the numpy array.

Also, use relative imports in tests.
Finally, fix some print-function bugs in the 'bin' and 'batchexamples' folder and rerun the python notebooks, so the output reflects the changes made.
@JoostJM
Copy link
Collaborator

JoostJM commented Feb 10, 2017

@kmader, In python 3, the implementation of the zip() function is changed, which then returns a zip object that must be cast first to e.g. a list object before using it to create a numpy array. This was not necessary in python 2, and not used in the PyRadiomics code. This broke one function. I pushed a commit which fixes that bug, and reran all notebooks, no errors.

Also, there were some python 2-style print calls left in the bin folder, so I also corrected those.

Copy link
Collaborator

@JoostJM JoostJM left a comment

Choose a reason for hiding this comment

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

LGTM, but I'd like to test it using CircleCI before I approve.

@fedorov
Copy link
Collaborator

fedorov commented Feb 10, 2017

Any ideas why CircleCI build is not being triggered for this PR?

I have problems with this PR on mac:

$ nosetests --with-xunit --logging-level=DEBUG --verbosity=3 tests/test_features.py
nose.config: INFO: Ignoring files matching ['^\\.', '^_', '^setup\\.py$']
Failure: ValueError (Attempted relative import in non-package) ... ERROR

======================================================================
ERROR: Failure: ValueError (Attempted relative import in non-package)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/fedorov/anaconda/lib/python2.7/site-packages/nose/loader.py", line 418, in loadTestsFromName
    addr.filename, addr.module)
  File "/Users/fedorov/anaconda/lib/python2.7/site-packages/nose/importer.py", line 47, in importFromPath
    return self.importFromDir(dir_path, fqname)
  File "/Users/fedorov/anaconda/lib/python2.7/site-packages/nose/importer.py", line 94, in importFromDir
    mod = load_module(part_fqname, fh, filename, desc)
  File "/Users/fedorov/github/pyradiomics/tests/test_features.py", line 7, in <module>
    from .testUtils import RadiomicsTestUtils, custom_name_func
ValueError: Attempted relative import in non-package
-------------------- >> begin captured logging << --------------------
pykwalify.compat: DEBUG: Using yaml library: /Users/fedorov/anaconda/lib/python2.7/site-packages/yaml/__init__.pyc
--------------------- >> end captured logging << ---------------------

----------------------------------------------------------------------
XML: /Users/fedorov/github/pyradiomics/nosetests.xml
----------------------------------------------------------------------
Ran 1 test in 0.005s

FAILED (errors=1)

The current master works fine.

@fedorov
Copy link
Collaborator

fedorov commented Feb 10, 2017

@jcfr FYI I disabled Appveyor build when no appveyor.yml is present in the org settings

image

Also did the same for TravisCI

@kmader
Copy link
Author

kmader commented Feb 10, 2017

@JoostJM thanks, that zip problem I had just ignored because it worked mostly fine without it (I calculate shape features on my own). Why did you change
this

from testUtils import RadiomicsTestUtils, custom_name_func

To this

from .testUtils import RadiomicsTestUtils, custom_name_func

The python3 style AFAIK is to just use the top way

@fedorov
Copy link
Collaborator

fedorov commented Feb 10, 2017

@kmader if I undo the change by @JoostJM I get the following errors on mac:

$ nosetests --with-xunit --logging-level=DEBUG --verbosity=3 tests/test_features.py
nose.config: INFO: Ignoring files matching ['^\\.', '^_', '^setup\\.py$']
Failure: TypeError (__name__ must be set to a string object) ... ERROR

======================================================================
ERROR: Failure: TypeError (__name__ must be set to a string object)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/fedorov/anaconda/lib/python2.7/site-packages/nose/loader.py", line 418, in loadTestsFromName
    addr.filename, addr.module)
  File "/Users/fedorov/anaconda/lib/python2.7/site-packages/nose/importer.py", line 47, in importFromPath
    return self.importFromDir(dir_path, fqname)
  File "/Users/fedorov/anaconda/lib/python2.7/site-packages/nose/importer.py", line 94, in importFromDir
    mod = load_module(part_fqname, fh, filename, desc)
  File "/Users/fedorov/github/pyradiomics/tests/test_features.py", line 21, in <module>
    class TestFeatures:
  File "/Users/fedorov/github/pyradiomics/tests/test_features.py", line 38, in TestFeatures
    @parameterized.expand(generate_scenarios(), testcase_func_name=custom_name_func)
  File "/Users/fedorov/anaconda/lib/python2.7/site-packages/nose_parameterized-0.5.0-py2.7.egg/nose_parameterized/parameterized.py", line 356, in parameterized_expand_wrapper
    frame_locals[name] = cls.param_as_standalone_func(p, f, name)
  File "/Users/fedorov/anaconda/lib/python2.7/site-packages/nose_parameterized-0.5.0-py2.7.egg/nose_parameterized/parameterized.py", line 366, in param_as_standalone_func
    standalone_func.__name__ = name
TypeError: __name__ must be set to a string object
-------------------- >> begin captured logging << --------------------
pykwalify.compat: DEBUG: Using yaml library: /Users/fedorov/anaconda/lib/python2.7/site-packages/yaml/__init__.pyc
testUtils: DEBUG: RadiomicsTestUtils
testUtils: DEBUG: Reading baseline for class firstorder
testUtils: DEBUG: Reading baseline for class glcm
testUtils: DEBUG: Reading baseline for class glrlm
testUtils: DEBUG: Reading baseline for class glszm
testUtils: DEBUG: Reading baseline for class shape
root: DEBUG: generate_scenarios: featureNames = ['10Percentile', '90Percentile', 'Energy', 'Entropy', 'InterquartileRange', 'Kurtosis', 'Maximum', 'MeanAbsoluteDeviation', 'Mean', 'Median', 'Minimum', 'Range', 'RobustMeanAbsoluteDeviation', 'RootMeanSquared', 'Skewness', 'StandardDeviation', 'TotalEnergy', 'Uniformity', 'Variance']
testUtils: DEBUG: custom_name_func: function name = test_scenario, param_num = 000, param.args = ('brain2', 'firstorder', '10Percentile')
--------------------- >> end captured logging << ---------------------

----------------------------------------------------------------------
XML: /Users/fedorov/github/pyradiomics/nosetests.xml
----------------------------------------------------------------------
Ran 1 test in 0.006s

FAILED (errors=1)

@kmader
Copy link
Author

kmader commented Feb 10, 2017

@fedorov maybe try adding absolute_import from __future__ import print_function, unicode_literals, division, absolute_import
or if that fails the less elegant

try:
    from .testUtils import RadiomicsTestUtils, custom_name_func
except ImportError:
    from testUtils import RadiomicsTestUtils, custom_name_func

@fedorov
Copy link
Collaborator

fedorov commented Feb 10, 2017

This does not help :-(

@fedorov
Copy link
Collaborator

fedorov commented Feb 10, 2017

@kmader can you confirm you tested this with Python 2?

@JoostJM
Copy link
Collaborator

JoostJM commented Feb 13, 2017

@kmader, @fedorov, I used the relative import as my IDE wasn't recognizing the modules. However, if you want to use relative imports in python 2.7, it must be a package (i.e. has an __init__.py file in the folder), as I didn't add this (error), it did not work. Additionally, in PR #158 most parts of PyRadiomics use relative imports within the package (and is the testing folder also marked as 'package'). @jcfr, did you have a specific reason for implementing this?
As to the errors, I now reverted back to an absolute import. If we want to change this in the future, this can be done in PR #158 or in a separate PR.

@JoostJM
Copy link
Collaborator

JoostJM commented Feb 13, 2017

@fedorov, @kmader, I found the 2nd error. The problem was that due to the compatibility changes, the custom_name_func returned a unicode string instead of a string. This then breaks the parameterized.expand function of nosetesting. Fixed it using a forced cast to str()

@JoostJM
Copy link
Collaborator

JoostJM commented Feb 13, 2017

@fedorov, @kmader, I have nose running locally for both python 2.7 and 3.5, but we haven't caught all issues yet. Currently I am working on a bug (filter function does not work properly in python 3.5 and as such deletes several diagonals in glrlm, causing large enough errors to fail the tests). Moreover, some other functions, mainly in glcm are also differing slightly from python 2.7 generated baseline, but these errors are fairly small.

Some functions expect string types (such as in nose-testing), but python 3.5 by default provides unicode strings. Add forced casts where necessary.
Also, 'wb' mode requires strings to be encoded as bytes, not string in python 3.5. The fix did not work in this case. Change mode to 'w', which accepts regular strings.
Additionally, safe_cmp produced errors for dictionary comparison in python 3.5, replace by `==` comparison.

Finally, update layout in testing files (such as replace `!=` by `is not` in comparisons to `None`).
Update feature name in testing output to also reflect the feature class.
In python 3.5 GLRLM results differed from baseline. This due to the different implementation of the `filter()` function, which returns an iterator instead of a list. In GLRLM calculation, there where 2 iterations over this iter-object (one for flat-angle check, one for matrix calculation), causing it to break in python 3.5. Fix by merging the 2 iterations into one loop.

Additionally, simplify the filter function.
@JoostJM
Copy link
Collaborator

JoostJM commented Feb 13, 2017

Any ideas why CircleCI build is not being triggered for this PR?

This is due to the fact that this PR is based out of @kmader's repository, and if it doesn't have a webhook defined for CircleCI, no tests are run. I ran CircleCI tests for both python 2.7 and python 3.5 locally (Windows) and received no errors after my last commit.

If I look at the diff-file generated, there are some functions with diff != 0, but the difference is always in the order of e-16, so most likely computer precision or epsilon difference...

As far as I can see, this PR is now ready for merging, with PyRadiomics running both on python 2.7 and python 3.5

@fedorov
Copy link
Collaborator

fedorov commented Feb 13, 2017

It works for me as well now.

About CircleCI - I still don't understand why it doesn't work. Webhooks should only affect builds of the branches for @kmader repository, but I thought incoming PRs for pyradiomics should always be built, and rely on pyradiomics webhooks setup.

@JoostJM
Copy link
Collaborator

JoostJM commented Feb 13, 2017

About CircleCI - I still don't understand why it doesn't work. Webhooks should only affect builds of the branches for @kmader repository, but I thought incoming PRs for pyradiomics should always be built, and rely on pyradiomics webhooks setup.

Not in it's current form, but let me take a look at the webhooks of this repository

@JoostJM
Copy link
Collaborator

JoostJM commented Feb 13, 2017

@fedorov, I found the problem, the webhook is configured to also send a notification on PRs from forked repos, but in CircleCI, the building of PRs from forked repos was turned off. It should work now.

@JoostJM
Copy link
Collaborator

JoostJM commented Feb 13, 2017

Redelivered webhook after last commit. CircleCI now testing...

@fedorov
Copy link
Collaborator

fedorov commented Feb 13, 2017

Makes sense now, thanks!

# Conflicts:
#	radiomics/__init__.py
#	radiomics/featureextractor.py
#	tests/test_docstrings.py
@JoostJM
Copy link
Collaborator

JoostJM commented Feb 13, 2017

Merged the new master to resolve conflicts. Rebase impossible, as this requires a force push (not allowed on master branch).

@fedorov
Copy link
Collaborator

fedorov commented Feb 13, 2017

this requires a force push (not allowed on master branch)

I was wondering why this PR comes from the master branch. How do you guys plan to sync it with pyradiomics master?

@JoostJM
Copy link
Collaborator

JoostJM commented Feb 13, 2017

I was wondering why this PR comes from the master branch. How do you guys plan to sync it with pyradiomics master?

Just like any other PR, as this is 4Quant:master branch, not pyradiomics:master. However, for future PRs, it is still advisable to do so on a non-master branch, as thing such as rebasing are easier.

@fedorov
Copy link
Collaborator

fedorov commented Feb 13, 2017

I suggest you disable force-push on 4Quant master, force 4Quant master to be identical to the current master, make a branch for this PR, and make a new PR off that branch.

I would recommend against merging the commit history as it shows up at the moment for this PR.

@JoostJM
Copy link
Collaborator

JoostJM commented Feb 14, 2017

@kmader, I will rebase this branch onto the new master, but I can't push it to your repository, so I'll push it to the central repo and create a new PR.

@kmader
Copy link
Author

kmader commented Feb 14, 2017

@JoostJM that sounds fine, thanks!

@JoostJM
Copy link
Collaborator

JoostJM commented Feb 14, 2017

superseded by #194

@JoostJM JoostJM closed this Feb 14, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants