<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>mosaic &amp;mdash; Marcin Modest Trzaska</title>
    <link>https://blog.argilus.online/czach/tag:mosaic</link>
    <description>Piszę, co mi przyjdzie do głowy. Na tematy różne.</description>
    <pubDate>Fri, 11 Sep 2026 05:19:46 +0200</pubDate>
    <item>
      <title>Seestar Mosaic Coverage</title>
      <link>https://blog.argilus.online/czach/seestar-mosaic-coverage</link>
      <description>&lt;![CDATA[Tak coś mnie tknęło… dawno nie robiłem żadnej mozaiki Seestarem.&#xA;Nie robiłem, bo się zniechęciłem — jakoś nie wychodziło to najlepiej…&#xA;&#xA;No i coś mnie tknęło, żeby sprawdzić pokrycie na mapie/obrazku…&#xA;&#xA;I takie coś mam od Klaudiusza. ;-)&#xA;&#xA;!/usr/bin/env python3&#xA;&#34;&#34;&#34;&#xA;seestarmosaiccoverage.py&#xA;&#xA;Wczytuje pliki FIT (Seestar, tryb mozaiki) z podanego folderu i rysuje&#xA;mape pokrycia nieba: kolorem pokazuje, ile klatek pokrywa dany fragment.&#xA;&#xA;Wymagania:&#xA;    pip install astropy numpy matplotlib&#xA;&#xA;Uzycie:&#xA;    python seestarmosaiccoverage.py /sciezka/do/folderuzfit&#xA;    python seestarmosaiccoverage.py /sciezka --fov-w 0.75 --fov-h 1.33 --grid 400&#xA;&#34;&#34;&#34;&#xA;&#xA;import argparse&#xA;import glob&#xA;import os&#xA;import sys&#xA;&#xA;import numpy as np&#xA;from astropy.io import fits&#xA;from astropy.wcs import WCS&#xA;import matplotlib.pyplot as plt&#xA;from matplotlib.path import Path as MplPath&#xA;&#xA;---------- odczyt naglowkow ----------&#xA;&#xA;RAKEYS = [&#34;RA&#34;, &#34;OBJCTRA&#34;, &#34;CRVAL1&#34;]&#xA;DECKEYS = [&#34;DEC&#34;, &#34;OBJCTDEC&#34;, &#34;CRVAL2&#34;]&#xA;ROTKEYS = [&#34;ROTATION&#34;, &#34;PA&#34;, &#34;ROTATANG&#34;, &#34;ORIENTAT&#34;, &#34;ROT&#34;]&#xA;&#xA;def firstpresent(header, keys):&#xA;    for k in keys:&#xA;        if k in header:&#xA;            return header[k]&#xA;    return None&#xA;&#xA;def todecimalra(val):&#xA;    &#34;&#34;&#34;RA moze byc liczba (stopnie) albo string HH:MM:SS.&#34;&#34;&#34;&#xA;    if isinstance(val, (int, float)):&#xA;        return float(val)&#xA;    val = str(val).strip()&#xA;    if &#34;:&#34; in val:&#xA;        h, m, s = [float(x) for x in val.split(&#34;:&#34;)]&#xA;        return (h + m / 60 + s / 3600)  15.0&#xA;    return float(val)&#xA;&#xA;def todecimaldec(val):&#xA;    if isinstance(val, (int, float)):&#xA;        return float(val)&#xA;    val = str(val).strip()&#xA;    sign = -1.0 if val.startswith(&#34;-&#34;) else 1.0&#xA;    val = val.lstrip(&#34;+-&#34;)&#xA;    if &#34;:&#34; in val:&#xA;        d, m, s = [float(x) for x in val.split(&#34;:&#34;)]&#xA;        return sign  (d + m / 60 + s / 3600)&#xA;    return float(val)  sign&#xA;&#xA;def readframepointing(path):&#xA;    &#34;&#34;&#34;Zwraca (radeg, decdeg, rotationdeg, footprintlubNone) dla jednej klatki FIT.&#34;&#34;&#34;&#xA;    header = fits.getheader(path)&#xA;&#xA;    # Sciezka 1: pelne WCS (jesli plik byl plate-solved, np. przez ASTAP)&#xA;    if &#34;CTYPE1&#34; in header and &#34;CRVAL1&#34; in header:&#xA;        try:&#xA;            wcs = WCS(header)&#xA;            footprint = wcs.calcfootprint(header=header)  # 4x2 [ra,dec] w stopniach&#xA;            rac, decc = footprint.mean(axis=0)&#xA;            return rac, decc, 0.0, footprint&#xA;        except Exception:&#xA;            pass&#xA;&#xA;    # Sciezka 2: naglowek Seestara (RA/DEC srodka klatki + ewentualna rotacja)&#xA;    raraw = firstpresent(header, RAKEYS)&#xA;    decraw = firstpresent(header, DECKEYS)&#xA;    if raraw is None or decraw is None:&#xA;        raise ValueError(&#34;brak RA/DEC w naglowku&#34;)&#xA;&#xA;    radeg = todecimalra(raraw)&#xA;    decdeg = todecimaldec(decraw)&#xA;    rotraw = firstpresent(header, ROTKEYS)&#xA;    rotdeg = float(rotraw) if rotraw is not None else 0.0&#xA;&#xA;    return radeg, decdeg, rotdeg, None&#xA;&#xA;---------- projekcja i footprinty ----------&#xA;&#xA;def gnomonic(radeg, decdeg, ra0deg, dec0deg):&#xA;    &#34;&#34;&#34;Rzutuje RA/Dec na plaszczyzne styczna wzgledem (ra0, dec0). Zwraca stopnie.&#34;&#34;&#34;&#xA;    ra, dec = np.radians(radeg), np.radians(decdeg)&#xA;    ra0, dec0 = np.radians(ra0deg), np.radians(dec0deg)&#xA;    dra = ra - ra0&#xA;    denom = np.sin(dec0)  np.sin(dec) + np.cos(dec0)  np.cos(dec)  np.cos(dra)&#xA;    x = np.cos(dec)  np.sin(dra) / denom&#xA;    y = (np.cos(dec0)  np.sin(dec) - np.sin(dec0)  np.cos(dec)  np.cos(dra)) / denom&#xA;    return np.degrees(x), np.degrees(y)&#xA;&#xA;def framecornerstangent(rac, decc, rotdeg, fovw, fovh, ra0, dec0):&#xA;    &#34;&#34;&#34;Rogi klatki (prostokat obrocony o rotdeg) w ukladzie stycznym.&#34;&#34;&#34;&#xA;    xc, yc = gnomonic(rac, decc, ra0, dec0)&#xA;    hw, hh = fovw / 2, fovh / 2&#xA;    local = np.array([(-hw, -hh), (hw, -hh), (hw, hh), (-hw, hh)])&#xA;    theta = np.radians(rotdeg)&#xA;    rot = np.array([[np.cos(theta), -np.sin(theta)],&#xA;                    [np.sin(theta), np.cos(theta)]])&#xA;    rotated = local @ rot.T&#xA;    return rotated + np.array([xc, yc])&#xA;&#xA;def wcscornerstangent(footprintradec, ra0, dec0):&#xA;    x, y = gnomonic(footprintradec[:, 0], footprintradec[:, 1], ra0, dec0)&#xA;    return np.columnstack([x, y])&#xA;&#xA;---------- glowna logika ----------&#xA;&#xA;def buildcoverage(folder, fovw, fovh, gridn):&#xA;    files = sorted(glob.glob(os.path.join(folder, &#34;.fit&#34;)) +&#xA;                    glob.glob(os.path.join(folder, &#34;.fits&#34;)))&#xA;    if not files:&#xA;        sys.exit(f&#34;Nie znaleziono plikow .fit/.fits w: {folder}&#34;)&#xA;&#xA;    raw = []&#xA;    for f in files:&#xA;        try:&#xA;            rac, decc, rot, footprint = readframepointing(f)&#xA;            raw.append((f, rac, decc, rot, footprint))&#xA;        except Exception as e:&#xA;            print(f&#34;[pomijam] {os.path.basename(f)}: {e}&#34;)&#xA;&#xA;    if not raw:&#xA;        sys.exit(&#34;Nie udalo sie odczytac zadnej klatki - sprawdz nazwy kluczy w naglowku (patrz ponizej).&#34;)&#xA;&#xA;    ra0 = float(np.mean([r[1] for r in raw]))&#xA;    dec0 = float(np.mean([r[2] for r in raw]))&#xA;&#xA;    polygons = []&#xA;    for f, rac, decc, rot, footprint in raw:&#xA;        if footprint is not None:&#xA;            corners = wcscornerstangent(footprint, ra0, dec0)&#xA;        else:&#xA;            corners = framecornerstangent(rac, decc, rot, fovw, fovh, ra0, dec0)&#xA;        polygons.append(corners)&#xA;&#xA;    allpts = np.vstack(polygons)&#xA;    pad = 0.1  max(fovw, fovh)&#xA;    xmin, ymin = allpts.min(axis=0) - pad&#xA;    xmax, ymax = allpts.max(axis=0) + pad&#xA;&#xA;    xs = np.linspace(xmin, xmax, gridn)&#xA;    ys = np.linspace(ymin, ymax, gridn)&#xA;    XX, YY = np.meshgrid(xs, ys)&#xA;    pts = np.columnstack([XX.ravel(), YY.ravel()])&#xA;&#xA;    count = np.zeros(pts.shape[0], dtype=int)&#xA;    for corners in polygons:&#xA;        path = MplPath(corners)&#xA;        count += path.containspoints(pts)&#xA;&#xA;    count = count.reshape(XX.shape)&#xA;    return XX, YY, count, polygons, len(raw), ra0, dec0&#xA;&#xA;def plotcoverage(XX, YY, count, polygons, nframes, ra0, dec0, outpath):&#xA;    fig, ax = plt.subplots(figsize=(9, 8))&#xA;&#xA;    maxcount = max(1, int(count.max()))&#xA;    mesh = ax.pcolormesh(XX, YY, count, shading=&#34;auto&#34;,&#xA;                          cmap=&#34;viridis&#34;, vmin=0, vmax=maxcount)&#xA;    cbar = fig.colorbar(mesh, ax=ax)&#xA;    cbar.setlabel(&#34;Liczba nakladajacych sie klatek&#34;)&#xA;&#xA;    for corners in polygons:&#xA;        closed = np.vstack([corners, corners[0]])&#xA;        ax.plot(closed[:, 0], closed[:, 1], color=&#34;white&#34;, linewidth=0.4, alpha=0.6)&#xA;&#xA;    ax.setxlabel(f&#34;Odsuniecie RA [deg] wzgledem {ra0:.3f}&#34;)&#xA;    ax.setylabel(f&#34;Odsuniecie Dec [deg] wzgledem {dec0:.3f}&#34;)&#xA;    ax.settitle(f&#34;Pokrycie mozaiki - {nframes} klatek&#34;)&#xA;    ax.invertxaxis()  # konwencja: RA rosnie w lewo&#xA;    ax.setaspect(&#34;equal&#34;)&#xA;&#xA;    fig.tightlayout()&#xA;    fig.savefig(outpath, dpi=150)&#xA;    print(f&#34;Zapisano: {outpath}&#34;)&#xA;    plt.show()&#xA;&#xA;def main():&#xA;    ap = argparse.ArgumentParser(description=&#34;Mapa pokrycia mozaiki Seestar z plikow FIT.&#34;)&#xA;    ap.addargument(&#34;folder&#34;, help=&#34;Folder z plikami .fit/.fits&#34;)&#xA;    ap.addargument(&#34;--fov-w&#34;, type=float, default=0.75,&#xA;                    help=&#34;Szerokosc FOV w stopniach (domyslnie 0.75 - Seestar S50)&#34;)&#xA;    ap.addargument(&#34;--fov-h&#34;, type=float, default=1.33,&#xA;                    help=&#34;Wysokosc FOV w stopniach (domyslnie 1.33 - Seestar S50)&#34;)&#xA;    ap.addargument(&#34;--grid&#34;, type=int, default=300,&#xA;                    help=&#34;Rozdzielczosc siatki liczacej nakladki (domyslnie 300)&#34;)&#xA;    ap.addargument(&#34;--out&#34;, default=&#34;coverage.png&#34;, help=&#34;Plik wyjsciowy PNG&#34;)&#xA;    args = ap.parseargs()&#xA;&#xA;    XX, YY, count, polygons, n, ra0, dec0 = buildcoverage(&#xA;        args.folder, args.fovw, args.fovh, args.grid&#xA;    )&#xA;    plotcoverage(XX, YY, count, polygons, n, ra0, dec0, args.out)&#xA;&#xA;if name == &#34;main&#34;:&#xA;    main()&#xA;&#xA;Najpierw&#xA;$ python3 -m venv astro&#xA;$ source astro/bin/activate&#xA;a później, na przykład:&#xA;$ python seestarmosaiccoverage.py SeestarS50/Messier/M\ 31manualmosaic&#xA;&#xA;div class=&#34;galeria&#34; &#xA;       img src=&#34;https://blgpht.argilus.online/api/files/128/original/coverageIC1848.jpeg&#34; alt=&#34;pokrycie mozaiki&#34;&#xA;       img src=&#34;https://blgpht.argilus.online/api/files/126/original/coverageNGC1499.jpeg&#34; alt=&#34;pokrycie mozaiki&#34;&#xA;       /div&#xA;&#xA;div class=&#34;galeria&#34; &#xA;img src=&#34;https://blgpht.argilus.online/api/files/124/original/coverageIC443.jpeg&#34; alt=&#34;pokrycie mozaiki&#34;&#xA;img src=&#34;https://blgpht.argilus.online/api/files/125/original/coverageIC5070.jpeg&#34; alt=&#34;pokrycie mozaiki&#34;&#xA;/div&#xA;&#xA;pokrycie M31&#xA;pokrycie NGC700&#xA;&#xA;No i te dwa ostatnie obrazki pokazują mi dobitnie, dlaczego mozaiki były fallusowe*.&#xA;&#xA;: Claude AI&#xA;&#xA;#seestar #mosaic #mozaika #astrophotography #astrofotografia&#xA;&#xA;-- &#xD;&#xA;Marcin &#34;czach&#34; Trzaska&#xD;&#xA;reply-to: @czach@mastodon.argilus.online]]&gt;</description>
      <content:encoded><![CDATA[<p>Tak coś mnie tknęło… dawno nie robiłem żadnej mozaiki Seestarem.
Nie robiłem, bo się zniechęciłem — jakoś nie wychodziło to najlepiej…</p>

<p>No i coś mnie tknęło, żeby sprawdzić pokrycie na mapie/obrazku…</p>

<p>I takie coś mam od <em>Klaudiusza</em>[^1]. ;–)</p>

<pre><code class="language-python">#!/usr/bin/env python3
&#34;&#34;&#34;
seestar_mosaic_coverage.py

Wczytuje pliki FIT (Seestar, tryb mozaiki) z podanego folderu i rysuje
mape pokrycia nieba: kolorem pokazuje, ile klatek pokrywa dany fragment.

Wymagania:
    pip install astropy numpy matplotlib

Uzycie:
    python seestar_mosaic_coverage.py /sciezka/do/folderu_z_fit
    python seestar_mosaic_coverage.py /sciezka --fov-w 0.75 --fov-h 1.33 --grid 400
&#34;&#34;&#34;

import argparse
import glob
import os
import sys

import numpy as np
from astropy.io import fits
from astropy.wcs import WCS
import matplotlib.pyplot as plt
from matplotlib.path import Path as MplPath

# ---------- odczyt naglowkow ----------

RA_KEYS = [&#34;RA&#34;, &#34;OBJCTRA&#34;, &#34;CRVAL1&#34;]
DEC_KEYS = [&#34;DEC&#34;, &#34;OBJCTDEC&#34;, &#34;CRVAL2&#34;]
ROT_KEYS = [&#34;ROTATION&#34;, &#34;PA&#34;, &#34;ROTATANG&#34;, &#34;ORIENTAT&#34;, &#34;ROT&#34;]


def _first_present(header, keys):
    for k in keys:
        if k in header:
            return header[k]
    return None


def _to_decimal_ra(val):
    &#34;&#34;&#34;RA moze byc liczba (stopnie) albo string HH:MM:SS.&#34;&#34;&#34;
    if isinstance(val, (int, float)):
        return float(val)
    val = str(val).strip()
    if &#34;:&#34; in val:
        h, m, s = [float(x) for x in val.split(&#34;:&#34;)]
        return (h + m / 60 + s / 3600) * 15.0
    return float(val)


def _to_decimal_dec(val):
    if isinstance(val, (int, float)):
        return float(val)
    val = str(val).strip()
    sign = -1.0 if val.startswith(&#34;-&#34;) else 1.0
    val = val.lstrip(&#34;+-&#34;)
    if &#34;:&#34; in val:
        d, m, s = [float(x) for x in val.split(&#34;:&#34;)]
        return sign * (d + m / 60 + s / 3600)
    return float(val) * sign


def read_frame_pointing(path):
    &#34;&#34;&#34;Zwraca (ra_deg, dec_deg, rotation_deg, footprint_lub_None) dla jednej klatki FIT.&#34;&#34;&#34;
    header = fits.getheader(path)

    # Sciezka 1: pelne WCS (jesli plik byl plate-solved, np. przez ASTAP)
    if &#34;CTYPE1&#34; in header and &#34;CRVAL1&#34; in header:
        try:
            wcs = WCS(header)
            footprint = wcs.calc_footprint(header=header)  # 4x2 [ra,dec] w stopniach
            ra_c, dec_c = footprint.mean(axis=0)
            return ra_c, dec_c, 0.0, footprint
        except Exception:
            pass

    # Sciezka 2: naglowek Seestara (RA/DEC srodka klatki + ewentualna rotacja)
    ra_raw = _first_present(header, RA_KEYS)
    dec_raw = _first_present(header, DEC_KEYS)
    if ra_raw is None or dec_raw is None:
        raise ValueError(&#34;brak RA/DEC w naglowku&#34;)

    ra_deg = _to_decimal_ra(ra_raw)
    dec_deg = _to_decimal_dec(dec_raw)
    rot_raw = _first_present(header, ROT_KEYS)
    rot_deg = float(rot_raw) if rot_raw is not None else 0.0

    return ra_deg, dec_deg, rot_deg, None


# ---------- projekcja i footprinty ----------

def gnomonic(ra_deg, dec_deg, ra0_deg, dec0_deg):
    &#34;&#34;&#34;Rzutuje RA/Dec na plaszczyzne styczna wzgledem (ra0, dec0). Zwraca stopnie.&#34;&#34;&#34;
    ra, dec = np.radians(ra_deg), np.radians(dec_deg)
    ra0, dec0 = np.radians(ra0_deg), np.radians(dec0_deg)
    dra = ra - ra0
    denom = np.sin(dec0) * np.sin(dec) + np.cos(dec0) * np.cos(dec) * np.cos(dra)
    x = np.cos(dec) * np.sin(dra) / denom
    y = (np.cos(dec0) * np.sin(dec) - np.sin(dec0) * np.cos(dec) * np.cos(dra)) / denom
    return np.degrees(x), np.degrees(y)


def frame_corners_tangent(ra_c, dec_c, rot_deg, fov_w, fov_h, ra0, dec0):
    &#34;&#34;&#34;Rogi klatki (prostokat obrocony o rot_deg) w ukladzie stycznym.&#34;&#34;&#34;
    xc, yc = gnomonic(ra_c, dec_c, ra0, dec0)
    hw, hh = fov_w / 2, fov_h / 2
    local = np.array([(-hw, -hh), (hw, -hh), (hw, hh), (-hw, hh)])
    theta = np.radians(rot_deg)
    rot = np.array([[np.cos(theta), -np.sin(theta)],
                    [np.sin(theta), np.cos(theta)]])
    rotated = local @ rot.T
    return rotated + np.array([xc, yc])


def wcs_corners_tangent(footprint_radec, ra0, dec0):
    x, y = gnomonic(footprint_radec[:, 0], footprint_radec[:, 1], ra0, dec0)
    return np.column_stack([x, y])


# ---------- glowna logika ----------

def build_coverage(folder, fov_w, fov_h, grid_n):
    files = sorted(glob.glob(os.path.join(folder, &#34;*.fit&#34;)) +
                    glob.glob(os.path.join(folder, &#34;*.fits&#34;)))
    if not files:
        sys.exit(f&#34;Nie znaleziono plikow .fit/.fits w: {folder}&#34;)

    raw = []
    for f in files:
        try:
            ra_c, dec_c, rot, footprint = read_frame_pointing(f)
            raw.append((f, ra_c, dec_c, rot, footprint))
        except Exception as e:
            print(f&#34;[pomijam] {os.path.basename(f)}: {e}&#34;)

    if not raw:
        sys.exit(&#34;Nie udalo sie odczytac zadnej klatki - sprawdz nazwy kluczy w naglowku (patrz ponizej).&#34;)

    ra0 = float(np.mean([r[1] for r in raw]))
    dec0 = float(np.mean([r[2] for r in raw]))

    polygons = []
    for f, ra_c, dec_c, rot, footprint in raw:
        if footprint is not None:
            corners = wcs_corners_tangent(footprint, ra0, dec0)
        else:
            corners = frame_corners_tangent(ra_c, dec_c, rot, fov_w, fov_h, ra0, dec0)
        polygons.append(corners)

    all_pts = np.vstack(polygons)
    pad = 0.1 * max(fov_w, fov_h)
    xmin, ymin = all_pts.min(axis=0) - pad
    xmax, ymax = all_pts.max(axis=0) + pad

    xs = np.linspace(xmin, xmax, grid_n)
    ys = np.linspace(ymin, ymax, grid_n)
    XX, YY = np.meshgrid(xs, ys)
    pts = np.column_stack([XX.ravel(), YY.ravel()])

    count = np.zeros(pts.shape[0], dtype=int)
    for corners in polygons:
        path = MplPath(corners)
        count += path.contains_points(pts)

    count = count.reshape(XX.shape)
    return XX, YY, count, polygons, len(raw), ra0, dec0


def plot_coverage(XX, YY, count, polygons, n_frames, ra0, dec0, out_path):
    fig, ax = plt.subplots(figsize=(9, 8))

    max_count = max(1, int(count.max()))
    mesh = ax.pcolormesh(XX, YY, count, shading=&#34;auto&#34;,
                          cmap=&#34;viridis&#34;, vmin=0, vmax=max_count)
    cbar = fig.colorbar(mesh, ax=ax)
    cbar.set_label(&#34;Liczba nakladajacych sie klatek&#34;)

    for corners in polygons:
        closed = np.vstack([corners, corners[0]])
        ax.plot(closed[:, 0], closed[:, 1], color=&#34;white&#34;, linewidth=0.4, alpha=0.6)

    ax.set_xlabel(f&#34;Odsuniecie RA [deg] wzgledem {ra0:.3f}&#34;)
    ax.set_ylabel(f&#34;Odsuniecie Dec [deg] wzgledem {dec0:.3f}&#34;)
    ax.set_title(f&#34;Pokrycie mozaiki - {n_frames} klatek&#34;)
    ax.invert_xaxis()  # konwencja: RA rosnie w lewo
    ax.set_aspect(&#34;equal&#34;)

    fig.tight_layout()
    fig.savefig(out_path, dpi=150)
    print(f&#34;Zapisano: {out_path}&#34;)
    plt.show()


def main():
    ap = argparse.ArgumentParser(description=&#34;Mapa pokrycia mozaiki Seestar z plikow FIT.&#34;)
    ap.add_argument(&#34;folder&#34;, help=&#34;Folder z plikami .fit/.fits&#34;)
    ap.add_argument(&#34;--fov-w&#34;, type=float, default=0.75,
                    help=&#34;Szerokosc FOV w stopniach (domyslnie 0.75 - Seestar S50)&#34;)
    ap.add_argument(&#34;--fov-h&#34;, type=float, default=1.33,
                    help=&#34;Wysokosc FOV w stopniach (domyslnie 1.33 - Seestar S50)&#34;)
    ap.add_argument(&#34;--grid&#34;, type=int, default=300,
                    help=&#34;Rozdzielczosc siatki liczacej nakladki (domyslnie 300)&#34;)
    ap.add_argument(&#34;--out&#34;, default=&#34;coverage.png&#34;, help=&#34;Plik wyjsciowy PNG&#34;)
    args = ap.parse_args()

    XX, YY, count, polygons, n, ra0, dec0 = build_coverage(
        args.folder, args.fov_w, args.fov_h, args.grid
    )
    plot_coverage(XX, YY, count, polygons, n, ra0, dec0, args.out)


if __name__ == &#34;__main__&#34;:
    main()
</code></pre>

<p>Najpierw</p>

<pre><code>$ python3 -m venv astro
$ source astro/bin/activate
</code></pre>

<p>a później, na przykład:</p>

<pre><code>$ python seestar_mosaic_coverage.py SeestarS50/Messier/M\ 31_manual_mosaic
</code></pre>

<div class="galeria">
       <img src="https://blgpht.argilus.online/api/files/128/original/coverage_IC1848.jpeg" alt="pokrycie mozaiki">
       <img src="https://blgpht.argilus.online/api/files/126/original/coverage_NGC1499.jpeg" alt="pokrycie mozaiki">
       </div>

<div class="galeria">
<img src="https://blgpht.argilus.online/api/files/124/original/coverage_IC443.jpeg" alt="pokrycie mozaiki">
<img src="https://blgpht.argilus.online/api/files/125/original/coverage_IC5070.jpeg" alt="pokrycie mozaiki">
</div>

<p><img src="https://blgpht.argilus.online/api/files/127/original/coverage_M31.jpeg" alt="pokrycie M31">
<img src="https://blgpht.argilus.online/api/files/129/original/coverage_NGC7000.jpeg" alt="pokrycie NGC700"></p>

<p>No i te dwa ostatnie obrazki pokazują mi dobitnie, dlaczego mozaiki były <em>fallusowe</em>.</p>

<p>[^1]: Claude AI</p>

<p><a href="/czach/tag:seestar" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">seestar</span></a> <a href="/czach/tag:mosaic" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">mosaic</span></a> <a href="/czach/tag:mozaika" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">mozaika</span></a> <a href="/czach/tag:astrophotography" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">astrophotography</span></a> <a href="/czach/tag:astrofotografia" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">astrofotografia</span></a></p>

<p>—
Marcin “czach” Trzaska
reply-to: <a href="https://blog.argilus.online/@/czach@mastodon.argilus.online" class="u-url mention" rel="nofollow">@<span>czach@mastodon.argilus.online</span></a></p>
]]></content:encoded>
      <guid>https://blog.argilus.online/czach/seestar-mosaic-coverage</guid>
      <pubDate>Fri, 04 Sep 2026 15:59:15 +0000</pubDate>
    </item>
  </channel>
</rss>