004 レポート Python用の国産GUIライブラリ公開 コード掲載
005 レポート Open Source Summit + AI_dev開催
製品レビュー 組み込み機器「ラズパイ AI HAT+」
007 NEWS FLASH
008 特集1 Streamlitで作るデータ分析Webアプリ/邑川真也 コード掲載
018 特集2 Oracleデータベース入門/宮本拓弥、百木和美、武井菜々子、北村海人
034 イベントレポート「第35回 プロコン」/塩田哲也
036 Raspberry Pi Pico W/WHで始める電子工作/米田聡
040 Pythonあれこれ/飯尾淳 コード掲載
046 Markdownを活用する/藤原由来 コード掲載
056 中小企業手作りIT化奮戦記/菅雄一
062 香川大学SLPからお届け!/原口莉奈 コード掲載
068 これだけは覚えておきたいLinuxコマンド/大津真
076 Techパズル/gori.sh
077 コラム「仕組みを作るSE」/シェル魔人
シェルスクリプトマガジンvol.93 Web掲載記事まとめ
レポート1(Vol.93掲載)
著者:末安 泰三
クジラ飛行机氏は2024年11月7日、手軽にGUIを作成可能なPythonライブラリ「TkEasyGUI」のバージョン1.0系列をリリースした。バージョン1.0系列では、複数項目を入力できるフォームダイアログが追加されるなどの機能拡充やバグ修正が施されている。
シェルスクリプトマガジン Vol.93は以下のリンク先でご購入できます。
図1 個人情報を入力させるウィンドウを作成するコードの例
1 2 3 4 5 6 7 8 9 10 11 12 |
import TkEasyGUI as eg layout = [ [eg.Text('氏名', size=(15, 1)), eg.InputText()], [eg.Text('住所', size=(15, 1)), eg.InputText()], [eg.Text('電話番号', size=(15, 1)), eg.InputText()], [eg.Text()], [eg.Button('OK'), eg.Button('Cancel')] ] with eg.Window('個人情報を入力', layout) as window: for event in window.event_iter(): pass |
図3 フォームダイアログを表示するコードの例
1 2 3 4 |
iimport TkEasyGUI as eg form = eg.popup_get_form(["氏名", "住所", "電話番号"], title="個人情報の入力") |
特集1 Streamlitで作るデータ分析Webアプリ(Vol.93記載)
著者:邑川 真也
「Streamlit」は、OSS(オープンソースソフトウエア)のPython向けフレームワーク(ライブラリ)です。データの可視化機能や分析機能、ダッシュボードなどを備えるWebアプリケーションを、Pythonを使って迅速かつ容易に作成できます。本特集では、さまざまなWebアプリケーションを実際に作成しながら、基本的なデータの可視化方法から、ノーコードでデータ分析を実現する方法まで、Streamlitのさまざまな活用方法を紹介します。
シェルスクリプトマガジン Vol.93は以下のリンク先でご購入できます。
図2 HTMLとCSS、JavaScriptを用いて棒グラフを描くコードの例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Bar Chart Example</title> <style> .chart-container { width: 70%; margin: 0 auto; } </style> </head> <body> <div class="chart-container"> <canvas id="myBarChart"></canvas> </div> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> <script> var ctx = document.getElementById('myBarChart').getContext('2d'); var myBarChart = new Chart(ctx, { type: 'bar', data: { labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'], datasets: [{ label: 'Sample Data', data: [65, 59, 80, 81, 56, 55, 40], backgroundColor: 'rgba(54, 162, 235, 0.2)', borderColor: 'rgba(54, 162, 235, 1)', borderWidth: 1 }] }, options: { scales: { y: { beginAtZero: true } } } }); </script> </body> </html> |
図3 Streamlitを用いて棒グラフを描くコードの例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import streamlit as st import pandas as pd import altair as alt data = pd.DataFrame({ 'Month': ['January', 'February', 'March', 'April', 'May', 'June', 'July'], 'Value': [65, 59, 80, 81, 56, 55, 40] }) chart = alt.Chart(data).mark_bar().encode( x='Month', y='Value', color=alt.value('steelblue') ) st.title('Bar Chart Example') st.altair_chart(chart, use_container_width=True) |
図6 シンプルなWebアプリケーションのコード
1 2 3 4 |
import streamlit as st # タイトルを表示 st.title("シェルスクリプトマガジン") |
図9 架空の株価データを生成して表示するコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
import streamlit as st import pandas as pd import numpy as np from datetime import datetime, timedelta # 株価データを生成(仮のデータ) dates = [datetime.today() - timedelta(days=i) for i in range(14)] dates.reverse() data = { 'Date': dates, 'Google': np.random.uniform(2700, 3000, 14), 'Apple': np.random.uniform(140, 160, 14), 'Meta': np.random.uniform(320, 350, 14), 'Amazon': np.random.uniform(3300, 3500, 14) } # データフレームに変換 df = pd.DataFrame(data) df.set_index('Date', inplace=True) # タイトルを表示 st.title("株価データの可視化") # データフレームの表示 st.dataframe(df) |
図14 「data.csv」ファイルに記述するデータ(抜粋)
1 2 3 4 5 6 7 |
Case ID,Company,Inquiry Summary,Status,Start Date,Severity 1,Acme Corp,大規模データセットに対するクエリの最適化について,Open,2024-05-19,Sev4 2,Globex Inc,DWHのベストプラクティスについて,Open,2024-05-26,Sev3 3,Globex Inc,データロード時のエラー,Open,2024-05-21,Sev3 4,Soylent Corp,新機能の有効化について,Open,2024-05-22,Sev2 5,Soylent Corp,レプリケートに失敗しています,Open,2024-05-22,Sev3 (略) |
図15 ダッシュボードアプリのコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
import streamlit as st import pandas as pd import numpy as np import matplotlib.pyplot as plt # ページ設定 st.set_page_config( page_title="サポートデスクダッシュボード", initial_sidebar_state="expanded", layout="wide" ) st.title("サポートデスクダッシュボード") # チケット一覧を表示 st.subheader("チケット一覧") # データ読み込み df = pd.read_csv("data.csv") with st.container(border=True, height=100): filter1, filter2 = st.columns(2) with filter1: # ステータスによるフィルタ status_list = df['Status'].unique() selected_status = st.multiselect("ステータス", options=status_list, default=status_list) with filter2: # 重要度によるフィルタ severity_list = df['Severity'].unique() selected_severity = st.multiselect("重要度", options=severity_list, default=severity_list) # フィルタを適用 if selected_status or selected_severity: df = df[(df['Status'].isin(selected_status) & df['Severity'].isin(selected_severity))] # データを表示 st.dataframe(df, use_container_width=True) # グラフを横並びに表示するレイアウトを作成 st.markdown("") col1, col2 = st.columns(2) # 縦棒グラフでステータスの数を表示 with col1: st.subheader("チケットごとのステータス集計") fig, ax = plt.subplots() status_counts = df["Status"].value_counts() ax.bar(status_counts.index, status_counts.values, color="skyblue") ax.set_xlabel("Status") ax.set_ylabel("Count") ax.set_title("Tickets by Status") with st.container(border=True): st.pyplot(fig) # 縦棒グラフで重要度の数を表示 with col2: st.subheader("チケットごとの重要度集計") severity_counts = df["Severity"].value_counts() fig, ax = plt.subplots() ax.bar(severity_counts.index, severity_counts.values, color="coral") ax.set_xlabel("Severity") ax.set_ylabel("Count") ax.set_title("Tickets by Severity") with st.container(border=True): st.pyplot(fig) # CSVダウンロード csv = df.to_csv(index=False) st.download_button( label="Download CSV", data=csv, file_name='sample_data.csv', mime='text/csv' ) |
図17 「config.toml」ファイルに記述するテーマ設定の例
1 2 3 4 5 6 |
[theme] primaryColor = "#F14143" # プライマリカラー backgroundColor = "#101216" # 背景色 secondaryBackgroundColor = "#191B20" # セカンダリ背景色 textColor = "#F9F9F9" # テキストカラー font = "sans serif" # フォント |
図18 「pygw_test.py」ファイルに記述するコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
from pygwalker.api.streamlit import StreamlitRenderer import pandas as pd import streamlit as st # Streamlitの画面幅を調整 st.set_page_config( page_title="Use Pygwalker In Streamlit", layout="wide" ) # データの取得 df = pd.read_csv("https://kanaries-app.s3.ap-northeast-1.amazonaws.com/public-datasets/bike_sharing_dc.csv") # PYGWalkerによる可視化 pyg_app = StreamlitRenderer(df) pyg_app.explorer() |
Pythonあれこれ(Vol.93掲載)
著者:飯尾 淳
本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温かく見守ってください。皆さんと共に勉強していきましょう。第23回では、機械学習ライブラリ「scikit-learn」で何ができるのかを紹介した上で、データを二つのクラスに分類するPythonプログラムを作成します。
シェルスクリプトマガジン Vol.93は以下のリンク先でご購入できます。
図9 テストデータを作成するPythonコード
1 2 3 4 5 6 7 8 9 10 11 12 |
import pandas as pd from sklearn.datasets import make_classification (data, label) = make_classification(n_samples=1000, n_features=2, n_classes=2, n_informative=2, n_redundant=0, random_state=133) df = pd.DataFrame({'x1':[d[0] for d in data], 'x2':[d[1] for d in data], 'label':label}) df.plot.scatter(x='x1', y='x2', c=df['label'].map({0:'blue', 1:'red'}), s=5) |
図10 LinearSVCを用いて学習と分類をするPythonコード
1 2 3 4 5 |
from sklearn.svm import LinearSVC from sklearn.metrics import accuracy_score clf = LinearSVC(random_state=0, dual='auto', tol=1e-5) clf.fit(data, label) accuracy_score(clf.predict(data), label) |
図11 分離面を描画するPythonコード
1 2 |
from mlxtend.plotting import plot_decision_regions plot_decision_regions(data, label, clf=clf) |
図13 NuSVCを用いて学習と分類をするPythonコード
1 2 3 4 |
from sklearn.svm import NuSVC clf = NuSVC(nu=0.1, random_state=0) clf.fit(data, label) accuracy_score(clf.predict(data), label) |
図15 SVCを用いて学習と分類をするPythonコード(その1)
1 2 3 4 |
from sklearn.svm import SVC clf = SVC(gamma=0.1, random_state=0) clf.fit(data, label) accuracy_score(clf.predict(data), label) |
図17 SVCを用いて学習と分類をするPythonコード(その2)
1 2 3 4 |
from sklearn.svm import SVC clf = SVC(gamma=0.1, random_state=0) clf.fit(data, label) accuracy_score(clf.predict(data), label) |
Markdownを活用する(Vol.93掲載)
著者:藤原 由来
本連載では文書の装飾・構造付けを手軽に行える記法であるMarkdownを用いて、さまざまな文書や成果物を作成する方法を紹介します。今回は、プレゼンテーション用のスライドを作成するためのプレゼンテーションアプリ「Marp」について解説します。
シェルスクリプトマガジン Vol.93は以下のリンク先でご購入できます。
図1 Marp用にMarkdownで記述したテキストの例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
--- marp: true --- # 美味しいカレーの作り方 シェル花子 --- # 材料 - 玉ねぎ - にんじん - じゃがいも - 肉 - カレールー - 水 --- # 作り方 1. 材料を切る 2. 肉を炒める 3. 野菜を加える 4. 水を加えて煮る 5. カレールーを入れる |
図8 見出しレベル1をスライドの区切りにしたMarkdown形式のテキスト例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
--- marp: true headingDivider: 1 --- # 美味しいカレーの作り方 ## シェル花子 # 材料 ## ここに ### 材料を書く # 作り方 ## 適当に作る |
図11 スライドの背景や文字色を変更したMarkdown形式のテキスト例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
--- marp: true --- # 通常のスライド --- <!-- _backgroundColor: black _color: white --> # 背景が黒く文字が白いスライド --- <!-- _backgroundImage: "linear-gradient(to bottom, #67b8e3, #0288d1)" _color: white --> # 背景が青いグラデーションのスライド --- <!-- _backgroundImage: url('cat1.png') --> # 背景が画像のスライド |
図13 背景画像表示とぼかしフィルタ適用のMarkdown形式のテキスト例
1 2 3 4 5 6 7 8 9 10 11 12 13 |
--- marp: true --- # 背景画像を表示する ![bg cover](cat1.png) --- # 背景画像にぼかしを入れる ![blur bg cover](cat1.png) |
図15 背景画像の横並びと縦並びのMarkdown形式のテキスト例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
--- marp: true --- # 背景に横並びに配置 ![bg](cat1.png) ![bg](cat1.png) ![bg](cat1.png) --- # 背景に縦並びに配置 ![bg vertical](cat1.png) ![bg](cat1.png) ![bg](cat1.png) |
図20 ChatGPTが生成したMarkdown形式のテキスト
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
--- marp: true theme: default paginate: true backgroundColor: #f0f0f0 --- # 知られざるインドカレー ### シェル花子 --- # 目次 1. 北インド: パニール・バターマサラ 2. 南インド: チェッティナード・チキンカレー 3. 西インド: ゴア・フィッシュカレー 4. 東インド: コサ・マンシャ 5. 中央インド: ダールバーフライ 6. 北東インド: アクホニカレー --- # 1. 北インド: パニール・バターマサラ - クリーミーで濃厚なグレイビー - トマト、バター、クリームを使用 - パニール(カッテージチーズ)入り - パンジャーブ地方で有名 - ナンやバスマティライスと食べる --- (略) |
香川大学SLPからお届け!(Vol.93掲載)
著者:原口 莉奈
私は最近、RPG(Role-Playing Game)の戦闘シーンをC++言語で作成しました。作成には、無償で利用できるゲーム開発用ライブラリ「DXライブラリ」(https://dxlib.xsrv.jp/)を用いています。今回は、その一部であるコマンド選択画面の実装について紹介します。
シェルスクリプトマガジン Vol.93は以下のリンク先でご購入できます。
図2 ビルド用のバッチファイル「build.bat」に記述する内容
1 2 3 4 5 6 |
g++ -c Main.cpp -DDX_GCC_COMPILE -I"DxLib" g++ -o game.exe Main.o -L"DxLib" -lgcc -lDxLib -lDxUseCLib ^ -lDxDrawFunc -ljpeg -lpng -lzlib -ltiff -ltheora_static ^ -lvorbis_static -lvorbisfile_static -logg_static ^ -lbulletdynamics -lbulletcollision -lbulletmath -lopusfile ^ -lopus -lsilk_common -lcelt |
図3 「Main.cpp」ファイルに記述するコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#define WIN_X 256 #define WIN_Y 224 #include <DxLib.h> int WINAPI WinMain( _In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR IpCmdLine, _In_ int nShowCmd) { ChangeWindowMode(TRUE); DxLib_Init(); // ウィンドウ初期化 SetWindowText("game"); // タイトル SetGraphMode(WIN_X, WIN_Y, 32); // 解像度と色深度 SetBackgroundColor(255, 255, 255); // 背景色 WaitKey(); DxLib_End(); return 0; } |
図5 「Main.cpp」ファイルに記述するコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
#define WIN_X 256 #define WIN_Y 224 #include <DxLib.h> #include "Status.h" #include "Battle.h" int WINAPI WinMain( _In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR IpCmdLine, _In_ int nShowCmd) { ChangeWindowMode(TRUE); DxLib_Init(); SetWindowText("game"); SetGraphMode(WIN_X, WIN_Y, 32); SetBackgroundColor(255, 255, 255); SetDrawScreen(DX_SCREEN_BACK); // 裏画面描画 SetWindowSize(1024, 960); // ウィンドウサイズ Battle.materialLoad(); while (ProcessMessage() == 0) { SetBackgroundColor(0, 0, 0); Battle.BattleDraw(); } DxLib_End(); return 0; } |
図6 「Status.h」ファイルに記述するコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
class BATTLE { public: struct Status { char name[50]; int HP, ATK, DEF; //体力、攻撃力、防御力 }; struct Status player = { "player",30, 10, 5 }; struct Status villain = { "villain",30, 7, 5 }; struct Status* Player = &player; const char* str[4] = { "たたかう", "まもる", "どうぐ", "にげる" }; const char* item[1] = { "回復" }; int firstsym = 80; int sym = firstsym; int sym_limit = firstsym + (4 - 1) * 17; int symbol = -1, symnext = 0; int pattern = 1; int firstarrow = 80; int arrow = firstarrow; int limit; // firstarrow + (num - 1) * 17 int a_posi = -1; int re = 0; int black, white, ms; void materialLoad(); void BattleDraw(); void one(); void two(); void Choices(int x, const char* str[], int n); private: }; |
図7 「Battle.h」ファイルに記述するコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
BATTLE Battle; void BATTLE::materialLoad() { black = GetColor(0, 0, 0); white = GetColor(255, 255, 255); ms = CreateFontToHandle("MS ゴシック", 10, 6, DX_FONTTYPE_ANTIALIASING_4X4); } void BATTLE::Choices(int x, const char* str[], int n) { int w = 0; for (int i = 0; i < n; i++) { DrawBox(x, firstarrow + w, x + 80, firstarrow + 16 + w, white, TRUE); DrawFormatStringToHandle(x + 5, firstarrow + 5 + w, black, ms, str[i]); w += 17; } } // 初期表示 void BATTLE::one() { sym = firstsym; pattern = 2; } // 矢印の移動 void BATTLE::two() { if (symnext == 0) { switch (WaitKey()) { case KEY_INPUT_DOWN: if (sym != sym_limit) sym += 17; break; case KEY_INPUT_UP: if (sym != firstsym) sym -= 17; break; case KEY_INPUT_ESCAPE: DxLib_End(); break; } if (symbol != -1) symnext++; } } void BATTLE::BattleDraw() { SetDrawScreen(DX_SCREEN_BACK); ClearDrawScreen(); switch(pattern): if (pattern == 1) one(); else if (pattern == 2) two(); Choices(160, str, 4); DrawFormatStringToHandle(144, sym, white, ms, "→"); ScreenFlip(); } |
図9 「Status.h」ファイルにHpDraw()関数の記述を追加
1 2 3 4 |
(略) void HpDraw(); void BattleDraw(); (略) |
図10 「Battle.h」ファイルのBattleDraw()関数定義部分をこのように書き換える
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
(略)void BATTLE::HpDraw() { DrawBox(180, 160, 250, 184, white, FALSE); DrawBox(180, 186, 250, 210, white, FALSE); DrawFormatStringToHandle(182, 162, white, ms, "villain\nHP: %d", villain.HP); DrawFormatStringToHandle(182, 188, white, ms, "player\nHP:%d", player.HP); } void BATTLE::BattleDraw() { SetDrawScreen(DX_SCREEN_BACK); ClearDrawScreen(); switch(pattern): if (pattern == 1) one(); else if (pattern == 2) two(); Choices(160, str, 4); DrawFormatStringToHandle(144, sym, white, ms, "→"); HpDraw(); ScreenFlip(); } |
シェルスクリプトマガジンvol.92 Web掲載記事まとめ
004 レポート 仮想化ソフト「VirtualBox」の新版リリース
005 レポート マイコンボード「Raspberry Pi Pico 2」登場
006 製品レビュー 組み込み機器「CH32V003ボード」
007 NEWS FLASH
008 特集1 古いRaspberry Piを復活させる/麻生二郎
016 特集2 統合運用管理ソフトウエア Hinemos入門/倉田晃次、加藤達也
026 特別企画 イノシシ撃退機を作る ソフト作成編/米田聡 コード掲載
036 特別企画 Rocky Linux 9のインストール/麻生二郎
042 Pythonあれこれ/飯尾淳 コード掲載
048 Markdownを活用する/藤原由来 コード掲載
058 中小企業手作りIT化奮戦記/菅雄一
062 香川大学SLPからお届け!/池内稜來斗 コード掲載
068 これだけは覚えておきたいLinuxコマンド/大津真
078 Techパズル/gori.sh
079 コラム「SEの働き方の新しい仕組みを作る」/シェル魔人
特別企画 イノシシ撃退機を作る ソフト作成編(Vol.92記載)
著者:米田 聡
「Interface」「CQ ham radio」「トランジスタ技術」などの雑誌を出版するCQ出版社の電子部品セット「イノシシ撃退機部品セットVer.1」を組み立て、ソフトウエアに一工夫を加えてイノシシ撃退機を作ります。今回は完成したハードウエア向けにソフトウエアを作成します。
シェルスクリプトマガジン Vol.92は以下のリンク先でご購入できます。
図18 PWM機能の初期化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
(略) #define PWM_PIN 20 (略) #define PWM_SLICE_NUM pwm_gpio_to_slice_num(PWM_PIN) (略) void pwm_gpio_init() { (略) gpio_set_function(PWM_PIN, GPIO_FUNC_PWM); pwm_set_wrap(PWM_SLICE_NUM, 4095); pwm_set_clkdiv(PWM_SLICE_NUM, 2); pwm_set_irq_enabled(PWM_SLICE_NUM, true); irq_set_exclusive_handler(PWM_IRQ_WRAP, on_pwm_wrap); irq_set_enabled(PWM_IRQ_WRAP, true); (略) } (略) |
図20 割り込みハンドラ
1 2 3 4 5 6 7 8 9 10 11 |
void on_pwm_wrap() { pwm_clear_irq(PWM_SLICE_NUM); (略) uint16_t duty = convert_flash_data_to_duty(spi_rx_buf[flash_data_index], spi_rx_buf[flash_data_index + 1]); pwm_set_chan_level(PWM_SLICE_NUM, PWM_CHAN_A, duty); flash_data_index += 2; if (flash_data_index >= 256) { flash_data_index = 0; } (略) } |
図21 ダブルバッファを使用する修正
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
■34~37行目の修正コード #ifdef FLASH_DATA_TO_DUTY // ダブルバッファ化 uint8_t spi_rx_buf[2][256]; volatile uint32_t flash_data_index = 0; #endif ■45~54行目の修正コード // バッファ選択 volatile uint32_t buf_flip = 0; void on_pwm_wrap() { pwm_clear_irq(PWM_SLICE_NUM); #ifdef FLASH_DATA_TO_DUTY uint16_t duty = convert_flash_data_to_duty(spi_rx_buf[buf_flip][flash_data_index], spi_rx_buf[buf_flip][flash_data_index + 1]); pwm_set_chan_level(PWM_SLICE_NUM, PWM_CHAN_A, duty); flash_data_index += 2; if (flash_data_index >= 256) { // バッファ切り替え buf_flip ^= 1; flash_data_index = 0; } } ■87~105行目の修正コード void play_sound() { // バッファ切り替え(ローカル) int flip = 0; flash_data_index = 0; buf_flip = 0; pwm_set_enabled(PWM_SLICE_NUM, true); //mu0918 for(uint32_t address = 0; address < FLASH_SIZE; address += 256) { gpio_put(PICO_DEFAULT_SPI_SSEL_PIN, 0); // Set the CS pin to active LOW // Send the read command uint8_t spi_tx_buf[] = {0x03, (address >> 16) & 0xFF, (address >> 8) & 0xFF, address & 0xFF}; spi_write_blocking(spi, spi_tx_buf, 4); // Read the data spi_read_blocking(spi, 0, spi_rx_buf[flip], 256); // バッファ切り替え flip ^= 1; gpio_put(PICO_DEFAULT_SPI_SSEL_PIN, 1); // Set the CS pin to non-active HIGH } pwm_set_enabled(PWM_SLICE_NUM, false); //mu0918 } |
Pythonあれこれ(Vol.92掲載)
著者:飯尾 淳
本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温かく見守ってください。皆さんと共に勉強していきましょう。第22回では、機械学習ライブラリ「PyTorch」を使って、画像内の、歩行者が写っている領域を自動認識するPythonプログラムを作成します。
シェルスクリプトマガジン Vol.92は以下のリンク先でご購入できます。
図3 47番のデータを確認するPythonコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
import matplotlib.pyplot as plt from torchvision.io import read_image image =read_image( "data/PennFudanPed/PNGImages/FudanPed00047.png") mask = read_image( "data/PennFudanPed/PedMasks/FudanPed00047_mask.png") plt.figure(figsize=(16, 8)) plt.subplot(121) plt.title("Image") plt.imshow(image.permute(1, 2, 0)) plt.subplot(122) plt.title("Mask") plt.imshow(mask.permute(1, 2, 0)) |
図5 PennFudanDataSetクラスを定義するPythonコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
import os import torch from torchvision.io import read_image from torchvision.ops.boxes import masks_to_boxes from torchvision import tv_tensors from torchvision.transforms.v2 import functional as F class PennFudanDataset(torch.utils.data.Dataset): def __init__(self, root, transforms): self.root = root self.transforms = transforms # イメージデータとマスクデータをロードし、ソートして並べておく self.imgs = \ list(sorted(os.listdir(os.path.join(root, "PNGImages")))) self.masks = \ list(sorted(os.listdir(os.path.join(root, "PedMasks")))) def __getitem__(self, idx): # イメージとマスクのロード img_path = \ os.path.join(self.root, "PNGImages", self.imgs[idx]) mask_path = \ os.path.join(self.root, "PedMasks", self.masks[idx]) img = read_image(img_path) mask = read_image(mask_path) # インスタンスは色別にエンコードされていて…… obj_ids = torch.unique(mask) # 最初のIDは背景色なので削除 obj_ids = obj_ids[1:] num_objs = len(obj_ids) # 色別にエンコードされているマスクを2値マスクに分ける masks = \ (mask == obj_ids[:, None, None]).to(dtype=torch.uint8) # マスクデータに対してバウンディングボックスを求める boxes = masks_to_boxes(masks) labels = torch.ones((num_objs,), dtype=torch.int64) image_id = idx area = (boxes[:, 3] - boxes[:, 1]) * (boxes[:, 2] - boxes[:, 0]) # 群衆でないと仮定 iscrowd = torch.zeros((num_objs,), dtype=torch.int64) # tv_tensorsイメージに変換する img = tv_tensors.Image(img) target = {} target["boxes"] = \ tv_tensors.BoundingBoxes(boxes, format="XYXY", canvas_size=F.get_size(img)) target["masks"] = tv_tensors.Mask(masks) target["labels"] = labels target["image_id"] = image_id target["area"] = area target["iscrowd"] = iscrowd if self.transforms is not None: img, target = self.transforms(img, target) return img, target def __len__(self): return len(self.imgs) |
図6 get_model_instance_segmentation()関数を定義するPythonコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
import torchvision from torchvision.models.detection.faster_rcnn \ import FastRCNNPredictor from torchvision.models.detection.mask_rcnn \ import MaskRCNNPredictor def get_model_instance_segmentation(num_classes): # COCOで事前学習済みのモデルデータをロードする model = torchvision.models.detection.maskrcnn_resnet50_fpn( weights="DEFAULT") # 入力特徴量 in_features = \ model.roi_heads.box_predictor.cls_score.in_features # num_classesで指定するクラスの分類器をセット(今回は2クラス) model.roi_heads.box_predictor = \ FastRCNNPredictor(in_features, num_classes) # マスク判別器も同様に設定 in_features_mask = \ model.roi_heads.mask_predictor.conv5_mask.in_channels hidden_layer = 256 model.roi_heads.mask_predictor = MaskRCNNPredictor( in_features_mask, hidden_layer, num_classes ) return model |
図7 get_transform()関数を定義するPythonコード
1 2 3 4 5 6 7 8 9 |
from torchvision.transforms import v2 as T def get_transform(train): transforms = [] if train: transforms.append(T.RandomHorizontalFlip(0.5)) transforms.append(T.ToDtype(torch.float, scale=True)) transforms.append(T.ToPureTensor()) return T.Compose(transforms) |
図8 追加学習の準備をするためのPythonコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
import utils from engine import train_one_epoch, evaluate device = torch.device('cuda') \ if torch.cuda.is_available() else torch.device('cpu') # 背景と人物の2クラス判別器を作成する num_classes = 2 # データセットはすでにロード済みのものを用いる dataset = \ PennFudanDataset('data/PennFudanPed', get_transform(train=True)) dataset_test = \ PennFudanDataset('data/PennFudanPed', get_transform(train=False)) # データセットを学習用とテスト用に分ける indices = torch.randperm(len(dataset)).tolist() dataset = torch.utils.data.Subset(dataset, indices[:-50]) dataset_test = torch.utils.data.Subset( dataset_test, indices[-50:]) # 学習と評価用のデータローダを定義 data_loader = torch.utils.data.DataLoader( dataset, batch_size=2, shuffle=True, num_workers=4, collate_fn=utils.collate_fn ) data_loader_test = torch.utils.data.DataLoader( dataset_test, batch_size=1, shuffle=False, num_workers=4, collate_fn=utils.collate_fn ) # 先ほど定義したヘルパーファンクションを用いてモデルを用意 model = get_model_instance_segmentation(num_classes) # モデルをデバイスに結び付ける model.to(device) # 最適化器を作成 params = [p for p in model.parameters() if p.requires_grad] optimizer = torch.optim.SGD( params, lr=0.005, momentum=0.9, weight_decay=0.0005 ) # 学習レートのスケジューラを設定 lr_scheduler = torch.optim.lr_scheduler.StepLR( optimizer, step_size=3, gamma=0.1 ) |
図9 物体認識と領域セグメンテーションを実施するPythonコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
import matplotlib.pyplot as plt from torchvision.utils \ import draw_bounding_boxes, draw_segmentation_masks image = \ read_image("data/PennFudanPed/PNGImages/FudanPed00047.png") eval_transform = get_transform(train=False) model.eval() with torch.no_grad(): x = eval_transform(image) # RGBA -> RGBにコンバートしてデバイスにひも付ける x = x[:3, ...].to(device) predictions = model([x, ]) pred = predictions[0] image = (255.0 * (image - image.min()) / (image.max() - image.min())).to(torch.uint8) image = image[:3, ...] pred_labels = [f"pedestrian: {score:.3f}" for label, \ score in zip(pred["labels"], pred["scores"])] pred_boxes = pred["boxes"].long() output_image = draw_bounding_boxes(image, pred_boxes, pred_labels, colors="red") masks = (pred["masks"] > 0.7).squeeze(1) output_image = draw_segmentation_masks(output_image, masks, alpha=0.5, colors="blue") plt.figure(figsize=(12, 12)) plt.imshow(output_image.permute(1, 2, 0)) |
図11 Penn-Fudanデータセットを用いて追加学習するPythonコード
1 2 3 4 5 6 7 8 9 10 |
# 2エポックだけ学習させてみる num_epochs = 2 for epoch in range(num_epochs): # 10回ごとに表示させながら1エポックの学習を実行 train_one_epoch(model, optimizer, data_loader, device, epoch, print_freq=10) # 学習レートをアップデート lr_scheduler.step() # テストデータで評価 evaluate(model, data_loader_test, device=device) |
Markdownを活用する(Vol.92掲載)
著者:藤原 由来
本連載では文書の装飾・構造付けを手軽に行える記法であるMarkdownを用いて、さまざまな文書や成果物を作成する方法を紹介します。今回は前回に引き続き、Markdownを用いて書籍を制作できる組版アプリ「Vivliostyle」について解説します。特に、技術同人誌の制作方法と、印刷所へ入稿する際の注意
点について説明します。
シェルスクリプトマガジン Vol.92は以下のリンク先でご購入できます。
図7 書き換えたVivliostyleの設定ファイル
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
module.exports = { title: 'sample-book-kagakunofushigi', author: 'アンリイ・ファブル(大杉栄、伊藤野枝訳)', language: 'ja', size: 'JIS-B5', theme: '@vivliostyle/theme-techbook@^1.0.1', entryContext: './manuscripts', entry: [ 'index.md', '01.md', '02.md', '03.md', '04.md', '05.md', '06.md', '07.md', '08.md', '09.md', 'colophon.md', ], workspaceDir: '.vivliostyle', } |
図8 扉のindex.mdファイル
1 2 3 4 |
# 科学の不思議 アンリイ・ファブル(大杉栄、伊藤野枝訳) © 某サークル, 20xx |
図9 奥付のcolophon.mdファイル
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
## 科学の不思議 20xx年x月x日 初版発行 ---------------- - 発行 某サークル - 著者 アンリイ・ファブル - 翻訳 大杉栄、伊藤野枝 - 編集 シェル花子 - 連絡先 foo@example.com - 印刷 サンプル印刷 ---------------- 底本は青空文庫から転載・改変しました。 - ファーブル ジャン・アンリ『科学の不思議』(大杉 栄、伊藤 野枝 訳) © 某サークル, 20xx |
図11 my-theme.cssファイル内のCSSを反映する設定
1 2 3 4 |
theme: [ '@vivliostyle/theme-techbook@^1.0.1', 'themes/my-theme.css', ], |
図12 扉と奥付用のCSS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
/* 扉 */ section#title-page { display: flex; justify-content: center; align-items: center; text-align: center; height: 50vh; margin: 0; } /* 奥付 */ section#colophon { position: relative; float-reference: page; float: bottom; margin-bottom: 0; } |
図13 扉のindex.mdファイルの変更
1 2 3 4 5 6 7 8 9 |
<section id="title-page"> # 科学の不思議 アンリイ・ファブル(大杉栄、伊藤野枝訳) © 某サークル, 20xx </section> |
図14 奥付のcolophon.mdファイルの変更
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
<section id="colophon" role="doc-colophon"> ## 科学の不思議 20xx年x月x日 初版発行 ---------------- - 発行 某サークル - 著者 アンリイ・ファブル - 翻訳 大杉栄、伊藤野枝 - 編集 シェル花子 - 連絡先 foo@example.com - 印刷 サンプル印刷 ---------------- 底本は青空文庫から転載・改変しました。 - ファーブル ジャン・アンリ『科学の不思議』(大杉 栄、伊藤 野枝 訳) © 某サークル, 20xx </section> |
図17 目次を自動設定する設定
1 2 3 4 5 6 7 8 9 10 11 |
(略) entryContext: './manuscripts', toc: { htmlPath: 'toc.html', title: '目次', }, entry: [ 'index.md', { rel: 'contents' }, '01.md', (略) |
香川大学SLPからお届け!(Vol.92掲載)
著者:池内稜來斗
各都道府県の都道府県庁所在地を尋ねるクイズを出題するWebサイトをPHPで作成しました。今回は、そのWebサイトの概要や作成手順などを紹介します。PHPの実行環境や、各種サーバーソフトウエアは、「XAMPP」(https://www.apachefriends.org/)を使ってインストールします。
シェルスクリプトマガジン Vol.92は以下のリンク先でご購入できます。
図2 「db.sql」ファイルに記述するSQL文
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
use pref_capitals_quiz; insert into pref_capitals (id, name_p, name_c, image_p) values (1, '北海道', '札幌市', 'img/hokkaidou.png'), (2, '青森県', '青森市', 'img/aomori.png'), (3, '岩手県', '盛岡市', 'img/iwate.png'), (4, '秋田県', '秋田市', 'img/akita.png'), (5, '宮城県', '仙台市', 'img/miyagi.png'), (6, '山形県', '山形市', 'img/yamagata.png'), (7, '福島県', '福島市', 'img/fukushima.png'), (8, '茨城県', '水戸市', 'img/ibaraki.png'), (9, '栃木県', '宇都宮市', 'img/tochigi.png'), (10, '群馬県', '前橋市', 'img/gunma.png'), (11, '埼玉県', 'さいたま市', 'img/saitama.png'), (12, '千葉県', '千葉市', 'img/chiba.png'), (13, '東京都', '東京', 'img/tokyo.png'), (略) (47, '沖縄県', '那覇市', 'img/okinawa.png'); commit; |
図3 「index.html」ファイルに記述するコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<html lang=ja> <head> <meta charset="utf-8"> <title>都道府県庁所在地クイズ!!</title> </head> <body> <h1>都道府県庁所在地クイズ!!</h1> <p>表示された都道府県の都道府県庁所在地を回答してください。<br> 東京都の場合は、区名と地域名のどちらで回答しても構いません。<br> すべての都道府県について回答したら終了です。 </p> <button type="button" onclick="location.href='question.php'"> クイズを始める </button> </body> </html> |
図4 「question.php」ファイルに記述するコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
<?php session_start(); if (!isset($_SESSION['times'])) { $_SESSION['times'] = 0; $_SESSION['right'] = 0; } if ($_SESSION['times'] == 0) { $r = new \Random\Randomizer(); $order = $r->shuffleArray(range(1, 47)); $_SESSION['order'] = $order; } $order = $_SESSION['order']; $times = $_SESSION['times']; $pref = $order[$times]; ?> <html lang=ja> <head> <meta charset="UTF-8"> <title>都道府県庁所在地クイズ!!</title> </head> <body> <?php $link = mysqli_connect('localhost','root','', 'pref_capitals_quiz'); mysqli_set_charset($link,'utf8'); $sql = "SELECT id, name_p, name_c, image_p " . "FROM pref_capitals WHERE id = $pref"; $result = mysqli_query($link, $sql); $row = mysqli_fetch_assoc($result); $path = $row['image_p']; echo "<br>"; echo "問題", $times + 1; echo '<img src="' . $path . '" alt="' . $row['name_p'] . 'の地図">'; print(' '.$row['name_p']); ?> <form method="POST" class="form" action="answer.php"> <input type="text" name="Answer" class="input"> <p> <input type="submit" value="回答" class="submit"> </p> </form> </body> </html> |
図5 「answer.php」ファイルに記述するコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
<?php session_start(); $times = $_SESSION['times']; $order = $_SESSION['order']; $right = $_SESSION['right']; $pref = $order[$times]; ?> <html lang=ja> <head> <meta charset="UTF-8"> <title>都道府県庁所在地クイズ!!</title> </head> <body> <?php $received_Answer = $_POST['Answer']; $link = mysqli_connect('localhost','root','', 'pref_capitals_quiz'); mysqli_set_charset($link,'utf8'); $sql = "SELECT id, name_p, name_c, image_p " . "FROM pref_capitals WHERE id = $pref"; $result = mysqli_query($link, $sql); $row = mysqli_fetch_assoc($result); // 東京都かどうか if ($row['id'] == 13) { if (($received_Answer == $row['name_c']) || ($received_Answer == '新宿区')) { echo "正解\n"; $right++; } } else if (($received_Answer == $row['name_c']) || (($received_Answer . "市") == $row['name_c'])) { echo "正解\n"; $right++; } else { echo "不正解\n"; } $_SESSION['times'] = ++$times; $_SESSION['right'] = $right; echo "<br>"; echo "問題{$times} {$row['name_p']} の答え"; echo "<br>"; echo $row['name_c']; if ($times != 47) { echo ' <form method="POST" class="form" action="question.php"> <button type="submit">次の問題</button> </form>'; } ?> <form method="POST" class="form" action="result.php"> <button type="submit">回答終了</button> </form> </body> </html> |
図6 「result.php」ファイルに記述するコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
<?php session_start(); if (isset($_POST['reset'])) { $_SESSION['times'] = 0; $_SESSION['right'] = 0; header("Location: index.html"); } ?> <html lang=ja> <head> <meta charset="UTF-8"> <title>都道府県庁所在地クイズ!!</title> </head> <body> <?php echo "今回の正答数は<br>"; echo $_SESSION['times'], "問中", $_SESSION['right'], "問"; echo "<br>"; echo "お疲れさまでした<br>"; ?> <form method="POST" action="result.php"> <input type="submit" value="トップページに戻る" name="reset"> </form> </body> </html> |
Vol.92 補足情報
連載 香川大学SLPからお届け!
記事中で作成したクイズWebサイト用のコードや、データベースへのデータ入力用のファイルをまとめたアーカイブファイルがここからダウンロードできます。
情報は随時更新致します。
Markdownを活用する(Vol.91掲載)
著者:藤原 由来
本連載では文書の装飾・構造付けを手軽に行える記法であるMarkdownを用いて、さまざまな文書や成果物を作成する方法を紹介します。今回は、Markdownを用いて書籍を制作できる組版アプリ「Vivliostyle」の導入と、書籍作成の基本について説明します。
シェルスクリプトマガジン Vol.91は以下のリンク先でご購入できます。
図11 chap1.mdファイルの内容
1 2 3 4 5 6 7 8 |
# 後注と脚注の例 これは後注[^1]の例です。 インラインで後注^[これも章の後ろに表示される]もできます。 [^1]: 各章の後部にまとめて出力されるタイプの注釈 こちらは脚注<span class="footnote">ページ直下に表示される</span>です。 |
図12 chap2.mdファイルの内容
1 2 3 |
# {吾輩|わがはい}は猫である。 {吾輩|わがはい}は猫である。名前はまだ無い。 |
中小企業手作りIT化奮戦記(Vol.91掲載)
著者:佐藤 楓真
今回は、「焼きなまし法」と呼ばれる手法で、すべての都市を1回訪問して出発地点に帰るまでの最短経路を求める「巡回セールスマン問題」を近似的に解いてみます。焼きなまし法のベースとなる「山登り法」についても解説します。使用するプログラミング言語は、C++です。
シェルスクリプトマガジン Vol.91は以下のリンク先でご購入できます。
図3 図1の巡回セールスマン問題を山登り法で解くC++プログラムの例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
#include <iostream> #include <algorithm> using namespace std; #define TL 3 //制限時間 #define N 4 int d[N][N] = {{0, 2, 10, 5}, {2, 0, 5, 3}, {10, 5, 0, 4}, {5, 3, 4, 0}}; int ans[N+1] = {0, 1, 3, 2, 0}; int rand_num(void) { return rand() % N + 1; } int calc_dist(void) { int sum = 0; for (int i = 0; i < N; i++) { sum += d[ans[i]][ans[i+1]]; } return sum; } int main(void) { srand((unsigned)time(NULL)); int st_time = clock(); while ((clock() - st_time) / CLOCKS_PER_SEC < TL) { int L = rand_num(), R = rand_num(); if (L > R) swap(L, R); int bf_dist = calc_dist(); reverse(ans + L, ans + R); int af_dist = calc_dist(); if (bf_dist < af_dist) reverse(ans + L, ans + R); } for (int i = 0; i < N+1; i++) { cout << ans[i] + 1 << " "; } cout << endl << calc_dist() << endl; return 0; } |
図4 都市数「5000」のテスト用データを作成するC++プログラム
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
#include <iostream> #include <vector> #include <fstream> #include <string> using namespace std; #define N 5000 int main(void) { srand((unsigned)time(NULL)); for (int num = 1; num <= 100; num++) { vector<vector<int>> d(N, vector<int>(N)); for (int i = 0; i < N; i++) { for (int j = i; j < N; j++) { if (i == j) { d[i][j] = 0; } else { d[i][j] = rand() % 5000 + 1; } } } for (int i = 0; i < N; i++) { for (int j = 0; j < i; j++) { d[i][j] = d[j][i]; } } string name = "case" + to_string(num) + ".txt"; ofstream out(name); out << N << endl; for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { out << d[i][j] << " "; } out << endl; } } return 0; } |
図5 テスト用データに基づく巡回セールスマン問題を山登り法で解くC++プログラムの例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
#include <iostream> #include <algorithm> #include <vector> #include <string> #include <fstream> using namespace std; #define TL 3 //制限時間 int calc_dist(vector<vector<int>>& dist, vector<int>& ans, int N) { int sum = 0; for (int i = 0; i < N; i++) { sum += dist[ans[i]][ans[i+1]]; } return sum; } int rand_num(int N){ return rand() % N + 1; } int main(void) { long long ave = 0; for (int num = 1; num <= 100; num++) { srand((unsigned)time(NULL)); string name = "case" + to_string(num) + ".txt"; ifstream infile(name); int N; infile >> N; vector<vector<int>> d(N, vector<int>(N)); for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { infile >> d[i][j]; } } vector<int> ans(N + 1); for (int i = 0; i < N; i++) ans[i] = i; ans[N] = 0; int st_time = clock(); while ((clock() - st_time) / CLOCKS_PER_SEC < TL) { int bf_dist = calc_dist(d, ans, N); int L = rand_num(N), R = rand_num(N); if (L > R) swap(L, R); reverse(ans.begin() + L, ans.begin() + R); int af_dist = calc_dist(d, ans, N); if (af_dist > bf_dist) reverse(ans.begin() + L, ans.begin() + R); } ave += calc_dist(d, ans, N); } cout << ave / 100.0 << endl; return 0; } |
図7 テスト用データに基づく巡回セールスマン問題を焼きなまし法で解くC++プログラムの例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
#include <iostream> #include <algorithm> #include <vector> #include <string> #include <fstream> #include <cmath> using namespace std; #define MAX_TMP 100.0 //初期温度 #define TL 3 //制限時間 int calc_dist(vector<vector<int>>& dist, vector<int>& ans, int N) { int sum = 0; for (int i = 0; i < N; i++) { sum += dist[ans[i]][ans[i+1]]; } return sum; } int rand_num(int N) { return rand() % N + 1; } double rand_p(void) { return (double)rand() / RAND_MAX; } int main(void) { srand((unsigned)time(NULL)); long long ave = 0; for (int num = 1; num <= 100; num++) { string name = "case" + to_string(num) + ".txt"; ifstream infile(name); int N; infile >> N; vector<vector<int>> d(N, vector<int>(N)); for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { infile >> d[i][j]; } } vector<int> ans(N + 1); for (int i = 0; i < N; i++) ans[i] = i; ans[N] = 0; int st_time = clock(); //焼きなまし開始時間 while ((clock() - st_time) / CLOCKS_PER_SEC < TL ) { int bf_dist = calc_dist(d, ans, N); int L = rand_num(N), R = rand_num(N); if (L > R) swap(L, R); reverse(ans.begin() + L, ans.begin() + R); int af_dist = calc_dist(d, ans, N); double t = (double)(clock() - st_time) / CLOCKS_PER_SEC; //経過時間 double tmp = MAX_TMP - MAX_TMP * (t / TL); //現在の温度 double p = exp((bf_dist - af_dist) / tmp); //採用確率 if (rand_p() > p) reverse(ans.begin() + L, ans.begin() + R); } ave += calc_dist(d, ans, N); } cout << ave / 100.0 <<endl; return 0; } |
これだけは覚えておきたいLinuxコマンド(Vol.91掲載)
著者:大津 真
本連載では、Linuxの初心者に覚えておいてほしいコマンドを紹介していきます。ここで紹介するコマンドさえ押さえておけば、基本的な操作に困ることはなくなるでしょう。第2回では、主にテキストファイルを処理するための基本コマンドについて解説します。
シェルスクリプトマガジン Vol.91は以下のリンク先でご購入できます。
図6 「members.csv」ファイルの内容
1 2 3 4 5 6 7 8 9 |
<code>001,大津一郎,男,43,東京 002,江南直子,女,8,埼玉 003,唐木田信ー,男,43,福岡 004,森岡一郎,男,41,東京 005,秋山敬一郎,男,44,秋田 006,山田謙一,男,9,福岡 007,山田一郎,男,45,東京 009,福岡花子,女,35,東京 010,白戸二郎,男,39,北海道</code> |
シェルスクリプトマガジンvol.91 Web掲載記事まとめ
004 レポート 統合開発環境「Theia IDE」正式版リリース
005 レポート サイバーエージェントがVLM、新LLM公開
006 製品レビュー パソコン「ASUS Vivobook S 15 S5507QA」
007 NEWS FLASH
008 特集1 Oracle Cloud Infrastructure入門/福島耕平、中村峻大
020 特集2 ChatGPTで手軽に実現/佐藤秀輔 コード掲載
028 特別企画 イノシシ撃退機を作る ハード製作編/米田聡
036 Pythonあれこれ/飯尾淳 コード掲載
042 Markdownを活用する/藤原由来 コード掲載
052 香川大学SLPからお届け!/佐藤楓真 コード掲載
058 中小企業手作りIT化奮戦記/菅雄一
064 これだけは覚えておきたいLinuxコマンド/大津真 コード掲載
073 ユニケージレポート/田中湧也
076 Techパズル/gori.sh
077 コラム「要求工学を取り入れる」/シェル魔人
特集2 ChatGPTで手軽に実現(Vol.91記載)
著者:佐藤 秀輔
対話型AIサービス「ChatGPT」が注目を集めています。特に、2023年3月に登場した「GPT-4」という大規模言語モデル(LLM)を利用可能になってからは自然言語の理解力が飛躍的に向上し、指示に基づいて、さまざまな処理を自動化できるようになっています。本特集では、ChatGPTを個人で活用する例として、家計簿レビューをさせる方法と、API(Application Programming Interface)を利用してWebスクレイピングをさせる方法を紹介します。
シェルスクリプトマガジン Vol.91は以下のリンク先でご購入できます。
図1 ランダムな家計簿データを生成するPythonスクリプト
1 2 3 4 5 6 7 8 9 10 11 12 13 |
import datetime import random itemlist = ['食料品', '外食費', '日用品', '家財道具', '交通費', '電気代', '水道代', 'ガス代', 'ガソリン代', 'その他'] for i in range(366): basedate = datetime.datetime(2024, 1, 1) date = basedate + datetime.timedelta(days=i) item = itemlist[random.randrange(10)] amount = random.randrange(100) * 100 print(f"{date.date()},{date.time()},{item},{amount}") |
図10 Webスクレイピング対象となるWebページの例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>サンプル商品リスト</title> </head> <body> <h1>商品リスト</h1> <div class="product" id="product1"> <h2>商品名:スニーカー</h2> <p>価格:\5000</p> <p>評価:4.5 星</p> </div> <div class="product" id="product2"> <h2>商品名:バックパック</h2> <p>価格:\8000</p> <p>評価:4.0 星</p> </div> <div class="product" id="product3"> <h2>商品名:水筒</h2> <p>価格:\1500</p> <p>評価:4.8 星</p> </div> </body> </html> |
図11 Webスクレイピング用のPythonスクリプトの例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
import json from bs4 import BeautifulSoup # HTMLファイルを開く with open('sample.html', 'r', encoding='utf-8') as file: html_content = file.read() # BeautifulSoupオブジェクトを作成 soup = BeautifulSoup(html_content, 'lxml') # 商品情報を抽出 products = soup.find_all('div', class_='product') product_list = [] for product in products: product_name = product.find('h2').text.replace('商品名:', '').strip() price = product.find_all('p')[0].text.replace('価格:', '').strip() rating = product.find_all('p')[1].text.replace('評価:', '').strip() # 各商品情報を辞書として追加 product_list.append({ '商品名': product_name, '価格': price, '評価': rating }) # JSON形式で結果を出力 json_output = json.dumps(product_list, ensure_ascii=False, indent=4) print(json_output) |
Pythonあれこれ(Vol.91掲載)
著者:飯尾 淳
本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温かく見守ってください。皆さんと共に勉強していきましょう。第21回では、「ハノイの塔」の解法「GDHP(Gniibe Distributed Hanoi Protocol)」の手順をアニメーションで表示するPythonアプリを作成します。
シェルスクリプトマガジン Vol.91は以下のリンク先でご購入できます。
図2 GDHPの手順をアニメーションで表示するJavaScriptコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 |
const COLORS = [ 'crimson', 'forestgreen', 'yellow', 'royalblue', 'saddlebrown', 'hotpink', 'darkorange', 'darkmagenta' ]; const NUM_OF_DISKS = COLORS.length; const BASE_LENGTH = 200; const C_WIDTH = 3.732 * BASE_LENGTH; const C_HEIGHT = 3.500 * BASE_LENGTH; const DISK_R = 0.9 * BASE_LENGTH; const POLE_R = 15; const POSITIONS = { 'Source' : [0.268, 0.714], 'Auxiliary' : [0.500, 0.286], 'Destination' : [0.732, 0.714] }; const FLASHING_COUNTER = 20; const STEPS = 30; class Vector { constructor(x,y) { this.x = x; this.y = y; } } class Position extends Vector { constructor(x,y) { super(x,y); } move(vec) { this.x += vec.x; this.y += vec.y; } } class Disk { constructor(level) { this.level = level; this.color = COLORS[level]; this.r = (DISK_R-POLE_R)*(NUM_OF_DISKS-level)/NUM_OF_DISKS + POLE_R; } } class MovingDisk extends Disk { constructor(level,from,to) { super(level); [sx,sy] = [from.pos.x,from.pos.y]; [dx,dy] = [to.pos.x, to.pos.y]; this.pos = new Position(sx,sy); this.mvec = new Vector((dx-sx)/STEPS,(dy-sy)/STEPS); this.move_ctr = 0; this.from = from; this.to = to; } step_forward() { this.pos.move(this.mvec); this.move_ctr++; } finish_p() { var ret_flag = false; if (ret_flag = (this.move_ctr == STEPS)) { this.to.disks.push(new Disk(this.level)); } return ret_flag; } } class Tower { constructor(name, disks, direction=null) { this.name = name; this.disks = []; for (var i = 0; i < disks; i++) { this.disks.push(new Disk(i)); } this.direction = direction; this.moving = false; this.flash_ctr = 0; } get toplevel() { var l = this.disks.length; // '-1' means there is no disk. return (l > 0) ? this.disks[l-1].level : -1; } } var src = new Tower('Source', NUM_OF_DISKS); var aux = new Tower('Auxiliary', 0, src); var dst = new Tower('Destination', 0, src); // In the case of NUM_OF_DISKS is odd, // the src must face the src. // Otherwise, the src faces the aux. src.direction = (COLORS.length % 2 == 1) ? dst : aux; // the reference to moving disk is stored to this variable. var moving_disk = null; function setup() { createCanvas(C_WIDTH, C_HEIGHT); frameRate(30); [src,aux,dst].forEach(function(t) { [rx,ry] = POSITIONS[t.name]; t.pos = new Position(rx * C_WIDTH, ry * C_HEIGHT); }) } function base_drawing() { background('beige'); [src,aux,dst].forEach(function(t) { // draw disks t.disks.forEach(function(d) { stroke('black'); fill(d.color); ellipse(t.pos.x,t.pos.y,2*d.r); }) // draw a pole stroke('brown'); fill(t.moving & (t.flash_ctr < FLASHING_COUNTER/2) ? 'gold' : 'white'); ellipse(t.pos.x,t.pos.y,2*POLE_R); // draw a direction stroke('navy'); [sx, sy] = [t.pos.x, t.pos.y]; [dx, dy] = [t.direction.pos.x, t.direction.pos.y]; r = POLE_R / Math.sqrt((dx-sx)*(dx-sx)+(dy-sy)*(dy-sy)); [dx, dy] = [(dx-sx)*r+sx, (dy-sy)*r+sy]; line(sx,sy,dx,dy); }) } function flash_poles() { [src,aux,dst].forEach(function(t) { t.moving = (t.direction.direction === t); t.flash_ctr += 1; t.flash_ctr %= FLASHING_COUNTER; }) } function pop_disk(src,aux,dst) { var towers = [src,aux,dst].filter(t => t.moving); var idx,from,to; idx = (towers[0].toplevel > towers[1].toplevel) ? 0 : 1; [from, to] = [towers[idx], towers[1-idx]]; return new MovingDisk(from.disks.pop().level,from,to); } function draw_moving_disk() { var d = moving_disk; d.step_forward(); stroke('black'); fill(d.color); ellipse(d.pos.x,d.pos.y,2*d.r); return d.finish_p(); } function turn() { [moving_disk.from,moving_disk.to].forEach(function(t) { t.direction = ([src,aux,dst] .filter(x => (x !== t) && (x !== t.direction)))[0]; t.moving = false; }) } function draw() { // base drawing base_drawing(); // find two exchange-towers out of three flash_poles(); // start moving if (moving_disk == null) { moving_disk = pop_disk(src,aux,dst); } else { if (draw_moving_disk()) { turn(); moving_disk = null; } } } |
図3 Pygameを用いて描画ウィンドウを表示するPythonコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import pygame pygame.init() screen = pygame.display.set_mode((640,480)) pygame.display.set_caption('Pygame Window') while True: for event in pygame.event.get(): if event.type == pygame.TEXTINPUT and event.text == 'q': pygame.quit() exit() screen.fill('lavender') pygame.display.flip() pygame.time.delay(30) |
図5 GDHPの手順をアニメーションで表示するPythonコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 |
import pygame import math COLORS = [ 'crimson', 'forestgreen', 'yellow', 'royalblue', 'saddlebrown', 'hotpink', 'darkorange', 'darkmagenta' ] NUM_OF_DISKS = len(COLORS) BASE_LENGTH = 200 C_WIDTH = 3.732 * BASE_LENGTH C_HEIGHT = 3.500 * BASE_LENGTH DISK_R = 0.9 * BASE_LENGTH POLE_R = 15 POSITIONS = { 'Source' : [0.268, 0.714], 'Auxiliary' : [0.500, 0.286], 'Destination' : [0.732, 0.714] } FLASHING_COUNTER = 20 STEPS = 30 class Vector: def __init__(self, x, y): self.x = x self.y = y class Position(Vector): def __init__(self, x, y): super().__init__(x, y) def move(self, vec): self.x += vec.x self.y += vec.y class Disk: def __init__(self, level): self.level = level self.color = COLORS[level] self.r = (DISK_R-POLE_R)*(NUM_OF_DISKS-level)/NUM_OF_DISKS + POLE_R class MovingDisk(Disk): def __init__(self, level, frm, to): super().__init__(level) [sx, sy] = [frm.pos.x, frm.pos.y] [dx, dy] = [to.pos.x, to.pos.y] self.pos = Position(sx,sy) self.mvec = Vector((dx-sx)/STEPS,(dy-sy)/STEPS) self.move_ctr = 0 self.frm = frm self.to = to def step_forward(self): self.pos.move(self.mvec) self.move_ctr += 1 def finish_p(self): ret_flag = (self.move_ctr == STEPS) if ret_flag: self.to.disks.append(Disk(self.level)) return ret_flag class Tower: def __init__(self, name, disks, direction=None): self.name = name self.disks = [] for i in range(disks): self.disks.append(Disk(i)) self.direction = direction self.moving = False self.flash_ctr = 0 def toplevel(self): l = len(self.disks) # '-1' means there is no disk. return self.disks[l-1].level if l > 0 else -1 def setup(): pygame.init() screen = pygame.display.set_mode((C_WIDTH, C_HEIGHT)) pygame.display.set_caption('GDHP') for t in [src,aux,dst]: [rx, ry] = POSITIONS[t.name] t.pos = Position(rx * C_WIDTH, ry * C_HEIGHT) return screen def base_drawing(): screen.fill('beige') for t in [src,aux,dst]: # draw disks for d in t.disks: pygame.draw.circle(screen, d.color, (t.pos.x,t.pos.y), d.r) pygame.draw.circle(screen, 'black', (t.pos.x,t.pos.y), d.r, 1) # draw a pole fillcolor = 'gold' \ if t.moving and t.flash_ctr < FLASHING_COUNTER/2 else 'white' pygame.draw.circle(screen, fillcolor, (t.pos.x,t.pos.y), POLE_R) pygame.draw.circle(screen, 'brown', (t.pos.x,t.pos.y), POLE_R, 1) # draw a direction [sx, sy] = [t.pos.x, t.pos.y] [dx, dy] = [t.direction.pos.x, t.direction.pos.y] r = POLE_R / math.sqrt((dx-sx)*(dx-sx)+(dy-sy)*(dy-sy)) [dx, dy] = [(dx-sx)*r+sx, (dy-sy)*r+sy] pygame.draw.line(screen, (0,0,128), (sx,sy), (dx,dy), 3) def flash_poles(): for t in [src,aux,dst]: t.moving = (t.direction.direction == t) t.flash_ctr += 1 t.flash_ctr %= FLASHING_COUNTER def pop_disk(src,aux,dst): towers = list(filter(lambda x: x.moving, [src,aux,dst])) idx = 0 if towers[0].toplevel() > towers[1].toplevel() else 1 [frm, to] = [towers[idx], towers[1-idx]] return MovingDisk(frm.disks.pop().level,frm,to) \ if len(frm.disks) > 0 else None def draw_moving_disk(): d = moving_disk d.step_forward() pygame.draw.circle(screen, d.color, (d.pos.x,d.pos.y), d.r) pygame.draw.circle(screen, 'black', (d.pos.x,d.pos.y), d.r, 1) return d.finish_p() def turn(): for t in [moving_disk.frm,moving_disk.to]: t.direction = list(filter( lambda x: (x != t) and (x != t.direction), [src,aux,dst]))[0] t.moving = False def draw(): # base drawing base_drawing() # find two exchange-towers out of three flash_poles() # start moving finish_p = False mdisk = moving_disk if mdisk == None: mdisk = pop_disk(src,aux,dst) if mdisk == None: finish_p = True else: if draw_moving_disk(): turn() mdisk = None return mdisk, finish_p # main routine if __name__ == '__main__': src = Tower('Source', NUM_OF_DISKS) aux = Tower('Auxiliary', 0, src) dst = Tower('Destination', 0, src) # In the case of NUM_OF_DISKS is odd, # the src must face the src. # Otherwise, the src faces the aux. src.direction = dst if len(COLORS) % 2 == 1 else aux # the reference to moving disk is stored to this variable. moving_disk = None screen = setup() while True: for event in pygame.event.get(): if event.type == pygame.TEXTINPUT and event.text == 'q': pygame.quit() exit() moving_disk, finish_p = draw() if finish_p: pygame.quit() exit() pygame.display.flip() pygame.time.delay(30) |
Vol.91 補足情報
特集2 ChatGPTで手軽に実現
クラウド型家計簿アプリ「Dr.Wallet」のサンプルデータがここからダウンロードできます。家計簿を付けていない人は、こちらを利用してください。なお、本サンプルデータ中のショップ名、電話番号、住所の情報はすべて架空のものです。実在の企業や事業者とは関係ありません。
情報は随時更新致します。
Vol.90 補足情報
特別企画 AlmaLinux 9のインストール
p.36の「2024年6月31日」は「2024年6月30日」の誤りです。同じページの最初から4行目の「をして」は「として」の誤りです。p.37の右段下から2行目の「Ubuntu Server」は「AlmaLinux」の誤りです。お詫びして訂正します。
p.37で紹介したISOイメージの書き込みツール「balenaEtcher」の最新版(バージョン1.19.×)では、「PORTABLE」版が提供されていません。記事で紹介したバージョン1.18.11のPORTABLE版(balenaEtcher-Portable-1.18.11.exe)はここからダウンロードできます。
情報は随時更新致します。
シェルスクリプトマガジンvol.90 Web掲載記事まとめ
004 レポート 米OpenAI社の新LLM「GPT-4o」登場
005 レポート JPEGライブラリ「jpegli」公開
006 製品レビュー バイク用エアバッグ「TECH-AIR 5」
007 NEWS FLASH
008 特集1 2024年大注目のMicrosoft Copilot/三沢友治
018 特集2 NVIDIA NeMoで独自の生成AIを構築する/澤井理紀、藤田充矩 コード掲載
026 特集3 Raspberry Pi 5を使う/麻生二郎
036 特別企画 AlmaLinux 9のインストール/麻生二郎
042 Raspberry Pi Pico W/WHで始める電子工作/米田聡 コード掲載
046 Markdownを活用する/藤原由来 コード掲載
052 中小企業手作りIT化奮戦記/菅雄一
056 香川大学SLPからお届け!/小河原直道 コード掲載
062 Pythonあれこれ/飯尾淳 コード掲載
068 これだけは覚えておきたいLinuxコマンド/大津真
076 Techパズル/gori.sh
077 コラム「プロジェクトリーダーの役割」/シェル魔人
特集2 NVIDIA NeMoで独自の生成AIを構築する(Vol.90記載)
著者:澤井 理紀、藤田 充矩
大規模言語モデル(LLM)に基づく生成AIが注目を集めています。そうした生成AIを活用するには、LLMを特定のタスクやデータセットに最適化する「ファインチューニング」が欠かせません。本特集では、生成AIを支える技術を解説した上で、NVIDIAが提供する生成AI向けのフレームワーク「NVIDIA NeMo」を使って独自の生成AIをローカル環境に構築し、LLMをファインチューニングする方法を紹介します。
シェルスクリプトマガジン Vol.90は以下のリンク先でご購入できます。
図2 LLMダウンロード用のPythonスクリプト
1 2 3 4 5 6 7 8 9 10 |
import os from huggingface_hub import snapshot_download MODEL_DIR = "./models" os.makedirs(MODEL_DIR, exist_ok=True) snapshot_download( repo_id="elyza/ELYZA-japanese-Llama-2-7b", local_dir=f"{MODEL_DIR}/ELYZA-japanese-Llama-2-7b", local_dir_use_symlinks=False ) |
図4 PEFTに使用するデータセットの形式を変換するPythonスクリプト
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
import json import os import random INPUT_TRAIN = "./JGLUE/datasets/jcommonsenseqa-v1.1/train-v1.1.json" INPUT_VALID = "./JGLUE/datasets/jcommonsenseqa-v1.1/valid-v1.1.json" OUTPUT_DIR = "./data/jcommonsenseqa-v1.1" random.seed(42) os.makedirs(OUTPUT_DIR, exist_ok=True) def write_jsonl(fname, json_objs): with open(fname, 'wt') as f: for o in json_objs: f.write(json.dumps(o, ensure_ascii=False)+"\n") def form_question(obj): st = "" st += "### 指示:\n" st += "与えられた選択肢の中から、最適な答えを選んでください。" st += "出力は以下から選択してください:\n" st += f"- {obj['choice0']}\n" st += f"- {obj['choice1']}\n" st += f"- {obj['choice2']}\n" st += f"- {obj['choice3']}\n" st += f"- {obj['choice4']}\n" st += "### 入力:\n" st += f"{obj['question']}\n" st += "### 応答:" return st def prosess(input_path, train=False): with open(input_path) as f: dataset = [json.loads(line) for line in f.readlines()] processed = [] for data in dataset: prompt = form_question(data) answer = data[f"choice{data['label']}"] processed.append({"input": prompt, "output": f"{answer}"}) if train: random.shuffle(processed) train_ds = processed[:-1000] valid_ds = processed[-1000:] write_jsonl(f"{OUTPUT_DIR}/train-v1.1.jsonl", train_ds) write_jsonl(f"{OUTPUT_DIR}/valid-v1.1.jsonl", valid_ds) else: write_jsonl(f"{OUTPUT_DIR}/test-v1.1.jsonl", processed) return def main(): prosess(INPUT_TRAIN, train=True) prosess(INPUT_VALID) if __name__ == "__main__": main() |
Raspberry Pi Pico W/WHで始める電子工作(Vol.90掲載)
著者:米田 聡
本連載では、人気のマイコンボード「Raspberry Pi Pico W/WH」を活用していきます。同ボードは、無
線LANやBluetoothの通信機能を搭載し、入手しやすく価格も手頃なので、IoT機器を自作するのに最適なハードウエアです。第6回は、DHT-11で測った湿温度をグラフ化します。
シェルスクリプトマガジン Vol.90は以下のリンク先でご購入できます。
図2 10 分おきに湿温度を測定して配列に保存する「DhtDataLogger」クラスのコード(ddl.py)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
import dht import time import gc import micropython import _thread from machine import Pin from machine import RTC from machine import Timer DHT_PIN = 15 TICK = 10 SIZE = 144 class DhtDataLogger(): def __init__(self, dht_gpio = DHT_PIN, tick=TICK, size=SIZE): self.rtc = RTC() self.dht = dht.DHT11(Pin(dht_gpio)) self.data = [] self.tick = tick self.size = size # 1分おきにタイマー割り込みを呼ぶ self.timer = Timer(period=60*1000, mode=Timer.PERIODIC, callback=self._timer_handler) self.lock = _thread.allocate_lock() def _timer_handler(self, t): now = self.rtc.datetime() if now[5] % self.tick == 0: micropython.schedule(self._logger, now) def _logger(self, now): # ロックをかける self.lock.acquire() try: self.dht.measure() except OSError as e: pass # 測定日時 nowstr = "{0:02d}:{1:02d}:{2:02d}".format(now[4],now[5],now[6]) self.data.append([nowstr, self.dht.temperature(), self.dht.humidity()]) if len(self.data) > self.size: del self.data[0] # debug # print(self.data[-1]) # ロック解除 self.lock.release() # ガベージコレクト gc.collect() def get_data(self): if len(self.data) == 0: return None # 配列のコピーを返す rval = [] self.lock.acquire() for v in self.data: rval.append(v) self.lock.release() return rval |
図3 蓄積した湿温度をグラフとして表示する簡易Webサーバーのプログラム(main.py)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
import usocket as socket import network import time import ujson as json import gc from machine import Timer from ddl import DhtDataLogger http_header = ''' HTTP/1.1 200 OK\r Content-Type: text/html;charset=UTF-8\r Cache-Control: no-store, no-cache, max-age=0, must-revalidate, proxy-revalidate\r Connection: close\r \r ''' html_base=''' <!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <title>Pico W</title> </head> %s </html> ''' graph_body = ''' <body> <canvas id="DHTChart"></canvas> <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.bundle.js"></script> <script> var dhtc = document.getElementById("DHTChart"); var DHTChart = new Chart(dhtc, { type: 'line', data: { labels: %s, datasets: [ { label: '気温(度)', data: %s, borderColor: "rgba(255,0,0,1)", backgroundColor: "rgba(0,0,0,0)" }, { label: '湿度(%)', data: %s, borderColor: "rgba(0,0,255,1)", backgroundColor: "rgba(0,0,0,0)" } ], }, options: { title: { display: true, text: '気温と湿度' }, scales: { yAxes: [{ ticks: { suggestedMax: 100, suggestedMin: -10, stepSize: 10, } }] }, } }); </script> </body> ''' logger = DhtDataLogger() sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.bind(('', 80)) sock.listen(4) try: while True: conn, addr = sock.accept() connect_from = str(addr[0]) req = conn.recv(1024).decode() if req.startswith('GET / HTTP/1.1'): tlist = logger.get_data() if tlist is None: body = html_base % ("<body><h1>Data not ready</h1></body>") else: labels = [] temps = [] hums = [] for v in tlist: labels.append(v[0]) temps.append(v[1]) hums.append(v[2]) body = graph_body % (json.dumps(labels), json.dumps(temps), json.dumps(hums)) html = html_base % (body) # 送信 conn.send(http_header) conn.sendall(html) conn.close() else: conn.sendall('HTTP/1.1 404 Not Found\r\n') conn.close() gc.collect() except KeyboardInterrupt: pass sock.close() |
Markdownを活用する(Vol.90掲載)
著者:藤原 由来
本連載では文書の装飾や構造付けを手軽に行える記法である「Markdown」を用いて、さまざまな文書や成果物を作成する方法を紹介します。今回も前回に引き続き、Markdownベースの高機能なノートアプリ「Obsidian」を扱います。前回の内容を踏まえて、Obsidianの発展的な使い方を紹介します。
図11 デイリーノートのテンプレート(テンプレート_カレー日記)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
テンプレート_カレー日記 作成日時:{{date}} {{time}} ## 今日のカレー - メニュー: - 感想: - 評価:★★★☆☆ ## お店の情報 - 店名: - 住所: - 最寄り駅: - URL: |
香川大学SLPからお届け!(Vol.90掲載)
著者:小河原 直道
Raspberry Pi 5(以下、ラズパイ5)が日本でも発売され、私も購入しました。センサーとの接続やサーバーの構築をしてみたいと思ったため、湿温度/気圧/CO₂濃度を測定できるセンサーを接続し、その値をWebブラウザから確認できるシステムを作成しました。このシステムの作成ポイントについて解説します。
シェルスクリプトマガジン Vol.90は以下のリンク先でご購入できます。
図8 BME280のチップIDを読み出すPythonコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
import spidev # 読み取りは8ビット目を「1」に, 書き込みは「0」にする def get_reg_addr(rw: str, addr: int) -> int: rw_flag = 0b1000_0000 if rw == "w": rw_flag = 0b0000_0000 return rw_flag | (addr & 0b0111_1111) spi = spidev.SpiDev() spi.open(0, 0) # bus0, cs0 spi.mode = 0 # mode0, CPOL=CPHA=0 spi.max_speed_hz = 10 * 10**6 # 10MHz bme_id = spi.xfer2([get_reg_addr("r", 0xD0), 0]) print(f"id: {hex(bme_id[1])}") spi.close() |
図9 BME280の気温データを読み出すPythonコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
import spidev # 読み取りは8ビット目を「1」に, 書き込みは「0」にする def get_reg_addr(rw: str, addr: int) -> int: rw_flag = 0b1000_0000 if rw == "w": rw_flag = 0b0000_0000 return rw_flag | (addr & 0b0111_1111) spi = spidev.SpiDev() spi.open(0, 0) # bus0, cs0 spi.mode = 0 # mode0, CPOL=CPHA=0 spi.max_speed_hz = 10 * 10**6 # 10MHz # 湿度の測定を有効化。オーバーサンプリングx1 r = spi.xfer2([get_reg_addr("r", 0xF2), 0])[1] ctrl_hum = r & 0b1111_1000 | 0b001 spi.xfer2([get_reg_addr("w", 0xF2), ctrl_hum]) # 気圧、気温の測定を有効化。オーバーサンプリングx1、ノーマルモードに ctrl_meas = (0b001 << 5) + (0b001 << 3) + 0b11 spi.xfer2([get_reg_addr("w", 0xF4), ctrl_meas]) # 気温データの読み出し temp_raw_bins = spi.xfer2([get_reg_addr("r", 0xFA), 0, 0, 0]) temp_raw = (temp_raw_bins[1] << 8+4) | (temp_raw_bins[2] << 4) |\ ((temp_raw_bins[3]>>4) & 0b00001111) print(f"気温: {temp_raw}") spi.close() |
図10 MH-Z19CのCO2濃度データを読み出すPythonコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
import serial import sys import time serial = serial.Serial(port="/dev/ttyAMA0", timeout=5) # 念のためバッファをリセットする serial.reset_input_buffer() serial.reset_output_buffer() time.sleep(2) # 待ち時間は適宜増減して調整 s = bytearray.fromhex("FF 01 86 00 00 00 00 00 79") serial.write(s) r = serial.read(size=9) serial.close() if r[0] != 0xFF: print("invalid data\n") sys.exit(-1) for_check=0 for i, b in enumerate(r): if i==0 or i==8: continue # チェックサムは1バイトのみ、あふれた分は捨てる for_check = for_check + b & 0b1111_1111 for_check = 0xFF - for_check + 1 if r[8] != for_check: print("checksum error\n") sys.exit(-1) print(f"{r[2]*256 + r[3]} ppm") |
図12 測定データをデータベースに保存するPythonコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
import mariadb import sys try: conn = mariadb.connect( user = ユーザー名, password = パスワード, host = "127.0.0.1", port = 3306, database = "sensor1", autocommit = True ) except mariadb.Error as e: print(f"db connect error: {e}") sys.exit(-1) cur = conn.cursor() sql = 'INSERT INTO sensor (datetime, temp, humid, press, co2) VALUES(NOW(), ?, ?, ?, ?)' データ(temp_data、humid_data、press_data、co2_data)取得用コードをここに記述 try: cur.execute(sql, (temp_data, humid_data, press_data, co2_data)) except Exception as e: print(f"insert error: {e}") finally: cur.close() conn.close() |
図13 「db/db_pool.js」ファイルに記述するコード
1 2 3 4 5 6 7 8 9 |
const mariadb = require("mariadb"); const pool = mariadb.createPool({ host: "localhost", user: ユーザー名, password: パスワード, database: "sensor1", connectionLimit: 5, }); module.exports = pool; |
図14 「route/api_v1.js」ファイルに記述するコード(抜粋)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
const router = require("express").Router(); const pool = require("../db/db_pool"); function getColumnName(sensor){ switch(sensor){ case "all":return "*"; case "temperature": return "datetime, temp" case "humidity": return "datetime, humid" case "pressure": return "datetime, press" case "co2": return "datetime, co2" default: throw new Error("sensor name error"); } } router.get("/:sensor/latest", async(req, res) => { let sqlCol; try{ sqlCol = getColumnName(req.params.sensor); } catch (e) { return res.status(404).send(`bad sensor name`); } try{ let dbRes = await pool.query( `select {sqlCol} from sensor order by datetime desc limit 1` ); // クエリー結果の最初のレコードをJSON形式で送る return res.status(200).json(dbRes[0]); } catch(err) { return res.status(500).json({mes:err.toString()}); } }); module.exports = router; |
図15 「route/index.js」ファイルに記述するコード
1 2 3 4 5 6 7 8 |
const express = require('express'); const router = express.Router(); const path = require('path'); router.get("/", (req, res) => { res.sendFile(path.join(__dirname, "../view/index.html")); }); router.use("/api/v1", require("./api_v1")); module.exports = router; |
図16 「app.js」ファイルに記述するコード
1 2 3 4 5 6 7 8 9 10 11 |
const express = require("express"); const app = express(); const path = require('path'); app.use("/", require("./route/index")); app.use(express.static(path.join(__dirname, "public"))); app.all("*", (req, res) => { return res.sendStatus(404); }); app.listen(3000, () => { console.log("Listening at port 3000"); }); |
Pythonあれこれ
筆者:飯尾 淳
本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温かく見守ってください。皆さんと共に勉強していきましょう。第20回では、データの可視化などにも活用できるWebアプリケーションフレームワーク「Streamlit」の、より進んだ使い方を紹介します。
シェルスクリプトマガジン Vol.90は以下のリンク先でご購入できます。
図4 プルダウンメニューでサブページに移動できるようにしたコード
1 2 3 4 5 6 7 8 9 |
import streamlit as st selected = st.selectbox('なに食べる?', ('-', 'ramen', 'curry', 'katsudon')) pages = {'ramen': 'pages/1_🍜_ramen.py', 'curry': 'pages/2_🍛_curry.py', 'katsudon': 'pages/3_🍚_katsudon.py' } if selected in pages.keys(): st.switch_page(pages[selected]) |
図8 「1_edit.py」ファイルに記述するコード
1 2 3 4 5 6 7 8 9 10 |
import streamlit as st import pandas as pd df = pd.read_excel('TODO.xlsx') df = st.data_editor(df) clicked = st.button('保存', type='primary', on_click=lambda:\ df.to_excel('TODO.xlsx', index=False)) if (clicked): st.switch_page('index.py') |
図11 「2_new.py」ファイルに記述するコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
import streamlit as st import pandas as pd df = pd.read_excel('TODO.xlsx') title = st.text_input(label='タイトル') memo = st.text_area(label='内容') date = st.date_input(label='締切') done = False def append(): global df df_new = pd.DataFrame({'タイトル': [title], '内容': [memo], '締切': [date], '実施': [done]}) df = pd.concat([df, df_new]) df.to_excel('TODO.xlsx', index=False) clicked = st.button('登録', type='primary', on_click=append) if (clicked): st.switch_page('index.py') |
図11 「2_new.py」ファイルに記述するコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
import streamlit as st import pandas as pd df = pd.read_excel('TODO.xlsx') title = st.text_input(label='タイトル') memo = st.text_area(label='内容') date = st.date_input(label='締切') done = False def append(): global df df_new = pd.DataFrame({'タイトル': [title], '内容': [memo], '締切': [date], '実施': [done]}) df = pd.concat([df, df_new]) df.to_excel('TODO.xlsx', index=False) clicked = st.button('登録', type='primary', on_click=append) if (clicked): st.switch_page('index.py') |
シェルスクリプトマガジンvol.89 Web掲載記事まとめ
004 レポート 進研ゼミに生成AI活用新サービス
005 レポート HHKB Studioのキートップカスタム
006 製品レビュー スマートリング「SOXAI RING 1」
007 NEWS FLASH
008 特集1 Amazon EC2入門/飯村望、清水祥伽
023 Hello Nogyo!
024 特集2 コンテンツクラウドのBox/有賀友三、結城亮史、葵健晴、辰己学
040 Markdownを活用する/藤原由来 コード掲載
050 Raspberry Pi Pico W/WHで始める電子工作/米田聡 コード掲載
054 中小企業手作りIT化奮戦記/菅雄一
058 香川大学SLPからお届け!/宇佐美志久眞
062 Pythonあれこれ/飯尾淳 コード掲載
068 Linux定番エディタ入門/大津真 コード掲載
076 Techパズル/gori.sh
077 コラム「顧客と一心同体の精神を育成」/シェル魔人
Raspberry Piを100%活用しよう(Vol.89掲載)
著者:米田 聡
本連載では、人気のマイコンボード「Raspberry Pi Pico W/WH」を活用していきます。同ボードは、無線LANやBluetoothの通信機能を搭載し、入手しやすく価格も手頃なので、IoT機器を自作するのに最適なハードウエアです。第5回は、DHT-11で測った湿温度をスマートフォンに通知します。
シェルスクリプトマガジン Vol.89は以下のリンク先でご購入できます。
図4 LINE Notifyを使って通知を送る「notify()」関数を定義したPythonプログラム
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import usocket as socket from urequests import post from micropython import const TOKEN = const("your_access_token") def notify(msg): url = 'https://notify-api.line.me/api/notify' rheaders = { 'Content-type':'application/x-www-form-urlencoded', 'Authorization':'Bearer ' + TOKEN } message = "message=" + msg req = post(url,headers=rheaders,data=message) return req.status_code |
図6 1時間おきに湿温度をスマートフォンに通知するサンプルプログラム(sample.py)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
from machine import Pin from machine import Timer from micropython import const from micropython import schedule import _thread import dht import line import time DHT_PIN = const(15) TICK = const(60) class Climate(): def __init__(self, dht_gpio=DHT_PIN, tick=TICK, callback=None, expire=TICK): self.dht = dht.DHT11(Pin(dht_gpio)) self.tick = tick self.lock = _thread.allocate_lock() self.timer = Timer(period=tick*1000, mode=Timer.PERIODIC, callback=self._timer_handler) self._humidity = 0 self._temperature = 0 self._measurement_dt = 0 self.measure() self._callback_func = callback self._expire = expire self._counter = 0 def set_callback(self, callback, expire): self._callback_func = callback self._expire = expire def measure(self, arg=0): self.dht.measure() self.lock.acquire() self._humidity = self.dht.humidity() self._temperature = self.dht.temperature() self._measurement_dt = time.time() self.lock.release() def _timer_handler(self,t): schedule(self.measure, 0) self._counter -= 1 if self._counter <= 0: self._counter = self._expire if self._callback_func is not None: schedule(self._callback_func, self) @property def temp(self): self.lock.acquire() rval = self._temperature self.lock.release() return rval @property def hum(self): self.lock.acquire() rval = self._humidity self.lock.release() return rval @property def measurement_date_time(self): self.lock.acquire() rval = self._measurement_dt self.lock.release() return rval def mycalback(c): tm = time.localtime(c.measurement_date_time) line.notify("temp=%d,hum=%d@%4d/%02d/%02d_%02d:%02d:%02d" % (c.temp, c.hum, tm[0],tm[1],tm[2],tm[3],tm[4],tm[5])) if __name__ == '__main__': cl = Climate(callback = mycalback) while(True): machine.idle() |
Pythonあれこれ(Vol.89掲載)
著者:飯尾 淳
本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温かく見守ってください。皆さんと共に勉強していきましょう。第19回では、データの可視化などにも活用できるWebアプリケーションフレームワーク「Streamlit」の基本的な使い方を紹介します。
シェルスクリプトマガジン Vol.89は以下のリンク先でご購入できます。
図7 「magic.py」ファイルに記述するコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
import streamlit as st # 素のテキストはマークダウンで表示される(docstring以外) ''' # Magicという仕組み 説明を _markdown_ で書けます。 ''' '### 文字列や変数の取り扱い' a = st.number_input("整数aの値を入れてください:+1:", value=0) b = st.number_input("整数bの値を入れてください:+1:", value=0) x = a + b 'a + b = ', x # 文字列と変数の値を並べて直接描画する '### 表の取り扱い' import pandas as pd df = pd.DataFrame({'名前': ['あや','ゆみ','みなこ','とめ'], '年齢': [24,22,32,95]}) df # データフレームを直接描画する '### チャートの取り扱い' import matplotlib.pyplot as plt import numpy as np arr = np.random.normal(1, 1, size=100) fig, ax = plt.subplots() ax.hist(arr, bins=20) fig # チャートも直接描画できる |
図9 キャッシュを設定するコードの例
1 2 3 4 5 6 7 8 9 10 11 12 13 |
'### チャートの取り扱い' import matplotlib.pyplot as plt import numpy as np @st.cache_data def make_chart(): arr = np.random.normal(1, 1, size=100) fig, ax = plt.subplots() ax.hist(arr, bins=20) return fig fig = make_chart() fig # チャートも直接描画できる |
図10 山手線の駅の位置を地図上にプロットするWebアプリのコード
1 2 3 4 5 6 7 8 9 10 11 12 13 |
import streamlit as st import pandas as pd import json with open('yamate.geojson','r') as f: data = json.load(f) coords = [f['geometry']['coordinates'] \ for f in data ['features'] if f['geometry']['type'] == 'Point'] map_data = pd.DataFrame(coords, columns=['lon', 'lat']) st.map(map_data) |
図12 ウィジェットをサイドバーに配置するコードの例
1 2 3 4 5 6 7 8 |
import streamlit as st selected = st.sidebar.selectbox( 'なに食べる?', ('ramen', 'curry', 'katsudon'), ) st.image(selected + '.jpg') |
Linux定番エディタ入門
筆者:大津 真
文章の作成やプログラミングに欠かせないのがテキストエディタ(以下、エディタ)です。この連載では、Linuxで利用できる定番エディタの特徴と使い方を解説していきます。最終回となる今回も、LinuxだけでなくWindowsやmacOSでも利用可能なGUIエディタの定番「Visual Studio Code(VSCode)」を引き続き紹介します。
シェルスクリプトマガジン Vol.89は以下のリンク先でご購入できます。
図1 「test1.md」ファイルに記述するテキストデータ
1 2 3 4 5 6 7 8 9 10 11 12 |
# VSCode 入門 VSCode はシンプルで扱いやすいエディタです。 ## いろいろなエディタ 1. VSCode 2. Vim 3. nano ## VSCode の特徴 - プラグインによる拡張 - ワークスペースによる管理 - コマンドパレットによるコマンド実行 |
Vol.88 補足情報
特集2 自宅Linuxサーバー構築
p.14の「Windows 10のサポートが終了する2024年6月末ごろ」は「Windows 10(バージョン22H2)のサポートが終了する2025年10月14日」の誤りです(詳しくサポート期間は、こちら)。お詫びして訂正いたします。
Windows 10(バージョン22H2)は、まだ1年半以上のサポートが受けられますので、安心してお使いください。
情報は随時更新致します。
シェルスクリプトマガジンvol.88 Web掲載記事まとめ
004 レポート Open Source Summit Japan開催
005 レポート 国内開発の日本語LLM
006 製品レビュー スマートテレビ「32S2」
007 NEWS FLASH
008 特集1 CentOS Linux 7からの移行先を考える/弦本春樹
013 Hello Nogyo!
014 特集2 自宅Linuxサーバー構築/麻生二郎 コード掲載
028 特別企画 ゆっくりMovieMaker4で映像制作 シナリオ作り編/ふる(FuruyamD)
038 Raspberry Pi Pico W/WHで始める電子工作/米田聡 コード掲載
042 大規模言語モデル/桑原滝弥、イケヤシロウ
044 Pythonあれこれ/飯尾淳 コード掲載
049 中小企業手作りIT化奮戦記/菅雄一
054 法林浩之のFIGHTING TALKS/法林浩之
056 タイ語から分かる現地生活/つじみき
062 香川大学SLPからお届け!/谷知紘
068 Linux定番エディタ入門/大津真
076 Techパズル/gori.sh
077 コラム「SELiunxとユニケージ」/シェル魔人
特集2 自宅Linuxサーバー構築(Vol.88記載)
著者:麻生 二郎
Linuxやサーバーソフト、インターネットの仕組みを知りたいのなら、Linuxサーバーを構築するのが一番です。使わなくなった古いノートパソコンを活用し、自宅内にLinuxサーバーを構築してみましょう。本特集では、その手順を分かりやすく紹介します。
シェルスクリプトマガジン Vol.88は以下のリンク先でご購入できます。
Part3 サーバーソフトを導入する
図1 Sambaの設定(/etc/samba/smb.conf)
1 2 3 4 5 6 7 8 9 10 11 |
[global] workgroup = SHMAG dos charset = CP932 unix charset = UTF8 [share] path = /var/share browsable = yes writable = yes create mask = 0777 directory mask = 0777 |
Pythonあれこれ(Vol.88掲載)
著者:飯尾 淳
本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温かく見守ってください。皆さんと共に勉強していきましょう。第18回では、米Google社が提供する機械学習フレームワーク「MediaPipe」の顔検出機能を用いて、お遊びのプログラム作成に挑戦します。
シェルスクリプトマガジン Vol.88は以下のリンク先でご購入できます。
図1 顔ランドマーク検出をするコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
import cv2 import mediapipe as mp spec = mp.solutions.drawing_utils.DrawingSpec(thickness=1, circle_radius=1) cap = cv2.VideoCapture(0) with mp.solutions.face_mesh.FaceMesh( min_detection_confidence=0.5, min_tracking_confidence=0.5) as face_mesh: while True: success, image = cap.read() if not success: print("Warning: No camera image"); continue image = cv2.cvtColor(cv2.flip(image, 1), cv2.COLOR_BGR2RGB) image.flags.writeable = False results = face_mesh.process(image) # Annotate the face mesh image.flags.writeable = True image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR) if results.multi_face_landmarks: for landmarks in results.multi_face_landmarks: mp.solutions.drawing_utils.draw_landmarks( image=image, landmark_list=landmarks, connections=mp.solutions.face_mesh.FACEMESH_TESSELATION, landmark_drawing_spec=spec, connection_drawing_spec=spec) cv2.imshow('MediaPipe FaceMesh', image) if cv2.waitKey(5) & 0xFF == 27: break cap.release() |
図4 「顔ハメ」アプリのコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
import cv2 import numpy as np from PIL import Image, ImageDraw import mediapipe as mp def scaleToHeight(img, height): h, w = img.shape[:2] width = round(w * (height / h)) dst = cv2.resize(img, dsize=(width, height)) return dst def convertToRGBA(src, type): return Image.fromarray(cv2.cvtColor(src, type))\ .convert('RGBA') def trimOutside(base, over, loc): drw = ImageDraw.Draw(base) drw.rectangle([(0, 0), (loc[0]-1, over.size[1]-1)], fill=(0, 0, 0)) drw.rectangle([(loc[0]+over.size[0], 0), (base.size[0]-1,over.size[1]-1)], fill=(0, 0, 0)) def overlayImage(src, overlay, location): # convert images to PIL format pil_src = convertToRGBA(src, cv2.COLOR_BGR2RGB) pil_overlay = convertToRGBA(overlay, cv2.COLOR_BGRA2RGBA) # conpose two images pil_tmp = Image.new('RGBA', pil_src.size, (0, 0, 0, 0)) pil_tmp.paste(pil_overlay, location, pil_overlay) trimOutside(pil_tmp, pil_overlay, location) result_image = Image.alpha_composite(pil_src, pil_tmp) # convert result to OpenCV format return cv2.cvtColor(np.asarray(result_image), cv2.COLOR_RGBA2BGRA) def decrementTimer(timer, image, p_idx): h, w = image.shape[:2] if timer < 0: p_idx = (p_idx + 1) % len(panels) return TIMER_INITIAL_VALUE, p_idx elif timer == 30: global still still = image cv2.rectangle(image, (0, 0), (w, h), (255,255,255), thickness=-1) return timer - 1, p_idx elif timer < 30: image = still return timer - 1, p_idx d, r = timer // 30, timer % 30 c = 255 / 60 * r + 128 cv2.putText(image, org=(int(w/2-100), int(h/2+100)), text=str(d), fontFace=cv2.FONT_HERSHEY_DUPLEX, fontScale=10.0, color=(c, c, c), thickness=30, lineType=cv2.LINE_AA) return timer - 1, p_idx # prepare the kao_hame_panels panels = [] panels.append(cv2.imread('img1.png', cv2.IMREAD_UNCHANGED)) panels.append(cv2.imread('img2.png', cv2.IMREAD_UNCHANGED)) panels.append(cv2.imread('img3.png', cv2.IMREAD_UNCHANGED)) # capture from a camera cap = cv2.VideoCapture(0) ret, frame = cap.read() # rescale the kao_hame image height, width = frame.shape[:2] for i in range(len(panels)): panels[i] = scaleToHeight(panels[i], height) p_idx = 0 panel = panels[p_idx] p_height, p_width = panel.shape[:2] # timing counter TIMER_INITIAL_VALUE = 119 timer = TIMER_INITIAL_VALUE with mp.solutions.face_mesh\ .FaceMesh(max_num_faces=1, refine_landmarks=True, min_detection_confidence=0.5, min_tracking_confidence=0.5)\ as face_mesh: while cap.isOpened(): success, image = cap.read() if not success: print("Ignoring empty camera frame.") continue image = cv2.flip(image, 1) location = ((width-p_width)//2, 0) image = overlayImage(image, panel, location) image2 = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) results = face_mesh.process(image2) if results.multi_face_landmarks != None: timer, p_idx = decrementTimer(timer, image, p_idx) panel = panels[p_idx] p_height, p_width = panel.shape[:2] else: timer = TIMER_INITIAL_VALUE # reset timer # triming the image image = image[0:p_height, location[0]:location[0]+p_width] cv2.imshow('Virtual Face-in-Hole Cutout', image) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows() |
Raspberry Pi Pico W/WHで始める電子工作(Vol.88掲載)
著者:米田 聡
本連載では、人気のマイコンボード「Raspberry Pi Pico W/WH」を活用していきます。同ボードは、無
線LANやBluetoothの通信機能を搭載し、入手しやすく価格も手頃なので、IoT機器を自作するのに最適なハードウエアです。第4回は、モノクロ有機ELディスプレイと湿温度センサー「DHT-11」で日本語表示の湿温度計を作ります。
シェルスクリプトマガジン Vol.88は以下のリンク先でご購入できます。
図9 温度と湿度を日本語で表示するプログラム(temp_hum.py)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
from machine import Pin from machine import I2C from ssd1306 import SSD1306_I2C from display import PinotDisplay from pnfont import Font import dht import time # ELパネル i2c=I2C(0, freq=400000) ssd = SSD1306_I2C(128, 32, i2c) # pinotライブラリ fnt = Font('/fonts/shnmk12u.pfn') disp = PinotDisplay(panel = ssd, font = fnt) # DHT-11センサー sensor = dht.DHT11(Pin(15)) while True: os_error = False try: sensor.measure() except OSError as e: os_error = True line1 = "温度: {0:2d} C".format(sensor.temperature()) line2 = "湿度: {0:2d} %".format(sensor.humidity()) line3 = "DHT-11エラー" if os_error else " " # 温度表示 disp.locate(1, 0) disp.text(line1) # 湿度表示 disp.locate(1,12) disp.text(line2) # エラー表示 disp.locate(1,24) disp.text(line3) time.sleep(2) |
シェルスクリプトマガジンvol.87 Web掲載記事まとめ
004 レポート Raspberry Pi 5が登場
005 レポート キーボード「HHKB Studio」が発売
006 製品レビュー 小型パソコン「X68000 Z PRODUCT EDITION BLACK MODEL」
007 NEWS FLASH
008 特集1 ゆっくりMovieMaker4で映像制作 編集作業編/ふる(FuruyamD)
014 特集2 電子帳簿保存法を知る/永禮啓大
021 Hello Nogyo!
022 特別企画 アプリバで業務システム開発/前佛雅人
032 Raspberry Pi Pico W/WHで始める電子工作/米田聡
035 行動経済学と心理学で円滑に業務を遂行/請園正敏
038 Pythonあれこれ/飯尾淳 コード掲載
044 法林浩之のFIGHTING TALKS/法林浩之
046 中小企業手作りIT化奮戦記/菅雄一 コード掲載
052 エコシステム/桑原滝弥、イケヤシロウ
054 香川大学SLPからお届け!/大村空良
060 タイ語から分かる現地生活/つじみき
066 ユニケージ通信/ぱぺぽ
070 Linux定番エディタ入門/大津真
076 Techパズル/gori.sh
077 コラム「ユニケージが実現・目指しているところ」/シェル魔人
Pythonあれこれ(Vol.87掲載)
著者:飯尾淳
本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温かく見守ってください。皆さんと共に勉強していきましょう。第17回では、データを記録して残す「永続化」の手法の続編として、O/Rマッパーを使ってデータベースにアクセスする方法について解説します。
シェルスクリプトマガジン Vol.87は以下のリンク先でご購入できます。
図3 宣言的マッピングをするコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
from typing import List from typing import Optional from sqlalchemy import ForeignKey from sqlalchemy import String from sqlalchemy.orm import DeclarativeBase from sqlalchemy.orm import Mapped from sqlalchemy.orm import mapped_column from sqlalchemy.orm import relationship class Base(DeclarativeBase): pass class User(Base): __tablename__ = "user_account" id: Mapped[int] = mapped_column(primary_key=True) name: Mapped[str] = mapped_column(String(30)) fullname: Mapped[Optional[str]] addresses: Mapped[List["Address"]] = relationship( back_populates="user", cascade="all, delete-orphan" ) def __repr__(self) -> str: return f"User(id={self.id!r},\ name={self.name!r}, fullname={self.fullname!r})" class Address(Base): __tablename__ = "address" id: Mapped[int] = mapped_column(primary_key=True) email_address: Mapped[str] user_id: Mapped[int] = mapped_column(ForeignKey("user_account.id")) user: Mapped["User"] = relationship(back_populates="addresses") def __repr__(self) -> str: return f"Address(id={self.id!r},\ email_address={self.email_address!r})" |
図4 データベースエンジンとなるオブジェクトを作成するコード
1 2 |
from sqlalchemy import create_engine engine = create_engine("sqlite:///a_db.sqlite3", echo=True) |
図5 発行されるSQL
1 2 3 4 5 6 7 8 9 10 11 12 13 |
CREATE TABLE user_account ( id INTEGER NOT NULL, name VARCHAR(30) NOT NULL, fullname VARCHAR, PRIMARY KEY (id) ) CREATE TABLE address ( id INTEGER NOT NULL, email_address VARCHAR NOT NULL, user_id INTEGER NOT NULL, PRIMARY KEY (id), FOREIGN KEY(user_id) REFERENCES user_account (id) ) |
図6 データ操作用オブジェクトを作成するコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
from sqlalchemy.orm import Session with Session(engine) as session: spongebob = User( name="spongebob", fullname="Spongebob Squarepants", addresses=[Address(email_address="spongebob@sqlalchemy.org")], ) sandy = User( name="sandy", fullname="Sandy Cheeks", addresses=[ Address(email_address="sandy@sqlalchemy.org"), Address(email_address="sandy@squirrelpower.org"), ], ) patrick = User(name="patrick", fullname="Patrick Star") session.add_all([spongebob, sandy, patrick]) session.commit() |
図7 図6のコードを実行した際の画面出力(抜粋)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
BEGIN (implicit) INSERT INTO user_account (name, fullname) VALUES (?, ?) RETURNING id (略)('spongebob', 'Spongebob Squarepants') INSERT INTO user_account (name, fullname) VALUES (?, ?) RETURNING id (略)('sandy', 'Sandy Cheeks') INSERT INTO user_account (name, fullname) VALUES (?, ?) RETURNING id (略)('patrick', 'Patrick Star') INSERT INTO address (email_address, user_id) VALUES (?, ?) RETURNING id (略)('spongebob@sqlalchemy.org', 1) INSERT INTO address (email_address, user_id) VALUES (?, ?) RETURNING id (略)('sandy@sqlalchemy.org', 2) INSERT INTO address (email_address, user_id) VALUES (?, ?) RETURNING id (略)('sandy@squirrelpower.org', 2) COMMIT |
図8 簡単な検索をするコード
1 2 3 4 5 6 7 |
from sqlalchemy.orm import Session from sqlalchemy import select session = Session(engine) stmt = select(User).where(User.name.in_(["spongebob", "sandy"])) for user in session.scalars(stmt): print(user) |
図9 図8のコードを実行した際に発行されるSQL
1 2 3 4 5 |
BEGIN (implicit) SELECT user_account.id, user_account.name, user_account.fullname FROM user_account WHERE user_account.name IN (?, ?) (略) ('spongebob', 'sandy') |
図10 図8のコードを実行した際に表示される検索結果
1 2 |
User(id=1, name='spongebob', fullname='Spongebob Squarepants') User(id=2, name='sandy', fullname='Sandy Cheeks') |
図11 少し複雑な検索をするコード
1 2 3 4 5 6 7 |
stmt = ( select(Address) .join(Address.user) .where(User.name == "sandy") .where(Address.email_address == "sandy@sqlalchemy.org") ) sandy_address = session.scalars(stmt).one() |
図12 図11のコードを実行した際に発行されるSQL
1 2 3 4 |
SELECT address.id, address.email_address, address.user_id FROM address JOIN user_account ON user_account.id = address.user_id WHERE user_account.name = ? AND address.email_address = ? (略) ('sandy', 'sandy@sqlalchemy.org') |
図13 データ変更のシンプルなコード例
1 2 |
sandy_address.email_address = "sandy_cheeks@sqlalchemy.org" session.commit() |
図14 図13のコードを実行した際に発行されるSQL
1 2 3 |
UPDATE address SET email_address=? WHERE address.id = ? (略)('sandy_cheeks@sqlalchemy.org', 2) COMMIT |
図15 データ変更の少し複雑なコード例
1 2 3 4 5 |
stmt = select(User).where(User.name == "patrick") patrick = session.scalars(stmt).one() patrick.addresses \ .append(Address(email_address="patrickstar@sqlalchemy.org")) session.commit() |
図16 図15のコードを実行した際に発行されるSQL
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
SELECT user_account.id, user_account.name, user_account.fullname FROM user_account WHERE user_account.name = ? (略)('patrick',) SELECT address.id AS address_id, address.email_address AS address_email_address, address.user_id AS address_user_id FROM address WHERE ? = address.user_id (略)(3,) INSERT INTO address (email_address, user_id) VALUES (?, ?) (略)('patrickstar@sqlalchemy.org', 3) COMMIT |
香川大学SLPからお届け!(Vol.87掲載)
著者:大村 空良
プログラミングにおいて重要な要素の一つが、プログラムの実行速度です。プログラムの実行速度は、より良いアルゴリズムを選択することで大きく改善できます。今回は、巡回セールスマン問題を解くC++プログラムを、アルゴリズムを変更して高速化した例を紹介します。
シェルスクリプトマガジン Vol.87は以下のリンク先でご購入できます。
図2 図1の巡回セールスマン問題を解くC++コード「sample1.cpp」
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
#include <bits/stdc++.h> using namespace std; int main() { int n = 4, ans = 1e9-1, sum; int a[4][4] = { {0, 3, 7, 8}, {3, 0, 4, 4}, {7, 4, 0, 3}, {8, 4, 3, 0} }; vector<int> v_ans, v = {0, 1, 2, 3}; do { //初期化 sum = 0; //距離を足す for (int i = 1; i < n; i++) { sum += a[v[i-1]][v[i]]; } sum += a[v[n-1]][v[0]]; //比較 if (ans > sum) { ans = sum; v_ans = v; } } while (next_permutation(v.begin(), v.end())); //結果表示 for_each(v_ans.begin(), v_ans.end(), [](int& item) { item++; }); copy(v_ans.begin(), v_ans.end(), ostream_iterator<int>(cout, "→")); cout << v_ans[0] << endl; cout << ans << endl; return 0; } |
図3 都市数を変えられるように図2のコードを拡張した「sample2.cpp」
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
#include <bits/stdc++.h> #include "sales.h" using namespace std; int main() { int ans = 1e9-1, sum; vector<int> v_ans; do { sum = 0; for (int i = 1; i < n; i++) { sum += a[v[i-1]][v[i]]; } sum += a[v[n-1]][v[0]]; if (ans > sum) { ans = sum; v_ans = v; } } while (next_permutation(v.begin(), v.end())); for_each(v_ans.begin(), v_ans.end(), [](int& item) { item++; }); copy(v_ans.begin(), v_ans.end(), ostream_iterator<int>(cout, "→")); cout << v_ans[0] << endl; cout << ans << endl; return 0; } |
図4 ヘッダーファイル生成用のPythonスクリプト「make_header.py」
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
import random import sys text = "dummy" while not text.isdecimal(): print("整数値を入力してください: ", file=sys.stderr, end='') text = input() num = int(text) if num > 15 or num < 1: sys.exit(1) array = [[0] * num for i in range(num)] random.seed() for i in range(num): for j in range(num): if i != j: if array[j][i] != 0: array[i][j] = array[j][i] else: array[i][j] = random.randint(1, 9) print('#include <vector>') print('using namespace std;') print(f'int n = {num};') print(f'int a[{num}][{num}] = {{') for i in range(num): line = str(array[i])[1:-1] print('{' + line + '},') print('};') line = list(range(0, num)) line = str(line)[1:-1] print(f'vector<int> v = {{{line}}};') |
図5 フィボナッチ数を求めるC++コード「sample3.cpp」
1 2 3 4 5 6 7 8 9 10 11 12 |
#include <bits/stdc++.h> using namespace std; unsigned long long fib(char n) { if ((n <= 0) || (n > 93)) return 0; if (n == 1) return 1; return fib(n - 1) + fib(n - 2); } int main() { char n = 5; cout << fib(n) << endl; return 0; } |
図7 動的計画法を用いて改良したコード「sample4.cpp」
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#include <bits/stdc++.h> using namespace std; unsigned long long fib(char n) { if ((n <= 0) || (n > 93)) return 0; if (n == 1) return 1; unsigned long long f[128]; f[0] = 0; f[1] = 1; for (char i = 2; i <= n; i++) { f[i] = f[i - 1] + f[i - 2]; } return f[n]; } int main() { char n = 5; cout << fib(n) << endl; return 0; } |
図10 動的計画法を用いて改良したコード「sample5.cpp」
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
#include <bits/stdc++.h> #include "sales.h" using namespace std; int main() { int dp[(1<<n)][n]; for (int i = 0; i < (1<<n); i++) { for (int j = 0; j < n; j++) { dp[i][j] = 1e9-1; } } dp[0][0] = 0; for (int i = 0; i < (1<<n); i++) { for (int j = 0; j < n; j++) { for (int k = 0; k < n; k++) { if (i==0 || !(i & (1<<k)) && (i & (1<<j))) { if( j != k ) { dp[i | (1<<k)][k] = min(dp[i | (1<<k)][k], dp[i][j] + a[j][k]); } } } } } cout << dp[(1<<n) - 1][0] << endl; return 0; } |
特集1 Markdown入門ガイド(Vol.86記載)
著者:藤原 由来
本特集では、文書の装飾や構造付けを手軽に実施できる記法「Markdown」について解説します。Markdownを使うと、テキストファイルでレポートやスライド、書籍などを作成でき、GitHubなどの各種Webサービスでの投稿やコミュニケーションがより便利になります。
シェルスクリプトマガジン Vol.86は以下のリンク先でご購入できます。
図2 Markdownの記述内容
1 2 3 4 5 6 7 8 9 10 11 |
# カレーの作り方 おいしいカレーを作りましょう。 ## 材料 - カレールー - 牛肉 - 野菜 - サラダ油 - 水 |
図19 Markdownテキストの例
1 2 3 4 5 6 7 8 9 10 11 |
#␣カレーの作り方 おいしいカレーを作りましょう。 ##␣材料 -␣カレールー -␣牛肉 -␣野菜 -␣サラダ油 -␣水 |
図46 MarpによるスライドのMarkdownテキスト例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
--- marp: true --- # カレーの作り方 おいしいカレーを作りましょう。 --- # 材料 - カレールー - 牛肉 - 野菜 - サラダ油 - 水 |
特集1 COBOL入門(Vol.86記載)
著者:比毛 寛之
レガシー問題や技術者不足などで話題の多いプログラミング言語が「COBOL」です 。古い言語というイメージがありますが、ISOから2023年の新規格が公表されたことでも分かるように、現在も進化を続けています。本特集では、COBOLの現状を紹介しつつ、「opensource COBOL 4J」というオープンソースソフトウエアのCOBOLコンパイラを使ってCOBOL言語の基本を解説します。
シェルスクリプトマガジン Vol.86は以下のリンク先でご購入できます。
図1 「HELLO WORLD!」という文字列を表示するCOBOLのサンプルコード
1 2 3 4 5 6 7 8 9 10 11 12 |
----+----1----+----2----+----3----+----4----+----5 IDENTIFICATION DIVISION. PROGRAM-ID. HELLOWORLD. DATA DIVISION. WORKING-STORAGE SECTION. 01 MY-TEXT PIC X(20). PROCEDURE DIVISION. MAIN-RTN. MOVE "HELLO WORLD!" TO MY-TEXT. DISPLAY MY-TEXT. STOP RUN. ----+----1----+----2----+----3----+----4----+----5 |
図4 COBOLのサンプルコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
----+----1----+----2----+----3----+----4----+----5----+----6----+----7----+----8 IDENTIFICATION DIVISION. PROGRAM-ID. MYCOBOL. DATA DIVISION. WORKING-STORAGE SECTION. 01 MY-GRP. * 03 MY-WORK-X PIC X(10). 20230804 03 MY-WORK-X2 PIC X(10). 20230804 03 MY-WORK-9 PIC 9(05). ******************************************** PROCEDURE DIVISION. ******************************************** PROC1 SECTION. PROC1-000. * MOVE "ABC" TO MY-WORK-X. 20230804 MOVE "DEF" TO MY-WORK-X2. 20230804 MOVE 100 TO MY-WORK-9. PROC1-900. STOP RUN. ----+----1----+----2----+----3----+----4----+----5----+----6----+----7----+----8 |
図5 コンソールからの入力と画面への出力をするサンプルコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
----+----1----+----2----+----3----+----4----+----5----+ IDENTIFICATION DIVISION. PROGRAM-ID. HELLO. DATA DIVISION. WORKING-STORAGE SECTION. 01 MY-NAME PIC X(20). PROCEDURE DIVISION. MAIN-RTN. DISPLAY "Enter your name: " NO ADVANCING. ACCEPT MY-NAME. DISPLAY "Hello " MY-NAME. MAIN-EXIT. STOP RUN. ----+----1----+----2----+----3----+----4----+----5----+ |
図6 ファイルにデータを出力するサンプルコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
----+----1----+----2----+----3----+----4----+----5----+----6----+----7 IDENTIFICATION DIVISION. PROGRAM-ID. EMPWRITE. ENVIRONMENT DIVISION. INPUT-OUTPUT SECTION. FILE-CONTROL. SELECT EMP-FILE ASSIGN TO "EMPFILE" ORGANIZATION IS INDEXED ACCESS MODE IS DYNAMIC RECORD KEY IS EMP-CD FILE STATUS IS EMP-STS. DATA DIVISION. FILE SECTION. FD EMP-FILE. 01 EMP-REC. 03 EMP-CD PIC X(04). 03 EMP-NAME PIC X(20). 03 EMP-DPT-CD PIC X(02). 03 EMP-ENT-DATE PIC 9(08). WORKING-STORAGE SECTION. 01 EMP-STS PIC 9(02). PROCEDURE DIVISION. MAIN-CONTROL SECTION. MAIN-000. DISPLAY "*** Creating Employee file ***". OPEN OUTPUT EMP-FILE. * MOVE "0011" TO EMP-CD. MOVE "Saitama Saburo" TO EMP-NAME. MOVE "01" TO EMP-DPT-CD. MOVE 20020401 TO EMP-ENT-DATE. WRITE EMP-REC. * ----+----+----+----+----+----+----+ WRITE EMP-REC FROM "0012Chiba Jiro 0219990401". WRITE EMP-REC FROM "0013Tokyo Taro 0319970401". WRITE EMP-REC FROM "0014Kanagawa Shiro 0120120401". WRITE EMP-REC FROM "0015Niigata Goroo 0220010401". * ----+----+----+----+----+----+----+ CLOSE EMP-FILE. MAIN-900. STOP RUN. ----+----1----+----2----+----3----+----4----+----5----+----6----+----7 |
図7 ファイル中のレコードを読み出して画面に表示するサンプルコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
----+----1----+----2----+----3----+----4----+----5----+----6----+ IDENTIFICATION DIVISION. PROGRAM-ID. EMPLIST. ENVIRONMENT DIVISION. INPUT-OUTPUT SECTION. FILE-CONTROL. SELECT EMP-FILE ASSIGN TO "EMPFILE" ORGANIZATION IS INDEXED ACCESS MODE IS DYNAMIC RECORD KEY IS EMP-CD FILE STATUS IS EMP-STS. DATA DIVISION. FILE SECTION. FD EMP-FILE. 01 EMP-REC. 03 EMP-CD PIC X(04). 03 EMP-NAME PIC X(20). 03 EMP-DPT-CD PIC X(02). 03 EMP-ENT-DATE PIC 9(08). WORKING-STORAGE SECTION. 01 EMP-STS PIC 9(02). 01 DSP-REC. 03 DSP-CD PIC X(04). 03 FILLER PIC X. 03 DSP-NAME PIC X(20). 03 FILLER PIC XX. 03 DSP-DPT-CD PIC X(02). 03 FILLER PIC X. 03 DSP-ENT-DATE PIC 9999/99/99. PROCEDURE DIVISION. MAIN-CONTROL SECTION. MAIN-000. OPEN INPUT EMP-FILE. DISPLAY "*** Employee List ***". DISPLAY "ID Employee Name Dpt Enter date". DISPLAY "---- -------------------- --- ----------". PERFORM UNTIL (EMP-STS NOT = ZERO) READ EMP-FILE NEXT AT END DISPLAY "EOF" NOT AT END MOVE EMP-CD TO DSP-CD MOVE EMP-NAME TO DSP-NAME MOVE EMP-DPT-CD TO DSP-DPT-CD MOVE EMP-ENT-DATE TO DSP-ENT-DATE DISPLAY DSP-REC END-READ END-PERFORM. CLOSE EMP-FILE. MAIN-900. STOP RUN. ----+----1----+----2----+----3----+----4----+----5----+----6----+ |
図9 「EMPSEARCH.cbl」ファイルに記述するコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
----+----1----+----2----+----3----+----4----+----5----+----6----+- IDENTIFICATION DIVISION. PROGRAM-ID. EMPSEARCH. DATA DIVISION. WORKING-STORAGE SECTION. 01 WK-AREA. 03 WK-CD PIC X(04). 03 WK-NAME PIC X(20). 03 WK-DPT-CD PIC X(02). 03 WK-ENT-DATE PIC 9(08). 03 WK-RETURN PIC 9(01). PROCEDURE DIVISION. MAIN-RTN. DISPLAY "*** Employee Search ***". DISPLAY "Code: : " NO ADVANCING. ACCEPT WK-CD. CALL "EMPREAD" USING WK-CD, WK-NAME, WK-DPT-CD, WK-ENT-DATE, WK-RETURN. IF WK-RETURN = ZERO DISPLAY "Name : " WK-NAME DISPLAY "Dept code : " WK-DPT-CD DISPLAY "Enter date: " WK-ENT-DATE ELSE DISPLAY "Employee not found!" END-IF. MAIN-EXIT. STOP RUN. ----+----1----+----2----+----3----+----4----+----5----+----6----+- |
図10 「EMPREAD.cbl」ファイルに記述するコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
----+----1----+----2----+----3----+----4----+----5----+----6----+----7 IDENTIFICATION DIVISION. PROGRAM-ID. EMPREAD. ENVIRONMENT DIVISION. INPUT-OUTPUT SECTION. FILE-CONTROL. SELECT EMP-FILE ASSIGN TO "EMPFILE" ORGANIZATION IS INDEXED ACCESS MODE IS DYNAMIC RECORD KEY IS EMP-CD FILE STATUS IS EMP-STS. DATA DIVISION. FILE SECTION. FD EMP-FILE. 01 EMP-REC. 03 EMP-CD PIC X(04). 03 EMP-NAME PIC X(20). 03 EMP-DPT-CD PIC X(02). 03 EMP-ENT-DATE PIC 9(08). WORKING-STORAGE SECTION. 01 EMP-STS PIC 9(02). LINKAGE SECTION. 01 LK-CD PIC X(04). 01 LK-NAME PIC X(20). 01 LK-DPT-CD PIC X(02). 01 LK-ENT-DATE PIC 9(08). 01 LK-RETURN PIC 9(01). PROCEDURE DIVISION USING LK-CD, LK-NAME, LK-DPT-CD, LK-ENT-DATE, LK-RETURN. MAIN-CONTROL SECTION. MAIN-000. INITIALIZE EMP-REC. MOVE ZERO TO LK-RETURN. OPEN INPUT EMP-FILE. MOVE LK-CD TO EMP-CD. READ EMP-FILE KEY IS EMP-CD INVALID KEY MOVE 1 TO LK-RETURN END-READ. MOVE EMP-NAME TO LK-NAME. MOVE EMP-DPT-CD TO LK-DPT-CD. MOVE EMP-ENT-DATE TO LK-ENT-DATE. CLOSE EMP-FILE. MAIN-900. EXIT PROGRAM. ----+----1----+----2----+----3----+----4----+----5----+----6----+----7 |
図11 「EmpSearchDemo.java」ファイルに記述するコード
1 2 3 4 5 6 7 8 9 10 11 12 13 |
import jp.osscons.opensourcecobol.libcobj.ui.*; public class EmpSearchDemo { public static void main(String[] args) throws Exception { EMPREAD prog = new EMPREAD(); CobolResultSet rs = prog.execute("0011", "", "", 0, 0); System.out.println("*** Employee Search from Java ***"); System.out.println("Code : " + rs.getString(1)); System.out.println("Name : " + rs.getString(2)); System.out.println("Dept code : " + rs.getString(3)); System.out.println("Enter date: " + rs.getInt(4)); } } |
Raspberry Pi Pico W/WHで始める電子工作(Vol.86掲載)
著者:米田 聡
本連載では、人気のマイコンボード「Raspberry Pi Pico W」を活用していきます。同ボードは、無線LANやBluetoothの通信機能を搭載し、入手しやすく価格も手頃なので、IoT機器を自作するのに最適なハードウエアです。第2回は、有機ELディスプレイを取り付けます。
シェルスクリプトマガジン Vol.86は以下のリンク先でご購入できます。
図6 SSD1306搭載有機ELディスプレイを動作させるサンプルプログラム(ssd1306_sample.py)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
from machine import I2C from ssd1306 import SSD1306_I2C import network i2c = I2C(0) ssd = SSD1306_I2C(width=128, height=32, i2c=i2c, addr=0x3C) ssd.fill(0) # 0クリア ssd.text("Hello, world", 0, 0) # IPアドレスを描画 conn = network.WLAN(network.STA_IF) if conn.isconnected(): conf = conn.ifconfig() ssd.text(conf[0], 0, 8) ssd.show() # フレームバッファをSSD1306に転送 |
Pythonあれこれ(Vol.86掲載)
著者:飯尾 淳
本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温
かく見守ってください。皆さんと共に勉強していきましょう。第16回では、データを永続的に残す「永続化」の手法の一つとして、Pythonプログラムからデータベースにアクセスする方法について解説します。
シェルスクリプトマガジン Vol.86は以下のリンク先でご購入できます。
図3 データベースにテーブルを作成してデータを格納するコード
1 2 3 4 5 6 7 8 |
from sqlalchemy import text with engine.connect() as conn: conn.execute(text("CREATE TABLE a_table (id int, name string)")) conn.execute( text("INSERT INTO a_table (id, name) VALUES (:id, :name)"), [{"id":1, "name":"John"}, {"id":2, "name":"Bob"}], ) conn.commit() |
図5 テーブルのデータを読み出すコード
1 2 3 4 |
with engine.connect() as conn: result = conn.execute(text("SELECT * FROM a_table")) for record in result: print(f"id: {record.id}, name: {record.name}") |
図6 commit()を忘れたコードの例
1 2 3 4 5 6 |
with engine.connect() as conn: conn.execute( text("INSERT INTO a_table (id, name) VALUES (:id, :name)"), [{"id":3, "name":"Kate"}, {"id":4, "name":"Bob"}], ) conn.commit() |
図8 データベースに含まれるテーブルを表示するコード
1 2 3 4 5 6 7 8 |
from sqlalchemy import create_engine engine = create_engine("sqlite:///:memory:", echo=True) with engine.connect() as conn: result = conn.execute( text("SELECT name FROM sqlite_master WHERE type='table'") ) for record in result: print(record) |
図9 ファイル内にデータベースを作成するコード
1 2 3 4 5 6 7 8 9 |
from sqlalchemy import create_engine, text engine = create_engine("sqlite:///db.sqlite3") with engine.connect() as conn: conn.execute(text("CREATE TABLE a_table (id int, name string)")) conn.execute( text("INSERT INTO a_table (id, name) VALUES (:id, :name)"), [{"id": 1, "name": "John"}, {"id": 2, "name": "Bob"}], ) conn.commit() |
図10 ファイル内データベースのテーブルからデータを読み出すコード
1 2 3 4 5 6 |
from sqlalchemy import create_engine, text engine = create_engine("sqlite:///db.sqlite3") with engine.connect() as conn: result = conn.execute(text("SELECT * FROM a_table")) for record in result: print(f"id: {record.id}, name: {record.name}") |
香川大学SLPからお届け!(Vol.86掲載)
著者:石上 椋一
今回は、私が開発した文章校正用のWebアプリケーションについて紹介します。文章校正機能は「textlint」というNode.js上で稼働するアプリケーションを使って実現しているため、少ないコード量で実装できました。米Google社のアプリケーション開発プラットフォーム「Firebase」を利用したユーザー認証機能を付加する方法も解説します。
シェルスクリプトマガジン Vol.86は以下のリンク先でご購入できます。
図1 テンプレートファイル「index.html」に記述するコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<!doctype html> <html lang="ja"> <head> <title>文章校正アプリ</title> </head> <body> <h1>こんにちは!文章校正アプリです</h1> <h2>PDFファイルアップローダー</h2> <form action="/result" method="POST" enctype="multipart/form-data"> <input type=file name="pdf_file"> <button>ファイル送信</button> </form> </body> </html> |
図2 テンプレートファイル「result.html」に記述するコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
<!doctype html> <html lang="ja"> <head> <title>文章校正アプリ 結果表示</title> </head> <body> <h1>こんにちは!文章校閲アプリです</h1> <h2>PDFファイルアップローダー</h2> <form action="/result" method="POST" enctype="multipart/form-data"> <input type=file name="pdf_file"> <button>ファイル送信</button> </form> <table border="1"> <thead> <tr> <th>何行目</th> <th>何文字目</th> <th>修正内容</th> </tr> </thead> <tbody> {% for result in result_table %} <tr> <td>{{result[0]}}</td> <td>{{result[1]}}</td> <td>{{result[2]}}</td> </tr> {% endfor %} </tbody> </table> <br> <p>修正した文章</p> <textarea rows="10" cols="80">{{result_text}}</textarea> </body> </html> |
図3 「web_app.py」ファイルに記述するコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
from flask import Flask, request, render_template, \ redirect, url_for, session import subprocess import re app = Flask(__name__) def upload_pdf_file(pdf_file): pdf_file.save("uploads/target.pdf") def run_pdf2text(): cmd = "pdftotext -nopgbrk uploads/target.pdf uploads/target.md" subprocess.run(cmd, shell=True, capture_output=True, text=True) def run_textlint_and_get_result_list(): cmd = "npx textlint uploads/target.md" result = subprocess.run(cmd, shell=True, capture_output=True, text=True) result_list = result.stdout.split("\n") result_table = [] for result in result_list[2:-4]: tmp_result = result.split(" error ") if len(tmp_result) >= 2: row = int(tmp_result[0].split(":")[0].strip()) colum = tmp_result[0].split(":")[1].strip() error_data = re.sub("ja-technical-writing/[a-z | -]*", "",tmp_result[1]).strip() result_table.append([row, colum, error_data]) return result_table def run_textlint_fix_data(): cmd = "npx textlint --fix ./uploads/target.md" subprocess.run(cmd, shell=True, capture_output=True, text=True) with open("./uploads/target.md", ) as md_file: data_lines = md_file.read() return data_lines @app.route("/index", methods=["GET"]) def index(): return render_template("index.html") @app.route("/", methods=["GET"]) def root(): return redirect(url_for("index")) @app.route("/result", methods=["GET", "POST"]) def result(): if request.method == "POST": pdf_file = request.files["pdf_file"] upload_pdf_file(pdf_file) run_pdf2text() result_table = run_textlint_and_get_result_list() result_text = run_textlint_fix_data() return render_template("result.html", result_table=result_table, result_text=result_text) return redirect(url_for('index')) if __name__ == "__main__": app.debug = True app.run(host='0.0.0.0', port=5000) |
図4 「.textlintrc.json」ファイルに記述する設定の例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
{ "rules": { "preset-ja-technical-writing": { "sentence-length": { "max": 100 }, "max-comma": { "max": 4 }, "max-ten": { "max": 4 }, "max-kanji-continuous-len": { "max": 7, "allow": [] }, "arabic-kanji-numbers": true, "no-mix-dearu-desumasu": { "preferInHeader": "", "preferInBody": "である", "preferInList": "である", "strict": true }, "ja-no-mixed-period": { "periodMark": "。" }, "no-double-negative-ja": true, "no-dropping-the-ra": true, "no-doubled-conjunctive-particle-ga": true, "no-doubled-conjunction": true, "no-doubled-joshi": { "min_interval": 1 }, "no-invalid-control-character": true, "no-zero-width-spaces": true, "no-exclamation-question-mark": true, "no-hankaku-kana": true, "ja-no-weak-phrase": true, "ja-no-successive-word": true, "ja-no-abusage": true, "ja-no-redundant-expression": true, "ja-unnatural-alphabet": true, "no-unmatched-pair": true } } } |
図10 「~/webapp/static/json/firebase.json」ファイルに記述する設定の例
1 2 3 4 5 6 7 8 9 |
{ "apiKey": "AIzaSyA1LQooSfvSda0jkBQl20ZfGR6lHOC5XBw", "authDomain": "test-1d03e.firebaseapp.com", "databaseURL": "https://test-1d03e-default-rtdb.firebaseio.com", "projectId": "test-1d03e", "storageBucket": "test-1d03e.appspot.com", "messagingSenderId": "919322583901", "appId": "1:919322583901:web:abb5d744e9ed04b8927eb4" } |
図11 テンプレートファイル「create_account.html」に記述するコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<!doctype html> <html lang="ja"> <head> <title>文章校正アプリ アカウント作成ページ</title> </head> <body> <form class="login-form" action='/create_account' method='POST'> <input type="text" name="email" placeholder="email address"/> <input type="password" name="password" placeholder="password"/> <button>Create an account</button> <p>{{msg}}</p> <p>Already registered? <a href="/login">Login</a> </p> </form> </body> </html> |
図12 テンプレートファイル「login.html」に記述するコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<!doctype html> <html lang="ja"> <head> <title>文章校正アプリ ログインページ</title> </head> <body> <form class="login-form" action="/login" method="POST"> <input type="text" name="email" placeholder="email address"/> <input type="password" name="password" placeholder="password"/> <button>Login</button> <p>{{msg}}</p> <p>Not registered? <a href="/create_account">Create an account</a> </p> </form> </body> </html> |
図13 「web_app.py」ファイルの「app = Flask(name)」行の前後に追加するコード
1 2 3 4 5 6 7 8 9 |
import os, json import pyrebase app = Flask(__name__) app.config["SECRET_KEY"] = os.urandom(24) with open("static/json/firebase.json") as f: firebaseConfig = json.loads(f.read()) firebase = pyrebase.initialize_app(firebaseConfig) auth = firebase.auth() |
図14 「web_app.py」ファイルの既存のルーティング設定にコードを追加
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
(略) @app.route("/index", methods=["GET"]) def index(): if session.get("usr") == None: return redirect(url_for("login")) return render_template("index.html") @app.route("/", methods=["GET"]) def root(): return redirect(url_for("index")) @app.route("/result", methods=["GET", "POST"]) def result(): if session.get("usr") == None: return redirect(url_for("login")) if request.method == "POST": pdf_file = request.files["pdf_file"] (略) |
図15 「web_app.py」ファイルにルーティング設定を追加
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
@app.route("/login", methods=["GET", "POST"]) def login(): if request.method == "GET": return render_template("login.html", msg="") email = request.form["email"] password = request.form["password"] try: auth.sign_in_with_email_and_password(email, password) session["usr"] = email return redirect(url_for("index")) except: message = "メールアドレスまたはパスワードが違います" return render_template("login.html", msg=message) @app.route("/create_account", methods=["GET", "POST"]) def create_account(): if request.method == "GET": return render_template("create_account.html", msg="") email = request.form["email"] password = request.form["password"] try: auth.create_user_with_email_and_password(email, password) session["usr"] = email return redirect(url_for("index")) except: message = "アカウントを作成できませんでした" return render_template("login.html", msg=message) @app.route('/logout') def logout(): del session["usr"] return redirect(url_for("login")) |
シェルスクリプトマガジンvol.86 Web掲載記事まとめ
004 レポート Python in Excelの開発版登場
005 レポート iPhone 15シリーズを発表
006 製品レビュー 電子小物「koko Tag(ココタグ)」
007 NEWS FLASH
008 特集1 Markdown入門ガイド/藤原由来 コード掲載
024 特集2 最新COBOL入門/比毛寛之 コード掲載
034 特別企画 CMSのPlone/酒井忠臣、烈
046 Raspberry Pi Pico W/WHで始める電子工作/米田聡 コード掲載
050 CMS/桑原滝弥、イケヤシロウ
052 Pythonあれこれ/飯尾淳 コード掲載
057 Hello Nogyo!
058 中小企業手作りIT化奮戦記/菅雄一
062 法林浩之のFIGHTING TALKS/法林浩之
064 香川大学SLPからお届け!/石上椋一 コード掲載
072 行動経済学と心理学で円滑に業務を遂行/請園正敏
076 タイ語から分かる現地生活/つじみき
082 Linux定番エディタ入門/大津真
088 ユニケージ通信/田渕智也、高橋未来哉 コード掲載
091 Techパズル/gori.sh
093 コラム「ユニケージアーキテクチャ」/シェル魔人
シェルスクリプトマガジンvol.85 Web掲載記事まとめ
004 レポート Wasm向けPOSIX互換ライブラリ「WASIX」
005 レポート 手のひらネットワーク機器
006 製品レビュー ロボット「NICOBO(ニコボ)」
007 NEWS FLASH
008 特集1 ゆっくりMovieMaker4で映像制作 準備編/ふる(FuruyamD)
015 Hello Nogyo!
016 特集2 ZabbixのAWS監視機能を使ってみよう/寺島広大、渡邊隼人、水谷和弘、狩俣樹
026 特集3 ChatGPTで注目の対話型AI/三沢友治
034 特別企画 成功するアジャイル開発/奥村剛史
044 Raspberry Pi Pico W/WHで始める電子工作/米田聡 コード掲載
054 Pythonあれこれ/飯尾淳
060 行動経済学と心理学で円滑に業務を遂行/請園正敏
064 中小企業手作りIT化奮戦記/菅雄一
068 SEO/桑原滝弥、イケヤシロウ
070 タイ語から分かる現地生活/つじみき
076 法林浩之のFIGHTING TALKS/法林浩之
078 香川大学SLPからお届け!/谷知紘 コード掲載
084 Linux定番エディタ入門/大津真
091 ユニケージ通信/田渕智也、高橋未来哉
096 Techパズル/gori.sh
097 コラム「ユニケージはデータ移行も得意」/シェル魔人
Raspberry Pi Pico W/WHで始める電子工作(Vol.85掲載)
著者:米田 聡
本連載では、人気のマイコンボード「Raspberry Pi Pico W/WH」を活用していきます。同ボードは、無線LANやBluetoothの通信機能を搭載し、入手しやすく、価格も手頃なので、IoT機器を自作するのに最適なハードウエアです。第1回は、プログラムの開発環境を構築します。
シェルスクリプトマガジン Vol.85は以下のリンク先でご購入できます。
図17 初期化プログラム(boot.py)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
import sys import time import network import ntptime from machine import RTC SSID='your_ssid' PASS='your_passphrase' IFCONFIG=('192.168.1.80', '255.255.255.0', '192.168.1.1', '192.168.1.1') def wifi_connect(ssid, passkey, timeout=20): conn = network.WLAN(network.STA_IF) if conn.isconnected(): return conn conn.active(True) conn.connect(ssid, passkey) while not conn.isconnected() and timeout > 0: time.sleep(1) timeout -= 1 if conn.isconnected(): return conn else: return None conn = wifi_connect(SSID, PASS) if conn is None: print('Can not connect to ' + SSID) else: conn.ifconfig(IFCONFIG) print('Connect to ' + SSID) # 日時設定 rtc = RTC() ntptime.host = 'ntp.nict.jp' now = time.localtime(ntptime.time() + 9 * 60 * 60) rtc.datetime((now[0], now[1], now[2], now[6], now[3], now[4], now[5], 0)) now = rtc.datetime() print("%04d-%02d-%02d %02d:%02d:%02d" %(now[0], now[1], now[2], now[4], now[5], now[6])) |
図18 簡易ネットワークサーバー(main.py)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
import usocket as socket import network import time html = ''' <html> <head><title>Pico W</title></head> <body> <h1>Welcome to Pico W</h1> </body> </html> ''' sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.bind(('', 80)) sock.listen(4) while True: conn, addr = sock.accept() print('Connect from %s' %(str(addr))); req = conn.recv(1024).decode() if not req.startswith('GET / HTTP/1.1'): conn.send('HTTP/1.1 404 Not Found\r\n') conn.close() else: conn.send('HTTP/1.1 200 OK\r\n') conn.send('Content-Type: text/html\r\n') conn.send('Connection: close\r\n\r\n') conn.sendall(html) conn.close() |
香川大学SLPからお届け!(Vol.85掲載)
著者:谷 知紘
今回は、C言語を用いて作成した、コンピュータと対戦できるリバーシを紹介します。あまり強くはありませんが、きちんとコンピュータが相手をしてくれます。C99以降の規格に対応するCコンパイラと標準Cライブラリがあれば、環境を問わずに動作するプログラムになっていますので、皆さんもコンパイルして楽しんでください。
シェルスクリプトマガジン Vol.85は以下のリンク先でご購入できます。
図5 盤面の生成用コード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
//盤面 空白(0) 黒(-1) 白(1) 番兵(2) int board[10][10] = {0}; //スコアを格納する配列 int weightdata[10][10] = { {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 30,-12, 0, -1, -1, 0,-12, 30, 0}, {0,-12,-15, -3, -3, -3, -3,-15,-12, 0}, {0, 0, -3, 0, -1, -1, 0, -3, 0, 0}, {0, -1, -3, -1, -1, -1, -1, -3, -1, 0}, {0, -1, -3, -1, -1, -1, -1, -3, -1, 0}, {0, 0, -3, 0, -1, -1, 0, -3, 0, 0}, {0,-12,-15, -3, -3, -3, -3,-15,-12, 0}, {0, 30,-12, 0, -1, -1, 0,-12, 30, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0} }; //盤面の生成 void make_board(){ //番兵 for(int i = 0; i < 10; i++){ board[0][i] = 2; board[9][i] = 2; board[i][0] = 2; board[i][9] = 2; } //初期配置する石 board[4][4] = 1; board[5][5] = 1; board[4][5] = -1; board[5][4] = -1; } |
図6 check_plc()関数のコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
//指定したマスに石を置けるかどうかを判定する関数 bool check_plc(int i, int j, int now_board[10][10]){ //マスが空かどうか if(board[i][j] == 0){ //全方向を探索 for(int dir_i = -1; dir_i < 2; dir_i++){ for(int dir_j = -1; dir_j < 2; dir_j++){ if(check_dir(i,j,dir_i,dir_j,now_board)){ //配置可能であればtrueを返す return true; } } } } return false; } |
図8 check_dir()関数のコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
//指定方向に何個の石を挟めるかを調べる関数 int check_dir(int i, int j, int dir_i, int dir_j, int now_board[10][10]){ //指定方向に相手の石がある場合は次のマスを探索 int times = 1; while(now_board[i+dir_i*times][j+dir_j*times] == player*-1){ times++; } //指定方向の最後に自分の石がある場合 if(now_board[i+dir_i*times][j+dir_j*times] == player){ //指定方向に相手の石が何個あるかを返す return times-1; } //指定方向の最後に自分の石がなければ0を返す return 0; } |
図9 place_stn()関数のコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
//指定したマスに石を配置して、挟んだ石を自分の石にする関数 void place_stn(int i, int j, int now_board[10][10]){ //全方向を調査 for(int dir_i = -1; dir_i < 2; dir_i++){ for(int dir_j = -1; dir_j < 2; dir_j++){ //挟んだ石の数 int change_num = check_dir(i,j,dir_i,dir_j,now_board); //挟んだ石の数だけ自分の石に変更 for(int k = 1; k < change_num+1; k++){ now_board[i+dir_i*k][j+dir_j*k] = player; } } } //指定したマスに自分の石を配置 now_board[i][j] = player; } |
図10 think()関数のコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
//コンピュータの手番で呼び出される関数 void think(){ //ハイスコアの初期化 int hightscore = -1000; int px, py; for(int y = 0; y < 10; y++){ for(int x = 0; x < 10; x++){ //石を置けない場合はスキップ if(!check_plc(y, x, board)){ continue; } int tmpdata[10][10] = {0}; //盤面データをコピーした仮の盤面を作成 copydata(tmpdata); //仮の盤面に石を置く place_stn(y, x, tmpdata); //総スコアを計算する int score = calcweight(tmpdata); //ハイスコアよりも総スコアが良ければ更新する if(score > hightscore){ hightscore = score; px = x; py = y; } } } //総スコアが最大のマスに石を置く place_stn(py, px, board); printf("PCは(x , y) = (%d , %d)に置きました\n", px, py); } |
図11 copydata()関数のコード
1 2 3 4 5 6 7 8 |
//盤面データをコピーする関数 void copydata(int tmpdata[10][10]){ for(int y = 0; y < 10; y++){ for(int x = 0; x < 10; x++){ tmpdata[y][x] = board[y][x]; } } } |
図12 calcweight()関数のコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
//総スコアを計算する関数 int calcweight(int tmpdata[10][10]){ int score = 0; for(int y = 0; y < 10; y++){ for(int x = 0; x < 10; x++){ //番兵はスキップ if(tmpdata[y][x] == 2){ continue; } //自分の石がある場所のスコアを足す if(tmpdata[y][x] == 1){ score += weightdata[y][x]; } } } return score; } |
Vol.85 補足情報
特集1 ゆっくりMovieMaker4で映像制作 準備編
p.12の表1の一番下の行にある「00d.png」は「00e.png」の誤りです。お詫びして訂正いたします。
特集2 ZabbixのAWS監視機能を使ってみよう
p.18に記した「後述するAWS用のテンプレートの利用を含め、すべてAWSの無料利用枠で試せる」という情報は誤りでした。正しくは、AWS用のテンプレート利用時には、CloudWatchからGetMetricData APIでメトリクスを取得する料金がかかります。料金は、メトリクス1000件当たり0.01ドルです。お詫びして訂正いたします。なお、Kindle版とPDF版(定期購読特典)は修正済みです。
情報は随時更新致します。
シェルスクリプトマガジンvol.84 Web掲載記事まとめ
004 レポート オープンソースLLM「MPT-7B」登場
005 レポート メタバースファッションフェア初開催
006 製品レビュー 調理家電「コーヒー豆焙煎機 MR-F60A」
007 NEWS FLASH
008 特集1 pandasを使いこなそう/鶴長鎮一
020 特集2 AutoML徹底解説 応用編/田村孝、山口武彦、新田陸、細野友基
034 特別企画 Raspberry Piを100%活用しよう 拡大版/米田聡 コード掲載
046 注目技術「XR BASE」/井上香
048 Pythonあれこれ/飯尾淳 コード掲載
054 法林浩之のFIGHTING TALKS/法林浩之
056 中小企業手作りIT化奮戦記/菅雄一 コード掲載
060 ツールキット/桑原滝弥、イケヤシロウ
062 タイ語から分かる現地生活/つじみき
068 香川大学SLPからお届け!/三井颯剛 コード掲載
075 Hello Nogyo!
076 行動経済学と心理学で円滑に業務を遂行/請園正敏
080 Linux定番エディタ入門/大津真 コード掲載
086 AWKでデジタル信号処理/斉藤博文 コード掲載
094 Techパズル/gori.sh
095 コラム「ユニケージにおける独特なドキュメント」/シェル魔人
特別企画 Raspberry Piを100%活用しよう 拡大版(Vol.84記載)
著者:米田 聡
小型コンピュータボード「Raspberry Pi」(ラズパイ)向けにさまざまな拡張ボードが発売されています。その拡張ボードとラズパイを組み合わせれば、ラズパイでいろいろなことが簡単に試せます。本企画では、前回に引き続き、ラズパイと連携して使用するマイコンボードを扱います。
シェルスクリプトマガジン Vol.84は以下のリンク先でご購入できます。
図8 I★2★CアドレスとGPIOピン番号の設定(ADRS2040U_i2c.h)
1 2 3 4 5 6 |
// ADRS2040U I2Cアドレス #define I2C0_SLAVE_ADDR 0x41 // I2Cで使うGPIO #define GPIO_SDA0 0 #define GPIO_SCK0 1 |
図9 I2CとI2C Slaveライブラリの初期化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
#include <stdio.h> #include <pico/stdlib.h> #include "hardware/i2c.h" ← hardware_i2cライブラリのヘッダーファイル #include "pico/i2c_slave.h" ← I2C Slaveライブラリのヘッダーファイル (略) #include "ADRS2040U_i2c.h" (略) static void i2c_slave_handler(i2c_inst_t *i2c, i2c_slave_event_t event) ← I★★2C割り込みコールバック関数 { (略) } void i2c_setup(void) { i2c_init(i2c0, 100 * 1000); ← I★2★Cコントローラの初期化(通信速度100Kビット/秒) gpio_set_function(GPIO_SDA0, GPIO_FUNC_I2C); ← I★2★Cで利用するGPIOピンの設定 gpio_set_function(GPIO_SCK0, GPIO_FUNC_I2C); // ADRS2040Uでは基板上でプルアップされているので // プルアップを無効化する gpio_disable_pulls(GPIO_SDA0); gpio_disable_pulls(GPIO_SCK0); (略) // I2Cスレーブ初期化 i2c_slave_init(i2c0, I2C0_SLAVE_ADDR, &i2c_slave_handler); } |
図10 コールバック関数「i2c_slave_handler()」
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
static void i2c_slave_handler(i2c_inst_t *i2c, i2c_slave_event_t event) { (略) switch(event) { // I2Cデータ受信 case I2C_SLAVE_RECEIVE: (略) break; // I2Cデータ要求 case I2C_SLAVE_REQUEST: (略) break; // STOP or RESTARTコンデション case I2C_SLAVE_FINISH: break; default: break; } } |
図11 ADCの初期化で呼び出す関数
1 2 3 4 |
adc_init(); adc_gpio_init(26); adc_select_input(0); |
図12 adc_fifo_setup()関数の引数パラメータ
1 2 3 4 5 6 7 |
adc_fifo_setup( en, dreq_en, dreq_threash, error_in_fifo, byte_shift ); |
図13 ADCの受信FIFOバッファ割り込み処理のサンプルコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
uint16_t adc_buffer[1024]; static int p = 0; (略) // ADC FIFO割り込み関数 void adc_interrupt(void) { // FIFOが空になるまでデータをadc_bufferに読み取る while(! adc_fifo_is_empty()) { adc_buffer[p++] = adc_fifo_get() & 0xFFF; } } void main(void) { (略) adc_fifo_setup(true, false, 1, true, false); (略) // 排他的割り込みの設定 irq_set_exclusive_handler(ADC_IRQ_FIFO, adc_interrupt); // 割り込みの有効化 irq_set_enabled(ADC_IRQ_FIFO, true); // フリーランスタート adc_run(true); (略) } |
図14 サンプリングレート設定のサンプルコード
1 2 3 4 |
int sample_rate = 1000; // 1000 サンプリング/秒 adc_clk = clock_get_hz(clk_adc); adc_set_clkdiv(adc_clk/sample_rate); |
図15 リングバッファのサンプルコード
1 2 3 4 5 6 7 8 9 |
uint16_t buffer[0x100]; int write_pointer, read_pointer; write_pointer = read_pointer = 0; // バッファの読み出し data = buffer[(read_pointer++) & 0xFF]; // バッファへの書き込み buffer[(write_pointer++) & 0xFF] = data; |
図16 クリティカルセクションの保護
1 2 3 4 5 |
mutex_enter_blocking(&my_mutex); ここがクリティカルセクション mutex_exit(&my_mutex); |
図17 サンプルファームウエアのレジスタ番号定義(ADRS2040U_i2c.h)
1 2 3 4 5 6 7 8 9 |
// I2Cレジスタ enum ADRS2040_CMD { ADRS2040_CMD_INVALID, // 無効 ADRS2040_CMD_ADC_START, // ADCフリーラン開始 ADRS2040_CMD_ADC_STOP, // ADCフリーラン停止 ADRS2040_CMD_SET_RATE, // サンプリングレート設定 ADRS2040_CMD_GET_COUNT, // バッファデータ数 ADRS2040_CMD_GET_VALUE, // ADCデータ読み取り }; |
図18 I2Cスレーブ動作時に使えるFIFOバッファ読み書き関数
1 2 3 4 5 6 7 8 |
// 1バイト読みだし i2c_read_byte_raw(i2c_inst_t *); // 指定バイト数の読み出し i2c_read_raw_blocking(i2c_inst_t *, uint8_t *, size_t); // 1バイト書き込み i2c_write_byte_raw(i2c_inst_t *, uint8_t); // 指定バイト数の書き込み i2c_write_raw_blocking(i2c_inst_t *, uint8_t *, size_t); |
図19 サンプルファームウエアのi2c_slave_handler()関数とその関連個所(main.cpp)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
// ADCドライバ ADC_Driver adcd; typedef union { uint16_t d; uint8_t b[2]; } I2C_WORD_t; static void i2c_slave_handler(i2c_inst_t *i2c, i2c_slave_event_t event) { static uint8_t ADRS2040U_cmd = ADRS2040_CMD_INVALID; switch(event) { // I2Cデータ受信 case I2C_SLAVE_RECEIVE: if(ADRS2040U_cmd == ADRS2040_CMD_INVALID) { ADRS2040U_cmd = i2c_read_byte_raw(i2c); } if(ADRS2040U_cmd == ADRS2040_CMD_ADC_START) { DEBUG_PRINT("ADC START\n"); adcd.run(true); ADRS2040U_cmd = ADRS2040_CMD_INVALID; } else if(ADRS2040U_cmd == ADRS2040_CMD_ADC_STOP) { DEBUG_PRINT("ADC STOP\n"); adcd.run(false); ADRS2040U_cmd = ADRS2040_CMD_INVALID; } else if(ADRS2040U_cmd == ADRS2040_CMD_SET_RATE) { I2C_WORD_t rate; i2c_read_raw_blocking(i2c, rate.b, sizeof(I2C_WORD_t)); DEBUG_PRINT("Rate = %d\n", rate.d * 10); adcd.set_sample_rate(rate.d * 10); ADRS2040U_cmd = ADRS2040_CMD_INVALID; } break; // I2Cデータ要求 case I2C_SLAVE_REQUEST: I2C_WORD_t sdata; sdata.d = 0; if(ADRS2040U_cmd == ADRS2040_CMD_GET_COUNT) { sdata.d = adcd.count(); } else if(ADRS2040U_cmd == ADRS2040_CMD_GET_VALUE) { int value = adcd.get_value(); if( value >= 0) { sdata.d = value & 0xFFF; } else { sdata.d = 0xFFFF; } } i2c_write_raw_blocking(i2c, sdata.b, sizeof(I2C_WORD_t)); ADRS2040U_cmd = ADRS2040_CMD_INVALID; break; // STOP or RESTARTコンデション case I2C_SLAVE_FINISH: break; default: break; } } (略) int main(void) { stdio_init_all(); i2c_setup(); while (true) { ; } } |
図20 受信FIFOバッファからデータを取り出すための前処理
1 2 3 4 5 6 7 |
i2c_hw_t *i2chw = i2c_get_hw(i2c0); uint32_t datacmd = i2chw->data_cmd; // FIFOからデータを取り出す uintu_t value = datacmd & I2C_IC_DATA_CMD_DAT_BITS; // 下位8ビットがデータ本体 if(datacmd & I2C_IC_DATA_CMD_FIRST_DATA_BYTE_BITS) { // I2Cアドレスに続く最初の書き込みバイト // つまりレジスタ番号 } |
図22 ADCへアクセスするサンプルプログラム(adcsample.py)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
from smbus2 import SMBus ADRS2040_ADDR=0x41 ADRS2040_CMD_INVALID = 0 ADRS2040_CMD_ADC_START = 1 ADRS2040_CMD_ADC_STOP = 2 ADRS2040_CMD_SET_RATE = 3 ADRS2040_CMD_GET_COUNT = 4 ADRS2040_CMD_GET_VALUE = 5 with SMBus(1) as i2c: # 500 spsで初期化・スタート self.i2c.write_word_data(ADRS2040_ADDR, ADRS2040_CMD_SET_RATE, 50) self.i2c.write_byte(ADRS2040_ADDR, ADRS2040_CMD_ADC_START) while True: nod = 0 nod = self.i2c.read_word_data(ADRS2040_ADDR,ADRS2040_CMD_GET_COUNT) for i in range(nod): value = self.i2c.read_word_data(ADRS2040_ADDR,ADRS2040_CMD_GET_VALUE) print(value) |
図23 ADCから取得したデータをグラフ化するサンプルプログラム(adctest.py)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
from smbus2 import SMBus import pyqtgraph as pg from pyqtgraph.Qt import QtCore, QtGui import numpy as np import sys import time # 定数 ADRS2040_ADDR=0x41 ADRS2040_CMD_INVALID = 0 ADRS2040_CMD_ADC_START = 1 ADRS2040_CMD_ADC_STOP = 2 ADRS2040_CMD_SET_RATE = 3 ADRS2040_CMD_GET_COUNT = 4 ADRS2040_CMD_GET_VALUE = 5 class ADCGraph: def __init__(self): # グラフウィンドウ self.win = pg.GraphicsWindow() self.win.setWindowTitle('ADC Input') self.plt = self.win.addPlot() self.plt.setYRange(0,1024) self.curve = self.plt.plot(pen=(0, 0, 255)) # グラフデータを用意 self.data = np.zeros(100) self.i2c = SMBus(1) # 500 spsで初期化・スタート self.i2c.write_word_data(ADRS2040_ADDR, ADRS2040_CMD_SET_RATE, 50) self.i2c.write_byte(ADRS2040_ADDR, ADRS2040_CMD_ADC_START) self.timer = QtCore.QTimer() self.timer.timeout.connect(self.update) # 10msごとに更新 self.timer.start(10) # グラフ更新 def update(self): nod = 0 nod = self.i2c.read_word_data(ADRS2040_ADDR,ADRS2040_CMD_GET_COUNT ) for i in range(nod): value = self.i2c.read_word_data(ADRS2040_ADDR,ADRS2040_CMD_GET_VALUE) self.data = np.delete(self.data, 0) self.data = np.append(self.data, value) self.curve.setData(self.data) if __name__ == "__main__": graphWin = ADCGraph() if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): QtGui.QApplication.instance().exec_() |
Pythonあれこれ(Vol.84掲載)
著者:飯尾 淳
本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温
かく見守ってください。皆さんと共に勉強していきましょう。第14回では、話題のAIチャットボット「ChatGPT」を使ったPythonプログ「ラムの作成にチャレンジします。ChatGPTは、どの程度のプログラムを生成してくれるのでしょうか。
シェルスクリプトマガジン Vol.84は以下のリンク先でご購入できます。
図2 1回目にChatGPTが生成したPythonプログラム
1 2 3 4 5 6 7 8 9 |
import sys if len(sys.argv) < 3: print("Usage: python program_name.py num1 num2") else: num1 = float(sys.argv[1]) num2 = float(sys.argv[2]) result = num1 + num2 print("Result:", result) |
図3 2回目にChatGPTが生成したPythonプログラム
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
import sys from fractions import Fraction if len(sys.argv) < 3: print("Usage: python program_name.py num1 num2") else: try: num1 = float(sys.argv[1]) num2 = float(sys.argv[2]) if num1.is_integer() and num2.is_integer(): num1, num2 = int(num1), int(num2) elif '/' in sys.argv[1] and '/' in sys.argv[2]: num1 = Fraction(sys.argv[1]) num2 = Fraction(sys.argv[2]) result = num1 + num2 print("Result:", result) except ValueError: print("Please enter numbers only.") |
図4 3回目にChatGPTが生成したPythonプログラム
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
import sys from fractions import Fraction if len(sys.argv) < 3: print("Usage: python program_name.py num1 num2") else: try: num1 = 0 if '/' in sys.argv[1]: num1 = Fraction(sys.argv[1]) else: num1 = float(sys.argv[1]) num2 = 0 if '/' in sys.argv[2]: num2 = Fraction(sys.argv[2]) else: num2 = float(sys.argv[2]) result = num1 + num2 print("Result:", result) except ValueError: print("Please enter numbers only.") |
図5 4回目にChatGPTが生成したPythonプログラム
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
import sys from fractions import Fraction def check_arg_validity(arg): try: if '/' in arg: num = Fraction(arg) else: num = float(arg) return num except ValueError: return None if len(sys.argv) < 3: print("Usage: python program_name.py num1 num2") else: try: num1 = check_arg_validity(sys.argv[1]) num2 = check_arg_validity(sys.argv[2]) if num1 is None or num2 is None: print("Please enter numbers only.") else: result = num1 + num2 print("Result:", result) |
図6 5回目にChatGPTが生成したPythonプログラム
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
import sys from fractions import Fraction def check_number(num_str): try: float(num_str) except ValueError: try: Fraction(num_str) except ValueError: print(f"{num_str} is not a valid number.") return None return num_str def add2(num1, num2): result = Fraction(num1) + Fraction(num2) print(f"{num1} + {num2} = {result}") if __name__ == '__main__': num1 = check_number(sys.argv[1]) num2 = check_number(sys.argv[2]) if num1 and num2: add2(num1, num2) |
中小企業手作りIT化奮戦記(Vol.84掲載)
筆者:菅 雄一
自社サーバーからレンタルサーバーへの移行が世の流れだ。本連載の第58回「PHP+MySQL環境にシステムを移行」に書いたが、筆者の勤務先でも保守作業の軽減や故障回避のためにレンタルサーバーに移行する方針になった。2023年1月に、四苦八苦しながら顧客向けWeb検索システムを移行し、ようやくレンタルサーバーへの移行が一段落した。今回は、この難航した移行作業について書くことにする。
シェルスクリプトマガジン Vol.84は以下のリンク先でご購入できます。
図2 バックアップファイル内の文字コード設定を変更
1 2 3 4 5 6 7 8 9 10 |
-- -- PostgreSQL database dump -- SET client_encoding = 'EUC'; SET standard_conforming_strings = off; SET check_function_bodies = false; SET client_min_messages = warning; SET escape_string_warning = off; (略) |
香川大学SLPからお届け!(Vol.84掲載)
著者:三井 颯剛
SLPでは、Webページの公開などに使用しているサーバーを立て直す計画が進行中です。再建後のサーバーでは、「Traefik(トラフィック) 」というコンテナ環境向けのリバースプロキシソフトウエアを採用する予定です。今回は、Dockerでサーバーコンテナを稼働させて、それに対するアクセスをTraefikで制御する場合を例に、Traefikの利用方法について紹介します。
シェルスクリプトマガジン Vol.84は以下のリンク先でご購入できます。
図3 「docker-compose.yml」ファイルに記述する設定
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
services: reverse-proxy: image: traefik:latest restart: always ports: - "80:80" environment: - TZ=Asia/Tokyo volumes: - /var/run/docker.sock:/var/run/docker.sock command: - "--providers.docker.exposedbydefault=false" - "--entrypoints.web.address=:80" whoami: image: traefik/whoami labels: - "traefik.enable=true" - "traefik.http.routers.whoami.rule=PathPrefix(`/whoami`)" - "traefik.http.routers.whoami.entrypoints=web" |
図6 ダッシュボードを有効にする場合の「docker-compose.yml」ファイルの記述
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
services: reverse-proxy: image: traefik:latest restart: always ports: - "80:80" - "8080:8080" environment: - TZ=Asia/Tokyo volumes: - /var/run/docker.sock:/var/run/docker.sock command: - "--providers.docker.exposedbydefault=false" - "--entrypoints.web.address=:80" - "--entrypoints.dashboard.address=:8080" - "--api.dashboard=true" labels: - "traefik.enable=true" - "traefik.http.routers.api.entrypoints=dashboard" - "traefik.http.routers.api.rule=Host(`localhost`)" - "traefik.http.routers.api.service=api@internal" whoami: image: traefik/whoami labels: - "traefik.enable=true" - "traefik.http.routers.whoami.rule=PathPrefix(`/whoami`)" - "traefik.http.routers.whoami.entrypoints=web" |
図9 「traefik.yml」ファイルに記述する内容
1 2 3 4 5 6 7 8 9 10 |
providers: docker: exposedByDefault: false entryPoints: web: address: ":80" dashboard: address: ":8080" api: dashboard: true |
図10 設定を分離した場合の「docker-compose.yml」ファイルの記述例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
services: reverse-proxy: image: traefik:latest restart: always ports: - "80:80" - "8080:8080" environment: - TZ=Asia/Tokyo volumes: - /var/run/docker.sock:/var/run/docker.sock - ./traefik.yml:/etc/traefik/traefik.yml labels: - "traefik.enable=true" - "traefik.http.routers.api.entrypoints=dashboard" - "traefik.http.routers.api.rule=Host(`localhost`)" - "traefik.http.routers.api.service=api@internal" whoami: image: traefik/whoami labels: - "traefik.enable=true" - "traefik.http.routers.whoami.rule=PathPrefix(`/whoami`)" - "traefik.http.routers.whoami.entrypoints=web" - "traefik.http.services.myservice.loadbalancer.server.port=80" |
AWKでデジタル信号処理(Vol.84掲載)
著者:斉藤 博文
プログラミング言語「AWK」は、データストリーム(データの流れ)を逐次処理するのに適しています。本連載では、電子回路の分野でその特徴を生かし、シェルスクリプトを組み合わせてデジタル信号を処理します。最終回は前回の続きとして、心電図データの解析について解説します。
シェルスクリプトマガジン Vol.84は以下のリンク先でご購入できます。
図5 ecg.awk内のlpf()関数
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# low pass filter function lpf(arr_x, arr_y, idx_x, idx_y, ord, len_x, len_y, _ret, _gain) { _ret = 0.0; _gain = 1.0 / (ord * ord); _ret += 2.0 * get_buffer(arr_y, idx_y - 1, len_y); _ret -= 1.0 * get_buffer(arr_y, idx_y - 2, len_y); _ret += 1.0 * _gain * arr_x[idx_x]; _ret -= 2.0 * _gain * get_buffer(arr_x, idx_x - ord, len_x); _ret += 1.0 * _gain * get_buffer(arr_x, idx_x - 2 * ord, len_x); return _ret; } |
図6 ecg.awk内の最終結果を出力するprintf()文で群遅延を補正
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
# print results printf("%f,%f,%f,%f,%f,%f,%f,%f\n", get_buffer( \ arr_data_raw, idx_data - (val_gd_lpf + val_gd_hpf + val_gd_dif + val_gd_sma) - 2 * val_fs, len_data), get_buffer( \ arr_data_lpf, idx_data - (val_gd_hpf + val_gd_dif + val_gd_sma) - 2 * val_fs, len_data), get_buffer( \ arr_data_hpf, idx_data - (val_gd_dif + val_gd_sma) - 2 * val_fs, len_data), get_buffer( \ arr_data_dif, idx_data - val_gd_sma - 2 * val_fs, len_data), get_buffer( \ arr_data_squ, idx_data - val_gd_sma - 2 * val_fs, len_data), get_buffer( \ arr_data_sma, idx_data - 2 * val_fs, len_data), cnt_rri_curr * 0.005 * 1000, get_buffer( \ arr_data_flg, idx_data - (val_gd_dif + val_gd_sma) - 2 * val_fs, len_data)); } |
図12 ecg.awk内のしきい値(val_data_sma_threshold)を設定している箇所
1 2 3 4 5 6 7 8 9 |
# update threshold every interval if (num_data % tap_interval == 0) { val_data_sma_threshold = val_data_sma_max * val_peak_rate; val_data_sma_max = 0; } else { if (val_data_sma_max < arr_data_sma[idx_data]) { val_data_sma_max = arr_data_sma[idx_data]; } } |
図13 ecg.awk内のval_data_flgを「1」に設定している箇所
1 2 3 4 5 |
if (val_data_sma_curr > val_data_sma_threshold && val_data_sma_threshold >= val_data_sma_last) { val_data_hpf_max = 0; idx_data_hpf_max = 0; val_data_flg = 1; } |
図14 ecg.awk内のidx_data_hpf_maxを求めている箇所
1 2 3 4 5 6 7 8 9 10 11 12 |
# search maximum index if (val_data_flg == 1) { val_data_hpf_curr = get_buffer(arr_data_hpf, idx_data - (val_gd_dif + val_gd_sma), len_data); idx_data_hpf_curr = idx_data - (val_gd_dif + val_gd_sma); idx_data_hpf_curr = idx_data_hpf_curr >= 0 ? idx_data_hpf_curr : idx_data_hpf_curr + len_data; if (val_data_hpf_max < val_data_hpf_curr) { val_data_hpf_max = val_data_hpf_curr; idx_data_hpf_max = idx_data_hpf_curr; } } |
図15 ecg.awk内のarr_data_flg[idx_data_hpf_max]を「1」に設定している箇所
1 2 3 4 |
# end point to detect main peak if (val_data_sma_curr < val_data_sma_threshold && val_data_sma_threshold <= val_data_sma_last) { arr_data_flg[idx_data_hpf_max] = 1; |
図18 ecg.awk内のRRIを求めている箇所
1 2 3 4 5 6 7 8 9 |
# calculate peak to peak interval if (idx_data - idx_data_hpf_max >= 0) { cnt_peak_curr = num_data - (idx_data - idx_data_hpf_max); } else { cnt_peak_curr = num_data - (idx_data - idx_data_hpf_max + len_data); } cnt_rri_curr = cnt_peak_curr - cnt_peak_last; cnt_peak_last = cnt_peak_curr; |
Linux定番エディタ入門(Vol.84掲載)
著者:大津 真
文章の作成やプログラミングに欠かせないのがテキストエディタ(以下、エディタ)です。この連載では、Linuxで利用できる定番エディタの特徴と使い方を解説していきます。第1回に取り上げるのは、CU(I Character User Interface)環境で利用できるシンプルで扱いやすい「GNU nano」です。
シェルスクリプトマガジン Vol.84は以下のリンク先でご購入できます。
図12 nanoの設定ファイル「~/.nanorc」の記述例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# バックアップファイルを作成 set backup # 各行の左に行番号を表示 set linenumbers # 文字列検索時に大文字小文字を区別 set casesensitive # 長い行を画面の右端で折り返す set softwrap # オートインデント set autoindent # タブサイズを「4」に(デフォルトは「8」) set tabsize 4 # マウスのサポートを有効に set mouse |
香川大学SLPからお届け!(Vol.83掲載)
著者:安田 大朗
最近、さまざまな場面でチャットbotなどの対話システムを見かけます。その中には、感情表現の機能を持ち、より人間らしい振る舞いをするものもあります。私はそうした対話システムに興味があり、ユーザーが入力したテキストの感情を分析し、それに応じた反応をする対話システムを作成しました。今回は、私が開発したその対話システムについて紹介します。
シェルスクリプトマガジン Vol.83は以下のリンク先でご購入できます。
図4 「telegram-bot.py」ファイルに記述するコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
from telegram.ext import (Updater, CommandHandler, MessageHandler, Filters) TOKEN = "トークン" class TelegramBot: def __init__(self, system): self.system = system def start(self, update, bot): input = {'utt':None, 'sessionId':str(update.message.from_user.id)} update.message.reply_text(self.system.initial_message(input)["utt"]) def message(self, update, bot): input = {'utt':update.message.text, 'sessionId':str(update.message.from_user.id)} system_output = self.system.reply(input) update.message.reply_text(system_output["utt"]) def run(self): updater = Updater(TOKEN, use_context=True) dp = updater.dispatcher dp.add_handler(CommandHandler("start", self.start)) dp.add_handler(MessageHandler(Filters.text, self.message)) updater.start_polling() updater.idle() |
図5 「aim_system1.py」ファイルに記述するコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
import aiml import MeCab from telegram_bot import TelegramBot class AimlSystem: def __init__(self): self.sessiondic = {} self.tagger = MeCab.Tagger('-Owakati') def initial_message(self, input): sessionId = input['sessionId'] kernel = aiml.Kernel() kernel.learn("aiml.xml") self.sessiondic[sessionId] = kernel return {'utt':'はじめまして,雑談を始めましょう', 'end':False} def reply(self, input): sessionId = input['sessionId'] utt = input['utt'] utt = self.tagger.parse(utt) response = self.sessiondic[sessionId].respond(utt) return {'utt':response, 'end':False} if __name__ == '__main__': system = AimlSystem() bot = TelegramBot(system) bot.run() |
図6 「aim.xml」ファイルに記述するルールの例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<?xml version="1.0" encoding="UTF-8"?> <aiml version="1.0.1" encoding="UTF-8"> <category> <pattern>* へ 旅行 に 行き まし た</pattern> <template><star/>に旅行ですか,いいなー.</template> </category> <category> <pattern>私 の 名前 は * です</pattern> <template><set name="username"><star/></set>さん、よろしくね!.</template> </category> <category> <pattern>じゃあね</pattern> <template><get name="username"/>さん、バイバイー.</template> </category> </aiml> |
図8 「aim_system2.py」ファイルに記述するコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
response = self.sessiondic[sessionId].respond(utt) emotion_dic = {'suki':'🥰', 'ikari':'😡', 'kowa':'😱', 'yasu':'😊', 'iya':'😫', 'aware':'😭', 'takaburi':'🤩', 'odoroki':'🙄', 'haji':'🤭', 'yorokobi':'😄'} emotion_analyzer = MLAsk() json_emot = emotion_analyzer.analyze(utt) if json_emot['emotion'] == None: return {'utt':response, 'end':False} else: emotion = json_emot['representative'][0] return {'utt':response + emotion_dic[emotion], 'end':False} if __name__ == '__main__': |
Bash入門(Vol.83記載)
著者:大津 真
LinuxやmacOSなど、UNIX系OSのコマンドラインで使用されるコマンドインタプリタをシェルと呼びます。本連載では高機能シェルとして人気の高い「Bash」の基本操作について説明していきます。最終回となる今回は、シェルで実行可能なプログラミング言語である「シェルスクリプト」について解説します。
シェルスクリプトマガジン Vol.83は以下のリンク先でご購入できます。
図2 図1のコマンド行と同様の処理をするシェルスクリプトの例
1 2 3 4 5 6 |
# サイズの大きなファイルを見つける echo "--「~/ピクチャ」以下にある1Mバイト以上のファイル--" find ~/ピクチャ -type f -size +1M -print0 | \ xargs -0 du | \ sort -nr | \ head -n 5 |
図5 図2のシェルスクリプトを変数を使うように書き換えた例
1 2 3 4 5 6 7 8 |
#!/usr/bin/bash dir=~/ピクチャ size="1M" echo "--「${dir}」以下にある${size}バイト以上のファイル--" find "$dir" -type f -size "+${size}" -print0 | \ xargs -0 du | \ sort -nr | \ head -n 5 |
図6 図5のシェルスクリプトをコマンドライン引数を参照するように書き換えた例
1 2 3 4 5 6 7 8 |
#!/usr/bin/bash dir=$1 size="1M" echo "--「${dir}」以下にある${size}バイト以上のファイル--" find "$dir" -type f -size "+${size}" -print0 | \ xargs -0 du | \ sort -nr | \ head -n 5 |
図7 if文を使用したシェルスクリプトの例
1 2 3 4 5 6 7 |
#!/usr/bin/bash if cd $1 2>/dev/null then echo "${1}に移動しました" else echo "${1}に移動できませんでした" fi |
図8 図6のシェルスクリプトを条件式とif文を使って書き換えた例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
#!/usr/bin/bash if [ $# -eq 0 ] then echo "エラー: 引数でディレクトリを指定してください" exit 1 ←③ fi if [ ! -d "$dir" ] then echo "エラー: ${dir}が見つかりません" exit 1 fi size="1M" echo "--「${dir}」以下にある${size}バイト以上のファイル--" find "$dir" -type f -size "+${size}" -print0 | \ xargs -0 du | \ sort -nr | \ head -n 5 |
図9 for文を使ったシェルスクリプトの例
1 2 3 4 5 6 |
#!/usr/bin/bash four_seasons="春 夏 秋 冬" for season in $four_seasons do echo "$season" done |
図10 図8のシェルスクリプトをfor文を使って書き換えた例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
#!/usr/bin/bash if [ $# -eq 0 ] then echo “エラー: 引数でディレクトリを指定してください” exit 1 fi size="1M" for dir in "$@" do if [ ! -d “$dir” ] then echo "エラー: ${dir}が見つかりません" else echo "--「${dir}」以下にある${size}バイト以上のファイル--" find $dir -type f -size "+${size}" -print0 | \ xargs -0 du | \ sort -nr | \ head -n 5 fi done |
図12 配列を使ったシェルスクリプトの例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#!/usr/bin/bash if [ $# -eq 0 ] || [ ! -d $1 ] then echo "エラー: 引数でディレクトリを指定してください" exit 1 fi target=~/公開/pictures extensions=(jpg jpeg png gif pdf) for ext in "${extensions[@]}" do for file in "$1"/*.${ext} do if [ -f "$file" ] then cp -v "$file" $target fi done done |
Vol.83 補足情報
特別企画 Raspberry Piを100%活用しよう 拡大版
同企画で扱いました、PlatformIO向けのRP2040用ソフトウエア基盤(フレームワーク)が「https://github.com/Wiz-IO/wizio-pico」から入手できなくなってしまいました。原作者に問い合わせましたが、返答はなく理由は不明です。ご迷惑をおかけして申し訳ございません。
「https://github.com/Wiz-IO/wizio-pico」にアクセスして図1のように表示されたときは、以下の方法で入手・導入してください。
(1)特別企画記事の通りに開発ツール「Visual Studio Code」(VSCode)とWindows版Gitクライアント「Git for Windows」をインストールします。
(2)PlatformIO向けのRP2040用ソフトウエア基盤(フレームワーク)を「https://future.quake4.jp/wizio-pico.zip」からダウンロードします。
(3)VSCodeを起動しているならいったん終了します。
(4)「C:¥Users¥ユーザー名¥.platformio¥」(ユーザー名はWindowsのアカウント名)の下にダウンロードした「wizio-pico.zip」ファイルを展開します。
図1 ページが見つからない場合の表示
情報は随時更新致します。
シェルスクリプトマガジンvol.83 Web掲載記事まとめ
004 レポート モーションキャプチャ「mocopi」 サンプル動画掲載
005 レポート ChatGPT API公開 コード掲載
006 製品レビュー ウエアラブルデバイス「Redmi Smart Band2」
007 NEWS FLASH
008 特集1 Ruby on Rails入門/安川要平 コード掲載
020 特集2 AutoML徹底解説 入門編/田村孝、山口武彦、新田陸、細野友基
034 特別企画 Raspberry Piを100%活用しよう 拡大版/米田聡 コード掲載
044 Pythonあれこれ/飯尾淳 コード掲載
049 Hello Nogyo!
050 香川大学SLPからお届け!/安田大朗 コード掲載
056 法林浩之のFIGHTING TALKS/法林浩之
058 中小企業手作りIT化奮戦記/菅雄一
062 SCM/桑原滝弥、イケヤシロウ
064 タイ語から分かる現地生活/つじみき
070 行動経済学と心理学で円滑に業務を遂行/請園正敏
074 AWKでデジタル信号処理/斉藤博文
082 ユニケージ通信/田渕智也、高橋未来哉
086 Bash入門/大津真 コード掲載
096 Techパズル/gori.sh
097 コラム「ユニケージ式プロジェクト管理法」/シェル魔人
レポート2(Vol.83掲載)
著者:末安 泰三
話題のAIチャットボット「ChatGPT」のAP(I Application Programming Interface)を、開発元の米OpenAIが有償公開した。APIの公開により、自作プロダクトにChatGPTの機能を手軽に組み込めるようになった。2023年3月時点の利用料金は、1000トークン当たり0.002ドルだ。
シェルスクリプトマガジン Vol.83は以下のリンク先でご購入できます。
図1 ChatGPT APIを利用するPythonコードの例
1 2 3 4 5 6 7 8 9 10 |
import openai openai.api_key = "ここにAPIキーを記述する" response = openai.ChatCompletion.create( model = "gpt-3.5-turbo", messages = [ {"role":"system", "content":"①ChatGPTの動作をここで指定する"}, {"role":"user", "content":"②ChatGPTに送るメッセージを記述する"} ] ) print(response['choices'][0]['message']['content']) |
特集1 Ruby on Rails入門(Vol.83記載)
著者:安川要平
本特集では、地図アプリの作成を題材に、Webアプリケーションフレームワーク「Ruby on Rails」によるWebアプリの開発手順を紹介します。説明は、米GitHub社が提供するクラウド開発環境「GitHub Codespaces」を使って進めます。そのため、Webブラウザさえあれば紹介する手順を確認できます。Webブラウザだけで体験できるようになった最新のRubyとRailsを一緒に触ってみましょう。
シェルスクリプトマガジン Vol.83は以下のリンク先でご購入できます。
図15 「app/models/spot.rb」ファイルに追加する1行
1 2 3 |
class Spot < ApplicationRecord has_one_attached :photo end |
図16 「app/views/apots/_form.html.erb」ファイルに追加する記述
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
(略) <div> <%= form.label :name, style: "display: block" %> <%= form.text_field :name, readonly: false %> </div> <div> <%= form.label :photo, style: "display: block" %> <%= form.file_field :photo, readonly: false %> </div> <div> <%= form.submit %> </div> <% end %> (略) |
図18 「app/controllers/spots_controller.rb」ファイルに追加する記述
1 2 3 4 5 6 7 |
(略) # Only allow a list of trusted parameters through. def spot_params params.require(:spot).permit(:lat, :lng, :name, :photo) end end |
図19 「app/helpers/spots_helper.rb」ファイルに追加する記述
1 2 3 4 5 6 7 8 |
(略) html << "<strong>Name:</strong> #{spot.name}<br />" if spot.photo.attached? html << "<strong>Photo:</strong> #{image_tag(spot.photo, width: '100%')}<br />" end return html.html_safe end end |
図25 提供されるサンプルデータの内容
1 2 3 4 5 6 7 8 9 10 11 |
[ { id: "1037", lat: "35.7087568", lng: "139.7196777", name: "早大で入試「一般選抜」がスタート" photo: "https://localmap.jp/images/takadanobaba/1037.jpg", url: "https://takadanobaba.keizai.biz/headline/1037/", }, (略) ] |
図26 サンプルデータを取得して画面に表示するコード
1 2 3 4 5 |
require 'net/http' # 'net-http'ではないことに注意 uri = URI('https://localmap.jp/scaffold.json') response = Net::HTTP.get(uri) # ファイルを取得 map_data = JSON.parse(response) # 結果を変数に格納 p map_data # 画面に表示 |
図28 サンプルデータを自動入力するコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
require 'net/http' uri = URI('https://localmap.jp/scaffold.json') response = Net::HTTP.get(uri) map_data = JSON.parse(response) # 各スポットのデータ(map)に対して以下のコードを実行 map_data.each do |map| # 新規スポットにサンプルデータを入力 spot = Spot.new( lat: map['lat'], lng: map['lng'], name: map['name'], ) # 新規スポットに画像を添付 spot.photo.attach( io: URI.open(map['photo']), filename: map['id'] + '.jpg', ) # 新規スポットをDBに保存 spot.save end |
特別企画 Raspberry Piを100%活用しよう 拡大版(Vol.83記載)
著者:米田 聡
小型コンピュータボード「Raspberry Pi」(ラズパイ)向けにさまざまな拡張ボードが発売されています。その拡張ボードとラズパイを組み合わせれば、ラズパイでいろいろなことが簡単に試せます。本企画では、ラズパイと連携して使用するマイコンボードを扱います。
シェルスクリプトマガジン Vol.83は以下のリンク先でご購入できます。
図A uf2conv.pyの修正箇所
1 2 3 4 5 6 7 8 9 10 |
(略) return resfile def to_str(b): # return b.decode("utf-8") return b.decode("cp932") def get_drives(): drives = [] (略) |
Pythonあれこれ(Vol.83掲載)
著者:飯尾 淳
本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温
かく見守ってください。皆さんと共に勉強していきましょう。第13回では、PyTorchという機械学習ライブラリを使って、手書きの数字を画像認識によって分類してみます。実際に作業することで、機械学習の効果を感じてください。
シェルスクリプトマガジン Vol.83は以下のリンク先でご購入できます。
図1 MNISTデータセットをダウンロードするためのコード
1 2 3 4 5 6 7 8 9 |
data_folder = '~/data' BATCH_SIZE = 8 mnist_data = MNIST(data_folder, train=True, download=True, transform=transforms.ToTensor()) data_loader = DataLoader(mnist_data, batch_size=BATCH_SIZE, shuffle=False) |
図4 MNISTデータセットのデータを1セット表示するコード
1 2 3 4 5 6 7 8 9 10 |
data_iterator = iter(data_loader) images, labels = next(data_iterator) # 最初の画像を表示 location = 0 # 画像データを28×28画素のデータに変換して表示 data = images[location].numpy() reshaped_data = data.reshape(28, 28) plt.imshow(reshaped_data, cmap='inferno', interpolation='bicubic') plt.show() print('ラベル:', labels[location]) |
図6 学習データと検証データを用意するコード
1 2 3 4 5 6 7 8 9 10 |
# 学習データ train_data_loader = DataLoader( MNIST(data_folder, train=True, download=True, transform=transforms.ToTensor()), batch_size=BATCH_SIZE, shuffle=True) # 検証データ test_data_loader = DataLoader( MNIST(data_folder, train=False, download=True, transform=transforms.ToTensor()), batch_size=BATCH_SIZE, shuffle=True) |
図9 ニューラルネットワークモデルを定義するコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
from torch.autograd import Variable import torch.nn as nn # 親クラスのnn.Moduleを継承してモデルを作成 class MLP(nn.Module): def __init__(self): super().__init__() self.layer1 = nn.Linear(28 * 28, 100) self.layer2 = nn.Linear(100, 50) self.layer3 = nn.Linear(50, 10) def forward(self, input_data): input_data = input_data.view(-1, 28 * 28) input_data = self.layer1(input_data) input_data = self.layer2(input_data) input_data = self.layer3(input_data) return input_data |
図11 学習前の準備をするコード
1 2 3 4 5 6 7 |
import torch.optim as optimizer # モデルの作成 model = MLP() # 評価器(誤差項)と最適化器の作成 lossResult = nn.CrossEntropyLoss() optimizer = optimizer.SGD(model.parameters(), lr=0.01) |
図12 画像認識の精度を検証するコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import torch # 検証した数と正解の数 total = 0 count_when_correct = 0 for data in test_data_loader: test_data, teacher_labels = data results = model(Variable(test_data)) _, predicted = torch.max(results.data, 1) total += teacher_labels.size(0) count_when_correct += (predicted == teacher_labels).sum() rate = int(count_when_correct) / int(total) print(f'count_when_correct:{count_when_correct}') print(f'total:{total}') print(f'正解率:{count_when_correct} / {total} = {rate}') |
図13 モデルを学習させるコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
# 最大学習回数 MAX_EPOCH = 4 for epoch in range(MAX_EPOCH): total_loss = 0.0 for i, data in enumerate(train_data_loader): train_data, teacher_labels = data train_data, teacher_labels = \ Variable(train_data), Variable(teacher_labels) # 勾配情報をリセット optimizer.zero_grad() outputs = model(train_data) loss = lossResult(outputs, teacher_labels) loss.backward() # 勾配を更新 optimizer.step() # 誤差を積み上げる total_loss += loss.data if i % 2000 == 1999: print(f'学習進捗:[{epoch+1}, {i+1}]', end='') print(f'学習誤差(loss): {total_loss / 2000:.3f}') total_loss = 0.0 print('学習終了') |
図15 個別の判定結果を確認するコード
1 2 3 4 5 6 7 8 9 10 |
# データの取得と検証 test_iterator = iter(test_data_loader) test_data, teacher_labels = next(test_iterator) results = model(Variable(test_data)) _, predicted_label = torch.max(results.data, 1) # 最初のデータを検証して画像を表示 location = 0 plt.imshow(test_data[location].numpy().reshape(28, 28), cmap='inferno', interpolation='bicubic') print('ラベル:', predicted_label[location]) |
シェルスクリプトマガジンvol.82 Web掲載記事まとめ
004 レポート ポッキーでプログラミング
005 レポート WebAssembly対応Ruby 3.2.0
006 製品レビュー 制御機器「NFCタグ」
007 NEWS FLASH
008 特集1 LibreOfficeでPythonマクロ/荒川雄介 コード掲載
016 特集2 Dropboxを活用しよう/岡崎隆之、進藤麻礼子、井川恵理
028 緊急企画 Mastodonサーバーを構築しよう/麻生二郎
046 Raspberry Piを100%活用しよう/米田聡
050 行動経済学と心理学で円滑に業務を遂行/請園正敏
052 中小企業手作りIT化奮戦記/菅雄一
057 Hello Nogyo!
058 Pythonあれこれ/飯尾淳
066 法林浩之のFIGHTING TALKS/法林浩之
068 タイ語から分かる現地生活/つじみき
072 Emotet/桑原滝弥、イケヤシロウ
074 香川大学SLPからお届け!/谷﨑勇太 コード掲載
080 AWKでデジタル信号処理/斉藤博文 コード掲載
086 ユニケージ通信/田渕智也、高橋未来哉
090 Bash入門/大津真
098 Techパズル/gori.sh
099 コラム「ユニケージ流思考法」/シェル魔人
特集1 LibreOfficeでPythonマクロ(Vol.82記載)
著者:荒川 雄介
オープンソースのオフィスソフト「LibreOffice」は、Pythonで記述されたマクロ(簡易スクリプト)を実行できます。しかしデフォルト状態では、Pythonマクロを作成しやすい環境が整備されていません。本特集では、開発環境を整備する手順を中心に、Pythonマクロ作成についての基礎知識を紹介します。使い慣れた言語を使って、LibreOfficeのさまざまな処理を効率化しましょう。
シェルスクリプトマガジン Vol.82は以下のリンク先でご購入できます。
図3 「Hello!」と書かれたダイアログを表示するPythonマクロのコード
1 2 3 4 5 |
from scriptforge import CreateScriptService def hello(): bas = CreateScriptService("Basic") bas.MsgBox("Hello!") g_exportedScripts = (hello,) |
図9 CSVファイルからCalcのシートにデータを読み込むPythonマクロのコード
1 2 3 4 5 |
from scriptforge import CreateScriptService def load_csv(): cal = CreateScriptService("Calc") cal.ImportFromCSVFile(r"C:\test.csv", "A1") g_exportedScripts = (load_csv, ) |
図11 図9のコードを新規のCalcシートを開くように変更したもの
1 2 3 4 5 6 |
from scriptforge import CreateScriptService def load_csv(): ui = CreateScriptService("UI") cal = ui.CreateDocument("Calc") cal.ImportFromCSVFile(r"C:\test.csv", "A1") g_exportedScripts = (load_csv, ) |
図12 文書ファイルがある場所からCSVファイルを読み込めるように図9のコードを変更したもの
1 2 3 4 5 6 7 8 9 10 |
from scriptforge import CreateScriptService import uno import os.path def load_csv(): doc = XSCRIPTCONTEXT.getDocument() path = uno.fileUrlToSystemPath(doc.URL) cdir = os.path.dirname(path) cal = CreateScriptService("Calc") cal.ImportFromCSVFile(cdir + r"\test.csv", "A1") g_exportedScripts = (load_csv, ) |
図13 データベースからデータを読み込んで集計/グラフ表示をするPythonマクロのコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
from scriptforge import CreateScriptService def importdata(): # データベースからデータを取得 db=CreateScriptService('database',registrationname='Bibliography') sql='SELECT [Custom1] AS [Language],[Identifier] FROM [biblio] ORDER BY [Language] ASC' data=db.GetRows(sql,header=True) db.CloseDatabase() # 取得したデータをシートにコピー calc=CreateScriptService("Calc") datarange=calc.setArray('Sheet1.A1',data) # ピボットテーブルを作成して集計 pivot=calc.CreatePivotTable('Pivot1',datarange,targetcell='D1', datafields='Identifier;Count',rowfields='Language', rowtotals=False,columntotals=False,filterbutton=False) # グラフを作成 chart=calc.CreateChart('NumberByLanguage','Sheet1',pivot,rowheader=True, columnheader=True) chart.ChartType, chart.Dim3D,chart.Legend='Pie',True,True chart.Resize(4000, 3000, 7000, 2500) g_exportedScripts = (importdata, ) |
図18 図9のコードをイベント駆動できるように修正
1 2 3 4 5 |
from scriptforge import CreateScriptService def load_csv(args=None): cal = CreateScriptService("Calc") cal.ImportFromCSVFile(r"C:\test.csv", "A1") g_exportedScripts = (load_csv, ) |
図19 Calcのシート(Sheet1)の内容をすべて消去するPythonマクロのコード
1 2 3 4 5 |
from scriptforge import CreateScriptService def clear(args=None): calc=CreateScriptService("Calc") calc.ClearAll('Sheet1.*') g_exportedScripts = (clear, ) |
図20 外部ライブラリ(NumPy)を利用するythonマクロのサンプルコード
1 2 3 4 5 6 7 8 |
from scriptforge import CreateScriptService import numpy as np def test_numpy(): A = np.array([1, 2, 3, 4, 5, 6]).reshape(2,3) B = A + 10 cal = CreateScriptService("Calc") cal.setArray("A1", B.tolist()) g_exportedScripts = (test_numpy, ) |
香川大学SLPからお届け!(Vol.82掲載)
著者:谷﨑 勇太
最近、さまざまな場面で「VTuber」が活躍しています。VTuberとは、2D/3Dアバターの表情や身体をリアルタイムに動かしながら動画配信をする人、あるいはそのアバターのことです。多くの場合は、カメラで人の動きを検知し、それをトレースするようにアバターを動かしています。今回は、私がPythonを使って作成した簡易VTuberシステムについて紹介します。
シェルスクリプトマガジン Vol.82は以下のリンク先でご購入できます。
図1 Webカメラの映像を読み取るためのベースコード
1 2 3 4 5 6 7 8 9 10 |
import cv2 camera = cv2.VideoCapture(0) while True: ret,image = camera.read() image = cv2.cvtColor(image,BGR2RGB) cv2.imshow("frame", image) if cv2.waitKey(1) & 0xFF == ord('q'): break camera.release() cv2.destroyAllWindows() |
図2 感情を検出するためのコード
1 2 3 4 5 |
from paz.pipelines import DetectMiniXceptionFER pipeline = DetectMiniXceptionFER([0.1, 0.1]) output = pipeline(image) if len(output["boxes2D"]) == 1: emotion = output["boxes2D"][0].class_name |
図3 左右の目の縦横比の平均値を算出する関数の定義コード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
import cv2 from imutils import face_utils import dlib face_detector = dlib.get_frontal_face_detector() face_parts_detector = \ dlib.shape_predictor("shape_predictor_68_face_landmarks.dat") def face_landmark_find(img): eye = 10 img_gry = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) faces = face_detector(img_gry, 1) for face in faces: landmark = face_parts_detector(img_gry, face) landmark = face_utils.shape_to_np(landmark) left_eye_ear = calc_ear(landmark[42:48]) right_eye_ear = calc_ear(landmark[36:42]) eye = (left_eye_ear + right_eye_ear) / 2.0 return eye |
図5 目の縦横比を計算する関数の定義コード
1 2 3 4 5 6 7 |
from scipy.spatial import distance def calc_ear(eye): A = distance.euclidean(eye[1], eye[5]) B = distance.euclidean(eye[2], eye[4]) C = distance.euclidean(eye[0], eye[3]) eye_ear = (A + B) / (2.0 * C) return eye_ear |
図6 目の開閉を判定して処理を分岐させるコード
1 2 3 4 5 |
if w != 0: if eye < EYE_AR_THRESH: image = overlayImage(image,close,(x,y),(w,h)) elif eye >= EYE_AR_THRESH: image = overlayImage(image,overlay,(x,y),(w,h)) |
図7 顔の座標と幅の情報を取得するコード
1 2 3 4 |
if len(output["boxes2D"]) ==1: x_min, y_min, x_max, y_max = output["boxes2D"][0].coordinates w,h = (x_max-x_min,y_max-y_min) x,y =x_min,y_min |
図8 overlayImage ()関数の定義コード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import cv2 from PIL import Image import numpy as np def overlayImage(image, overlay, location, size): image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) pil_image = Image.fromarray(image) pil_image = pil_image.convert('RGBA') overlay = cv2.cvtColor(overlay, cv2.COLOR_BGRA2RGBA) pil_overlay = Image.fromarray(overlay) pil_overlay = pil_overlay.convert('RGBA') pil_overlay = pil_overlay.resize(size) pil_tmp = Image.new('RGBA', pil_image.size, (255, 255, 255, 0)) pil_tmp.paste(pil_overlay, location, pil_overlay) result_image = Image.alpha_composite(pil_image, pil_tmp) return cv2.cvtColor(np.asarray(result_image), cv2.COLOR_RGBA2BGRA) |
図9 insert()関数の定義コード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
import os def insert(): r = os.path.exists('user.txt') if r == False: print("初期設定を行います") print("目を閉じてください") setting() eye_int() elif r == True: count = len(open('user.txt').readlines()) if count == 0: print("初期設定を行います") print("目を閉じてください") setting() eye_int() elif count == 1: eye_int() |
図10 setting ()関数の定義コード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
import cv2 import math def setting(): f = open('user.txt', 'w') count = 0 eye_sum=0 while True: ret,rgb = cap.read() eye = face_landmark_find(rgb) if ret == True: if eye != 10: count +=1 eye_sum += eye if(count > 50): x=eye_sum/50+0.01 break cap.release() cv2.destroyAllWindows() x = math.floor(x*100)/100 f.write(str(x)+'\n') f.close() |
図11 eye_int()関数の定義コード
1 2 3 4 5 6 |
def eye_int(): f = open('user.txt','r+') lins = f.readlines() print('推奨する設定値は'+lins[0]+'です') tmp = input('設定値を入力してください:') f.write(tmp) |
AWKでデジタル信号処理(Vol.82掲載)
著者:斉藤博文
プログラミング言語「AWK」は、データストリーム(データの流れ)を逐次処理するのに適しています。本連載では、電子回路の分野でその特徴を生かし、シェルスクリプトを組み合わせてデジタル信号を処理します。第4回は「微分フィルタ」を使って目的の波形を取り出す方法を紹介します。
シェルスクリプトマガジン Vol.82は以下のリンク先でご購入できます。
図10 微分フィルタの差分方程式のAWKプログラム(dif.awk)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
#! /usr/bin/gawk -f BEGIN { # define number of order num_ord = num_ord ? num_ord : 31; # define length of ring buffers len_data_raw = num_ord + 1; len_data_dif = 1; # initialize ring buffers for (i = 0; i < len_data_raw; i++) { arr_data_raw[i] = 0.0; } for (i = 0; i < len_data_dif; i++) { arr_data_dif[i] = 0.0; } # initialize index of ring buffers idx_data_raw = 0; idx_data_dif = 0; # initialize number of data num_data_raw = 0; } { # add number of data num_data_raw++; # update index of ring buffers (write pointers) idx_data_raw = num_data_raw % len_data_raw; idx_data_dif = num_data_raw % len_data_dif; # clear number of data if (idx_data_raw == 0 && idx_data_dif == 0) { num_data_raw = 0; } # store input raw data val_data_raw = $0; arr_data_raw[idx_data_raw] = val_data_raw; # apply differential filter arr_data_dif[idx_data_dif] = dif(arr_data_raw, idx_data_raw, num_ord, len_data_raw); # print results print arr_data_dif[idx_data_dif]; } # get value of ring buffer function get_buffer(arr, idx, len) { if (idx < 0) { return arr[idx + len]; } return arr[idx]; } # differential filter function dif(arr_x, idx_x, ord, len_x, _ret, _half, i) { _ret = 0.0; _half = int((ord - 1) / 2); for (i = 0; i < ord; i++) { _ret += (_half - i) * get_buffer(arr_x, idx_x - i, len_x); } return _ret; } |
図12 群遅延を考慮したAWKプログラム(dif_gd.awk)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
#! /usr/bin/gawk -f BEGIN { # define number of order num_ord = num_ord ? num_ord : 31; # define group delay val_gd = int((num_ord - 1) / 2); # define length of ring buffers len_data_raw = num_ord + 1; len_data_dif = 1; # initialize ring buffers for (i = 0; i < len_data_raw; i++) { arr_data_raw[i] = 0.0; } for (i = 0; i < len_data_dif; i++) { arr_data_dif[i] = 0.0; } # initialize index of ring buffers idx_data_raw = 0; idx_data_dif = 0; # initialize number of data num_data_raw = 0; } { # add number of data num_data_raw++; # update index of ring buffers (write pointers) idx_data_raw = num_data_raw % len_data_raw; idx_data_dif = num_data_raw % len_data_dif; # clear number of data if (idx_data_raw == 0 && idx_data_dif == 0) { num_data_raw = 0; } # store input raw data val_data_raw = $0; arr_data_raw[idx_data_raw] = val_data_raw; # apply differential filter arr_data_dif[idx_data_dif] = dif(arr_data_raw, idx_data_raw, num_ord, len_data_raw); # print results print get_buffer(arr_data_raw, idx_data_raw - val_gd, len_data_raw), arr_data_dif[idx_data_dif]; } # get value of ring buffer function get_buffer(arr, idx, len) { if (idx < 0) { return arr[idx + len]; } return arr[idx]; } # differential filter function dif(arr_x, idx_x, ord, len_x, _ret, _half, _gain i) { _ret = 0.0; _half = int((ord - 1) / 2); _gain = 0.0; for (i = 0; i < ord; i++) { _gain += (_half - i)^2; } for (i = 0; i < ord; i++) { _ret += (_half - i) * get_buffer(arr_x, idx_x - i, len_x); } return _ret / _gain; } |
図14 係数の補正を加えた関数dif()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
# differential filter function dif(arr_x, idx_x, ord, len_x, _ret, _half, _gain i) { _ret = 0.0; _half = int((ord - 1) / 2); _gain = 0.0; for (i = 0; i < ord; i++) { _gain += (_half - i)^2; } for (i = 0; i < ord; i++) { _ret += (_half - i) * get_buffer(arr_x, idx_x - i, len_x); } return _ret / _gain; } |
Vol.82 補足情報
Pythonあれこれ
記事中で使用するデータファイルは以下のWebページから入手できます。
https://github.com/shellscript-magazine/python_this_and_that/releases/tag/ver2.0
情報は随時更新致します。
Raspberry Piを100%活用しよう(Vol.81掲載)
著者:米田 聡
小型コンピュータボード「Raspberry Pi」(ラズパイ)向けにさまざまな拡張ボードが発売されています。その拡張ボードとラズパイを組み合わせれば、ラズパイでいろいろなことが簡単に試せます。第14回は、高品質なアナログ音声を出力する拡張基板を扱います。
シェルスクリプトマガジン Vol.81は以下のリンク先でご購入できます。
図7 MP3形式の音楽ファイルを再生するPythonプログラム(player.py)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import time from pydub import AudioSegment from pydub.playback import _play_with_simpleaudio audio = AudioSegment.from_file("sample.mp3", "mp3") play = _play_with_simpleaudio(audio) try: while True: time.sleep(1) except KeyboardInterrupt: pass play.stop() |
Pythonあれこれ(Vol.81掲載)
筆者:飯尾 淳
本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温
かく見守ってください。皆さんと共に勉強していきましょう。第11回では、永続的な非同期通信を簡単に実現できる「WebSocket」というプロトコルを利用するシンプルなチャットアプリの作成に挑戦しま
す。
シェルスクリプトマガジン Vol.81は以下のリンク先でご購入できます。
図1 エコーサーバーを実現するコード
1 2 3 4 5 6 7 8 9 10 11 12 13 |
import asyncio import websockets async def echo(soc): async for msg in soc: print(f'RECV: {msg}') await soc.send(msg) async def main(): async with websockets.serve(echo, port=8765, ping_timeout=None): await asyncio.Future() asyncio.run(main()) |
図2 エコーサーバーに接続するクライアントのコード
1 2 3 4 5 6 7 8 9 10 11 12 13 |
import asyncio import websockets async def client(): uri = "ws://localhost:8765/" async with websockets.connect(uri, ping_timeout=None) as soc: while True: msg = input('> ') await soc.send(msg) msg = await soc.recv() print(f'< {msg}') asyncio.run(client()) |
図4 チャットサーバーのコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
import asyncio import websockets clients = {} async def echo(soc): async for msg in soc: if not (soc in clients): clients[soc] = msg print(f'{msg} is registered') for s in clients: await s.send(f'{clients[soc]} が参加しました') else: print(f'RECV: {msg}') for s in clients: await s.send(f'{clients[soc]}: {msg}') async def main(): async with websockets.serve(echo, port=8765, ping_timeout=None): await asyncio.Future() asyncio.run(main()) |
図5 チャットクライアントのHTMLコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
<!doctype html> <html lang="ja"> <head> <meta charset="utf-8"> <title>A Simple BBS</title> <meta name="description" content="WebSockets TEST"> <meta name="author" content="Jun IIO"> <script src="jquery-3.6.1.min.js"></script> <script src="scripts.js"></script> </head> <body> <h1>シンプル・チャット・システム</h1> <div> 名前: <input id="regname" type="text"> <button id="register">登録</button> </div> <div> <textarea id="msgarea" cols=80 rows=10 readonly disabled="disabled"></textarea> </div> <div> <input id="chatmsg" type="text" size=80 disabled="disabled"> <button id="send" disabled="disabled">送信</button> </div> </body> </html> |
図6 チャットクライアントのJavaScriptコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
$(function(){ let socket = new WebSocket("ws://localhost:8765/"); $("#register").on("click", function(event) { name = $("#regname").val(); if (name != "") { socket.send(name); $("#register").prop("disabled", true); $("#regname").prop("disabled", true); $("#msgarea").prop("disabled", false); $("#chatmsg").prop("disabled", false); $("#send").prop("disabled", false); }; }); socket.onmessage = function(event) { text = $("#msgarea").val(); $("#msgarea").val(text + event.data + "\n"); }; $("#send").on("click", function(event) { msg = $("#chatmsg").val(); if (msg != "") { socket.send(msg); $("#chatmsg").val(""); }; }); }); |
香川大学SLPからお届け!(Vol.81掲載)
著者:永田 歩
今回は、米Google社が提供するメディアデータ向けの機械学習ライブラリ「MediaPiPe」を用いて、動画内にある人の顔を検出し、そのデータを基に3D CGアニメーションを生成するプログラムを紹介します。AIを3D CGに利用する試みの一つとして参考にしてください。
シェルスクリプトマガジン Vol.81は以下のリンク先でご購入できます。
図1 動画をフレームに分割するプログラム「video2img.py」のコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
import cv2 as cv import os video_path = './video/test_video.mp4' dir_path = './image' basename = 'img_frame' ext = 'jpg' # 動画を読み込んで存在しなければ終了 cap = cv.VideoCapture(video_path) if not cap.isOpened(): exit() os.makedirs(dir_path, exist_ok=True) base_path = os.path.join(dir_path, basename) # 画像保存名のためにフレームの桁数をカウント digit = len(str(int(cap.get(cv.CAP_PROP_FRAME_COUNT)))) n = 0 while True: ret, frame = cap.read() if ret: # 画像の保存 cv.imwrite('{}_{}.{}'.format(base_path, str(n).zfill(digit), ext), frame) n += 1 else: exit() |
図2 顔のランドマーク座標を出力するコード「images2npy.py」のメイン部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import cv2 as cv import mediapipe as mp import os import glob import numpy as np # 使用する画像のパス名を取得してリスト化 resource_dir = r'./image' file_list = glob.glob(os.path.join(resource_dir, "*.jpg")) xyzval = [] for file_path in file_list: # 画像を読み込む image = cv.imread(file_path) xyzval.append(get_landmark(image, file_path)) # 3次元numpy配列のバイナリファイルとして保存 np.save('result/np_save', xyzval) |
図3 get_landmark()関数の定義コード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
def get_landmark(image, file_path): # 顔のランドマークを取得する準備 mp_face_mesh = mp.solutions.face_mesh face_mesh = mp_face_mesh.FaceMesh( static_image_mode=True, max_num_faces=1, min_detection_confidence=0.5) rgb_image = cv.cvtColor(image, cv.COLOR_BGR2RGB) # ランドマーク検出 results = face_mesh.process(rgb_image) # 顔が検出されなければ終了 if not results.multi_face_landmarks: exit() face_landmarks = results.multi_face_landmarks[0] file_path = os.path.splitext(file_path)[0] file_name = file_path.split('/')[-1] # 保存先のフォルダ作成 result_txt_dir = 'result/txt' os.makedirs(result_txt_dir, exist_ok=True) txtfile_path = result_txt_dir+'/'+file_name+'.txt' # テキストファイルにRawデータを書き込む f = open(txtfile_path, 'w') f.write(str(face_landmarks)) f.close() make_landmarkimg(image, face_landmarks, file_name, mp_face_mesh) face_mesh.close() return get_original_scalexyz(image, txtfile_path) |
図5 get_original_scalexyz()関数の定義コード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
def get_original_scalexyz(image, txtfile_path): height, width = image.shape[:2] vers = [] i = 0 with open(txtfile_path) as ft: lines = ft.read() for l in lines.split("\n"): if i%5 == 0 or i%5 == 4: i += 1 continue elif i%5 == 1: tmp = l.split() x = f'{float(tmp[1]) *width:.5f}' elif i%5 == 2: tmp = l.split() y = f'{float(tmp[1]) *height:.5f}' else: tmp = l.split() z = f'{float(tmp[1]) *width:.5f}' vers.append([float(x), float(y), float(z)]) i += 1 return vers |
図6 make_landmarkimg()関数の定義コード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
def make_landmarkimg(image, face_landmarks, file_name, mp_face_mesh): result_image_dir = 'result/image' os.makedirs(result_image_dir, exist_ok=True) # 顔のランドマークを画像上に出力する準備 mp_drawing = mp.solutions.drawing_utils drawing_spec = mp_drawing.DrawingSpec(thickness=1, circle_radius=1) # 元画像のコピーを作成 annotated_image = image.copy() # 顔のランドマークを描画した画像を生成 mp_drawing.draw_landmarks( image=annotated_image, landmark_list=face_landmarks, connections=mp_face_mesh.FACEMESH_TESSELATION, landmark_drawing_spec=drawing_spec, connection_drawing_spec=drawing_spec) cv.imwrite(result_image_dir+'/'+file_name+'.png', annotated_image) |
図8 3D CGアニメーションを作成するコード「npy2blender.py」のメイン部分
1 2 3 4 5 6 7 8 9 |
import numpy as np import bpy filepath = r'3次元numpy配列のバイナリファイルのパス名' # 例 → r'C:/Users/user1/python/shellmag/result/np_save.npy' xyzval = np.load(filepath) ver_count = len(xyzval[0]) frame_count = len(xyzval) generate_ver(ver_count) move_ver_anyframe(xyzval, frame_count) |
図9 generate_ver()関数の定義コード
1 2 3 4 5 6 7 8 9 |
def generate_ver(ver_count): # 点群オブジェクトの生成 bpy.data.meshes.new(name='shellmag_Mesh') bpy.data.objects.new(name='shellmag_Obj', object_data=bpy.data.meshes['shellmag_Mesh']) bpy.context.scene.collection.objects.link(bpy.data.objects['shellmag_Obj']) Obj = bpy.data.objects['shellmag_Obj'].data Obj.vertices.add(ver_count) return |
図10 move_ver_anyframe()関数の定義コード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
def move_ver_anyframe(xyzval, frame_count): ob = bpy.data.objects['shellmag_Obj'].data for frame_num in range(frame_count): i = 0 # XYZ座標を各点ごとに定義してフレームとして保存 for p in xyzval[frame_num]: X = p[0] Y = p[1] Z = p[2] ver = ob.vertices[i] ver.co.x = X ver.co.y = Y ver.co.z = Z i += 1 ver.keyframe_insert('co',index = -1,frame = frame_num) return |
AWKでデジタル信号処理(Vol.81掲載)
著者:斉藤 博文
プログラミング言語「AWK」は、データストリーム(データの流れ)を逐次処理するのに適しています。本連載では、電子回路の分野でその特徴を生かし、シェルスクリプトを組み合わせてデジタル信号を処理します。第3回は低周波成分を減らす「ハイパスフィルタ」について解説します。
シェルスクリプトマガジン Vol.81は以下のリンク先でご購入できます。
図7 ハイパスフィルタの移動平均プログラム(hpf.awk)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
#! /usr/bin/gawk -f BEGIN { # define number of order num_ord = num_ord ? num_ord : 255; # define length of ring buffers len_data_raw = num_ord + 1; len_data_hpf = 2; # initialize ring buffers for (i = 0; i < len_data_raw; i++) { arr_data_raw[i] = 0.0; } for (i = 0; i < len_data_hpf; i++) { arr_data_hpf[i] = 0.0; } # initialize index of ring buffers idx_data_raw = 0; idx_data_hpf = 0; # initialize number of data num_data_raw = 0; } { # add number of data num_data_raw++; # update index of ring buffers (write pointers) idx_data_raw = num_data_raw % len_data_raw; idx_data_hpf = num_data_raw % len_data_hpf; # clear number of data if (idx_data_raw == 0 && idx_data_hpf == 0) { num_data_raw = 0; } # store input raw data val_data_raw = $0; arr_data_raw[idx_data_raw] = val_data_raw; # apply high pass filter arr_data_hpf[idx_data_hpf] = hpf( \ arr_data_raw, arr_data_hpf, idx_data_raw, idx_data_hpf, num_ord, len_data_raw, len_data_hpf); # print results print arr_data_hpf[idx_data_hpf]; } # get value of ring buffer function get_buffer(arr, idx, len) { if (idx < 0) { return arr[idx + len]; } return arr[idx]; } # high pass filter function hpf(arr_x, arr_y, idx_x, idx_y, ord, len_x, len_y, _ret, _gain) { _ret = 0.0; _gain = 1.0 / ord; _ret += get_buffer(arr_y, idx_y - 1, len_y); _ret -= _gain * get_buffer(arr_x, idx_x, len_x); _ret += get_buffer(arr_x, idx_x - int((ord - 1) / 2), len_x); _ret -= get_buffer(arr_x, idx_x - int((ord + 1) / 2), len_x); _ret += _gain * get_buffer(arr_x, idx_x - ord, len_x); return _ret; } |
図12 群遅延を補正したプログラム(hpf_gd.awk)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
#! /usr/bin/gawk -f BEGIN { # define number of order num_ord = num_ord ? num_ord : 255; # define group delay val_gd = int((num_ord - 1) / 2); # define length of ring buffers len_data_raw = num_ord + 1; len_data_hpf = val_gd + 1; # initialize ring buffers for (i = 0; i < len_data_raw; i++) { arr_data_raw[i] = 0.0; } for (i = 0; i < len_data_hpf; i++) { arr_data_hpf[i] = 0.0; } # initialize index of ring buffers idx_data_raw = 0; idx_data_hpf = 0; # initialize number of data num_data_raw = 0; } { # add number of data num_data_raw++; # update index of ring buffers (write pointers) idx_data_raw = num_data_raw % len_data_raw; idx_data_hpf = num_data_raw % len_data_hpf; # clear number of data if (idx_data_raw == 0 && idx_data_hpf == 0) { num_data_raw = 0; } # store input raw data val_data_raw = $0; arr_data_raw[idx_data_raw] = val_data_raw; # apply high pass filter arr_data_hpf[idx_data_hpf] = hpf( \ arr_data_raw, arr_data_hpf, idx_data_raw, idx_data_hpf, num_ord, len_data_raw, len_data_hpf); # print results print get_buffer(arr_data_raw, idx_data_raw - val_gd, len_data_raw), arr_data_hpf[idx_data_hpf]; } # get value of ring buffer function get_buffer(arr, idx, len) { if (idx < 0) { return arr[idx + len]; } return arr[idx]; } # high pass filter function hpf(arr_x, arr_y, idx_x, idx_y, ord, len_x, len_y, _ret, _gain) { _ret = 0.0; _gain = 1.0 / ord; _ret += get_buffer(arr_y, idx_y - 1, len_y); _ret -= _gain * get_buffer(arr_x, idx_x, len_x); _ret += get_buffer(arr_x, idx_x - int((ord - 1) / 2), len_x); _ret -= get_buffer(arr_x, idx_x - int((ord + 1) / 2), len_x); _ret += _gain * get_buffer(arr_x, idx_x - ord, len_x); return _ret; } |
シェルスクリプトマガジンvol.81 Web掲載記事まとめ
004 レポート Windows 11が動くVirtualBox
005 レポート Webブラウザ動作のPostgreSQL
006 製品レビュー 玩具「coemo(コエモ)」
007 NEWS FLASH
008 特集1 本格的なホームサーバーを構築/麻生二郎 コード掲載
018 特集2 Google AppSheet入門/伊藤勇斗、石野耀久、江口隆司
038 特集3 さくらのクラウド/前佛雅人
050 特別企画 MySQL HeatWave Database Serviceの新機能/生駒眞知子
057 Hello Nogyo!
058 Raspberry Piを100%活用しよう/米田聡 コード掲載
062 Pythonあれこれ/飯尾淳 コード掲載
066 法林浩之のFIGHTING TALKS/法林浩之
068 中小企業手作りIT化奮戦記/菅雄一
074 メタバース/桑原滝弥、イケヤシロウ
076 タイ語から分かる現地生活/つじみき
080 香川大学SLPからお届け!/永田歩 コード掲載
086 AWKでデジタル信号処理/斉藤博文 コード掲載
094 ユニケージ通信/田渕智也、高橋未来哉
098 Bash入門/大津真
104 Techパズル/gori.sh
105 コラム「ユニケージの学術論文が出ました」/シェル魔人
特集1 本格的なホームサーバーを構築(Vol.81記載)
著者:麻生 二郎
小型コンピュータボードの最上位モデルである「Raspberry Pi 4 Model B」の4G/8Gバイト版と、人気のLinuxディストリビューション「Ubuntu」のサーバー版を組み合わせて、本格的なサーバーを構築しましょう。本特集では、サーバーの可用性や信頼性を向上する方法を紹介します。
シェルスクリプトマガジン Vol.81は以下のリンク先でご購入できます。
図A3 ラズパイサーバーの初期設定(ubuntu_init1.sh)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
#!/bin/sh ##日本のタイムゾーン設定 sudo timedatectl set-timezone Asia/Tokyo ##全ソフトウエア更新 sudo apt update sudo apt -y upgrade sudo apt -y autoremove sudo apt clean ##ファームウエアアップデート sudo apt -y install rpi-eeprom sudo rpi-eeprom-update ##完了後の再起動 read -p "再起動しますか [y/N]:" YN if [ " $YN" = " y" ] || [ " $YN" = " Y" ]; then sudo reboot fi |
図A4 ラズパイサーバーの初期設定(ubuntu_init2.sh)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
#!/bin/sh ##固定IPアドレスとルーターのIPアドレス IP_ADDRESS="192.168.10.100" ROUTER_IP="192.168.10.1" ##旧設定バックアップ mkdir -p ~/old_settings sudo mv /etc/netplan/50-cloud-init.yaml ~/old_settings/. ##新ネットワーク設定作成 cat << EOF | sudo tee /etc/netplan/50-cloud-init.yaml > /dev/null network: ethernets: eth0: dhcp4: false addresses: [ip_address/24] gateway4: router_ip nameservers: addresses: [8.8.8.8] version: 2 EOF sudo sed -i -e "s%ip_address%$IP_ADDRESS%" /etc/netplan/50-cloud-init.yaml sudo sed -i -e "s%router_ip%$ROUTER_ip%" /etc/netplan/50-cloud-init.yaml ##ネットワーク設定反映 sudo netplan apply |
シェルスクリプトマガジンvol.80 Web掲載記事まとめ
004 レポート 6GHz帯の無線LAN国内解禁
005 レポート Zen 4採用のRyzenプロセッサ出荷
006 製品レビュー パソコン「LAVIE GX(GX750/EAB)」
007 NEWS FLASH
008 特集1 Red Hat Enterprise Linux 9/森若和雄
022 特集2 Linux初心者テストにチャレンジ!/長原宏治 コード掲載
042 緊急特集 チャットサービスを立ち上げよう/麻生二郎
058 Raspberry Piを100%活用しよう/米田聡 コード掲載
062 Pythonあれこれ/飯尾淳 コード掲載
068 NFT/桑原滝弥、イケヤシロウ
070 中小企業手作りIT化奮戦記/菅雄一
075 Hello Nogyo!
076 法林浩之のFIGHTING TALKS/法林浩之
078 香川大学SLPからお届け!/佐々木龍之介 コード掲載
082 タイ語から分かる現地生活/つじみき
088 AWKでデジタル信号処理/斉藤博文 コード掲載
096 Bash入門/大津真
104 Techパズル/gori.sh
105 コラム「ユニケージ黎明(れいめい)期」/シェル魔人
特集1 Linux初心者テストにチャレンジ!(Vol.80記載)
著者:長原 宏治
Linuxエンジニアの認定試験としては「LPIC」が有名ですが、より幅広くLinux初学者を対象とする「Linux Essentials」という認定試験が2020年から開始されています。本特集では出題範囲に対する理解を確認できる例題を挙げながら、Linux Essentials試験の内容を紹介します。つまずきがちな部分について問う例題を盛り込みましたので、腕に覚えがある人もぜひチャレンジしてみてください。
シェルスクリプトマガジン Vol.80は以下のリンク先でご購入できます。
Part3 演習問題集の解答と解説
図1 問3-3-1の解答例
1 2 3 4 |
#!/bin/bash codefile=data.csv prefecture=$1 grep $prefecture $codefile | sort -t , -k 5 | cut -d , -f 3 | tail -n +2 |
図2 問3-3-2の解答例
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#!/bin/bash codefile=data.csv if [ $# -le 0 ] then echo Usage: $0 PREFACTURE... >&2 exit 1 fi for prefecture in $* do echo ${prefecture}: grep $prefecture $codefile | sort -t , -k 5 | cut -d, -f 3 | tail -n +2 done exit 0 |
Raspberry Piを100%活用しよう(Vol.80掲載)
著者:米田 聡
小型コンピュータボード「Raspberry Pi」(ラズパイ)向けにさまざまな拡張ボードが発売されています。その拡張ボードとラズパイを組み合わせれば、ラズパイでいろいろなことが簡単に試せます。第13回は、照明用などの高輝度LEDの明るさを制御できる拡張基板を扱います。
シェルスクリプトマガジン Vol.80は以下のリンク先でご購入できます。
図5 LEDの明るさを制御するプログラム(ADRSZSW.py)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
import RPi.GPIO as GPIO import smbus import time ADDR=0x56 # I2Cアドレス SW=5 # スイッチのGPIO bus = None value: int = 0x0 # 明るさの値 # スイッチのコールバック関数 def switch(ch): global value value += 0x05 # デューティ比を書き込む bus.write_byte_data(ADDR, 0x01, (value & 0xFF)) # デューティー比を読み出す current = bus.read_byte_data(ADDR,0) print(current) if __name__ == "__main__": bus = smbus.SMBus(1) bus.write_byte_data(ADDR, 0x01, 0) # GPIOの設定 GPIO.setmode(GPIO.BCM) GPIO.setup(SW, GPIO.IN) GPIO.add_event_detect(SW, GPIO.FALLING, callback=switch, bouncetime=200) try: while True: time.sleep(30) except KeyboardInterrupt: pass GPIO.remove_event_detect(SW) GPIO.cleanup(SW) |
Pythonあれこれ(Vol.80掲載)
著者:飯尾 淳
本連載では「Pythonを昔から使っているものの、それほど使いこなしてはいない」という筆者が、いろいろな日常業務をPythonで処理することで、立派な「蛇使い」に育つことを目指します。その過程を温
かく見守ってください。皆さんと共に勉強していきましょう。第10回は、データを可視化する例として、プログラムの対話型実行環境「Jupyter Notebook」をローカル環境で動かして簡単な地理情報を表示させてみます。
シェルスクリプトマガジン Vol.80は以下のリンク先でご購入できます。
図7 町丁目データを地図上に重ねて表示するためのコード
1 2 3 4 5 6 7 8 9 |
for _, r in df.iterrows(): sim_geo = gpd.GeoSeries(r['geometry']) geo_j = sim_geo.to_json() geo_j = folium.GeoJson(data=geo_j, style_function=lambda x: {'fillColor': 'grey', 'color': 'grey', 'weight': 0.5, 'fill_opacity': 0.3, 'line_opacity': 0.1 }) folium.Popup(r['Name']).add_to(geo_j) geo_j.add_to(m) m |
図10 駅の位置と乗降客数のデータをまとめるためのコード
1 2 3 |
df2 = pd.merge(df_stations.drop('Description', axis=1), \ df_passengers, on='Name') df2.head() |
図12 駅の位置を地図に表示するためのコード(その1)
1 2 3 4 5 6 |
for _, row in df2.iterrows(): folium.Marker( location=[row['geometry'].y, row['geometry'].x], popup=row['Name'] ).add_to(m) m |
図14 駅の位置を地図に表示するためのコード(その2)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
m = folium.Map(location=[35.65, 139.34], \ zoom_start=12, tiles=‘openstreetmap’) for _, r in df.iterrows(): sim_geo = gpd.GeoSeries(r['geometry']) geo_j = sim_geo.to_json() geo_j = folium.GeoJson(data=geo_j, style_function=lambda x: {'fillColor': 'grey', 'color': 'grey', 'weight': 0.5, 'fill_opacity': 0.3, 'line_opacity': 0.1 }) folium.Popup(r['Name']).add_to(geo_j) geo_j.add_to(m) for _, row in df2.iterrows(): folium.CircleMarker( location=[row['geometry'].y, row['geometry'].x], radius=3, color='red', fill_color='red', weight=2 ).add_to(m) m |
図16 「緯度」「経度」「乗降客数」のカラムを持つデータフレームを作成するためのコード
1 2 3 4 5 |
df3 = pd.DataFrame(data={'Lat': df2[‘geometry'].y, 'Lng': df2['geometry'].x, 'Passengers': df2['Passengers']}) df3.head() |
香川大学SLPからお届け!(Vol.80掲載)
著者:佐々木 龍之介
今回は、数値解析アルゴリズムの一つである「ニュートン法」を紹介します。ニュートン法を利用すると、反復計算によって方程式の解を近似的に求められます。C言語を使ったサンプルプログラムを挙げながら、同手法について簡単に解説します。
シェルスクリプトマガジン Vol.80は以下のリンク先でご購入できます。
図3 「x2 – 1 = 0」という方程式の解を求めるCプログラムの例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#include <stdio.h> #include <math.h> #define e 0.000001 double f(double x) { return x*x - 1; } double df(double x) { return 2*x; } int main() { double x1 = 2.5, x2; while(1) { x2 = x1 - f(x1)/df(x1); if(fabs(f(x2)) < e) { break; } x1 = x2; } printf("%f\n", x2); } |
図4 「x2 – 1 = 0」という方程式の解を求めるCプログラムの改良版
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#include <stdio.h> #include <math.h> #define e 0.000001 double f(double x) { return x*x - 1; } double df(double x) { return 2*x; } int main() { double x1 = 2.5, x2; while(1) { x2 = x1 - f(x1)/df(x1); if(fabs(x2 - x1) < e) { break; } x1 = x2; } printf("%f\n", x2); } |
図5 √2の近似解を求めるCプログラムの例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
#include <stdio.h> #include <math.h> #define e 0.000001 double f(double x) { return x*x - 2; } double df(double x) { return 2*x; } double g(double x) { return x - f(x) / df(x); } int main() { double x1 = 2.0, x2; while(1){ x2 = g(x1); if(fabs(x2 - x1) < e) { break; } x1 = x2; printf("%f\n", x2); } printf("√2= %f\n", x2); } |
AWKでデジタル信号処理(Vol.80掲載)
著者:斉藤 博文
プログラミング言語「AWK」は、データストリーム(データの流れ)を逐次処理するのに適しています。本連載では、電子回路の分野でその特徴を生かし、シェルスクリプトを組み合わせてデジタル信号を処理します。第2回は、高周波ノイズを除去する「ローパスフィルタ」について解説します。
シェルスクリプトマガジン Vol.80は以下のリンク先でご購入できます。
図6 移動平均のプログラム(lpf.awk)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
#! /usr/bin/gawk -f BEGIN { # define number of order num_ord = num_ord ? num_ord : 7; # define length of ring buffers len_data_raw = num_ord + 1; len_data_lpf = 2; # initialize ring buffers for (i = 0; i < len_data_raw; i++) { arr_data_raw[i] = 0.0; } for (i = 0; i < len_data_lpf; i++) { arr_data_lpf[i] = 0.0; } # initialize index of ring buffers idx_data_raw = 0; idx_data_lpf = 0; # initialize number of data num_data_raw = 0; } { # add number of data num_data_raw++; # update index of ring buffers (write pointers) idx_data_raw = num_data_raw % len_data_raw; idx_data_lpf = num_data_raw % len_data_lpf; # clear number of data if (idx_data_raw == 0 && idx_data_lpf == 0) { num_data_raw = 0; } # store input raw data val_data_raw = $0; arr_data_raw[idx_data_raw] = val_data_raw; # apply low pass filter arr_data_lpf[idx_data_lpf] = lpf( \ arr_data_raw, arr_data_lpf, idx_data_raw, idx_data_lpf, num_ord, len_data_raw, len_data_lpf); # print results print arr_data_lpf[idx_data_lpf]; } |
図10 ずれ補正を加えた移動平均のプログラム(lpf_gd.awk)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
#! /usr/bin/gawk -f BEGIN { # define number of order num_ord = num_ord ? num_ord : 7; # define group delay val_gd = int((num_ord - 1) / 2); # define length of ring buffers len_data_raw = num_ord + 1; len_data_lpf = val_gd + 1; # initialize ring buffers for (i = 0; i < len_data_raw; i++) { arr_data_raw[i] = 0.0; } for (i = 0; i < len_data_lpf; i++) { arr_data_lpf[i] = 0.0; } # initialize index of ring buffers idx_data_raw = 0; idx_data_lpf = 0; # initialize number of data num_data_raw = 0; } { # add number of data num_data_raw++; # update index of ring buffers (write pointers) idx_data_raw = num_data_raw % len_data_raw; idx_data_lpf = num_data_raw % len_data_lpf; # clear number of data if (idx_data_raw == 0 && idx_data_lpf == 0) { num_data_raw = 0; } # store input raw data val_data_raw = $0; arr_data_raw[idx_data_raw] = val_data_raw; # apply low pass filter arr_data_lpf[idx_data_lpf] = lpf( \ arr_data_raw, arr_data_lpf, idx_data_raw, idx_data_lpf, num_ord, len_data_raw, len_data_lpf); # print results print get_buffer(arr_data_raw, idx_data_raw - val_gd, len_data_raw), arr_data_lpf[idx_data_lpf]; } # get value of ring buffer function get_buffer(arr, idx, len) { if (idx < 0) { return arr[idx + len]; } return arr[idx]; } # low pass filter function lpf(arr_x, arr_y, idx_x, idx_y, ord, len_x, len_y, _ret, _gain) { _ret = 0.0; _gain = 1.0 / ord; _ret += get_buffer(arr_y, idx_y - 1, len_y); _ret += _gain * get_buffer(arr_x, idx_x, len_x); _ret -= _gain * get_buffer(arr_x, idx_x - ord, len_x); return _ret; } |
Vol.80 補足情報
Pythonあれこれ
記事中で使用するデータファイルは以下のWebページから入手できます。
https://github.com/shellscript-magazine/python_this_and_that/releases/tag/ver1.0
情報は随時更新致します。
特集2 FlutterでGUIアプリを開発しよう(Vol.79記載)
著者:三好 文二郎
「Flutter」は、米Google社がOSS(オープンソースソフトウエア)として提供する、GUI アプリ開発用のSDK(Software Development Kit)です。単一のコードで複数のプラットフォーム向けのGUIアプリを作成できるほか、標準で用意されるUIコンポーネントが豊富、Hot Reloadによる開発者体験が素晴らしいことなどから開発者の間で人気が高まっています。Flutterの概要と、Flutterを利用したアプリ開発方法について紹介します。
シェルスクリプトマガジン Vol.79は以下のリンク先でご購入できます。
図18 ウィジェットに関する記述の例
1 2 3 4 5 6 7 8 9 |
children: <Widget>[ const Text( 'You have pushed the button this many times:', ), Text( '$_counter', style: Theme.of(context).textTheme.headline4, ), ], |
図19 ボタンウィジェットを追加するコードの例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
children: <Widget>[ const Text( 'You have pushed the button this many times:', ), Text( '$_counter', style: Theme.of(context).textTheme.headline4, ), MaterialButton ( color: Colors.cyan.withOpacity(0.7), shape: RoundedRectangleBorder(borderRadius:BorderRadius.circular(30.0)), onPressed: () {}, child: const Text('this is a button widget'), ), ], |
図21 my_first_appプロジェクトの「pubspec.yaml」ファイルの内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
name: my_first_app description: A new Flutter project. publish_to: "none" version: 1.0.0+1 environment: sdk: ">=2.16.2 <3.0.0" dependencies: flutter: sdk: flutter cupertino_icons: ^1.0.2 dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^1.0.0 flutter: uses-material-design: true |
図22 go_routerパッケージを利用する場合の記述例
1 2 3 4 5 |
dependencies: flutter: sdk: flutter cupertino_icons: ^1.0.2 go_router: ^3.1.1 |
図23 画面を追加するためのコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class NavigatedPage extends StatelessWidget { const NavigatedPage({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('navigated page'), ), body: const Center( child: Text('navigated with go_router'), ), ); } } |
図24 画面遷移のためのコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
class MyApp extends StatelessWidget { MyApp({Key? key}) : super(key: key); final _router = GoRouter( routes: [ GoRoute( path: '/', builder: (context, state) => const MyHomePage(title: 'Flutter Demo Home Page'), ), GoRoute( path: '/navigated', builder: (context, state) => const NavigatedPage(), ), ], ); @override Widget build(BuildContext context) { return MaterialApp.router( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), routeInformationParser: _router.routeInformationParser, routerDelegate: _router.routerDelegate); } } |
図25 ボタンウィジェットに画面遷移処理を割り当てるコード
1 2 3 4 5 6 7 8 |
MaterialButton ( color: Colors.cyan.withOpacity(0.7), shape: RoundedRectangleBorder(borderRadius:BorderRadius.circular(30.0)), onPressed: () { GoRouter.of(context).go('/navigated'); }, child: const Text('Navigate'), ), |
図26 main()関数のコードを変更
1 2 3 |
void main() { runApp(MyApp()); } |
特集3 ユニケージ開発のシステム(Vol.79記載)
著者:松浦 智之
システム開発手法「ユニケージ」では、データ保存用に「ファイル」を使い、ユニケージ専用コマンド群「usp Tukubai」と「シェルスクリプト」で業務システムを開発します。usp Tukubaiは有償ソフトですが、無償のオープンソース版「Open usp Tukubai」もあります。このOpen usp Tukubaiを使って、ユニケージ開発を無料で始めてみましょう。
シェルスクリプトマガジン Vol.79は以下のリンク先でご購入できます。
図6 Apache HTTP Serverの郵便番号・住所検索システム用設定ファイル(/etc/apache2/sites-available/zip2addr.conf)
1 2 3 4 5 6 7 8 |
<IfModule alias_module> Alias /zip2addr /home/ユーザー名/ZIP2ADDR/public_html <Directory /home/ユーザー名/ZIP2ADDR/public_html> AddHandler cgi-script .cgi Options all Require all granted </Directory> </IfModule> |
図10 MK_ZIPTBL.SHのソースコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
#!/bin/sh -u (略) homd=$(d=${0%/*}/; [ "_$d" = "_$0/" ] && d='./'; cd "$d.."; pwd) datd=$homd/DATA shld=$homd/SHELL webd=$homd/public_html url_ken=https://www.post.japanpost.jp/zipcode/dl/oogaki/zip/ken_all.zip url_jig=https://www.post.japanpost.jp/zipcode/dl/jigyosyo/zip/jigyosyo.zip export LC_ALL=C (略) nocmds='' type wget >/dev/null 2>&1 || { nocmds="$nocmds,wget" ; } type gunzip >/dev/null 2>&1 || { nocmds="$nocmds,gunzip"; } type iconv >/dev/null 2>&1 || { nocmds="$nocmds,iconv" ; } if [ -n "$nocmds" ]; then echo "${0##*/}: ${nocmds#,} not found. Install them in advance." 1>&2 exit 1 fi (略) wget -q -O - "$url_ken" | gunzip | tr -d '\r' | iconv -c -f Shift_JIS -t UTF-8 | tr -d '"' | awk -F , '{print $3,$7,$8,$9}' > $datd/ziptbl_ken.txt if [ ! -s $datd/ziptbl_ken.txt ]; then echo "${0##*/}: Failed to make zip_ken.txt" 1>&2; exit 1 fi (略) wget -q -O - "$url_jig" | gunzip | tr -d '\r' | iconv -c -f Shift_JIS -t UTF-8 | tr -d '"' | awk -F , '{print $8,$4,$5,$6 $7}' > $datd/ziptbl_jig.txt if [ ! -s $datd/ziptbl_jig.txt ]; then echo "${0##*/}: Failed to make zip_ken.txt" 1>&2; exit 1 fi (略) exit 0 |
図12 ZIP2ADDR.AJAX.cgiのコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
#!/bin/sh -u (略) homd=$(d=${0%/*}/; [ "_$d" = "_$0/" ] && d='./'; cd "$d.."; pwd) datd=$homd/DATA shld=$homd/SHELL webd=$homd/public_html export LC_ALL=C (略) exit_trap() { set -- ${1:-} $? # $? is set as $1 if no argument given trap '' EXIT HUP INT QUIT PIPE ALRM TERM [ -d "${tmpd:-}" ] && rm -rf "$tmpd" trap - EXIT HUP INT QUIT PIPE ALRM TERM exit $1 } error500_exit() { echo 'Status: 500 Internal Server Error' echo 'Content-Type: text/plain' echo echo '500 Internal Server Error' echo "($@)" exit 1 } error400_exit() { echo 'Status: 400 Bad Request' echo 'Content-Type: text/plain' echo echo '400 Bad Request' echo "($@)" exit 1 } (略) trap 'exit_trap' EXIT HUP INT QUIT PIPE ALRM TERM tmpd=$(mktemp -d -t "_${0##*/}.$$.XXXXXXXXXXX") [ -d "$tmpd" ] || error500_exit 'Failed to mktemp' (略) printf '%s\n' "${QUERY_STRING:-}" | cgi-name > $tmpd/cgivars zip=$(nameread zipcode $tmpd/cgivars) printf '%s\n' "$zip" | grep -qE '^[0-9]{7}$' || error400_exit 'Invalid zipcode' (略) echo $homd/DATA/ziptbl* | grep -qF '*' && error500_exit 'No table files exist' cat $homd/DATA/ziptbl* | awk '$1=="'"$zip"'"{print "pref",$2; print "city",$3; print "town",$4;}' > $tmpd/address123 [ -s $tmpd/address123 ] || error400_exit 'No address found' (略) echo 'Content-Type: text/html; charset=utf-8' echo 'Cache-Control: private, no-store, no-cache, must-revalidate' echo 'Pragma: no-cache' echo formhame $webd/ZIP2ADDR.html $tmpd/address123 | sed -n '/BEGIN ADDRESS123/,/END ADDRESS123/p' (略) [ -d "${tmpd:-}" ] && rm -rf "$tmpd" exit 0 |
図13 ZIP2ADDR.htmlのコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
<!DOCTYPE html> (略) <script type="text/javascript" src="ZIP2ADDR.js"></script> (略) </head> <body> <h1>郵便番号→住所検索 デモ</h1> <form action="#dummy"> <table border="0" id="addressform"> <tr> <td> <dl> <dt>郵便番号</dt> <dd><input id="zipcode1" type="text" name="zipcode1" value="" size="3" maxlength="3" />-<input id="zipcode2" type="text" name="zipcode2" value="" size="4" maxlength="4" /></dd> <dd><input id="run" type="button" name="run" value="検索実行!" onclick="zip2addr();"></dd> </dl> </td> </tr> <tr> <td> <dl id="adress123"> <!-- BEGIN ADDRESS123--> <dt>住所(都道府県名)</dt> <dd> <select id="pref" name="pref"> <option value="(未選択)">(未選択)</option> (略) <option value="沖縄県" >沖縄県</option> </select> </dd> <dt>住所(市区町村名)</dt><dd><input id="city" type="text" size="60" name="city" value="" /></dd> <dt>住所(町名以降)</dt><dd><input id="town" type="text" size="60" name="town" value="" /></dd> <!-- END ADDRESS123--> </dl> </td> </tr> </table> </form> </body> </html> |
図14 ZIP2ADDR.jsのコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
(略) function zip2addr() { var url; var zipcode; var xhr; (略) if (! document.getElementById('zipcode1').value.match(/^([0-9]{3})$/)) { alert('郵便番号(前の3桁)が正しくありません'); return; } zipcode = RegExp.$1; if (! document.getElementById('zipcode2').value.match(/^([0-9]{4})$/)) { alert('郵便番号(後の4桁)が正しくありません'); return; } zipcode += RegExp.$1; (略) xhr = createXMLHttpRequest(); if (xhr) { url = 'ZIP2ADDR.AJAX.cgi?zipcode='+zipcode; url += '&dummy='+parseInt((new Date)/1); xhr.open('GET', url, true); xhr.onreadystatechange = function(){zip2addr_callback(xhr)}; xhr.send(null); } (略) return; } (略) function zip2addr_callback(xhr) { var e; (略) if (xhr.readyState != 4) {return;} if (xhr.status == 0 ) {return;} if (xhr.status == 400) { alert('郵便番号が正しくありません'); return; } else if (xhr.status != 200) { alert('アクセスエラー(' + xhr.status + ')'); return; } (略) e = document.getElementById('adress123'); if (!e) {alert('エラー: 住所欄が存在しない!'); return;} e.innerHTML = xhr.responseText; (略) return; } |
特別企画 本格的なホームサーバーを構築(Vol.79記載)
著者:麻生 二郎
小型コンピュータボードの最上位モデルである「Raspberry Pi 4 Model B」の2G/4G/8Gバイト版と、人気のLinuxディストリビューション「Ubuntu」のサーバー版を組み合わせて、本格的なサーバーを構築しましょう。本特集では、サーバーをインターネットに公開する方法を紹介します。
シェルスクリプトマガジン Vol.79は以下のリンク先でご購入できます。
図A3 ラズパイサーバーの初期設定(ubuntu_init1.sh)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
#!/bin/sh ##日本のタイムゾーン設定 sudo timedatectl set-timezone Asia/Tokyo ##全ソフトウエア更新 sudo apt update sudo apt -y upgrade sudo apt -y autoremove sudo apt clean ##ファームウエアアップデート sudo apt -y install rpi-eeprom sudo rpi-eeprom-update ##完了後の再起動 read -p "再起動しますか [y/N]:" YN if [ " $YN" = " y" ] || [ " $YN" = " Y" ]; then sudo reboot fi |
図A4 ラズパイサーバーの初期設定(ubuntu_init2.sh)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
#!/bin/sh ##固定IPアドレスとルーターのIPアドレス IP_ADDRESS="192.168.10.100" ROUTER_IP="192.168.10.1" ##旧設定バックアップ mkdir -p ~/old_settings sudo mv /etc/netplan/50-cloud-init.yaml ~/old_settings/. ##新ネットワーク設定作成 cat << EOF | sudo tee /etc/netplan/50-cloud-init.yaml > /dev/null network: ethernets: eth0: dhcp4: false addresses: [ip_address/24] gateway4: router_ip nameservers: addresses: [8.8.8.8] version: 2 EOF sudo sed -i -e "s%ip_address%$IP_ADDRESS%" /etc/netplan/50-cloud-init.yaml sudo sed -i -e "s%router_ip%$ROUTER_ip%" /etc/netplan/50-cloud-init.yaml ##ネットワーク設定反映 sudo netplan apply |
Raspberry Piを100%活用しよう(Vol.79掲載)
著者:米田 聡
小型コンピュータボード「Raspberry Pi」(ラズパイ)向けにさまざまな拡張ボードが発売されています。その拡張ボードとラズパイを組み合わせれば、ラズパイでいろいろなことが簡単に試せます。第12回は、電流を測定するクランプメーターを搭載する拡張基板を扱います。
シェルスクリプトマガジン Vol.79は以下のリンク先でご購入できます。
図6 本連載で作成したサンプルプログラム(sample.py)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
import time import board import math import busio import adafruit_ads1x15.ads1115 as ADS from adafruit_ads1x15.ads1x15 import Mode from adafruit_ads1x15.analog_in import AnalogIn GAIN = 1 RATE = 860 i2c = busio.I2C(board.SCL, board.SDA, frequency=1000000) ads = ADS.ADS1115(i2c, GAIN) # 差動入力で初期化 in0 = AnalogIn(ads, ADS.P0, ADS.P1) # 連続モードの設定 ads.mode = Mode.CONTINUOUS ads.data_rate = RATE SAMPLE_INTERVAL = 1.0 / ads.data_rate # ADS1115のCONTINUOUSモードは初回読み取り実行後に設定が行われ # 設定に2サンプリングクロックが必要になる _ = in0.voltage # 設定用の空読み込み value = 0.0 current_value = 0.0 sqrtI = 0.0 time_next_sample = time.monotonic() + SAMPLE_INTERVAL for i in range(ads.data_rate): while time.monotonic() < (time_next_sample): pass # 前回と同じ値が読み取られた場合は読み飛ばす current_value = in0.voltage while current_value == value: current_value = in0.voltage value = current_value sqrtI += value * value time_next_sample = time.monotonic() + SAMPLE_INTERVAL # 電流値(RMS)の計算 ampere = math.sqrt(sqrtI/ads.data_rate) * 20.0 print(ampere) |
香川大学SLPからお届け!(Vol.79掲載)
著者:岩本 和真
人間の言語をコンピュータで処理する自然言語処理の分野は、近年、急速に進歩しています。それによって例えば、自然な翻訳や文章生成をする技術などが開発されています。同分野の学習成果として筆者は、「秘書チャット」と名付けたチャットボットアプリを作成しました。今回は、このアプリに盛り込んだ機能について、技術的に解説します。
シェルスクリプトマガジン Vol.79は以下のリンク先でご購入できます。
図5 形態素解析処理をするmorpheme()関数の定義コード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
def morpheme(self, input, speech=False): input=unicodedata.normalize('NFKC', input) if speech: speech_list = [] sentence = self.wakati.parse(input).split() node = self.wakati.parseToNode(input) while node: if node.feature.split(",")[0] != "BOS/EOS": speech_list.append(node.feature.split(",")[0]) node = node.next return sentence, speech_list else: sentence = self.wakati.parse(input).split() return sentence |
図8 予定の内容を抽出するcontent_extract()関数の定義コード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
def content_extract(self, input_list, speech_list): out_list = [] ban_word = ["覚え", "記憶"] pass_word = ["予定", "こと"] for i, input in enumerate(input_list): if input in ban_word: break elif input in pass_word: continue elif input.isdecimal() and \ input_list[i+1] in self.date_key: continue elif (input_list[i-1]).isdecimal() and \ input in self.date_key: continue elif out_list != [] and speech_list[i-1] == "名詞" and \ speech_list[i+1] == "名詞" and \ input_list[i+1] not in pass_word: out_list.append(input_list[i]) elif speech_list[i] == "名詞": out_list.append(input) else: continue return ''.join(out_list) |
図9 Webページのテキスト情報を抽出するscraping()関数の定義コード
1 2 3 4 5 6 7 8 9 10 11 12 |
from bs4 import BeautifulSoup import requests def scraping(url, file_path): responses = requests.get(url) soup = BeautifulSoup(responses.content, 'html.parser') text_list = soup.get_text().splitlines() text_list = list(set(text_list)) text_list = [text.replace('\u3000', '') for text in text_list] text = '\n'.join(text_list) with open(file_path, 'w', encoding='utf_8') as f: f.write(text) |
図12 曜日や豆知識を答える機能のコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
def week_teach(self, input): year = None month = None day = None input = self.date_update.convert(input) input_list = self.morpheme(input) if "年" in input_list: year = self.date_specify("年", input_list) else: year = self.year if "月" in input_list: month = self.date_specify("月", input_list) else: month = self.month day = self.date_specify("日", input_list) d_key = dt.date(year, month, day) week_key = d_key.weekday() return year, month, day, self.week_list[week_key] def knowledge_teach(self): file_path = "text_data/min_kl.txt" with open(file_path, 'r', encoding='UTF-8') as f: knowledge_data = f.readlines() knowledge = random.choice(knowledge_data) return knowledge |
AWKでデジタル信号処理(Vol.79掲載)
著者:斉藤 博文
プログラミング言語「AWK」は、データストリーム(データの流れ)を逐次処理するのに適しています。本連載では、電子回路の分野でその特徴を生かし、シェルスクリプトを組み合わせてデジタル信号を処理します。第1回は「デジタルフィルタ」と「リングバッファ」について解説します。
シェルスクリプトマガジン Vol.79は以下のリンク先でご購入できます。
図14 1~10000の出力データから末尾の10行を取り出すAWKプログラム(tail.awk)
1 2 3 4 5 6 7 8 9 10 11 12 |
#! /usr/bin/gawk -f { lines[NR] = $0; } END { for (i = NR - 9; i <= NR; i++) { print lines[i]; } } |
図15 リングバッファを用いたAWKプログラム(tail_ring.awk)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
#! /usr/bin/gawk -f BEGIN { len = 10; } { idx = NR % 10; buf[idx] = $0; } END { for (i = 9; i >= 0; i--) { print get_buf(buf, idx - i, len); } } function get_buf(buf, idx, len) { if (idx < 0) { return buf[idx + len]; } else { return buf[idx]; } } |
図17 「get_buf」関数をPythonのプログラムで記述
1 2 3 4 5 |
def get_buf(buf, idx, len): if (idx < 0): return buf[idx + len] else: return buf[idx] |
Vol.79 補足情報
特集3 ユニケージ開発のシステム
p.30の中央右段に「163mm」の不要な文字が入り込み、本文と重なってしまいました。お詫びして訂正いたします。
特別企画 本格的なホームサーバーを構築 インターネット公開編
p.46の左段にある「$ wget -O /home/pi/DDNSNow_update.log “https://f5.si/update.php?domain=ユーザー名&password=パスワード”」の「pi」は「Ubuntu Serverのユーザー名」の誤り、右段にある「0-59 * * * * wget -O DDNSNow_update.log “https://f5.si/update.php?domain=ユーザー名&password=パスワード”」の「 -O DDNSNow_update.log」は「 -O /home/Ubuntu Serverのユーザー名/DDNSNow_update.log」の誤りです。お詫びして訂正いたします。
IPアドレスの調べ方
ルーターのDHCP(Dynamic Host Configuration Protocol)のリース情報から、Raspberry Piに割り当てられたIPアドレスを、次のように調べます。
パソコンのWebブラウザを開いて、ルーターの管理画面にログインします。ログイン方法は、ルーターのマニュアルや、インターネットの情報で調べてください。
管理画面が開いたら、DHCPのリース情報を表示します。こちらもルーターの機種によって操作方法や名称が異なりますが、一般的な家庭用ルーターには必ずあります。例にしたルーターでは、「詳細設定」から「LAN」「DHCPリース」でネットワーク機器に割り当てられているIPアドレスを表示できます(図1)。
リース情報を表示した状態で、Raspberry Piを起動するとRaspberry Piに割り当てられたIPアドレスの情報が追加されます(図2の赤枠)。IPアドレスを使ってリモートからアクセスできます。
ちなみに、このリース情報画面でRaspberry PiのMACアドレスと、割り当てたいIPアドレスを設定すれば、Raspberry Pi側のネットワーク設定を変更せずとも常に同じIPアドレス、固定IPアドレスを設定できます。
The Poetry and Art Collection
p.64の右上に前号で掲載した詩の一部(複雑のひと 単純のこころ 唯一のひかり 無限のやみ)が入ってしまいした。お詫びして訂正いたします。
情報は随時更新致します。
シェルスクリプトマガジンvol.79 Web掲載記事まとめ
004 レポート テキストエディタ「Vim 9.0」リリース コード掲載
005 レポート 「Raspberry Pi Zero 2 W」国内版発売 コード掲載
006 製品レビュー イヤホン「LinkBuds」
007 NEWS FLASH
008 特集1 仮想化ソフト「VirtualBox」 徹底活用/麻生二郎
020 特集2 FlutterでGUIアプリを開発しよう/三好文二郎 コード掲載
030 特集3 ユニケージ開発のシステム/松浦智之 コード掲載
042 特別企画 本格的なホームサーバーを構築 インターネット公開編/麻生二郎 コード掲載
051 Hello Nogyo!
052 Raspberry Piを100%活用しよう/米田聡 コード掲載
056 Pythonあれこれ/飯尾淳
064 マルウエア/桑原滝弥、イケヤシロウ
066 中小企業手作りIT化奮戦記/菅雄一
070 タイ語から分かる現地生活/つじみき
076 法林浩之のFIGHTING TALKS/法林浩之
078 香川大学SLPからお届け!/岩本和真 コード掲載
084 AWKでデジタル信号処理/斉藤博文 コード掲載
090 Bash入門/大津真
098 Techパズル/gori.sh
099 コラム「こうしてユニケージは生まれた」/シェル魔人
レポート テキストエディタ「Vim 9.0」リリース(Vol.79掲載)
著者:末安 泰三
テキストエディタ「Vim」の最新版「Vim 9.0」が2022年6月28日に公開された。前版「Vim 8.2」の公開は2019年12月のため、約2年半ぶりのリリースとなる。Vim 9.0の目玉となる新機能は、文法を見直して大幅な高速化を実現したスクリプト言語「Vim9 Script」のサポートである。
シェルスクリプトマガジン Vol.79は以下のリンク先でご購入できます。
図2 新旧のコードで定義した関数の実行結果と実行時間を表示するコードの例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
command! -bar TimerStart let start_time = reltime() command! -bar TimerEnd echo reltimestr(reltime(start_time)) \ | unlet start_time function Add_old() let a = 0 for i in range(1, 1000000) let a += i endfor return a endfunction def Add_new(): number var a = 0 for i in range(1, 1000000) a += i endfor return a enddef TimerStart echo Add_old() TimerEnd TimerStart echo Add_new() TimerEnd |
カテゴリー コード のアーカイブを表示しています。