diff --git a/doc/logo/Makefile b/doc/logo/Makefile new file mode 100644 index 00000000..46c36fda --- /dev/null +++ b/doc/logo/Makefile @@ -0,0 +1,11 @@ +.PHONY: logo + +logo: green_orange_snake.png snake_logo.svg + inkscape --export-png=scikits_image_logo.png --export-dpi=100 \ + --export-area-drawing --export-background-opacity=1 \ + snake_logo.svg + python shrink_logo.py + +green_orange_snake.png: + python scikits_image_logo.py --no-plot + diff --git a/doc/logo/data/scipy.png b/doc/logo/data/scipy.png new file mode 100644 index 00000000..72fa0508 Binary files /dev/null and b/doc/logo/data/scipy.png differ diff --git a/doc/logo/data/snake_pixabay.jpg b/doc/logo/data/snake_pixabay.jpg new file mode 100644 index 00000000..cb3c337c Binary files /dev/null and b/doc/logo/data/snake_pixabay.jpg differ diff --git a/doc/logo/scikits_image_logo.py b/doc/logo/scikits_image_logo.py new file mode 100644 index 00000000..e96a7f25 --- /dev/null +++ b/doc/logo/scikits_image_logo.py @@ -0,0 +1,226 @@ +""" +Script to draw scikits.image logo using Scipy logo as stencil. The easiest +starting point is the `plot_colorized_logo`; the "if-main" demonstrates its use. + +Original snake image from pixabay [1]_ + +.. [1] http://pixabay.com/en/snake-green-toxic-close-yellow-3237/ +""" +import numpy as np +import matplotlib.pyplot as plt +import scipy.misc + +import scikits.image.io as sio +import scikits.image.filter as imfilt + +import scipy_logo + + +# Utility functions +# ================= + +def get_edges(img): + edge = np.empty(img.shape) + if len(img.shape) == 3: + for i in range(3): + edge[:, :, i] = imfilt.sobel(img[:, :, i]) + else: + edge = imfilt.sobel(img) + edge = rescale_intensity(edge) + return edge + +def rescale_intensity(img): + i_range = float(img.max() - img.min()) + img = (img - img.min()) / i_range * 255 + return np.uint8(img) + +def colorize(img, color, whiten=False): + """Return colorized image from gray scale image + + Parameters + ---------- + img : N x M array + grayscale image + color : length-3 sequence of floats + RGB color spec. Float values should be between 0 and 1. + whiten : bool + If True, a color value less than 1 increases the image intensity. + """ + color = np.asarray(color)[np.newaxis, np.newaxis, :] + img = img[:, :, np.newaxis] + if whiten: + # truncate and stretch intensity range to enhance contrast + img = np.clip(img, 80, 255) + img = rescale_intensity(img) + return np.uint8(color * (255 - img) + img) + else: + return np.uint8(img * color) + + +def prepare_axes(ax): + plt.sca(ax) + ax.xaxis.set_visible(False) + ax.yaxis.set_visible(False) + for spine in ax.spines.itervalues(): + spine.set_visible(False) + + +_rgb_stack = np.ones((1, 1, 3), dtype=bool) +def gray2rgb(arr): + """Return RGB image from a grayscale image. + + Expand h x w image to h x w x 3 image where color channels are simply copies + of the grayscale image. + """ + return arr[:, :, np.newaxis] * _rgb_stack + + +# Logo generating classes +# ======================= + +class LogoBase(object): + + def __init__(self): + self.logo = scipy_logo.ScipyLogo(radius=self.radius) + self.mask_1 = self.logo.get_mask(self.img.shape, 'upper left') + self.mask_2 = self.logo.get_mask(self.img.shape, 'lower right') + self.edges = get_edges(self.img) + # truncate and stretch intensity range to enhance contrast + self.edges = np.clip(self.edges, 0, 100) + self.edges = rescale_intensity(self.edges) + + + def _crop_image(self, img): + w = 2 * self.radius + x, y = self.origin + return img[y:y+w, x:x+w] + + def get_canvas(self): + return 255 * np.ones(self.img.shape, dtype=np.uint8) + + def plot_curve(self, **kwargs): + self.logo.plot_snake_curve(**kwargs) + + +class SnakeLogo(LogoBase): + + def __init__(self): + self.radius = 250 + self.origin = (420, 0) + img = sio.imread('data/snake_pixabay.jpg') + img = self._crop_image(img) + + img = img.astype(float) * 1.1 + img[img > 255] = 255 + self.img = img.astype(np.uint8) + + LogoBase.__init__(self) + + +snake_color = SnakeLogo() +snake = SnakeLogo() +# turn RGB image into gray image +snake.img = np.mean(snake.img, axis=2) +snake.edges = np.mean(snake.edges, axis=2) + + +# Demo plotting functions +# ======================= + +def plot_colorized_logo(logo, color, edges='light', switch=False, whiten=False): + """Convenience function to plot artificially colored logo. + + Parameters + ---------- + logo : subclass of LogoBase + color : length-3 sequence of floats + RGB color spec. Float values should be between 0 and 1. + edges : {'light'|'dark'} + Specifies whether Sobel edges are drawn light or dark + switch : bool + If False, the image is drawn on the southeast half of the Scipy curve + and the edge image is drawn on northwest half. + whiten : bool + If True, a color value less than 1 increases the image intensity. + """ + if not hasattr(color[0], '__iter__'): + color = [color] * 2 + if not hasattr(whiten, '__iter__'): + whiten = [whiten] * 2 + img = gray2rgb(logo.get_canvas()) + mask_img = gray2rgb(logo.mask_2) + mask_edge = gray2rgb(logo.mask_1) + if switch: + mask_img, mask_edge = mask_edge, mask_img + if edges == 'dark': + lg_edge = colorize(255 - logo.edges, color[0], whiten=whiten[0]) + else: + lg_edge = colorize(logo.edges, color[0], whiten=whiten[0]) + lg_img = colorize(logo.img, color[1], whiten=whiten[1]) + img[mask_img] = lg_img[mask_img] + img[mask_edge] = lg_edge[mask_edge] + logo.plot_curve(lw=5, color='w') + plt.imshow(img) + + +def red_light_edges(logo, **kwargs): + plot_colorized_logo(logo, (1, 0, 0), edges='light', **kwargs) + + +def red_dark_edges(logo, **kwargs): + plot_colorized_logo(logo, (1, 0, 0), edges='dark', **kwargs) + +def blue_light_edges(logo, **kwargs): + plot_colorized_logo(logo, (0.35, 0.55, 0.85), edges='light', **kwargs) + + +def blue_dark_edges(logo, **kwargs): + plot_colorized_logo(logo, (0.35, 0.55, 0.85), edges='dark', **kwargs) + + +def green_orange_light_edges(logo, **kwargs): + colors = ((0.6, 0.8, 0.3), (1, 0.5, 0.1)) + plot_colorized_logo(logo, colors, edges='light', **kwargs) + +def green_orange_dark_edges(logo, **kwargs): + colors = ((0.6, 0.8, 0.3), (1, 0.5, 0.1)) + plot_colorized_logo(logo, colors, edges='dark', **kwargs) + + +if __name__ == '__main__': + + import sys + plot = False + if len(sys.argv) < 2 or sys.argv[1] != '--no-plot': + plot = True + + print "Run with '--no-plot' flag to generate logo silently." + + def plot_all(): + plotters = (red_light_edges, red_dark_edges, + blue_light_edges, blue_dark_edges, + green_orange_light_edges, green_orange_dark_edges) + + f, axes_array = plt.subplots(nrows=2, ncols=len(plotters)) + for plot, ax_col in zip(plotters, axes_array.T): + prepare_axes(ax_col[0]) + plot(snake) + prepare_axes(ax_col[1]) + plot(snake, whiten=True) + plt.tight_layout() + + def plot_snake(): + + f, ax = plt.subplots() + prepare_axes(ax) + green_orange_dark_edges(snake, whiten=(False, True)) + plt.savefig('green_orange_snake.png', bbox_inches='tight') + + if plot: + plot_all() + + plot_snake() + + if plot: + plt.show() + diff --git a/doc/logo/scipy_logo.py b/doc/logo/scipy_logo.py new file mode 100644 index 00000000..985fe77e --- /dev/null +++ b/doc/logo/scipy_logo.py @@ -0,0 +1,262 @@ +""" +Code used to trace Scipy logo. +""" +import numpy as np +import matplotlib.pyplot as plt +import scikits.image.io as imgio +from scipy.misc import lena +import matplotlib.nxutils as nx + + +class SymmetricAnchorPoint(object): + """Anchor point in a parametric curve with symmetric handles + + Parameters + ---------- + pt : length-2 sequence + (x, y) coordinates of anchor point + theta : float + angle of control handle + length : float + half-length of symmetric control handle. Each control point is `length` + distance away from the anchor point. + use_degrees : bool + If True, convert input `theta` from degrees to radians. + """ + + def __init__(self, pt, theta, length, use_degrees=False): + self.pt = pt + if use_degrees: + theta = theta * np.pi / 180 + self.theta = theta + self.length = length + + def control_points(self): + """Return control points for symmetric handles + + The first point is in the direction of theta and the second is directly + opposite. For example, if `theta = 0`, then the first `p1` will be + directly to the right of the anchor point, and `p2` will be directly + to the left. + """ + theta = self.theta + offset = self.length * np.array([np.cos(theta), np.sin(theta)]) + p1 = self.pt + offset + p2 = self.pt - offset + return p1, p2 + + def __repr__(self): + v = (self.pt, self.theta * 180/np.pi, self.length) + return 'SymmetricAnchorPoint(pt={0}, theta={1}, length={2})'.format(*v) + + +def curve_from_anchor_points(pts): + """Return curve from a list of SymmetricAnchorPoints""" + assert len(pts) > 1 + bezier_pts = [] + for anchor in pts: + c1, c2 = anchor.control_points() + bezier_pts.extend([c2, anchor.pt, c1]) + # clip control points from ends + bezier_pts = bezier_pts[1:-1] + x, y = [], [] + # every third point is an anchor point + for i in range(0, len(bezier_pts)-1, 3): + xi, yi = cubic_curve(*bezier_pts[i:i+4]) + x.append(xi) + y.append(yi) + return np.hstack(x), np.hstack(y) + + +def cubic_curve(p0, p1, p2, p3, npts=20): + """Return points on a cubic Bezier curve + + Parameters + ---------- + p0, p3 : length-2 sequences + end points of curve + p1, p2 : length-2 sequences + control points of curve + npts : int + number of points to return (including end points) + + Returns + ------- + x, y : arrays + points on cubic curve + """ + t = np.linspace(0, 1, npts)[:, np.newaxis] + # cubic bezier curve from http://en.wikipedia.org/wiki/Bezier_curve + b = (1-t)**3 * p0 + 3*t*(1-t)**2 * p1 + 3*t**2*(1-t) * p2 + t**3 * p3 + x, y = b.transpose() + return x, y + + +class Circle(object): + + def __init__(self, center, radius): + self.center = center + self.radius = radius + + def point_from_angle(self, angle): + r = self.radius + # `angle` can be a scalar or 1D array: transpose twice for best results + pts = r * np.array((np.cos(angle), np.sin(angle))).T + self.center + return pts.T + + def plot(self, **kwargs): + ax = kwargs.pop('ax', plt.gca()) + fc = kwargs.pop('fc', 'none') + c = plt.Circle(self.center, self.radius, fc=fc, **kwargs) + ax.add_patch(c) + + +class ScipyLogo(object): + """Object to generate scipy logo + + Parameters + ---------- + center : length-2 array + the Scipy logo will be centered on this point. + radius : float + radius of logo + """ + + CENTER = np.array((254, 246)) + RADIUS = 252.0 + THETA_START = 2.58 + THETA_END = -0.368 + + def __init__(self, center=None, radius=None): + if center is None: + if radius is None: + center = self.CENTER + else: + center = np.array((radius, radius)) + self.center = center + if radius is None: + radius = self.RADIUS + self.radius = radius + + + # calculate end points of curve so that it lies exactly on circle + logo_circle = Circle(self.CENTER, self.RADIUS) + s_start = logo_circle.point_from_angle(self.THETA_START) + s_end = logo_circle.point_from_angle(self.THETA_END) + + self.circle = Circle(self.center, self.radius) + # note that angles are clockwise because of inverted y-axis + self._anchors = [SymmetricAnchorPoint(*t, use_degrees=True) + for t in [(s_start, -37, 90), + ((144, 312), 7, 20), + ((205, 375), 52, 50), + ((330, 380), -53, 60), + ((290, 260),-168, 50), + ((217, 245),-168, 50), + ((182, 118), -50, 60), + ((317, 125), 53, 60), + ((385, 198), 10, 20), + (s_end, -25, 60)]] + # normalize anchors so they have unit radius and are centered at origin + for a in self._anchors: + a.pt = (a.pt - self.CENTER) / self.RADIUS + a.length = a.length / self.RADIUS + + def snake_anchors(self): + """Return list of SymmetricAnchorPoints defining snake curve""" + anchors = [] + for a in self._anchors: + pt = self.radius * a.pt + self.center + length = self.radius * a.length + anchors.append(SymmetricAnchorPoint(pt, a.theta, length)) + return anchors + + def snake_curve(self): + """Return x, y coordinates of snake curve""" + return curve_from_anchor_points(self.snake_anchors()) + + def plot_snake_curve(self, **kwargs): + ax = kwargs.pop('ax', plt.gca()) + x, y = self.snake_curve() + ax.plot(x, y, 'k', **kwargs) + + def plot_circle(self, **kwargs): + self.circle.plot(**kwargs) + + def plot_image(self, **kwargs): + ax = kwargs.pop('ax', plt.gca()) + img = imgio.imread('data/scipy.png') + ax.imshow(img, **kwargs) + + def get_mask(self, shape, region): + """ + Parameters + ---------- + region : {'upper left', 'lower right'} + """ + if region == 'upper left': + theta = np.linspace(self.THETA_END, self.THETA_START - 2 * np.pi) + elif region == 'lower right': + theta = np.linspace(self.THETA_END, self.THETA_START) + else: + msg = "Expected 'upper left' or 'lower right'; got %s" % region + raise ValueError(msg) + xy_circle = self.circle.point_from_angle(theta).T + x, y = self.snake_curve() + xy_curve = np.array((x, y)).T + xy_poly = np.vstack((xy_curve, xy_circle)) + + h, w = shape[:2] + y_img, x_img = np.mgrid[:h, :w] + xy_points = np.column_stack((x_img.flat, y_img.flat)) + + mask = nx.points_inside_poly(xy_points, xy_poly) + return mask.reshape((h, w)) + + +def plot_scipy_trace(): + plt.figure() + logo = ScipyLogo() + logo.plot_snake_curve() + logo.plot_circle() + logo.plot_image() + plot_anchors(logo.snake_anchors()) + + +def plot_anchors(anchors, color='r', alpha=0.7): + for a in anchors: + c = a.control_points() + x, y = np.transpose(c) + plt.plot(x, y, 'o-', color=color, mfc='w', mec=color, alpha=alpha) + plt.plot(a.pt[0], a.pt[1], 'o', color=color, alpha=alpha) + + +def plot_snake_overlay(): + plt.figure() + logo = ScipyLogo((670, 250), 250) + logo.plot_snake_curve() + logo.plot_circle() + img = imgio.imread('data/snake_pixabay.jpg') + #mask = logo.get_mask(img.shape, 'upper left') + #img[mask] = 255 + plt.imshow(img) + + +def plot_lena_overlay(): + plt.figure() + logo = ScipyLogo((300, 300), 180) + logo.plot_snake_curve() + logo.plot_circle() + img = lena() + #mask = logo.get_mask(img.shape, 'upper left') + #img[mask] = 255 + plt.imshow(img) + + +if __name__ == '__main__': + plot_scipy_trace() + plot_snake_overlay() + plot_lena_overlay() + + plt.show() + diff --git a/doc/logo/shrink_logo.py b/doc/logo/shrink_logo.py new file mode 100644 index 00000000..f9c16e2c --- /dev/null +++ b/doc/logo/shrink_logo.py @@ -0,0 +1,16 @@ +from scikits.image import io, transform + +s = 0.7 + +img = io.imread('scikits_image_logo.png') +h, w, c = img.shape + +print "\nScaling down logo by %.1fx..." % s + +img = transform.homography(img, [[s, 0, 0], + [0, s, 0], + [0, 0, 1]], + output_shape=(int(h*s), int(w*s), 4), + order=3) + +io.imsave('scikits_image_logo_small.png', img) diff --git a/doc/logo/snake_logo.svg b/doc/logo/snake_logo.svg new file mode 100644 index 00000000..caa47694 --- /dev/null +++ b/doc/logo/snake_logo.svg @@ -0,0 +1,92 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + scikits-image + image processing in python + + + diff --git a/doc/source/_static/scikits_image_logo_small.png b/doc/source/_static/scikits_image_logo_small.png new file mode 100644 index 00000000..efd71678 Binary files /dev/null and b/doc/source/_static/scikits_image_logo_small.png differ diff --git a/doc/source/conf.py b/doc/source/conf.py index 34694a1d..c9959dd4 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -123,7 +123,7 @@ html_title = 'scikits.image v%s docs' % version # The name of an image file (relative to this directory) to place at the top # of the sidebar. -#html_logo = None +html_logo = "scikits_image_logo_small.png" # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 diff --git a/doc/source/themes/agogo/static/agogo.css_t b/doc/source/themes/agogo/static/agogo.css_t index 41aebe30..99d44d62 100644 --- a/doc/source/themes/agogo/static/agogo.css_t +++ b/doc/source/themes/agogo/static/agogo.css_t @@ -584,7 +584,7 @@ pre { background-color: {{ theme_codebgcolor }}; color: {{ theme_codetextcolor }}; line-height: 120%; - border: 1px solid #ac9; + border: 0px solid #ac9; border-left: none; border-right: none; }