How to make an enumerating in a foreach command along with its positioning in TikZ? - latex

I am using TikZ in LaTeX. I want to make a foreach to enumerate text in several position. Example;
Text 'Statement 1' in coordinate A;
Text 'Statement 2' in coordinate B;
Text 'Statement 3' in coordinate C;
Text 'Statement 4' in coordinate D;
Here is my code
\documentclass[12pt]{article}
\usepackage[utf8]{inputenc}
\usepackage{tikz}
\usetikzlibrary{intersections, angles, quotes}
\usepackage{pgffor}
\usepackage{amsfonts}
\usepackage{amsmath}
\usepackage{amssymb}
\begin{document}
\begin{center}
\begin{tikzpicture}
\draw[very thick] (7,5) rectangle (11,6.5);
\coordinate (A) at (9,5.75);
\node at (A) {IF-CONDITIONAL};
\draw[very thick, ->] (9,5)--(9,3);
\foreach \s in {3, -2.5, -8, -13.5} \draw[very thick]
(9,\s)--(6,{\s-2})--(9,{\s-4})--(12,{\s-2})--cycle;
\node at (9,1) {Condition 1 (\textit{true?})};
\node at (9,-4.5) {Condition 2 (\textit{true?})};
\node at (9,-10){Condition ... (\textit{true?})};
\node at (9,-15.5){Else (\textit{true?})};
\foreach \s in {-1, -6.5, -12} \draw[very thick, ->] (9,\s)--(9,{\s-1.5})
node[fill=white, midway]{NO};
\foreach \s in {1, -4.5, -10, -15.5} \draw[very thick, ->] (12,\s)--(14,\s)
node[fill=white, midway]{YES};
\foreach \s in {1, -4.5, -10, -15.5} \draw[very thick, ->] (19, \s)--(20, \s);
\foreach \x in {0,-5.5, -11, -16.5} \draw[very thick] (14,\x) rectangle (19,{\x+2});
\draw[very thick, ->] (9, -17.5)--(9,-19)--(20, -19);
\node[fill=white] at (9, -18.25){NO};
\draw[very thick, ->] (20, 1)--(20, -21);
\node at (16.5,1){Statement 1};
\end{tikzpicture}
\end{center}
\end{document}
This code produce the output
How to write the text 'Statement 1/2/3/4' in the blank boxes using foreach ?

To answer the question you ask: You can use the count option of \foreach
\documentclass[12pt]{article}
\usepackage[utf8]{inputenc}
\usepackage{tikz}
\usetikzlibrary{intersections, angles, quotes}
\usepackage{pgffor}
\usepackage{amsfonts}
\usepackage{amsmath}
\usepackage{amssymb}
\begin{document}
\begin{center}
\begin{tikzpicture}
\draw[very thick] (7,5) rectangle (11,6.5);
\coordinate (A) at (9,5.75);
\node at (A) {IF-CONDITIONAL};
\draw[very thick, ->] (9,5)--(9,3);
\foreach \s in {3, -2.5, -8, -13.5} \draw[very thick]
(9,\s)--(6,{\s-2})--(9,{\s-4})--(12,{\s-2})--cycle;
\node at (9,1) {Condition 1 (\textit{true?})};
\node at (9,-4.5) {Condition 2 (\textit{true?})};
\node at (9,-10){Condition ... (\textit{true?})};
\node at (9,-15.5){Else (\textit{true?})};
\foreach \s in {-1, -6.5, -12} \draw[very thick, ->] (9,\s)--(9,{\s-1.5})
node[fill=white, midway]{NO};
\foreach \s in {1, -4.5, -10, -15.5} \draw[very thick, ->] (12,\s)--(14,\s)
node[fill=white, midway]{YES};
\foreach \s in {1, -4.5, -10, -15.5} \draw[very thick, ->] (19, \s)--(20, \s);
\foreach[count=\xi] \x in {0,-5.5, -11, -16.5}{
\node[draw,very thick,minimum width=5cm,minimum height=2cm] at (16.5,\x+1) {Statement \xi};
}
\draw[very thick, ->] (9, -17.5)--(9,-19)--(20, -19);
\node[fill=white] at (9, -18.25){NO};
\draw[very thick, ->] (20, 1)--(20, -21);
\end{tikzpicture}
\end{center}
\end{document}
However instead of manually messing around with absolute coordinates, you could simply let tikz do the positioning for you:
\documentclass[12pt]{article}
\usepackage[utf8]{inputenc}
\usepackage{tikz}
\usetikzlibrary{intersections, angles, quotes,positioning,shapes.geometric}
\usepackage{pgffor}
\usepackage{amsfonts}
\usepackage{amsmath}
\usepackage{amssymb}
\begin{document}
\begin{center}
\begin{tikzpicture}[
scale=0.6,
transform shape,
very thick,
node distance=1.6cm and 3cm,
dia/.style={draw,diamond,shape aspect=1.3,minimum width=6cm,minimum height=4cm}
]
\node[draw,minimum height=1.5cm,minimum width=4cm] (A) {IF-CONDITIONAL};
\node[dia] (B1) [below=of A] {Condition 1 (\textit{true?})};
\node[dia] (B2) [below=of B1] {Condition 2 (\textit{true?})};
\node[dia] (B3) [below=of B2] {Condition ... (\textit{true?})};
\node[dia] (B4) [below=of B3] {Else (\textit{true?})};
\draw[ ->] (A) -- (B1);
\draw[ ->] (B1) -- (B2) node[fill=white,midway] {NO};
\draw[ ->] (B2) -- (B3) node[fill=white,midway] {NO};
\draw[ ->] (B3) -- (B4) node[fill=white,midway] {NO};
\foreach \x in {1,...,4}{
\node[draw,minimum width=5cm,minimum height=2cm] (C\x) [right=of B\x] {Statement \x};
\draw[ ->] (B\x) -- (C\x) node[fill=white,midway] {YES};
\draw[ ->] (C\x.east) -- ++(1cm,0);
}
\draw[ ->] (B4.south) |- ++(12cm,-1.5cm) node[fill=white,near start] {NO};
\draw[->] (C1) -| ([xshift=12cm,yshift=-3cm]B4.south);
\end{tikzpicture}
\end{center}
\end{document}

Related

Writing small numbers in Latex figures

I have a basic figure where I want the following numbers to be in the Y axis
0, 0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07, 0.08, 0.09, 0.1
When I write them in the code they appear in a different way as you see in the screenshot.
I know this is mathematically correct but they look odd and I want to them to be shown exactly as I put them in the code.
this is the code
\documentclass{article}
\usepackage{graphicx} % Required for inserting images
\usepackage{pgfplots}
\begin{document}
\begin{figure}
\centering
\begin{tikzpicture} [style={outer sep=5}]
\begin{axis}[ymin=0,ymax=0.1, xmin=15, xmax=50,
xtick={15,20,...,50},
ytick={0,0.01,...,0.1},
grid={major},clip=false,grid style={dashed},
title={},
legend style={at={(1.2 ,1)},anchor=north},
font=\footnotesize]
% 25
\addplot[color=green, mark=*, samples=10] [error bars/.cd,y dir=both, y explicit] coordinates {
(15, 0.0032)
(20, 0.0039)
(25, 0.0052)
(30, 0.007)
(35, 0.009)
(40, 0.0095)
(45, 0.01)
(50, 0.012)
};
\end{axis}
\end{tikzpicture}
\caption[]{}
\end{figure}
\end{document}
The other figure code is
\begin{figure} [H]
\centering
\begin{tikzpicture} [style={outer sep=5}]
\begin{axis}[ymin=0.004,ymax=0.018, xmin=15, xmax=50,
xlabel= $Number of Nodes$, ylabel = $end-to-end (sec)$,
xtick={15,20,...,50},
ytick={0.004,0.005,...,0.018},
yticklabel style={/pgf/number format/fixed},
grid={major},clip=false,grid style={dashed},
title={},
legend style={at={(1.2 ,1)},anchor=north},
font=\footnotesize]
% TAODV
\addplot[color=blue, mark=*, samples=10] [error bars/.cd,y dir=both, y explicit] coordinates {
(15, 0.0072) +-(0.001, -0.001)
(20, 0.0079) +-(0.001, -0.001)
(25, 0.0092) +-(0.001, -0.001)
(30, 0.01) +-(0.001, -0.001)
(35, 0.013) +-(0.0018, -0.0018)
(40, 0.0137) +-(0.001, -0.001)
(45, 0.015) +-(0.001, -0.001)
(50, 0.0168)+-(0.001, -0.001)
};
% AODV
\addplot[color=red, mark=*, samples=10] [error bars/.cd,y dir=both, y explicit] coordinates {
(15, 0.0052) +-(0.001, -0.001)
(20, 0.0057) +-(0.001, -0.001)
(25, 0.0067) +-(0.001, -0.001)
(30, 0.0074) +-(0.001, -0.001)
(35, 0.0082) +-(0.0018, -0.0018)
(40, 0.0095) +-(0.001, -0.001)
(45, 0.01) +-(0.001, -0.001)
(50, 0.0109) +-(0.001, -0.001)
};
\legend{
$TAODV$,
$AODV$
}
\end{axis}
\end{tikzpicture}
\caption[End-to-end delay of TAODV vs. AODV]{End-to-end delay of TAODV vs. AODV with \%95 confidence interval}
\end{figure}
You can use yticklabel style={/pgf/number format/fixed} to change the number format:
\documentclass{article}
\usepackage{graphicx} % Required for inserting images
\usepackage{pgfplots}
\begin{document}
\begin{figure}
\centering
\begin{tikzpicture} [style={outer sep=5}]
\begin{axis}[ymin=0,ymax=0.1, xmin=15, xmax=50,
xtick={15,20,...,50},
ytick={0,0.01,...,0.1},
grid={major},clip=false,grid style={dashed},
title={},
legend style={at={(1.2 ,1)},anchor=north},
font=\footnotesize,
scaled y ticks=false,
yticklabel style={/pgf/number format/fixed},
]
% 25
\addplot[color=green, mark=*, samples=10] [error bars/.cd,y dir=both, y explicit] coordinates {
(15, 0.0032)
(20, 0.0039)
(25, 0.0052)
(30, 0.007)
(35, 0.009)
(40, 0.0095)
(45, 0.01)
(50, 0.012)
};
\end{axis}
\end{tikzpicture}
\caption[]{}
\end{figure}
\end{document}

How can I place a figure on top of the page?

I am currently working on this code (this is a shortened version):
\documentclass{scrartcl}
\usepackage[utf8]{inputenc}
\usepackage{tikz}
\usetikzlibrary{calc}
\begin{document}
% Syntax:
% \DoublLine[half of the double line distance]{first node}{second node}{options line 1}{options line 2}
\newcommand\DoubleLine[5][4pt]{%
\path(#2)--(#3)coordinate[at start](h1)coordinate[at end](h2);
\draw[#4]($(h1)!#1!90:(h2)$)--($(h2)!#1!-90:(h1)$);
% node [midway, above=1pt, fill=none] {3};
\draw[#5]($(h1)!#1!-90:(h2)$)--($(h2)!#1!90:(h1)$);
% node [midway, below=1pt, fill=none] {3};
}
\begin{figure}[h]
\begin{tikzpicture}[myn/.style={very thick,draw,inner sep=0.25cm,outer sep=3pt}]
\scalebox{0.5}{
\centering
% place nodes
\node[myn] (a) at (2,5) {Node 6};
\node[myn] (b) at (4, 8) {Node 12};
\node[myn] (c) at (5, 5) {Node 19};
\node[myn] (d) at (5, 3) {Node 20};
\node[myn] (e) at (7.5,5) {Node 18};
\node[myn] (f) at (7.5,10) {Node 10};
\node[myn] (g) at (10,3) {Node 4};
\node[myn] (h) at (10,5) {Node 2};
\node[myn] (i) at (10,8) {Node 13};
\node[myn] (j) at (10,10) {Node 14};
\node[myn] (k) at (13,5) {Node 21};
\node[myn] (l) at (12,7) {Node 1};
\node[myn] (m) at (12,10) {Node 16};
\node[myn] (n) at (15.5,5) {Node 3};
\node[myn] (o) at (13,12) {Node 8};
\node[myn] (p) at (16,3) {Node 5};
\node[myn] (q) at (16,7) {Node 9};
\node[myn] (r) at (7.5,3) {Node 11};
\node[myn] (s) at (11,1){Node 7};
\node[myn] (t) at (12,-1){Node 17};
\node[myn] (u) at (13,-3){Node 22};
\node[myn] (v) at (7.5,-1){Node 15};
%Single line orange
\draw[edge][color=orange, very thick] (i)--(j);
%Single line orange
\draw[edge][color=orange, very thick] (n)--(q);
\draw[edge][color=orange, very thick] (h)--(d);
%double line orange
\DoubleLine{o}{m}{-,very thick,cyan}{-,very thick,orange};
\DoubleLine{i}{m}{-,very thick,cyan}{-,very thick,orange};
}
\end{tikzpicture}
\caption{Line plan: Model A on Set RL}
\label{fig:Line plan Model A on Set RL}
\end{figure}
\end{document}
The figure is shown in the middle of the page and not even centered. As I am quite a beginner in latex, I am wondering how this could work? I need the figure to be on top of the page and centered.
I tried [h] and [t] and also \vspace*{3in}, but it does not work.
A couple of problems:
your figure is too large, it includes tons of white space caused by some of the problem mentioned in the points below. You can see a warning about the overfull box in the log file. Thus it can't be placed according to your [t] floating specifier
using \centering and \scalebox inside the tikz picture makes no sense. If you want the picture to be centred, use it before the tikzpicture
don't use \scalebox for elements which contain text.
tikz has it's own commands to scale things
if you want a figure to be at the top of the page, the page actually needs some text on it
and finally:
The code does NOT run if it throws an error message! Latex only syntax checks the rest of the document, not necessarily producing sensible output. Never ignore error messages.
\documentclass{scrartcl}
\usepackage[utf8]{inputenc}
\usepackage{tikz}
\usetikzlibrary{calc}
\begin{document}
% Syntax:
% \DoublLine[half of the double line distance]{first node}{second node}{options line 1}{options line 2}
\newcommand\DoubleLine[5][4pt]{%
\path(#2)--(#3)coordinate[at start](h1)coordinate[at end](h2);
\draw[#4]($(h1)!#1!90:(h2)$)--($(h2)!#1!-90:(h1)$);
% node [midway, above=1pt, fill=none] {3};
\draw[#5]($(h1)!#1!-90:(h2)$)--($(h2)!#1!90:(h1)$);
% node [midway, below=1pt, fill=none] {3};
}
some text
\begin{figure}[t]
\centering
\begin{tikzpicture}[scale=0.5,transform shape,myn/.style={very thick,draw,inner sep=0.25cm,outer sep=3pt}]
\node[myn] (a) at (2,5) {Node 6};
\node[myn] (b) at (4, 8) {Node 12};
\node[myn] (c) at (5, 5) {Node 19};
\node[myn] (d) at (5, 3) {Node 20};
\node[myn] (e) at (7.5,5) {Node 18};
\node[myn] (f) at (7.5,10) {Node 10};
\node[myn] (g) at (10,3) {Node 4};
\node[myn] (h) at (10,5) {Node 2};
\node[myn] (i) at (10,8) {Node 13};
\node[myn] (j) at (10,10) {Node 14};
\node[myn] (k) at (13,5) {Node 21};
\node[myn] (l) at (12,7) {Node 1};
\node[myn] (m) at (12,10) {Node 16};
\node[myn] (n) at (15.5,5) {Node 3};
\node[myn] (o) at (13,12) {Node 8};
\node[myn] (p) at (16,3) {Node 5};
\node[myn] (q) at (16,7) {Node 9};
\node[myn] (r) at (7.5,3) {Node 11};
\node[myn] (s) at (11,1){Node 7};
\node[myn] (t) at (12,-1){Node 17};
\node[myn] (u) at (13,-3){Node 22};
\node[myn] (v) at (7.5,-1){Node 15};
%Single line orange
\draw[][color=orange, very thick] (i)--(j);
%Single line orange
\draw[][color=orange, very thick] (n)--(q);
\draw[][color=orange, very thick] (h)--(d);
%double line orange
\DoubleLine{o}{m}{-,very thick,cyan}{-,very thick,orange};
\DoubleLine{i}{m}{-,very thick,cyan}{-,very thick,orange};
\end{tikzpicture}
\caption{Line plan: Model A on Set RL}
\label{fig:Line plan Model A on Set RL}
\end{figure}
\end{document}

Assigning a range of values to a variable in tikz

I would like to assign a range of values to a variable in LaTeX to be used in a loop within a tikzpicture environment.
In the below code I would like to replace these lines
\begin{tikzpicture}
\foreach \x in {1, 3, 5, 7}
\foreach \y in {2, ..., 5}{
with something like
first_range = {1, 3, 5, 7}
second_range = {2, ..., 5}
\begin{tikzpicture}
\foreach \x in first_range
\foreach \y in second_range{
A complete runnable code section is below:
\documentclass{article}
\usepackage{tikz}
\usepackage{ifthen}
\begin{document}
\begin{tikzpicture}
\foreach \x in {1, 3, 5, 7}
\foreach \y in {2, ..., 5}{
\ifthenelse{\(\x=1 \OR \x=7\) \AND \y = 3}{
%\filldraw[fill=white] (\x, \y) circle (0.2);
\node[] at (\x, \y) {\vdots};
}{
\filldraw[fill=red] (\x, \y) circle (0.2);
}
}
\end{tikzpicture}
\end{document}
You can store the list in a macro:
\documentclass{article}
\usepackage{tikz}
\usepackage{ifthen}
\def\first{1, 3, 5, 7}
\def\second{2, ..., 5}
\begin{document}
\begin{tikzpicture}
\foreach \x in \first
\foreach \y in \second {
\ifthenelse{\(\x=1 \OR \x=7\) \AND \y = 3}{
%\filldraw[fill=white] (\x, \y) circle (0.2);
\node[] at (\x, \y) {\vdots};
}{
\filldraw[fill=red] (\x, \y) circle (0.2);
}
}
\end{tikzpicture}
\end{document}

Prevent LaTeX deluxetable from starting on new page

I have a giant deluxetable (~4 pages long) that I want to start on the same page as my section header. I'm very new to LaTeX so I'm not sure how to achieve this. I'm using \documentclass[preprint]{aastex}. Here's a snippet of my code:
\section{Additional Tables}
\begin{deluxetable}{rrrrrr}
\tablecolumns{6}
\tablewidth{0pc}
\tablecaption{Observational Data}
\tablehead{\colhead{Object} & \colhead{SpT} & \colhead{Night Observed} & \colhead{J$_\text{s}$-Band} & \colhead{H-Band} & \colhead{K$_\text{s}$-Band}}
\startdata
\textbf{2M0106} & L0 & 9 Sep 2014 & \ding{51} & \ding{51} & \ding{51}\\
% way more rows of data...
\enddata
\end{deluxetable}
I've seen some fixes for normal tables that unnecessarily start on a new page (like this one) but I don't know enough LaTeX to apply that to my deluxetable. Any help you could give me would be very much appreciated!
I found a way to make longtable look very similar to deluxetable on this website. It works pretty well.
\usepackage{longtable}
\begin{center}
\begin{longtable}{lll}
%Here is the caption, the stuff in [] is the table of contents entry,
%the stuff in {} is the title that will appear on the first page of the
%table.
\caption[Feasible triples for a highly variable Grid]{Feasible triples
for highly variable Grid, MLMMH.} \label{grid_mlmmh} \\
%This is the header for the first page of the table...
\hline \hline \\[-2ex]
\multicolumn{1}{c}{\textbf{Time (s)}} &
\multicolumn{1}{c}{\textbf{Triple chosen}} &
\multicolumn{1}{c}{\textbf{Other feasible triples}} \\[0.5ex] \hline
\\[-1.8ex]
\endfirsthead
%This is the header for the remaining page(s) of the table...
\multicolumn{3}{c}{{\tablename} \thetable{} -- Continued} \\[0.5ex]
\hline \hline \\[-2ex]
\multicolumn{1}{c}{\textbf{Time (s)}} &
\multicolumn{1}{c}{\textbf{Triple chosen}} &
\multicolumn{1}{c}{\textbf{Other feasible triples}} \\[0.5ex] \hline
\\[-1.8ex]
\endhead
%This is the footer for all pages except the last page of the table...
\multicolumn{3}{l}{{Continued on Next Page\ldots}} \\
\endfoot
%This is the footer for the last page of the table...
\\[-1.8ex] \hline \hline
\endlastfoot
%Now the data...
0 & (1, 11, 13725) & (1, 12, 10980), (1, 13, 8235), (2, 2, 0), (3, 1, 0) \\
2745 & (1, 12, 10980) & (1, 13, 8235), (2, 2, 0), (2, 3, 0), (3, 1, 0) \\
5490 & (1, 12, 13725) & (2, 2, 2745), (2, 3, 0), (3, 1, 0) \\
8235 & (1, 12, 16470) & (1, 13, 13725), (2, 2, 2745), (2, 3, 0), (3, 1, 0) \\
% <data removed>
164700 & (1, 13, 13725) & (2, 2, 2745), (2, 3, 0), (3, 1, 0) \\
\end{longtable}
\end{center}

Filling gaps in shape edges

Is there an algorithm that would perform well in terms of filling holes like those on the sample image? Dilation doesn't work well, cause before I eventually manage to connect ends of those curves, the curves get really thick. I'd like to avoid thickening the lines. Thank you for any help.
Yes, there can be just any letter or shape in the image with holes like those.
Another, simpler way, that will probably translate better into OpenCV as it uses convolution rather than sequential Perl/C code.
Basically set all the black pixels to value 10, and all the white pixels to value 0, then convolve the image with the following 3x3 kernel:
1 1 1
1 10 1
1 1 1
Now, a black pixel in the middle of the kernel will give 100 (10x10) and any other black pixel in the neighbourhood will give 10 (10x1). So if we want points that have a central black pixel with just one single adjacent black pixel, it will have a value of 110 (100+10). So let's colour all pixels that have the value 110 in with red. That gives this command:
convert EsmKh.png -colorspace gray -fill gray\(10\) -opaque black -fill gray\(0\) -opaque white -morphology convolve '3x3: 1,1,1 1,10,1 1,1,1' -fill red -opaque gray\(110\) out.png
with the resulting image (you may need to zoom in on gaps to see the red):
If you want a list of the red pixels, replace the output filename with txt: and search like this:
convert EsmKh.png -colorspace gray -fill rgb\(10,10,10\) -opaque black -fill rgb\(0,0,0\) -opaque white -morphology convolve '3x3: 1,1,1 1,10,1 1,1,1' txt: | grep "110,110,110"
which gives:
86,55: (110,110,110) #6E6E6E grey43
459,55: (110,110,110) #6E6E6E grey43
83,56: (110,110,110) #6E6E6E grey43
507,59: (110,110,110) #6E6E6E grey43
451,64: (110,110,110) #6E6E6E grey43
82,65: (110,110,110) #6E6E6E grey43
134,68: (110,110,110) #6E6E6E grey43
519,75: (110,110,110) #6E6E6E grey43
245,81: (110,110,110) #6E6E6E grey43
80,83: (110,110,110) #6E6E6E grey43
246,83: (110,110,110) #6E6E6E grey43
269,84: (110,110,110) #6E6E6E grey43
288,85: (110,110,110) #6E6E6E grey43
315,87: (110,110,110) #6E6E6E grey43
325,87: (110,110,110) #6E6E6E grey43
422,104: (110,110,110) #6E6E6E grey43
131,116: (110,110,110) #6E6E6E grey43
524,116: (110,110,110) #6E6E6E grey43
514,117: (110,110,110) #6E6E6E grey43
122,118: (110,110,110) #6E6E6E grey43
245,122: (110,110,110) #6E6E6E grey43
76,125: (110,110,110) #6E6E6E grey43
456,128: (110,110,110) #6E6E6E grey43
447,129: (110,110,110) #6E6E6E grey43
245,131: (110,110,110) #6E6E6E grey43
355,135: (110,110,110) #6E6E6E grey43
80,146: (110,110,110) #6E6E6E grey43
139,151: (110,110,110) #6E6E6E grey43
80,156: (110,110,110) #6E6E6E grey43
354,157: (110,110,110) #6E6E6E grey43
144,160: (110,110,110) #6E6E6E grey43
245,173: (110,110,110) #6E6E6E grey43
246,183: (110,110,110) #6E6E6E grey43
76,191: (110,110,110) #6E6E6E grey43
82,197: (110,110,110) #6E6E6E grey43
126,200: (110,110,110) #6E6E6E grey43
117,201: (110,110,110) #6E6E6E grey43
245,204: (110,110,110) #6E6E6E grey43
248,206: (110,110,110) #6E6E6E grey43
297,209: (110,110,110) #6E6E6E grey43
309,210: (110,110,110) #6E6E6E grey43
Now you can process the list of red points, and for each one, find the nearest other red point and join them with a straight line - or do some curve fitting if you are feeling really keen. Of course, there may be some refining to do, and you may wish to set a maximum length of gap-filling line.
I had a little try at this. It may need some tweaking but it is an idea. My algorithm is as follows:
Find all black pixels that have exactly 1 black neighbouring pixel, colour it red and put it on a list of pixels at ends.
Go through list of all red pixels, and find nearest other red pixel and draw straight line between the two.
By the way, I only implemented the first part - gotta leave something for the reader to do ;-)
#!/usr/bin/perl
use strict;
use warnings;
use Image::Magick;
use Data::Dumper;
my $im=Image::Magick->new();
$im->Read('EsmKh.png');
my ($width,$height)=$im->Get('width','height');
my $out=Image::Magick->new();
$out->Read('EsmKh.png');
my #pixels;
# Iterate over pixels
for my $y (0..($height-1)){
for my $x (0..($width-1)){
my (#pixel) = split(/,/, $im->Get("pixel[$x,$y]"));
$pixels[$x][$y]=$pixel[0];
}
}
# Find black pixels that have precisely 1 black neighbour
for my $y (1..($height-2)){
for my $x (1..($width-2)){
next if $pixels[$x][$y]!=0;
my $neighbours=0;
for(my $i=$x-1;$i<=$x+1;$i++){
for(my $j=$y-1;$j<=$y+1;$j++){
$neighbours++ if $pixels[$i][$j]==0;
}
}
$neighbours--; # Uncount ourself !
if($neighbours==1){
$out->Set("pixel[$x,$y]"=>'red');
}
}
}
$out->Write(filename=>'out.png');
Result
You will have to zoom right in to see the red pixels...
Zoomed Image
After you get the thickened image, you can recover your "thin" shape using skeletonization. I found an implementation of skeletonization here.
If you want avoid too much thickening (as it distorts the image and parts of the shape merge together), use mild erosion and skeletonization alternatively until you get the holes filled.
Here is another solution using OpenCV/Python.
Solution:
The solution is divided into 2 parts:
Find loose ends in all shapes. These are the disconnected points.
Among these points, connect those that are closest among them.
Part 1: Finding loose ends
I am using the Hit-or-Miss transform to find loose ends in each shape. It is a morphological operation that looks for certain patterns in a binary image. Though it is an extension of the common erosion and dilation operations, the hit-or-miss operates differently. While the kernels in erosion/dilation look for overlapping foreground pixels, the hit-or-miss kernels look for overlapping foreground and background pixels. To understand in depth please visit this page
Each point/pixel is surrounded by eight other points. Every loose end is a point that is connected only to one of those eight points. We design a kernel each, to capture all the 8 variations.
1. One variation that can occur is when the end pixel is between 2 other pixels like the following:
1 --> foreground pixel (white pixel)
0 --> background pixel (black pixel)
|1 1 1|
|0 1 0|
|0 0 0|
|0 0 1|
|0 1 1|
|0 0 1|
|0 0 0|
|0 1 0|
|1 1 1|
|1 0 0|
|1 1 0|
|1 0 0|
2. Another variation is when the end pixel is at the end of a diagonal; like the following:
|0 0 0|
|0 1 0|
|1 0 0|
|1 0 0|
|0 1 0|
|0 0 0|
|0 0 1|
|0 1 0|
|0 0 0|
|0 0 0|
|0 1 0|
|0 0 1|
Part 2: Connecting disconnected points based on distance
After finding all the loose ends, in this step we iterate each point and connect it with the one closest to it. The closest distance in this case is the Euclidean distance.
Code:
img = cv2.imread('broken_shapes.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# inverse binary image
th = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
# skeletonize
sk = cv2.ximgproc.thinning(th, None, 1)
# Kernels for each of the 8 variations
k1 = np.array(([0, 0, 0], [-1, 1, -1], [-1, -1, -1]), dtype="int")
k2 = np.array(([0, -1, -1], [0, 1, -1], [0, -1, -1]), dtype="int")
k3 = np.array(([-1, -1, 0], [-1, 1, 0], [-1, -1, 0]), dtype="int")
k4 = np.array(([-1, -1, -1], [-1, 1, -1], [0, 0, 0]), dtype="int")
k5 = np.array(([-1, -1, -1], [-1, 1, -1], [0, -1, -1]), dtype="int")
k6 = np.array(([-1, -1, -1], [-1, 1, -1], [-1, -1, 0]), dtype="int")
k7 = np.array(([-1, -1, 0], [-1, 1, -1], [-1, -1, -1]), dtype="int")
k8 = np.array(([0, -1, -1], [-1, 1, -1], [-1, -1, -1]), dtype="int")
# hit-or-miss transform
o1 = cv2.morphologyEx(sk, cv2.MORPH_HITMISS, k1)
o2 = cv2.morphologyEx(sk, cv2.MORPH_HITMISS, k2)
o3 = cv2.morphologyEx(sk, cv2.MORPH_HITMISS, k3)
o4 = cv2.morphologyEx(sk, cv2.MORPH_HITMISS, k4)
out1 = o1 + o2 + o3 + o4
o5 = cv2.morphologyEx(sk, cv2.MORPH_HITMISS, k5)
o6 = cv2.morphologyEx(sk, cv2.MORPH_HITMISS, k6)
o7 = cv2.morphologyEx(sk, cv2.MORPH_HITMISS, k7)
o8 = cv2.morphologyEx(sk, cv2.MORPH_HITMISS, k8)
out2 = o5 + o6 + o7 + o8
# contains all the loose end points
out = cv2.add(out1, out2)
# store the loose end points and draw them for visualization
pts = np.argwhere(out == 255)
loose_ends = img.copy()
for pt in pts:
loose_ends = cv2.circle(loose_ends, (pt[1], pt[0]), 3, (0,255,0), -1)
# convert array of points to list of tuples
pts = list(map(tuple, pts))
final = img.copy()
# iterate every point in the list and draw a line between nearest point in the same list
for i, pt1 in enumerate(pts):
min_dist = max(img.shape[:2])
sub_pts = pts.copy()
del sub_pts[i]
pt_2 = None
for pt2 in sub_pts:
dist = int(np.linalg.norm(np.array(pt1) - np.array(pt2)))
#print(dist)
if dist < min_dist:
min_dist = dist
pt_2 = pt2
final = cv2.line(final, (pt1[1], pt1[0]), (pt_2[1], pt_2[0]), (0, 0, 255), thickness = 2)
Results:
Result of loose_ends:
Result of final:
This is an OpenCV, C++ implementation of Mark Setchell's algorithm. It is very straightforward, uses the same kernel and convolutes the input image via the cv::filter2D function. I've, optionally, inverted the input image so target pixels have a value of 255:
//Read input Image
cv::Mat inputImage = cv::imread( "C://opencvImages//blobs.png", cv::IMREAD_GRAYSCALE );
//Invert the image:
inputImage = 255 - inputImage;
//Threshold the image so that white pixels get a value of 0 and
//black pixels a value of 10:
cv::threshold( inputImage, inputImage, 128, 10, cv::THRESH_BINARY );
Now, setup the kernel and convolute the image, like this:
//Set up the end-point kernel:
cv::Mat kernel = ( cv::Mat_<int>(3, 3) <<
1, 1, 1,
1, 10, 1,
1, 1, 1
);
//Convolute image with kernel:
cv::filter2D( inputImage, inputImage, -1 , kernel, cv::Point( -1, -1 ), 0, cv::BORDER_DEFAULT );
The direct result of the convolution is this, pixels at the end points now have a value of 110, which can be seen (barely) in this output:
Let's threshold these pixels and overlay them on the original image. This is the result (pixels in red):
Additionally, the skeleton of the image can be computed at the beginning. The skeleton has a normalized line-width of 1 pixel. The function is part of the Extended Image Processing module of OpenCV:
#include <opencv2/ximgproc.hpp>
//Compute the skeleton of the input:
cv::Mat skel;
int algorithmType = 1;
cv::ximgproc::thinning( inputImage, skel, algorithmType );

Resources