<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>Leonard – IMG.LY Blog</title><description>Posts by Leonard on the IMG.LY blog.</description><link>https://img.ly/blog/author/leonard/</link><language>en-us</language><image><url>https://img.ly/apple-touch-icon.png</url><title>Leonard – IMG.LY Blog</title><link>https://img.ly/blog/author/leonard/</link></image><atom:link href="https://img.ly/blog/author/leonard/rss.xml" rel="self" type="application/rss+xml"/><generator>Astro</generator><lastBuildDate>Fri, 19 Jun 2026 11:25:53 GMT</lastBuildDate><ttl>60</ttl><item><title>A Remote Data Aggregation Pipeline to Provide Machine Learning Datasets</title><link>https://img.ly/blog/data-aggregation/</link><guid isPermaLink="true">https://img.ly/blog/data-aggregation/</guid><description>This blog explores how we aggregate datasets remotely, keep track of already generated datasets, and download datasets from a remote server using a generalizable architecture pattern.</description><pubDate>Fri, 16 Apr 2021 15:55:59 GMT</pubDate><content:encoded>&lt;p&gt;In our NRW.EFRE funded research project &lt;a href=&quot;https://kidesign.img.ly/&quot;&gt;KI Design&lt;/a&gt;, we spent a lot of time in training and testing convolutional networks together with our fellows from the &lt;a href=&quot;http://www.bo-i-t.de/&quot;&gt;Bochumer Institute of Technology (BO-I-T)&lt;/a&gt;. Our goal: Using Artificial Intelligence (AI) to make photo editing more comprehensive and easier at the same time. Details about the project and the motivation behind it, and some results can be found on our &lt;a href=&quot;https://kidesign.img.ly&quot;&gt;project homepage&lt;/a&gt; and in this &lt;a href=&quot;https://img.ly/blog/image-inpainting/&quot;&gt;blog post&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Figure 1: Example of an AI alpha-matted image of a wood duck.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 2000px) 2000px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;2000&quot; height=&quot;721&quot; src=&quot;https://img.ly/_astro/Matting_example_sW2Wq.webp&quot; srcset=&quot;/_astro/Matting_example_ZESjaN.webp 640w, /_astro/Matting_example_ZDYIuA.webp 750w, /_astro/Matting_example_Z136yJu.webp 828w, /_astro/Matting_example_2bLPjB.webp 1080w, /_astro/Matting_example_ZkqN8b.webp 1280w, /_astro/Matting_example_Z70KUf.webp 1668w, /_astro/Matting_example_sW2Wq.webp 2000w&quot;&gt;&lt;/p&gt;
&lt;p&gt;Today, we want to share details about our custom-made approach for a remote data aggregation and data transfer pipeline that we developed to support seamless integration of data preprocessing and storage into the training procedure. We think that this topic is of interest to the machine learning community, because the generation, versioning, and handling of datasets for the training of machine learning algorithms constitute a challenge for many researchers and developers.&lt;/p&gt;
&lt;h3 id=&quot;introduction&quot;&gt;Introduction&lt;/h3&gt;
&lt;p&gt;A major challenge in nearly every project in which machine learning and deep learning are applied, is set in the data preparation and augmentation for the training process. As nowadays many approaches and algorithms are data-driven, having training data in the right amount and quality even can make the difference between a project’s success or failure. This also includes a well-organized data infrastructure to store data, possibly in different versions of datasets.&lt;/p&gt;
&lt;p&gt;In our joint project KI Design, we face the setting that our server cluster is split up geographically, having a data storage server at the BO-I-T laboratory and a computing server at the IMG.LY site. This, of course, makes the design of a training pipeline a bit more demanding in how to efficiently use resources. After searching for an existing software solution, we decided to develop our custom-made approach, adapted to our requirements that we formulated as follows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The efficient workflow between data (pre)processing, storage, and provisioning on one side, as well as training initiation and execution on the other side&lt;/li&gt;
&lt;li&gt;A possibility to requests data preparations and augmentations based on configurations generated at the training side&lt;/li&gt;
&lt;li&gt;Versioning of datasets or configurations to ensure reproducibility&lt;/li&gt;
&lt;li&gt;Availability of already computed datasets to omit preparation and processing of equal data requests multiple times&lt;/li&gt;
&lt;li&gt;A notification process implemented on the data server to signal the availability of a dataset and trigger download as well as training processes to the computation side&lt;/li&gt;
&lt;li&gt;Integration into a TensorFlow training pipeline&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;our-custom-made-approach---overview&quot;&gt;Our Custom-made Approach - Overview&lt;/h3&gt;
&lt;p&gt;In this part, we briefly sketch our concept, before diving into our technical implementation in the next part. To train new models on our work-thirsty computing server at IMG.LY, we need training data. As mentioned before, data is stored on the data server at BO-I-T. We work on different tasks with different experiments in our project, thus we need to perform several different pieces of training of machine learning algorithms, many of which require different datasets.&lt;/p&gt;
&lt;p&gt;A dataset can be created in a data preparation process. In our context, the data preparation can be based on one of several “raw datasets” such as the &lt;a href=&quot;https://cocodataset.org/#home&quot;&gt;COCO&lt;/a&gt;, the &lt;a href=&quot;http://saliencydetection.net/duts/&quot;&gt;DUTS&lt;/a&gt;, or some self-assembled datasets and might include data pre-formatting (e.g. adjusting image size or section) and augmentation (e.g. image rotation, brightness adjustment, or combining foreground subjects with different backgrounds). Our idea was to use the data server for the whole process of data preparation and augmentation, to not waste valuable resources on the client for this.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Figure 2: Example of an image data preparation process. Left: raw image data; top right: cropped and flipped image; bottom right: cropped and rotated image with adjusted exposure.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 2000px) 2000px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;2000&quot; height=&quot;1057&quot; src=&quot;https://img.ly/_astro/Augmentation_example-2_yGquC.webp&quot; srcset=&quot;/_astro/Augmentation_example-2_1292sz.webp 640w, /_astro/Augmentation_example-2_1YQ7yv.webp 750w, /_astro/Augmentation_example-2_1TJESd.webp 828w, /_astro/Augmentation_example-2_vt6fU.webp 1080w, /_astro/Augmentation_example-2_1LJEyq.webp 1280w, /_astro/Augmentation_example-2_Bxbj.webp 1668w, /_astro/Augmentation_example-2_yGquC.webp 2000w&quot;&gt;&lt;/p&gt;
&lt;p&gt;The pre-training data aggregation phase can be described like that:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The client on the computing server should be able to request datasets from the data server. To do so, the client should transmit a (parameter) description of an exact configuration of the required dataset (image type and resolution size, as well as meta information) to the data server.&lt;/li&gt;
&lt;li&gt;Waiting for the requested dataset to be generated and provided, the computing server can spend its resources into other scheduled training jobs. (Computation time is money!)&lt;/li&gt;
&lt;li&gt;Meanwhile, the data server checks if the requested dataset is already existent — prepared from a previous request — or whether a new data generation process needs to be launched.&lt;/li&gt;
&lt;li&gt;If a dataset is available and ready to be downloaded, either directly after the request (because it was already created in a previous request with the exact same configuration) or after the time it took to create the new dataset on the server, it sends back a notification message to the client.&lt;/li&gt;
&lt;li&gt;Receiving this response, the computing server can download the dataset and initiate the training process.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;To implement this concept, we set up an architecture that is organized by three services, c.f. Figure 3:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;a &lt;em&gt;dataset-client&lt;/em&gt;,&lt;/li&gt;
&lt;li&gt;a &lt;em&gt;dataset-server&lt;/em&gt; and&lt;/li&gt;
&lt;li&gt;a &lt;em&gt;dataset-handler&lt;/em&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://lh3.googleusercontent.com/6ef2FEye8FD5EtWNXYVzqspJwpHA9gTcLXZpKJ32S-Qha5BjTlJzVX78yVVCQuo5zHlkXqJgd2TJFQDH7-rv4BP_PMdUnNcDew8YH3Br8pVTpAeRrRVGrP1B4hTpr9SEqJi6Rj1b&quot; alt=&quot;Figure 3: Schema of the data transfer process&quot;&gt;&lt;/p&gt;
&lt;p&gt;For our implementation, we developed a dataset-client service that provides the functionality required to cope with the client-side. The dataset-server service is the counterpart on the server-side. While the previous two components are pretty straightforward to understand, the third part, the dataset-handler, requires a little explanation: not only the implementation and training of machine learning models is part of our research project. With similar importance, we develop new strategies and approaches for data preparation. Thus, the server can not be provided with all preparation functions a-priori. Instead, it needs to be able to get to the required code for, e.g., new augmentation procedures and other algorithms, in their respective latest versions just before the preparation starts. The data-handler is the concept for this: it is the adjustable tool the server uses, to perform the data preparation.&lt;/p&gt;
&lt;h2 id=&quot;our-custom-made-approach---technical-implementation&quot;&gt;Our Custom-made Approach - Technical Implementation&lt;/h2&gt;
&lt;p&gt;In this part, we do not aim to provide a full description of our implementation. Instead, our goal is to give some insights into which frameworks we used and how we implemented the interfaces between the client, the data server, and the data handler.&lt;/p&gt;
&lt;h3 id=&quot;framework-and-languages&quot;&gt;Framework and Languages&lt;/h3&gt;
&lt;p&gt;To set up our remote data pipeline we used a combination of different tools, frameworks and programming languages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Apache2&lt;/li&gt;
&lt;li&gt;Node.js&lt;/li&gt;
&lt;li&gt;Python&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For our data server, we used Apache2 as a webserver to allow requests from outside and redirect them. Our REST API and WebSocket connection are set up in Node.js. Here we use the &lt;em&gt;Express.js&lt;/em&gt; and WebSocket libraries to handle the REST requests and establish a WebSocket connection. Further server-sided processes such as checking the availability of datasets by a hashtag, setting up a virtual environment (to be sure that all libraries are available on the server we set up a pipenv environment), and trigger the data handler configuration are written in Python. Here, we used libraries like hashlib, subprocess and zip file for the implementation as well as some other basic libraries.&lt;/p&gt;
&lt;p&gt;Besides the &lt;em&gt;data-server&lt;/em&gt; service, the &lt;em&gt;data-handler&lt;/em&gt; and the &lt;em&gt;data-client&lt;/em&gt; are built up in Python. Here we used libraries as requests, asyncio, and WebSocket to establish the client-sided connection with the data server. Further used libraries of the data handler strongly depend on the task to perform and thus vary a lot. Just to call some examples: we frequently use libraries as pillow, imageio, or OpenCV for image manipulation.&lt;/p&gt;
&lt;h3 id=&quot;client-side&quot;&gt;Client Side&lt;/h3&gt;
&lt;p&gt;The dataset client is running on the client and triggers the whole data aggregation process. It covers the following functionalities:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;requesting datasets,&lt;/li&gt;
&lt;li&gt;establishing a WebSocket connection,&lt;/li&gt;
&lt;li&gt;downloading aggregated datasets,&lt;/li&gt;
&lt;li&gt;starting the model training.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To get preprocessed training data from the server, a REST-based post request is sent from the client to the data server. This request includes a configuration, defining the exact dataset attributes, as well as meta information that specify the raw dataset that should be used for the preprocessing and the version of data-handler. Here is a code snippet showing the required post parameters and the data type:&lt;/p&gt;
&lt;table&gt;&lt;colgroup&gt;&lt;/colgroup&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;p dir=&quot;ltr&quot;&gt;&lt;span&gt;request_dataset(dataset_name: str, handler_version: str, dataset_config: Dict)&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The parameter &lt;em&gt;dataset_name&lt;/em&gt; and &lt;em&gt;handler_version&lt;/em&gt; are passed as strings and the config settings &lt;em&gt;dataset_config&lt;/em&gt; in a dictionary. Different handlers are assigned to different projects and are customized to their specific needs. Therefore, the config settings vary on each project. But, to get an idea of some configurations here is a (short) example:&lt;/p&gt;
&lt;table&gt;&lt;colgroup&gt;&lt;/colgroup&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;p dir=&quot;ltr&quot;&gt;&lt;span&gt;short_example_ config = {&lt;/span&gt;&lt;span&gt;&lt;br&gt;&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&apos;input_attributes&apos;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;&apos;image&apos;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;span&gt;&lt;br&gt;&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&apos;batch_size&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;100&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;br&gt;&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&apos;image_size&apos;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;1024&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;1024&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;span&gt;&lt;br&gt;&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&apos;variations_per_sample&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;br&gt;&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&apos;crop_size&apos;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;800&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;800&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;span&gt;&lt;br&gt;&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&apos;random_crop_centered&apos;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;True&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;br&gt;&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;It shows some basic parameters like, e.g., image type, image resolution as well as batch size, but also options of further operations as centered cropping. These settings serve as instructions for the &lt;em&gt;data_handler&lt;/em&gt; to create the dataset and are further fully customizable and adapted to a use case.&lt;/p&gt;
&lt;p&gt;Receiving a successful response of the post request, the dataset client will establish a WebSocket connection. This allows continuous communication between the client and the data server, which is important for a server-sided notification in case of the finished data preprocessing (as an alternative, we could have implemented a regularly scheduled client-side polling to check for dataset states). Here we depict an example of our &lt;em&gt;asyncio&lt;/em&gt; event loop command:&lt;/p&gt;
&lt;table&gt;&lt;colgroup&gt;&lt;/colgroup&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;p dir=&quot;ltr&quot;&gt;&lt;span&gt;asyncio.get_event_loop().run_until_complete(wait_for_completion(&lt;/span&gt;&lt;span&gt;&apos;topic_id&apos;&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;With this loop, the client waits until it receives a related response from the server. If a success token is returned, the client leaves the loop and starts to download the dataset and further, the actual training process (as soon as the training scheduler assigns resources, but this is a different story to tell).&lt;/p&gt;
&lt;h3 id=&quot;server-side&quot;&gt;Server Side&lt;/h3&gt;
&lt;p&gt;On the server-side, two services are running: the &lt;em&gt;dataset_server&lt;/em&gt; and the &lt;em&gt;dateset_handler&lt;/em&gt;. The &lt;em&gt;dataset_server&lt;/em&gt; handles the communication with the client and receives configuration requests as well as download requests. Furthermore, it checks the availability of datasets and if necessary triggers the &lt;em&gt;data_handler&lt;/em&gt; to run a dataset creation process. In summary, the &lt;em&gt;dataset_server&lt;/em&gt; covers the following functionalities:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;receiving request over REST service&lt;/li&gt;
&lt;li&gt;set up websocket communication protocol&lt;/li&gt;
&lt;li&gt;check for existing datasets&lt;/li&gt;
&lt;li&gt;install &lt;em&gt;dataset_handler&lt;/em&gt; and initialize dataset creation&lt;/li&gt;
&lt;li&gt;send notification to the client&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Using a REST-API as an entry point, the data server receives the post request of the client and checks, if the required dataset was already created. This check is being done by a string matching of a Universally Unique Identifier (UUID) of a configuration: Using an md5 hash, each request is converted to a special UUID, generated from the transferred dataset configurations. Information that is taken into account for the UUID/hash generation is the dataset name, the handler version as well as the md5 hash of import config settings. We set the dataset name and handler version in front of our md5 hash as it might be useful information in case we run out of disk space. Here is our function of the generation of the UUID.&lt;/p&gt;
&lt;table&gt;&lt;colgroup&gt;&lt;col width=&quot;601&quot;&gt;&lt;/colgroup&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;p dir=&quot;ltr&quot;&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;hash_dataset_name&lt;/span&gt;&lt;span&gt;(dataset_name: str, handler_version: str, dataset_config: str)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;&lt;br&gt;&lt;/span&gt;&lt;span&gt;&lt;br&gt;&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;h = hashlib.md5()&lt;/span&gt;&lt;span&gt;&lt;br&gt;&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;h.update(dataset_config.encode(&lt;/span&gt;&lt;span&gt;&quot;utf-8&quot;&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;span&gt;&lt;br&gt;&lt;/span&gt;&lt;span&gt;&lt;br&gt;&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;.join([dataset_name, &lt;/span&gt;&lt;span&gt;&quot;_&quot;&lt;/span&gt;&lt;span&gt;, handler_version, &lt;/span&gt;&lt;span&gt;&quot;_&quot;&lt;/span&gt;&lt;span&gt;, h.hexdigest(), &lt;/span&gt;&lt;span&gt;&quot;.zip&quot;&lt;/span&gt;&lt;span&gt;])&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;If the UUID matches with an existing dataset, a notification to the client will be returned, pointing to the related zip archive on the datastore. Otherwise, the &lt;em&gt;data_handler&lt;/em&gt; will be advised to start data preprocessing. However, before the generation process can be started, the correct &lt;em&gt;data_handler&lt;/em&gt; has to be downloaded and installed. Different &lt;em&gt;data_handler&lt;/em&gt; versions are stored in a GitHub repository, available to be downloaded from &lt;em&gt;data_server&lt;/em&gt;. This is dispatched by application of a Python integrated bash command, running a “&lt;em&gt;pip install&lt;/em&gt;”:&lt;/p&gt;
&lt;table&gt;&lt;colgroup&gt;&lt;/colgroup&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;p dir=&quot;ltr&quot;&gt;&lt;span&gt;subprocess.call([&lt;/span&gt;&lt;span&gt;&quot;pipenv&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&quot;run&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&quot;pip&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;install&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;--no-cache-dir&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;--upgrade&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;--process-dependency-links&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;git+https://github.com/imgly/dataset-handlers@{}#egg=dataset-handlers&quot;&lt;/span&gt;&lt;span&gt;.format(handler_version)])&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Note: We need pip in version 18 here because it is the last version supporting the “&lt;em&gt;—process-dependency-links&lt;/em&gt;” option, which ensures that the dependencies inside of dataset-handlersare installed. The &lt;em&gt;data_handler&lt;/em&gt; version is directly passed into the pip command, linking to the corresponding GitHub repository and handler release version.&lt;/p&gt;
&lt;h3 id=&quot;data-handler&quot;&gt;Data Handler&lt;/h3&gt;
&lt;p&gt;After successful installation of the &lt;em&gt;dataset_handler&lt;/em&gt;, we can simply import the handler in Python: &lt;em&gt;import dataset_handlers&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;As described earlier, the core functionality of the &lt;em&gt;data_handler&lt;/em&gt; is to create a dataset based on a given configuration and return zipped .tfrecord files. For those of you who are not familiar with the &lt;em&gt;.tfrecord&lt;/em&gt; file format: it is a special TensorFlow format for handling data as binary records in a sequence. This has the advantage of using less disc space, being faster in copying as well as being more efficient to read data from disk. But let’s go on with our &lt;em&gt;data_handler&lt;/em&gt;: Depending on the project and use case, the data handlers vary strongly and offer different methods. This makes an example a bit difficult at this point. But we can present some abstract methods of our class DatasetHandler():&lt;/p&gt;
&lt;p&gt;We start with the initialization method or constructor:&lt;/p&gt;
&lt;table&gt;&lt;colgroup&gt;&lt;/colgroup&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;p dir=&quot;ltr&quot;&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;DatasetHandler&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;&lt;br&gt;&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;__init__&lt;/span&gt;&lt;span&gt;(self, config: Dict[str, any], base_data_path: str)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;&lt;br&gt;&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;self.base_data_path = base_data_path&lt;/span&gt;&lt;span&gt;&lt;br&gt;&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; k, v &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; config.items():&lt;/span&gt;&lt;span&gt;&lt;br&gt;&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;setattr(self, k, v)&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The input variables from the client are passed into the constructor method and set as class attributes. Additionally, the base path of the raw data is required and set as an attribute. These are the main settings needed for data generation. Furthermore, the following methods are important for a generation process:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a static method keeping all allowed config operations,&lt;/li&gt;
&lt;li&gt;a method to create the correct raw data path and&lt;/li&gt;
&lt;li&gt;a method that returns a .tfrecord file list.&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;&lt;colgroup&gt;&lt;/colgroup&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;p dir=&quot;ltr&quot;&gt;&lt;span&gt;      def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;as_tfrecord&lt;/span&gt;&lt;span&gt;(self)&lt;/span&gt;&lt;span&gt; -&gt; List[str]:&lt;/span&gt;&lt;span&gt;&lt;br&gt;&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;pass&lt;/span&gt;&lt;span&gt;&lt;br&gt;&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;config_options&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; -&gt; Dict[str, Any]:&lt;/span&gt;&lt;span&gt;&lt;br&gt;&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;pass&lt;/span&gt;&lt;span&gt;&lt;br&gt;&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;tf_records_mapper&lt;/span&gt;&lt;span&gt;(self, files: List[str])&lt;/span&gt;&lt;span&gt; -&gt; tf.data.TFRecordDataset:&lt;/span&gt;&lt;span&gt;&lt;br&gt;&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;pass&lt;/span&gt;&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Finally, after successfully dataset creation, the server creates a .zip from the .tfrecord Mfiles and returns the file name to the client over the WebSocket connection. The client can now download the dataset.&lt;/p&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;
&lt;p&gt;Today we introduced our concept and some insights into the implementation of our developed remote data pipeline used to prepare and provide training data for complex machine learning training in our research project. Using this pipeline, we have solved the challenging task of a split infrastructure without suffering from strong performance-related deficits. This remote data transfer was one of the first milestones in our project to be reached and further the base of a successful collaboration.&lt;/p&gt;
&lt;p&gt;The advantage of our presented solution is the shared use of resources: The computing server focuses its resources entirely on training models, while the data server handles the labor-intensive creation of datasets. To give you an idea of why this is so important: as we work with large-size image data, a full augmentation process can easily take up to 12 hours. Waste of valuable computing resources if tasks are not split up. Furthermore, we usually start multiple training sessions all requiring the same dataset at the same time. Without our data pipeline, each experiment would create its own version of the same data set and will block even more resources. With our solution, this is solved in a far more efficient way.&lt;br&gt;
Our developed solution could be extended in further versions by features like a monitoring tool with visualization. Important information and statistics to display could be, for example, the status of currently running data preparation processes, a list of all cached datasets including their configurations, as well as statistics about usage and downloads. This could help to keep the storage more structured and clean.&lt;/p&gt;
&lt;p&gt;All in all, we were very content with how fast the communication and transfer between our servers take place and are very content with our self developed approach.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;This project was funded by the European Regional Development Fund (ERDF).&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 449px) 449px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;449&quot; height=&quot;112&quot; src=&quot;https://img.ly/_astro/9_Zv71r7.webp&quot; srcset=&quot;/_astro/9_Zv71r7.webp 449w&quot;&gt;&lt;/p&gt;</content:encoded><dc:creator>Leonard</dc:creator><dc:creator>Philip</dc:creator><dc:creator>Pascal</dc:creator><dc:creator>Sebastian</dc:creator><media:content url="https://blog.img.ly/2021/04/Zeichenfl-che-1-100-1.jpg" medium="image"/><category>Machine Learning</category><category>AI</category></item><item><title>Smart Cropping - Automatically crop images to optimal regions with deep neural networks</title><link>https://img.ly/blog/smart-cropping/</link><guid isPermaLink="true">https://img.ly/blog/smart-cropping/</guid><description>Many websites ask you to upload images in specific aspect ratios. Smart cropping allows you to automatically find such images for any aspect ratio. It&apos;s powered by Convolutional Neural Networks, predicting the salient areas of a picture. We describe how smart cropping works and why it&apos;s important.</description><pubDate>Fri, 24 Jul 2020 11:48:34 GMT</pubDate><content:encoded>&lt;p&gt;Pictures are omnipresent on the social web. It is common to instantly post photos of all kinds of events to share with friends and followers. Also, businesses want to show presence on social networks and employ designated social media managers to represent the company and to communicate to customers.&lt;/p&gt;
&lt;p&gt;Let’s assume you work as a social media manager in a company. Your job is to communicate with customers and represent your company on various social media platforms. One part of your job is to share pictures of your company’s work. Since you’re serving multiple social media platforms, you always have to consider their specific aspect ratio requirements for images. One platform wants you to provide square photos, whereas another one asks for pictures in a wide landscape format.&lt;/p&gt;
&lt;p&gt;You are a busy person, you don’t want to waste time on cropping hundreds of images into the proper format, but you also don’t want to crop your pictures weirdly.&lt;/p&gt;
&lt;p&gt;Suppose you want a portrait-oriented version of the following image. Simply choosing the center would lead to an odd picture containing only one half of the bird. What you want is the image to include the region of interest; here, probably the whole bird in the center of the image.&lt;/p&gt;
&lt;p&gt;But how can we automatically find such image regions?&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A bad image cutout (left) and a good one (right)&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 2000px) 2000px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;2000&quot; height=&quot;927&quot; src=&quot;https://img.ly/_astro/robin_Z27I8c6.webp&quot; srcset=&quot;/_astro/robin_r6J45.webp 640w, /_astro/robin_2wSwjf.webp 750w, /_astro/robin_3nxbk.webp 828w, /_astro/robin_VFwr5.webp 1080w, /_astro/robin_Zz1bpt.webp 1280w, /_astro/robin_Z2uCr6I.webp 1668w, /_astro/robin_Z27I8c6.webp 2000w&quot;&gt;&lt;/p&gt;
&lt;p&gt;When humans look at images, they intuitively focus on significant elements of the photos first. If you look at the following pictures, …&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 2048px) 2048px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;2048&quot; height=&quot;1365&quot; src=&quot;https://img.ly/_astro/image-4_1WFNUQ.webp&quot; srcset=&quot;/_astro/image-4_Z29KSCi.webp 640w, /_astro/image-4_Z2bs8LD.webp 750w, /_astro/image-4_27k41M.webp 828w, /_astro/image-4_Zbc8vk.webp 1080w, /_astro/image-4_Z2hFSu1.webp 1280w, /_astro/image-4_J693X.webp 1668w, /_astro/image-4_1WFNUQ.webp 2048w&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 2000px) 2000px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;2000&quot; height=&quot;1333&quot; src=&quot;https://img.ly/_astro/image-2_5wphL.webp&quot; srcset=&quot;/_astro/image-2_Z205taz.webp 640w, /_astro/image-2_Z21LIjU.webp 750w, /_astro/image-2_2h0ttv.webp 828w, /_astro/image-2_ZllleT.webp 1080w, /_astro/image-2_Z2rP6dA.webp 1280w, /_astro/image-2_yVVko.webp 1668w, /_astro/image-2_5wphL.webp 2000w&quot;&gt;&lt;/p&gt;
&lt;p&gt;… you will probably notice that your first focus on the salient parts of the image (maybe the geyser or the sundown for the first image, and the reindeers on the road for the second image).&lt;/p&gt;
&lt;p&gt;As it turns out, it is possible to train neural networks to predict such &lt;em&gt;salient regions&lt;/em&gt;. A prediction of such a network is called a &lt;em&gt;saliency map&lt;/em&gt;. It basically is a grayscale image of the same size as the picture. Each pixel intensity encodes the degree of saliency. These saliency maps allow us to find the best image region for a given aspect ratio.&lt;/p&gt;
&lt;p&gt;But how can networks be trained to predict salient regions in a picture? And how do we, given the salience information, crop an image to an optimal region?&lt;/p&gt;
&lt;p&gt;Fortunately, there was already a considerable amount of research regarding saliency prediction. Basically, there are two main approaches: attention-based saliency prediction and segmentation based saliency prediction. The first group focuses on predicting the center points of human attention regardless of object segmentation and boundaries, whereas the latter considers the most salient objects as a whole.&lt;/p&gt;
&lt;p&gt;For our application, it seemed more suitable to choose an attention-based approach. We decided to go with an &lt;a href=&quot;https://arxiv.org/pdf/1611.09571.pdf&quot;&gt;LSTM based model&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Model architecture, adapted from https://arxiv.org/pdf/1611.09571.pdf&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 1738px) 1738px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1738&quot; height=&quot;1010&quot; src=&quot;https://img.ly/_astro/image_1taTmC.webp&quot; srcset=&quot;/_astro/image_Z15hRr1.webp 640w, /_astro/image_ZNGbNo.webp 750w, /_astro/image_Z1BzDLm.webp 828w, /_astro/image_24kOwp.webp 1080w, /_astro/image_Z28kxgt.webp 1280w, /_astro/image_217aWP.webp 1668w, /_astro/image_1taTmC.webp 1738w&quot;&gt;&lt;/p&gt;
&lt;p&gt;Briefly summarized, the approach works as follows: A deep convolutional neural network (CNN), pre-trained on image classification, acts as a feature extractor. The value of some intermediate layer (or &lt;em&gt;hidden layer&lt;/em&gt;) is forwarded to the recurrent LSTM that further improves the prediction. The saliency map then is the output of the LSTM, combined with the Gaussian priors.&lt;/p&gt;
&lt;p&gt;In particular, a dilated convolutional network, in our case, a modified RESNET50 already pre-trained on the &lt;a href=&quot;http://salicon.net/challenge-2017/&quot;&gt;SALICON&lt;/a&gt; dataset, is deployed for feature extraction. The original &lt;a href=&quot;https://arxiv.org/pdf/1611.09571.pdf&quot;&gt;paper&lt;/a&gt; for this method used a network for image classification. Many CNNs can act as feature detectors, but they don’t perform equally well. For example, compared to the standard convolutional feature extraction networks, the dilated networks prevent the harmful effects of image rescaling on the saliency prediction. The extracted feature maps are then fed into an attentive convolutional LSTM (recurrent neural network). This iteratively improves the saliency prediction on the obtained feature maps. Finally, multiple trainable (isotropic) Gaussian priors are added to take the bias of human attention into account, since humans tend to focus on the image center.&lt;/p&gt;
&lt;p&gt;We trained the network on the &lt;a href=&quot;http://salicon.net/challenge-2017/&quot;&gt;SALICON&lt;/a&gt; dataset, which includes 20,000 images from Microsoft COCO and 15,000 corresponding saliency maps. The saliency maps were generated by empirical studies modeling human eye fixation by mouse movements. We optimized our network with a composed loss function considering the Pearson Correlation Coefficient and the Kullback-Leibler divergence, representing standard saliency prediction loss measures.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Model input, RESNET50 predicted saliency map, and 1:1 image version&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 2000px) 2000px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;2000&quot; height=&quot;491&quot; src=&quot;https://img.ly/_astro/geyser_WW79i.webp&quot; srcset=&quot;/_astro/geyser_ZsdNHS.webp 640w, /_astro/geyser_Zz15Pp.webp 750w, /_astro/geyser_ZmXXUp.webp 828w, /_astro/geyser_SBKzc.webp 1080w, /_astro/geyser_L9wAT.webp 1280w, /_astro/geyser_Z18xSoY.webp 1668w, /_astro/geyser_WW79i.webp 2000w&quot;&gt;&lt;/p&gt;
&lt;p&gt;With this approach, we could already predict pleasing saliency maps suitable for smart image cropping. Unfortunately, our first successful model took up way too much memory, thus being useless for practical applications. Therefore we had to compress the model to a suitable size while maintaining the smart cropping performance as high as possible. After a while of unsatisfactory trials, we found that instead of the RESNET50, we could just deploy the way smaller Keras-intern MobileNet as our feature extraction model.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;MobileNet predicted saliency map and 1:1 image version&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 2000px) 2000px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;2000&quot; height=&quot;789&quot; src=&quot;https://img.ly/_astro/geyser_mobilenet_ZsyIuK.webp&quot; srcset=&quot;/_astro/geyser_mobilenet_Z1I51LR.webp 640w, /_astro/geyser_mobilenet_2pPX08.webp 750w, /_astro/geyser_mobilenet_1wIvCO.webp 828w, /_astro/geyser_mobilenet_Z21Sz0p.webp 1080w, /_astro/geyser_mobilenet_ZUkA0F.webp 1280w, /_astro/geyser_mobilenet_Z1TpF7L.webp 1668w, /_astro/geyser_mobilenet_ZsyIuK.webp 2000w&quot;&gt;&lt;/p&gt;
&lt;p&gt;This model option indeed provides less precise results for the saliency map prediction. However, it is still suitable for the smart image cropping, since we only need to know the position of the focus points roughly. Not only could we save much memory capacity employing this model variation, but also we could increase the runtime of our model significantly, which was our goal. This way, we created a model suitable for practical applications that are supposed to take over the inconvenient manual cropping process.&lt;/p&gt;
&lt;p&gt;Once we have the saliency map, the smart crop can be determined quite easily. First, we compute the edge length of a window covering the given image as much as possible while fulfilling the required aspect ratio. Afterward, we slide this window over the predicted saliency map and determine the position that maximizes the covered saliency density. Now we only need to crop the image based on the optimal window position. Thus we obtain our smart cropped image suiting the required aspect ratio. The method is inspired by &lt;a href=&quot;https://ieeexplore.ieee.org/document/7780430&quot;&gt;this&lt;/a&gt; paper.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Original image, saliency map, square, 16:9, and 9:16 versions&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 2000px) 2000px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;2000&quot; height=&quot;296&quot; src=&quot;https://img.ly/_astro/image-13_Z1t1mdm.webp&quot; srcset=&quot;/_astro/image-13_1pxPx2.webp 640w, /_astro/image-13_36nzF.webp 750w, /_astro/image-13_1XIeq7.webp 828w, /_astro/image-13_ZP5jrF.webp 1080w, /_astro/image-13_1RzOQz.webp 1280w, /_astro/image-13_Z1Mm1vK.webp 1668w, /_astro/image-13_Z1t1mdm.webp 2000w&quot;&gt;&lt;/p&gt;
&lt;p&gt;To sum up, using saliency prediction and maximization, smart cropping enables us to find the best image regions for any aspect ratio. This technique, which we’re currently building into our &lt;a href=&quot;https://img.ly/blog/building-the-creative-engine-of-the-world/&quot;&gt;UBQ engine&lt;/a&gt;, reduces the user’s burden to manually crop images into the required aspect ratio.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 2000px) 2000px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;2000&quot; height=&quot;502&quot; src=&quot;https://img.ly/_astro/EFRE_Foerderhinweis_englisch_farbig_Z1Uf1uq.webp&quot; srcset=&quot;/_astro/EFRE_Foerderhinweis_englisch_farbig_Zdua0T.webp 640w, /_astro/EFRE_Foerderhinweis_englisch_farbig_Z1Ir0o7.webp 750w, /_astro/EFRE_Foerderhinweis_englisch_farbig_Z27Rae0.webp 828w, /_astro/EFRE_Foerderhinweis_englisch_farbig_10Uf38.webp 1080w, /_astro/EFRE_Foerderhinweis_englisch_farbig_13LHJ0.webp 1280w, /_astro/EFRE_Foerderhinweis_englisch_farbig_1KOi8c.webp 1668w, /_astro/EFRE_Foerderhinweis_englisch_farbig_Z1Uf1uq.webp 2000w&quot;&gt;&lt;/p&gt;
&lt;p&gt;This project was funded by the European Regional Development Fund (ERDF).&lt;/p&gt;</content:encoded><dc:creator>Vivien</dc:creator><dc:creator>Leonard</dc:creator><media:content url="https://blog.img.ly/2020/07/0_Geysir.jpg" medium="image"/><category>Deep Learning</category><category>Machine Learning</category><category>Artificial Intelligence</category><category>AI</category><category>Insights</category></item></channel></rss>