How to Annotate an Image in LaTeX

annotate an image in LaTeX using TikZ
  • This tutorial is about annotating images in LaTeX using TikZ and Pgfplots packages. It reviews different methods and issues that you may face when you overlying text over an image (PNG, JPEG, PDF or any supported file). Not only writing text into a graphic but also drawing arrowsshapes and overlying two images, one over another!
  • The Basic Idea: To annotate a picture using TikZ, we need to follow 3 simple steps: 
       Step 1: Include the image in a node.
       Step 2: Add axis to simplify labels' positioning.
       Step 3: Add labels, shapes and arrows.

Let's get into the detail!

Step 1: Include an image in TikZ

In LaTeX, we use \includegraphics command to add an image which requires loading the package graphicx. In TikZ, we will use the same command inside a node. Here is an illustrative example:

\documentclass{standalone}

\usepackage{tikz}

\begin{document}

\begin{tikzpicture}

	\node (image) at (0,0) {
		\includegraphics[width=\textwidth]{example-image}
	};

\end{tikzpicture}

\end{document}
Tikz Node Image

Image added inside a node in TikZ

Comments:

- We loaded the TikZ package using \usepackage{tikz}. It should be noted that there is no need to load graphicx package as it is already loaded by TikZ.

- We created a node at the point with coordinates (0,0) and we named it (image)

- The image, with name (image), is added using \includegraphics command with a width equal to the text width of the document. I invite you to check the set of images that can be used for creating a minimal working example (MWE).

  • Interesting question: I am having trouble locating points in TikZ, Where is the (0,0) with respect to the image? By default, the (0,0) is located at the center of the image. We are talking about the point (0,0) as it's the one used to create the node. For example, if you create a node at the point (x,y) then (x,y) is located at the center of the image. 

a. Positioning Nodes using Anchors

For easier positioning, It is better to put the origin at the bottom left corner of the image. This can be achieved by modifying the anchor of the node as follows: 

\documentclass{standalone}

\usepackage{tikz}

\begin{document}

\begin{tikzpicture}

\node[anchor=south west] (image) at (0,0) {
	\includegraphics[width=\textwidth]{example-image}
};

\end{tikzpicture}

\end{document}

We simply added the option anchor=south west to the \node command. Here is a list of different cases. We used the word text instead of an image without loss of generality.

Positioning node using anchors 1

Positioning nodes using anchor mechanism 1

Positioning nodes using anchor mechanism 2

Positioning node using anchors 3

Positioning nodes using anchor mechanism 3

Positioning node using anchors 4

Positioning nodes using anchor mechanism 4

To be honest, I prefer to use the basic placement options instead of the anchoring mechanism as It provides the location of the text (or the image) with respect to the coordinates. Here is a list that compares both positioning approaches and for more details, check the manual "section: Positioning Nodes".

Basic Placement options node Tikz 1

Basic placement VS anchor mechanism 1

Basic Placement options node Tikz 2

Basic placement VS anchor mechanism 2

Basic Placement options node Tikz 3

Basic placement VS anchor mechanism 3

Basic Placement options node Tikz 4

Basic placement VS anchor mechanism 4

Using the basic placement option, we put the image above and right of the (0,0). Here is the corresponding code in this case:

\documentclass{standalone}

\usepackage{tikz}

\begin{document}

\begin{tikzpicture}

\node[above right] (image) at (0,0) {
	\includegraphics[width=\textwidth]{example-image}
};

\end{tikzpicture}

\end{document}

It should be noted that above right is different than above,right. In the latter, the option right will be used as it is the last option for positioning. 

b. Remove Inner Separation

You may remarked in the previous illustration a small white margin induced by the node command. It can be controlled by adding the option inner separation=value to the \node command. This allows us to precisely put the origin at the bottom left corner of the image. 

Inner separation tikz latex

Setting the inner separation in TikZ  

Here is the final code of step 1 (include the image in a node):

\documentclass{standalone}

\usepackage{tikz}

\begin{document}

\begin{tikzpicture}

\node[above right, inner sep=0] (image) at (0,0) {
	\includegraphics[width=\textwidth]{example-image}
};

\end{tikzpicture}

\end{document}

Step 2: Image with axes

Now, we need to add axes with a grid in order to be able to put labels at specific positions in the image. These positions are relative to the image size which allows us to resize our illustration without any issues (arrows and shapes will also be resized automatically).  

This can be done using a scope environment as below:

 \documentclass{standalone}

\usepackage{tikz}
\usetikzlibrary{calc}

\begin{document}

\begin{tikzpicture}

% Include the image in a node
\node [
	above right,
	inner sep=0] (image) at (0,0) {\includegraphics[width=\textwidth]{example-image}};

% Create scope with normalized axes
\begin{scope}[
x={($0.1*(image.south east)$)},
y={($0.1*(image.north west)$)}]

% Grid
	\draw[lightgray,step=1] (image.south west) grid (image.north east);
\end{scope}

\end{tikzpicture}

\end{document}

where we get an image with 10x10 grid: 

Grid on image in TikZ

Comments:

- In the scope, we specified x=<value> and y=<value> for axes units. The x-axis unit is chosen one tenth the image width. The y-axis unit is chosen one tenth the image height.

- The image width and height are obtained from (image.south east) and (image.north west), respectively.

- For coordinate calculations, we loaded the TikZ library calc. It allows us to choose the one tenth of the image size as units for the axes.

- We created a grid using the command grid between the image corners: (image.south west) and (image.north east).

- Grid lines are chosen with a light gray color and a step of 1 (normalized unit). For a more precision, you can modify the step to a smaller value.

Let's add labels and as it is a repetitive thing we will use a for loop (read this post to learn how to use foreach loop).

Here is the final code of step 2 (add axes and labels):

 \documentclass{standalone}

\usepackage{tikz}
\usetikzlibrary{calc}

\begin{document}

\begin{tikzpicture}

% Include the image in a node
\node [
	above right,
	inner sep=0] (image) at (0,0) {\includegraphics[width=\textwidth]{example-image}};

% Create scope with normalized axes
\begin{scope}[
	x={($0.1*(image.south east)$)},
	y={($0.1*(image.north west)$)}]

% Grid
	\draw[lightgray,step=1] (image.south west) grid (image.north east);

% Axes' labels
	\foreach \x in {0,1,...,10} { \node [below] at (\x,0) {\x}; }
	\foreach \y in {0,1,...,10} { \node [left] at (0,\y) {\y};}

\end{scope}

\end{tikzpicture}

\end{document}

and the corresponding image:

Grid on image and axes

Comments:

- We used two \foreach loops, one for the x-axis labels and one for the y-axis labels. The loop counter varies from 0 to 10 with an increment equal to 1.

- The loop counter (\x and \y) is used for coordinates and for printing labels.

- At each value of the loop counter of the x-axis, we add a node below the coordinates by providing the option below, check above the basic placement options.

- The same for the y-axis, we use the option left to print labels on the left of the y-axis.

Labels, circles and arrows will be added inside the scope in question, which is the aim of the next section!

Step 3: Add labels, circles and arrows

In this part, we will learn how to add different labels to annotate our illustration. Let's start by the first label "circled number". 

The line code:

\node[circle,fill=green] at (7.25,6.75){\small 2};

adds a node at the point with coordinates (7.25,675). The node shape is a circle filled with a green color (check this post for predefined shapes and this post for predefined colors). 

The line code:

\draw[stealth-, very thick,green] (5,1.75) -- ++(0.5,-0.5) node[right,black,fill=white]{\small Dspace card};

- draws a very thick arrow with a green color. The arrow tip style is specified by the option stealth-, we can use also latex-.

- stealth- means the arrow tip is on the starting point of the straight line, -stealth means that the arrow tip is on the end point and stealth-stealth means the straight line has arrow tips on both extremities.

- The arrow starts from the point with coordinates (5,1.75) and increased by 0.5 along the x-axis and decreased by 0.5 along the y-axis.

- We added a node on the right of the end point of the straight line (we used basic placement for that).

- The node contains a small text "Dspace card" which is written with black color.
By default, the node shape is a rectangle (we change it to a circle in the previous line code), we filled it with a white color using the option fill=white.

The line code:

\draw[latex-, very thick,green] (2.5,1) -- ++(-0.5,0) node[left,black,fill=white]{\small Voltage source};

adds the label "Voltage source" to the illustration. In this case, we used the arrow tip latex-.

The line code:

\draw[very thick,green] (0.5,2.5) rectangle (4,9.5) node[below left,black,fill=green]{\small 1};

- draws a rectangle from its two opposite corners ((0.5,2.5) and (4,9.5)). 

- The rectangle is drawn with a green very thick line (check the next illustration about different options of line thickness).

- We added a node at the bottom left of the point (4,9.5). It is filled with green color and it has the rectangular shape (by default).

Line width in TikZ

Line width options

Here is the code of the annotated image (you can comment the grid and axes' labels to get the final results). In the next section, we will highlight another method based on Pgfplots package

Final code of step 3 (add labels, shapes and arrows):

 \documentclass{standalone}

\usepackage{tikz}
\usetikzlibrary{calc}

\begin{document}

\begin{tikzpicture}

% Include the image in a node
\node [
	above right,
	inner sep=0] (image) at (0,0) {\includegraphics[width=\textwidth]{example-image}};

% Create scope with normalized axes
\begin{scope}[
x={($0.1*(image.south east)$)},
y={($0.1*(image.north west)$)}]

% Grid
	\draw[lightgray,step=1] (image.south west) grid (image.north east);

% Axes' labels
	\foreach \x in {0,1,...,10} { \node [below] at (\x,0) {\x}; }
	\foreach \y in {0,1,...,10} { \node [left] at (0,\y) {\y};}

% Labels
	\node[circle,fill=green] at (7.25,6.75){\small 2};

	\draw[latex-, very thick,green] (2.5,1) -- ++(-0.5,0)
		node[left,black,fill=white]{\small Voltage source};

	\draw[stealth-, very thick,green] (5,1.75) -- ++(0.5,-0.5)
		node[right,black,fill=white]{\small Dspace card};

	\draw[very thick,green] (0.5,2.5) rectangle (4,9.5) 
		node[below left,black,fill=green]{\small 1};

	\draw[latex-, very thick,green] (5.5,4) edge (5.5,5.5)
		(5.75,4.5) -- (5.5,5.5)
		node[above,black,fill=white]{\small R-L load};
\end{scope}

\end{tikzpicture}

\end{document}

Can we do the same using Pgfplots?

I published a post about plotting functions in LaTeX using Pgfplots, I deeply invite you to check it. Besides plotting functions and data, Pgfplots package has the feature to include external graphics. The above annotated image is recreated using Pgfplots package and here is the LaTeX code:

 \documentclass{standalone}

\usepackage{pgfplots}
\pgfplotsset{compat = newest}

\begin{document}

\begin{tikzpicture}

\begin{axis}
[
	xmin = 0, xmax = 10,
	ymin = 0, ymax = 10,
	width =\textwidth,
	height =0.6\textwidth,
	scale only axis,
	grid = both,
	axis on top,
	%hide axis
]

% Add the image 
\addplot graphics[xmin=0,ymin=0,xmax=10,ymax=10] {ExpSetup};

% Labels
	\node[circle,fill=green] at (axis cs:7.25,6.75){\small 2};

	\draw[latex-, very thick,green] (axis cs:2.5,1) -- ++(-0.5,0)
		node[left,black,fill=white]{\small Voltage source};

	\draw[stealth-, very thick,green] (axis cs:5,1.75) -- ++(0.5,-0.5)
		node[right,black,fill=white]{\small Dspace card};

	\draw[very thick,green] (axis cs:0.5,2.5) rectangle (axis cs:4,9.5) 
		node[below left,black,fill=green]{\small 1};

	\draw[latex-, very thick,green] (axis cs:5.5,4) edge (axis cs:5.5,5.5)
		(axis cs:5.75,4.5) -- (axis cs:5.5,5.5)
		node[above,black,fill=white]{\small R-L load};
\end{axis}

\end{tikzpicture}

\end{document}

Comments:

Inside the axis environment we created an axis with the options:
- xmin =0, xmax=10, ymin=0, ymax=10: to set axes min and max labels.

- width=\textwidth, height=0.6\textwidth: to fix the figure size

- scale only axis: to force the axis box to fills the above desired dimensions. Without this option, the figure size corresponds to the axis box, axes' ticks and x-y labels.

- grid=both: to add a grid to the axis box.

- axis on top: without this option the grid will be hidden by the image

- hide axis: this option should be commented to display the grid. once we annotate our illustration we will use this option to hide the axis box, ticks and the grid.

The image is added in Pgfplots using the line code:

\addplot graphics[xmin=0,ymin=0,xmax=10,ymax=10] {ExpSetup};

  • In Pgfplots, coordinates can be accessed using the syntax (axis cs: x,y). Hence, adding labels is the same as in the previous method with slight modification. Instead of using the point with coordinates (x,y), we use (axis cs: x,y). That's all!

For now, we reached the end of this tutorial and I hope you learned something ❤️. The post will be extended in near future to include these points: 

  • How to superimpose data to a given graph provided in an image format.
  • Useful packages for figures annotation in LaTeX such as tikz-imagelabels package and overpic package.
  • Online LaTeX overlay generator.

If you have any remarks or suggestions, please feel free to use the comment section, I will be happy to hear from you!

Thanks and Stay safe!

4 thoughts on “How to Annotate an Image in LaTeX”

Comments are closed.