AutoHotKeyにより日報の入力を半自動化

今回は題の通りで、日報の入力作業をAutoHotKeyによって半自動化した話となります。

弊社の日報はWebで入力する形式です。
しかし、自分は前日の日報をコピーして編集・記入することが多いためWebで直接入力せず、
①テキストファイルで作成→②翌日は前日のファイルをコピー・編集し作成→③Webに転記
のように入力していたのですが、
この③のWebへの転記作業にて1項目ずつコピペする必要があり大変煩わしく思っていました。
※今年度からは日報に詳細は記載する必要はなくなったため簡素な入力でも問題ないのですが、
 翌日の作業の整理と翌週の朝礼で報告する内容の確認のために
 自分は以前と同じように入力しています。

煩わしく思いつつ特に何もしていなかったのですが、
転記作業自体は自動化すれば良いことに今更気が付きました。
そこでチャットAIにWebへの入力作業を自動化するのに適した手法を聞いてみたところ、
単純な作業なら作業自動化用スクリプト言語のAutoHotKeyが良さそうだと教えてもらい、
AutoHotKeyで転記処理を実装することにしました。

一旦チャットAIにコードを書いてもらったのですが上手く動作しなかった(※)ので、
それをひな型に自分で組み直しました。
※AutoHotKeyで何ができるのかわかっていなかったのもあり、そもそも指示が良くなかったです。
実装したコードは下記になります。

内容としては、
1. 起動時にクリップボードから入力データを読取
2. ホットキーで始動する処理内で、入力データをデータごと・項目ごとにキー送信
を実現しているコードとなります。
コードの前に参考までに、入力データ例(※)と入力画面を貼っておきます。
※入力データのフォーマットは今回の実装に合わせて少し調整しました。

組んでいる途中で思ったのですが、
単純な処理なのでC#でSendKeyを使用して実現するのでも良かった気がしますね。
ですがスクリプト言語は手軽ではあるので、一手段として触っておくのは意義がある気がします。
AutoHotKeyの文法自体は癖がなく書きやすいと感じました。
ただ代入が=ではなく:=なため、何度も癖でコロンを忘れてしまいエラーになりました。。。

以上、日報入力の半自動化(テキスト手動編集後のWebへの入力作業自動化)を行った話でした。
今後も自動化可能な作業に見つかった際は折を見て自動化するようにしたいと思います。

入力データ例

9999
0.5
[雑務]
1. 掃除・朝礼
---
12345678
7.5
[計測ソフト開発]
1. 仕様書作成
2. 実装中
---
_
_
[明日の予定]
1. 計測ソフト開発
2. サーバープログラムビルド送付
---
_
_
[出退]
08:55
18:15

入力画面

実装したコード:DailyReportAutomation.ahk

#Requires AutoHotkey v2.0
;開発使用バージョン:v2.0.19
;-------------------------------------------------------------------------------
;[グローバル変数]
; 標準出力へのグローバル参照:
;  ファイル名に*を指定してFileOpenすることで標準出力へ書き込み
;  実行時にmoreコマンドを指定して出力のリダイレクトを行う必要あり
;  (moreなしだとエラーになる)
;  次のように実行→ AutoHotkey64.exe DailyReportAutomation.ahk | more
global Stdout := FileOpen("*", "w")

;-------------------------------------------------------------------------------
;[起動時初期化処理:クリップボードを読み取り入力用レコードデータ作成]
if !ClipWait(1) {
    ; クリップボードが空ならエラー終了
    MsgBox("No text found in clipboard.", "Error", 16)
    ExitApp
}
buff := ClipboardAll() ; クリップボードを退避
clipboard := A_Clipboard ; クリップボードを取得
records := Parse(clipboard) ; クリップボードからレコードリストをパース
for item in records
    Log(item.ToString() . "`n")
A_Clipboard := buff ; 退避していたクリップボードを復元
if (records.Length = 0) {
    ; クリップボードが空ならエラー終了
    MsgBox("No records in clipboard.", "Error", 16)
    ExitApp
}
if (records.Length > 10) {
    ; クリップボードが10以上ならエラー終了
    MsgBox("Too much records in clipboard.", "Error", 16)
    ExitApp
}
; エラーなければ常駐開始

;-------------------------------------------------------------------------------
;[常駐時ホットキー]
;1. Windows + s:入力処理開始(開始前に先頭の入力欄にカーソルを合わせておくこと
#s:: {
    ; IMEが全角か半角かチェックし、全角(true)なら半角に切り替えます
    if (CheckImeIsFullOrHalf("Infortec 日報"))
        SwithImeToFullorHalf()
    ; 与えられたレコードデータの項目を順に入力
    for record in records {
        ; 工番入力→案件名入力→内容入力→コメント欄飛ばす→進捗欄飛ばす→時間入力
        ClearSend(record.ProjectNo)
        Next()
        ClearSend(record.ProjectName)
        Next()
        ClearSend(record.Content)
        Next()
        Next()
        Next()
        ClearSend(record.Time)
        Next()
    }
    ExitApp ; アプリ終了
}

;2. Windows + q:アプリ終了
#q:: {
    ExitApp
}

;-------------------------------------------------------------------------------
;[クラス]
;レコードクラス
class RecordData{
    ;コンストラクタ:工番、時間、案件名、内容
    __New(projectNo, time, projectName, content){
        this.ProjectNo := projectNo
        this.Time := time
        this.ProjectName := projectName
        this.Content := content
    }

    ;Func 文字列化
    ToString(){
        txts := [this.ProjectNo, this.Time, this.ProjectName, this.Content]
        return Join(txts, "`n")
    }
}

;-------------------------------------------------------------------------------
;[関数群]
;Func 指定したタイトルを持つウィンドウのIMEが全角か半角かチェックします
CheckImeIsFullOrHalf(windowTitle) {
    ; 日報ウィンドウのIMEの状態を取得
    SetTitleMatchMode 1 ; タイトル部分一致
    WM_IME_CONTROL := 0x0283
    IMC_GETOPENSTATUS := 0x0005
    imeWnd := DllCall("imm32.dll\ImmGetDefaultIMEWnd", "Uint", WinExist(windowTitle))
    imeStatus := DllCall("user32.dll\SendMessageA", "UInt", imeWnd, "UInt", WM_IME_CONTROL, "Int", IMC_GETOPENSTATUS, "Int", 0)
    Log(imeStatus ? "全角" : "半角")
    return imeStatus
}

;Func IMEの全角/半角を切り替えます。
SwithImeToFullorHalf() {
    Send("{vkF3sc029}") ; 半角/全角キー送信
}

;Func 入力欄をクリアして与えられた文字列を入力します
ClearSend(text) {
    Sleep 80
    Send("^a")
    Sleep 20
    Send("{Del}")
    Sleep 20
    Send(text)
    Sleep 80
}

;Func 次の入力欄へ移動します
Next(){
    Send("{Tab}")
    Sleep 20
}

;Func テキストからレコードリストをパースします
Parse(text){
    ; 改行を正規化
    text := StrReplace(text, "`r`n", "`n")
    text := StrReplace(text, "`r", "`n")
    ; 項目ごとに文字列を分割
    items := StrSplit(text, "---")
    ; 項目が複数ないなら終了
    if (items.Length < 2)
        return []
    records := []
    for item in items {
        ; 項目からレコードを読み取る
        item := Trim(item, "`n")
        ; 行ごとに分割
        rows := StrSplit(item, "`n")
        ; 4行未満なら無効データとして飛ばす
        if (rows.Length < 4)
            Continue
        ; 行ごとの処理:主に複数行に跨る内容行の結合を行う
        contents := []
        for index, row in rows {
            ; プレースホルダ _ は空行に変換
            rows[index] := row := row = "_" ? "" : row
            ; 4項目目までは飛ばす
            if (index < 4){
                Continue
            }
            ; 4項目目以降を内容行配列に追加
            contents.Push(row)
        }
        content := Join(contents, "`n")
        ; レコード作成:工番1行目、時間2行目、案件名3行目、内容4行目~
        record := RecordData(rows[1], rows[2], rows[3], content)
        records.Push(record)
    }
    return records
}

;Func 文字列配列を区切り文字で連結
Join(array, delimiter){
    ret := ""
    ; 戻り値に配列内の各文字列を区切り文字と合わせて結合
    for str in array
	    ret .= str . delimiter
    ret := RTrim(ret, delimiter) ; 末尾の区切り文字を削除
    return ret
}

;Func ログ出力
Log(msg){
    Stdout.WriteLine(msg)
}

高速処理を実現するための地味だけど効くチューニング

製造業や研究開発の現場では、センサーデータや画像処理、シミュレーション結果など、膨大なデータをリアルタイムで処理するニーズが年々高まっています。先日開発したあるソフトウェアでは、1秒間に約30万件の2次元データを処理する必要がありました。これだけのデータ量になると、気にせず実装すると要求速度に間に合わなくなります。

今回は高速化のために以下のようなチューニングを行いました。どれも派手さはありませんが、確実に効くテクニックです。

1. クラスではなく構造体(struct)を使う

C#などの言語では、クラスはヒープに配置されるため、GC(ガーベジコレクション)の影響を受けやすくなります。構造体はスタックに配置されるため、生成・破棄のコストが低く、処理速度に貢献します。特に大量のデータを扱う場合、この差は無視できません。

2. ListやDictionaryを避け、配列を使う

コレクションクラスは便利ですが、内部的にメモリの再確保やハッシュ計算などが発生し、オーバーヘッドが大きくなります。今回は、あえて2次元配列を使い、アクセスパターンを最適化しました。これにより、CPUキャッシュの効率も向上しました。ただし単に2次元配列にするとコードの書き方が古臭く保守しずらくなるため、2次元配列ということを意識せずにアクセスするための拡張メソッドを大量に追加しました。

3. インスタンスのプーリング

毎回 new して破棄するのではなく、使い終わったインスタンスを再利用する「オブジェクトプール」を導入しました。これにより、GCの発生頻度を抑え、安定したスループットを実現しています。今回の場合、グラフ描画などのGUIで威力を発揮しました。

結果

これらの地道な最適化を積み重ねた結果、無事にお客様の要求する処理速度をクリアすることができました。見た目は地味ですが、こうした「低レイヤーの工夫」が、システム全体の性能を大きく左右します。見えないところで効く技術は大事です。

仕様の検討

こんにちは mtj です

システムの仕様の検討 難しい内容です しかしここさえしっかり決まればあとは実現するだけの内容です
システムの良し悪しはほぼここに詰まっていると思います(動作が重たい等は別の話)

検討する上でやはり不明点を洗い出す事かと思われます

例えばデータの入力が不明ならどういった場面でどのように入力するのか どのような人が入力するのか
操作するシチュエーションも大事です 座ってじっくり入力するのか さっと入力したいのか

そういった要点を押さえる 情報を集めるとなるといろんな人の関わりが必要となります
なのでやはりエンジニアもクライアントとの会話が必要になります

そういった会話の技能も高めていきたいと思いました

ログイン処理改造に伴うDB情報取得方法の仕様見直し

開発しているサービスのセキュリティ向上のため、
ログイン時に連続して失敗した場合に一定時間のログイン制限処理を実装します。
ログイン時のエラー情報は既にDBに記録されていたので、
単純にログイン開始時及びエラー時にDBを検索しようと思ったのですが、
データ数次第で検索に時間がかかる・DBと接続できない場合に取得不可など、
ログイン処理の長時間化や動作不良の懸念がありました。
そのため、ログイン時にはDB検索を行わずに済むよう、
エラー記録時にサーバーにてキャッシュし、
ログイン処理にてエラー情報取得時にキャッシュから取得する方針にしました。
またこれだけだとサーバーが再起動した際にはキャッシュが消えてしまうので、
初期化時にDBを検索し、有効な情報のみキャッシュに追加することにします。
この方針で仕様を練り直して、問題なく動作するように実装したいと思います。

画像描画GUIに多角形選択機能を追加

社内ライブラリに実装しています画像描画GUIに、新たに「多角形の範囲選択機能」を追加しました。これまでの矩形選択や楕円選択に加え、より柔軟で精密な範囲指定が可能になります。

製品検査や研究用途で画像を扱う際、対象物が必ずしも矩形や楕円の形状とは限りません。例えば、検査領域の形状や微細構造の解析では、複雑な輪郭を持つ対象を正確に切り出す必要があります。多角形選択機能を使えば、任意の頂点を指定して自由な形状で範囲を選択できるため、従来の選択方式では難しかった精密な処理が可能になります。

ペイントソフトライクな操作性

操作感にもこだわりました。多角形選択の挙動は、GIMPなどのペイントソフトに近づけて設計しています。クリックで頂点を追加し、開始点クリックで選択完了。頂点のドラッグにも対応しています。直感的な操作で、初めて使う方でもすぐに慣れていただけます。

技術的なポイント

多角形選択の実装には、頂点情報をベクトル形式で保持し、リアルタイムでポリゴン描画を行うアルゴリズムを採用しています。選択範囲のマスク生成や、画像処理ライブラリとの連携もスムーズに行えるよう設計されており、OpenCVなどとの統合も容易です。既存の業務システムへの組み込みも柔軟に対応可能です。

コードの良し悪し

こんにちは mtjです

プログラムという物はいろんな書き方があり 結果は同じでも多種多様なコードが生まれます
見やすい、短いコードもあれば 処理速度が早い、メモリ使用率が低いコード等 結果は同じですが 効果は様々なコードがあります。

どのコードが一番良いかを判断するためにはソフトが稼働する端末のスペック、どの程度の時間まで許容できるか できそうか
演算量で言えば 時間あたりのデータ数等様々な情報が必要になります

全てを満足に満たせるコードがあれば良いのですが 演算速度を高めようとすると可読性、負荷が高くなったり等やはり課題は出てきます。
そういった要求スペックを満たすコードを作るのが自分たちの役目だと思ってますし そこがプログラムの楽しさでもあると思ってます。

システムを作る場合にはそういった情報も漏らさず確認、推測していきたいですね

WordPressにて記事を投稿できなくなる問題を解決

先月からブログを投稿できなくなっておりました。
手が空いたので本日調査したところ、無事解決できたので、
以下にまとめます。

#症状
WordPressの投稿画面にて、寄稿者権限でないにも関わらず、「公開」ボタンが非表示となり、
代わりに「レビュー待ちとして送信」ボタンが表示されていました。
「レビュー待ちとして送信」ボタンをクリックするとエラーになり、
投稿はおろかレビュー待ちとして送信もできず。

#解決策
プラグイン Incorrect Datetime Bug Fix (※) をインストールして有効化しました。
※データの日付が ’0000-00-00 00:00:00′ の場合エラーとする設定を無効化するプラグイン
 Incorrect Datetime Bug Fix – WordPress plugin | WordPress.org

#経緯
レンタルサーバーの更新が先月頭にあったのでそれによるものではないかと言う話になり、
レンタルサーバー運営に問い合わせ。

WordPressが古過ぎるのが原因ではないかと言われる。

WordPressの更新は手間なので一旦保留に。

他の解決策を探す。
一番初めに見つかるのはDBの容量不足だが、容量は問題なし。

解決策は見つからないものの、wp-config.phpにて
define('WP_DEBUG', true);
を設定するとデバッグモードとなりエラーログなど出力できると判明。
WordPressで新規投稿ができなくなった原因がプラグインだった | #interest_ae

レンタルサーバーコントロールパネルから wp-config.php を開き、デバッグモードをオンに。

投稿画面を開くと以下のエラーログが表示。
 WordPress database error: [Incorrect datetime value: '0000-00-00 00:00:00' for column 'post_date_gmt' at row 1]
 INSERT INTO …
 …
 VALUES (…,’0000-00-00 00:00:00′,…)

エラー内容で検索して解決策記載ページを発見!
MySQLで設定変えたら、WordPressで投稿時に「Incorrect datetime value」エラーが出たので対処法 – たぬきのぶろぐ

#推測される原因
推測なのですが、おそらくレンタルサーバーの更新によりMySqlのバージョンが更新され、
その際に日付が無効値 ‘0000-00-00’ の場合に
エラーとなる設定が追加されたのではないかと思われます。
しかしWordPressの投稿開始時にDBに仮挿入される初期データ内の日付が、
ログを見るに ‘0000-00-00’ のため、投稿開始時にエラーとなり投稿できなくなってしまったのかと。
その問題となる設定を Incorrect Datetime Bug Fix プラグインは無効化してくれるので、
エラーにならずに投稿開始できるようになったのでしょう。

調べてみたらすんなり解決して良かったです。
毎度のことながら、Webの先人達に感謝!

OpenCV4 × YOLOによる物体検出技術の製造業向け応用

近年、製造業における品質管理や自動化のニーズが高まる中、画像処理技術の進化が注目されています。弊社では現在、社内画像処理ライブラリにOpenCV4YOLO(You Only Look Once)を組み合わせた物体検出機能の追加を進めており、生産ラインでの外観検査や誤組検査への応用を視野に入れた事前検証を行っています。

OpenCV4とYOLOの技術的特徴

OpenCVは画像処理の定番ライブラリであり、フィルタ処理や輪郭抽出、幾何変換など多彩な機能を備えています。一方、YOLOは深層学習ベースの物体検出アルゴリズムで、画像内の複数の対象物を高速かつ高精度に検出できる点が特長です。これらを組み合わせることで、従来のルールベースの検査では困難だった複雑な形状や微細な違いの検出が可能になります。

製造現場での活用例

  • 外観検査:製造工程でのOK品/NG品をリアルタイムで選別
  • 誤組検査:部品の有無や配置の誤りを自動判定
  • 工程監視:作業手順の逸脱や異常動作の検出

これらの検査は、従来は人手に頼っていた部分が多く、検査精度や作業負荷に課題がありました。画像処理による自動化は、検査品質の均一化と省人化を同時に実現する手段として、今後ますます重要性を増すと考えられます。

今後の予定

弊社では、検証段階で得られた知見をもとに、製造業向けに最適化された画像処理ライブラリの開発を進めてまいります。現場のニーズに即した柔軟なソリューション提供を目指しています。

Macの容量削減

VS for Macがサポート終了してからはMacで開発を行うことがなくなったため、
Macの用途はもっぱら次の3つです。
・MAUIのビルド(のためにWindowsのVSとペアリング)
・iPhoneアプリのリリース申請
・iPhoneアプリの証明書更新
今回MAUI9.0(.NET9.0)に対応する必要が生じ、
そのビルドのためにXcodeのバージョンアップを行おうと思ったのですが、
OSが対応していなかったためまずはOSの更新から行うことになりました。

しかし、Macの容量はもうパンパンで残り2GB(そんなことあります?)となっていたので、
Finder上で目に付いた不要そうなファイルを整理しました。
それにより空きが8GB弱となり、ダウンロードされるOSの容量7GB弱分を確保できたので
意気揚々と更新を開始したのですが、容量が足りないと怒られました。
更新時に25GB弱必要だそうです。Oh…

設定のストレージから整理しろ(※)と言われたのでストレージを見てみたところ、
詳細を確認可能な項目の内、デベロッパの項目が47GBと抜きんでていました。
※Macにいまだに不慣れなのでiOSと同じ形式でストレージが管理できるの失念していました
中身を見てみるとXcode関係で、
iOSデバイスサポートのセクションに数GB単位の項目がずらりと並んでいました。
消せそうだと思い調べてみたところ、

ここは、実機デバッグをするとシンボル情報などが実機からダウンロードされて保存されるようです。
実機のOSバージョン毎に追加され、一つが大きいので、肥大化しやすいようです。
消しても次回接続したときにまた作成されます。

引用元:開発macの空き容量を20GB以上増やした。 #iOS – Qiita

とのことだったので全て削除して50GB以上の空きができ、
無事OSの更新を開始できました。
長年デバッグのために接続したデバイス・OSの情報が蓄積され続けていたのですね。。。

以下のキャプチャは消している最中のものです。

大分容量に余裕が出たので、
MAUIのビルドのためにVSとMacをペアリングする際によく容量が少なくなってると注意を受けて
Xamarinのキャッシュを削除していたのですが、その必要もなくなりそうで良かったです。

以上です。

特定の言語

こんにちは mtj です

自分は昔はCOBOLだったり JAVAだったりそれこそC,C++等も覚えたりしましたが
JAVAを勉強している時に別の学生に言われたことは「C++が一番なんじゃないの?」のようなセリフでした
おそらくどこかの教授の受売のような話なのでしょうが 自分は用途に対する利点と欠点はあったとしても〇〇の言語が一番のような話は無いと思っています。

同じようにCが基礎 だから覚えないといけない という話も基本無視するレベルの話だと思っています。

言語はその時々で必要な物を選択できてこそエンジニアだと思っています
時々は必要な開発期間、コスト等すべて含めてとなります

C#よりC++のほうが実行速度が早くとも 実行時間が100秒が99秒になるレベルであればコストが一番低い方を選択するべきだと思っています