{
  "cells": [
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "%matplotlib inline"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# Applying Textures {#texture_example}\n\nPlot a mesh with an image projected onto it as a texture.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "from matplotlib.cm import get_cmap\nimport numpy as np\n\nimport pyvista as pv\nfrom pyvista import examples"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Texture mapping is easily implemented using PyVista. Many of the\ngeometric objects come preloaded with texture coordinates, so quickly\ncreating a surface and displaying an image is simply:\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "# load a sample texture\ntex = examples.download_masonry_texture()\n\n# create a surface to host this texture\nsurf = pv.Cylinder()\n\nsurf.plot(texture=tex)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "But what if your dataset doesn\\'t have texture coordinates? Then you can\nharness the\n`pyvista.DataSetFilters.texture_map_to_plane`{.interpreted-text\nrole=\"func\"} filter to properly map an image to a dataset\\'s surface.\nFor example, let\\'s map that same image of bricks to a curvey surface:\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "# create a structured surface\nx = np.arange(-10, 10, 0.25)\ny = np.arange(-10, 10, 0.25)\nx, y = np.meshgrid(x, y)\nr = np.sqrt(x**2 + y**2)\nz = np.sin(r)\ncurvsurf = pv.StructuredGrid(x, y, z)\n\n# Map the curved surface to a plane - use best fitting plane\ncurvsurf.texture_map_to_plane(inplace=True)\n\ncurvsurf.plot(texture=tex)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Display scalar data along with a texture by ensuring the\n`interpolate_before_map` setting is `False` and specifying both the\n`texture` and `scalars` arguments.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "elevated = curvsurf.elevation()\n\nelevated.plot(scalars='Elevation', cmap='terrain', texture=tex, interpolate_before_map=False)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Note that this process can be completed with any image texture.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "# use the puppy image\ntex = examples.download_puppy_texture()\ncurvsurf.plot(texture=tex)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# Textures from Files\n\nWhat about loading your own texture from an image? This is often most\neasily done using the `pyvista.read_texture`{.interpreted-text\nrole=\"func\"} function - simply pass an image file\\'s path, and this\nfunction with handle making a `vtkTexture` for you to use.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "image_file = examples.mapfile\ntex = pv.read_texture(image_file)\ncurvsurf.plot(texture=tex)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# NumPy Arrays as Textures\n\nWant to use a programmatically built image?\n`pyvista.ImageData`{.interpreted-text role=\"class\"} objects can be\nconverted to textures using `pyvista.image_to_texture`{.interpreted-text\nrole=\"func\"} and 3D NumPy (X by Y by RGB) arrays can be converted to\ntextures using `pyvista.numpy_to_texture`{.interpreted-text\nrole=\"func\"}.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "# create an image using numpy,\nxx, yy = np.meshgrid(np.linspace(-200, 200, 20), np.linspace(-200, 200, 20))\nA, b = 500, 100\nzz = A * np.exp(-0.5 * ((xx / b) ** 2.0 + (yy / b) ** 2.0))\n\n# Creating a custom RGB image\ncmap = get_cmap(\"nipy_spectral\")\nnorm = lambda x: (x - np.nanmin(x)) / (np.nanmax(x) - np.nanmin(x))\nhue = norm(zz.ravel())\ncolors = (cmap(hue)[:, 0:3] * 255.0).astype(np.uint8)\nimage = colors.reshape((xx.shape[0], xx.shape[1], 3), order=\"F\")\n\n# Convert 3D numpy array to texture\ntex = pv.numpy_to_texture(image)\n\n# Render it\ncurvsurf.plot(texture=tex)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# Create a GIF Movie with updating textures\n\nGenerate a moving gif from an active plotter with updating textures.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "mesh = curvsurf.extract_surface()\n\n# Create a plotter object\nplotter = pv.Plotter(notebook=False, off_screen=True)\n\nactor = plotter.add_mesh(mesh, smooth_shading=True, color=\"white\")\n\n# Open a gif\nplotter.open_gif(\"texture.gif\")\n\n# Update Z and write a frame for each updated position\nnframe = 15\nfor phase in np.linspace(0, 2 * np.pi, nframe + 1)[:nframe]:\n    # create an image using numpy,\n    z = np.sin(r + phase)\n    mesh.points[:, -1] = z.ravel()\n\n    # Creating a custom RGB image\n    zz = A * np.exp(-0.5 * ((xx / b) ** 2.0 + (yy / b) ** 2.0))\n    hue = norm(zz.ravel()) * 0.5 * (1.0 + np.sin(phase))\n    colors = (cmap(hue)[:, 0:3] * 255.0).astype(np.uint8)\n    image = colors.reshape((xx.shape[0], xx.shape[1], 3), order=\"F\")\n\n    # Convert 3D numpy array to texture\n    actor.texture = pv.numpy_to_texture(image)\n\n    # must update normals when smooth shading is enabled\n    mesh.compute_normals(cell_normals=False, inplace=True)\n    plotter.write_frame()\n    plotter.clear()\n\n# Closes and finalizes movie\nplotter.close()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# Textures with Transparency\n\nTextures can also specify per-pixel opacity values. The image must\ncontain a 4th channel specifying the opacity value from 0\n\\[transparent\\] to 255 \\[fully visible\\]. To enable this feature just\npass the opacity array as the 4th channel of the image as a 3\ndimensional matrix with shape \\[nrows, ncols, 4\\]\n`pyvista.numpy_to_texture`{.interpreted-text role=\"func\"}.\n\nHere we can download an image that has an alpha channel:\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "rgba = examples.download_rgba_texture()\nrgba.n_components"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "# Render it\ncurvsurf.plot(texture=rgba, show_grid=True)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# Repeating Textures\n\nWhat if you have a single texture that you\\'d like to repeat across a\nmesh? Simply define the texture coordinates for all nodes explicitly.\n\nHere we create the texture coordinates to fill up the grid with several\nmappings of a single texture. In order to do this we must define texture\ncoordinates outside of the typical `(0, 1)` range:\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "axial_num_puppies = 4\nxc = np.linspace(0, axial_num_puppies, curvsurf.dimensions[0])\nyc = np.linspace(0, axial_num_puppies, curvsurf.dimensions[1])\n\nxxc, yyc = np.meshgrid(xc, yc)\npuppy_coords = np.c_[yyc.ravel(), xxc.ravel()]"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "By defining texture coordinates that range `(0, 4)` on each axis, we\nwill produce 4 repetitions of the same texture on this mesh.\n\nThen we must associate those texture coordinates with the mesh through\nthe `pyvista.DataSet.active_texture_coordinates`{.interpreted-text\nrole=\"attr\"} property.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "curvsurf.active_texture_coordinates = puppy_coords"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Now display all the puppies.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "# use the puppy image\ntex = examples.download_puppy_texture()\ncurvsurf.plot(texture=tex, cpos=\"xy\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# Spherical Texture Coordinates\n\nWe have a built in convienance method for mapping textures to spherical\ncoordinate systems much like the planar mapping demoed above.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "mesh = pv.Sphere()\ntex = examples.download_masonry_texture()\n\nmesh.texture_map_to_sphere(inplace=True)\nmesh.plot(texture=tex)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "The helper method above does not always produce the desired texture\ncoordinates, so sometimes it must be done manually. Here is a great,\nuser contributed example from [this support\nissue](https://github.com/pyvista/pyvista-support/issues/257)\n\nManually create the texture coordinates for a globe map. First, we\ncreate the mesh that will be used as the globe. Note the\n[start_theta]{.title-ref} for a slight overlappig\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "sphere = pv.Sphere(\n    radius=1,\n    theta_resolution=120,\n    phi_resolution=120,\n    start_theta=270.001,\n    end_theta=270,\n)\n\n# Initialize the texture coordinates array\nsphere.active_texture_coordinates = np.zeros((sphere.points.shape[0], 2))\n\n# Populate by manually calculating\nfor i in range(sphere.points.shape[0]):\n    sphere.active_texture_coordinates[i] = [\n        0.5 + np.arctan2(-sphere.points[i, 0], sphere.points[i, 1]) / (2 * np.pi),\n        0.5 + np.arcsin(sphere.points[i, 2]) / np.pi,\n    ]\n\n# And let's display it with a world map\ntex = examples.load_globe_texture()\nsphere.plot(texture=tex)"
      ]
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3.12.2"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}