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

How to include OpenCV? #30

Closed
ivoflipse opened this issue Aug 2, 2014 · 29 comments
Closed

How to include OpenCV? #30

ivoflipse opened this issue Aug 2, 2014 · 29 comments

Comments

@ivoflipse
Copy link

I just tried to create an installer, but got the following error message:

Traceback (most recent call last):
  File "C:\Dropbox\Development\Pawlabeling\build\nsis\Pawlabeling.launch.py", line 21, in <module>
    from pawlabeling.widgets.mainwindow import main
  File "C:\Dropbox\Development\Pawlabeling\build\nsis\pkgs\pawlabeling\widgets\mainwindow.py", line 14, in <module>
    from ..models import model
  File "C:\Dropbox\Development\Pawlabeling\build\nsis\pkgs\pawlabeling\models\model.py", line 7, in <module>
    from ..models import table, subjectmodel, sessionmodel, measurementmodel, contactmodel, platemodel
  File "C:\Dropbox\Development\Pawlabeling\build\nsis\pkgs\pawlabeling\models\contactmodel.py", line 2, in <module>
    import cv2
ImportError: DLL load failed: The specified module could not be found.

It can't find OpenCV, even though I specified cv2 in my include list:

[Application]
name=Pawlabeling
version=0.2.1
entry_point=pawlabeling.widgets.mainwindow:main
icon=favicon.ico
console=true

[Python]
version=2.7.5
bitness=64

[Include]
# Importable packages that your application requires, one per line
packages = pawlabeling
    numpy
     PySide
     cv2
     scipy
     matplotlib
     pubsub
     tables
     pandas

# Other files and folders that should be installed
files = COPYING.txt
    readme.md
    requirements.txt
    docs/
    pawlabeling/

It seems to have forgotten to pick up the OpenCV dll's which is probably a side-effect of the rather quirky way OpenCV was installed (I used Gohlke's installer). All the dll's are in Lib/site-packages and are called something like opencv_*.dll instead of something resembling cv2.

I'm not sure how to make sure it get's included properly, I guess I could run a script that runs Miniconda to install it anyway or bundle the installer and have it run that. Any suggestions on how to fix this?

@takluyver
Copy link
Owner

Hi ivo,

Pynsist doesn't currently do any dependency analysis, so it doesn't know
that the cv2 module requires extra DLLs. My experience with that kind of
automation is that it only works well enough to confuse people when it goes
wrong, but this is certainly a case where it would be useful. I'll think
about what can be done.

As a workaround, copy the DLLs into a folder called pynsist_pkgs before
building, and it should include them in the pkgs directory.

@ivoflipse
Copy link
Author

Perhaps it would be easier to download the dependencies through Conda/Miniconda, since most of my dependencies seem to be available through Anaconda or Binstar.

Apparently even matplotlib lacked some dependencies like dateutil, so simply installing the libraries through pip or Conda could work, though it requires an internet connection.

I tried to create a preamble_script but it wouldn't accept Path to the preamble file, even though it works find for the entry_point.

[Application]
name = Pawlabeling
version = 0.3.1
entry_point = pawlabeling.widgets.mainwindow:main
#script = Pawlabeling.launch.py
icon = favicon.ico
console = true
extra_preamble = pynsis_preamble.py
C:\Dropbox\Development\Pawlabeling>python -m pynsist installer.cfg
Downloading Python MSI...
Downloading PyLauncher MSI...
Traceback (most recent call last):
  File "C:\Anaconda64\lib\runpy.py", line 162, in _run_module_as_main
    "__main__", fname, loader, pkg_name)
  File "C:\Anaconda64\lib\runpy.py", line 72, in _run_code
    exec code in run_globals
  File "C:\Anaconda64\Scripts\pynsist.py", line 3, in <module>
    main()
  File "C:\Anaconda64\lib\site-packages\nsist\__init__.py", line 379, in main
    nsi_template = cfg.get('Build', 'nsi_template', fallback=DEFAULT_NSI_TEMPLAT
E),
  File "C:\Anaconda64\lib\site-packages\nsist\__init__.py", line 324, in run
    self.prepare_shortcuts()
  File "C:\Anaconda64\lib\site-packages\nsist\__init__.py", line 216, in prepare
_shortcuts
    extra_preamble.read().rstrip())
AttributeError: 'unicode' object has no attribute 'read'

@takluyver
Copy link
Owner

The problem with extra_preamble is a straightforward bug, and I should have a fix for that shortly. I'm not sure that's actually what you want, though - the preamble is executed at runtime, not at install time. I'm also thinking about a way of subclassing templates (see #29) which would make it easier to run custom code on install.

Yep, there's really no attempt at dependency tracking at present, so for something like matplotlib's dependency on dateutil, you will need to specify both packages. I've spent some time working with cx_Freeze, which does try to automatically handle dependencies, and about half the problems are when it gets that wrong, so I've deliberately gone for a very simple model to start with: it copies only the files and folders you ask for, nothing more.

I'm beginning to think about working with some packaging system (see also #22). But do I go with PyPI, which is by far more familiar to Python developers outside the scientific world, or conda, which is more powerful? Do I set up the packaging system on the target machine and install packages there, possibly into virtual/conda envs, or do I download the packages on the app developer's machine and copy their contents into the installer? What files should be copied and where to? I want to think about this carefully, because it's hard to go back after it's been implemented and released. I also want to see how use of wheels matures, because they're quite a nice format that may influence the decision.

@takluyver
Copy link
Owner

The preamble bug should be fixed by d30f215.

@ivoflipse
Copy link
Author

If the preamble is ran at runtime, then all I could do is check if everything is installed and if not, get whatever requirements are missing.

I personally like conda, because it makes installing the scientific stack on Windows a breeze. You can't reliably pip install numpy or scipy. Obviously at the moment its mostly focused on the scientific world, but through binstar you can also get all of PyPi (though it seems to be a work in progress). You could try downloading through conda and fall back on pip if something is not available.

Setting up on the target machine might make installation quite slow, because all the dependencies have to be downloaded and installed. But if you want to support multiple platforms or bitness versions, than you would have to provide multiple installers or have one big installer for all versions, so there's always a trade-off.

My preference would be to download all the packages on the developer's machine, for example in a conda environment. That way conda can take care of all the dependences and you don't have to reinvent the wheel.

Basically it would be similar to how Travis-CI sets up a machine to run its tests, you tell it what your environment should look like and it downloads everything that's needed. That way you also know your setup is reproducible, instead of copying over files from my personal site-packages and potentially missing any dependencies.

But I can understand your hesitations for not making any rash decisions, so for now I'll just have the launch script check whether everything is installed and if not, download whatever dependency is missing.

@takluyver
Copy link
Owner

Thanks for those ideas - I'll start thinking about this stuff in a background thread.

Cross platform installers aren't an issue - NSIS and hence Pynsist only target Windows.

I think downloading on the developer's machine makes sense too. But that does mean we need to download packages that may not match the platform we're currently on (part of why I made pynsist is so I can build installers on Linux), and work out how to arrange them for the application to work, so it's a bit more complex.

@cachitas
Copy link
Contributor

I am still struggling with adding cv2.
@ivoflipse can you explain me how you manage it?
I placed all the DLLs in the folder as stated above but running the application still reports that the DLLs for cv2 could not be found.

@ivoflipse
Copy link
Author

@cachitas I do not, I would now recommend someone to use conda for managing packages like OpenCV. I haven't looked into a way of bundling a conda environment into some kind of application yet, though I believe it's possible using conda constructor (https://github.com/conda/constructor)

@cachitas
Copy link
Contributor

Got it working!

Python 3.5.2 with OpenCV 3.1 (64 bits)

  1. Download the OpenCV Windows wheel from the Unofficial Windows Binaries site
  2. Extract the files in the folder <wheel_zip>/opencv_python-3.1.0.data/data/Lib/site-packages/* to the folder pynsist_pkgs used by pynsist
  3. Extract the file <wheel_zip>/opencv_python-3.1.0.data/data/opencv_ffmpeg310_64.dll to your Python 3.5 folder
  4. Profit!

Note: Maybe you also need to provide Numpy. Because my project at hands uses Numpy, I included it and did not test including OpenCV without Numpy.

installer.cfg

[Python]
version=3.5.2
bitness=64

[Include]
packages=...
    (did not include here cv2, numpy or any other which I provided in pynsist_pkgs)

So this has the drawback that the user has to make sure they have the opencv file in their Python folder. Without it, it is possible to import cv2 but upon opening a video capture.isOpen() always failed.

PS: Extra question for @takluyver : including Numpy in pynsist_pkgs means adding 300+ Mb to the installer... is there something I can do about this?

@takluyver
Copy link
Owner

Extract the file <wheel_zip>/opencv_python-3.1.0.data/data/opencv_ffmpeg310_64.dll to your Python 3.5 folder

Have you definitely checked that it works on a computer without this DLL? I'm not sure how putting this in your Python 3.5 folder would affect what Pynsist builds.

including Numpy in pynsist_pkgs means adding 300+ Mb to the installer... is there something I can do about this?

Yikes, is it really that big? The wheels on PyPI (which Pynsist can now use :-) are about 14MB, and compression should be at least as good in the installer. Are you using numpy with the MKL from conda? That can be big, but more performant. Or are there examples and docs in there? You can strip stuff like examples and docs out of a copy in pynsist_pkgs, if they're in the package for some reason.

@cachitas
Copy link
Contributor

cachitas commented Aug 31, 2016

I'm not sure how putting this in your Python 3.5 folder would affect what Pynsist builds.

This is completely independent of Pynsist. Let me explain what I did in detail.
On a first try I had all opencv DLLs in the pkgs folder created by Pynsist. I could import cv2 but when opening a video with cv2.VideoCapture, it would fail silently. Checking if capture was open returned False so I tried moving opencv_ffmpeg310_64.dll to my C:\Python35 folder (I used format=installer) and everything worked. I moved this DLL because it was the solution for previous OpenCV issues on Windows and also because inside its wheel, it is duplicated and separated from other DLLs. I don't know if Pynsist can help with this step...

Regarding the size, I am doing this the worst way possible! You are right that the sizes in PyPI are way smaller. I am using the wheels I downloaded from Unofficial Windows Binaries. Numpy there is ~100Mb and on top of that, I unzipped the wheel to get only the numpy folder. This results in the 300Mb reported above. I will try to distribute this using PyPI wheels now.

Edit: Seems that I need to unzip the wheels and get the folder with the package name. Else the application can not find the packages later on.

Edit 2: Turns out I need the Numpy wheel I was using before, the big one. It includes MKL. Using the wheel from PyPI results in ImportError: cannot import name NUMPY_MKL

@takluyver
Copy link
Owner

I tried moving opencv_ffmpeg310_64.dll to my C:\Python35 folder (I used format=installer) and everything worked.

I'm still a bit confused. Is this something you do on your development machine before building the installer, or have you customised the installer to move it there on the target machine after installation?

Edit 2: Turns out I need the Numpy wheel I was using before, the big one. It includes MKL. Using the wheel from PyPI results in ImportError: cannot import name NUMPY_MKL

Is that error from OpenCV? If Gohlke is distributing numpy with the MKL, I'd guess that all the packages he builds that require numpy are built against that, so you need to get the matching version of numpy. MKL is big, though I'm still a bit surprised that it's that big.

@cachitas
Copy link
Contributor

Is this something you do on your development machine before building the installer, or have you customised the installer to move it there on the target machine after installation?

Apologies for the confusion.
I moved the file myself in the target machine. That laptop happens to be by my side :)

Is that error from OpenCV?

No. Actually it comes from Scipy, another dependency of my app. Maybe it would also happen with OpenCV, but I don't know since it hit the Scipy dependency first. I downloaded the Gohlke's Scipy wheel (there was no amd64 in PyPI) so what you say may be true:

I'd guess that all the packages he builds that require numpy are built against that, so you need to get the matching version of numpy.

Just to give an example, my current build folder is almost 900 Mb. It includes:

  • PyQt5 (~200Mb)
  • Numpy (~300Mb)
  • Scipy, Pandas (~30Mb each)
  • OpenCV DLLs (~60 Mb)
  • plus some smaller modules (kbs)
  • generated .exe (~200Mb)

@takluyver
Copy link
Owner

I moved the file myself in the target machine. That laptop happens to be by my side :)

Aha :-). So you have a workaround, but not a way to build an installer that works without the manual step?

I might try with a format=bundled installer, and use the 'include files' option to put the extra DLLs into $INSTDIR\Python. Docs for [include] section.

No. Actually it comes from Scipy, another dependency of my app. Maybe it would also happen with OpenCV, but I don't know since it hit the Scipy dependency first. I downloaded the Gohlke's Scipy wheel (there was no amd64 in PyPI) so what you say may be true:

OK, I'd certainly expect scipy to need numpy matching what it was built with. Unfortunately there are no Windows wheels on PyPI yet for Scipy.

generated .exe (~200Mb)

Ah, OK, so numpy is 300 MB unpacked, not 300 MB when packed into the installer. That's more in line with what I'd expect for MKL.

@cachitas
Copy link
Contributor

So you have a workaround, but not a way to build an installer that works without the manual step?

Yes it is working for now. But it would be better to have Pynsist making sure the OpenCV DLL is there in case it is a dependency, instead of having to tell the user to copy the file there in the installation instructions...

I did not test using the bundled format. I will let you know how it goes if I try this in another machine meanwhile.

@cachitas
Copy link
Contributor

Good news!
I tested the bundled format, and it works great with the dependencies mentioned above.

Remember that I had to place all OpenCV DLLs inside pynsist_pkgs folder. As you suggested, I use the [include] section to move the required DLL to the bundled Python installation directory.

My installer.cfg file has the following:

[Python]
version=3.5.2
bitness=64
format=bundled

[Include]
files=pynsist_pkgs/opencv_ffmpeg310_64.dll > $INSTDIR\Python

@takluyver
Copy link
Owner

Awesome :-).

Heads up, if you put a file in pynsist_pkgs and list it in [Include], it might get copied into your installer twice, making it a bit bigger than it needs to be. pynsist_pkgs is for files that will go into $INSTDIR\pkgs, and you don't need to list them in the config file, because it's so common to include extra files there.

So try putting it in a different folder, or next to the installer.cfg file, and see if that still works.

After that, could I tempt you to contribute an example using OpenCV to the repo, to record what you've found?

@cachitas
Copy link
Contributor

Thanks for the warning!

I went this way because this DLL is among the ones needed to be in pynsist_pkgs for OpenCV to work. But because it also needs to be in the Python directory, I thought about using this one to give the instruction to then move a copy there. In this case we talk about ~13Mb so maybe it is a solution to work with OpenCV.

Note: I need to try this without opencv_ffmpeg310_64.dll in the pynsist_pkgs folder to make sure OpenCV really needs it there. If not, then I move the file out and the duplication is gone.

I am very tempted to work on a minimal working example of a PyQt5 application using OpenCV to add to the repo examples. 😃
But first I would like to hear your opinion on deploying the dev environment for something like this. If useful, this could also be illustrated in the future example.
I will briefly tackle it here but we can move the discussion to email to avoid polluting this issue.

So in order to prepare the dev environment to build the application with Pynsist I need to download the dependencies (in the example case just Numpy and OpenCV) and extract the needed files to pynsist_pkgs. I want to automate this and wanted to ask your advice. Should it be a Makefile or some Python script? Via curl/wget or urllib/requests/pip machinery? I am looking for something simple to get the zip files from http://www.lfd.uci.edu/~gohlke/pythonlibs or PyPI and then extract them to the right place. Bonus would be to download several dependencies concurrently. What do you think?

@takluyver
Copy link
Owner

So in order to prepare the dev environment to build the application with Pynsist I need to download the dependencies (in the example case just Numpy and OpenCV) and extract the needed files to pynsist_pkgs. I want to automate this and wanted to ask your advice. Should it be a Makefile or some Python script? Via curl/wget or urllib/requests/pip machinery? I am looking for something simple to get the zip files from http://www.lfd.uci.edu/~gohlke/pythonlibs or PyPI and then extract them to the right place. Bonus would be to download several dependencies concurrently. What do you think?

Numpy definitely has Windows wheels, so you should be able to put that in the config file (see the PyQt5 example for comparison). There are also third party wheels for OpenCV on PyPI which you could try building with.

There are some other examples which use shell scripts to download and unpack pre-built binaries (e.g. for PyQt4). That has the rather strange consequence that it won't conveniently work on Windows. A Python script, which could be cross platform, would be better, but shell scripting is very convenient for this kind of thing.

Gohlke's site goes to some lengths to prevent automated downloads, so I tend not to use it for this.

@cachitas
Copy link
Contributor

Alright, I will give it a try using the available wheels.

Looking at your PyQt4 example reminded me to also mention here that including the OpenCV DLLs also require the Visual C++ Redistributable to be installed. I shall mention this later on the my example documentation.

@takluyver
Copy link
Owner

There should be a way to include the Visual C++ redistributable into the application bundle. I think Qt and possibly also matplotlib use it, and they include the necessary files somehow. But all the Visual C/C++ library stuff is a bit confusing, and I've never completely got it.

@cachitas
Copy link
Contributor

There are also third party wheels for OpenCV on PyPI which you could try building with.

Sadly I've encounter some difficulties in making use of opencv-python as reported in opencv/opencv-python#14. This issue concerns Ubuntu, but now using Pynsist and opencv-python I get a black display in Windows so my suspicions fall on ffmpeg...

I think for now I will adopt the strategy I used before: download and extract OpenCV DLLs in pynsist_pkgs.

Regarding the examples, I'm working on two fronts:

  • one example using OpenCV only: simple script with loop showing the camera
  • one example using PyQt5 + OpenCV: a simple widget showing the camera involving a QThread and a QTimer.

Do you find both useful or one is enough?
I did not want to duplicate the OpenCV DLLs by doing it the old way, so I was thinking of automating it at build time. What do you suggest?

@takluyver
Copy link
Owner

With the wheels:

  • Are there any error messages in the log file it writes in %APPDATA%?
  • Do the Windows wheels work if you install them directly with pip? i.e. are they broken in general, or just when packaged and installed by Pynsist?

Downloading and extracting them is OK if necessary, but it's so much neater to use wheels where possible, so that's why I'm pushing it a bit. ;-)

Do you find both useful or one is enough?

I think I'd rather just stick to one example. Maybe the one including PyQt5, since that's more realistic and PyQt5 is easy to include now. I don't want to maintain too large a collection of examples that don't show something clearly distinct.

I did not want to duplicate the OpenCV DLLs by doing it the old way, so I was thinking of automating it at build time.

I'm not quite sure what you mean?

@cachitas
Copy link
Contributor

cachitas commented Sep 20, 2016

I did not want to duplicate the OpenCV DLLs by doing it the old way, so I was thinking of automating it at build time.

I meant that by having both examples, I would need OpenCV DLLs in pynsist_pkgs of them both, so it would be duplicated data in the repo. But nevermind this for I agree with you the the example using PyQt5 is enough.

  • Are there any error messages in the log file it writes in %APPDATA%?

No. As far as I know it only complains if it can not find cv2 or if it can not use OpenCV DLLs (this is solved by installing Visual C++ 2015 Redistributable).
The opencv-python wheels do not include ffmpeg (see opencv/opencv-python#6 and opencv/opencv-python#14). This does not raise any Exception when Python runs unfortunately and, from what I understood, this is an issue with OpenCV itself on how it generates its Python bindings. Not having ffmpeg makes it impossible to instantiate a VideoCapture:

>>> cap = VideoCapture(-1)  # or 0, or a path to a video file....
>>> cap.isOpened()
False

I can only see this fail after installing it in the Windows machine.

  • Do the Windows wheels work if you install them directly with pip? i.e. are they broken in general, or just when packaged and installed by Pynsist?

I didn't try to install the opencv-python wheel on Windows. At least on Linux it is broken, since it is not of much use without ffmpeg... I believe the same is true for Windows at the moment.

@takluyver
Copy link
Owner

@cachitas how's it going with the OpenCV example? I'm thinking of doing another release. There's no real need to synchronise examples with releases, but if it's likely to turn up in the next few days, I'll wait so I can put it in the release notes.

@cachitas
Copy link
Contributor

cachitas commented Sep 24, 2016

The example is working. Just need to include the OpenCV DLLs since the package opencv-python is not working. I will perform the PR as soon as possible for us to discuss it.

Edit: Ignore that last bit. Looks like there are new builds including ffmpeg. I ran pynsist using a build artifact but only later will have access to a Windows machine to test it. Hopefully, this will be available on PyPI soon...

@takluyver
Copy link
Owner

It looks like opencv-python just got a commit to include the ffmpeg DLLs on Windows (opencv/opencv-python@68fc11e - thanks @techtonik). So once a new release of that is made, it should be possible to do the example with just wheels! 🎉

@takluyver
Copy link
Owner

Closing this as I think things are now working with the opencv-python wheels.

@techtonik
Copy link

I am glad it helped. )

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

No branches or pull requests

4 participants