
<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.9.2">Jekyll</generator><link href="https://kaczmarj.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://kaczmarj.github.io/" rel="alternate" type="text/html" /><updated>2022-08-09T13:13:15+00:00</updated><id>https://kaczmarj.github.io/feed.xml</id><title type="html">Jakub Kaczmarzyk</title><subtitle>Jakub Kaczmarzyk's personal website</subtitle><author><name>Jakub Kaczmarzyk</name><email>jakub.kaczmarzyk@stonybrookmedicine.edu</email></author><entry><title type="html">Making pyramidal TIF masks for segmentation</title><link href="https://kaczmarj.github.io/masking-tif-masks/" rel="alternate" type="text/html" title="Making pyramidal TIF masks for segmentation" /><published>2022-08-08T00:00:00+00:00</published><updated>2022-08-08T00:00:00+00:00</updated><id>https://kaczmarj.github.io/masking-tif-masks</id><content type="html" xml:base="https://kaczmarj.github.io/masking-tif-masks/">&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/kaczmarj/geojson2tif/main/sample.png&quot; alt=&quot;Image of nuclei labels overlaid on histology.&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This post will explain how I created multi-resolution (pyramidal) TIF images of binary
masks for use with histology. &lt;strong&gt;The goal of this&lt;/strong&gt;
is to create a whole slide image of segmentations, which can be accessed in the same
way as the original histology image. The segmentation image is the same size (width, length)
as the original histology image, and they have the same physical units (i.e., microns per pixel).&lt;/p&gt;

&lt;p&gt;The segmentations can be of many different things, including nuclei, cell boundaries, tumor regions, stroma, etc.&lt;/p&gt;

&lt;p&gt;As part of this, I created a command-line tool to convert
a GeoJSON representation of polygons to a binary mask TIF image. Find it here:
&lt;a href=&quot;https://github.com/kaczmarj/geojson2tif/&quot;&gt;https://github.com/kaczmarj/geojson2tif/&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The main workhorse is the &lt;a href=&quot;https://computationalpathologygroup.github.io/ASAP/&quot;&gt;ASAP whole slide image viewer&lt;/a&gt;
and the &lt;a href=&quot;https://github.com/DIAGNijmegen/pathology-whole-slide-data&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wholeslidedata&lt;/code&gt; Python package&lt;/a&gt;.
These packages implement the conversion of a set of polygons to a whole slide image format.&lt;/p&gt;

&lt;h2 id=&quot;geojson&quot;&gt;GeoJSON&lt;/h2&gt;

&lt;p&gt;GeoJSON is a powerful format to represent geometries, like polygons. Below I show an
example of GeoJSON with one polygon. This is simply a JSON object that conforms to a schema.
As such, it is highly flexible. You can use common JSON tools (like Python’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;json&lt;/code&gt; module in the standard library)
with GeoJSON. Additionally, QuPATH is able to consume GeoJSON (just drag and drop into the viewer).
In the context of this blog post, the coordinates should be in base pixels (base meaning the highest
resolution of the whole slide image).&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;FeatureCollection&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;features&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Feature&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;properties&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;geometry&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Polygon&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;coordinates&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;geojson-to-tif&quot;&gt;GeoJSON to TIF&lt;/h2&gt;

&lt;p&gt;Once we have our data in GeoJSON format, we can use it with other tools. For example,
we can use it directly with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;geojson2tif&lt;/code&gt; command-line tool I wrote. Below, I demonstrate
how to use it with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;apptainer&lt;/code&gt; (formerly Singularity).&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;apptainer pull docker://kaczmarj/geojson2tif:latest
apptainer exec --pwd $(pwd) geojson2tif_latest.sif \
    --wsi image.tif --geojson polygons.json --output mask.tif
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Here is an explanation of the arguments:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--wsi image.tif&lt;/code&gt; : the original whole slide image with which the labels are associated.
This will often be a hematoxylin and eosin slide.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--geojson polygons.json&lt;/code&gt; : the GeoJSON file with the polygon coordinates. This can
be gzipped.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--output mask.tif&lt;/code&gt; : the path of the output TIF image containing the binary mask.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This process can take some time… perhaps 20 to 30 minutes. Perhaps more. Maybe less. But probably more.&lt;/p&gt;

&lt;p&gt;The script that converts the GeoJSON to TIF is relatively straightforward. The difficult
work of constructing the pyramidal TIF is done by ASAP and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wholeslidedata&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here are the operations:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Convert the GeoJSON format to a &lt;a href=&quot;https://github.com/DIAGNijmegen/pathology-whole-slide-data/blob/e6ba4338ed2528e4fd40552edaee3c845973e7f8/wholeslidedata/annotation/parser.py#L18-L47&quot;&gt;format specific to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wholeslidedata&lt;/code&gt;&lt;/a&gt;.
    &lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
 &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;index&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
 &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;polygon&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
 &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;coordinates&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
 &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;label&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
     &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;nucleus&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
     &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
   &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
 &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;Identify the physical units of the whole slide image. This is microns per pixel.
If this is not correct, the segmentations will not overlap properly with the original slide image.&lt;/li&gt;
  &lt;li&gt;Decide on a tile size for the tiled TIF (1024 is a reasonable choice).&lt;/li&gt;
  &lt;li&gt;Write the mask with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wholeslidedata.accessories.asap.write_mask2.convert_annotations_to_mask&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For a complete example, see &lt;a href=&quot;https://github.com/kaczmarj/geojson2tif/blob/1be6b99ea1f323e55f2c032905cb2cadd6b6af0d/geojson-to-tif.py#L29-L50&quot;&gt;this section of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;geojson2tif.py&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;why-apptainer-containers-in-general&quot;&gt;Why Apptainer (containers in general?)&lt;/h2&gt;

&lt;p&gt;ASAP is an incredibly powerful tool, but in my experience, the installation is awkward.
Building from source requires lots of different dependencies (which is expected), and
thankfully the maintainers provide &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.deb&lt;/code&gt; packages for installation on Debian-based systems.
On my laptop, I run Debian Testing, but I did not want to install ASAP globally (not to mention its dependencies).
Containers to the rescue. I created &lt;a href=&quot;https://github.com/kaczmarj/geojson2tif/blob/main/Dockerfile&quot;&gt;a Dockerfile&lt;/a&gt;
that builds a Docker image based on Ubuntu and installs ASAP. I chose to build it as a Docker image instead
of directly as Apptainer/Singularity because of personal preference. I then pushed it to DockerHub,
and Apptainer/Singularity will happilly pull and convert this image. Long story short,
I am able to use ASAP without having to install it globally on my laptop. I can even
use it headless on a high performance computing cluster, where I don’t have root access.&lt;/p&gt;

&lt;h2 id=&quot;using-the-images-in-a-machine-learning-pipeline&quot;&gt;Using the images in a machine learning pipeline&lt;/h2&gt;

&lt;p&gt;I will cover this in another blog post.&lt;/p&gt;</content><author><name>Jakub Kaczmarzyk</name><email>jakub.kaczmarzyk@stonybrookmedicine.edu</email></author><summary type="html">Convert polygons in GeoJSON to a TIF whole slide image mask.</summary></entry></feed>