Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 31 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
language: python
sudo: false
deploy:
provider: pypi
user: arokem
Expand All @@ -10,5 +9,35 @@ deploy:
repo: arokem/python-matlab-bridge
# until this is fixed: https://github.com/travis-ci/travis-ci/issues/1675
all_branches: true
env:
- CONDA="python=2.7 numpy=1.7"
- CONDA="python=3.3 numpy"
before_install:
- sudo apt-add-repository -y ppa:octave/stable;
- sudo apt-get update -qq;
- sudo apt-get install -qq octave liboctave-dev default-jdk;
- wget http://repo.continuum.io/miniconda/Miniconda-latest-Linux-x86_64.sh -O miniconda.sh;
- bash miniconda.sh -b -p $HOME/miniconda
- export PATH="$HOME/miniconda/bin:$PATH"
- hash -r
- conda config --set always_yes yes
- conda update conda
- conda info -a
- travis_retry conda create -n test $CONDA IPython pip nose pyzmq
- source activate test
- travis_retry pip install coveralls

install:
- export USE_OCTAVE=True
- python setup.py install

script:
- true
# run coverage on py2.7, regular on others
- if [[ $CONDA == python=2.7* ]]; then
nosetests --exe -v --with-cov --cover-package pymatbridge;
else
nosetests --exe -v pymatbridge;
fi

after_success:
- coveralls
29 changes: 15 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,17 +115,7 @@ Rather than looking for `matlab` at the shell, this will look for `octave`.
As with `pymatbridge.Matlab`, you can override this by specifying the
`executable` keyword argument.

There are a few caveats to note about Octave support:

* The zmq messenger used to communicate with the Octave session is written in C
and needs to be compiled. For MATLAB various prebuilt binaries are provided
and added to MATLAB's runtime path, but as of this writing the same isn't
true for Octave. You'll need to compile the messenger (in
`messenger/src/messenger.c`) using an incantation like
`mkoctfile --mex -lzmq messenger.c` and place the resulting `messenger.mex`
file somewhere in Octave's runtime path.

Finally, rather than `~/startup.m`, Octave looks for an `~/.octaverc` file for
Rather than `~/startup.m`, Octave looks for an `~/.octaverc` file for
commands to execute before every session. (This is a good place to manipulate
the runtime path, for example).

Expand Down Expand Up @@ -154,11 +144,11 @@ More examples are provided in the `examples` directory
The installation of `pymatbridge` includes a binary of a mex function to communicate between
Python and Matlab using the [0MQ](http://zeromq.org/) messaging library. This should work
without any need for compilation on most computers. However, in some cases, you might want
to build the pymatbridge messenger from source. To do so, you will need to follow the
to build the pymatbridge messenger from source. To do so, you will need to follow the instructions below:

### Install zmq library
Please refer to the [official guide](http://zeromq.org/intro:get-the-software) on how to
build and install zmq. After zmq is installed, make sure you can find the location where
build and install zmq. On Ubuntu, it is as simple as `sudo apt-get install libzmq3-dev`. After zmq is installed, make sure you can find the location where
libzmq is installed. The library extension name and default location on different systems
are listed below.

Expand Down Expand Up @@ -215,12 +205,23 @@ be supported. If you have an old version of pyzmq, please update it.

### Install pymatbridge
After the steps above are done, you can install pymatbridge. Download the zip file of the
latest release. Unzip it somewhere on your machine and then issue:
latest release. Unzip it somewhere on your machine.

For Matlab:

cd messenger
python make.py
cd ..
python setup.py install


For Octave:

cd messenger/octave
# edit make.py if not using Ubuntu-derivative
python make.py
cd ..
python setup.py

This should make the python-matlab-bridge import-able.

Expand Down
7 changes: 7 additions & 0 deletions messenger/octave/make.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

import os

os.system('sudo apt-get install libzmq3-dev liboctave-dev')
os.system('cd ../src; mkoctfile --mex -lzmq messenger.c')
os.system('mv ../src/messenger.mex .')
os.system('cp messenger.mex ../pymatbridge/matlab')
Binary file added messenger/octave/messenger.mex
Binary file not shown.
4 changes: 2 additions & 2 deletions pymatbridge/matlab/util/isrow.m
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
%
%

if ndims(X)==2 & size(X,1)==1 & size(X,2)>=1
if ndims(X)==2 && size(X,1)==1 && size(X,2)>=1
Y = logical(1);
else
Y = logical(0);
end
end
2 changes: 1 addition & 1 deletion pymatbridge/matlab/util/json_v0.2.2/json/json_dump.m
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@
end
elseif isnumeric(value)
if isreal(value)
obj = javaObject('java.lang.Double', value);
obj = value;
% Encode complex number as a struct
else
complex_struct = struct;
Expand Down
1 change: 1 addition & 0 deletions pymatbridge/matlab/util/pymat_get_variable.m
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
else
response.exists = true;
response.var = evalin('base', varname);
response.success = 'true';
end

json_response = json_dump(response);
Expand Down
59 changes: 21 additions & 38 deletions pymatbridge/matlab_magic.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,36 +7,23 @@

"""

import sys, os
import tempfile
from glob import glob
from shutil import rmtree
from getopt import getopt

import numpy as np
try:
import scipy.io as sio
has_io = True
except ImportError:
has_io = False
no_io_str = "Must have scipy.io to perform i/o"
no_io_str += "operations with the Matlab session"

import IPython

ipython_version = int(IPython.__version__[0])

from IPython.core.displaypub import publish_display_data
from IPython.core.magic import (Magics, magics_class, cell_magic, line_magic,
from IPython.core.magic import (Magics, magics_class,
line_cell_magic, needs_local_scope)
from IPython.testing.skipdoctest import skip_doctest
from IPython.core.magic_arguments import (argument, magic_arguments,
parse_argstring)
from IPython.utils.py3compat import str_to_unicode, unicode_to_str, PY3
from IPython.utils.py3compat import unicode_to_str, PY3

import pymatbridge as pymat
from .compat import text_type

ipython_version = int(IPython.__version__[0])


class MatlabInterperterError(RuntimeError):
"""
Expand Down Expand Up @@ -95,7 +82,10 @@ def __init__(self, shell,
super(MatlabMagics, self).__init__(shell)
self.cache_display_data = cache_display_data

self.Matlab = pymat.Matlab(matlab, maxtime=maxtime)
if 'octave' in matlab.lower():
self.Matlab = pymat.Octave(matlab, maxtime=maxtime)
else:
self.Matlab = pymat.Matlab(matlab, maxtime=maxtime)
self.Matlab.start()
self.pyconverter = pyconverter

Expand Down Expand Up @@ -173,19 +163,15 @@ def matlab(self, line, cell=None, local_ns=None):
self.Matlab.set_default_plot_size(width, height)

if args.input:
if has_io:
for input in ','.join(args.input).split(','):
try:
val = local_ns[input]
except KeyError:
val = self.shell.user_ns[input]
# The _Session.set_variable function which this calls
# should correctly detect numpy arrays and serialize them
# as json correctly.
self.set_matlab_var(input, val)

else:
raise RuntimeError(no_io_str)
for input in ','.join(args.input).split(','):
try:
val = local_ns[input]
except KeyError:
val = self.shell.user_ns[input]
# The _Session.set_variable function which this calls
# should correctly detect numpy arrays and serialize them
# as json correctly.
self.set_matlab_var(input, val)

try:
result_dict = self.eval(code)
Expand Down Expand Up @@ -216,7 +202,8 @@ def matlab(self, line, cell=None, local_ns=None):
if len(imgf):
# Store the path to the directory so that you can delete it
# later on:
image = open(imgf, 'rb').read()
with open(imgf, 'rb') as fid:
image = fid.read()
if ipython_version < 3:
display_data.append(('MatlabMagic.matlab',
{'image/png':image}))
Expand All @@ -234,11 +221,8 @@ def matlab(self, line, cell=None, local_ns=None):
rmtree(data_dir)

if args.output:
if has_io:
for output in ','.join(args.output).split(','):
self.shell.push({output:self.Matlab.get_variable(output)})
else:
raise RuntimeError(no_io_str)
for output in ','.join(args.output).split(','):
self.shell.push({output:self.Matlab.get_variable(output)})


_loaded = False
Expand All @@ -253,6 +237,5 @@ def unload_ipython_extension(ip):
global _loaded
if _loaded:
magic = ip.magics_manager.registry.pop('MatlabMagics')
magic.Matlab.stop()
_loaded = False

11 changes: 6 additions & 5 deletions pymatbridge/pymatbridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

import os
import time
import codecs
import base64
import zmq
import subprocess
import sys
Expand Down Expand Up @@ -60,7 +60,7 @@ def encode_ndarray(obj):
except AttributeError:
data = obj.astype(float64).tostring()

data = codecs.encode(data, 'base64').decode('utf-8')
data = base64.b64encode(data).decode('utf-8')
return data, shape


Expand Down Expand Up @@ -89,19 +89,19 @@ def default(self, obj):
def decode_arr(data):
"""Extract a numpy array from a base64 buffer"""
data = data.encode('utf-8')
return frombuffer(codecs.decode(data, 'base64'), float64)
return frombuffer(base64.b64decode(data), float64)


# JSON decoder for arrays and complex numbers
def decode_pymat(dct):
if 'ndarray' in dct and 'data' in dct:
value = decode_arr(dct['data'])
shape = decode_arr(dct['shape'])
shape = decode_arr(dct['shape']).astype(int)
return value.reshape(shape, order='F')
elif 'ndarray' in dct and 'imag' in dct:
real = decode_arr(dct['real'])
imag = decode_arr(dct['imag'])
shape = decode_arr(dct['shape'])
shape = decode_arr(dct['shape']).astype(int)
data = real + 1j * imag
return data.reshape(shape, order='F')
elif 'real' in dct and 'imag' in dct:
Expand Down Expand Up @@ -428,6 +428,7 @@ def _preamble_code(self):
if self.log:
code.append("diary('./pymatbridge/logs/octavelog_%s.txt')" % self.id)
code.append("set(0, 'defaultfigurevisible', 'off');")
code.append("graphics_toolkit('gnuplot')")
return code

def _execute_flag(self):
Expand Down
16 changes: 11 additions & 5 deletions pymatbridge/tests/test_magic.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,30 @@
import os

import pymatbridge as pymat
import IPython
from IPython.testing.globalipapp import get_ipython

import numpy.testing as npt


class TestMagic:

# Create an IPython shell and load Matlab magic
@classmethod
def setup_class(cls):
cls.ip = IPython.InteractiveShell()
cls.ip = get_ipython()
cls.ip.run_cell('import random')
cls.ip.run_cell('import numpy as np')
pymat.load_ipython_extension(cls.ip)
if 'USE_OCTAVE' in os.environ:
matlab = 'octave'
else:
matlab = 'matlab'
pymat.load_ipython_extension(cls.ip, matlab=matlab)

# Unload the magic, shut down Matlab
# Unload the magic
@classmethod
def teardown_class(cls):
pymat.unload_ipython_extension(cls.ip)


# Test single operation on different data structures
def test_cell_magic_number(self):
# A double precision real number
Expand Down
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ def copy_bin(bin_path):

for copy_this in ["./messenger/mexmaci64/messenger.mexmaci64",
"./messenger/mexa64/messenger.mexa64",
"./messenger/mexw64/messenger.mexw64"]:
"./messenger/mexw64/messenger.mexw64",
"./messenger/octave/messenger.mex"]:
copy_bin(copy_this)

# Get version and release info, which is all stored in pymatbridge/version.py
Expand Down