Skip to content
Open
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
146 changes: 124 additions & 22 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,36 +1,136 @@
#!/usr/bin/env python2.7

import glob
import numpy as np
import os
import sys
from distutils.command.build_ext import build_ext
from setuptools import setup
from setuptools.extension import Extension


try:
import _tkinter
except (ImportError, OSError):
# pypy emits an oserror
_tkinter = None


# Borrowed from https://bitbucket.org/django/django/src/tip/setup.py
def fullsplit(path, result=None):
"""
Split a pathname into components (the opposite of os.path.join) in a
platform-neutral way.
"""
if result is None:
result = []
head, tail = os.path.split(path)
if head == "":
return [tail] + result
if head == path:
return result
return fullsplit(head, [tail] + result)


def _add_directory(path, dir, where=None):
if dir is None:
return
dir = os.path.realpath(dir)
if os.path.isdir(dir) and dir not in path:
if where is None:
path.append(dir)
else:
path.insert(where, dir)


class sgdf_build_ext(build_ext):
def _find_library_file(self, library):
# Fix for 3.2.x <3.2.4, 3.3.0, shared lib extension is the python shared
# lib extension, not the system shared lib extension: e.g. .cpython-33.so
# vs .so. See Python bug http://bugs.python.org/16754
if 'cpython' in self.compiler.shared_lib_extension:
existing = self.compiler.shared_lib_extension
self.compiler.shared_lib_extension = "." + existing.split('.')[-1]
ret = self.compiler.find_library_file(
self.compiler.library_dirs, library)
self.compiler.shared_lib_extension = existing
return ret
else:
return self.compiler.find_library_file(
self.compiler.library_dirs, library)

def build_extensions(self):
# Extra support for _numpytk native library
# Borrowed from https://github.com/python-pillow/Pillow/blob/master/setup.py

exts = []

if _tkinter:
TCL_VERSION = _tkinter.TCL_VERSION[:3]

version = TCL_VERSION[0] + TCL_VERSION[2]
if self._find_library_file("tcl" + version):
tcl_feature = "tcl" + version
elif self._find_library_file("tcl" + TCL_VERSION):
tcl_feature = "tcl" + TCL_VERSION
if self._find_library_file("tk" + version):
tk_feature = "tk" + version
elif self._find_library_file("tk" + TCL_VERSION):
tk_feature = "tk" + TCL_VERSION

if sys.platform == "darwin":
# locate Tcl/Tk frameworks
frameworks = []
framework_roots = ["/Library/Frameworks",
"/System/Library/Frameworks"]
for root in framework_roots:
root_tcl = os.path.join(root, "Tcl.framework")
root_tk = os.path.join(root, "Tk.framework")
if (os.path.exists(root_tcl) and os.path.exists(root_tk)):
print("--- using frameworks at %s" % root)
frameworks = ["-framework", "Tcl", "-framework", "Tk"]
dir = os.path.join(root_tcl, "Headers")
_add_directory(self.compiler.include_dirs, dir, 0)
dir = os.path.join(root_tk, "Headers")
_add_directory(self.compiler.include_dirs, dir, 1)
break
assert frameworks, "Cannot find Tcl/Tk headers"
if frameworks:
exts.append(Extension("sgdf.gui._numpytk",
glob.glob(os.path.join("sgdf", "gui", "_numpytk", "*")),
extra_compile_args=frameworks,
extra_link_args=frameworks,
include_dirs=[np.get_include(), "/usr/X11/include"]))
else:
assert tcl_feature and tk_feature
exts.append(Extension("sgdf.gui._numpytk",
glob.glob(os.path.join("sgdf", "gui", "_numpytk", "*")),
libraries=[tcl_feature, tk_feature],
include_dirs=[np.get_include()]))

self.extensions[:] = exts

build_ext.build_extensions(self)


if __name__ == "__main__":
# Borrowed from https://bitbucket.org/django/django/src/tip/setup.py
def fullsplit(path, result=None):
"""
Split a pathname into components (the opposite of os.path.join) in a
platform-neutral way.
"""
if result is None:
result = []
head, tail = os.path.split(path)
if head == "":
return [tail] + result
if head == path:
return result
return fullsplit(head, [tail] + result)

packages, data_files = [], []
packages, extensions, data_files = [], [], []
root_dir = os.path.dirname(__file__)
if root_dir != "":
os.chdir(root_dir)

for dirpath, dirnames, filenames in os.walk("sgdf"):
# Ignore dirnames that start with "."
for i, dirname in enumerate(dirnames):
if dirname.startswith("."):
del dirnames[i]
if "__init__.py" in filenames:
if os.path.basename(dirpath).startswith("_"):
# if os.path.basename(dirpath) == "_numpytk":
# # The _numpytk library is configured manually
# continue
c_sources = filter(lambda name: name.endswith(".c"), filenames)
print "Extension:", ".".join(fullsplit(dirpath))
extension = Extension(".".join(fullsplit(dirpath)),
map(lambda name: os.path.join(dirpath, name), c_sources),
include_dirs=[np.get_include()])
extensions.append(extension)
elif "__init__.py" in filenames:
packages.append(".".join(fullsplit(dirpath)))
elif filenames:
data_files.append([dirpath, [os.path.join(dirpath, f) for f in filenames]])
Expand All @@ -44,4 +144,6 @@ def fullsplit(path, result=None):
data_files=data_files,
install_requires=["matplotlib", "numpy", "scipy"],
test_suite="nose.collector",
tests_require=["nose", "unittest2"])
tests_require=["nose", "unittest2"],
cmdclass={"build_ext": sgdf_build_ext},
ext_modules=extensions)
99 changes: 99 additions & 0 deletions sgdf/gui/_numpytk/numpytk.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#include <stdlib.h>
#include "Python.h"
#include "numpy/arrayobject.h"
#include "tk.h"
#include "tk.h"

/* copied from _tkinter.c (this isn't as bad as it may seem: for new
versions, we use _tkinter's interpaddr hook instead, and all older
versions use this structure layout) */

typedef struct {
PyObject_HEAD
Tcl_Interp* interp;
} TkappObject;


static PyArrayObject *lookup_ndarray(const char *id) {
return (PyArrayObject *) atol(id);
}

static int PyNumpyTkPut(ClientData clientdata, Tcl_Interp* interp, int argc, const char **argv) {
Tk_PhotoImageBlock block;
Tk_PhotoHandle photo;
PyArrayObject *ndarray;

if (argc != 3) {
Tcl_AppendResult(interp, "usage: ", argv[0],
" destPhoto srcNdarray", (char *) NULL);
return TCL_ERROR;
}

photo = Tk_FindPhoto(interp, argv[1]);
if (photo == NULL) {
Tcl_AppendResult(interp, "destination photo must exist", (char *) NULL);
return TCL_ERROR;
}

ndarray = lookup_ndarray(argv[2]);

if (PyArray_NDIM(ndarray) != 3) {
Tcl_AppendResult(interp, "ndarray dimensions should be 3", (char *) NULL);
return TCL_ERROR;
}

npy_intp *ndarray_dims = PyArray_DIMS(ndarray);
if (ndarray_dims[2] != 3) {
Tcl_AppendResult(interp, "ndarray 3rd dimension should have size 3", (char *) NULL);
return TCL_ERROR;
}

npy_intp *ndarray_strides = PyArray_STRIDES(ndarray);

block.pixelSize = ndarray_strides[1];
block.offset[0] = 0;
block.offset[1] = 1;
block.offset[2] = 2;
block.offset[3] = 0;
block.height = ndarray_dims[0];
block.width = ndarray_dims[1];
block.pitch = ndarray_strides[0];
block.pixelPtr = (unsigned char *) PyArray_BYTES(ndarray);

return Tk_PhotoPutBlock(interp, photo, &block, 0, 0, block.width, block.height,
TK_PHOTO_COMPOSITE_SET);
}

static PyObject *_tkinit(PyObject *self, PyObject *args) {
Tcl_Interp *interp;

Py_ssize_t arg;
int is_interp;
if (!PyArg_ParseTuple(args, "ni", &arg, &is_interp))
return NULL;

if (is_interp)
interp = (Tcl_Interp *) arg;
else {
TkappObject *app;
/* Do it the hard way. This will break if the TkappObject
layout changes */
app = (TkappObject *) arg;
interp = app->interp;
}

Tcl_CreateCommand(interp, "PyNumpyTkPut", &PyNumpyTkPut, (ClientData) 0, (Tcl_CmdDeleteProc*) NULL);

Py_INCREF(Py_None);
return Py_None;
}

static PyMethodDef functions[] = {
{"tkinit", (PyCFunction)_tkinit, 1},
{NULL, NULL}
};

PyMODINIT_FUNC
init_numpytk(void) {
Py_InitModule("_numpytk", functions);
}
6 changes: 3 additions & 3 deletions sgdf/gui/editor/canvas.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Tkinter as tk
from PIL import Image, ImageTk
from sgdf.benchmarking import log_timer
from sgdf.gui.numpytk import PhotoImage


class EditorViewCanvas(tk.Canvas):
Expand All @@ -16,7 +16,7 @@ def draw_numpy(self, ndarray):
"""
with log_timer("EditorViewCanvas.draw_numpy"):
if self.active_image_container is None:
self.active_image_container = ImageTk.PhotoImage(Image.fromarray(ndarray))
self.active_image_container = PhotoImage(ndarray=ndarray)
self.itemconfig(self.active_image_id, image=self.active_image_container)
else:
self.active_image_container.paste(Image.fromarray(ndarray))
self.active_image_container.paste(ndarray=ndarray)
49 changes: 49 additions & 0 deletions sgdf/gui/numpytk.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import Tkinter as tk
import numpy as np


class PhotoImage(object):
def __init__(self, ndarray=None, size=None, **kwargs):
assert ndarray is not None or size is not None, "Please give me either a image or size"

if ndarray is not None:
size = ndarray.shape[:2]
elif size is not None:
ndarray = np.zeros(size)
self.__nparray = ndarray

kwargs["height"], kwargs["width"] = size[:2]
self.__size = self.__nparray.shape
self.__photo = tk.PhotoImage(**kwargs)
self.tk = self.__photo.tk
self.paste(ndarray)

def __str__(self):
"""
Get the Tkinter photo image identifier. This method is automatically called by Tkinter
whenever a PhotoImage object is passed to a Tkinter method.

"""
return str(self.__photo)

def width(self):
return self.__size[0]

def height(self):
return self.__size[1]

def paste(self, ndarray):
interp = self.__photo.tk
try:
interp.call("PyNumpyTkPut", self.__photo, id(ndarray))
except tk.TclError:
# activate Tkinter hook
try:
from sgdf.gui import _numpytk
try:
_numpytk.tkinit(interp.interpaddr(), 1)
except AttributeError:
_numpytk.tkinit(id(interp), 0)
interp.call("PyNumpyTkPut", self.__photo, id(ndarray))
except (ImportError, AttributeError, tk.TclError):
raise # configuration problem; cannot attach to Tkinter