<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="../assets/xml/rss.xsl" media="all"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>.:: Marcos Dione/StyXman's glob ::. (Posts about image_processing)</title><link>https://www.grulic.org.ar/~mdione/glob/</link><description></description><atom:link href="https://www.grulic.org.ar/~mdione/glob/categories/image_processing.xml" rel="self" type="application/rss+xml"></atom:link><language>en</language><copyright>Contents © 2025 &lt;a href="mailto:mdione@grulic.org.ar"&gt;Marcos Dione&lt;/a&gt; </copyright><lastBuildDate>Thu, 29 May 2025 15:41:16 GMT</lastBuildDate><generator>Nikola (getnikola.com)</generator><docs>http://blogs.law.harvard.edu/tech/rss</docs><item><title>Stacking photos with Python</title><link>https://www.grulic.org.ar/~mdione/glob/posts/stacking-photos-with-python/</link><dc:creator>Marcos Dione</dc:creator><description>&lt;p&gt;Last month we went on vacations to a place with medium light pollution. It was also meteor shower season;
the Perseids were peaking that week. So I decided to try some astro-photography. I bought a cheap barn
door tracker, but it didn't arrive before we left, so I had to change my plans of
taking Milky Way pictures.&lt;/p&gt;
&lt;p&gt;I went for the other extreme; instead of steady stars, why not star trails?
Luckily, my Nikon D7200 already has Interval Timer Shooting, so it was only a matter of setting it up
correctly&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="https://www.grulic.org.ar/~mdione/glob/posts/stacking-photos-with-python/#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt; and let it take as many pictures as long as it still has power. You can say that was the
easy part. The hard part was to do the stacking.&lt;/p&gt;
&lt;p&gt;The way you stack star trail photos is that you combine them in
&lt;a href="https://www.w3.org/TR/SVGCompositing/#comp-op-property"&gt;Lighten mode&lt;/a&gt;. Ihad already used that technique
to produce
&lt;a href="https://dionecanali.hd.free.fr/~mdione/anotherg/misc/?imageLightboxIndex=2019-11-28-004.jpg"&gt;this photo&lt;/a&gt;
Gimp
can do it, if you load all the images as layers and then set each layer &lt;em&gt;by hand&lt;/em&gt; with a lighten mode.
The &lt;em&gt;by hand&lt;/em&gt; part is the hard part. My first attempt gave me only 58 photos, so it was not &lt;em&gt;that&lt;/em&gt; bad,
but the repetitiveness of the task (select a layer, change the mode, iterate, a mouse only operation)
asked for automation. Unluckily there is not a good way to generate a file that gimp can load with all
that info prefilled; its native format, XCF, is binary, and embeds all the images, so 58x12Mpx4 channels
equals 2784MB! Plus metadata... The second attempt yielded 250+ photos. Stacking &lt;em&gt;that one&lt;/em&gt; was
&lt;em&gt;tedious&lt;/em&gt;, and the outcome (because of some technical reasons) was not that great.&lt;/p&gt;
&lt;p&gt;Why not programmatically? I asked around what good Python modules that could do composition operation
and I was pointed to GTK, but the API didn't look very friendly (to me it looks like it's more aimed to
implement things
like Inscape than GIMP). I looked for a second time and found
&lt;a href="https://github.com/FHPythonUtils/BlendModes/"&gt;BlendModes&lt;/a&gt;. That lead to a short script, but it was
&lt;em&gt;slow&lt;/em&gt;, and consumed lots of RAM. I didn't look into the code, but it's probably implemented in pure
Python and might be doing some things wrong.&lt;/p&gt;
&lt;p&gt;Third time's the charm: I found &lt;a href="https://github.com/psd-tools/image-blender/"&gt;Image Blender&lt;/a&gt;. It's
written in
Cython, so it's mostly readable &lt;em&gt;and&lt;/em&gt; fast, at least fast enough. I simply modified the original code
and got this:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="ch"&gt;#! /usr/bin/python3&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;PIL&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;image_blender&lt;/span&gt;

&lt;span class="n"&gt;in_files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;out_file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;in_files&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;out_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;in_file&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;in_files&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"[&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;] &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;in_file&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;in_image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;in_file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;convert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'RGBA'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;out_data&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;out_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'RGBA'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;in_image&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;new_out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frombytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'RGBA'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;in_image&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;image_blender&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lighten&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out_data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tobytes&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;in_image&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tobytes&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;

    &lt;span class="n"&gt;in_image&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;out_data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;out_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new_out&lt;/span&gt;

&lt;span class="n"&gt;out_data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;convert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'RGB'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out_file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Not pretty, but it does the work and fast enough! Faster than selecting 250+ layers in GIMP just to
change the Mode :)&lt;/p&gt;
&lt;div class="footnote"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;I made myself a checklist, not complete:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Make sure the battery is full; use spare if needed.&lt;/li&gt;
&lt;li&gt;Setup tripod with ballast for stability.&lt;/li&gt;
&lt;li&gt;Large photo size (12Mp in my case).&lt;/li&gt;
&lt;li&gt;Fine detail (I don't shoot RAW, don't have much time and expertise for developing photos, so low JPEG
  compression level).&lt;/li&gt;
&lt;li&gt;No noise reduction.&lt;/li&gt;
&lt;li&gt;VR off.&lt;/li&gt;
&lt;li&gt;ISO between 400 and 1600; I think I took most with 800.&lt;/li&gt;
&lt;li&gt;Manual Shooting Mode, 30s exposure&lt;sup id="fnref:2"&gt;&lt;a class="footnote-ref" href="https://www.grulic.org.ar/~mdione/glob/posts/stacking-photos-with-python/#fn:2"&gt;2&lt;/a&gt;&lt;/sup&gt;:, f as wide as possible; I used f/11.&lt;/li&gt;
&lt;li&gt;Manual Focus.&lt;/li&gt;
&lt;li&gt;Focus aiming at the brightest star, lens' zoom at peak (140mm in my case), then open back at the
  desired focal length, recompose.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a class="footnote-backref" href="https://www.grulic.org.ar/~mdione/glob/posts/stacking-photos-with-python/#fnref:1" title="Jump back to footnote 1 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;I wonder why all the cameras I have only allow exposures of up to 30s before going into bulb mode? &lt;a class="footnote-backref" href="https://www.grulic.org.ar/~mdione/glob/posts/stacking-photos-with-python/#fnref:2" title="Jump back to footnote 2 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description><category>image_processing</category><category>photography</category><category>python</category><guid>https://www.grulic.org.ar/~mdione/glob/posts/stacking-photos-with-python/</guid><pubDate>Thu, 09 Sep 2021 19:11:28 GMT</pubDate></item></channel></rss>