<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://www.manuelcastillo.eu/feed.xml" rel="self" type="application/atom+xml" /><link href="https://www.manuelcastillo.eu/" rel="alternate" type="text/html" /><updated>2026-06-03T20:08:00+02:00</updated><id>https://www.manuelcastillo.eu/feed.xml</id><title type="html">Ph.D. Manuel Castillo-Cara</title><subtitle>Manuel Castillo Cara is an Udemy instructor and postdoctoral researcher fellow at Universidad Politécnica de Madrid. Research lines: Wireless Sensor Networks, Distributed Computing, Pattern Recognition and Artificial Intelligence. Manuel Castillo Cara es instructor Udemy e investigador postdoctoral en la Universidad Politécnica de Madrid. Líneas de investigación: Redes de Sensores, Computación Distribuida, Reconocimiento de Patrones e Inteligencia Artificial.</subtitle><author><name>Ph.D. Manuel Castillo-Cara</name><email>manwest.c@gmail.com</email></author><entry><title type="html">Improving Deep Learning by Exploiting Synthetic Images — Part II: From Synthetic Images to Hybrid Neural Networks</title><link href="https://www.manuelcastillo.eu/blog/2026/06/04-from-synthetic-images-to-hybrid-neural-networks/" rel="alternate" type="text/html" title="Improving Deep Learning by Exploiting Synthetic Images — Part II: From Synthetic Images to Hybrid Neural Networks" /><published>2026-06-03T00:00:00+02:00</published><updated>2026-06-03T00:00:00+02:00</updated><id>https://www.manuelcastillo.eu/blog/2026/06/04-from-synthetic-images-to-hybrid-neural-networks-part-2</id><content type="html" xml:base="https://www.manuelcastillo.eu/blog/2026/06/04-from-synthetic-images-to-hybrid-neural-networks/"><![CDATA[<link rel="canonical" href="https://www.manuelcastillo.eu/blog/2026/06/04-from-synthetic-images-to-hybrid-neural-networks/" />

<meta name="robots" content="index,follow,max-image-preview:large" />

<meta name="description" content="Part II of a theoretical and technical series on synthetic images for tabular data, focusing on TINTOlib, preferred spatial encoding methods, hybrid neural networks, and explainable AI." />

<meta property="og:type" content="article" />

<meta property="og:title" content="Improving Deep Learning by Exploiting Synthetic Images — Part II: From Synthetic Images to Hybrid Neural Networks" />

<meta property="og:description" content="Part II of a theoretical and technical series on synthetic images for tabular data, focusing on TINTOlib, preferred spatial encoding methods, hybrid neural networks, and explainable AI." />

<meta property="og:url" content="https://www.manuelcastillo.eu/blog/2026/06/04-from-synthetic-images-to-hybrid-neural-networks/" />

<meta property="og:image" content="https://www.manuelcastillo.eu/images/Blog/2026-06-02-04-xai-hynn.png" />

<meta property="article:published_time" content="2026-06-03T00:00:00+02:00" />

<meta property="article:modified_time" content="2026-06-03T00:00:00+02:00" />

<meta property="article:author" content="Manuel Castillo-Cara" />

<meta property="article:section" content="TINTOlib" />

<meta property="article:tag" content="TINTOlib" />

<meta property="article:tag" content="Synthetic Images" />

<meta property="article:tag" content="Tabular-to-Image" />

<meta property="article:tag" content="Hybrid Neural Networks" />

<meta property="article:tag" content="Deep Learning" />

<meta property="article:tag" content="CNN" />

<meta property="article:tag" content="Vision Transformer" />

<meta property="article:tag" content="Explainable AI" />

<meta property="article:tag" content="Spatial Encoding" />

<meta name="twitter:card" content="summary_large_image" />

<meta name="twitter:title" content="Improving Deep Learning by Exploiting Synthetic Images — Part II: From Synthetic Images to Hybrid Neural Networks" />

<meta name="twitter:description" content="Part II of a theoretical and technical series on synthetic images for tabular data, focusing on TINTOlib, preferred spatial encoding methods, hybrid neural networks, and explainable AI." />

<meta name="twitter:image" content="https://www.manuelcastillo.eu/images/Blog/2026-06-02-04-xai-hynn.png" />

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "BlogPosting",
  "headline": "Improving Deep Learning by Exploiting Synthetic Images — Part II: From Synthetic Images to Hybrid Neural Networks",
  "description": "Part II of a theoretical and technical series on synthetic images for tabular data, focusing on TINTOlib, preferred spatial encoding methods, hybrid neural networks, and explainable AI.",
  "image": "https://www.manuelcastillo.eu/images/Blog/2026-06-02-04-xai-hynn.png",
  "author": {
    "@type": "Person",
    "name": "Manuel Castillo-Cara",
    "url": "https://www.manuelcastillo.eu/"
  },
  "publisher": {
    "@type": "Person",
    "name": "Manuel Castillo-Cara",
    "url": "https://www.manuelcastillo.eu/"
  },
  "datePublished": "2026-06-03T00:00:00+02:00",
  "dateModified": "2026-06-03T00:00:00+02:00",
  "mainEntityOfPage": {
    "@type": "WebPage",
    "@id": "https://www.manuelcastillo.eu/blog/2026/06/04-from-synthetic-images-to-hybrid-neural-networks/"
  },
  "articleSection": "TINTOlib",
  "keywords": "TINTOlib, Synthetic Images, Tabular-to-Image, Hybrid Neural Networks, Deep Learning, CNN, Vision Transformer, Explainable AI, Spatial Encoding"
}
</script>

<div style="background: linear-gradient(135deg, #1a237e 0%, #4a148c 45%, #065f46 100%); border-radius: 12px; padding: 2.5rem 2rem; margin: 1.5rem 0 2.5rem; display: flex; flex-wrap: wrap; align-items: center; gap: 2rem; color: #fff;">
  <div style="flex: 1 1 280px; min-width: 0;">
    <p style="margin: 0 0 0.4rem; font-size: 0.78rem; letter-spacing: 0.12em; text-transform: uppercase; color: #c4b5fd; font-weight: 600;">TINTOlib · Hybrid Neural Networks · Explainable AI</p>
    <h1 style="margin: 0 0 0.75rem; font-size: clamp(1.5rem, 4vw, 2.1rem); font-weight: 800; line-height: 1.2; color: #fff;">From Synthetic Images to Hybrid Neural Networks</h1>
    <p style="margin: 0 0 1rem; font-size: 0.97rem; color: #ede9fe; line-height: 1.55;">Part II — How TINTOlib transforms tabular data into spatial representations, which methods should be preferred, and how CNNs, ViTs and XAI fit into the pipeline.</p>
    <div style="display: flex; flex-wrap: wrap; gap: 0.5rem;">
      <span style="background: rgba(255,255,255,0.15); border-radius: 20px; padding: 0.25rem 0.75rem; font-size: 0.78rem; color: #ede9fe;">Synthetic Images</span>
      <span style="background: rgba(255,255,255,0.15); border-radius: 20px; padding: 0.25rem 0.75rem; font-size: 0.78rem; color: #ede9fe;">CNN</span>
      <span style="background: rgba(255,255,255,0.15); border-radius: 20px; padding: 0.25rem 0.75rem; font-size: 0.78rem; color: #ede9fe;">Vision Transformer</span>
      <span style="background: rgba(255,255,255,0.15); border-radius: 20px; padding: 0.25rem 0.75rem; font-size: 0.78rem; color: #ede9fe;">XAI</span>
    </div>
  </div>
  <div style="flex: 0 0 auto; max-width: 260px; width: 100%;">
    <img src="/images/Blog/2026-06-02-04-xai-hynn.png" alt="From synthetic images to hybrid neural networks with TINTOlib" style="width: 100%; border-radius: 10px; box-shadow: 0 8px 32px rgba(0,0,0,0.45); display: block;" />
  </div>
</div>

<hr />

<blockquote>
  <blockquote>
    <ul>
      <li><strong>Author:</strong> Manuel Castillo-Cara, PhD</li>
      <li><strong>Affiliation:</strong> Dpt. of Artificial Intelligence, Universidad Nacional de Educación a Distancia (UNED), Spain</li>
      <li><strong>Role:</strong> Researcher, Professor, and TINTOlib Python Library Developer</li>
      <li><strong>License:</strong> <a href="https://creativecommons.org/licenses/by-nc/4.0/">CC BY-NC 4.0</a> unless otherwise stated.</li>
    </ul>
  </blockquote>
</blockquote>

<hr />

<h2 id="video-overview">Video overview</h2>

<p>This post is part of a two-part technical summary derived from the conference <em>Improving Deep Learning by Exploiting Synthetic Images</em>, delivered in Peru.</p>

<p>The following short video provides an English overview of the main ideas discussed across both posts: why tabular data remains challenging for deep learning, how synthetic images can introduce spatial representations, and how TINTOlib connects structured data with computer vision, hybrid neural networks and explainable AI.</p>

<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden; max-width: 100%; margin: 1.5rem 0;">
  <video controls="" preload="metadata" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border-radius: 10px; background: #000;">
    <source src="/video/Blog/2026-06-02-03-improving-deep-learning.mp4" type="video/mp4" />
    Your browser does not support the video tag.
  </video>
</div>

<blockquote>
  <p><strong>Additional material.</strong> The original conference recording is available in <strong>Spanish</strong>. The two blog posts provide an English technical synthesis and discussion based on that conference.</p>

  <p><a href="https://unedo365-my.sharepoint.com/:v:/g/personal/manuelcastillo_dia_uned_es/IQDov79-I54sS7jNgIIAaTS3AVs0L5cVinne29xXLZZNsc0?e=DVgrOZ">Open the original conference recording in SharePoint</a></p>
</blockquote>

<h2 id="from-theory-to-modelling">From theory to modelling</h2>

<p><a href="/blog/2026/06/03-improving-deep-learning-exploiting-synthetic-images-part-1/">Part I</a> introduced the theoretical motivation behind synthetic images for tabular data. The central point was that deep learning does not fail on tabular data because it is weak, but because many of its most successful architectures require structural assumptions that tables do not naturally provide.</p>

<p>This second part moves from the theoretical problem to the modelling pipeline. Once we accept that representation is central, the next questions are more practical:</p>

<ul>
  <li>How should the spatial representation be constructed?</li>
  <li>Which transformation methods are methodologically preferable?</li>
  <li>How can synthetic images be connected with CNNs, Vision Transformers and hybrid neural networks?</li>
  <li>How can visual explainability be mapped back to original tabular variables?</li>
</ul>

<p>These questions define the practical contribution of TINTOlib.</p>

<h2 id="tintolib-as-an-experimental-framework">TINTOlib as an experimental framework</h2>

<p>TINTOlib is not only a library for generating images. It is a framework for experimenting with a family of spatial representations for tabular data.</p>

<p>The value of a unified framework is methodological. Before TINTOlib, many tabular-to-image methods were implemented in separate repositories, with different APIs, assumptions and preprocessing requirements. This made systematic comparison difficult. TINTOlib provides a common interface that allows researchers to instantiate different methods, generate synthetic datasets and evaluate downstream architectures under more controlled conditions.</p>

<p>A typical experimental pipeline contains four stages:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Original tabular dataset
      |
      | train / validation / test split
      v
Fit spatial transformation only on training data
      |
      | transform each split
      v
Synthetic image dataset
      |
      | CNN / ViT / HyNN / XAI
      v
Evaluation and interpretation
</code></pre></div></div>

<p>The separation between fitting and transforming is essential. The spatial layout must be learned only from the training data. If the validation or test set participates in the construction of the feature layout, data leakage occurs. The model would indirectly access information from the evaluation split before prediction.</p>

<h2 id="preferred-families-of-spatial-encodings">Preferred families of spatial encodings</h2>

<p>The most important methodological distinction is between methods that learn or optimize the spatial structure of the features and methods that remain dependent on the arbitrary column order.</p>

<p>Methods such as <strong>TINTO</strong>, <strong>DeepInsight</strong>, <strong>IGTD</strong>, <strong>REFINED</strong>, <strong>Fotomics</strong> and recent unsupervised learning-based approaches such as <strong>Clusters</strong> are preferable when the goal is to build a spatial representation that reflects relationships in the data. They attempt to place related features close to one another through dimensionality reduction, similarity preservation, manifold learning, optimization or unsupervised representation learning.</p>

<p>This is the family of methods that best addresses the original problem. If tabular data have no natural spatial structure, the transformation method should construct one from the data, not inherit one from the arbitrary table layout.</p>

<p>By contrast, <strong>BarGraph</strong>, <strong>DistanceMatrix</strong>, <strong>Combination</strong>, <strong>SuperTML</strong>, <strong>BIE</strong> and <strong>FeatureWrap</strong> are more dependent on the original feature arrangement or on encoding schemes that can be influenced by how variables are ordered. These approaches can still be useful for comparison, teaching or specific datasets with meaningful order, but they should not be the first choice when working with generic tabular data whose column order is arbitrary.</p>

<p>The distinction can be summarized as follows:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Order-dependent encodings
    |
    | risk: inherit arbitrary column order
    v
Useful as baselines, but not recommended as primary methods for unordered tables

Data-driven spatial encodings
    |
    | objective: learn or optimize feature geometry
    v
Recommended for rigorous tabular-to-image experimentation
</code></pre></div></div>

<p>The methodological distinction becomes clearer when we compare the visual outputs of different transformation families. Some approaches generate images by imposing relatively direct or handcrafted encodings over the original feature arrangement. These methods can be useful for comparison, teaching, or specific datasets with meaningful feature order, but they may remain sensitive to the arbitrary ordering of the tabular columns.</p>

<p>Other methods attempt to construct a spatial representation from the data structure itself. In this second family, the image is not merely a visual formatting of the table; it is the result of a projection, optimization, similarity-preserving transformation, or unsupervised representation learning process. This distinction is central when synthetic images are used as input to CNNs, Vision Transformers, or hybrid neural architectures.</p>

<p><img src="/images/Blog/2026-06-02-04-supervised-synthetic-image-methods.png" alt="Supervised tabular-to-image methods" />
<em>(Figure 1. Examples of supervised tabular-to-image transformation methods available in the TINTOlib ecosystem. These methods generate synthetic image representations from structured data, but they differ substantially in how they define or learn the spatial arrangement of the original variables. The methodological relevance of each image depends not only on its visual appearance, but also on whether its spatial structure reflects meaningful relationships between features.)</em></p>

<p><img src="/images/Blog/2026-06-02-04-unsupervised-synthetic-image-methods.png" alt="Unsupervised tabular-to-image methods" />
<em>(Figure 2. Examples of unsupervised tabular-to-image transformation methods. Unlike encodings that directly inherit the original column order, data-driven spatial methods aim to construct image-like representations from statistical, geometric or neighbourhood relationships among variables. This is essential when the original table has no natural spatial layout.)</em></p>

<h2 id="why-blurring-matters">Why blurring matters</h2>

<p>One of the distinctive aspects of the TINTO method is the use of blurring. Blurring should not be understood as a cosmetic operation. It has a representational role.</p>

<p>When a feature value is projected into a pixel grid, the resulting image may be sparse or discontinuous. Blurring smooths local regions and can create more continuous spatial patterns. This may help convolutional filters detect local structures more effectively, because the information is not restricted to isolated pixels.</p>

<p>From a neural network perspective, blurring can be understood as a way of inducing local continuity. It transforms a discrete feature placement into a smoother visual signal. This does not guarantee better performance, but it creates a representation that is more aligned with the assumptions of convolutional processing.</p>

<p>The important point is that blurring must be evaluated empirically. Its usefulness may depend on the dataset, the image size, the density of the feature layout and the architecture used downstream.</p>

<h2 id="connecting-synthetic-images-with-cnns-and-vision-transformers">Connecting synthetic images with CNNs and Vision Transformers</h2>

<p>Once the tabular dataset has been transformed into synthetic images, several visual architectures become available.</p>

<p>A CNN processes the synthetic image through convolutional filters. If the feature layout has been constructed properly, nearby pixels may correspond to related variables, and local filters may capture interactions between them.</p>

<p>A Vision Transformer processes the image as a set of patches. This makes it possible to model longer-range relationships between regions of the synthetic image. For larger synthetic images or richer feature layouts, attention mechanisms may capture interactions that are not strictly local.</p>

<p>However, neither CNNs nor ViTs should be used as black-box replacements for classical models. The correct experimental design should include strong baselines:</p>

<ul>
  <li>tree-based models such as Random Forest, XGBoost, LightGBM or CatBoost;</li>
  <li>an MLP trained directly on the original tabular data;</li>
  <li>several tabular-to-image methods;</li>
  <li>several image sizes;</li>
  <li>CNN, ViT and hybrid architectures;</li>
  <li>ablation studies with and without blurring;</li>
  <li>validation protocols that prevent data leakage.</li>
</ul>

<p>Only under this type of comparison can we determine whether the synthetic image representation adds value.</p>

<h2 id="hybrid-neural-networks">Hybrid Neural Networks</h2>

<p>A particularly relevant architecture is the <strong>Hybrid Neural Network</strong>. Instead of choosing between the original tabular data and the synthetic image, a hybrid model uses both.</p>

<p>A typical design includes:</p>

<ol>
  <li>a tabular branch, usually an MLP that receives the original feature vector;</li>
  <li>an image branch, usually a CNN or ViT that receives the synthetic image;</li>
  <li>a fusion layer, where both representations are concatenated or combined;</li>
  <li>a prediction head for classification or regression.</li>
</ol>

<p>This architecture is conceptually attractive because it avoids discarding the original tabular representation. The tabular branch can learn direct variable-level patterns, while the image branch can learn spatial interactions produced by the transformation method.</p>

<p>Since the main image of this post already summarizes the hybrid-neural-network idea, we will not duplicate that slide here. The important point is architectural: the hybrid model preserves the original tabular information while simultaneously exploiting a synthetic image representation derived from TINTOlib.</p>

<p>In indoor localisation, this design is especially meaningful. Wireless measurements can be represented as tabular features, but they may also contain latent spatial or structural patterns. A hybrid architecture can exploit both perspectives simultaneously.</p>

<p><img src="/images/Blog/2026-06-02-04-from-synthetic-images-to-hybrid-neural-networks.png" alt="Unsupervised tabular-to-image methods" />
<em>(Figure 3. A simplified illustration of the HyNN architecture. This figure shows that either CNN or ViT can be used as the vision component, while the MLP component processes tabular data directly. It also highlights the flexibility of the model, which allows for using only the vision or MLP part, illustrating the different architectures evaluated in this study.)</em></p>

<h2 id="explainable-ai-over-synthetic-images">Explainable AI over synthetic images</h2>

<p>Interpretability is one of the most important motivations for the synthetic image paradigm.</p>

<p>In a conventional neural network trained on tabular data, explanations are usually produced at the variable level. In a CNN trained on synthetic images, explanations can be produced visually. For example, methods such as Grad-CAM or saliency maps can identify regions of the synthetic image that were influential for the prediction.</p>

<p>The key step is to map those regions back to the original variables. Since the synthetic image is generated from a known feature layout, each pixel or region can be associated with one or more original tabular features. This allows a visual explanation to be translated back into the structured-data domain.</p>

<p>The first level of interpretation operates directly over the synthetic image. Since each region of the image is generated from a known spatial encoding of the original variables, visual attribution maps can be inspected not only as image explanations, but also as indirect explanations over the tabular feature space.</p>

<p><img src="/images/Blog/2026-06-02-04-aix-tintolib-feature-map.png" alt="AIX in TINTOlib feature attribution map" />
<em>(Figure 4. AIX-based interpretation of synthetic tabular images in TINTOlib. Visual attribution maps can identify relevant regions of the synthetic image, which can then be related back to the original tabular variables through the known spatial encoding. This provides a bridge between image-based explainability and feature-level interpretation in structured data problems.)</em></p>

<p>In hybrid architectures, this interpretability layer becomes even more relevant. The model can be analysed from two complementary perspectives: the tabular branch, which operates over the original feature vector, and the image branch, which operates over the synthetic spatial representation. This makes it possible to study whether both branches rely on consistent information or whether each one captures different aspects of the problem.</p>

<p><img src="/images/Blog/2026-06-02-04-xai-hynn.png" alt="XAI in hybrid neural networks" />
<em>(Figure 5. Explainable AI workflow for hybrid neural networks combining tabular and synthetic image representations. The image branch enables visual attribution over the generated synthetic representation, while the tabular branch preserves direct access to the original variables. This makes it possible to connect spatial explanations with feature-level reasoning in hybrid deep learning models.)</em></p>

<p>This is one of the reasons why the method is not only useful for prediction. It also creates a framework for studying how neural networks use transformed tabular information.</p>

<h2 id="indoor-localisation-as-a-representative-use-case">Indoor localisation as a representative use case</h2>

<p>The conference used indoor localisation as one of the representative domains for this paradigm. In MIMO-based and Bluetooth-based localisation problems, the input may consist of signal measurements, antenna-related variables or channel-state information. These variables are usually stored as tables, but they may contain complex structural dependencies.</p>

<p>Transforming these signals into synthetic images makes it possible to evaluate whether spatial encodings expose patterns that are difficult to exploit from the raw tabular representation. CNNs and ViTs can then process those patterns, while hybrid architectures can combine them with the original signal vector.</p>

<p>This does not mean that indoor localisation is the only application. The same idea can be applied to biomedical data, industrial monitoring, educational analytics, cybersecurity, financial risk modelling, sensor networks and many other domains where the input is tabular but the relationships between variables are complex.</p>

<h2 id="what-the-paradigm-does-not-claim">What the paradigm does not claim</h2>

<p>A rigorous interpretation of synthetic images for tabular data must avoid exaggerated claims.</p>

<p>The method does not claim that every table should become an image. It does not claim that CNNs will always outperform XGBoost. It does not claim that a synthetic image is semantically equivalent to a natural image.</p>

<p>The claim is more precise:</p>

<blockquote>
  <p>If the feature layout is constructed in a meaningful way, synthetic images can provide a spatial representation that allows visual and hybrid neural architectures to exploit relationships in tabular data.</p>
</blockquote>

<p>This statement is testable. It requires benchmarks, ablations, strong baselines and careful validation.</p>

<h2 id="practical-recommendations">Practical recommendations</h2>

<p>For researchers and students working with TINTOlib, the following recommendations are important.</p>

<p>First, always split the data before fitting the transformation. Data leakage at the representation stage invalidates the evaluation.</p>

<p>Second, do not rely on order-dependent encodings as primary methods when the original feature order is arbitrary. Use them as baselines or teaching tools, but prioritize data-driven spatial encodings such as TINTO, DeepInsight, IGTD, REFINED, Fotomics and Clusters.</p>

<p>Third, compare against strong classical baselines. A synthetic image pipeline should not be evaluated only against weak neural models.</p>

<p>Fourth, evaluate several image sizes and architectures. The optimal spatial resolution may depend on the number of variables and the density of the feature layout.</p>

<p>Fifth, include interpretability analyses. One of the strengths of synthetic images is that they create a bridge between visual explanations and tabular variables.</p>

<h2 id="conclusion">Conclusion</h2>

<p>Synthetic image generation from tabular data is not a superficial visualization technique. It is a representational strategy for connecting structured data with computer vision and hybrid neural architectures.</p>

<p>The central methodological requirement is that the image must have a meaningful spatial structure. If the image merely reflects the arbitrary order of the columns, the transformation reproduces the original limitation of tabular data. If, however, the spatial layout is learned or optimized from the relationships between variables, the resulting representation can provide a useful bridge toward deep learning.</p>

<p>TINTOlib provides the practical environment for exploring this idea. It enables systematic comparison of spatial encodings, integration with CNNs and Vision Transformers, development of hybrid neural networks and application of explainability techniques to synthetic tabular images.</p>

<p>The broader lesson is that deep learning performance depends not only on the architecture, but also on the representation. For tabular data, representation may be the decisive step.</p>

<h2 id="references-and-related-publications">References and related publications</h2>

<p>The concepts presented in this tutorial are connected to the following research and software publications on TINTO, TINTOlib, tabular-to-image transformation, synthetic spatial representations, hybrid neural networks, indoor localisation and explainable artificial intelligence.</p>

<h3 id="research-articles">Research articles</h3>

<ol>
  <li>
    <p>Jiayun Liu, Manuel Castillo-Cara et al. <strong>Interpretable Hybrid Vision Transformer Architectures for MIMO-Based Indoor Localization using Synthetic Spatial Representations</strong>. <em>IEEE Internet of Things</em>. DOI: <a href="https://doi.org/10.1109/JIOT.2026.3696106">10.1109/JIOT.2026.3696106</a></p>
  </li>
  <li>
    <p>Jiayun Liu, Manuel Castillo-Cara et al. <strong>A Comprehensive Benchmark of Spatial Encoding Methods for Tabular Data with Deep Neural Networks</strong>. <em>Information Fusion</em>. DOI: <a href="https://doi.org/10.1016/j.inffus.2025.104088">10.1016/j.inffus.2025.104088</a></p>
  </li>
  <li>
    <p>Giovanny Mondragon-Ruiz, Jiayun Liu, Manuel Castillo-Cara et al. <strong>Interpretable CNN–KAN hybrid architectures for tabular data with synthetic image encoding</strong>. <em>Information Processing and Management</em>. DOI: <a href="https://doi.org/10.1016/j.ipm.2026.104954">10.1016/j.ipm.2026.104954 </a></p>
  </li>
  <li>
    <p>Manuel Castillo-Cara et al. <strong>MIMO-Based Indoor Localisation with Hybrid Neural Networks</strong>. <em>IEEE Journal of Selected Topics in Signal Processing</em>. DOI: <a href="https://doi.org/10.1109/JSTSP.2025.3555067">10.1109/JSTSP.2025.3555067</a></p>
  </li>
  <li>
    <p>Reewos Talla-Chumpitaz, Manuel Castillo-Cara et al. <strong>Blurring Image Techniques for Bluetooth-based Indoor Localisation</strong>. <em>Information Fusion</em>. DOI: <a href="https://doi.org/10.1016/j.inffus.2022.10.011">10.1016/j.inffus.2022.10.011</a></p>
  </li>
</ol>

<h3 id="software-articles">Software articles</h3>

<ol>
  <li>
    <p>Jiayun Liu et al. <strong>TINTOlib: A Python library for transforming tabular data into synthetic images for deep neural networks</strong>. <em>SoftwareX</em>. DOI: <a href="https://doi.org/10.1016/j.softx.2025.102444">10.1016/j.softx.2025.102444</a></p>
  </li>
  <li>
    <p>Manuel Castillo-Cara et al. <strong>TINTO: Converting Tidy Data into Image for Classification with 2-Dimensional Convolutional Neural Networks</strong>. <em>SoftwareX</em>. DOI: <a href="https://doi.org/10.1016/j.softx.2023.101391">10.1016/j.softx.2023.101391</a></p>
  </li>
</ol>

<!-- ===== Blog Post Footer: CTA → Author → License → Export ===== -->
<style>
  /* Hide default Minimal Mistakes related posts (replaced by custom cards below) */
  .page__related { display: none !important; }

  /* ----- Copy-code button ----- */
  .page__content pre { position: relative; }
  .copy-code-btn {
    position: absolute;
    top: 0.35rem;
    right: 0.45rem;
    padding: 0.15rem 0.55rem;
    font-size: 0.72rem;
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
    background: #eff6ff;
    color: #1d4ed8;
    border: 1px solid #bfdbfe;
    border-radius: 4px;
    cursor: pointer;
    transition: background 0.15s, color 0.15s;
    line-height: 1.5;
    z-index: 2;
    user-select: none;
    font-weight: 600;
  }
  .copy-code-btn:hover { background: #dbeafe; color: #1e40af; border-color: #93c5fd; }
  .copy-code-btn.copied { background: #d1fae5; color: #065f46; border-color: #6ee7b7; }

  /* ----- Post footer wrapper ----- */
  .blog-post-footer {
    margin-top: 3rem;
    border-top: 2px solid #e2e8f0;
    padding-top: 2rem;
  }

  /* ----- CTA (first) ----- */
  .blog-cta {
    background: linear-gradient(135deg, #1565c0 0%, #6d28d9 100%);
    color: #fff;
    border-radius: 12px;
    padding: 1.4rem 1.75rem;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    margin-bottom: 1.5rem;
    box-shadow: 0 4px 18px rgba(21,101,192,0.22);
  }
  .blog-cta-text h3 { margin: 0 0 0.3rem; font-size: 1.05rem; color: #fff; }
  .blog-cta-text p  { margin: 0; font-size: 0.88rem; color: rgba(255,255,255,0.85); }
  .blog-cta-btn {
    display: inline-block;
    background: #fff;
    color: #1565c0 !important;
    font-weight: 800;
    font-size: 0.9rem;
    text-decoration: none !important;
    padding: 0.55rem 1.3rem;
    border-radius: 8px;
    white-space: nowrap;
    transition: box-shadow 0.15s, transform 0.1s;
  }
  .blog-cta-btn:hover { box-shadow: 0 4px 14px rgba(0,0,0,.22); transform: translateY(-1px); }

  /* ----- Author card (second) ----- */
  .blog-author-card {
    display: flex;
    align-items: center;
    gap: 0.85rem;
    background: #f8fafc;
    border: 1px solid #e2e8f0;
    border-radius: 12px;
    padding: 0.8rem 1.1rem;
    margin-bottom: 0.75rem;
    box-shadow: 0 2px 8px rgba(0,0,0,.05);
  }
  .blog-author-avatar {
    width: 58px;
    height: 76px;
    border-radius: 999px;
    object-fit: cover;
    flex-shrink: 0;
    border: 2px solid #c7d9f0;
    background: #e8edf5;
    box-shadow: 0 2px 8px rgba(0,0,0,.12);
  }
  .blog-author-info { flex: 1; min-width: 0; }
  .blog-author-card p { margin: 0; line-height: 1.25; }
  .blog-author-name { font-weight: 800; font-size: 0.95rem !important; color: #1f2937; }
  .blog-author-role { font-size: 0.82rem !important; color: #374151; }
  .blog-author-spec { font-size: 0.82rem !important; color: #374151; }
  .blog-author-dept { font-size: 0.78rem !important; color: #4b5563; }
  .blog-author-inst { font-size: 0.76rem !important; color: #6b7280; }

  /* ----- License (third) ----- */
  .blog-license {
    font-size: 0.81rem;
    color: #6b7280;
    background: #f1f5f9;
    border: 1px solid #e2e8f0;
    border-radius: 8px;
    padding: 0.55rem 1rem;
    margin-bottom: 1.1rem;
  }
  .blog-license a { color: #1976d2; }

  /* ----- Resource / export buttons (fourth) ----- */
  .blog-resources {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin-bottom: 0.5rem;
  }
  .blog-resource-btn {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.38rem 0.9rem;
    border-radius: 8px;
    font-size: 0.82rem;
    font-weight: 700;
    text-decoration: none !important;
    cursor: pointer;
    border: 1px solid transparent;
    transition: box-shadow 0.15s, transform 0.1s;
    line-height: 1.4;
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  }
  .blog-resource-btn:hover { box-shadow: 0 4px 12px rgba(0,0,0,.14); transform: translateY(-1px); }
  .blog-resource-btn--notebook { background: #fff3e0; color: #e65100 !important; border-color: #ffcc80; }
  .blog-resource-btn--pdf      { background: #fce4ec; color: #c62828 !important; border-color: #f48fb1; }
  .blog-resource-btn--print    { background: #e8f5e9; color: #2e7d32 !important; border-color: #a5d6a7; }
  .blog-resource-btn--save     { background: #e8eaf6; color: #283593 !important; border-color: #9fa8da; }
  .blog-resource-btn--python   { background: #fef9c3; color: #713f12 !important; border-color: #fde68a; }
  .blog-resource-btn--ipynb    { background: #fff7ed; color: #92400e !important; border-color: #fed7aa; }

  /* ----- Related posts grid ----- */
  .bpf-related { margin-top: 2rem; padding-top: 1.5rem; border-top: 1px solid #e2e8f0; }
  .bpf-related-title {
    font-size: 0.78rem;
    font-weight: 800;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: #6b7280;
    margin: 0 0 1rem;
  }
  .bpf-related-grid {
    display: grid;
    grid-template-columns: repeat(3, minmax(0,1fr));
    gap: 1rem;
  }
  @media (max-width: 860px) { .bpf-related-grid { grid-template-columns: repeat(2, minmax(0,1fr)); } }
  @media (max-width: 540px) { .bpf-related-grid { grid-template-columns: 1fr; } }
  .bpf-card {
    background: #f4f7fb;
    border: 1px solid #cfd8dc;
    border-radius: 12px;
    overflow: hidden;
    display: flex;
    flex-direction: column;
    box-shadow: 0 2px 8px rgba(0,0,0,.05);
    transition: box-shadow .15s, transform .12s;
    text-decoration: none !important;
  }
  .bpf-card:hover { box-shadow: 0 6px 20px rgba(0,0,0,.1); transform: translateY(-2px); }
  .bpf-card-thumb {
    width: 100%;
    aspect-ratio: 16/9;
    object-fit: cover;
    display: block;
    background: #e8edf5;
  }
  .bpf-card-body { padding: .75rem .9rem .9rem; display: flex; flex-direction: column; flex: 1; }
  .bpf-card-meta {
    display: flex; align-items: center; gap: .45rem;
    font-size: .78rem; color: #6b7280; margin-bottom: .35rem; flex-wrap: wrap;
  }
  .bpf-card-cat {
    background: #e0ecff; color: #0f3d8a;
    font-weight: 700; font-size: .72rem;
    padding: .12rem .45rem; border-radius: 999px;
  }
  .bpf-card-title {
    font-size: .95rem; font-weight: 800; color: #1565c0;
    line-height: 1.3; margin: 0 0 .45rem;
  }
  .bpf-card-title a { color: inherit; text-decoration: none; }
  .bpf-card-title a:hover { text-decoration: underline; }
  .bpf-card-tags { display: flex; flex-wrap: wrap; gap: .25rem; margin-bottom: .65rem; }
  .bpf-tag {
    background: #f1f5f9; border: 1px solid #cbd5e1;
    color: #6b7280; font-size: .72rem;
    padding: .1rem .4rem; border-radius: 999px;
  }
  .bpf-btn {
    display: inline-block;
    background: #1976d2; color: #fff !important;
    text-decoration: none !important;
    padding: .4rem .85rem; border-radius: 10px;
    font-weight: 800; font-size: .84rem; text-align: center;
    transition: background .15s, box-shadow .15s;
    align-self: flex-start; margin-top: auto;
  }
  .bpf-btn:hover { background: #0f60b6; box-shadow: 0 4px 12px rgba(0,0,0,.12); }

  @media (max-width: 560px) {
    .blog-author-card { flex-direction: column; align-items: center; text-align: center; }
    .blog-author-avatar { width: 52px; height: 68px; }
    .blog-cta { text-align: center; justify-content: center; }
    .blog-resources { justify-content: center; }
  }
</style>

<div class="blog-post-footer">

  <!-- 1. CTA -->
  <div class="blog-cta">
    <div class="blog-cta-text">
      <h3>Continue learning Artificial Intelligence</h3>
      <p>Explore practical courses on AI, Machine Learning, Deep Learning, Python, R and applied data science.</p>
    </div>
    <a class="blog-cta-btn" href="https://www.manuelcastillo.eu/udemy/" target="_blank" rel="noopener noreferrer">View AI Courses &rarr;</a>
  </div>

  <!-- 2. Author card -->
  <div class="blog-author-card">
    <img class="blog-author-avatar" src="/images/profile.jpg" alt="Manuel Castillo-Cara" onerror="this.style.display='none'" />
    <div class="blog-author-info">
      <p class="blog-author-name">Manuel Castillo-Cara, PhD</p>
      <p class="blog-author-spec">TINTOlib Python Library Developer</p>
      <p class="blog-author-role">Researcher &amp; Professor</p>
      <p class="blog-author-dept">Department of Artificial Intelligence</p>
      <p class="blog-author-inst">Universidad Nacional de Educación a Distancia (UNED)</p>
      <p class="blog-author-inst">Almerimar (Almería), Spain</p>
    </div>
  </div>

  <!-- 3. License -->
  <p class="blog-license">
    &copy; Manuel Castillo-Cara, PhD. Content licensed under
    <a href="https://creativecommons.org/licenses/by-nc/4.0/" target="_blank" rel="noopener noreferrer">CC BY-NC 4.0</a>
    unless otherwise stated.
  </p>

  <!-- 4. Export / resource buttons -->
  <div class="blog-resources">
    
    
    <button type="button" class="blog-resource-btn blog-resource-btn--print" onclick="window.print()">
      🖨️ Print / Save as PDF
    </button>
    <button type="button" class="blog-resource-btn blog-resource-btn--save" id="btn-save-html">
      💾 Save as HTML
    </button>
    <button type="button" class="blog-resource-btn blog-resource-btn--python" id="btn-download-py">
      🐍 Download Python
    </button>
    <button type="button" class="blog-resource-btn blog-resource-btn--ipynb" id="btn-download-ipynb">
      📓 Notebook (.ipynb)
    </button>
  </div>

  <!-- 5. Related posts -->
  
  
    <div class="bpf-related">
      <p class="bpf-related-title">You may also enjoy</p>
      <div class="bpf-related-grid">
        
          
          
          <article class="bpf-card">
            
              <img class="bpf-card-thumb" src="/images/Blog/2026-06-02-03-improving-deep-learning-exploiting-synthetic-images.png" alt="Improving Deep Learning by Exploiting Synthetic Images — Part I: Why Tabular Data Needs Spatial Representations" loading="lazy" />
            
            <div class="bpf-card-body">
              <div class="bpf-card-meta">
                <time datetime="2026-06-02T00:00:00+02:00">02 Jun 2026</time>
                <span class="bpf-card-cat">TINTOlib</span>
              </div>
              <h3 class="bpf-card-title">
                <a href="/blog/2026/06/03-improving-deep-learning-exploiting-synthetic-images-part-1/">Improving Deep Learning by Exploiting Synthetic Images — Part I: Why Tabular Data Needs Spatial Representations</a>
              </h3>
              
                <div class="bpf-card-tags">
                  
                    <span class="bpf-tag">TINTOlib</span>
                  
                    <span class="bpf-tag">Synthetic Images</span>
                  
                    <span class="bpf-tag">Tabular-to-Image</span>
                  
                    <span class="bpf-tag">Tabular Data</span>
                  
                </div>
              
              <a class="bpf-btn" href="/blog/2026/06/03-improving-deep-learning-exploiting-synthetic-images-part-1/">Read article →</a>
            </div>
          </article>
        
          
          
          <article class="bpf-card">
            
              <img class="bpf-card-thumb" src="/images/Blog/2026-05-28-02-what-is-it-tintolib-synthetic-images.png" alt="What is TINTOlib and why should we transform tabular data into synthetic images?" loading="lazy" />
            
            <div class="bpf-card-body">
              <div class="bpf-card-meta">
                <time datetime="2026-05-28T00:00:00+02:00">28 May 2026</time>
                <span class="bpf-card-cat">TINTOlib</span>
              </div>
              <h3 class="bpf-card-title">
                <a href="/blog/2026/05/02-what-is-it-tintolib-synthetic-images/">What is TINTOlib and why should we transform tabular data into synthetic images?</a>
              </h3>
              
                <div class="bpf-card-tags">
                  
                    <span class="bpf-tag">TINTOlib</span>
                  
                    <span class="bpf-tag">Tabular Data</span>
                  
                    <span class="bpf-tag">Synthetic Images</span>
                  
                    <span class="bpf-tag">Deep Learning</span>
                  
                </div>
              
              <a class="bpf-btn" href="/blog/2026/05/02-what-is-it-tintolib-synthetic-images/">Read article →</a>
            </div>
          </article>
        
          
          
          <article class="bpf-card">
            
              <img class="bpf-card-thumb" src="/images/Blog/2026-05-27-01-introduction-to-tintolib-tabular-to-image.png" alt="Introduction to TINTOlib: Unlocking the Power of Vision Architectures for Tabular Data" loading="lazy" />
            
            <div class="bpf-card-body">
              <div class="bpf-card-meta">
                <time datetime="2026-05-27T00:00:00+02:00">27 May 2026</time>
                <span class="bpf-card-cat">TINTOlib</span>
              </div>
              <h3 class="bpf-card-title">
                <a href="/blog/2026/05/01-introduction-to-tintolib-tabular-to-image/">Introduction to TINTOlib: Unlocking the Power of Vision Architectures for Tabular Data</a>
              </h3>
              
                <div class="bpf-card-tags">
                  
                    <span class="bpf-tag">TINTOlib</span>
                  
                    <span class="bpf-tag">Tabular-to-Image</span>
                  
                    <span class="bpf-tag">Synthetic Images</span>
                  
                    <span class="bpf-tag">Deep Learning</span>
                  
                </div>
              
              <a class="bpf-btn" href="/blog/2026/05/01-introduction-to-tintolib-tabular-to-image/">Read article →</a>
            </div>
          </article>
        
      </div>
    </div>
  

</div>

<!-- ===== Copy-code + Export logic ===== -->
<script>
(function () {

  /* ---------- helpers ---------- */
  function slugify(str) {
    return (str || 'blog-post')
      .replace(/[^a-z0-9\-_]/gi, '-')
      .replace(/-+/g, '-')
      .toLowerCase()
      .substring(0, 60)
      .replace(/^-+|-+$/g, '');
  }

  function download(content, filename, mime) {
    var blob = new Blob([content], { type: mime });
    var url  = URL.createObjectURL(blob);
    var a    = document.createElement('a');
    a.href = url;
    a.download = filename;
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
    setTimeout(function () { URL.revokeObjectURL(url); }, 5000);
  }

  function getCodeBlocks() {
    var nodes = document.querySelectorAll('.page__content pre code');
    var blocks = [];
    nodes.forEach(function (node) {
      var text = node.textContent.trimEnd();
      if (text) blocks.push(text);
    });
    return blocks;
  }

  /* ---------- Copy-code buttons ---------- */
  var pres = document.querySelectorAll('.page__content pre');
  pres.forEach(function (pre) {
    var code = pre.querySelector('code');
    var getText = function () {
      if (code) return code.textContent;
      var clone = pre.cloneNode(true);
      clone.querySelectorAll('.copy-code-btn').forEach(function (b) { b.parentNode.removeChild(b); });
      return clone.textContent;
    };
    var btn = document.createElement('button');
    btn.type = 'button';
    btn.className = 'copy-code-btn';
    btn.textContent = 'Copy';
    btn.setAttribute('aria-label', 'Copy code to clipboard');
    pre.appendChild(btn);
    btn.addEventListener('click', function () {
      var text = getText();
      if (navigator.clipboard && navigator.clipboard.writeText) {
        navigator.clipboard.writeText(text).then(function () { flash(btn); });
      } else {
        var ta = document.createElement('textarea');
        ta.value = text;
        ta.style.cssText = 'position:fixed;opacity:0;top:0;left:0';
        document.body.appendChild(ta);
        ta.focus(); ta.select();
        try { document.execCommand('copy'); } catch (e) {}
        document.body.removeChild(ta);
        flash(btn);
      }
    });
  });
  function flash(btn) {
    btn.textContent = 'Copied \u2713';
    btn.classList.add('copied');
    setTimeout(function () { btn.textContent = 'Copy'; btn.classList.remove('copied'); }, 1600);
  }

  /* ---------- Save as HTML ---------- */
  var saveHtmlBtn = document.getElementById('btn-save-html');
  if (saveHtmlBtn) {
    saveHtmlBtn.addEventListener('click', function () {
      var clone = document.documentElement.cloneNode(true);
      var origin = window.location.origin;
      clone.querySelectorAll('img[src]').forEach(function (img) {
        var src = img.getAttribute('src');
        if (src && src.charAt(0) === '/') img.setAttribute('src', origin + src);
      });
      clone.querySelectorAll('link[href]').forEach(function (el) {
        var href = el.getAttribute('href');
        if (href && href.charAt(0) === '/') el.setAttribute('href', origin + href);
      });
      clone.querySelectorAll('script[src]').forEach(function (el) {
        var src = el.getAttribute('src');
        if (src && src.charAt(0) === '/') el.setAttribute('src', origin + src);
      });
      var html = '<!doctype html>\n' + clone.outerHTML;
      download(html, slugify(document.title) + '.html', 'text/html;charset=utf-8');
    });
  }

  /* ---------- Download Python ---------- */
  var pyBtn = document.getElementById('btn-download-py');
  if (pyBtn) {
    pyBtn.addEventListener('click', function () {
      var blocks = getCodeBlocks();
      var lines;
      if (blocks.length === 0) {
        lines = ['# No code blocks found in this post.\n'];
      } else {
        lines = [];
        blocks.forEach(function (block, i) {
          lines.push('# ---- Code block ' + (i + 1) + ' ----\n');
          lines.push(block + '\n\n');
        });
      }
      download(lines.join(''), slugify(document.title) + '.py', 'text/x-python;charset=utf-8');
    });
  }

  /* ---------- Download Notebook (.ipynb) — full article walker ---------- */
  var ipynbBtn = document.getElementById('btn-download-ipynb');
  if (ipynbBtn) {
    ipynbBtn.addEventListener('click', function () {

      function mdLines(text) {
        /* Split into source array as Jupyter expects (lines ending with \n except last) */
        var lines = text.split('\n');
        return lines.map(function (l, i) { return i < lines.length - 1 ? l + '\n' : l; });
      }

      function mkdCell(text) {
        var t = (text || '').trim();
        if (!t) return null;
        return { cell_type: 'markdown', metadata: {}, source: mdLines(t) };
      }

      function codeCell(text) {
        var t = (text || '').trimEnd();
        if (!t) return null;
        return { cell_type: 'code', execution_count: null, metadata: {}, outputs: [], source: mdLines(t) };
      }

      function nodeToMd(el) {
        var tag = el.tagName ? el.tagName.toLowerCase() : '';
        /* headings */
        if (/^h[1-6]$/.test(tag)) {
          var level = parseInt(tag.slice(1), 10);
          return Array(level + 1).join('#') + ' ' + el.textContent.trim();
        }
        /* paragraph */
        if (tag === 'p') return el.textContent.trim();
        /* blockquote */
        if (tag === 'blockquote') {
          return el.textContent.trim().split('\n').map(function (l) { return '> ' + l; }).join('\n');
        }
        /* lists */
        if (tag === 'ul' || tag === 'ol') {
          var items = [];
          el.querySelectorAll('li').forEach(function (li, i) {
            items.push((tag === 'ol' ? (i + 1) + '. ' : '- ') + li.textContent.trim());
          });
          return items.join('\n');
        }
        /* figure / img */
        if (tag === 'figure') {
          var img = el.querySelector('img');
          if (img) {
            var src = img.getAttribute('src') || '';
            if (src.charAt(0) === '/') src = window.location.origin + src;
            var alt = img.getAttribute('alt') || '';
            var cap = el.querySelector('figcaption');
            return '!['+ alt +']('+ src +')' + (cap ? '\n*' + cap.textContent.trim() + '*' : '');
          }
        }
        if (tag === 'img') {
          var src2 = el.getAttribute('src') || '';
          if (src2.charAt(0) === '/') src2 = window.location.origin + src2;
          return '![' + (el.getAttribute('alt') || '') + '](' + src2 + ')';
        }
        return '';
      }

      function getArticleCellsForNotebook() {
        var container = document.querySelector('.page__content');
        if (!container) return [];

        var clone = container.cloneNode(true);
        /* Remove footer, scripts, styles, copy buttons */
        ['script','style','.blog-post-footer','.copy-code-btn'].forEach(function (sel) {
          clone.querySelectorAll(sel).forEach(function (n) { n.parentNode.removeChild(n); });
        });

        var cells = [];
        var INLINE_TAGS = /^(h[1-6]|p|ul|ol|blockquote|figure|img)$/;

        function walk(node) {
          if (node.nodeType !== 1) return; /* element nodes only */
          var tag = node.tagName.toLowerCase();

          /* code block */
          if (tag === 'pre') {
            var code = node.querySelector('code');
            var text = (code ? code : node).textContent.trimEnd();
            var cell = codeCell(text);
            if (cell) cells.push(cell);
            return;
          }

          /* inline-mappable tags */
          if (INLINE_TAGS.test(tag)) {
            var md = nodeToMd(node);
            var cell2 = mkdCell(md);
            if (cell2) cells.push(cell2);
            return;
          }

          /* containers: div, section, article — recurse children */
          Array.prototype.forEach.call(node.childNodes, function (child) {
            walk(child);
          });
        }

        Array.prototype.forEach.call(clone.childNodes, function (child) {
          walk(child);
        });

        return cells;
      }

      var cells = [];
      /* Header cells */
      cells.push(mkdCell('# ' + (document.title || 'Blog Post')));
      var pageUrl = window.location.href;
      cells.push(mkdCell('**Source:** [' + pageUrl + '](' + pageUrl + ')'));

      /* Article body cells */
      var bodyCells = getArticleCellsForNotebook();
      if (bodyCells.length === 0) {
        cells.push(mkdCell('*No content extracted from this post.*'));
      } else {
        cells = cells.concat(bodyCells);
      }

      var nb = {
        nbformat: 4,
        nbformat_minor: 5,
        metadata: {
          kernelspec: { display_name: 'Python 3', language: 'python', name: 'python3' },
          language_info: { name: 'python', version: '3.x' }
        },
        cells: cells
      };

      download(JSON.stringify(nb, null, 2), slugify(document.title) + '.ipynb', 'application/json;charset=utf-8');
    });
  }

}());
</script>]]></content><author><name>Ph.D. Manuel Castillo-Cara</name><email>manwest.c@gmail.com</email></author><category term="TINTOlib" /><category term="TINTOlib" /><category term="Synthetic Images" /><category term="Tabular-to-Image" /><category term="Hybrid Neural Networks" /><category term="Deep Learning" /><category term="CNN" /><category term="Vision Transformer" /><category term="Explainable AI" /><category term="Spatial Encoding" /><summary type="html"><![CDATA[Part II of a theoretical and technical series on synthetic images for tabular data, focusing on TINTOlib, preferred spatial encoding methods, hybrid neural networks, and explainable AI.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.manuelcastillo.eu/images/Blog/2026-06-02-04-xai-hynn.png" /><media:content medium="image" url="https://www.manuelcastillo.eu/images/Blog/2026-06-02-04-xai-hynn.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Improving Deep Learning by Exploiting Synthetic Images — Part I: Why Tabular Data Needs Spatial Representations</title><link href="https://www.manuelcastillo.eu/blog/2026/06/03-improving-deep-learning-exploiting-synthetic-images-part-1/" rel="alternate" type="text/html" title="Improving Deep Learning by Exploiting Synthetic Images — Part I: Why Tabular Data Needs Spatial Representations" /><published>2026-06-02T00:00:00+02:00</published><updated>2026-06-02T00:00:00+02:00</updated><id>https://www.manuelcastillo.eu/blog/2026/06/03-improving-deep-learning-exploiting-synthetic-images-part-1</id><content type="html" xml:base="https://www.manuelcastillo.eu/blog/2026/06/03-improving-deep-learning-exploiting-synthetic-images-part-1/"><![CDATA[<link rel="canonical" href="https://www.manuelcastillo.eu/blog/2026/06/03-improving-deep-learning-exploiting-synthetic-images-part-1/" />

<meta name="robots" content="index,follow,max-image-preview:large" />

<meta name="description" content="Part I of a theoretical and technical series on why deep learning still struggles with tabular data, and why synthetic image representations provide a promising bridge between structured data and computer vision architectures." />

<meta property="og:type" content="article" />

<meta property="og:title" content="Improving Deep Learning by Exploiting Synthetic Images — Part I: Why Tabular Data Needs Spatial Representations" />

<meta property="og:description" content="Part I of a theoretical and technical series on why deep learning still struggles with tabular data, and why synthetic image representations provide a promising bridge between structured data and computer vision architectures." />

<meta property="og:url" content="https://www.manuelcastillo.eu/blog/2026/06/03-improving-deep-learning-exploiting-synthetic-images-part-1/" />

<meta property="og:image" content="https://www.manuelcastillo.eu/images/Blog/2026-06-02-03-improving-deep-learning-exploiting-synthetic-images.png" />

<meta property="article:published_time" content="2026-06-02T00:00:00+02:00" />

<meta property="article:modified_time" content="2026-06-02T00:00:00+02:00" />

<meta property="article:author" content="Manuel Castillo-Cara" />

<meta property="article:section" content="TINTOlib" />

<meta property="article:tag" content="TINTOlib" />

<meta property="article:tag" content="Synthetic Images" />

<meta property="article:tag" content="Tabular-to-Image" />

<meta property="article:tag" content="Tabular Data" />

<meta property="article:tag" content="Deep Learning" />

<meta property="article:tag" content="Computer Vision" />

<meta property="article:tag" content="Spatial Encoding" />

<meta property="article:tag" content="Machine Learning" />

<meta property="article:tag" content="Data Science" />

<meta name="twitter:card" content="summary_large_image" />

<meta name="twitter:title" content="Improving Deep Learning by Exploiting Synthetic Images — Part I: Why Tabular Data Needs Spatial Representations" />

<meta name="twitter:description" content="Part I of a theoretical and technical series on why deep learning still struggles with tabular data, and why synthetic image representations provide a promising bridge between structured data and computer vision architectures." />

<meta name="twitter:image" content="https://www.manuelcastillo.eu/images/Blog/2026-06-02-03-improving-deep-learning-exploiting-synthetic-images.png" />

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "BlogPosting",
  "headline": "Improving Deep Learning by Exploiting Synthetic Images — Part I: Why Tabular Data Needs Spatial Representations",
  "description": "Part I of a theoretical and technical series on why deep learning still struggles with tabular data, and why synthetic image representations provide a promising bridge between structured data and computer vision architectures.",
  "image": "https://www.manuelcastillo.eu/images/Blog/2026-06-02-03-improving-deep-learning-exploiting-synthetic-images.png",
  "author": {
    "@type": "Person",
    "name": "Manuel Castillo-Cara",
    "url": "https://www.manuelcastillo.eu/"
  },
  "publisher": {
    "@type": "Person",
    "name": "Manuel Castillo-Cara",
    "url": "https://www.manuelcastillo.eu/"
  },
  "datePublished": "2026-06-02T00:00:00+02:00",
  "dateModified": "2026-06-02T00:00:00+02:00",
  "mainEntityOfPage": {
    "@type": "WebPage",
    "@id": "https://www.manuelcastillo.eu/blog/2026/06/03-improving-deep-learning-exploiting-synthetic-images-part-1/"
  },
  "articleSection": "TINTOlib",
  "keywords": "TINTOlib, Synthetic Images, Tabular-to-Image, Tabular Data, Deep Learning, Computer Vision, Spatial Encoding, Machine Learning, Data Science"
}
</script>

<div style="background: linear-gradient(135deg, #0f172a 0%, #1e3a8a 45%, #14532d 100%); border-radius: 12px; padding: 2.5rem 2rem; margin: 1.5rem 0 2.5rem; display: flex; flex-wrap: wrap; align-items: center; gap: 2rem; color: #fff;">
  <div style="flex: 1 1 280px; min-width: 0;">
    <p style="margin: 0 0 0.4rem; font-size: 0.78rem; letter-spacing: 0.12em; text-transform: uppercase; color: #bfdbfe; font-weight: 600;">TINTOlib · Synthetic Images · Tabular Data</p>
    <h1 style="margin: 0 0 0.75rem; font-size: clamp(1.5rem, 4vw, 2.1rem); font-weight: 800; line-height: 1.2; color: #fff;">Improving Deep Learning by Exploiting Synthetic Images</h1>
    <p style="margin: 0 0 1rem; font-size: 0.97rem; color: #dbeafe; line-height: 1.55;">Part I — Why tabular data remains a difficult domain for deep learning, and why representation is the central methodological problem.</p>
    <div style="display: flex; flex-wrap: wrap; gap: 0.5rem;">
      <span style="background: rgba(255,255,255,0.15); border-radius: 20px; padding: 0.25rem 0.75rem; font-size: 0.78rem; color: #e0f2fe;">Tabular Data</span>
      <span style="background: rgba(255,255,255,0.15); border-radius: 20px; padding: 0.25rem 0.75rem; font-size: 0.78rem; color: #e0f2fe;">Deep Learning</span>
      <span style="background: rgba(255,255,255,0.15); border-radius: 20px; padding: 0.25rem 0.75rem; font-size: 0.78rem; color: #e0f2fe;">Spatial Encoding</span>
      <span style="background: rgba(255,255,255,0.15); border-radius: 20px; padding: 0.25rem 0.75rem; font-size: 0.78rem; color: #e0f2fe;">TINTOlib</span>
    </div>
  </div>
  <div style="flex: 0 0 auto; max-width: 260px; width: 100%;">
    <img src="/images/Blog/2026-06-02-03-improving-deep-learning-exploiting-synthetic-images.png" alt="Improving deep learning by exploiting synthetic images" style="width: 100%; border-radius: 10px; box-shadow: 0 8px 32px rgba(0,0,0,0.45); display: block;" />
  </div>
</div>

<hr />

<blockquote>
  <blockquote>
    <ul>
      <li><strong>Author:</strong> Manuel Castillo-Cara, PhD</li>
      <li><strong>Affiliation:</strong> Dpt. of Artificial Intelligence, Universidad Nacional de Educación a Distancia (UNED), Spain</li>
      <li><strong>Role:</strong> Researcher, Professor, and TINTOlib Python Library Developer</li>
      <li><strong>License:</strong> <a href="https://creativecommons.org/licenses/by-nc/4.0/">CC BY-NC 4.0</a> unless otherwise stated.</li>
    </ul>
  </blockquote>
</blockquote>

<hr />

<h2 id="video-overview">Video overview</h2>

<p>This post is part of a two-part technical summary derived from the conference <em>Improving Deep Learning by Exploiting Synthetic Images</em>, delivered in Peru.</p>

<p>The following short video provides an English overview of the main ideas discussed across both posts: why tabular data remains challenging for deep learning, how synthetic images can introduce spatial representations, and how TINTOlib connects structured data with computer vision, hybrid neural networks and explainable AI.</p>

<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden; max-width: 100%; margin: 1.5rem 0;">
  <video controls="" preload="metadata" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border-radius: 10px; background: #000;">
    <source src="/video/Blog/2026-06-02-03-improving-deep-learning.mp4" type="video/mp4" />
    Your browser does not support the video tag.
  </video>
</div>

<blockquote>
  <p><strong>Additional material.</strong> The original conference recording is available in <strong>Spanish</strong>. The two blog posts provide an English technical synthesis and discussion based on that conference.</p>

  <p><a href="https://unedo365-my.sharepoint.com/:v:/g/personal/manuelcastillo_dia_uned_es/IQDov79-I54sS7jNgIIAaTS3AVs0L5cVinne29xXLZZNsc0?e=DVgrOZ">Open the original conference recording in SharePoint</a></p>
</blockquote>

<h2 id="introduction-why-another-post-on-synthetic-images">Introduction: why another post on synthetic images?</h2>

<p>Most introductions to deep learning start from domains where the data already have a rich native structure: images, text, audio, video or graphs. In those domains, the input representation is not merely a container of values; it already carries some form of organization. Pixels are arranged in a plane. Words appear in sequences. Audio samples evolve over time. Graphs encode nodes and edges.</p>

<p>Tabular data are different. A table is one of the most common data structures in science, industry and public administration, but its geometry is weakly defined. Rows represent samples and columns represent variables, yet the order of those variables is usually arbitrary. A tabular dataset may contain age, sex, income, biomarkers, signal measurements, sensor values or derived indicators, but the fact that one column appears before another rarely means that both variables are spatial neighbours.</p>

<p>This creates a paradox. Deep learning has transformed computer vision, natural language processing and generative modelling, but for many tabular problems classical machine learning models remain extremely strong. Random Forest, XGBoost, LightGBM and CatBoost are still difficult baselines to beat. The problem is not simply that deep learning is less powerful. The problem is that many of its most successful architectures are designed to exploit structure that tabular data do not naturally provide.</p>

<p>The theoretical question addressed in the conference, and in this post, is therefore the following:</p>

<blockquote>
  <p>Can we improve deep learning on tabular data by constructing a meaningful spatial representation before applying the neural architecture?</p>
</blockquote>

<p>This question is the starting point for tabular-to-image transformation, synthetic image generation from structured data and the TINTOlib framework.</p>

<h2 id="data-science-as-a-transformation-from-data-to-knowledge">Data science as a transformation from data to knowledge</h2>

<p>A useful way to frame data science is to see it as a transformation pipeline: data are collected, curated, represented, modelled and interpreted in order to produce knowledge. This knowledge should not be limited to a numerical prediction. It should be reproducible, generalizable and sufficiently interpretable to support scientific or operational decisions.</p>

<p>In practice, the first difficulty is that real-world data are heterogeneous. Data may arrive as images, time series, text, graphs or tables. In many institutional and industrial scenarios, however, the dominant format is tabular. Corporate databases, biomedical registries, educational records, indoor localisation measurements, sensor logs and many scientific datasets are naturally stored as tables.</p>

<p>This is why the tabular domain is so important. A method that improves learning from tabular data can have impact across many applied areas. At the same time, this is why the problem is difficult. Tabular datasets often combine numerical and categorical variables, heterogeneous scales, missing values, non-linear dependencies, interactions between variables and relatively small sample sizes compared with modern image or language datasets.</p>

<p><img src="/images/Blog/2026-06-02-03-tabular-bottleneck.png" alt="Tabular data bottleneck" /></p>

<p><em>(Figure 1. Motivation of the tabular data bottleneck. A large fraction of real-world organisational and scientific data is stored in tabular form, yet standard deep learning architectures do not naturally exploit this representation. The key methodological challenge is to transform structured data into representations that preserve variable relationships while remaining suitable for modern neural architectures.)</em></p>

<p>The central claim of this post is that representation is not a secondary detail. It is a fundamental modelling decision. If the representation is inadequate, even a powerful neural architecture may fail to exploit the relevant structure of the problem.</p>

<h2 id="why-cnns-and-vits-work-so-well-on-images">Why CNNs and ViTs work so well on images</h2>

<p>To understand why tabular data are difficult, it is useful to recall why deep learning works so well in computer vision.</p>

<p>A natural image has spatial locality. Neighbouring pixels tend to be related. They may belong to the same edge, texture, object or background region. CNNs exploit this property through convolutional filters that slide across the image. These filters detect local patterns and reuse them across different spatial positions. This creates an architectural prior: the model assumes that local structures matter and that patterns can appear in different parts of the image.</p>

<p>Vision Transformers follow a different strategy, but they also assume an organized visual representation. An image is divided into patches, each patch is embedded, and attention mechanisms learn relationships between patches. The model is not restricted to local filters in the same way as a CNN, but it still begins from a spatially arranged representation.</p>

<p>In both cases, the input representation supports the architecture. The image is not just a vector. It is a structured object.</p>

<p>A raw tabular dataset does not provide this kind of support. If a table is treated as an image by simply reshaping columns into a matrix, the resulting arrangement may be arbitrary. The position of a feature in the synthetic grid may depend on column order rather than on meaningful relationships. This is precisely the problem that tabular-to-image methods attempt to solve.</p>

<p><img src="/images/Blog/2026-06-02-03-cnn-fails-tabular-direct-matrix.png" alt="Why CNNs fail on direct tabular matrices" /></p>

<p><em>(Figure 2. Visual metaphor illustrating why CNNs are not directly suited to raw tabular matrices. In natural images, neighbouring pixels encode meaningful local and spatial correlations, allowing convolutional filters to detect edges, textures and shapes. In tabular data, however, column order is usually arbitrary: reordering variables such as age, salary or sex changes the apparent pixel arrangement while preserving the semantic content of the sample. A CNN applied directly to such a matrix may therefore learn artefacts induced by column order rather than meaningful feature interactions.)</em></p>

<p>This visual metaphor is also important when evaluating tabular-to-image transformation methods. A method that simply converts a table into an image without solving the ordering problem may still expose a CNN to an arbitrary geometry. The objective is not only to obtain an image, but to construct a spatial representation whose neighbourhoods have methodological meaning.</p>

<h2 id="why-tabular-data-remains-the-difficult-domain">Why tabular data remains the difficult domain</h2>

<p>Deep learning on tabular data is not impossible. There are many neural architectures for structured data, including MLPs, attention-based models, entity embeddings, differentiable trees and transformer variants for tables. However, the empirical picture remains complex. In many benchmarks, strong ensemble models still outperform standard neural networks.</p>

<p>There are several reasons for this.</p>

<p>First, tabular datasets are often small or medium-sized. Deep neural networks usually benefit from large-scale data, while many tabular problems contain hundreds, thousands or tens of thousands of samples rather than millions.</p>

<p>Second, tabular features are heterogeneous. Some are continuous, others categorical, ordinal, binary or derived. Their scales and distributions may differ substantially.</p>

<p>Third, interactions between variables can be sparse, non-linear and dataset-specific. A variable may be informative only in combination with another variable, or only for a subset of the population.</p>

<p>Fourth, tabular data have no canonical spatial arrangement. Unlike images, tables do not tell us which variables should be neighbours.</p>

<p>This fourth point is especially relevant for the synthetic image paradigm. If a CNN is to process a tabular sample as an image, the image must not be an arbitrary reshaping of the table. It must be a constructed representation in which spatial proximity has methodological meaning.</p>

<p><img src="/images/Blog/2026-06-02-03-last-unconquered-castle.png" alt="Deep learning and tabular data as an open problem" />
<em>(Figure 3. Deep learning for tabular data as an open research problem. Although deep neural networks have achieved remarkable success in computer vision, natural language processing and generative AI, tabular data remain a challenging domain where ensemble methods are still strong baselines. This motivates the search for alternative representations rather than naive applications of generic neural architectures.)</em></p>

<h2 id="the-central-problem-is-not-the-model-it-is-the-representation">The central problem is not the model; it is the representation</h2>

<p>A frequent mistake is to treat model selection and data representation as independent decisions. In reality, architecture and representation are deeply coupled.</p>

<p>A CNN is not simply a classifier. It is a classifier designed for spatially organized data. A ViT is not simply an attention mechanism. It assumes a patch-based visual input. A recurrent neural network assumes a sequence. A graph neural network assumes a graph.</p>

<p>Therefore, when applying deep learning to tabular data, we must ask what structure the model is expected to exploit. If no structure is provided, the model must infer everything from scratch. If an arbitrary structure is imposed, the model may learn artifacts.</p>

<p>Synthetic image generation is a representation strategy. It proposes that instead of forcing a neural network to process a raw table, we first map the table into a spatial domain. Then we can use visual architectures in a more coherent way.</p>

<p>The resulting image is not natural. It is synthetic. It does not represent a physical scene. It represents the values of a tabular instance arranged according to a spatial encoding of the variables.</p>

<h2 id="from-tabular-vectors-to-synthetic-images">From tabular vectors to synthetic images</h2>

<p>The transformation can be understood in two stages.</p>

<p>First, the method learns or defines a spatial layout of the features. Each feature is assigned a position in a two-dimensional grid. Ideally, features that are statistically, geometrically or semantically related should be placed close to one another.</p>

<p>Second, each sample is projected into that grid. The feature values of the sample become pixel intensities or spatial patterns, producing a synthetic image.</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Tabular dataset
    |
    | learn feature layout
    v
2D feature map
    |
    | project each sample
    v
Synthetic image dataset
    |
    | CNN / ViT / hybrid neural network
    v
Prediction and explanation
</code></pre></div></div>

<p>This transformation does not claim that tabular data are images. Rather, it constructs an image-like representation that allows the use of architectures developed for the visual domain.</p>

<p>The methodological quality of the approach depends heavily on how the feature layout is constructed. A good transformation should reduce arbitrariness. A poor transformation may simply reproduce the arbitrary ordering of the original table.</p>

<h2 id="a-necessary-distinction-spatially-informed-methods-vs-order-dependent-methods">A necessary distinction: spatially informed methods vs. order-dependent methods</h2>

<p>This distinction is crucial.</p>

<p>Some tabular-to-image methods are strongly influenced by the original order or structure of the table. Examples include <strong>BarGraph</strong>, <strong>DistanceMatrix</strong>, <strong>Combination</strong>, <strong>SuperTML</strong>, <strong>BIE</strong> and <strong>FeatureWrap</strong>. These methods may be useful as baselines or didactic examples, but they should be used with caution as primary transformation strategies when the feature order is arbitrary.</p>

<p>The reason is methodological. If the generated image depends on whether <code class="language-plaintext highlighter-rouge">age</code> appears before or after <code class="language-plaintext highlighter-rouge">sex</code>, then the transformation inherits the same limitation that motivated the problem in the first place: tabular data do not possess a natural spatial order. In that case, the synthetic image may encode artifacts of the column arrangement rather than meaningful relationships between variables.</p>

<p>This does not mean that these methods are never useful. They can be relevant when the input table has a meaningful pre-existing order, when the objective is educational, or when they are included as comparative baselines. However, for rigorous tabular-to-image modelling, they should not be treated as the preferred family of methods if the feature ordering is arbitrary.</p>

<p>In contrast, methods such as <strong>TINTO</strong>, <strong>DeepInsight</strong>, <strong>IGTD</strong>, <strong>REFINED</strong>, <strong>Fotomics</strong> and recent unsupervised learning-based approaches such as <strong>ILUSTRE/ILUSTRES</strong> aim to construct the spatial representation from the data structure itself. These methods attempt to reduce dependence on the original column order by learning or optimizing the spatial arrangement of features. Their goal is precisely to create a meaningful visual representation where the geometry reflects relationships in the data, not the accidental ordering of the spreadsheet.</p>

<p>This distinction should be made explicit in any serious discussion of synthetic images for tabular data. The key question is not only whether a method produces an image. The key question is whether the image has a defensible spatial structure.</p>

<h2 id="the-paradigm-shift">The paradigm shift</h2>

<p>The paradigm shift introduced by TINTOlib can therefore be understood as a representational shift. Instead of applying deep learning directly to unordered tabular vectors, the method first constructs a spatial representation where feature relationships can be expressed geometrically. Once this representation exists, computer vision architectures can be applied under assumptions that are more coherent with their original design.</p>

<p>This does not mean that synthetic images are universally superior to classical tabular models. Rather, it means that they create a new experimental space: one in which tabular data can be studied through visual, hybrid and explainable neural architectures.</p>

<p>From a teaching perspective, this paradigm is also useful because it makes the problem visible. Students can compare different spatial encodings, inspect the resulting images and observe how representation affects the behaviour of the model.</p>

<h2 id="what-part-ii-will-cover">What Part II will cover</h2>

<p>This first part has focused on the theoretical motivation: why tabular data remain difficult for deep learning, why representation matters, and why spatially meaningful synthetic images are different from arbitrary reshaping.</p>

<p>In the second part, we will move from the conceptual problem to the modelling pipeline. We will discuss how TINTOlib operationalizes this paradigm, how different transformation methods should be compared, how hybrid neural networks combine tabular and visual branches, and how visual explainability methods can be adapted to synthetic tabular images.</p>

<p><a href="/blog/2026/06/04-from-synthetic-images-to-hybrid-neural-networks/">Continue with Part II: From Synthetic Images to Hybrid Neural Networks</a></p>

<h2 id="references-and-related-publications">References and related publications</h2>

<p>The concepts presented in this tutorial are connected to the following research and software publications on TINTO, TINTOlib, tabular-to-image transformation, synthetic spatial representations, hybrid neural networks, indoor localisation and explainable artificial intelligence.</p>

<h3 id="research-articles">Research articles</h3>

<ol>
  <li>
    <p>Jiayun Liu, Manuel Castillo-Cara et al. <strong>Interpretable Hybrid Vision Transformer Architectures for MIMO-Based Indoor Localization using Synthetic Spatial Representations</strong>. <em>IEEE Internet of Things</em>. DOI: <a href="https://doi.org/10.1109/JIOT.2026.3696106">10.1109/JIOT.2026.3696106</a></p>
  </li>
  <li>
    <p>Jiayun Liu, Manuel Castillo-Cara et al. <strong>A Comprehensive Benchmark of Spatial Encoding Methods for Tabular Data with Deep Neural Networks</strong>. <em>Information Fusion</em>. DOI: <a href="https://doi.org/10.1016/j.inffus.2025.104088">10.1016/j.inffus.2025.104088</a></p>
  </li>
  <li>
    <p>Giovanny Mondragon-Ruiz, Jiayun Liu, Manuel Castillo-Cara et al. <strong>Interpretable CNN–KAN hybrid architectures for tabular data with synthetic image encoding</strong>. <em>Information Processing and Management</em>. DOI: <a href="https://doi.org/10.1016/j.ipm.2026.104954">10.1016/j.ipm.2026.104954 </a></p>
  </li>
  <li>
    <p>Manuel Castillo-Cara et al. <strong>MIMO-Based Indoor Localisation with Hybrid Neural Networks</strong>. <em>IEEE Journal of Selected Topics in Signal Processing</em>. DOI: <a href="https://doi.org/10.1109/JSTSP.2025.3555067">10.1109/JSTSP.2025.3555067</a></p>
  </li>
  <li>
    <p>Reewos Talla-Chumpitaz, Manuel Castillo-Cara et al. <strong>Blurring Image Techniques for Bluetooth-based Indoor Localisation</strong>. <em>Information Fusion</em>. DOI: <a href="https://doi.org/10.1016/j.inffus.2022.10.011">10.1016/j.inffus.2022.10.011</a></p>
  </li>
</ol>

<h3 id="software-articles">Software articles</h3>

<ol>
  <li>
    <p>Jiayun Liu et al. <strong>TINTOlib: A Python library for transforming tabular data into synthetic images for deep neural networks</strong>. <em>SoftwareX</em>. DOI: <a href="https://doi.org/10.1016/j.softx.2025.102444">10.1016/j.softx.2025.102444</a></p>
  </li>
  <li>
    <p>Manuel Castillo-Cara et al. <strong>TINTO: Converting Tidy Data into Image for Classification with 2-Dimensional Convolutional Neural Networks</strong>. <em>SoftwareX</em>. DOI: <a href="https://doi.org/10.1016/j.softx.2023.101391">10.1016/j.softx.2023.101391</a></p>
  </li>
</ol>

<!-- ===== Blog Post Footer: CTA → Author → License → Export ===== -->
<style>
  /* Hide default Minimal Mistakes related posts (replaced by custom cards below) */
  .page__related { display: none !important; }

  /* ----- Copy-code button ----- */
  .page__content pre { position: relative; }
  .copy-code-btn {
    position: absolute;
    top: 0.35rem;
    right: 0.45rem;
    padding: 0.15rem 0.55rem;
    font-size: 0.72rem;
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
    background: #eff6ff;
    color: #1d4ed8;
    border: 1px solid #bfdbfe;
    border-radius: 4px;
    cursor: pointer;
    transition: background 0.15s, color 0.15s;
    line-height: 1.5;
    z-index: 2;
    user-select: none;
    font-weight: 600;
  }
  .copy-code-btn:hover { background: #dbeafe; color: #1e40af; border-color: #93c5fd; }
  .copy-code-btn.copied { background: #d1fae5; color: #065f46; border-color: #6ee7b7; }

  /* ----- Post footer wrapper ----- */
  .blog-post-footer {
    margin-top: 3rem;
    border-top: 2px solid #e2e8f0;
    padding-top: 2rem;
  }

  /* ----- CTA (first) ----- */
  .blog-cta {
    background: linear-gradient(135deg, #1565c0 0%, #6d28d9 100%);
    color: #fff;
    border-radius: 12px;
    padding: 1.4rem 1.75rem;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    margin-bottom: 1.5rem;
    box-shadow: 0 4px 18px rgba(21,101,192,0.22);
  }
  .blog-cta-text h3 { margin: 0 0 0.3rem; font-size: 1.05rem; color: #fff; }
  .blog-cta-text p  { margin: 0; font-size: 0.88rem; color: rgba(255,255,255,0.85); }
  .blog-cta-btn {
    display: inline-block;
    background: #fff;
    color: #1565c0 !important;
    font-weight: 800;
    font-size: 0.9rem;
    text-decoration: none !important;
    padding: 0.55rem 1.3rem;
    border-radius: 8px;
    white-space: nowrap;
    transition: box-shadow 0.15s, transform 0.1s;
  }
  .blog-cta-btn:hover { box-shadow: 0 4px 14px rgba(0,0,0,.22); transform: translateY(-1px); }

  /* ----- Author card (second) ----- */
  .blog-author-card {
    display: flex;
    align-items: center;
    gap: 0.85rem;
    background: #f8fafc;
    border: 1px solid #e2e8f0;
    border-radius: 12px;
    padding: 0.8rem 1.1rem;
    margin-bottom: 0.75rem;
    box-shadow: 0 2px 8px rgba(0,0,0,.05);
  }
  .blog-author-avatar {
    width: 58px;
    height: 76px;
    border-radius: 999px;
    object-fit: cover;
    flex-shrink: 0;
    border: 2px solid #c7d9f0;
    background: #e8edf5;
    box-shadow: 0 2px 8px rgba(0,0,0,.12);
  }
  .blog-author-info { flex: 1; min-width: 0; }
  .blog-author-card p { margin: 0; line-height: 1.25; }
  .blog-author-name { font-weight: 800; font-size: 0.95rem !important; color: #1f2937; }
  .blog-author-role { font-size: 0.82rem !important; color: #374151; }
  .blog-author-spec { font-size: 0.82rem !important; color: #374151; }
  .blog-author-dept { font-size: 0.78rem !important; color: #4b5563; }
  .blog-author-inst { font-size: 0.76rem !important; color: #6b7280; }

  /* ----- License (third) ----- */
  .blog-license {
    font-size: 0.81rem;
    color: #6b7280;
    background: #f1f5f9;
    border: 1px solid #e2e8f0;
    border-radius: 8px;
    padding: 0.55rem 1rem;
    margin-bottom: 1.1rem;
  }
  .blog-license a { color: #1976d2; }

  /* ----- Resource / export buttons (fourth) ----- */
  .blog-resources {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin-bottom: 0.5rem;
  }
  .blog-resource-btn {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.38rem 0.9rem;
    border-radius: 8px;
    font-size: 0.82rem;
    font-weight: 700;
    text-decoration: none !important;
    cursor: pointer;
    border: 1px solid transparent;
    transition: box-shadow 0.15s, transform 0.1s;
    line-height: 1.4;
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  }
  .blog-resource-btn:hover { box-shadow: 0 4px 12px rgba(0,0,0,.14); transform: translateY(-1px); }
  .blog-resource-btn--notebook { background: #fff3e0; color: #e65100 !important; border-color: #ffcc80; }
  .blog-resource-btn--pdf      { background: #fce4ec; color: #c62828 !important; border-color: #f48fb1; }
  .blog-resource-btn--print    { background: #e8f5e9; color: #2e7d32 !important; border-color: #a5d6a7; }
  .blog-resource-btn--save     { background: #e8eaf6; color: #283593 !important; border-color: #9fa8da; }
  .blog-resource-btn--python   { background: #fef9c3; color: #713f12 !important; border-color: #fde68a; }
  .blog-resource-btn--ipynb    { background: #fff7ed; color: #92400e !important; border-color: #fed7aa; }

  /* ----- Related posts grid ----- */
  .bpf-related { margin-top: 2rem; padding-top: 1.5rem; border-top: 1px solid #e2e8f0; }
  .bpf-related-title {
    font-size: 0.78rem;
    font-weight: 800;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: #6b7280;
    margin: 0 0 1rem;
  }
  .bpf-related-grid {
    display: grid;
    grid-template-columns: repeat(3, minmax(0,1fr));
    gap: 1rem;
  }
  @media (max-width: 860px) { .bpf-related-grid { grid-template-columns: repeat(2, minmax(0,1fr)); } }
  @media (max-width: 540px) { .bpf-related-grid { grid-template-columns: 1fr; } }
  .bpf-card {
    background: #f4f7fb;
    border: 1px solid #cfd8dc;
    border-radius: 12px;
    overflow: hidden;
    display: flex;
    flex-direction: column;
    box-shadow: 0 2px 8px rgba(0,0,0,.05);
    transition: box-shadow .15s, transform .12s;
    text-decoration: none !important;
  }
  .bpf-card:hover { box-shadow: 0 6px 20px rgba(0,0,0,.1); transform: translateY(-2px); }
  .bpf-card-thumb {
    width: 100%;
    aspect-ratio: 16/9;
    object-fit: cover;
    display: block;
    background: #e8edf5;
  }
  .bpf-card-body { padding: .75rem .9rem .9rem; display: flex; flex-direction: column; flex: 1; }
  .bpf-card-meta {
    display: flex; align-items: center; gap: .45rem;
    font-size: .78rem; color: #6b7280; margin-bottom: .35rem; flex-wrap: wrap;
  }
  .bpf-card-cat {
    background: #e0ecff; color: #0f3d8a;
    font-weight: 700; font-size: .72rem;
    padding: .12rem .45rem; border-radius: 999px;
  }
  .bpf-card-title {
    font-size: .95rem; font-weight: 800; color: #1565c0;
    line-height: 1.3; margin: 0 0 .45rem;
  }
  .bpf-card-title a { color: inherit; text-decoration: none; }
  .bpf-card-title a:hover { text-decoration: underline; }
  .bpf-card-tags { display: flex; flex-wrap: wrap; gap: .25rem; margin-bottom: .65rem; }
  .bpf-tag {
    background: #f1f5f9; border: 1px solid #cbd5e1;
    color: #6b7280; font-size: .72rem;
    padding: .1rem .4rem; border-radius: 999px;
  }
  .bpf-btn {
    display: inline-block;
    background: #1976d2; color: #fff !important;
    text-decoration: none !important;
    padding: .4rem .85rem; border-radius: 10px;
    font-weight: 800; font-size: .84rem; text-align: center;
    transition: background .15s, box-shadow .15s;
    align-self: flex-start; margin-top: auto;
  }
  .bpf-btn:hover { background: #0f60b6; box-shadow: 0 4px 12px rgba(0,0,0,.12); }

  @media (max-width: 560px) {
    .blog-author-card { flex-direction: column; align-items: center; text-align: center; }
    .blog-author-avatar { width: 52px; height: 68px; }
    .blog-cta { text-align: center; justify-content: center; }
    .blog-resources { justify-content: center; }
  }
</style>

<div class="blog-post-footer">

  <!-- 1. CTA -->
  <div class="blog-cta">
    <div class="blog-cta-text">
      <h3>Continue learning Artificial Intelligence</h3>
      <p>Explore practical courses on AI, Machine Learning, Deep Learning, Python, R and applied data science.</p>
    </div>
    <a class="blog-cta-btn" href="https://www.manuelcastillo.eu/udemy/" target="_blank" rel="noopener noreferrer">View AI Courses &rarr;</a>
  </div>

  <!-- 2. Author card -->
  <div class="blog-author-card">
    <img class="blog-author-avatar" src="/images/profile.jpg" alt="Manuel Castillo-Cara" onerror="this.style.display='none'" />
    <div class="blog-author-info">
      <p class="blog-author-name">Manuel Castillo-Cara, PhD</p>
      <p class="blog-author-spec">TINTOlib Python Library Developer</p>
      <p class="blog-author-role">Researcher &amp; Professor</p>
      <p class="blog-author-dept">Department of Artificial Intelligence</p>
      <p class="blog-author-inst">Universidad Nacional de Educación a Distancia (UNED)</p>
      <p class="blog-author-inst">Almerimar (Almería), Spain</p>
    </div>
  </div>

  <!-- 3. License -->
  <p class="blog-license">
    &copy; Manuel Castillo-Cara, PhD. Content licensed under
    <a href="https://creativecommons.org/licenses/by-nc/4.0/" target="_blank" rel="noopener noreferrer">CC BY-NC 4.0</a>
    unless otherwise stated.
  </p>

  <!-- 4. Export / resource buttons -->
  <div class="blog-resources">
    
    
    <button type="button" class="blog-resource-btn blog-resource-btn--print" onclick="window.print()">
      🖨️ Print / Save as PDF
    </button>
    <button type="button" class="blog-resource-btn blog-resource-btn--save" id="btn-save-html">
      💾 Save as HTML
    </button>
    <button type="button" class="blog-resource-btn blog-resource-btn--python" id="btn-download-py">
      🐍 Download Python
    </button>
    <button type="button" class="blog-resource-btn blog-resource-btn--ipynb" id="btn-download-ipynb">
      📓 Notebook (.ipynb)
    </button>
  </div>

  <!-- 5. Related posts -->
  
  
    <div class="bpf-related">
      <p class="bpf-related-title">You may also enjoy</p>
      <div class="bpf-related-grid">
        
          
          
          <article class="bpf-card">
            
              <img class="bpf-card-thumb" src="/images/Blog/2026-06-02-04-xai-hynn.png" alt="Improving Deep Learning by Exploiting Synthetic Images — Part II: From Synthetic Images to Hybrid Neural Networks" loading="lazy" />
            
            <div class="bpf-card-body">
              <div class="bpf-card-meta">
                <time datetime="2026-06-03T00:00:00+02:00">03 Jun 2026</time>
                <span class="bpf-card-cat">TINTOlib</span>
              </div>
              <h3 class="bpf-card-title">
                <a href="/blog/2026/06/04-from-synthetic-images-to-hybrid-neural-networks/">Improving Deep Learning by Exploiting Synthetic Images — Part II: From Synthetic Images to Hybrid Neural Networks</a>
              </h3>
              
                <div class="bpf-card-tags">
                  
                    <span class="bpf-tag">TINTOlib</span>
                  
                    <span class="bpf-tag">Synthetic Images</span>
                  
                    <span class="bpf-tag">Tabular-to-Image</span>
                  
                    <span class="bpf-tag">Hybrid Neural Networks</span>
                  
                </div>
              
              <a class="bpf-btn" href="/blog/2026/06/04-from-synthetic-images-to-hybrid-neural-networks/">Read article →</a>
            </div>
          </article>
        
          
          
          <article class="bpf-card">
            
              <img class="bpf-card-thumb" src="/images/Blog/2026-05-28-02-what-is-it-tintolib-synthetic-images.png" alt="What is TINTOlib and why should we transform tabular data into synthetic images?" loading="lazy" />
            
            <div class="bpf-card-body">
              <div class="bpf-card-meta">
                <time datetime="2026-05-28T00:00:00+02:00">28 May 2026</time>
                <span class="bpf-card-cat">TINTOlib</span>
              </div>
              <h3 class="bpf-card-title">
                <a href="/blog/2026/05/02-what-is-it-tintolib-synthetic-images/">What is TINTOlib and why should we transform tabular data into synthetic images?</a>
              </h3>
              
                <div class="bpf-card-tags">
                  
                    <span class="bpf-tag">TINTOlib</span>
                  
                    <span class="bpf-tag">Tabular Data</span>
                  
                    <span class="bpf-tag">Synthetic Images</span>
                  
                    <span class="bpf-tag">Deep Learning</span>
                  
                </div>
              
              <a class="bpf-btn" href="/blog/2026/05/02-what-is-it-tintolib-synthetic-images/">Read article →</a>
            </div>
          </article>
        
          
          
          <article class="bpf-card">
            
              <img class="bpf-card-thumb" src="/images/Blog/2026-05-27-01-introduction-to-tintolib-tabular-to-image.png" alt="Introduction to TINTOlib: Unlocking the Power of Vision Architectures for Tabular Data" loading="lazy" />
            
            <div class="bpf-card-body">
              <div class="bpf-card-meta">
                <time datetime="2026-05-27T00:00:00+02:00">27 May 2026</time>
                <span class="bpf-card-cat">TINTOlib</span>
              </div>
              <h3 class="bpf-card-title">
                <a href="/blog/2026/05/01-introduction-to-tintolib-tabular-to-image/">Introduction to TINTOlib: Unlocking the Power of Vision Architectures for Tabular Data</a>
              </h3>
              
                <div class="bpf-card-tags">
                  
                    <span class="bpf-tag">TINTOlib</span>
                  
                    <span class="bpf-tag">Tabular-to-Image</span>
                  
                    <span class="bpf-tag">Synthetic Images</span>
                  
                    <span class="bpf-tag">Deep Learning</span>
                  
                </div>
              
              <a class="bpf-btn" href="/blog/2026/05/01-introduction-to-tintolib-tabular-to-image/">Read article →</a>
            </div>
          </article>
        
      </div>
    </div>
  

</div>

<!-- ===== Copy-code + Export logic ===== -->
<script>
(function () {

  /* ---------- helpers ---------- */
  function slugify(str) {
    return (str || 'blog-post')
      .replace(/[^a-z0-9\-_]/gi, '-')
      .replace(/-+/g, '-')
      .toLowerCase()
      .substring(0, 60)
      .replace(/^-+|-+$/g, '');
  }

  function download(content, filename, mime) {
    var blob = new Blob([content], { type: mime });
    var url  = URL.createObjectURL(blob);
    var a    = document.createElement('a');
    a.href = url;
    a.download = filename;
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
    setTimeout(function () { URL.revokeObjectURL(url); }, 5000);
  }

  function getCodeBlocks() {
    var nodes = document.querySelectorAll('.page__content pre code');
    var blocks = [];
    nodes.forEach(function (node) {
      var text = node.textContent.trimEnd();
      if (text) blocks.push(text);
    });
    return blocks;
  }

  /* ---------- Copy-code buttons ---------- */
  var pres = document.querySelectorAll('.page__content pre');
  pres.forEach(function (pre) {
    var code = pre.querySelector('code');
    var getText = function () {
      if (code) return code.textContent;
      var clone = pre.cloneNode(true);
      clone.querySelectorAll('.copy-code-btn').forEach(function (b) { b.parentNode.removeChild(b); });
      return clone.textContent;
    };
    var btn = document.createElement('button');
    btn.type = 'button';
    btn.className = 'copy-code-btn';
    btn.textContent = 'Copy';
    btn.setAttribute('aria-label', 'Copy code to clipboard');
    pre.appendChild(btn);
    btn.addEventListener('click', function () {
      var text = getText();
      if (navigator.clipboard && navigator.clipboard.writeText) {
        navigator.clipboard.writeText(text).then(function () { flash(btn); });
      } else {
        var ta = document.createElement('textarea');
        ta.value = text;
        ta.style.cssText = 'position:fixed;opacity:0;top:0;left:0';
        document.body.appendChild(ta);
        ta.focus(); ta.select();
        try { document.execCommand('copy'); } catch (e) {}
        document.body.removeChild(ta);
        flash(btn);
      }
    });
  });
  function flash(btn) {
    btn.textContent = 'Copied \u2713';
    btn.classList.add('copied');
    setTimeout(function () { btn.textContent = 'Copy'; btn.classList.remove('copied'); }, 1600);
  }

  /* ---------- Save as HTML ---------- */
  var saveHtmlBtn = document.getElementById('btn-save-html');
  if (saveHtmlBtn) {
    saveHtmlBtn.addEventListener('click', function () {
      var clone = document.documentElement.cloneNode(true);
      var origin = window.location.origin;
      clone.querySelectorAll('img[src]').forEach(function (img) {
        var src = img.getAttribute('src');
        if (src && src.charAt(0) === '/') img.setAttribute('src', origin + src);
      });
      clone.querySelectorAll('link[href]').forEach(function (el) {
        var href = el.getAttribute('href');
        if (href && href.charAt(0) === '/') el.setAttribute('href', origin + href);
      });
      clone.querySelectorAll('script[src]').forEach(function (el) {
        var src = el.getAttribute('src');
        if (src && src.charAt(0) === '/') el.setAttribute('src', origin + src);
      });
      var html = '<!doctype html>\n' + clone.outerHTML;
      download(html, slugify(document.title) + '.html', 'text/html;charset=utf-8');
    });
  }

  /* ---------- Download Python ---------- */
  var pyBtn = document.getElementById('btn-download-py');
  if (pyBtn) {
    pyBtn.addEventListener('click', function () {
      var blocks = getCodeBlocks();
      var lines;
      if (blocks.length === 0) {
        lines = ['# No code blocks found in this post.\n'];
      } else {
        lines = [];
        blocks.forEach(function (block, i) {
          lines.push('# ---- Code block ' + (i + 1) + ' ----\n');
          lines.push(block + '\n\n');
        });
      }
      download(lines.join(''), slugify(document.title) + '.py', 'text/x-python;charset=utf-8');
    });
  }

  /* ---------- Download Notebook (.ipynb) — full article walker ---------- */
  var ipynbBtn = document.getElementById('btn-download-ipynb');
  if (ipynbBtn) {
    ipynbBtn.addEventListener('click', function () {

      function mdLines(text) {
        /* Split into source array as Jupyter expects (lines ending with \n except last) */
        var lines = text.split('\n');
        return lines.map(function (l, i) { return i < lines.length - 1 ? l + '\n' : l; });
      }

      function mkdCell(text) {
        var t = (text || '').trim();
        if (!t) return null;
        return { cell_type: 'markdown', metadata: {}, source: mdLines(t) };
      }

      function codeCell(text) {
        var t = (text || '').trimEnd();
        if (!t) return null;
        return { cell_type: 'code', execution_count: null, metadata: {}, outputs: [], source: mdLines(t) };
      }

      function nodeToMd(el) {
        var tag = el.tagName ? el.tagName.toLowerCase() : '';
        /* headings */
        if (/^h[1-6]$/.test(tag)) {
          var level = parseInt(tag.slice(1), 10);
          return Array(level + 1).join('#') + ' ' + el.textContent.trim();
        }
        /* paragraph */
        if (tag === 'p') return el.textContent.trim();
        /* blockquote */
        if (tag === 'blockquote') {
          return el.textContent.trim().split('\n').map(function (l) { return '> ' + l; }).join('\n');
        }
        /* lists */
        if (tag === 'ul' || tag === 'ol') {
          var items = [];
          el.querySelectorAll('li').forEach(function (li, i) {
            items.push((tag === 'ol' ? (i + 1) + '. ' : '- ') + li.textContent.trim());
          });
          return items.join('\n');
        }
        /* figure / img */
        if (tag === 'figure') {
          var img = el.querySelector('img');
          if (img) {
            var src = img.getAttribute('src') || '';
            if (src.charAt(0) === '/') src = window.location.origin + src;
            var alt = img.getAttribute('alt') || '';
            var cap = el.querySelector('figcaption');
            return '!['+ alt +']('+ src +')' + (cap ? '\n*' + cap.textContent.trim() + '*' : '');
          }
        }
        if (tag === 'img') {
          var src2 = el.getAttribute('src') || '';
          if (src2.charAt(0) === '/') src2 = window.location.origin + src2;
          return '![' + (el.getAttribute('alt') || '') + '](' + src2 + ')';
        }
        return '';
      }

      function getArticleCellsForNotebook() {
        var container = document.querySelector('.page__content');
        if (!container) return [];

        var clone = container.cloneNode(true);
        /* Remove footer, scripts, styles, copy buttons */
        ['script','style','.blog-post-footer','.copy-code-btn'].forEach(function (sel) {
          clone.querySelectorAll(sel).forEach(function (n) { n.parentNode.removeChild(n); });
        });

        var cells = [];
        var INLINE_TAGS = /^(h[1-6]|p|ul|ol|blockquote|figure|img)$/;

        function walk(node) {
          if (node.nodeType !== 1) return; /* element nodes only */
          var tag = node.tagName.toLowerCase();

          /* code block */
          if (tag === 'pre') {
            var code = node.querySelector('code');
            var text = (code ? code : node).textContent.trimEnd();
            var cell = codeCell(text);
            if (cell) cells.push(cell);
            return;
          }

          /* inline-mappable tags */
          if (INLINE_TAGS.test(tag)) {
            var md = nodeToMd(node);
            var cell2 = mkdCell(md);
            if (cell2) cells.push(cell2);
            return;
          }

          /* containers: div, section, article — recurse children */
          Array.prototype.forEach.call(node.childNodes, function (child) {
            walk(child);
          });
        }

        Array.prototype.forEach.call(clone.childNodes, function (child) {
          walk(child);
        });

        return cells;
      }

      var cells = [];
      /* Header cells */
      cells.push(mkdCell('# ' + (document.title || 'Blog Post')));
      var pageUrl = window.location.href;
      cells.push(mkdCell('**Source:** [' + pageUrl + '](' + pageUrl + ')'));

      /* Article body cells */
      var bodyCells = getArticleCellsForNotebook();
      if (bodyCells.length === 0) {
        cells.push(mkdCell('*No content extracted from this post.*'));
      } else {
        cells = cells.concat(bodyCells);
      }

      var nb = {
        nbformat: 4,
        nbformat_minor: 5,
        metadata: {
          kernelspec: { display_name: 'Python 3', language: 'python', name: 'python3' },
          language_info: { name: 'python', version: '3.x' }
        },
        cells: cells
      };

      download(JSON.stringify(nb, null, 2), slugify(document.title) + '.ipynb', 'application/json;charset=utf-8');
    });
  }

}());
</script>]]></content><author><name>Ph.D. Manuel Castillo-Cara</name><email>manwest.c@gmail.com</email></author><category term="TINTOlib" /><category term="TINTOlib" /><category term="Synthetic Images" /><category term="Tabular-to-Image" /><category term="Tabular Data" /><category term="Deep Learning" /><category term="Computer Vision" /><category term="Spatial Encoding" /><category term="Machine Learning" /><category term="Data Science" /><summary type="html"><![CDATA[Part I of a theoretical and technical series on why deep learning still struggles with tabular data, and why synthetic image representations provide a promising bridge between structured data and computer vision architectures.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.manuelcastillo.eu/images/Blog/2026-06-02-03-improving-deep-learning-exploiting-synthetic-images.png" /><media:content medium="image" url="https://www.manuelcastillo.eu/images/Blog/2026-06-02-03-improving-deep-learning-exploiting-synthetic-images.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">What is TINTOlib and why should we transform tabular data into synthetic images?</title><link href="https://www.manuelcastillo.eu/blog/2026/05/02-what-is-it-tintolib-synthetic-images/" rel="alternate" type="text/html" title="What is TINTOlib and why should we transform tabular data into synthetic images?" /><published>2026-05-28T00:00:00+02:00</published><updated>2026-05-28T00:00:00+02:00</updated><id>https://www.manuelcastillo.eu/blog/2026/05/02-what-is-it-tintolib-synthetic-images</id><content type="html" xml:base="https://www.manuelcastillo.eu/blog/2026/05/02-what-is-it-tintolib-synthetic-images/"><![CDATA[<link rel="canonical" href="https://www.manuelcastillo.eu/blog/2026/05/02-what-is-it-tintolib-synthetic-images/" />

<meta name="robots" content="index,follow,max-image-preview:large" />

<meta name="description" content="An introduction to TINTOlib: why tabular data requires spatial encoding, how to generate synthetic images avoiding data leakage, and a complete end-to-end CNN pipeline in PyTorch." />

<meta property="og:type" content="article" />

<meta property="og:title" content="What is TINTOlib and why should we transform tabular data into synthetic images?" />

<meta property="og:description" content="An introduction to TINTOlib: why tabular data requires spatial encoding, how to generate synthetic images avoiding data leakage, and a complete end-to-end CNN pipeline in PyTorch." />

<meta property="og:url" content="https://www.manuelcastillo.eu/blog/2026/05/02-what-is-it-tintolib-synthetic-images/" />

<meta property="og:image" content="https://www.manuelcastillo.eu/images/Blog/2026-05-28-02-what-is-it-tintolib-synthetic-images.png" />

<meta property="article:published_time" content="2026-05-28T00:00:00+02:00" />

<meta property="article:modified_time" content="2026-05-28T00:00:00+02:00" />

<meta property="article:author" content="Manuel Castillo-Cara" />

<meta property="article:section" content="TINTOlib" />

<meta property="article:tag" content="TINTOlib" />

<meta property="article:tag" content="Tabular Data" />

<meta property="article:tag" content="Synthetic Images" />

<meta property="article:tag" content="Deep Learning" />

<meta property="article:tag" content="CNN" />

<meta property="article:tag" content="PyTorch" />

<meta property="article:tag" content="Python" />

<meta property="article:tag" content="Blurring" />

<meta property="article:tag" content="Tabular-to-Image" />

<meta name="twitter:card" content="summary_large_image" />

<meta name="twitter:title" content="What is TINTOlib and why should we transform tabular data into synthetic images?" />

<meta name="twitter:description" content="An introduction to TINTOlib: why tabular data requires spatial encoding, how to generate synthetic images avoiding data leakage, and a complete end-to-end CNN pipeline in PyTorch." />

<meta name="twitter:image" content="https://www.manuelcastillo.eu/images/Blog/2026-05-28-02-what-is-it-tintolib-synthetic-images.png" />

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "BlogPosting",
  "headline": "What is TINTOlib and why should we transform tabular data into synthetic images?",
  "description": "An introduction to TINTOlib: why tabular data requires spatial encoding, how to generate synthetic images avoiding data leakage, and a complete end-to-end CNN pipeline in PyTorch.",
  "image": "https://www.manuelcastillo.eu/images/Blog/2026-05-28-02-what-is-it-tintolib-synthetic-images.png",
  "author": {
    "@type": "Person",
    "name": "Manuel Castillo-Cara",
    "url": "https://www.manuelcastillo.eu/"
  },
  "publisher": {
    "@type": "Person",
    "name": "Manuel Castillo-Cara",
    "url": "https://www.manuelcastillo.eu/"
  },
  "datePublished": "2026-05-28T00:00:00+02:00",
  "dateModified": "2026-05-28T00:00:00+02:00",
  "mainEntityOfPage": {
    "@type": "WebPage",
    "@id": "https://www.manuelcastillo.eu/blog/2026/05/02-what-is-it-tintolib-synthetic-images/"
  },
  "articleSection": "TINTOlib",
  "keywords": "TINTOlib, Tabular Data, Synthetic Images, Deep Learning, CNN, PyTorch, Python, Blurring, Tabular-to-Image"
}
</script>

<div style="background: linear-gradient(135deg, #1a237e 0%, #4a148c 50%, #311b92 100%); border-radius: 12px; padding: 2.5rem 2rem; margin: 1.5rem 0 2.5rem; display: flex; flex-wrap: wrap; align-items: center; gap: 2rem; color: #fff;">
  <div style="flex: 1 1 280px; min-width: 0;">
    <p style="margin: 0 0 0.4rem; font-size: 0.78rem; letter-spacing: 0.12em; text-transform: uppercase; color: #b39ddb; font-weight: 600;">TINTOlib · Synthetic Images · Tabular-to-Image</p>
    <h1 style="margin: 0 0 0.75rem; font-size: clamp(1.5rem, 4vw, 2.1rem); font-weight: 800; line-height: 1.2; color: #fff;">What is TINTOlib?</h1>
    <p style="margin: 0 0 1rem; font-size: 0.97rem; color: #e1d5f5; line-height: 1.55;">Why does tabular data need spatial encoding? How are synthetic images generated — and what does blurring do? A complete introduction with a first end-to-end CNN pipeline in PyTorch.</p>
    <div style="display: flex; flex-wrap: wrap; gap: 0.5rem;">
      <span style="background: rgba(255,255,255,0.15); border-radius: 20px; padding: 0.25rem 0.75rem; font-size: 0.78rem; color: #e8d5ff;">TINTOlib</span>
      <span style="background: rgba(255,255,255,0.15); border-radius: 20px; padding: 0.25rem 0.75rem; font-size: 0.78rem; color: #e8d5ff;">Synthetic Images</span>
      <span style="background: rgba(255,255,255,0.15); border-radius: 20px; padding: 0.25rem 0.75rem; font-size: 0.78rem; color: #e8d5ff;">Blurring</span>
      <span style="background: rgba(255,255,255,0.15); border-radius: 20px; padding: 0.25rem 0.75rem; font-size: 0.78rem; color: #e8d5ff;">Tabular-to-Image</span>
      <span style="background: rgba(255,255,255,0.15); border-radius: 20px; padding: 0.25rem 0.75rem; font-size: 0.78rem; color: #e8d5ff;">Deep Learning</span>
    </div>
  </div>
  <div style="flex: 0 0 auto; max-width: 260px; width: 100%;">
    <img src="/images/Blog/2026-05-28-02-what-is-it-tintolib-synthetic-images.png" alt="TINTOlib synthetic images from tabular data" style="width: 100%; border-radius: 10px; box-shadow: 0 8px 32px rgba(0,0,0,0.45); display: block;" />
  </div>
</div>

<hr />

<blockquote>
  <blockquote>
    <ul>
      <li><strong>Author:</strong> Manuel Castillo-Cara, PhD</li>
      <li><strong>Affiliation:</strong> Dpt. of Artificial Intelligence, Universidad Nacional de Educación a Distancia (UNED), Spain</li>
      <li><strong>Role:</strong> Researcher, Professor, and TINTOlib Python Library Developer</li>
      <li><strong>License:</strong> <a href="https://creativecommons.org/licenses/by-nc/4.0/">CC BY-NC 4.0</a> unless otherwise stated.</li>
    </ul>
  </blockquote>
</blockquote>

<hr />

<!-- ======== TINTOlib: Overview Videos (ES/EN) ======== -->
<section id="tintolib-overview-videos" style="margin: 1.75rem 0 2rem;">
  <h2 style="margin: 0 0 0.5rem;">TINTOlib overview videos</h2>
    <p style="margin: 0 0 1rem; line-height: 1.6;">
      The following short videos provide a bilingual introduction to <strong>TINTOlib</strong>, explaining how tabular data can be transformed into synthetic images and processed with computer vision architectures such as CNNs, Vision Transformers and hybrid neural networks.
    </p>
      <div class="tintolib-video-toggle" role="tablist" aria-label="Select video language">
    <button type="button" class="tintolib-video-btn active" data-target="#tintolib-video-es" role="tab" aria-selected="true">
      Español
    </button>
    <button type="button" class="tintolib-video-btn" data-target="#tintolib-video-en" role="tab" aria-selected="false">
      English
    </button>
  </div>
    <div class="tintolib-video-wrap">
    <video id="tintolib-video-es" class="tintolib-video-panel active" controls="" controlsList="nodownload" preload="metadata" playsinline="" aria-label="TINTOlib overview video in Spanish">
      <source src="/video/TINTOlib-video-Es.mp4" type="video/mp4" />
      Your browser does not support the video tag.
      <a href="/video/TINTOlib-video-Es.mp4">Open the Spanish video</a>.
    </video>
    <video id="tintolib-video-en" class="tintolib-video-panel" controls="" controlsList="nodownload" preload="metadata" playsinline="" aria-label="TINTOlib overview video in English">
  <source src="/video/TINTOlib-video-En.mp4" type="video/mp4" />
  Your browser does not support the video tag.
  <a href="/video/TINTOlib-video-En.mp4">Open the English video</a>.
  </video>
  </div>
</section>
<style>
  #tintolib-overview-videos .tintolib-video-toggle {
    display: flex;
    gap: 0.5rem;
    flex-wrap: wrap;
    margin: 0.75rem 0 1rem;
  }

  #tintolib-overview-videos .tintolib-video-btn {
    background: #eef2ff;
    border: 1px solid #dbe3ff;
    color: #1f2937;
    font-weight: 800;
    border-radius: 999px;
    padding: 0.35rem 0.8rem;
    cursor: pointer;
  }

  #tintolib-overview-videos .tintolib-video-btn.active {
    background: #2563eb;
    color: #fff;
    border-color: #2563eb;
  }

  #tintolib-overview-videos .tintolib-video-wrap {
    position: relative;
    width: 100%;
    max-width: 900px;
    margin: 0 auto;
  }

  #tintolib-overview-videos .tintolib-video-panel {
    display: none;
    width: 100%;
    aspect-ratio: 16 / 9;
    object-fit: cover;
    border: 1px solid #d0d7de;
    border-radius: 12px;
    box-shadow: 0 2px 8px rgba(0,0,0,.08);
    background: #000;
  }

  #tintolib-overview-videos .tintolib-video-panel.active {
    display: block;
  }
</style>

<script>
  document.addEventListener('DOMContentLoaded', function () {
    const container = document.getElementById('tintolib-overview-videos');
    if (!container) return;

    const buttons = container.querySelectorAll('.tintolib-video-btn');
    const panels = container.querySelectorAll('.tintolib-video-panel');

    buttons.forEach(function (button) {
      button.addEventListener('click', function () {
        const target = button.getAttribute('data-target');

        buttons.forEach(function (btn) {
          btn.classList.remove('active');
          btn.setAttribute('aria-selected', 'false');
        });

        button.classList.add('active');
        button.setAttribute('aria-selected', 'true');

        panels.forEach(function (panel) {
          if ('#' + panel.id === target) {
            panel.classList.add('active');
          } else {
            if (panel.tagName.toLowerCase() === 'video') {
              try { panel.pause(); } catch (e) {}
            }
            panel.classList.remove('active');
          }
        });
      });
    });
  });
</script>

<p>In many applied machine learning problems, there is a recurrent practical distinction: for images, text, or audio, we often use Deep Learning; for tabular data, tree-based models and ensembles such as Random Forest, XGBoost, LightGBM, or CatBoost remain extremely competitive.</p>

<p>This distinction is not accidental. Convolutional neural networks were designed to exploit spatial structure: local neighborhoods, edges, textures, shapes, and patterns that repeat across an image. A tabular dataset, however, does not naturally have this type of geometry. The fact that one variable appears in column 2 and another in column 3 does not imply that both variables are semantically or statistically close.</p>

<p>This leads to a relevant research question:</p>

<blockquote>
  <p>Can we transform each tabular instance into a synthetic image so that computer vision architectures can exploit relationships between variables?</p>
</blockquote>

<p>This question motivates the field of tabular-to-image transformation, also referred to as <em>tabular2image</em>, <em>spatial encoding for tabular data</em>, or synthetic image generation from tabular data. In this post, we introduce the main idea, explain where TINTOlib fits, and build a simple convolutional neural network in PyTorch to classify synthetic images generated from tabular data.</p>

<h2 id="the-problem-tabular-data-has-no-natural-spatial-locality">The problem: tabular data has no natural spatial locality</h2>

<p>A digital image can be represented as a matrix of pixels. In a natural image, nearby pixels usually belong to the same visual region: an edge, a texture, a shadow, or an object. This property allows CNNs to apply local filters and learn reusable spatial patterns.</p>

<p>A tabular instance is different. It is usually represented as a vector:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>x = [age, blood_pressure, cholesterol, glucose, ...]
</code></pre></div></div>

<p>The order of the columns is often arbitrary. If two variables are swapped, the semantic meaning of the sample does not necessarily change. For a CNN, however, that change would modify the input geometry. This is one of the reasons why applying convolutions directly to tabular vectors is usually problematic: the model would assume a local neighborhood structure that is not explicitly present.</p>

<p>Tabular-to-image transformation addresses this limitation by constructing an artificial spatial layout. The idea is to place the original variables on a 2D grid so that related variables are positioned close to each other. Then, for each row in the dataset, the values of those variables are projected onto the grid, producing a synthetic image.</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Tabular data                    Synthetic image

+--------+--------+--------+       +----+----+----+----+
| feat_1 | feat_2 | feat_3 |  --&gt;  |    | f2 |    | f8 |
| feat_4 | feat_5 | feat_6 |       +----+----+----+----+
|  ...   |  ...   |  ...   |       | f1 |    | f5 |    |
+--------+--------+--------+       +----+----+----+----+
                                   |    | f3 | f4 |    |
                                   +----+----+----+----+
</code></pre></div></div>

<p><em>(Conceptual schematic of the transformation from tabular data to a synthetic 2D image. Each feature is mapped to a grid position such that related variables are placed close to each other.)</em></p>

<p>This image is not a natural image. It does not represent a real-world scene. It is a spatial representation of a tabular vector. The important point is not whether the image is visually meaningful to a human observer, but whether the representation encodes useful relationships for a vision-based model.</p>

<p><img src="/images/Blog/2026-05-28-02-what-is-it-tintolib-synthetic-images.png" alt="Tabular Data into Synthetic Images Methodology" />
<em>(Figure 1. Conceptual workflow of TINTOlib for transforming tabular data into synthetic images. The original tabular features are spatially encoded into a two-dimensional grid, producing image-like representations that preserve feature relationships and can be processed by computer vision models such as convolutional neural networks.)</em></p>

<h2 id="why-transform-tabular-data-into-images">Why transform tabular data into images?</h2>

<p>The transformation of tabular data into synthetic images opens at least three relevant possibilities.</p>

<p>First, it allows us to use computer vision architectures on structured data. Once each tabular instance has been transformed into an image, we can use CNNs, Vision Transformers, pretrained backbones, or hybrid neural architectures.</p>

<p>Second, it introduces a spatial inductive bias. If the transformation method places related variables close to each other, convolutional filters can learn local interactions between features.</p>

<p>Third, it enables visual explainability techniques. Methods such as saliency maps, activation maps, or Grad-CAM can be adapted to study which regions of the synthetic image — and therefore which original variables — contributed most to the prediction.</p>

<p>This does not mean that tabular-to-image transformation will always outperform classical machine learning models. Tree-based models remain strong baselines for tabular data. The value of this approach depends on the dataset, the number of samples, the number of features, the transformation method, the neural architecture, and the evaluation protocol. Its main interest is that it provides an alternative representation that can be combined with modern vision-based deep learning methods.</p>

<h2 id="what-is-tintolib">What is TINTOlib?</h2>

<p>TINTOlib is a Python library for transforming tabular data into synthetic images in a systematic, modular, and reproducible way. Its main goal is to provide a unified interface for different state-of-the-art tabular-to-image transformation methods that were previously scattered across independent implementations, often with heterogeneous APIs.</p>

<p>The library follows a Scikit-Learn-like design, including methods such as <code class="language-plaintext highlighter-rouge">fit</code>, <code class="language-plaintext highlighter-rouge">transform</code>, and <code class="language-plaintext highlighter-rouge">fit_transform</code>. This is particularly useful when designing machine learning pipelines, because it allows us to separate the learning of the spatial representation from the transformation of new data.</p>

<p>TINTOlib includes both parametric and non-parametric methods. Some examples are:</p>

<ul>
  <li><strong>TINTO</strong>, based on dimensionality reduction techniques such as PCA or t-SNE, with optional image blurring.</li>
  <li><strong>IGTD</strong>, which optimizes the feature arrangement to preserve similarity relationships.</li>
  <li><strong>REFINED</strong>, which relies on multidimensional scaling and optimization to preserve feature-neighborhood information.</li>
  <li><strong>DeepInsight</strong>, another projection-based method that arranges features in a 2D space.</li>
  <li><strong>BarGraph</strong>, <strong>DistanceMatrix</strong>, <strong>Combination</strong>, <strong>FeatureWrap</strong>, <strong>SuperTML</strong>, and <strong>BIE</strong>, among others.</li>
</ul>

<p>The documentation is available at <strong><a href="https://tintolib.readthedocs.io/">tintolib.readthedocs.io</a></strong>.<br />
The source code and examples are available at <strong><a href="https://github.com/oeg-upm/TINTOlib">github.com/oeg-upm/TINTOlib</a></strong>.</p>

<p>In practice, TINTOlib allows us to move from this representation:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>samples x features
</code></pre></div></div>

<p>to this one:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>samples x channels x height x width
</code></pre></div></div>

<p>which is the natural input format for computer vision models in PyTorch.</p>

<h2 id="avoiding-data-leakage-fit-only-on-the-training-data">Avoiding data leakage: fit only on the training data</h2>

<p>A critical point when using tabular-to-image methods is data leakage.</p>

<p>The spatial arrangement of the variables must be learned only from the training data. If we use the full dataset to learn the transformation — including validation or test samples — information from the evaluation split can indirectly influence the representation. This would lead to an optimistic and methodologically incorrect estimate of model performance.</p>

<p>Therefore, even though TINTOlib provides <code class="language-plaintext highlighter-rouge">fit_transform</code> for convenience, in a proper experimental pipeline we should use:</p>

<ol>
  <li><code class="language-plaintext highlighter-rouge">fit</code> on the training set only;</li>
  <li><code class="language-plaintext highlighter-rouge">transform</code> on the training set;</li>
  <li><code class="language-plaintext highlighter-rouge">transform</code> on the validation and test sets using the already-fitted transformation.</li>
</ol>

<p>Conceptually, the workflow should be:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Train tabular data  ---- fit ----&gt; learned spatial layout
Train tabular data  -- transform -&gt; train images
Validation data     -- transform -&gt; validation images
Test data           -- transform -&gt; test images
</code></pre></div></div>

<p>This is the same principle used with scalers, imputers, PCA, feature selectors, and other preprocessing methods in standard machine learning pipelines.</p>

<h2 id="first-practical-example-generating-synthetic-images-with-tintolib">First practical example: generating synthetic images with TINTOlib</h2>

<p>For this example, we consider a multiclass classification problem using the Wine dataset from Scikit-Learn. Each row represents a sample, the input columns are numerical features, and the target column identifies the class label.</p>

<p>Install TINTOlib with:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pip <span class="nb">install </span>TINTOlib
</code></pre></div></div>

<p>Then, we prepare the train, validation, and test splits before fitting the tabular-to-image transformation.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>

<span class="kn">import</span> <span class="nn">pandas</span> <span class="k">as</span> <span class="n">pd</span>
<span class="kn">from</span> <span class="nn">sklearn.datasets</span> <span class="kn">import</span> <span class="n">load_wine</span>
<span class="kn">from</span> <span class="nn">sklearn.model_selection</span> <span class="kn">import</span> <span class="n">train_test_split</span>
<span class="kn">from</span> <span class="nn">sklearn.preprocessing</span> <span class="kn">import</span> <span class="n">LabelEncoder</span>

<span class="kn">from</span> <span class="nn">TINTOlib.tinto</span> <span class="kn">import</span> <span class="n">TINTO</span>

<span class="c1"># Reproducibility
</span><span class="n">seed</span> <span class="o">=</span> <span class="mi">42</span>

<span class="c1"># Load multiclass tabular dataset
</span><span class="n">raw_data</span> <span class="o">=</span> <span class="n">load_wine</span><span class="p">()</span>

<span class="c1"># Build dataframe
</span><span class="n">df</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="n">DataFrame</span><span class="p">(</span><span class="n">raw_data</span><span class="p">.</span><span class="n">data</span><span class="p">,</span> <span class="n">columns</span><span class="o">=</span><span class="n">raw_data</span><span class="p">.</span><span class="n">feature_names</span><span class="p">)</span>
<span class="n">df</span><span class="p">[</span><span class="s">"target"</span><span class="p">]</span> <span class="o">=</span> <span class="n">raw_data</span><span class="p">.</span><span class="n">target</span>

<span class="c1"># Separate features and target
</span><span class="n">target_col</span> <span class="o">=</span> <span class="s">"target"</span>
<span class="n">X</span> <span class="o">=</span> <span class="n">df</span><span class="p">.</span><span class="n">drop</span><span class="p">(</span><span class="n">columns</span><span class="o">=</span><span class="p">[</span><span class="n">target_col</span><span class="p">])</span>
<span class="n">y</span> <span class="o">=</span> <span class="n">df</span><span class="p">[</span><span class="n">target_col</span><span class="p">]</span>

<span class="c1"># Encode labels if needed
</span><span class="n">label_encoder</span> <span class="o">=</span> <span class="n">LabelEncoder</span><span class="p">()</span>
<span class="n">y_encoded</span> <span class="o">=</span> <span class="n">label_encoder</span><span class="p">.</span><span class="n">fit_transform</span><span class="p">(</span><span class="n">y</span><span class="p">)</span>

<span class="c1"># First split: train + temporary set
</span><span class="n">X_train</span><span class="p">,</span> <span class="n">X_tmp</span><span class="p">,</span> <span class="n">y_train</span><span class="p">,</span> <span class="n">y_tmp</span> <span class="o">=</span> <span class="n">train_test_split</span><span class="p">(</span>
    <span class="n">X</span><span class="p">,</span>
    <span class="n">y_encoded</span><span class="p">,</span>
    <span class="n">test_size</span><span class="o">=</span><span class="mf">0.30</span><span class="p">,</span>
    <span class="n">random_state</span><span class="o">=</span><span class="n">seed</span><span class="p">,</span>
    <span class="n">stratify</span><span class="o">=</span><span class="n">y_encoded</span>
<span class="p">)</span>

<span class="c1"># Second split: validation + test
</span><span class="n">X_val</span><span class="p">,</span> <span class="n">X_test</span><span class="p">,</span> <span class="n">y_val</span><span class="p">,</span> <span class="n">y_test</span> <span class="o">=</span> <span class="n">train_test_split</span><span class="p">(</span>
    <span class="n">X_tmp</span><span class="p">,</span>
    <span class="n">y_tmp</span><span class="p">,</span>
    <span class="n">test_size</span><span class="o">=</span><span class="mf">0.50</span><span class="p">,</span>
    <span class="n">random_state</span><span class="o">=</span><span class="n">seed</span><span class="p">,</span>
    <span class="n">stratify</span><span class="o">=</span><span class="n">y_tmp</span>
<span class="p">)</span>

<span class="c1"># Rebuild dataframes because TINTOlib expects the target column together with the features
</span><span class="n">train_df</span> <span class="o">=</span> <span class="n">X_train</span><span class="p">.</span><span class="n">copy</span><span class="p">()</span>
<span class="n">train_df</span><span class="p">[</span><span class="n">target_col</span><span class="p">]</span> <span class="o">=</span> <span class="n">y_train</span>

<span class="n">val_df</span> <span class="o">=</span> <span class="n">X_val</span><span class="p">.</span><span class="n">copy</span><span class="p">()</span>
<span class="n">val_df</span><span class="p">[</span><span class="n">target_col</span><span class="p">]</span> <span class="o">=</span> <span class="n">y_val</span>

<span class="n">test_df</span> <span class="o">=</span> <span class="n">X_test</span><span class="p">.</span><span class="n">copy</span><span class="p">()</span>
<span class="n">test_df</span><span class="p">[</span><span class="n">target_col</span><span class="p">]</span> <span class="o">=</span> <span class="n">y_test</span>
</code></pre></div></div>

<p>Now we instantiate the TINTO transformation. In this example we use the TINTO method, but the same structure can be used with other TINTOlib methods such as IGTD, REFINED, DeepInsight, BarGraph, DistanceMatrix, SuperTML, or BIE.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Output folders
</span><span class="n">output_root</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="s">"tinto_images"</span><span class="p">)</span>
<span class="n">train_folder</span> <span class="o">=</span> <span class="n">output_root</span> <span class="o">/</span> <span class="s">"train"</span>
<span class="n">val_folder</span> <span class="o">=</span> <span class="n">output_root</span> <span class="o">/</span> <span class="s">"val"</span>
<span class="n">test_folder</span> <span class="o">=</span> <span class="n">output_root</span> <span class="o">/</span> <span class="s">"test"</span>

<span class="c1"># TINTO model
# pixels defines the image size: pixels x pixels
</span><span class="n">image_model</span> <span class="o">=</span> <span class="n">TINTO</span><span class="p">(</span>
    <span class="n">problem</span><span class="o">=</span><span class="s">"supervised"</span><span class="p">,</span>
    <span class="n">algorithm</span><span class="o">=</span><span class="s">"PCA"</span><span class="p">,</span>
    <span class="n">pixels</span><span class="o">=</span><span class="mi">20</span><span class="p">,</span>
    <span class="n">blur</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span>
    <span class="n">random_seed</span><span class="o">=</span><span class="n">seed</span>
<span class="p">)</span>

<span class="c1"># Learn the spatial layout ONLY from the training data
</span><span class="n">image_model</span><span class="p">.</span><span class="n">fit</span><span class="p">(</span><span class="n">train_df</span><span class="p">)</span>

<span class="c1"># Transform each split using the same fitted representation
</span><span class="n">image_model</span><span class="p">.</span><span class="n">transform</span><span class="p">(</span><span class="n">train_df</span><span class="p">,</span> <span class="n">folder</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">train_folder</span><span class="p">))</span>
<span class="n">image_model</span><span class="p">.</span><span class="n">transform</span><span class="p">(</span><span class="n">val_df</span><span class="p">,</span> <span class="n">folder</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">val_folder</span><span class="p">))</span>
<span class="n">image_model</span><span class="p">.</span><span class="n">transform</span><span class="p">(</span><span class="n">test_df</span><span class="p">,</span> <span class="n">folder</span><span class="o">=</span><span class="nb">str</span><span class="p">(</span><span class="n">test_folder</span><span class="p">))</span>

<span class="k">print</span><span class="p">(</span><span class="s">"Synthetic images generated successfully."</span><span class="p">)</span>
</code></pre></div></div>

<p>This separation is essential. The validation and test sets are transformed using the spatial layout learned from the training data, but they do not participate in learning that layout.</p>

<div style="display: flex; gap: 0.75rem; justify-content: center; flex-wrap: wrap; margin: 1.5rem 0 0.5rem;">
  <img src="/images/Blog/synthetic_images/2026-05-28-02_TINTO1-class1.png" alt="Synthetic image generated by TINTOlib TINTO with blurring for class 1" style="width: 120px; height: 120px; object-fit: contain; border: 1px solid #e2e8f0; border-radius: 4px; background: #f8fafc;" />
  <img src="/images/Blog/synthetic_images/2026-05-28-02_TINTO1-class2.png" alt="Synthetic image generated by TINTOlib TINTO with blurring for class 2" style="width: 120px; height: 120px; object-fit: contain; border: 1px solid #e2e8f0; border-radius: 4px; background: #f8fafc;" />
  <img src="/images/Blog/synthetic_images/2026-05-28-02_TINTO1-class3.png" alt="Synthetic image generated by TINTOlib TINTO with blurring for class 3" style="width: 120px; height: 120px; object-fit: contain; border: 1px solid #e2e8f0; border-radius: 4px; background: #f8fafc;" />
</div>
<p><em>(Figure 2. Synthetic image samples generated with TINTOlib using the TINTO method with blurring, showing one representative instance from each class of the Wine multiclass dataset. Each image corresponds to a different class and encodes the original tabular feature values into a two-dimensional spatial representation, where local smoothing enhances spatial continuity while preserving class-dependent patterns that can be exploited by CNN-based and hybrid deep learning models.)</em></p>

<h3 id="can-we-use-another-method-instead-of-tinto">Can we use another method instead of TINTO?</h3>

<p>Yes. One of the main advantages of TINTOlib is that the same general workflow can be reused with different transformation methods. For example, depending on the method available in your installation, you could replace the import and model definition with another transformer:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Example: using another TINTOlib method instead of TINTO
# from TINTOlib.igtd import IGTD
# image_model = IGTD(problem="supervised", pixels=20)
</span>
<span class="c1"># from TINTOlib.refined import REFINED
# image_model = REFINED(problem="supervised", pixels=20)
</span></code></pre></div></div>

<p>The high-level logic remains the same:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">image_model</span><span class="p">.</span><span class="n">fit</span><span class="p">(</span><span class="n">train_df</span><span class="p">)</span>
<span class="n">image_model</span><span class="p">.</span><span class="n">transform</span><span class="p">(</span><span class="n">train_df</span><span class="p">,</span> <span class="n">folder</span><span class="o">=</span><span class="s">"..."</span><span class="p">)</span>
<span class="n">image_model</span><span class="p">.</span><span class="n">transform</span><span class="p">(</span><span class="n">val_df</span><span class="p">,</span> <span class="n">folder</span><span class="o">=</span><span class="s">"..."</span><span class="p">)</span>
<span class="n">image_model</span><span class="p">.</span><span class="n">transform</span><span class="p">(</span><span class="n">test_df</span><span class="p">,</span> <span class="n">folder</span><span class="o">=</span><span class="s">"..."</span><span class="p">)</span>
</code></pre></div></div>

<p>For an experimental paper or benchmark, this is particularly useful because it allows us to compare different spatial encodings while keeping the same downstream neural architecture.</p>

<h2 id="loading-the-generated-images-in-pytorch">Loading the generated images in PyTorch</h2>

<p>Once the images have been generated, we can load them in PyTorch. The exact folder structure may depend on the selected TINTOlib method and configuration. For a simple classification pipeline, it is convenient to organize the images using the structure expected by <code class="language-plaintext highlighter-rouge">torchvision.datasets.ImageFolder</code>:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>tinto_images/
├── train/
│   ├── class_0/
│   ├── class_1/
│   └── class_2/
├── val/
│   ├── class_0/
│   ├── class_1/
│   └── class_2/
└── test/
    ├── class_0/
    ├── class_1/
    └── class_2/
</code></pre></div></div>

<p>If your generated files are stored differently, you can either reorganize them into this structure or implement a custom PyTorch <code class="language-plaintext highlighter-rouge">Dataset</code>.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">torch</span>
<span class="kn">from</span> <span class="nn">torch.utils.data</span> <span class="kn">import</span> <span class="n">DataLoader</span>
<span class="kn">from</span> <span class="nn">torchvision</span> <span class="kn">import</span> <span class="n">datasets</span><span class="p">,</span> <span class="n">transforms</span>

<span class="n">batch_size</span> <span class="o">=</span> <span class="mi">32</span>
<span class="n">img_size</span> <span class="o">=</span> <span class="mi">20</span>

<span class="n">transform</span> <span class="o">=</span> <span class="n">transforms</span><span class="p">.</span><span class="n">Compose</span><span class="p">([</span>
    <span class="n">transforms</span><span class="p">.</span><span class="n">Grayscale</span><span class="p">(</span><span class="n">num_output_channels</span><span class="o">=</span><span class="mi">1</span><span class="p">),</span>
    <span class="n">transforms</span><span class="p">.</span><span class="n">Resize</span><span class="p">((</span><span class="n">img_size</span><span class="p">,</span> <span class="n">img_size</span><span class="p">)),</span>
    <span class="n">transforms</span><span class="p">.</span><span class="n">ToTensor</span><span class="p">(),</span>
<span class="p">])</span>

<span class="n">train_dataset</span> <span class="o">=</span> <span class="n">datasets</span><span class="p">.</span><span class="n">ImageFolder</span><span class="p">(</span><span class="n">root</span><span class="o">=</span><span class="n">train_folder</span><span class="p">,</span> <span class="n">transform</span><span class="o">=</span><span class="n">transform</span><span class="p">)</span>
<span class="n">val_dataset</span> <span class="o">=</span> <span class="n">datasets</span><span class="p">.</span><span class="n">ImageFolder</span><span class="p">(</span><span class="n">root</span><span class="o">=</span><span class="n">val_folder</span><span class="p">,</span> <span class="n">transform</span><span class="o">=</span><span class="n">transform</span><span class="p">)</span>
<span class="n">test_dataset</span> <span class="o">=</span> <span class="n">datasets</span><span class="p">.</span><span class="n">ImageFolder</span><span class="p">(</span><span class="n">root</span><span class="o">=</span><span class="n">test_folder</span><span class="p">,</span> <span class="n">transform</span><span class="o">=</span><span class="n">transform</span><span class="p">)</span>

<span class="n">num_classes</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">train_dataset</span><span class="p">.</span><span class="n">classes</span><span class="p">)</span>

<span class="n">train_loader</span> <span class="o">=</span> <span class="n">DataLoader</span><span class="p">(</span>
    <span class="n">train_dataset</span><span class="p">,</span>
    <span class="n">batch_size</span><span class="o">=</span><span class="n">batch_size</span><span class="p">,</span>
    <span class="n">shuffle</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span>
    <span class="n">num_workers</span><span class="o">=</span><span class="mi">2</span>
<span class="p">)</span>

<span class="n">val_loader</span> <span class="o">=</span> <span class="n">DataLoader</span><span class="p">(</span>
    <span class="n">val_dataset</span><span class="p">,</span>
    <span class="n">batch_size</span><span class="o">=</span><span class="n">batch_size</span><span class="p">,</span>
    <span class="n">shuffle</span><span class="o">=</span><span class="bp">False</span><span class="p">,</span>
    <span class="n">num_workers</span><span class="o">=</span><span class="mi">2</span>
<span class="p">)</span>

<span class="n">test_loader</span> <span class="o">=</span> <span class="n">DataLoader</span><span class="p">(</span>
    <span class="n">test_dataset</span><span class="p">,</span>
    <span class="n">batch_size</span><span class="o">=</span><span class="n">batch_size</span><span class="p">,</span>
    <span class="n">shuffle</span><span class="o">=</span><span class="bp">False</span><span class="p">,</span>
    <span class="n">num_workers</span><span class="o">=</span><span class="mi">2</span>
<span class="p">)</span>

<span class="k">print</span><span class="p">(</span><span class="s">"Classes:"</span><span class="p">,</span> <span class="n">train_dataset</span><span class="p">.</span><span class="n">classes</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="s">"Number of classes:"</span><span class="p">,</span> <span class="n">num_classes</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="s">"Training images:"</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">train_dataset</span><span class="p">))</span>
<span class="k">print</span><span class="p">(</span><span class="s">"Validation images:"</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">val_dataset</span><span class="p">))</span>
<span class="k">print</span><span class="p">(</span><span class="s">"Test images:"</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">test_dataset</span><span class="p">))</span>
</code></pre></div></div>

<h2 id="a-simple-cnn-in-pytorch">A simple CNN in PyTorch</h2>

<div style="background: #f8fafc; border-left: 4px solid #4a148c; padding: 1rem 1.25rem; margin: 1.5rem 0; border-radius: 6px;">
  <strong>Note on the neural architecture.</strong>
  The convolutional neural network used in this tutorial is intentionally simple and is not optimized for the Wine dataset. Its purpose is to provide a clear and reproducible baseline showing how synthetic images generated with TINTOlib can be connected to a PyTorch classification pipeline. Students are encouraged to modify the architecture, tune the hyperparameters, and compare alternative designs to improve predictive performance.
</div>

<p>The following network defines a compact CNN baseline for verifying that the synthetic images generated by TINTOlib can be loaded, processed, and classified within a standard PyTorch workflow.</p>

<p>We assume grayscale images of size 20 x 20 pixels.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">torch</span>
<span class="kn">import</span> <span class="nn">torch.nn</span> <span class="k">as</span> <span class="n">nn</span>


<span class="k">class</span> <span class="nc">SimpleTINTOCNN</span><span class="p">(</span><span class="n">nn</span><span class="p">.</span><span class="n">Module</span><span class="p">):</span>
    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">num_classes</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">img_size</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">20</span><span class="p">):</span>
        <span class="nb">super</span><span class="p">().</span><span class="n">__init__</span><span class="p">()</span>

        <span class="bp">self</span><span class="p">.</span><span class="n">features</span> <span class="o">=</span> <span class="n">nn</span><span class="p">.</span><span class="n">Sequential</span><span class="p">(</span>
            <span class="n">nn</span><span class="p">.</span><span class="n">Conv2d</span><span class="p">(</span><span class="n">in_channels</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span> <span class="n">out_channels</span><span class="o">=</span><span class="mi">32</span><span class="p">,</span> <span class="n">kernel_size</span><span class="o">=</span><span class="mi">3</span><span class="p">,</span> <span class="n">padding</span><span class="o">=</span><span class="mi">1</span><span class="p">),</span>
            <span class="n">nn</span><span class="p">.</span><span class="n">ReLU</span><span class="p">(),</span>
            <span class="n">nn</span><span class="p">.</span><span class="n">MaxPool2d</span><span class="p">(</span><span class="n">kernel_size</span><span class="o">=</span><span class="mi">2</span><span class="p">),</span>

            <span class="n">nn</span><span class="p">.</span><span class="n">Conv2d</span><span class="p">(</span><span class="n">in_channels</span><span class="o">=</span><span class="mi">32</span><span class="p">,</span> <span class="n">out_channels</span><span class="o">=</span><span class="mi">64</span><span class="p">,</span> <span class="n">kernel_size</span><span class="o">=</span><span class="mi">3</span><span class="p">,</span> <span class="n">padding</span><span class="o">=</span><span class="mi">1</span><span class="p">),</span>
            <span class="n">nn</span><span class="p">.</span><span class="n">ReLU</span><span class="p">(),</span>
            <span class="n">nn</span><span class="p">.</span><span class="n">MaxPool2d</span><span class="p">(</span><span class="n">kernel_size</span><span class="o">=</span><span class="mi">2</span><span class="p">),</span>
        <span class="p">)</span>

        <span class="c1"># For img_size=20: 20 -&gt; 10 -&gt; 5 after two MaxPool2d layers
</span>        <span class="n">reduced_size</span> <span class="o">=</span> <span class="n">img_size</span> <span class="o">//</span> <span class="mi">4</span>

        <span class="bp">self</span><span class="p">.</span><span class="n">classifier</span> <span class="o">=</span> <span class="n">nn</span><span class="p">.</span><span class="n">Sequential</span><span class="p">(</span>
            <span class="n">nn</span><span class="p">.</span><span class="n">Flatten</span><span class="p">(),</span>
            <span class="n">nn</span><span class="p">.</span><span class="n">Linear</span><span class="p">(</span><span class="mi">64</span> <span class="o">*</span> <span class="n">reduced_size</span> <span class="o">*</span> <span class="n">reduced_size</span><span class="p">,</span> <span class="mi">128</span><span class="p">),</span>
            <span class="n">nn</span><span class="p">.</span><span class="n">ReLU</span><span class="p">(),</span>
            <span class="n">nn</span><span class="p">.</span><span class="n">Dropout</span><span class="p">(</span><span class="n">p</span><span class="o">=</span><span class="mf">0.3</span><span class="p">),</span>
            <span class="n">nn</span><span class="p">.</span><span class="n">Linear</span><span class="p">(</span><span class="mi">128</span><span class="p">,</span> <span class="n">num_classes</span><span class="p">)</span>
        <span class="p">)</span>

    <span class="k">def</span> <span class="nf">forward</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">x</span><span class="p">):</span>
        <span class="n">x</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">features</span><span class="p">(</span><span class="n">x</span><span class="p">)</span>
        <span class="n">x</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">classifier</span><span class="p">(</span><span class="n">x</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">x</span>


<span class="n">device</span> <span class="o">=</span> <span class="n">torch</span><span class="p">.</span><span class="n">device</span><span class="p">(</span><span class="s">"cuda"</span> <span class="k">if</span> <span class="n">torch</span><span class="p">.</span><span class="n">cuda</span><span class="p">.</span><span class="n">is_available</span><span class="p">()</span> <span class="k">else</span> <span class="s">"cpu"</span><span class="p">)</span>

<span class="n">model</span> <span class="o">=</span> <span class="n">SimpleTINTOCNN</span><span class="p">(</span><span class="n">num_classes</span><span class="o">=</span><span class="n">num_classes</span><span class="p">,</span> <span class="n">img_size</span><span class="o">=</span><span class="n">img_size</span><span class="p">).</span><span class="n">to</span><span class="p">(</span><span class="n">device</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">model</span><span class="p">)</span>
</code></pre></div></div>

<h2 id="training-the-model">Training the model</h2>

<p>For multiclass classification, we use <code class="language-plaintext highlighter-rouge">CrossEntropyLoss</code>. In PyTorch, this loss expects raw logits, so the final layer should not include a <code class="language-plaintext highlighter-rouge">softmax</code> activation.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">torch.optim</span> <span class="k">as</span> <span class="n">optim</span>

<span class="n">criterion</span> <span class="o">=</span> <span class="n">nn</span><span class="p">.</span><span class="n">CrossEntropyLoss</span><span class="p">()</span>
<span class="n">optimizer</span> <span class="o">=</span> <span class="n">optim</span><span class="p">.</span><span class="n">Adam</span><span class="p">(</span><span class="n">model</span><span class="p">.</span><span class="n">parameters</span><span class="p">(),</span> <span class="n">lr</span><span class="o">=</span><span class="mf">1e-3</span><span class="p">)</span>


<span class="k">def</span> <span class="nf">train_one_epoch</span><span class="p">(</span><span class="n">model</span><span class="p">,</span> <span class="n">dataloader</span><span class="p">,</span> <span class="n">criterion</span><span class="p">,</span> <span class="n">optimizer</span><span class="p">,</span> <span class="n">device</span><span class="p">):</span>
    <span class="n">model</span><span class="p">.</span><span class="n">train</span><span class="p">()</span>
    <span class="n">running_loss</span> <span class="o">=</span> <span class="mf">0.0</span>
    <span class="n">correct</span> <span class="o">=</span> <span class="mi">0</span>
    <span class="n">total</span> <span class="o">=</span> <span class="mi">0</span>

    <span class="k">for</span> <span class="n">images</span><span class="p">,</span> <span class="n">labels</span> <span class="ow">in</span> <span class="n">dataloader</span><span class="p">:</span>
        <span class="n">images</span> <span class="o">=</span> <span class="n">images</span><span class="p">.</span><span class="n">to</span><span class="p">(</span><span class="n">device</span><span class="p">)</span>
        <span class="n">labels</span> <span class="o">=</span> <span class="n">labels</span><span class="p">.</span><span class="n">to</span><span class="p">(</span><span class="n">device</span><span class="p">)</span>

        <span class="n">optimizer</span><span class="p">.</span><span class="n">zero_grad</span><span class="p">()</span>
        <span class="n">outputs</span> <span class="o">=</span> <span class="n">model</span><span class="p">(</span><span class="n">images</span><span class="p">)</span>
        <span class="n">loss</span> <span class="o">=</span> <span class="n">criterion</span><span class="p">(</span><span class="n">outputs</span><span class="p">,</span> <span class="n">labels</span><span class="p">)</span>
        <span class="n">loss</span><span class="p">.</span><span class="n">backward</span><span class="p">()</span>
        <span class="n">optimizer</span><span class="p">.</span><span class="n">step</span><span class="p">()</span>

        <span class="n">running_loss</span> <span class="o">+=</span> <span class="n">loss</span><span class="p">.</span><span class="n">item</span><span class="p">()</span> <span class="o">*</span> <span class="n">images</span><span class="p">.</span><span class="n">size</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
        <span class="n">predictions</span> <span class="o">=</span> <span class="n">outputs</span><span class="p">.</span><span class="n">argmax</span><span class="p">(</span><span class="n">dim</span><span class="o">=</span><span class="mi">1</span><span class="p">)</span>
        <span class="n">correct</span> <span class="o">+=</span> <span class="p">(</span><span class="n">predictions</span> <span class="o">==</span> <span class="n">labels</span><span class="p">).</span><span class="nb">sum</span><span class="p">().</span><span class="n">item</span><span class="p">()</span>
        <span class="n">total</span> <span class="o">+=</span> <span class="n">labels</span><span class="p">.</span><span class="n">size</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>

    <span class="n">epoch_loss</span> <span class="o">=</span> <span class="n">running_loss</span> <span class="o">/</span> <span class="n">total</span>
    <span class="n">epoch_acc</span> <span class="o">=</span> <span class="n">correct</span> <span class="o">/</span> <span class="n">total</span>

    <span class="k">return</span> <span class="n">epoch_loss</span><span class="p">,</span> <span class="n">epoch_acc</span>


<span class="o">@</span><span class="n">torch</span><span class="p">.</span><span class="n">no_grad</span><span class="p">()</span>
<span class="k">def</span> <span class="nf">evaluate</span><span class="p">(</span><span class="n">model</span><span class="p">,</span> <span class="n">dataloader</span><span class="p">,</span> <span class="n">criterion</span><span class="p">,</span> <span class="n">device</span><span class="p">):</span>
    <span class="n">model</span><span class="p">.</span><span class="nb">eval</span><span class="p">()</span>
    <span class="n">running_loss</span> <span class="o">=</span> <span class="mf">0.0</span>
    <span class="n">correct</span> <span class="o">=</span> <span class="mi">0</span>
    <span class="n">total</span> <span class="o">=</span> <span class="mi">0</span>

    <span class="k">for</span> <span class="n">images</span><span class="p">,</span> <span class="n">labels</span> <span class="ow">in</span> <span class="n">dataloader</span><span class="p">:</span>
        <span class="n">images</span> <span class="o">=</span> <span class="n">images</span><span class="p">.</span><span class="n">to</span><span class="p">(</span><span class="n">device</span><span class="p">)</span>
        <span class="n">labels</span> <span class="o">=</span> <span class="n">labels</span><span class="p">.</span><span class="n">to</span><span class="p">(</span><span class="n">device</span><span class="p">)</span>

        <span class="n">outputs</span> <span class="o">=</span> <span class="n">model</span><span class="p">(</span><span class="n">images</span><span class="p">)</span>
        <span class="n">loss</span> <span class="o">=</span> <span class="n">criterion</span><span class="p">(</span><span class="n">outputs</span><span class="p">,</span> <span class="n">labels</span><span class="p">)</span>

        <span class="n">running_loss</span> <span class="o">+=</span> <span class="n">loss</span><span class="p">.</span><span class="n">item</span><span class="p">()</span> <span class="o">*</span> <span class="n">images</span><span class="p">.</span><span class="n">size</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
        <span class="n">predictions</span> <span class="o">=</span> <span class="n">outputs</span><span class="p">.</span><span class="n">argmax</span><span class="p">(</span><span class="n">dim</span><span class="o">=</span><span class="mi">1</span><span class="p">)</span>
        <span class="n">correct</span> <span class="o">+=</span> <span class="p">(</span><span class="n">predictions</span> <span class="o">==</span> <span class="n">labels</span><span class="p">).</span><span class="nb">sum</span><span class="p">().</span><span class="n">item</span><span class="p">()</span>
        <span class="n">total</span> <span class="o">+=</span> <span class="n">labels</span><span class="p">.</span><span class="n">size</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>

    <span class="n">epoch_loss</span> <span class="o">=</span> <span class="n">running_loss</span> <span class="o">/</span> <span class="n">total</span>
    <span class="n">epoch_acc</span> <span class="o">=</span> <span class="n">correct</span> <span class="o">/</span> <span class="n">total</span>

    <span class="k">return</span> <span class="n">epoch_loss</span><span class="p">,</span> <span class="n">epoch_acc</span>
</code></pre></div></div>

<p>Now we train the network.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">num_epochs</span> <span class="o">=</span> <span class="mi">20</span>

<span class="n">history</span> <span class="o">=</span> <span class="p">{</span>
    <span class="s">"train_loss"</span><span class="p">:</span> <span class="p">[],</span>
    <span class="s">"train_acc"</span><span class="p">:</span> <span class="p">[],</span>
    <span class="s">"val_loss"</span><span class="p">:</span> <span class="p">[],</span>
    <span class="s">"val_acc"</span><span class="p">:</span> <span class="p">[],</span>
<span class="p">}</span>

<span class="k">for</span> <span class="n">epoch</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">num_epochs</span><span class="p">):</span>
    <span class="n">train_loss</span><span class="p">,</span> <span class="n">train_acc</span> <span class="o">=</span> <span class="n">train_one_epoch</span><span class="p">(</span>
        <span class="n">model</span><span class="p">,</span>
        <span class="n">train_loader</span><span class="p">,</span>
        <span class="n">criterion</span><span class="p">,</span>
        <span class="n">optimizer</span><span class="p">,</span>
        <span class="n">device</span>
    <span class="p">)</span>

    <span class="n">val_loss</span><span class="p">,</span> <span class="n">val_acc</span> <span class="o">=</span> <span class="n">evaluate</span><span class="p">(</span>
        <span class="n">model</span><span class="p">,</span>
        <span class="n">val_loader</span><span class="p">,</span>
        <span class="n">criterion</span><span class="p">,</span>
        <span class="n">device</span>
    <span class="p">)</span>

    <span class="n">history</span><span class="p">[</span><span class="s">"train_loss"</span><span class="p">].</span><span class="n">append</span><span class="p">(</span><span class="n">train_loss</span><span class="p">)</span>
    <span class="n">history</span><span class="p">[</span><span class="s">"train_acc"</span><span class="p">].</span><span class="n">append</span><span class="p">(</span><span class="n">train_acc</span><span class="p">)</span>
    <span class="n">history</span><span class="p">[</span><span class="s">"val_loss"</span><span class="p">].</span><span class="n">append</span><span class="p">(</span><span class="n">val_loss</span><span class="p">)</span>
    <span class="n">history</span><span class="p">[</span><span class="s">"val_acc"</span><span class="p">].</span><span class="n">append</span><span class="p">(</span><span class="n">val_acc</span><span class="p">)</span>

    <span class="k">print</span><span class="p">(</span>
        <span class="sa">f</span><span class="s">"Epoch [</span><span class="si">{</span><span class="n">epoch</span> <span class="o">+</span> <span class="mi">1</span><span class="si">:</span><span class="mi">02</span><span class="n">d</span><span class="si">}</span><span class="s">/</span><span class="si">{</span><span class="n">num_epochs</span><span class="si">}</span><span class="s">] "</span>
        <span class="sa">f</span><span class="s">"Train Loss: </span><span class="si">{</span><span class="n">train_loss</span><span class="si">:</span><span class="p">.</span><span class="mi">4</span><span class="n">f</span><span class="si">}</span><span class="s"> | Train Acc: </span><span class="si">{</span><span class="n">train_acc</span><span class="si">:</span><span class="p">.</span><span class="mi">4</span><span class="n">f</span><span class="si">}</span><span class="s"> | "</span>
        <span class="sa">f</span><span class="s">"Val Loss: </span><span class="si">{</span><span class="n">val_loss</span><span class="si">:</span><span class="p">.</span><span class="mi">4</span><span class="n">f</span><span class="si">}</span><span class="s"> | Val Acc: </span><span class="si">{</span><span class="n">val_acc</span><span class="si">:</span><span class="p">.</span><span class="mi">4</span><span class="n">f</span><span class="si">}</span><span class="s">"</span>
    <span class="p">)</span>
</code></pre></div></div>

<h2 id="evaluating-on-the-test-set">Evaluating on the test set</h2>

<p>The test set should only be used once the model selection process has finished. Here we report the final test performance using the model selected during validation.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">test_loss</span><span class="p">,</span> <span class="n">test_acc</span> <span class="o">=</span> <span class="n">evaluate</span><span class="p">(</span><span class="n">model</span><span class="p">,</span> <span class="n">test_loader</span><span class="p">,</span> <span class="n">criterion</span><span class="p">,</span> <span class="n">device</span><span class="p">)</span>

<span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"Test Loss: </span><span class="si">{</span><span class="n">test_loss</span><span class="si">:</span><span class="p">.</span><span class="mi">4</span><span class="n">f</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"Test Accuracy: </span><span class="si">{</span><span class="n">test_acc</span><span class="si">:</span><span class="p">.</span><span class="mi">4</span><span class="n">f</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>
</code></pre></div></div>

<h2 id="visualizing-the-training-curves">Visualizing the training curves</h2>

<p>In a Jupyter notebook, it is useful to visualize the learning curves to check whether the model is learning and whether overfitting appears.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">matplotlib.pyplot</span> <span class="k">as</span> <span class="n">plt</span>

<span class="n">plt</span><span class="p">.</span><span class="n">figure</span><span class="p">(</span><span class="n">figsize</span><span class="o">=</span><span class="p">(</span><span class="mi">7</span><span class="p">,</span> <span class="mi">4</span><span class="p">))</span>
<span class="n">plt</span><span class="p">.</span><span class="n">plot</span><span class="p">(</span><span class="n">history</span><span class="p">[</span><span class="s">"train_loss"</span><span class="p">],</span> <span class="n">label</span><span class="o">=</span><span class="s">"Train loss"</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">plot</span><span class="p">(</span><span class="n">history</span><span class="p">[</span><span class="s">"val_loss"</span><span class="p">],</span> <span class="n">label</span><span class="o">=</span><span class="s">"Validation loss"</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">xlabel</span><span class="p">(</span><span class="s">"Epoch"</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">ylabel</span><span class="p">(</span><span class="s">"Loss"</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">title</span><span class="p">(</span><span class="s">"Training and validation loss"</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">legend</span><span class="p">()</span>
<span class="n">plt</span><span class="p">.</span><span class="n">grid</span><span class="p">(</span><span class="bp">True</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">show</span><span class="p">()</span>

<span class="n">plt</span><span class="p">.</span><span class="n">figure</span><span class="p">(</span><span class="n">figsize</span><span class="o">=</span><span class="p">(</span><span class="mi">7</span><span class="p">,</span> <span class="mi">4</span><span class="p">))</span>
<span class="n">plt</span><span class="p">.</span><span class="n">plot</span><span class="p">(</span><span class="n">history</span><span class="p">[</span><span class="s">"train_acc"</span><span class="p">],</span> <span class="n">label</span><span class="o">=</span><span class="s">"Train accuracy"</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">plot</span><span class="p">(</span><span class="n">history</span><span class="p">[</span><span class="s">"val_acc"</span><span class="p">],</span> <span class="n">label</span><span class="o">=</span><span class="s">"Validation accuracy"</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">xlabel</span><span class="p">(</span><span class="s">"Epoch"</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">ylabel</span><span class="p">(</span><span class="s">"Accuracy"</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">title</span><span class="p">(</span><span class="s">"Training and validation accuracy"</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">legend</span><span class="p">()</span>
<span class="n">plt</span><span class="p">.</span><span class="n">grid</span><span class="p">(</span><span class="bp">True</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="n">show</span><span class="p">()</span>
</code></pre></div></div>

<h2 id="visualizing-synthetic-images">Visualizing synthetic images</h2>

<p>It is also useful to inspect some generated images. This helps to emphasize that these are not natural images, but artificial spatial encodings of tabular samples.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">images</span><span class="p">,</span> <span class="n">labels</span> <span class="o">=</span> <span class="nb">next</span><span class="p">(</span><span class="nb">iter</span><span class="p">(</span><span class="n">train_loader</span><span class="p">))</span>

<span class="n">plt</span><span class="p">.</span><span class="n">figure</span><span class="p">(</span><span class="n">figsize</span><span class="o">=</span><span class="p">(</span><span class="mi">10</span><span class="p">,</span> <span class="mi">4</span><span class="p">))</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="nb">min</span><span class="p">(</span><span class="mi">8</span><span class="p">,</span> <span class="n">images</span><span class="p">.</span><span class="n">size</span><span class="p">(</span><span class="mi">0</span><span class="p">))):</span>
    <span class="n">plt</span><span class="p">.</span><span class="n">subplot</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="n">i</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span>
    <span class="n">plt</span><span class="p">.</span><span class="n">imshow</span><span class="p">(</span><span class="n">images</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">squeeze</span><span class="p">(</span><span class="mi">0</span><span class="p">),</span> <span class="n">cmap</span><span class="o">=</span><span class="s">"gray"</span><span class="p">)</span>
    <span class="n">plt</span><span class="p">.</span><span class="n">title</span><span class="p">(</span><span class="sa">f</span><span class="s">"Class: </span><span class="si">{</span><span class="n">labels</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">item</span><span class="p">()</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>
    <span class="n">plt</span><span class="p">.</span><span class="n">axis</span><span class="p">(</span><span class="s">"off"</span><span class="p">)</span>

<span class="n">plt</span><span class="p">.</span><span class="n">tight_layout</span><span class="p">()</span>
<span class="n">plt</span><span class="p">.</span><span class="n">show</span><span class="p">()</span>
</code></pre></div></div>

<p><img src="/images/Blog/synthetic_images/2026-05-28-02_TINTO-final.png" alt="Examples of TINTO synthetic images" />
<em>(Figure 3. Visualization of a batch of synthetic images generated with TINTOlib using the TINTO method with blurring. Each panel represents an individual tabular sample from the Wine dataset, encoded as a grayscale two-dimensional image and labeled according to its class. The observed spatial intensity patterns illustrate how different classes may produce distinguishable synthetic representations that can be processed by CNN-based models.)</em></p>

<h2 id="what-should-we-compare-next">What should we compare next?</h2>

<p>This first experiment trains a simple CNN, but a rigorous evaluation should include additional baselines and ablations:</p>

<ul>
  <li>a classical machine learning baseline, such as Random Forest, XGBoost, LightGBM, or CatBoost;</li>
  <li>a multilayer perceptron trained on the original tabular data;</li>
  <li>different tabular-to-image transformations, such as TINTO, IGTD, REFINED, DeepInsight, and SuperTML;</li>
  <li>different image sizes;</li>
  <li>deeper CNNs or Vision Transformers;</li>
  <li>hybrid architectures combining a tabular branch and an image branch.</li>
</ul>

<p>The main question is not only whether a CNN can outperform a tree-based model. The scientifically relevant question is when, why, and under which conditions a spatial representation of tabular data is beneficial.</p>

<h2 id="conclusion">Conclusion</h2>

<p>TINTOlib makes it easier to experiment with a powerful idea: transforming tabular data into visual representations that can be processed by computer vision architectures. This transformation does not automatically make a tabular problem easier, but it provides a different representation where spatial inductive biases can be exploited.</p>

<p>The key element is the spatial arrangement of the features. If related variables are placed close to each other, CNN filters can learn local interactions that are not directly available in the original table. In addition, this representation enables hybrid architectures, Vision Transformers, and visual explainability methods for structured data.</p>

<p>In this post, we introduced the motivation behind tabular-to-image transformation, the role of TINTOlib, the importance of avoiding data leakage, and a first CNN implementation in PyTorch. In future posts, we will compare different TINTOlib methods, build hybrid tabular-image neural networks, and apply explainability techniques to recover the relevance of the original variables.</p>

<h2 id="references-and-related-publications">References and related publications</h2>

<p>The concepts presented in this tutorial are connected to the following research and software publications on TINTO, TINTOlib, tabular-to-image transformation, synthetic spatial representations, hybrid neural networks, and indoor localisation.</p>

<h3 id="research-articles">Research articles</h3>

<ol>
  <li>
    <p>Jiayun Liu, Manuel Castillo-Cara et al. <strong>Interpretable Hybrid Vision Transformer Architectures for MIMO-Based Indoor Localization using Synthetic Spatial Representations</strong>. <em>IEEE Internet of Things</em>. DOI: <a href="https://doi.org/10.1109/JIOT.2026.3696106">10.1109/JIOT.2026.3696106</a></p>
  </li>
  <li>
    <p>Jiayun Liu, Manuel Castillo-Cara et al. <strong>A Comprehensive Benchmark of Spatial Encoding Methods for Tabular Data with Deep Neural Networks</strong>. <em>Information Fusion</em>. DOI: <a href="https://doi.org/10.1016/j.inffus.2025.104088">10.1016/j.inffus.2025.104088</a></p>
  </li>
  <li>
    <p>Giovanny Mondragon-Ruiz, Jiayun Liu, Manuel Castillo-Cara et al. <strong>Interpretable CNN–KAN hybrid architectures for tabular data with synthetic image encoding</strong>. <em>Information Processing and Management</em>. DOI: <a href="https://doi.org/10.1016/j.ipm.2026.104954">10.1016/j.ipm.2026.104954 </a></p>
  </li>
  <li>
    <p>Manuel Castillo-Cara et al. <strong>MIMO-Based Indoor Localisation with Hybrid Neural Networks</strong>. <em>IEEE Journal of Selected Topics in Signal Processing</em>. DOI: <a href="https://doi.org/10.1109/JSTSP.2025.3555067">10.1109/JSTSP.2025.3555067</a></p>
  </li>
  <li>
    <p>Reewos Talla-Chumpitaz, Manuel Castillo-Cara et al. <strong>Blurring Image Techniques for Bluetooth-based Indoor Localisation</strong>. <em>Information Fusion</em>. DOI: <a href="https://doi.org/10.1016/j.inffus.2022.10.011">10.1016/j.inffus.2022.10.011</a></p>
  </li>
</ol>

<h3 id="software-articles">Software articles</h3>

<ol>
  <li>
    <p>Jiayun Liu et al. <strong>TINTOlib: A Python library for transforming tabular data into synthetic images for deep neural networks</strong>. <em>SoftwareX</em>. DOI: <a href="https://doi.org/10.1016/j.softx.2025.102444">10.1016/j.softx.2025.102444</a></p>
  </li>
  <li>
    <p>Manuel Castillo-Cara et al. <strong>TINTO: Converting Tidy Data into Image for Classification with 2-Dimensional Convolutional Neural Networks</strong>. <em>SoftwareX</em>. DOI: <a href="https://doi.org/10.1016/j.softx.2023.101391">10.1016/j.softx.2023.101391</a></p>
  </li>
</ol>

<!-- ===== Blog Post Footer: CTA → Author → License → Export ===== -->
<style>
  /* Hide default Minimal Mistakes related posts (replaced by custom cards below) */
  .page__related { display: none !important; }

  /* ----- Copy-code button ----- */
  .page__content pre { position: relative; }
  .copy-code-btn {
    position: absolute;
    top: 0.35rem;
    right: 0.45rem;
    padding: 0.15rem 0.55rem;
    font-size: 0.72rem;
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
    background: #eff6ff;
    color: #1d4ed8;
    border: 1px solid #bfdbfe;
    border-radius: 4px;
    cursor: pointer;
    transition: background 0.15s, color 0.15s;
    line-height: 1.5;
    z-index: 2;
    user-select: none;
    font-weight: 600;
  }
  .copy-code-btn:hover { background: #dbeafe; color: #1e40af; border-color: #93c5fd; }
  .copy-code-btn.copied { background: #d1fae5; color: #065f46; border-color: #6ee7b7; }

  /* ----- Post footer wrapper ----- */
  .blog-post-footer {
    margin-top: 3rem;
    border-top: 2px solid #e2e8f0;
    padding-top: 2rem;
  }

  /* ----- CTA (first) ----- */
  .blog-cta {
    background: linear-gradient(135deg, #1565c0 0%, #6d28d9 100%);
    color: #fff;
    border-radius: 12px;
    padding: 1.4rem 1.75rem;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    margin-bottom: 1.5rem;
    box-shadow: 0 4px 18px rgba(21,101,192,0.22);
  }
  .blog-cta-text h3 { margin: 0 0 0.3rem; font-size: 1.05rem; color: #fff; }
  .blog-cta-text p  { margin: 0; font-size: 0.88rem; color: rgba(255,255,255,0.85); }
  .blog-cta-btn {
    display: inline-block;
    background: #fff;
    color: #1565c0 !important;
    font-weight: 800;
    font-size: 0.9rem;
    text-decoration: none !important;
    padding: 0.55rem 1.3rem;
    border-radius: 8px;
    white-space: nowrap;
    transition: box-shadow 0.15s, transform 0.1s;
  }
  .blog-cta-btn:hover { box-shadow: 0 4px 14px rgba(0,0,0,.22); transform: translateY(-1px); }

  /* ----- Author card (second) ----- */
  .blog-author-card {
    display: flex;
    align-items: center;
    gap: 0.85rem;
    background: #f8fafc;
    border: 1px solid #e2e8f0;
    border-radius: 12px;
    padding: 0.8rem 1.1rem;
    margin-bottom: 0.75rem;
    box-shadow: 0 2px 8px rgba(0,0,0,.05);
  }
  .blog-author-avatar {
    width: 58px;
    height: 76px;
    border-radius: 999px;
    object-fit: cover;
    flex-shrink: 0;
    border: 2px solid #c7d9f0;
    background: #e8edf5;
    box-shadow: 0 2px 8px rgba(0,0,0,.12);
  }
  .blog-author-info { flex: 1; min-width: 0; }
  .blog-author-card p { margin: 0; line-height: 1.25; }
  .blog-author-name { font-weight: 800; font-size: 0.95rem !important; color: #1f2937; }
  .blog-author-role { font-size: 0.82rem !important; color: #374151; }
  .blog-author-spec { font-size: 0.82rem !important; color: #374151; }
  .blog-author-dept { font-size: 0.78rem !important; color: #4b5563; }
  .blog-author-inst { font-size: 0.76rem !important; color: #6b7280; }

  /* ----- License (third) ----- */
  .blog-license {
    font-size: 0.81rem;
    color: #6b7280;
    background: #f1f5f9;
    border: 1px solid #e2e8f0;
    border-radius: 8px;
    padding: 0.55rem 1rem;
    margin-bottom: 1.1rem;
  }
  .blog-license a { color: #1976d2; }

  /* ----- Resource / export buttons (fourth) ----- */
  .blog-resources {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin-bottom: 0.5rem;
  }
  .blog-resource-btn {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.38rem 0.9rem;
    border-radius: 8px;
    font-size: 0.82rem;
    font-weight: 700;
    text-decoration: none !important;
    cursor: pointer;
    border: 1px solid transparent;
    transition: box-shadow 0.15s, transform 0.1s;
    line-height: 1.4;
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  }
  .blog-resource-btn:hover { box-shadow: 0 4px 12px rgba(0,0,0,.14); transform: translateY(-1px); }
  .blog-resource-btn--notebook { background: #fff3e0; color: #e65100 !important; border-color: #ffcc80; }
  .blog-resource-btn--pdf      { background: #fce4ec; color: #c62828 !important; border-color: #f48fb1; }
  .blog-resource-btn--print    { background: #e8f5e9; color: #2e7d32 !important; border-color: #a5d6a7; }
  .blog-resource-btn--save     { background: #e8eaf6; color: #283593 !important; border-color: #9fa8da; }
  .blog-resource-btn--python   { background: #fef9c3; color: #713f12 !important; border-color: #fde68a; }
  .blog-resource-btn--ipynb    { background: #fff7ed; color: #92400e !important; border-color: #fed7aa; }

  /* ----- Related posts grid ----- */
  .bpf-related { margin-top: 2rem; padding-top: 1.5rem; border-top: 1px solid #e2e8f0; }
  .bpf-related-title {
    font-size: 0.78rem;
    font-weight: 800;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: #6b7280;
    margin: 0 0 1rem;
  }
  .bpf-related-grid {
    display: grid;
    grid-template-columns: repeat(3, minmax(0,1fr));
    gap: 1rem;
  }
  @media (max-width: 860px) { .bpf-related-grid { grid-template-columns: repeat(2, minmax(0,1fr)); } }
  @media (max-width: 540px) { .bpf-related-grid { grid-template-columns: 1fr; } }
  .bpf-card {
    background: #f4f7fb;
    border: 1px solid #cfd8dc;
    border-radius: 12px;
    overflow: hidden;
    display: flex;
    flex-direction: column;
    box-shadow: 0 2px 8px rgba(0,0,0,.05);
    transition: box-shadow .15s, transform .12s;
    text-decoration: none !important;
  }
  .bpf-card:hover { box-shadow: 0 6px 20px rgba(0,0,0,.1); transform: translateY(-2px); }
  .bpf-card-thumb {
    width: 100%;
    aspect-ratio: 16/9;
    object-fit: cover;
    display: block;
    background: #e8edf5;
  }
  .bpf-card-body { padding: .75rem .9rem .9rem; display: flex; flex-direction: column; flex: 1; }
  .bpf-card-meta {
    display: flex; align-items: center; gap: .45rem;
    font-size: .78rem; color: #6b7280; margin-bottom: .35rem; flex-wrap: wrap;
  }
  .bpf-card-cat {
    background: #e0ecff; color: #0f3d8a;
    font-weight: 700; font-size: .72rem;
    padding: .12rem .45rem; border-radius: 999px;
  }
  .bpf-card-title {
    font-size: .95rem; font-weight: 800; color: #1565c0;
    line-height: 1.3; margin: 0 0 .45rem;
  }
  .bpf-card-title a { color: inherit; text-decoration: none; }
  .bpf-card-title a:hover { text-decoration: underline; }
  .bpf-card-tags { display: flex; flex-wrap: wrap; gap: .25rem; margin-bottom: .65rem; }
  .bpf-tag {
    background: #f1f5f9; border: 1px solid #cbd5e1;
    color: #6b7280; font-size: .72rem;
    padding: .1rem .4rem; border-radius: 999px;
  }
  .bpf-btn {
    display: inline-block;
    background: #1976d2; color: #fff !important;
    text-decoration: none !important;
    padding: .4rem .85rem; border-radius: 10px;
    font-weight: 800; font-size: .84rem; text-align: center;
    transition: background .15s, box-shadow .15s;
    align-self: flex-start; margin-top: auto;
  }
  .bpf-btn:hover { background: #0f60b6; box-shadow: 0 4px 12px rgba(0,0,0,.12); }

  @media (max-width: 560px) {
    .blog-author-card { flex-direction: column; align-items: center; text-align: center; }
    .blog-author-avatar { width: 52px; height: 68px; }
    .blog-cta { text-align: center; justify-content: center; }
    .blog-resources { justify-content: center; }
  }
</style>

<div class="blog-post-footer">

  <!-- 1. CTA -->
  <div class="blog-cta">
    <div class="blog-cta-text">
      <h3>Continue learning Artificial Intelligence</h3>
      <p>Explore practical courses on AI, Machine Learning, Deep Learning, Python, R and applied data science.</p>
    </div>
    <a class="blog-cta-btn" href="https://www.manuelcastillo.eu/udemy/" target="_blank" rel="noopener noreferrer">View AI Courses &rarr;</a>
  </div>

  <!-- 2. Author card -->
  <div class="blog-author-card">
    <img class="blog-author-avatar" src="/images/profile.jpg" alt="Manuel Castillo-Cara" onerror="this.style.display='none'" />
    <div class="blog-author-info">
      <p class="blog-author-name">Manuel Castillo-Cara, PhD</p>
      <p class="blog-author-spec">TINTOlib Python Library Developer</p>
      <p class="blog-author-role">Researcher &amp; Professor</p>
      <p class="blog-author-dept">Department of Artificial Intelligence</p>
      <p class="blog-author-inst">Universidad Nacional de Educación a Distancia (UNED)</p>
      <p class="blog-author-inst">Almerimar (Almería), Spain</p>
    </div>
  </div>

  <!-- 3. License -->
  <p class="blog-license">
    &copy; Manuel Castillo-Cara, PhD. Content licensed under
    <a href="https://creativecommons.org/licenses/by-nc/4.0/" target="_blank" rel="noopener noreferrer">CC BY-NC 4.0</a>
    unless otherwise stated.
  </p>

  <!-- 4. Export / resource buttons -->
  <div class="blog-resources">
    
    
    <button type="button" class="blog-resource-btn blog-resource-btn--print" onclick="window.print()">
      🖨️ Print / Save as PDF
    </button>
    <button type="button" class="blog-resource-btn blog-resource-btn--save" id="btn-save-html">
      💾 Save as HTML
    </button>
    <button type="button" class="blog-resource-btn blog-resource-btn--python" id="btn-download-py">
      🐍 Download Python
    </button>
    <button type="button" class="blog-resource-btn blog-resource-btn--ipynb" id="btn-download-ipynb">
      📓 Notebook (.ipynb)
    </button>
  </div>

  <!-- 5. Related posts -->
  
  
    <div class="bpf-related">
      <p class="bpf-related-title">You may also enjoy</p>
      <div class="bpf-related-grid">
        
          
          
          <article class="bpf-card">
            
              <img class="bpf-card-thumb" src="/images/Blog/2026-06-02-04-xai-hynn.png" alt="Improving Deep Learning by Exploiting Synthetic Images — Part II: From Synthetic Images to Hybrid Neural Networks" loading="lazy" />
            
            <div class="bpf-card-body">
              <div class="bpf-card-meta">
                <time datetime="2026-06-03T00:00:00+02:00">03 Jun 2026</time>
                <span class="bpf-card-cat">TINTOlib</span>
              </div>
              <h3 class="bpf-card-title">
                <a href="/blog/2026/06/04-from-synthetic-images-to-hybrid-neural-networks/">Improving Deep Learning by Exploiting Synthetic Images — Part II: From Synthetic Images to Hybrid Neural Networks</a>
              </h3>
              
                <div class="bpf-card-tags">
                  
                    <span class="bpf-tag">TINTOlib</span>
                  
                    <span class="bpf-tag">Synthetic Images</span>
                  
                    <span class="bpf-tag">Tabular-to-Image</span>
                  
                    <span class="bpf-tag">Hybrid Neural Networks</span>
                  
                </div>
              
              <a class="bpf-btn" href="/blog/2026/06/04-from-synthetic-images-to-hybrid-neural-networks/">Read article →</a>
            </div>
          </article>
        
          
          
          <article class="bpf-card">
            
              <img class="bpf-card-thumb" src="/images/Blog/2026-06-02-03-improving-deep-learning-exploiting-synthetic-images.png" alt="Improving Deep Learning by Exploiting Synthetic Images — Part I: Why Tabular Data Needs Spatial Representations" loading="lazy" />
            
            <div class="bpf-card-body">
              <div class="bpf-card-meta">
                <time datetime="2026-06-02T00:00:00+02:00">02 Jun 2026</time>
                <span class="bpf-card-cat">TINTOlib</span>
              </div>
              <h3 class="bpf-card-title">
                <a href="/blog/2026/06/03-improving-deep-learning-exploiting-synthetic-images-part-1/">Improving Deep Learning by Exploiting Synthetic Images — Part I: Why Tabular Data Needs Spatial Representations</a>
              </h3>
              
                <div class="bpf-card-tags">
                  
                    <span class="bpf-tag">TINTOlib</span>
                  
                    <span class="bpf-tag">Synthetic Images</span>
                  
                    <span class="bpf-tag">Tabular-to-Image</span>
                  
                    <span class="bpf-tag">Tabular Data</span>
                  
                </div>
              
              <a class="bpf-btn" href="/blog/2026/06/03-improving-deep-learning-exploiting-synthetic-images-part-1/">Read article →</a>
            </div>
          </article>
        
          
          
          <article class="bpf-card">
            
              <img class="bpf-card-thumb" src="/images/Blog/2026-05-27-01-introduction-to-tintolib-tabular-to-image.png" alt="Introduction to TINTOlib: Unlocking the Power of Vision Architectures for Tabular Data" loading="lazy" />
            
            <div class="bpf-card-body">
              <div class="bpf-card-meta">
                <time datetime="2026-05-27T00:00:00+02:00">27 May 2026</time>
                <span class="bpf-card-cat">TINTOlib</span>
              </div>
              <h3 class="bpf-card-title">
                <a href="/blog/2026/05/01-introduction-to-tintolib-tabular-to-image/">Introduction to TINTOlib: Unlocking the Power of Vision Architectures for Tabular Data</a>
              </h3>
              
                <div class="bpf-card-tags">
                  
                    <span class="bpf-tag">TINTOlib</span>
                  
                    <span class="bpf-tag">Tabular-to-Image</span>
                  
                    <span class="bpf-tag">Synthetic Images</span>
                  
                    <span class="bpf-tag">Deep Learning</span>
                  
                </div>
              
              <a class="bpf-btn" href="/blog/2026/05/01-introduction-to-tintolib-tabular-to-image/">Read article →</a>
            </div>
          </article>
        
      </div>
    </div>
  

</div>

<!-- ===== Copy-code + Export logic ===== -->
<script>
(function () {

  /* ---------- helpers ---------- */
  function slugify(str) {
    return (str || 'blog-post')
      .replace(/[^a-z0-9\-_]/gi, '-')
      .replace(/-+/g, '-')
      .toLowerCase()
      .substring(0, 60)
      .replace(/^-+|-+$/g, '');
  }

  function download(content, filename, mime) {
    var blob = new Blob([content], { type: mime });
    var url  = URL.createObjectURL(blob);
    var a    = document.createElement('a');
    a.href = url;
    a.download = filename;
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
    setTimeout(function () { URL.revokeObjectURL(url); }, 5000);
  }

  function getCodeBlocks() {
    var nodes = document.querySelectorAll('.page__content pre code');
    var blocks = [];
    nodes.forEach(function (node) {
      var text = node.textContent.trimEnd();
      if (text) blocks.push(text);
    });
    return blocks;
  }

  /* ---------- Copy-code buttons ---------- */
  var pres = document.querySelectorAll('.page__content pre');
  pres.forEach(function (pre) {
    var code = pre.querySelector('code');
    var getText = function () {
      if (code) return code.textContent;
      var clone = pre.cloneNode(true);
      clone.querySelectorAll('.copy-code-btn').forEach(function (b) { b.parentNode.removeChild(b); });
      return clone.textContent;
    };
    var btn = document.createElement('button');
    btn.type = 'button';
    btn.className = 'copy-code-btn';
    btn.textContent = 'Copy';
    btn.setAttribute('aria-label', 'Copy code to clipboard');
    pre.appendChild(btn);
    btn.addEventListener('click', function () {
      var text = getText();
      if (navigator.clipboard && navigator.clipboard.writeText) {
        navigator.clipboard.writeText(text).then(function () { flash(btn); });
      } else {
        var ta = document.createElement('textarea');
        ta.value = text;
        ta.style.cssText = 'position:fixed;opacity:0;top:0;left:0';
        document.body.appendChild(ta);
        ta.focus(); ta.select();
        try { document.execCommand('copy'); } catch (e) {}
        document.body.removeChild(ta);
        flash(btn);
      }
    });
  });
  function flash(btn) {
    btn.textContent = 'Copied \u2713';
    btn.classList.add('copied');
    setTimeout(function () { btn.textContent = 'Copy'; btn.classList.remove('copied'); }, 1600);
  }

  /* ---------- Save as HTML ---------- */
  var saveHtmlBtn = document.getElementById('btn-save-html');
  if (saveHtmlBtn) {
    saveHtmlBtn.addEventListener('click', function () {
      var clone = document.documentElement.cloneNode(true);
      var origin = window.location.origin;
      clone.querySelectorAll('img[src]').forEach(function (img) {
        var src = img.getAttribute('src');
        if (src && src.charAt(0) === '/') img.setAttribute('src', origin + src);
      });
      clone.querySelectorAll('link[href]').forEach(function (el) {
        var href = el.getAttribute('href');
        if (href && href.charAt(0) === '/') el.setAttribute('href', origin + href);
      });
      clone.querySelectorAll('script[src]').forEach(function (el) {
        var src = el.getAttribute('src');
        if (src && src.charAt(0) === '/') el.setAttribute('src', origin + src);
      });
      var html = '<!doctype html>\n' + clone.outerHTML;
      download(html, slugify(document.title) + '.html', 'text/html;charset=utf-8');
    });
  }

  /* ---------- Download Python ---------- */
  var pyBtn = document.getElementById('btn-download-py');
  if (pyBtn) {
    pyBtn.addEventListener('click', function () {
      var blocks = getCodeBlocks();
      var lines;
      if (blocks.length === 0) {
        lines = ['# No code blocks found in this post.\n'];
      } else {
        lines = [];
        blocks.forEach(function (block, i) {
          lines.push('# ---- Code block ' + (i + 1) + ' ----\n');
          lines.push(block + '\n\n');
        });
      }
      download(lines.join(''), slugify(document.title) + '.py', 'text/x-python;charset=utf-8');
    });
  }

  /* ---------- Download Notebook (.ipynb) — full article walker ---------- */
  var ipynbBtn = document.getElementById('btn-download-ipynb');
  if (ipynbBtn) {
    ipynbBtn.addEventListener('click', function () {

      function mdLines(text) {
        /* Split into source array as Jupyter expects (lines ending with \n except last) */
        var lines = text.split('\n');
        return lines.map(function (l, i) { return i < lines.length - 1 ? l + '\n' : l; });
      }

      function mkdCell(text) {
        var t = (text || '').trim();
        if (!t) return null;
        return { cell_type: 'markdown', metadata: {}, source: mdLines(t) };
      }

      function codeCell(text) {
        var t = (text || '').trimEnd();
        if (!t) return null;
        return { cell_type: 'code', execution_count: null, metadata: {}, outputs: [], source: mdLines(t) };
      }

      function nodeToMd(el) {
        var tag = el.tagName ? el.tagName.toLowerCase() : '';
        /* headings */
        if (/^h[1-6]$/.test(tag)) {
          var level = parseInt(tag.slice(1), 10);
          return Array(level + 1).join('#') + ' ' + el.textContent.trim();
        }
        /* paragraph */
        if (tag === 'p') return el.textContent.trim();
        /* blockquote */
        if (tag === 'blockquote') {
          return el.textContent.trim().split('\n').map(function (l) { return '> ' + l; }).join('\n');
        }
        /* lists */
        if (tag === 'ul' || tag === 'ol') {
          var items = [];
          el.querySelectorAll('li').forEach(function (li, i) {
            items.push((tag === 'ol' ? (i + 1) + '. ' : '- ') + li.textContent.trim());
          });
          return items.join('\n');
        }
        /* figure / img */
        if (tag === 'figure') {
          var img = el.querySelector('img');
          if (img) {
            var src = img.getAttribute('src') || '';
            if (src.charAt(0) === '/') src = window.location.origin + src;
            var alt = img.getAttribute('alt') || '';
            var cap = el.querySelector('figcaption');
            return '!['+ alt +']('+ src +')' + (cap ? '\n*' + cap.textContent.trim() + '*' : '');
          }
        }
        if (tag === 'img') {
          var src2 = el.getAttribute('src') || '';
          if (src2.charAt(0) === '/') src2 = window.location.origin + src2;
          return '![' + (el.getAttribute('alt') || '') + '](' + src2 + ')';
        }
        return '';
      }

      function getArticleCellsForNotebook() {
        var container = document.querySelector('.page__content');
        if (!container) return [];

        var clone = container.cloneNode(true);
        /* Remove footer, scripts, styles, copy buttons */
        ['script','style','.blog-post-footer','.copy-code-btn'].forEach(function (sel) {
          clone.querySelectorAll(sel).forEach(function (n) { n.parentNode.removeChild(n); });
        });

        var cells = [];
        var INLINE_TAGS = /^(h[1-6]|p|ul|ol|blockquote|figure|img)$/;

        function walk(node) {
          if (node.nodeType !== 1) return; /* element nodes only */
          var tag = node.tagName.toLowerCase();

          /* code block */
          if (tag === 'pre') {
            var code = node.querySelector('code');
            var text = (code ? code : node).textContent.trimEnd();
            var cell = codeCell(text);
            if (cell) cells.push(cell);
            return;
          }

          /* inline-mappable tags */
          if (INLINE_TAGS.test(tag)) {
            var md = nodeToMd(node);
            var cell2 = mkdCell(md);
            if (cell2) cells.push(cell2);
            return;
          }

          /* containers: div, section, article — recurse children */
          Array.prototype.forEach.call(node.childNodes, function (child) {
            walk(child);
          });
        }

        Array.prototype.forEach.call(clone.childNodes, function (child) {
          walk(child);
        });

        return cells;
      }

      var cells = [];
      /* Header cells */
      cells.push(mkdCell('# ' + (document.title || 'Blog Post')));
      var pageUrl = window.location.href;
      cells.push(mkdCell('**Source:** [' + pageUrl + '](' + pageUrl + ')'));

      /* Article body cells */
      var bodyCells = getArticleCellsForNotebook();
      if (bodyCells.length === 0) {
        cells.push(mkdCell('*No content extracted from this post.*'));
      } else {
        cells = cells.concat(bodyCells);
      }

      var nb = {
        nbformat: 4,
        nbformat_minor: 5,
        metadata: {
          kernelspec: { display_name: 'Python 3', language: 'python', name: 'python3' },
          language_info: { name: 'python', version: '3.x' }
        },
        cells: cells
      };

      download(JSON.stringify(nb, null, 2), slugify(document.title) + '.ipynb', 'application/json;charset=utf-8');
    });
  }

}());
</script>]]></content><author><name>Ph.D. Manuel Castillo-Cara</name><email>manwest.c@gmail.com</email></author><category term="TINTOlib" /><category term="TINTOlib" /><category term="Tabular Data" /><category term="Synthetic Images" /><category term="Deep Learning" /><category term="CNN" /><category term="PyTorch" /><category term="Python" /><category term="Blurring" /><category term="Tabular-to-Image" /><summary type="html"><![CDATA[An introduction to TINTOlib: why tabular data requires spatial encoding, how to generate synthetic images avoiding data leakage, and a complete end-to-end CNN pipeline in PyTorch.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.manuelcastillo.eu/images/Blog/2026-05-28-02-what-is-it-tintolib-synthetic-images.png" /><media:content medium="image" url="https://www.manuelcastillo.eu/images/Blog/2026-05-28-02-what-is-it-tintolib-synthetic-images.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Introduction to TINTOlib: Unlocking the Power of Vision Architectures for Tabular Data</title><link href="https://www.manuelcastillo.eu/blog/2026/05/01-introduction-to-tintolib-tabular-to-image/" rel="alternate" type="text/html" title="Introduction to TINTOlib: Unlocking the Power of Vision Architectures for Tabular Data" /><published>2026-05-27T00:00:00+02:00</published><updated>2026-05-27T00:00:00+02:00</updated><id>https://www.manuelcastillo.eu/blog/2026/05/01-introduction-to-tintolib-tabular-to-image</id><content type="html" xml:base="https://www.manuelcastillo.eu/blog/2026/05/01-introduction-to-tintolib-tabular-to-image/"><![CDATA[<link rel="canonical" href="https://www.manuelcastillo.eu/blog/2026/05/01-introduction-to-tintolib-tabular-to-image/" />

<meta name="robots" content="index,follow,max-image-preview:large" />

<meta name="description" content="Technical introduction to TINTOlib, a Python framework for transforming tabular data into synthetic images and applying CNN-based deep learning architectures." />

<meta property="og:type" content="article" />

<meta property="og:title" content="Introduction to TINTOlib: Unlocking the Power of Vision Architectures for Tabular Data" />

<meta property="og:description" content="Technical introduction to TINTOlib, a Python framework for transforming tabular data into synthetic images and applying CNN-based deep learning architectures." />

<meta property="og:url" content="https://www.manuelcastillo.eu/blog/2026/05/01-introduction-to-tintolib-tabular-to-image/" />

<meta property="og:image" content="https://www.manuelcastillo.eu/images/Blog/2026-05-27-01-introduction-to-tintolib-tabular-to-image.png" />

<meta property="article:published_time" content="2026-05-27T00:00:00+02:00" />

<meta property="article:modified_time" content="2026-05-27T00:00:00+02:00" />

<meta property="article:author" content="Manuel Castillo-Cara" />

<meta property="article:section" content="TINTOlib" />

<meta property="article:tag" content="TINTOlib" />

<meta property="article:tag" content="Tabular-to-Image" />

<meta property="article:tag" content="Synthetic Images" />

<meta property="article:tag" content="Deep Learning" />

<meta property="article:tag" content="CNN" />

<meta property="article:tag" content="PyTorch" />

<meta property="article:tag" content="Machine Learning" />

<meta name="twitter:card" content="summary_large_image" />

<meta name="twitter:title" content="Introduction to TINTOlib: Unlocking the Power of Vision Architectures for Tabular Data" />

<meta name="twitter:description" content="Technical introduction to TINTOlib, a Python framework for transforming tabular data into synthetic images and applying CNN-based deep learning architectures." />

<meta name="twitter:image" content="https://www.manuelcastillo.eu/images/Blog/2026-05-27-01-introduction-to-tintolib-tabular-to-image.png" />

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "BlogPosting",
  "headline": "Introduction to TINTOlib: Unlocking the Power of Vision Architectures for Tabular Data",
  "description": "Technical introduction to TINTOlib, a Python framework for transforming tabular data into synthetic images and applying CNN-based deep learning architectures.",
  "image": "https://www.manuelcastillo.eu/images/Blog/2026-05-27-01-introduction-to-tintolib-tabular-to-image.png",
  "author": {
    "@type": "Person",
    "name": "Manuel Castillo-Cara",
    "url": "https://www.manuelcastillo.eu/"
  },
  "publisher": {
    "@type": "Person",
    "name": "Manuel Castillo-Cara",
    "url": "https://www.manuelcastillo.eu/"
  },
  "datePublished": "2026-05-27T00:00:00+02:00",
  "dateModified": "2026-05-27T00:00:00+02:00",
  "mainEntityOfPage": {
    "@type": "WebPage",
    "@id": "https://www.manuelcastillo.eu/blog/2026/05/01-introduction-to-tintolib-tabular-to-image/"
  },
  "articleSection": "TINTOlib",
  "keywords": "TINTOlib, Tabular-to-Image, Synthetic Images, Deep Learning, CNN, PyTorch, Machine Learning"
}
</script>

<div style="background: linear-gradient(135deg, #1a237e 0%, #4a148c 50%, #311b92 100%); border-radius: 12px; padding: 2.5rem 2rem; margin: 1.5rem 0 2.5rem; display: flex; flex-wrap: wrap; align-items: center; gap: 2rem; color: #fff;">
  <div style="flex: 1 1 280px; min-width: 0;">
    <p style="margin: 0 0 0.4rem; font-size: 0.78rem; letter-spacing: 0.12em; text-transform: uppercase; color: #b39ddb; font-weight: 600;">TINTOlib · Deep Learning · Tabular-to-Image</p>
    <h1 style="margin: 0 0 0.75rem; font-size: clamp(1.5rem, 4vw, 2.1rem); font-weight: 800; line-height: 1.2; color: #fff;">Introduction to TINTOlib</h1>
    <p style="margin: 0 0 1rem; font-size: 0.97rem; color: #e1d5f5; line-height: 1.55;">A Python framework for transforming tabular data into synthetic images and applying CNN-based vision architectures — bridging the gap between structured data and deep learning.</p>
    <div style="display: flex; flex-wrap: wrap; gap: 0.5rem;">
      <span style="background: rgba(255,255,255,0.15); border-radius: 20px; padding: 0.25rem 0.75rem; font-size: 0.78rem; color: #e8d5ff;">PyTorch</span>
      <span style="background: rgba(255,255,255,0.15); border-radius: 20px; padding: 0.25rem 0.75rem; font-size: 0.78rem; color: #e8d5ff;">CNN</span>
      <span style="background: rgba(255,255,255,0.15); border-radius: 20px; padding: 0.25rem 0.75rem; font-size: 0.78rem; color: #e8d5ff;">Tabular Data</span>
      <span style="background: rgba(255,255,255,0.15); border-radius: 20px; padding: 0.25rem 0.75rem; font-size: 0.78rem; color: #e8d5ff;">Synthetic Images</span>
    </div>
  </div>
  <div style="flex: 0 0 auto; max-width: 260px; width: 100%;">
    <img src="/images/Blog/2026-05-27-01-introduction-to-tintolib-tabular-to-image.png" alt="TINTOlib tabular-to-image transformation" style="width: 100%; border-radius: 10px; box-shadow: 0 8px 32px rgba(0,0,0,0.45); display: block;" />
  </div>
</div>

<hr />

<blockquote>
  <blockquote>
    <ul>
      <li><strong>Author:</strong> Manuel Castillo-Cara, PhD</li>
      <li><strong>Affiliation:</strong> Dpt. of Artificial Intelligence, Universidad Nacional de Educación a Distancia (UNED), Spain</li>
      <li><strong>Role:</strong> Researcher, Professor, and TINTOlib Python Library Developer</li>
      <li><strong>License:</strong> <a href="https://creativecommons.org/licenses/by-nc/4.0/">CC BY-NC 4.0</a> unless otherwise stated.</li>
    </ul>
  </blockquote>
</blockquote>

<hr />

<!-- ======== TINTOlib: Overview Videos (ES/EN) ======== -->
<section id="tintolib-overview-videos" style="margin: 1.75rem 0 2rem;">
  <h2 style="margin: 0 0 0.5rem;">TINTOlib overview videos</h2>
    <p style="margin: 0 0 1rem; line-height: 1.6;">
      The following short videos provide a bilingual introduction to <strong>TINTOlib</strong>, explaining how tabular data can be transformed into synthetic images and processed with computer vision architectures such as CNNs, Vision Transformers and hybrid neural networks.
    </p>
      <div class="tintolib-video-toggle" role="tablist" aria-label="Select video language">
    <button type="button" class="tintolib-video-btn active" data-target="#tintolib-video-es" role="tab" aria-selected="true">
      Español
    </button>
    <button type="button" class="tintolib-video-btn" data-target="#tintolib-video-en" role="tab" aria-selected="false">
      English
    </button>
  </div>
    <div class="tintolib-video-wrap">
    <video id="tintolib-video-es" class="tintolib-video-panel active" controls="" controlsList="nodownload" preload="metadata" playsinline="" aria-label="TINTOlib overview video in Spanish">
      <source src="/video/TINTOlib-video-Es.mp4" type="video/mp4" />
      Your browser does not support the video tag.
      <a href="/video/TINTOlib-video-Es.mp4">Open the Spanish video</a>.
    </video>
    <video id="tintolib-video-en" class="tintolib-video-panel" controls="" controlsList="nodownload" preload="metadata" playsinline="" aria-label="TINTOlib overview video in English">
  <source src="/video/TINTOlib-video-En.mp4" type="video/mp4" />
  Your browser does not support the video tag.
  <a href="/video/TINTOlib-video-En.mp4">Open the English video</a>.
  </video>
  </div>
</section>
<style>
  #tintolib-overview-videos .tintolib-video-toggle {
    display: flex;
    gap: 0.5rem;
    flex-wrap: wrap;
    margin: 0.75rem 0 1rem;
  }

  #tintolib-overview-videos .tintolib-video-btn {
    background: #eef2ff;
    border: 1px solid #dbe3ff;
    color: #1f2937;
    font-weight: 800;
    border-radius: 999px;
    padding: 0.35rem 0.8rem;
    cursor: pointer;
  }

  #tintolib-overview-videos .tintolib-video-btn.active {
    background: #2563eb;
    color: #fff;
    border-color: #2563eb;
  }

  #tintolib-overview-videos .tintolib-video-wrap {
    position: relative;
    width: 100%;
    max-width: 900px;
    margin: 0 auto;
  }

  #tintolib-overview-videos .tintolib-video-panel {
    display: none;
    width: 100%;
    aspect-ratio: 16 / 9;
    object-fit: cover;
    border: 1px solid #d0d7de;
    border-radius: 12px;
    box-shadow: 0 2px 8px rgba(0,0,0,.08);
    background: #000;
  }

  #tintolib-overview-videos .tintolib-video-panel.active {
    display: block;
  }
</style>

<script>
  document.addEventListener('DOMContentLoaded', function () {
    const container = document.getElementById('tintolib-overview-videos');
    if (!container) return;

    const buttons = container.querySelectorAll('.tintolib-video-btn');
    const panels = container.querySelectorAll('.tintolib-video-panel');

    buttons.forEach(function (button) {
      button.addEventListener('click', function () {
        const target = button.getAttribute('data-target');

        buttons.forEach(function (btn) {
          btn.classList.remove('active');
          btn.setAttribute('aria-selected', 'false');
        });

        button.classList.add('active');
        button.setAttribute('aria-selected', 'true');

        panels.forEach(function (panel) {
          if ('#' + panel.id === target) {
            panel.classList.add('active');
          } else {
            if (panel.tagName.toLowerCase() === 'video') {
              try { panel.pause(); } catch (e) {}
            }
            panel.classList.remove('active');
          }
        });
      });
    });
  });
</script>

<p>In contemporary Data Science, an established paradigm governs model selection: Deep Learning architectures dominate unstructured modalities such as computer vision and natural language processing, whereas gradient-boosted decision trees (GBDTs)—including <strong>XGBoost</strong>, <strong>LightGBM</strong>, and <strong>CatBoost</strong>—remain the gold standard for structured tabular datasets.</p>

<p>However, recent advancements in deep learning have challenged this dichotomy through the introduction of <strong>spatial encoding techniques</strong>. By transforming tabular features into synthetic multi-dimensional images, researchers can leverage the structural inductive biases of advanced computer vision networks, such as Convolutional Neural Networks (CNNs) and Vision Transformers (ViTs). This article provides a comprehensive theoretical and practical introduction to <strong>TINTOlib</strong>, the state-of-the-art Python library designed to streamline this transformation pipeline.</p>

<h2 id="the-theoretical-framework-why-map-tables-to-images">The Theoretical Framework: Why Map Tables to Images?</h2>

<p>The primary impediment to directly applying CNNs to tabular datasets is the <strong>absence of spatial locality</strong>. In natural images, adjacent pixels exhibit strong semantic and structural correlations (e.g., forming edges, textures, and continuous geometric shapes). Conversely, rows and columns in a standard tabular matrix possess an arbitrary ordering; swapping column 2 and column $d$ changes the array indices but preserves the underlying data semantics. This structural format violates the <strong>spatial inductive bias</strong>—specifically, translation invariance and locality—upon which convolutional filters rely.</p>

<p>To overcome this limitation, spatial encoding methodologies project the feature space onto a discrete 2D coordinate system. Features that exhibit strong statistical correlations or mutual dependencies are mapped to proximal spatial coordinates within a synthetic “canvas,” generating a <strong>synthetic pseudo-image</strong>.</p>

<p><img src="/images/Blog/2026-05-27-01-introduction-to-tintolib-tabular-to-image.png" alt="Tabular Data into Synthetic Images Methodology" />
<em>(Figure 1: Conceptual diagram illustrating the topological mapping of tabular feature vectors into a structured 2D pixel grid via TINTOlib spatial encoding).</em></p>

<p>Through this transformation, the vision model’s convolutional kernels can extract higher-order hierarchical feature interactions. Furthermore, this paradigm shifts tabular analysis from black-box numeric mapping to a visual space, enabling the direct integration of post-hoc Explainable AI (XAI) frameworks—such as <strong>Grad-CAM</strong>, <strong>SHAP</strong>, or <strong>PermGrad</strong>—to visually interpret feature attributions via saliency maps.</p>

<h2 id="what-is-tintolib">What is TINTOlib?</h2>

<p><strong><a href="https://github.com/oeg-upm/TINTOlib">TINTOlib</a></strong> is an open-source Python framework that unifies a comprehensive suite of state-of-the-art tabular-to-image transformation algorithms under a single, cohesive, Scikit-Learn-compliant interface. For a comprehensive overview of the framework’s capabilities, consult the official <strong><a href="https://tintolib.readthedocs.io/en/latest/">TINTOlib Documentation</a></strong>.</p>

<p>Historically, evaluating different spatial encoding strategies required integrating disjointed, unstandardized repositories written across varying programming languages. TINTOlib resolves this fragmentation by categorizing and implementing both parametric and non-parametric approaches. While this tutorial focuses on the <strong>TINTO</strong> method (which utilizes manifold learning techniques like t-SNE or Principal Component Analysis to determine spatial feature positions), the library allows researchers to pivot to <strong>any other methodology</strong>—such as <strong>IGTD</strong>, <strong>REFINED</strong>, <strong>SuperTML</strong>, <strong>BarGraph</strong>, or <strong>Binary Image Encoding (BIE)</strong>—by modifying a single line of code, ensuring a seamless benchmarking experience.</p>

<h2 id="practical-implementation-building-your-first-pytorch-cnn-pipeline">Practical Implementation: Building Your First PyTorch CNN Pipeline</h2>

<p>To demonstrate the efficacy of this paradigm, we will construct an end-to-end classification pipeline. To ensure strict reproducibility, we will utilize the standard <em>Breast Cancer Wisconsin</em> dataset from Scikit-Learn.</p>

<h3 id="1-spatial-encoding-and-data-leakage-mitigation">1. Spatial Encoding and Data Leakage Mitigation</h3>

<p>A critical methodological vulnerability in spatial encoding is <strong>Data Leakage</strong>. Algorithms that learn the optimal spatial arrangement of pixels based on feature similarities (such as t-SNE, PCA, or distance-matrix optimizations) must <strong>never</strong> be exposed to the validation or testing partitions during the fitting phase. Doing so allows the topological properties of the unseen test data to influence the coordinate mapping, invalidating subsequent generalization metrics.</p>

<p>To ensure rigorous validation, the pipeline must strictly segregate data prior to mapping: call <code class="language-plaintext highlighter-rouge">.fit()</code> exclusively on the training partition to define the spatial configuration, and subsequently apply <code class="language-plaintext highlighter-rouge">.transform()</code> to generate the synthetic images for both sets independently.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">pandas</span> <span class="k">as</span> <span class="n">pd</span>
<span class="kn">from</span> <span class="nn">sklearn.datasets</span> <span class="kn">import</span> <span class="n">load_breast_cancer</span>
<span class="kn">from</span> <span class="nn">sklearn.model_selection</span> <span class="kn">import</span> <span class="n">train_test_split</span>
<span class="kn">from</span> <span class="nn">TINTOlib.tinto</span> <span class="kn">import</span> <span class="n">TINTO</span>

<span class="c1"># 1. Load the structured tabular dataset (Breast Cancer - 30 numeric features)
</span><span class="n">raw_data</span> <span class="o">=</span> <span class="n">load_breast_cancer</span><span class="p">()</span>
<span class="n">df</span> <span class="o">=</span> <span class="n">pd</span><span class="p">.</span><span class="n">DataFrame</span><span class="p">(</span><span class="n">raw_data</span><span class="p">.</span><span class="n">data</span><span class="p">,</span> <span class="n">columns</span><span class="o">=</span><span class="n">raw_data</span><span class="p">.</span><span class="n">feature_names</span><span class="p">)</span>
<span class="c1"># Append the target variable as the final column, conforming to tidy data standards
</span><span class="n">df</span><span class="p">[</span><span class="s">'target'</span><span class="p">]</span> <span class="o">=</span> <span class="n">raw_data</span><span class="p">.</span><span class="n">target</span> 

<span class="c1"># 2. Partition the dataset to guarantee strict validation boundaries
</span><span class="n">X_train</span><span class="p">,</span> <span class="n">X_test</span> <span class="o">=</span> <span class="n">train_test_split</span><span class="p">(</span><span class="n">df</span><span class="p">,</span> <span class="n">test_size</span><span class="o">=</span><span class="mf">0.2</span><span class="p">,</span> <span class="n">random_state</span><span class="o">=</span><span class="mi">42</span><span class="p">)</span>

<span class="c1"># 3. Instantiate the spatial encoder (TINTO via t-SNE optimization, outputting a 20x20 canvas)
# Note: To alternate methods, simply import and instantiate IGTD or REFINED here.
</span><span class="n">encoder</span> <span class="o">=</span> <span class="n">TINTO</span><span class="p">(</span><span class="n">problem</span><span class="o">=</span><span class="s">"supervised"</span><span class="p">,</span> <span class="n">algorithm</span><span class="o">=</span><span class="s">"t-SNE"</span><span class="p">,</span> <span class="n">pixels</span><span class="o">=</span><span class="mi">20</span><span class="p">,</span> <span class="n">blur</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>

<span class="c1"># 4. Fit the spatial coordinate mapping matrix ONLY using the training split
</span><span class="n">encoder</span><span class="p">.</span><span class="n">fit</span><span class="p">(</span><span class="n">X_train</span><span class="p">)</span>

<span class="c1"># 5. Transform the tabular matrices into synthetic image repositories
</span><span class="n">encoder</span><span class="p">.</span><span class="n">transform</span><span class="p">(</span><span class="n">X_train</span><span class="p">,</span> <span class="s">"synthetic_dataset/train/"</span><span class="p">)</span>
<span class="n">encoder</span><span class="p">.</span><span class="n">transform</span><span class="p">(</span><span class="n">X_test</span><span class="p">,</span> <span class="s">"synthetic_dataset/test/"</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="s">"Transformation completed successfully. Spatial representations isolated."</span><span class="p">)</span>
</code></pre></div></div>

<div style="display: flex; gap: 0.75rem; justify-content: center; flex-wrap: wrap; margin: 1.5rem 0 0.5rem;">
  <img src="/images/Blog/synthetic_images/2026-05-27-01_TINTO1.png" alt="Synthetic image 1 generated by TINTOlib TINTO with blurring" style="width: 120px; height: 120px; object-fit: contain; border: 1px solid #e2e8f0; border-radius: 4px; background: #f8fafc;" />
  <img src="/images/Blog/synthetic_images/2026-05-27-01_TINTO2.png" alt="Synthetic image 2 generated by TINTOlib TINTO with blurring" style="width: 120px; height: 120px; object-fit: contain; border: 1px solid #e2e8f0; border-radius: 4px; background: #f8fafc;" />
  <img src="/images/Blog/synthetic_images/2026-05-27-01_TINTO3.png" alt="Synthetic image 3 generated by TINTOlib TINTO with blurring" style="width: 120px; height: 120px; object-fit: contain; border: 1px solid #e2e8f0; border-radius: 4px; background: #f8fafc;" />
</div>
<p><em>(Figure 2. Synthetic image samples generated with TINTOlib using the TINTO method with blurring. Each image corresponds to an individual tabular instance from the Breast Cancer Wisconsin dataset and encodes its feature values into a two-dimensional spatial representation. The resulting intensity patterns can be processed by CNN-based and hybrid neural architectures.)</em></p>

<h3 id="2-architectural-specification-in-pytorch">2. Architectural Specification in PyTorch</h3>

<p>Following image generation, we construct a standard Convolutional Neural Network tailored to ingest the single-channel (grayscale) $20 \times 20$ pixel representations yielded by TINTOlib.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">torch</span>
<span class="kn">import</span> <span class="nn">torch.nn</span> <span class="k">as</span> <span class="n">nn</span>
<span class="kn">import</span> <span class="nn">torch.nn.functional</span> <span class="k">as</span> <span class="n">F</span>

<span class="k">class</span> <span class="nc">TabularCNN</span><span class="p">(</span><span class="n">nn</span><span class="p">.</span><span class="n">Module</span><span class="p">):</span>
    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">num_classes</span><span class="o">=</span><span class="mi">2</span><span class="p">):</span>
        <span class="nb">super</span><span class="p">(</span><span class="n">TabularCNN</span><span class="p">,</span> <span class="bp">self</span><span class="p">).</span><span class="n">__init__</span><span class="p">()</span>
        
        <span class="c1"># Convolutional Block 1: Input channels = 1, Output feature maps = 32
</span>        <span class="bp">self</span><span class="p">.</span><span class="n">conv1</span> <span class="o">=</span> <span class="n">nn</span><span class="p">.</span><span class="n">Conv2d</span><span class="p">(</span><span class="n">in_channels</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span> <span class="n">out_channels</span><span class="o">=</span><span class="mi">32</span><span class="p">,</span> <span class="n">kernel_size</span><span class="o">=</span><span class="mi">3</span><span class="p">,</span> <span class="n">padding</span><span class="o">=</span><span class="mi">1</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">pool1</span> <span class="o">=</span> <span class="n">nn</span><span class="p">.</span><span class="n">MaxPool2d</span><span class="p">(</span><span class="n">kernel_size</span><span class="o">=</span><span class="mi">2</span><span class="p">,</span> <span class="n">stride</span><span class="o">=</span><span class="mi">2</span><span class="p">)</span>
        
        <span class="c1"># Convolutional Block 2: Input channels = 32, Output feature maps = 64
</span>        <span class="bp">self</span><span class="p">.</span><span class="n">conv2</span> <span class="o">=</span> <span class="n">nn</span><span class="p">.</span><span class="n">Conv2d</span><span class="p">(</span><span class="n">in_channels</span><span class="o">=</span><span class="mi">32</span><span class="p">,</span> <span class="n">out_channels</span><span class="o">=</span><span class="mi">64</span><span class="p">,</span> <span class="n">kernel_size</span><span class="o">=</span><span class="mi">3</span><span class="p">,</span> <span class="n">padding</span><span class="o">=</span><span class="mi">1</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">pool2</span> <span class="o">=</span> <span class="n">nn</span><span class="p">.</span><span class="n">MaxPool2d</span><span class="p">(</span><span class="n">kernel_size</span><span class="o">=</span><span class="mi">2</span><span class="p">,</span> <span class="n">stride</span><span class="o">=</span><span class="mi">2</span><span class="p">)</span>
        
        <span class="c1"># Given a 20x20 input, two successive MaxPool reductions yield a 5x5 spatial size
</span>        <span class="bp">self</span><span class="p">.</span><span class="n">flatten</span> <span class="o">=</span> <span class="n">nn</span><span class="p">.</span><span class="n">Flatten</span><span class="p">()</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">fc1</span> <span class="o">=</span> <span class="n">nn</span><span class="p">.</span><span class="n">Linear</span><span class="p">(</span><span class="mi">64</span> <span class="o">*</span> <span class="mi">5</span> <span class="o">*</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">128</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">fc2</span> <span class="o">=</span> <span class="n">nn</span><span class="p">.</span><span class="n">Linear</span><span class="p">(</span><span class="mi">128</span><span class="p">,</span> <span class="n">num_classes</span><span class="p">)</span>

    <span class="k">def</span> <span class="nf">forward</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">x</span><span class="p">):</span>
        <span class="c1"># Block 1 forward pass
</span>        <span class="n">x</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">pool1</span><span class="p">(</span><span class="n">F</span><span class="p">.</span><span class="n">relu</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">conv1</span><span class="p">(</span><span class="n">x</span><span class="p">)))</span>
        <span class="c1"># Block 2 forward pass
</span>        <span class="n">x</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">pool2</span><span class="p">(</span><span class="n">F</span><span class="p">.</span><span class="n">relu</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">conv2</span><span class="p">(</span><span class="n">x</span><span class="p">)))</span>
        <span class="c1"># Classification head
</span>        <span class="n">x</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">flatten</span><span class="p">(</span><span class="n">x</span><span class="p">)</span>
        <span class="n">x</span> <span class="o">=</span> <span class="n">F</span><span class="p">.</span><span class="n">relu</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">fc1</span><span class="p">(</span><span class="n">x</span><span class="p">))</span>
        <span class="n">x</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">fc2</span><span class="p">(</span><span class="n">x</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">x</span>

<span class="c1"># Instantiate the model architecture for binary classification
</span><span class="n">vision_model</span> <span class="o">=</span> <span class="n">TabularCNN</span><span class="p">(</span><span class="n">num_classes</span><span class="o">=</span><span class="mi">2</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">vision_model</span><span class="p">)</span>
</code></pre></div></div>

<p>The generated synthetic images stored in <code class="language-plaintext highlighter-rouge">synthetic_dataset/</code> can be seamlessly loaded via PyTorch’s standard <code class="language-plaintext highlighter-rouge">torchvision.datasets.ImageFolder</code> class combined with a <code class="language-plaintext highlighter-rouge">DataLoader</code> loop, optimizing models using typical criteria like <code class="language-plaintext highlighter-rouge">CrossEntropyLoss</code> or <code class="language-plaintext highlighter-rouge">BCEWithLogitsLoss</code>.</p>

<h2 id="conclusion-and-open-horizons">Conclusion and Open Horizons</h2>

<p>Transforming tabular features into synthetic spatial layouts offers a compelling methodology to bridge classical data problems with state-of-the-art visual architectures. Utilizing <strong>TINTOlib</strong> provides data scientists with a rigorous, reproducible, and standardized framework to systematically test, compare, and scale these transformations while avoiding common pitfalls such as data leakage.</p>

<p>In subsequent entries, we will delve deeper into benchmarking comparative analysis (e.g., non-parametric IGTD vs. parallelized REFINED), evaluating performance shifts when deploying Vision Transformers (ViTs), and rendering feature importance maps through advanced XAI methods.</p>

<h2 id="references-and-related-publications">References and related publications</h2>

<p>The concepts presented in this tutorial are connected to the following research and software publications on TINTO, TINTOlib, tabular-to-image transformation, synthetic spatial representations, hybrid neural networks, and indoor localisation.</p>

<h3 id="research-articles">Research articles</h3>

<ol>
  <li>
    <p>Jiayun Liu, Manuel Castillo-Cara et al. <strong>Interpretable Hybrid Vision Transformer Architectures for MIMO-Based Indoor Localization using Synthetic Spatial Representations</strong>. <em>IEEE Internet of Things</em>. DOI: <a href="https://doi.org/10.1109/JIOT.2026.3696106">10.1109/JIOT.2026.3696106</a></p>
  </li>
  <li>
    <p>Jiayun Liu, Manuel Castillo-Cara et al. <strong>A Comprehensive Benchmark of Spatial Encoding Methods for Tabular Data with Deep Neural Networks</strong>. <em>Information Fusion</em>. DOI: <a href="https://doi.org/10.1016/j.inffus.2025.104088">10.1016/j.inffus.2025.104088</a></p>
  </li>
  <li>
    <p>Giovanny Mondragon-Ruiz, Jiayun Liu, Manuel Castillo-Cara et al. <strong>Interpretable CNN–KAN hybrid architectures for tabular data with synthetic image encoding</strong>. <em>Information Processing and Management</em>. DOI: <a href="https://doi.org/10.1016/j.ipm.2026.104954">10.1016/j.ipm.2026.104954 </a></p>
  </li>
  <li>
    <p>Manuel Castillo-Cara et al. <strong>MIMO-Based Indoor Localisation with Hybrid Neural Networks</strong>. <em>IEEE Journal of Selected Topics in Signal Processing</em>. DOI: <a href="https://doi.org/10.1109/JSTSP.2025.3555067">10.1109/JSTSP.2025.3555067</a></p>
  </li>
  <li>
    <p>Reewos Talla-Chumpitaz, Manuel Castillo-Cara et al. <strong>Blurring Image Techniques for Bluetooth-based Indoor Localisation</strong>. <em>Information Fusion</em>. DOI: <a href="https://doi.org/10.1016/j.inffus.2022.10.011">10.1016/j.inffus.2022.10.011</a></p>
  </li>
</ol>

<h3 id="software-articles">Software articles</h3>

<ol>
  <li>
    <p>Jiayun Liu et al. <strong>TINTOlib: A Python library for transforming tabular data into synthetic images for deep neural networks</strong>. <em>SoftwareX</em>. DOI: <a href="https://doi.org/10.1016/j.softx.2025.102444">10.1016/j.softx.2025.102444</a></p>
  </li>
  <li>
    <p>Manuel Castillo-Cara et al. <strong>TINTO: Converting Tidy Data into Image for Classification with 2-Dimensional Convolutional Neural Networks</strong>. <em>SoftwareX</em>. DOI: <a href="https://doi.org/10.1016/j.softx.2023.101391">10.1016/j.softx.2023.101391</a></p>
  </li>
</ol>

<!-- ===== Blog Post Footer: CTA → Author → License → Export ===== -->
<style>
  /* Hide default Minimal Mistakes related posts (replaced by custom cards below) */
  .page__related { display: none !important; }

  /* ----- Copy-code button ----- */
  .page__content pre { position: relative; }
  .copy-code-btn {
    position: absolute;
    top: 0.35rem;
    right: 0.45rem;
    padding: 0.15rem 0.55rem;
    font-size: 0.72rem;
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
    background: #eff6ff;
    color: #1d4ed8;
    border: 1px solid #bfdbfe;
    border-radius: 4px;
    cursor: pointer;
    transition: background 0.15s, color 0.15s;
    line-height: 1.5;
    z-index: 2;
    user-select: none;
    font-weight: 600;
  }
  .copy-code-btn:hover { background: #dbeafe; color: #1e40af; border-color: #93c5fd; }
  .copy-code-btn.copied { background: #d1fae5; color: #065f46; border-color: #6ee7b7; }

  /* ----- Post footer wrapper ----- */
  .blog-post-footer {
    margin-top: 3rem;
    border-top: 2px solid #e2e8f0;
    padding-top: 2rem;
  }

  /* ----- CTA (first) ----- */
  .blog-cta {
    background: linear-gradient(135deg, #1565c0 0%, #6d28d9 100%);
    color: #fff;
    border-radius: 12px;
    padding: 1.4rem 1.75rem;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    margin-bottom: 1.5rem;
    box-shadow: 0 4px 18px rgba(21,101,192,0.22);
  }
  .blog-cta-text h3 { margin: 0 0 0.3rem; font-size: 1.05rem; color: #fff; }
  .blog-cta-text p  { margin: 0; font-size: 0.88rem; color: rgba(255,255,255,0.85); }
  .blog-cta-btn {
    display: inline-block;
    background: #fff;
    color: #1565c0 !important;
    font-weight: 800;
    font-size: 0.9rem;
    text-decoration: none !important;
    padding: 0.55rem 1.3rem;
    border-radius: 8px;
    white-space: nowrap;
    transition: box-shadow 0.15s, transform 0.1s;
  }
  .blog-cta-btn:hover { box-shadow: 0 4px 14px rgba(0,0,0,.22); transform: translateY(-1px); }

  /* ----- Author card (second) ----- */
  .blog-author-card {
    display: flex;
    align-items: center;
    gap: 0.85rem;
    background: #f8fafc;
    border: 1px solid #e2e8f0;
    border-radius: 12px;
    padding: 0.8rem 1.1rem;
    margin-bottom: 0.75rem;
    box-shadow: 0 2px 8px rgba(0,0,0,.05);
  }
  .blog-author-avatar {
    width: 58px;
    height: 76px;
    border-radius: 999px;
    object-fit: cover;
    flex-shrink: 0;
    border: 2px solid #c7d9f0;
    background: #e8edf5;
    box-shadow: 0 2px 8px rgba(0,0,0,.12);
  }
  .blog-author-info { flex: 1; min-width: 0; }
  .blog-author-card p { margin: 0; line-height: 1.25; }
  .blog-author-name { font-weight: 800; font-size: 0.95rem !important; color: #1f2937; }
  .blog-author-role { font-size: 0.82rem !important; color: #374151; }
  .blog-author-spec { font-size: 0.82rem !important; color: #374151; }
  .blog-author-dept { font-size: 0.78rem !important; color: #4b5563; }
  .blog-author-inst { font-size: 0.76rem !important; color: #6b7280; }

  /* ----- License (third) ----- */
  .blog-license {
    font-size: 0.81rem;
    color: #6b7280;
    background: #f1f5f9;
    border: 1px solid #e2e8f0;
    border-radius: 8px;
    padding: 0.55rem 1rem;
    margin-bottom: 1.1rem;
  }
  .blog-license a { color: #1976d2; }

  /* ----- Resource / export buttons (fourth) ----- */
  .blog-resources {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin-bottom: 0.5rem;
  }
  .blog-resource-btn {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.38rem 0.9rem;
    border-radius: 8px;
    font-size: 0.82rem;
    font-weight: 700;
    text-decoration: none !important;
    cursor: pointer;
    border: 1px solid transparent;
    transition: box-shadow 0.15s, transform 0.1s;
    line-height: 1.4;
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  }
  .blog-resource-btn:hover { box-shadow: 0 4px 12px rgba(0,0,0,.14); transform: translateY(-1px); }
  .blog-resource-btn--notebook { background: #fff3e0; color: #e65100 !important; border-color: #ffcc80; }
  .blog-resource-btn--pdf      { background: #fce4ec; color: #c62828 !important; border-color: #f48fb1; }
  .blog-resource-btn--print    { background: #e8f5e9; color: #2e7d32 !important; border-color: #a5d6a7; }
  .blog-resource-btn--save     { background: #e8eaf6; color: #283593 !important; border-color: #9fa8da; }
  .blog-resource-btn--python   { background: #fef9c3; color: #713f12 !important; border-color: #fde68a; }
  .blog-resource-btn--ipynb    { background: #fff7ed; color: #92400e !important; border-color: #fed7aa; }

  /* ----- Related posts grid ----- */
  .bpf-related { margin-top: 2rem; padding-top: 1.5rem; border-top: 1px solid #e2e8f0; }
  .bpf-related-title {
    font-size: 0.78rem;
    font-weight: 800;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: #6b7280;
    margin: 0 0 1rem;
  }
  .bpf-related-grid {
    display: grid;
    grid-template-columns: repeat(3, minmax(0,1fr));
    gap: 1rem;
  }
  @media (max-width: 860px) { .bpf-related-grid { grid-template-columns: repeat(2, minmax(0,1fr)); } }
  @media (max-width: 540px) { .bpf-related-grid { grid-template-columns: 1fr; } }
  .bpf-card {
    background: #f4f7fb;
    border: 1px solid #cfd8dc;
    border-radius: 12px;
    overflow: hidden;
    display: flex;
    flex-direction: column;
    box-shadow: 0 2px 8px rgba(0,0,0,.05);
    transition: box-shadow .15s, transform .12s;
    text-decoration: none !important;
  }
  .bpf-card:hover { box-shadow: 0 6px 20px rgba(0,0,0,.1); transform: translateY(-2px); }
  .bpf-card-thumb {
    width: 100%;
    aspect-ratio: 16/9;
    object-fit: cover;
    display: block;
    background: #e8edf5;
  }
  .bpf-card-body { padding: .75rem .9rem .9rem; display: flex; flex-direction: column; flex: 1; }
  .bpf-card-meta {
    display: flex; align-items: center; gap: .45rem;
    font-size: .78rem; color: #6b7280; margin-bottom: .35rem; flex-wrap: wrap;
  }
  .bpf-card-cat {
    background: #e0ecff; color: #0f3d8a;
    font-weight: 700; font-size: .72rem;
    padding: .12rem .45rem; border-radius: 999px;
  }
  .bpf-card-title {
    font-size: .95rem; font-weight: 800; color: #1565c0;
    line-height: 1.3; margin: 0 0 .45rem;
  }
  .bpf-card-title a { color: inherit; text-decoration: none; }
  .bpf-card-title a:hover { text-decoration: underline; }
  .bpf-card-tags { display: flex; flex-wrap: wrap; gap: .25rem; margin-bottom: .65rem; }
  .bpf-tag {
    background: #f1f5f9; border: 1px solid #cbd5e1;
    color: #6b7280; font-size: .72rem;
    padding: .1rem .4rem; border-radius: 999px;
  }
  .bpf-btn {
    display: inline-block;
    background: #1976d2; color: #fff !important;
    text-decoration: none !important;
    padding: .4rem .85rem; border-radius: 10px;
    font-weight: 800; font-size: .84rem; text-align: center;
    transition: background .15s, box-shadow .15s;
    align-self: flex-start; margin-top: auto;
  }
  .bpf-btn:hover { background: #0f60b6; box-shadow: 0 4px 12px rgba(0,0,0,.12); }

  @media (max-width: 560px) {
    .blog-author-card { flex-direction: column; align-items: center; text-align: center; }
    .blog-author-avatar { width: 52px; height: 68px; }
    .blog-cta { text-align: center; justify-content: center; }
    .blog-resources { justify-content: center; }
  }
</style>

<div class="blog-post-footer">

  <!-- 1. CTA -->
  <div class="blog-cta">
    <div class="blog-cta-text">
      <h3>Continue learning Artificial Intelligence</h3>
      <p>Explore practical courses on AI, Machine Learning, Deep Learning, Python, R and applied data science.</p>
    </div>
    <a class="blog-cta-btn" href="https://www.manuelcastillo.eu/udemy/" target="_blank" rel="noopener noreferrer">View AI Courses &rarr;</a>
  </div>

  <!-- 2. Author card -->
  <div class="blog-author-card">
    <img class="blog-author-avatar" src="/images/profile.jpg" alt="Manuel Castillo-Cara" onerror="this.style.display='none'" />
    <div class="blog-author-info">
      <p class="blog-author-name">Manuel Castillo-Cara, PhD</p>
      <p class="blog-author-spec">TINTOlib Python Library Developer</p>
      <p class="blog-author-role">Researcher &amp; Professor</p>
      <p class="blog-author-dept">Department of Artificial Intelligence</p>
      <p class="blog-author-inst">Universidad Nacional de Educación a Distancia (UNED)</p>
      <p class="blog-author-inst">Almerimar (Almería), Spain</p>
    </div>
  </div>

  <!-- 3. License -->
  <p class="blog-license">
    &copy; Manuel Castillo-Cara, PhD. Content licensed under
    <a href="https://creativecommons.org/licenses/by-nc/4.0/" target="_blank" rel="noopener noreferrer">CC BY-NC 4.0</a>
    unless otherwise stated.
  </p>

  <!-- 4. Export / resource buttons -->
  <div class="blog-resources">
    
    
    <button type="button" class="blog-resource-btn blog-resource-btn--print" onclick="window.print()">
      🖨️ Print / Save as PDF
    </button>
    <button type="button" class="blog-resource-btn blog-resource-btn--save" id="btn-save-html">
      💾 Save as HTML
    </button>
    <button type="button" class="blog-resource-btn blog-resource-btn--python" id="btn-download-py">
      🐍 Download Python
    </button>
    <button type="button" class="blog-resource-btn blog-resource-btn--ipynb" id="btn-download-ipynb">
      📓 Notebook (.ipynb)
    </button>
  </div>

  <!-- 5. Related posts -->
  
  
    <div class="bpf-related">
      <p class="bpf-related-title">You may also enjoy</p>
      <div class="bpf-related-grid">
        
          
          
          <article class="bpf-card">
            
              <img class="bpf-card-thumb" src="/images/Blog/2026-06-02-04-xai-hynn.png" alt="Improving Deep Learning by Exploiting Synthetic Images — Part II: From Synthetic Images to Hybrid Neural Networks" loading="lazy" />
            
            <div class="bpf-card-body">
              <div class="bpf-card-meta">
                <time datetime="2026-06-03T00:00:00+02:00">03 Jun 2026</time>
                <span class="bpf-card-cat">TINTOlib</span>
              </div>
              <h3 class="bpf-card-title">
                <a href="/blog/2026/06/04-from-synthetic-images-to-hybrid-neural-networks/">Improving Deep Learning by Exploiting Synthetic Images — Part II: From Synthetic Images to Hybrid Neural Networks</a>
              </h3>
              
                <div class="bpf-card-tags">
                  
                    <span class="bpf-tag">TINTOlib</span>
                  
                    <span class="bpf-tag">Synthetic Images</span>
                  
                    <span class="bpf-tag">Tabular-to-Image</span>
                  
                    <span class="bpf-tag">Hybrid Neural Networks</span>
                  
                </div>
              
              <a class="bpf-btn" href="/blog/2026/06/04-from-synthetic-images-to-hybrid-neural-networks/">Read article →</a>
            </div>
          </article>
        
          
          
          <article class="bpf-card">
            
              <img class="bpf-card-thumb" src="/images/Blog/2026-06-02-03-improving-deep-learning-exploiting-synthetic-images.png" alt="Improving Deep Learning by Exploiting Synthetic Images — Part I: Why Tabular Data Needs Spatial Representations" loading="lazy" />
            
            <div class="bpf-card-body">
              <div class="bpf-card-meta">
                <time datetime="2026-06-02T00:00:00+02:00">02 Jun 2026</time>
                <span class="bpf-card-cat">TINTOlib</span>
              </div>
              <h3 class="bpf-card-title">
                <a href="/blog/2026/06/03-improving-deep-learning-exploiting-synthetic-images-part-1/">Improving Deep Learning by Exploiting Synthetic Images — Part I: Why Tabular Data Needs Spatial Representations</a>
              </h3>
              
                <div class="bpf-card-tags">
                  
                    <span class="bpf-tag">TINTOlib</span>
                  
                    <span class="bpf-tag">Synthetic Images</span>
                  
                    <span class="bpf-tag">Tabular-to-Image</span>
                  
                    <span class="bpf-tag">Tabular Data</span>
                  
                </div>
              
              <a class="bpf-btn" href="/blog/2026/06/03-improving-deep-learning-exploiting-synthetic-images-part-1/">Read article →</a>
            </div>
          </article>
        
          
          
          <article class="bpf-card">
            
              <img class="bpf-card-thumb" src="/images/Blog/2026-05-28-02-what-is-it-tintolib-synthetic-images.png" alt="What is TINTOlib and why should we transform tabular data into synthetic images?" loading="lazy" />
            
            <div class="bpf-card-body">
              <div class="bpf-card-meta">
                <time datetime="2026-05-28T00:00:00+02:00">28 May 2026</time>
                <span class="bpf-card-cat">TINTOlib</span>
              </div>
              <h3 class="bpf-card-title">
                <a href="/blog/2026/05/02-what-is-it-tintolib-synthetic-images/">What is TINTOlib and why should we transform tabular data into synthetic images?</a>
              </h3>
              
                <div class="bpf-card-tags">
                  
                    <span class="bpf-tag">TINTOlib</span>
                  
                    <span class="bpf-tag">Tabular Data</span>
                  
                    <span class="bpf-tag">Synthetic Images</span>
                  
                    <span class="bpf-tag">Deep Learning</span>
                  
                </div>
              
              <a class="bpf-btn" href="/blog/2026/05/02-what-is-it-tintolib-synthetic-images/">Read article →</a>
            </div>
          </article>
        
      </div>
    </div>
  

</div>

<!-- ===== Copy-code + Export logic ===== -->
<script>
(function () {

  /* ---------- helpers ---------- */
  function slugify(str) {
    return (str || 'blog-post')
      .replace(/[^a-z0-9\-_]/gi, '-')
      .replace(/-+/g, '-')
      .toLowerCase()
      .substring(0, 60)
      .replace(/^-+|-+$/g, '');
  }

  function download(content, filename, mime) {
    var blob = new Blob([content], { type: mime });
    var url  = URL.createObjectURL(blob);
    var a    = document.createElement('a');
    a.href = url;
    a.download = filename;
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
    setTimeout(function () { URL.revokeObjectURL(url); }, 5000);
  }

  function getCodeBlocks() {
    var nodes = document.querySelectorAll('.page__content pre code');
    var blocks = [];
    nodes.forEach(function (node) {
      var text = node.textContent.trimEnd();
      if (text) blocks.push(text);
    });
    return blocks;
  }

  /* ---------- Copy-code buttons ---------- */
  var pres = document.querySelectorAll('.page__content pre');
  pres.forEach(function (pre) {
    var code = pre.querySelector('code');
    var getText = function () {
      if (code) return code.textContent;
      var clone = pre.cloneNode(true);
      clone.querySelectorAll('.copy-code-btn').forEach(function (b) { b.parentNode.removeChild(b); });
      return clone.textContent;
    };
    var btn = document.createElement('button');
    btn.type = 'button';
    btn.className = 'copy-code-btn';
    btn.textContent = 'Copy';
    btn.setAttribute('aria-label', 'Copy code to clipboard');
    pre.appendChild(btn);
    btn.addEventListener('click', function () {
      var text = getText();
      if (navigator.clipboard && navigator.clipboard.writeText) {
        navigator.clipboard.writeText(text).then(function () { flash(btn); });
      } else {
        var ta = document.createElement('textarea');
        ta.value = text;
        ta.style.cssText = 'position:fixed;opacity:0;top:0;left:0';
        document.body.appendChild(ta);
        ta.focus(); ta.select();
        try { document.execCommand('copy'); } catch (e) {}
        document.body.removeChild(ta);
        flash(btn);
      }
    });
  });
  function flash(btn) {
    btn.textContent = 'Copied \u2713';
    btn.classList.add('copied');
    setTimeout(function () { btn.textContent = 'Copy'; btn.classList.remove('copied'); }, 1600);
  }

  /* ---------- Save as HTML ---------- */
  var saveHtmlBtn = document.getElementById('btn-save-html');
  if (saveHtmlBtn) {
    saveHtmlBtn.addEventListener('click', function () {
      var clone = document.documentElement.cloneNode(true);
      var origin = window.location.origin;
      clone.querySelectorAll('img[src]').forEach(function (img) {
        var src = img.getAttribute('src');
        if (src && src.charAt(0) === '/') img.setAttribute('src', origin + src);
      });
      clone.querySelectorAll('link[href]').forEach(function (el) {
        var href = el.getAttribute('href');
        if (href && href.charAt(0) === '/') el.setAttribute('href', origin + href);
      });
      clone.querySelectorAll('script[src]').forEach(function (el) {
        var src = el.getAttribute('src');
        if (src && src.charAt(0) === '/') el.setAttribute('src', origin + src);
      });
      var html = '<!doctype html>\n' + clone.outerHTML;
      download(html, slugify(document.title) + '.html', 'text/html;charset=utf-8');
    });
  }

  /* ---------- Download Python ---------- */
  var pyBtn = document.getElementById('btn-download-py');
  if (pyBtn) {
    pyBtn.addEventListener('click', function () {
      var blocks = getCodeBlocks();
      var lines;
      if (blocks.length === 0) {
        lines = ['# No code blocks found in this post.\n'];
      } else {
        lines = [];
        blocks.forEach(function (block, i) {
          lines.push('# ---- Code block ' + (i + 1) + ' ----\n');
          lines.push(block + '\n\n');
        });
      }
      download(lines.join(''), slugify(document.title) + '.py', 'text/x-python;charset=utf-8');
    });
  }

  /* ---------- Download Notebook (.ipynb) — full article walker ---------- */
  var ipynbBtn = document.getElementById('btn-download-ipynb');
  if (ipynbBtn) {
    ipynbBtn.addEventListener('click', function () {

      function mdLines(text) {
        /* Split into source array as Jupyter expects (lines ending with \n except last) */
        var lines = text.split('\n');
        return lines.map(function (l, i) { return i < lines.length - 1 ? l + '\n' : l; });
      }

      function mkdCell(text) {
        var t = (text || '').trim();
        if (!t) return null;
        return { cell_type: 'markdown', metadata: {}, source: mdLines(t) };
      }

      function codeCell(text) {
        var t = (text || '').trimEnd();
        if (!t) return null;
        return { cell_type: 'code', execution_count: null, metadata: {}, outputs: [], source: mdLines(t) };
      }

      function nodeToMd(el) {
        var tag = el.tagName ? el.tagName.toLowerCase() : '';
        /* headings */
        if (/^h[1-6]$/.test(tag)) {
          var level = parseInt(tag.slice(1), 10);
          return Array(level + 1).join('#') + ' ' + el.textContent.trim();
        }
        /* paragraph */
        if (tag === 'p') return el.textContent.trim();
        /* blockquote */
        if (tag === 'blockquote') {
          return el.textContent.trim().split('\n').map(function (l) { return '> ' + l; }).join('\n');
        }
        /* lists */
        if (tag === 'ul' || tag === 'ol') {
          var items = [];
          el.querySelectorAll('li').forEach(function (li, i) {
            items.push((tag === 'ol' ? (i + 1) + '. ' : '- ') + li.textContent.trim());
          });
          return items.join('\n');
        }
        /* figure / img */
        if (tag === 'figure') {
          var img = el.querySelector('img');
          if (img) {
            var src = img.getAttribute('src') || '';
            if (src.charAt(0) === '/') src = window.location.origin + src;
            var alt = img.getAttribute('alt') || '';
            var cap = el.querySelector('figcaption');
            return '!['+ alt +']('+ src +')' + (cap ? '\n*' + cap.textContent.trim() + '*' : '');
          }
        }
        if (tag === 'img') {
          var src2 = el.getAttribute('src') || '';
          if (src2.charAt(0) === '/') src2 = window.location.origin + src2;
          return '![' + (el.getAttribute('alt') || '') + '](' + src2 + ')';
        }
        return '';
      }

      function getArticleCellsForNotebook() {
        var container = document.querySelector('.page__content');
        if (!container) return [];

        var clone = container.cloneNode(true);
        /* Remove footer, scripts, styles, copy buttons */
        ['script','style','.blog-post-footer','.copy-code-btn'].forEach(function (sel) {
          clone.querySelectorAll(sel).forEach(function (n) { n.parentNode.removeChild(n); });
        });

        var cells = [];
        var INLINE_TAGS = /^(h[1-6]|p|ul|ol|blockquote|figure|img)$/;

        function walk(node) {
          if (node.nodeType !== 1) return; /* element nodes only */
          var tag = node.tagName.toLowerCase();

          /* code block */
          if (tag === 'pre') {
            var code = node.querySelector('code');
            var text = (code ? code : node).textContent.trimEnd();
            var cell = codeCell(text);
            if (cell) cells.push(cell);
            return;
          }

          /* inline-mappable tags */
          if (INLINE_TAGS.test(tag)) {
            var md = nodeToMd(node);
            var cell2 = mkdCell(md);
            if (cell2) cells.push(cell2);
            return;
          }

          /* containers: div, section, article — recurse children */
          Array.prototype.forEach.call(node.childNodes, function (child) {
            walk(child);
          });
        }

        Array.prototype.forEach.call(clone.childNodes, function (child) {
          walk(child);
        });

        return cells;
      }

      var cells = [];
      /* Header cells */
      cells.push(mkdCell('# ' + (document.title || 'Blog Post')));
      var pageUrl = window.location.href;
      cells.push(mkdCell('**Source:** [' + pageUrl + '](' + pageUrl + ')'));

      /* Article body cells */
      var bodyCells = getArticleCellsForNotebook();
      if (bodyCells.length === 0) {
        cells.push(mkdCell('*No content extracted from this post.*'));
      } else {
        cells = cells.concat(bodyCells);
      }

      var nb = {
        nbformat: 4,
        nbformat_minor: 5,
        metadata: {
          kernelspec: { display_name: 'Python 3', language: 'python', name: 'python3' },
          language_info: { name: 'python', version: '3.x' }
        },
        cells: cells
      };

      download(JSON.stringify(nb, null, 2), slugify(document.title) + '.ipynb', 'application/json;charset=utf-8');
    });
  }

}());
</script>]]></content><author><name>Ph.D. Manuel Castillo-Cara</name><email>manwest.c@gmail.com</email></author><category term="TINTOlib" /><category term="TINTOlib" /><category term="Tabular-to-Image" /><category term="Synthetic Images" /><category term="Deep Learning" /><category term="CNN" /><category term="PyTorch" /><category term="Machine Learning" /><summary type="html"><![CDATA[Technical introduction to TINTOlib, a Python framework for transforming tabular data into synthetic images and applying CNN-based deep learning architectures.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.manuelcastillo.eu/images/Blog/2026-05-27-01-introduction-to-tintolib-tabular-to-image.png" /><media:content medium="image" url="https://www.manuelcastillo.eu/images/Blog/2026-05-27-01-introduction-to-tintolib-tabular-to-image.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>