木星観測報告用文字入れスクリプト

2019年シーズンから、撮影した木星の画像を ALPO-Jに報告しています。

個人的には画像の上が南(いわゆる South up)に慣れているので、画像処理や保存は South up派なのですが、報告にあたって、昨今は(特に海外を意識すると)North upが増えているように感じ、迷いながらも North upで報告しています。

報告の際に、画像を North upになるように回転し、撮影日時から体系ごとの中央経度を計算して、画像内に各種撮影条件などとともに入れることになりますが、数が多くなるほど地味に面倒なので、それらを一気に処理するために作った perlスクリプトです。

観測画像のサイズが変動しても同じサイズにリサイズします。

perlモジュールは以下のものを使っています。

  • Image::Magick
  • File::Basename

文字を入れる位置などはスクリプトに埋め込みで、望遠鏡などのある程度固定的なデータとシーイングなどの画像ごとに変動するデータはテキストファイルから読み込む仕様です。

また、日時は WinJUPOSから吐き出されるファイル名(YYYY-MM-DD_HHMM_s[.*].jpg)から採るようにしていて、テキストファイルのベースネームは画像ファイルと同じであることを前提としています。

個人でしか使わない前提で書いたものですが、誰かの目に留まって便利に使っていただけるようならと思いひっそりと公開します。At your own riskで、ご自由にお使いください。

 

使い方

  1. 画像ファイル名確認
  2. テキストファイルの用意
    1. 画像ファイルと同じ basenameで、拡張子は '.txt'としたテキストファイルをカレントディレクトリに準備します。ひな型となるファイルを用意しておけば、それをコピーして編集するだけですみます。
  3. スクリプト実行

 

ひな型の例

#
#  annotation text
#
###
SEEING          ?/10
TRANSP          ?/5
GAIN            260
SS              5.0
EXP             30
STACK_PCT       best 50%
DEROTATION      12(6min)
## =======
OBJECT          Jupiter
TELESCOPE_DIA   150
TELESCOPE_FL    1260
TELESCOPE_TYPE  Newtonian
TELESCOPE_OPT   2X Barlow lens
CAMERA_MAKER    ZWO
CAMERA_TYPE     ASI 462MC
FILTER          ZWO UV/IR cut filter

 

コード

#!/usr/local/bin/perl
#
sub usage {
        print <<"EOM";
Usage: $0 <--image-from file>
        Input image file name is important!!
        It should have format as 'YYYY-MM-DD_HHMM_s[.*].jpg'
        (default name convention of WinJUPOS)
EOM
        exit (0);
}
#
#   Created: 2019/05/28  yamane  Initial revision.
#   Revised: 2019/09/12  yamane  Add CM calculation routine.
#                                Special thanks to M.Sato.
#   Revised: 2023/11/10  yamane  Enlarge canvas size and adjust text position.
#

use strict;
use Image::Magick;
use File::Basename 'basename';
use Data::Dumper;

# =====================================================
# const
# =====================================================
my $DEBUG = 0;

## my $CANVAS_SIZE_X = 640;
## my $CANVAS_SIZE_Y = 640;
## my $IMAGE_SIZE_X = 800;
## my $IMAGE_SIZE_Y = 800;
my $CANVAS_SIZE_X = 720;
my $CANVAS_SIZE_Y = 800;
my $IMAGE_SIZE_X = 960;
my $IMAGE_SIZE_Y = 960;         # should be same as $IMAGE_SIZE_X
## my $FONT_BASE = "DejaVu-Sans-Mono";
my $FONT_BASE = "DejaVu-Sans";
my $FONT_COLOR = "white";
my $TEXTFILE_FMT = "%04d-%02d-%02d-%02d%02d_%1d.txt";
my $OUTFILE_FMT = "obs_%04d-%02d-%02d_%02d-%02d_%1d.jpg";

# =====================================================
# globals
# =====================================================
my $imagefile = "./input_image.jpg";    # default
my $textfile = "./text.txt";            # default
my $outfile = "./output_image.jpg";     # default
my %Config = (
        # --- defauls: these should be read from textfile input
        'DATE' => '9999/99/99',
        'TIME' => '99:99.9',
        'CM_I' => 999.9,
        'CM_II' => 999.9,
        'CM_III' => 333.3,
        'SEEING' => '9/5',
        'TRANSP' => '9/5',
        'GAIN' => 999,
        'SS' => 99,
        'EXP' => 99,
        'STACK_PCT' => 99,
        'DEROTATION' => 99,
        # --- almost fixed: will be overriden by testfile input
        'TELESCOPE_DIA' => 999,
        'TELESCOPE_FL' => 9999,
        'TELESCOPE_TYPE' => '????????',
        'TELESCOPE_OPT' => '2X Barlow',
        'CAMERA_MAKER' => '???',
        'CAMERA_TYPE' => '????????',
        'FILTER' => '??? ????? fter',
);      # hasref: read from text file

if ($DEBUG) { print("DEBUG: Config\n"); print(Dumper {%Config}); print("\n"); }

# =====================================================
# main
# =====================================================
while ($#ARGV >= 0) {
        if ($ARGV[0] eq '--image-from') {
                $imagefile = $ARGV[1];  shift;
                print("INFO: imagefile=$imagefile\n");
        } else {
                print(STDERR "Unknown option $ARGV[0].\n");
                usage();
        }
        shift;
}

# ---------------------------------------------------------
# * get image file name and Date/Time from its name.
# * apply them to output file name
# ---------------------------------------------------------
if (! -f $imagefile) {
        print(STDERR "ERROR: $imagefile: no such image file.\n");
        usage();
}
my $basename = basename($imagefile);
if ($basename =~ /(\d{4})-(\d{2})-(\d{2})-(\d{2})(\d{2})_(\d).*\.jpg/) {
        my ($yy, $mm, $dd, $hh, $mi, $mf) = ($1 + 0, $2 + 0, $3 + 0, $4 + 0, $5 + 0, $6 + 0);
        $Config{'DATE'} = sprintf("%04d/%02d/%02d", $yy, $mm, $dd);
        $Config{'Time'} = sprintf("%02d:%02d.%d", $hh, $mi, $mf);
        $outfile = sprintf($OUTFILE_FMT, $yy, $mm, $dd, $hh, $mi, $mf);
        $textfile = sprintf($TEXTFILE_FMT, $yy, $mm, $dd, $hh, $mi, $mf);
        print("DEBUG: Config{'DATE'}=$Config{'DATE'} / Config{'Time'}=$Config{'Time'} / outfile='$outfile'\n") if ($DEBUG);
        print("INFO: textfile='$textfile'\n");
        print("INFO: outfile='$outfile'\n");

        ## Calculate CM[1..3]
        $mi = "$mi.$mf" + 0.0;
        my $tz = 0;     # for UT
        my ($mjd) = MJD($yy, $mm, $dd, $hh, $mi, $tz);
        print("DEBUG: yy=$yy / mm=$mm / dd=$dd / hh=$hh / mi=$mi / tz=$tz ==> $mjd\n");
        my ($cm1, $cm2, $cm3) = CM($mjd);
        $Config{'CM_I'}   = sprintf("%-6.1f", $cm1);
        $Config{'CM_II'}  = sprintf("%-6.1f", $cm2);
        $Config{'CM_III'} = sprintf("%-6.1f", $cm3);
        print("DEBUG: CM_I=$Config{'CM_I'} / CM_II=$Config{'CM_II'} / CM_III=$Config{'CM_III'}\n");
}

# ---------------------------------------------------------
# read variable data from file
# ---------------------------------------------------------
if (! -r $textfile) {
        print(STDERR "ERROR: $textfile: no such text file found.\n");
        usage();
}
if (! open(TXT, "$textfile")) {
        print(STDERR "ERROR: $textfile: open() failed.\n");
        exit (9);
}
my @tmp_ks = keys(%Config);
if ($DEBUG) { print("DEBUG: tmp_ks\n"); print(Dumper @tmp_ks); print("\n"); }
while () {
        next if (/^\s*#/);
        chomp;
        my ($k, $v) = split(/\t+/);
        print("DEBUG: read_text(): k=$k / v='$v'\n") if ($DEBUG);
        if (grep(/^${k}$/, @tmp_ks)) {
                print("DEBUG: regist to config: $k -> '$v'\n") if ($DEBUG);
                $Config{$k} = $v;
        }
}
close(TXT);

# ---------------------------------------------------------
# execute: read orig image -> resize -> extent
# ---------------------------------------------------------
my $img = Image::Magick->new;
if ($img->Read($imagefile) != 1) {
        print(STDERR "ERROR: Read Image failed.\n");
        exit (9);
}
if ($img->Rotate(degrees=>180) == 0) {
        print(STDERR "ERROR: Rotate failed.\n");
        exit (9);
}
if ($img->Resize(width=>${IMAGE_SIZE_X}, height=>${IMAGE_SIZE_Y}) == 0) {
        print(STDERR "ERROR: Resize failed.\n");
        exit (9);
}
if (${CANVAS_SIZE_X} > ${IMAGE_SIZE_X}) {
        my $dx = (${IMAGE_SIZE_X} - ${CANVAS_SIZE_X}) / 2;
        my $dy = (${IMAGE_SIZE_Y} - ${CANVAS_SIZE_Y}) / 2;
        print("INFO: extent image.");
        if ($img->Extent(background=>"black", width=>${CANVAS_SIZE_X}, height=>${CANVAS_SIZE_Y}, x=>$dx, y=>$dy) == 0) {
                print(STDERR "ERROR: Extent failed.\n");
                exit (9);
        }
} elsif (${CANVAS_SIZE_X} < ${IMAGE_SIZE_X}) {
        my $dx = (${IMAGE_SIZE_X} - ${CANVAS_SIZE_X}) / 2;
        my $dy = (${IMAGE_SIZE_Y} - ${CANVAS_SIZE_Y}) / 2;
        print("INFO: crop image.");
        if ($img->Crop(width=>${CANVAS_SIZE_X}, height=>${CANVAS_SIZE_Y}, x=>$dx, y=>$dy) == 0) {
                print(STDERR "ERROR: Crop failed.\n");
                exit (9);
        }
}

# ---------------------------------------------------------
# execute: annotation
# ---------------------------------------------------------
my $text = [
        # --- Title --- Fiexed
        { "bold"=>1, "pt"=>24, "x"=>180, "y"=> 90,  "text"=>"Jupiter" },
        { "bold"=>0, "pt"=>10, "x"=>710, "y"=>104,  "text"=>"North Up image" },
        # --- Date/Time --- read from textfile
        { "bold"=>0, "pt"=>18, "x"=>220, "y"=>125,  "text"=>"$Config{'DATE'}" },
        { "bold"=>0, "pt"=>18, "x"=>330, "y"=>125,  "text"=>"$Config{'Time'} UT" },
        # --- seeing/transparency --- read from textfile
        { "bold"=>0, "pt"=>14, "x"=>470, "y"=>130, "text"=>"Seeing: $Config{'SEEING'}" },
        { "bold"=>0, "pt"=>14, "x"=>580, "y"=>130, "text"=>"Transp: $Config{'TRANSP'}" },
        # --- Central Longitude for each system --- read from textfile
        { "bold"=>0, "pt"=>14, "x"=>220, "y"=>150, "text"=>"CM-I=$Config{'CM_I'}" },
        { "bold"=>0, "pt"=>14, "x"=>400, "y"=>150, "text"=>"CM-II=$Config{'CM_II'}" },
        { "bold"=>0, "pt"=>14, "x"=>580, "y"=>150, "text"=>"CM-III=$Config{'CM_III'}" },
        # --- Telescope --- almost fiexed
        { "bold"=>0, "pt"=>14, "x"=>250, "y"=>770, "text"=>"D=$Config{'TELESCOPE_DIA'}mm f.l.=$Config{'TELESCOPE_FL'}mm $Config{'TELESCOPE_TYPE'}  $Config{'TELESCOPE_OPT'}" },
        # --- Camera --- almost fiexed
        { "bold"=>0, "pt"=>14, "x"=>250, "y"=>790, "text"=>"$Config{'CAMERA_MAKER'} $Config{'CAMERA_TYPE'}" },
        { "bold"=>0, "pt"=>14, "x"=>500, "y"=>790, "text"=>"$Config{'FILTER'}" },
        # --- exposure --- read from textfile
        { "bold"=>0, "pt"=>14, "x"=>250, "y"=>810, "text"=>"gain: $Config{'GAIN'}" },
        { "bold"=>0, "pt"=>14, "x"=>380, "y"=>810, "text"=>"ss: $Config{'SS'}ms" },
        { "bold"=>0, "pt"=>14, "x"=>500, "y"=>810, "text"=>"exp: $Config{'EXP'}s" },
        # --- image processing --- read from textfile
        { "bold"=>0, "pt"=>14, "x"=>250, "y"=>830, "text"=>"stack: $Config{'STACK_PCT'}" },
        { "bold"=>0, "pt"=>14, "x"=>500, "y"=>830, "text"=>"derotation: $Config{'DEROTATION'}" },
        # --- Observer Info. --- Fiexed
        { "bold"=>0, "pt"=>14, "x"=>250, "y"=>850, "text"=>"Satoru Yamane" },
        { "bold"=>0, "pt"=>14, "x"=>380, "y"=>850, "text"=>"(Fujinomiya, Shizuoka, Japan)" },
];
for (my $i = 0; $i < scalar(@$text); $i++) {
        my $hashref = @$text[$i];
        my $font = ($hashref->{"bold"} == 0) ? $FONT_BASE : "${FONT_BASE}-Bold";
        my $pt = $hashref->{"pt"};
        my $geom = sprintf("+%d+%d", $hashref->{"x"}, $hashref->{"y"});
        my $text = $hashref->{"text"};
        print("DEBUG: $i: font=$font / pt=$pt / geom=$geom / text=$text\n") if ($DEBUG);
        $img->Annotate(text=>$text, geometry=>$geom,
                        font=>$font, fill=>$FONT_COLOR,
                        gravity=>'northwest', pointsize=>$pt);
}
# ---------------------------------------------------------
# execute: write
# ---------------------------------------------------------
if ($img->Write($outfile) == 0) {
        print(STDERR "ERROR: $outfile: write failed.\n");
        exit (9);
}
undef $img;
print("INFO: completed.\n");

exit (0);

### ========================================================
### Calculate CM
###
###   Special thanks to M.Sato
###
###   $version = "jupcmcal.cg-v1.1";
### ========================================================

# -----------------------------------------------
# 日数計算サブルーチン
# -----------------------------------------------
sub MJD {
        my ($sy, $sm, $sd, $sh, $smin, $stz) = @_;
        my ($mjd);

        if ($sm > 2) {
                $sy = $sy - 1900;
                $sm = $sm;
        } else {
                $sy = $sy - 1901;
                $sm = $sm + 12;
        }
        $mjd = int($sy * 365.25) + int(30.6 * $sm + 0.6) + $sd - 34 + $stz + $sh / 24 + $smin / 1440 + 0.5;

        ($mjd) ;
}

# -----------------------------------------------
# CM計算
# -----------------------------------------------
sub CM {
        my ($mjd) = shift;
        my ($cm1, $cm2, $cm3) = ();     # return values

        my $RAD = 0.0174532925199433;

        my $IR = (134.630 + $mjd * 0.00111587) * $RAD;
        my $JR = (358.476 + $mjd * 0.9856) * $RAD;
        my $KR = (225.328 + $mjd * 0.083085 + 0.33 * sin($IR)) * $RAD;

        my $L = 221.650 + $mjd * 0.902518 - 0.33 * sin($IR);
        my $M = 1.916 * sin($JR) + 0.02  * sin(2 * $JR);
        my $N = 5.552 * sin($KR) + 0.167 * sin(2 * $KR);

        my $OR = ($L + $M - $N) * $RAD;

        my $P = 1.0  - 0.0167 * cos($JR) - 0.00014 * cos(2 * $JR);
        my $Q = 5.21 - 0.252  * cos($KR) - 0.0061  * cos(2 * $KR);

        my $R = sqrt($Q * $Q + $P * $P - 2 * $Q * $P * cos($OR));

        my $SSI = $P * sin($OR) / $R;
        my $SCO = sqrt(1 - $SSI * $SSI);
        my $SR = atan2($SSI , $SCO);
        my $S = $SR / $RAD;

        my $sk = ($S < 0) ? 1 : -1;

        my $T = 877.8169088 * ($mjd - $R / 173.0) + $S - $N + 269.10 + 57.3 * $sk * (sin($SR * 0.5)) ** 2;
        my $U = 870.1869088 * ($mjd - $R / 173.0) + $S - $N + 291.04 + 57.3 * $sk * (sin($SR * 0.5)) ** 2;
        my $V = 870.4529088 * ($mjd - $R / 173.0) + $S - $N + 177.04 + 57.3 * $sk * (sin($SR * 0.5)) ** 2;

        $cm1 = $T - 360 * int($T / 360.0);
        $cm2 = $U - 360 * int($U / 360.0);
        $cm3 = $V - 360 * int($V / 360.0);

        ($cm1, $cm2, $cm3) ;
}