# Project export: ECGo

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: TreeHacks 2025
- Tagline: Mobile ECG with Wi-Fi capabilities that makes AI-powered diagnoses and stores data on a remote server.
- Devpost: https://devpost.com/software/ecgo
- GitHub: https://github.com/fightingj305/ecgo
- Video: https://www.youtube.com/embed/IxavWJJbxuU?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Result: winner (Web3 Prize: Most Crazy Idea ($7k Cash + 7k EIGEN))
- Team: 2 GitHub contributor(s) — fightingj305 (18 commits), Kelvin Nguyen (8 commits)

## Devpost submission (written by the team)

### Overview

(Pardon the very basic video - we worked straight to the wire to bring this project to fruition!)

### Inspiration

Both our team members come from families working in the medical device industry, and we know firsthand how important critical medical devices are for saving lives around the world. Our project aims to solve the lack of portable ECG machines on hand in developing regions or crisis-stricken areas, where such devices (and the doctors needed to operate them) are incredibly expensive or simply unavailable.

### What it does

ECGo takes a three second sample of your heartbeat using attached electrodes. It then forwards this sample to a remotely hosted database, from which an AI model is able to run an arrhythmia diagnosis on the test data. This information is then sent back to the user, along with a sample ID they can use to access their data in the future from our web application.

### How we built it

The heart (get it?) of the hardware on this project consists of an ESP32 microcontroller, which uses its built-in ADC to read values from an AD8232 heartbeat filtering IC. This data is then displayed on two 0.96" OLED screens controlled over I2C. The user can also use a joystick to pan and zoom the ECG waveform displayed on the screen. For safety, both the microcontroller and IC are powered by AA batteries to avoid any potentially dangerous power connections. On the software side, the ESP32 must be connected via Wi-Fi to some network which allows it to make HTTP requests. We use these requests to write data to an InfluxDB database, and also read the AI model's diagnosis out from the same database. Each sample is randomly assigned an ID from 0-10000 for use with the web interface. The front-end software is a simple streamlit app, which uses Python to query the database for user data and displays the resulting data. The server uses a CNN Transformer which we pretrained on open-source ECG data. It also interfaces with the InfluxDB database by checking it periodically for a new series of data, and writes its inference to the database when it finds new data.

### Challenges we ran into

One early challenge with the hardware bringup was the fact that my OLED screens were manufactured with a single I2C address, meaning that there would be no way to use two screens to display different data. To fix this we had to desolder a small surface mounted resistor and shift it to a different pin, which was quite a challenge. Otherwise, noise was (and is) a big issue with the project; we do our best to avoid being in areas with tons of electromagnetic interference when taking detailed samples. Testing the model was also a challenge, since we don’t have a good way of measuring an irregular heartbeat with our electrodes. The model did report a positive diagnosis of arrhythmia for incredibly noisy environments, which have more irregular signals, and a negative diagnosis for a regular heartbeat. We explored the web3 track and experimented with placing an AI-powered agent on the application also assists with interpreting their test results and giving medical suggestions. However, this was rather difficult to integrate in the limited time we had left and so in the final product we left it off.

### Accomplishments we're proud of

As a team of only two (one hardware/firmware and one software/AI), we are very proud of having a successful final product with so many different features and complexities. We’re very happy with the way the hardware fabrication went, from prototyping to the final PCB soldering without any major hiccups. Furthermore, the entire data flow of chip -> HTTP -> Database -> Model -> Database -> HTTP -> chip is surprisingly reliable and effective.

### What we learned

This project was a new experience for both of us when it comes to integrating our areas of technical expertise with each other, as interactions between embedded systems and AI on such a close level are less common in our normal work. Technically, we both learned a lot about working with time-series data and InfluxDB. Working through the aforementioned integration gave us both a better understanding of how to work with diverse inputs and outputs that we don’t usually see.

### What's next

ECGo is not just about arrhythmia diagnosis - this paradigm of an inexpensive, mobile device for use in the field in conjunction with more powerful AI diagnostics and support in a remote server has great potential to us in bringing healthcare to areas around the world with less access to technology or medical personnel. The next future steps are to further develop the diagnostic capabilities and improve the ECG filtering, and then adapt the device to be even more technology-free by moving the patient query onto the ESP32 as well, making the relatively inexpensive hardware component the only item we need on-hand to perform the whole suite of diagnostic, data storage, and recommendation.

## README (from the GitHub repository)

# ECGO: Mobile ECG for Arrhythmia Detection - Treehacks 2025 Winner for Most Crazy Idea (Sponsored by Eigenlayer)

See our devpost!
https://devpost.com/software/ecgo

We would like to credit the below paper for helping inform our diagnostic model:
"Hu R, Chen J, Zhou L. A transformer-based deep neural network for arrhythmia detection using continuous ECG signals[J]. Computers in Biology and Medicine, 2022, 144: 105325.") Hu R, Chen J, Zhou L. A transformer-based deep neural network for arrhythmia detection using continuous ECG signals[J]. Computers in Biology and Medicine, 2022, 144: 105325.


## Detected evidence (automated analysis)

Indexed codebase: 19 recognized source files, 69 KB.
- Python (language) — detected in the code
- C (language) — claimed on Devpost, not found in the code
- PyTorch (technology) — claimed on Devpost, not found in the code
- Streamlit (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (26 of 26)

```
.gitignore
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/settings.json
datasets/__init__.py
datasets/evaluator.py
datasets/MIT_BIH_dataset.py
ecg_app.py
ecg/ecg.ino
engine.py
inference.py
loaddataset.py
main.py
models/__init__.py
models/backbone.py
models/ECG_DETR.py
models/matcher.py
models/position_encoding.py
models/transformer.py
outputs/log.txt
README.md
test.ipynb
util.py
utils/__init__.py
utils/box_ops.py
utils/plot_utils.py
```

### Dependencies

No dependency index available.

### Recent commits (newest first)

- Update README.md
- Update README.md
- Final bugfixes!
- Merge branch 'model'
- ECG Front End
- added ai features
- Fixing underflow error causing HTTP 400; display ID
- Diagnosis Display
- Diagnostic receive code via HTTP query
- Added collection id field to data
- Added code to make HTTP Posts to InfluxDB
- Merge branch 'main' of https://github.com/fightingj305/treehacks-2025
- Add collection timer
- Merge pull request #2 from fightingj305/model
- delete extra files
- Merge branch 'main' of https://github.com/fightingj305/treehacks-2025
- upload model, changed trianing code to add early stopping, added inference
- Fixed pins due to conflict with wifi functionality
- Dry write of basic wifi connectivity
- Dry write of button toggle/debounce

## Key source files (fetched from GitHub, selected and truncated for size)

### main.py

```python
import torch, random, os, time, json, pdb
import datetime
import numpy as np
from models import build_model
from datasets import build_dataset, collate_fn
from utils import plot_logs
from torch.utils.data import DataLoader
from engine import train_one_epoch, evaluate
from torch.utils.tensorboard import SummaryWriter

os.environ["CUDA_VISIBLE_DEVICES"] = "0"

class Arguments(object):
    def __init__(self) -> None:
        print(f"This machine has {torch.cuda.device_count()} gpu...")
        self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
        self.rootpath = r"C:\Users\kelvi\03 MyDocuments\30 MyCode\TreeHacks 2025\ECG-arrhythmia-detection-based-on-DETR\\" ## dataset path
        self.numfolds = 10
        self.seed = 10086
        self.batchsize = 128
        self.epochs = 150
        self.clip_max_norm = 0.15
        self.lr_drop = 80
        self.output_dir = "./outputs/"
        self.early_stop_patience = 10  # Number of epochs with no improvement after which training will be stopped

def main():
    # pdb.set_trace()
    args = Arguments()
    print("loaded arguments")
    if not torch.cuda.is_available():
        print("GPU is not available")
        raise Exception("GPU is not available")
    if not os.path.exists(args.output_dir):
        os.mkdir(args.output_dir)
        logpath = os.path.join(args.output_dir, "log.txt")
        if os.path.exists(logpath):
            os.remove(logpath)
    
    writer = SummaryWriter(log_dir=args.output_dir)
    
    torch.manual_seed(args.seed)
    np.random.seed(args.seed)
    random.seed(args.seed)

    in_chan, d_model, num_class, num_queries, aux_loss = 1, 128, 4, 10, True
    model, criterion, postprocessor = build_model(in_chan, d_model, num_class, num_queries, aux_loss=aux_loss)
    model.to(args.device)

    n_parameters = sum(p.numel() for p in model.parameters() if p.requires_grad)
    print(f"number of params: {n_parameters}")

    param_dicts = [
        {"params": [p for n, p in model.named_parameters() if "backbone" not in n and p.requires_grad]}, 
        {"params": [p for n, p in model.named_parameters() if "backbone" in n and p.requires_grad], 
        "lr": 1e-3}
        ]
    optimizer = torch.optim.Adam(param_dicts, lr=1e-3, weight_decay=1e-4)
    lr_schedular = torch.optim.lr_scheduler.StepLR(optimizer, args.lr_drop, gamma=0.25)

    all_samples = [f for f in os.listdir(os.path.join(args.rootpath, "data")) if f.endswith(".txt")]
    random.shuffle(all_samples)
    # all_samples = all_samples[:2000]
    samples_each_fold = len(all_samples) // args.numfolds
    for fold in range(1):
        val_samples = all_samples[fold*samples_each_fold:(fold+1)*samples_each_fold]
        train_samples = [sam for sam in all_samples if sam not in val_samples]

        dataset_train = build_dataset(args.rootpath, train_samples)
        dataset_val = build_dataset(args.rootpath, val_samples)

        data_loader_train = DataLoader(dataset_train, args.batchsize, shuffle=True, collate_fn=collate_fn)
        data_loader_val = DataLoader(dataset_val, args.batchsize, shuffle=False, collate_fn=collate_fn)

        print("Start training...")
        start_time = time.time()
        # pdb.set_trace()
        
        best_val_class_error = float("inf")
        patience_counter = 0
        
        for epoch in range(args.epochs):
            train_stats = train_one_epoch(
                model, criterion, data_loader_train, optimizer, args.device, 
                epoch, args.clip_max_norm
            )
            lr_schedular.step()

            test_stats = evaluate(
                model, criterion, postprocessor, data_loader_val, args.device, args.output_dir
            )

            log_stats = {"epoch": epoch,
                        "n_params": n_parameters,
                        **{f"train_{k}": v for k, v in train_stats.items()},
                        **{f"test_{k}": v for k, v in test_stats.items()},
                        }

            writer.add_scalar('Training Loss', train_stats['loss'], epoch)
            writer.add_scalar('Validation Loss', test_stats['loss'], epoch)
            writer.add_scalar('Class Error', test_stats['class_error'], epoch)

            if test_stats["class_error"] < best_val_class_error:
                best_val_class_error = test_stats["class_error"]
                patience_counter = 0  # Reset patience counter
                
                ckpt = os.path.join(args.output_dir, f"best_checkpoint2.pth")
                torch.save({
                    "epoch": epoch,
                    "args": args,
                    "model": model.state_dict(),
                    "optimizer": optimizer.state_dict(),
                    "lr_scheduler": lr_schedular.state_dict()
                }, ckpt)
                print(f"Model improved at epoch {epoch}, saved!")
            else:
                patience_counter += 1
                if patience_counter >= args.early_stop_patience:
                    print(f"Early stopping triggered after {epoch+1} epochs.")
                    break

            if args.output_dir:
                with open(os.path.join(args.output_dir, "log.txt"), "a") as f:
                    f.write(json.dumps(log_stats) + "\n")

        plot_logs(args.output_dir, log_name="log.txt", fields=("loss", "loss_ce", "loss_bbox", "loss_giou", "class_error"))
        total_time = time.time() - start_time
        total_time_str = str(datetime.timedelta(seconds=int(total_time)))
        print('Fold.{} Training time {}'.format(fold, total_time_str))
    
    writer.close()

if __name__ == "__main__":
    main()
```

### loaddataset.py

```python
import numpy as np
import wfdb

rootpath = "d:\\Desktop\\ECG分类研究/mit-bih-arrhythmia-database-1.0.0/"
# 读取心电数据和对应标签,并对数据进行小波去噪
def getDataSet(number, X_data):
    # 读取心电数据记录
    print("正在读取 " + number + " 号心电数据...")
    record = wfdb.rdrecord(rootpath + number, channel_names=['MLII'])  #源文件都放在ecg_data这个文件夹中了
    data = record.p_signal.flatten()
    #data=np.array(data)

    # 获取心电数据记录中R波的位置和对应的标签
    annotation = wfdb.rdann(rootpath + number, 'atr')
    Rlocation = annotation.sample  #对应位置
    Rclass = annotation.symbol  #对应标签
    print(set(Rclass))

    X_data.append(data)

    return

def loadData():
    numberSet = ['100', '101', '103', '105', '106', '107', '108', '109', '111', '112', '113', '114', '115',
                 '116', '117', '119', '121', '122', '123', '124', '200', '201', '202', '203', '205', '208',
                 '210', '212', '213', '214', '215', '217', '219', '220', '221', '222', '223', '228', '230',
                 '231', '232', '233', '234']
    dataSet = []
    for n in numberSet:
        getDataSet(n, dataSet)
    return dataSet

def main():
    dataSet = loadData()
    dataSet = np.array(dataSet)
    print(dataSet.shape)
    print("data ok!!!")

if __name__ == '__main__':
    main()

```

### inference.py

```python
import torch
import numpy as np  
from models import build_model
import torch.nn.functional as F
from main import Arguments


def load_model():

    # class Arguments(object):
    #     def __init__(self) -> None:
    #         print(f"This machine has {torch.cuda.device_count()} gpu...")
    #         self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    #         self.rootpath = r"C:\Users\kelvi\03 MyDocuments\30 MyCode\TreeHacks 2025\ECG-arrhythmia-detection-based-on-DETR\\" ## dataset path
    #         self.numfolds = 10
    #         self.seed = 10086
    #         self.batchsize = 128
    #         self.epochs = 150
    #         self.clip_max_norm = 0.15
    #         self.lr_drop = 80
    #         self.output_dir = "./outputs/"
    #         self.early_stop_patience = 10  # Number of epochs with no improvement after which training will be stopped
    model_path = r"C:\Users\kelvi\03 MyDocuments\30 MyCode\TreeHacks 2025\ECG-arrhythmia-detection-based-on-DETR\outputs\best_checkpoint.pth"
    model_file = torch.load(model_path, weights_only=False)
    print("Loading model weights from:", model_path)
    model_weights = model_file['model']

    in_chan, d_model, num_class, num_queries, aux_loss = 1, 128, 5, 10, True
    model, _, _ = build_model(in_chan, d_model, num_class, num_queries, aux_loss=aux_loss)

    model.load_state_dict(model_weights, strict=False)
    model.to("cuda")
    model.eval()
    print("Model loaded successfully.")

    return model
def forward(model, data):
    # Load the model weights
    # model_path = r"C:\Users\kelvi\03 MyDocuments\30 MyCode\TreeHacks 2025\ECG-arrhythmia-detection-based-on-DETR\outputs\best_checkpoint.pth"
    # model_file = torch.load(model_path, map_location=torch.device('cpu'))
    # print("Loading model weights from:", model_path)
    # model_weights = model_file['model']

    # Load the model architecture and weights

    target_sizes = torch.tensor([1080])
    
    outputs = model(torch.tensor(data, dtype=torch.float32).unsqueeze(0).unsqueeze(0).to("cuda"))


    out_logits, out_box = outputs["pred_logits"], outputs["pred_boxes"]
    # pdb.set_trace()
    assert len(out_logits) == len(target_sizes)
    assert target_sizes.ndim == 1

    prob = F.softmax(out_logits, dim=-1)
    scores, labels = prob[..., :-1].max(dim=-1) 

    results = [{"scores": s, "labels": l} for s, l, in zip(scores, labels)][0]

    if results['scores'][0] > results['scores'][1]:
        return 0
    else:
        return 1






```

### ecg_app.py

```python
from dotenv import load_dotenv
import streamlit as st
from influxdb_client import InfluxDBClient
from influxdb_client.client.write_api import SYNCHRONOUS
import pandas as pd
import os

load_dotenv()

# InfluxDB connection parameters
url = "https://us-east-1-1.aws.cloud2.influxdata.com"
token = os.environ["INFLUX_TOKEN"]
org = "ECG Data"
bucket = "data"

# Check if the token is available
if not token:
    st.error("INFLUX_TOKEN environment variable is not set. Please set it and restart the app.")
    st.stop()

def query_influxdb_label(sample_id):
    client = InfluxDBClient(url=url, token=token, org=org)
    query_api = client.query_api()

    query = f'''
    from(bucket: "{bucket}")
    |> range(start: -1d)
    |> filter(fn: (r) => r._field == "id" or r._field == "label")
    |> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value")
    |> filter(fn: (r) => r.id == {sample_id})
    |> sort(columns: ["_time"])
    |> limit(n:1)
    '''

    result = query_api.query(query)

    if result and len(result) > 0 and len(result[0].records) > 0:
        return result[0].records[0].values.get('label')
    else:
        return None

def query_influxdb_ecg_data(sample_id):
    client = InfluxDBClient(url=url, token=token, org=org)
    query_api = client.query_api()

    query = f'''
    from(bucket: "{bucket}")
    |> range(start: -30d)
    |> filter(fn: (r) => r._field == "collection_id" or r._field == "adc_value" or r._field == "collection_time")
    |> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value")
    |> filter(fn: (r) => r.collection_id == {sample_id})
    |> sort(columns: ["_time"])
    '''
    
    result = query_api.query_data_frame(query)
    
    return result

def main():
    st.title("ECG Data Lookup and Visualization App")

    st.write("Please enter a sample ID between 0 and 10000.")

    sample_id = st.number_input("Sample ID", min_value=0, max_value=10000, value=0, step=1)

    if st.button("Look up Data"):
        label = query_influxdb_label(sample_id)
        if label is not None:
            st.success(f"The label for Sample ID {sample_id} is: {label}")
        else:
            st.error(f"No label found for Sample ID {sample_id}")

        # Query ECG data
        ecg_data = query_influxdb_ecg_data(sample_id)
        
        if not ecg_data.empty:
            # Set the collection_time as the index
            ecg_data.set_index('collection_time', inplace=True)
            
            # Create the plot
            st.line_chart(ecg_data['adc_value'])
            st.write("ECG Data (ADC Value vs Collection Time)")
        else:
            st.error(f"No ECG data found for Sample ID {sample_id}")

if __name__ == "__main__":
    main()
```

### engine.py

```python
from typing import Iterable
import torch, math, sys, pdb
from tqdm import tqdm
from collections import deque, defaultdict


class SmootheValue(object):
    def __init__(self, window_size=20) -> None:
        self.deque = deque(maxlen=window_size)
        self.total = 0.0
        self.count = 0

    def update(self, value, n=1):
        self.deque.append(value)
        self.count += n
        self.total += value * n

    @property
    def median(self):
        d = torch.tensor(list(self.deque))
        return d.median().item()

    @property
    def avg(self):
        d = torch.tensor(list(self.deque), dtype=torch.float32)
        return d.mean().item()

    @property
    def global_avg(self):
        return self.total / self.count

    @property
    def max(self):
        return max(self.deque)

    @property
    def value(self):
        return self.deque[-1]

def train_one_epoch(model: torch.nn.Module, criterion: torch.nn.Module,
    data_loader: Iterable, optimizer: torch.optim.Optimizer,
    device: torch.device, epoch: int, max_norm: float = 0):
    
    model.train()
    criterion.train()
    header = 'Epoch: [{}] Training: '.format(epoch)
    meters = defaultdict(SmootheValue)
    meters["loss"] = SmootheValue(window_size=20)
    meters["lr"] = SmootheValue(window_size=1)
    pbar = tqdm(data_loader)

    # pdb.set_trace()
    for samples, targets in pbar:
        samples = samples.to(device)
        targets = [{k: v.to(device) for k, v in t.items()} for t in targets]

        outputs = model(samples)
        # print(samples.shape)
        print("target", targets)
        loss_dict = criterion(outputs, targets)
        for k in loss_dict.keys():
            if k not in meters.keys():
                meters[k] = SmootheValue(window_size=20)
            meters[k].update(loss_dict[k].cpu().item())
            
        weight_dict = criterion.weight_dict
        losses = sum(loss_dict[k]*weight_dict[k] for k in loss_dict.keys() if k in weight_dict.keys())

        loss_value = losses.cpu().item()

        if not math.isfinite(loss_value):
            print(f"loss is {loss_value}, stop training...")
            print(loss_dict)
            sys.exit(1)

        optimizer.zero_grad()
        losses.backward()
        if max_norm > 0:
            torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm)
        optimizer.step()
        meters["loss"].update(loss_value)
        meters["lr"].update(optimizer.param_groups[0]["lr"])
        pbar.set_description("Train-> lr: {:.5f} loss: {:.4f} loss_ce: {:.4f}, loss_bbox: {:.4f}, loss_giou: {:.4f}, class_error: {:.4f}%".format(
            meters["lr"].value, meters["loss"].avg, meters["loss_ce"].avg, meters["loss_bbox"].avg, meters["loss_giou"].avg, meters["class_error"].avg
        ))

    stats = {k: meter.global_avg for k, meter in meters.items()}
    print(header, stats)
    return stats

@torch.no_grad()
def evaluate(model: torch.nn.Module, criterion: torch.nn.Module, postprocessor: torch.nn.Module,
            data_loader: Iterable, device: torch.device, output_dir: str):
    # pdb.set_trace()
    model.eval()
    criterion.eval()
    meters = defaultdict(SmootheValue)
    meters["loss"] = SmootheValue(window_size=20)
    pbar = tqdm(data_loader)

    for samples, targets in pbar:
        samples = samples.to(device)
        targets = [{k: v.to(device) for k, v in t.items()} for t in targets]

        print(samples.shape)
        outputs = model(samples)
        loss_dict = criterion(outputs, targets)
        for k in loss_dict.keys():
            if k not in meters.keys():
                meters[k] = SmootheValue(window_size=20)
            meters[k].update(loss_dict[k].cpu().item())

        weight_dict = criterion.weight_dict
        losses = sum(loss_dict[k]*weight_dict[k] for k in loss_dict.keys() if k in weight_dict.keys())
        loss_value = losses.cpu().item()
        meters["loss"].update(loss_value)
        pbar.set_description("Test-> loss: {:.4f} loss_ce: {:.4f}, loss_bbox: {:.4f}, loss_giou: {:.4f}, class_error: {:.4f}%".format(
            meters["loss"].avg, meters["loss_ce"].avg, meters["loss_bbox"].avg, meters["loss_giou"].avg, meters["class_error"].avg
        ))
    stats = {k: meter.global_avg for k, meter in meters.items()}
    print("Testing: ", stats, "\n\n")
    return stats

```

### util.py

```python
import influxdb_client
import socket
from datetime import datetime
import influxdb_client, os, time
from influxdb_client import InfluxDBClient, Point, WritePrecision
from influxdb_client.client.write_api import SYNCHRONOUS
import numpy as np
import matplotlib.pyplot as plt

from main import Arguments
# Now you can import the module
import inference

token = os.environ.get("INFLUX_TOKEN")
org = "ECG DATA"
url = os.environ.get("INFLUX_ADDR")


client = influxdb_client.InfluxDBClient(url=url, token=token, org=org)
bucket="data"
write_api = client.write_api(write_options=SYNCHRONOUS)

query_api = client.query_api()



def write(label, id_val):
    point = influxdb_client.Point("model_output").field("label", label).field("id", np.uint(id_val))
    write_api.write(bucket=bucket, org="org", record=point)
def read():
    # Get the last 60 points from the bucket, specifically the adc_value field
    query = f'''from(bucket: "{bucket}")
  |> range(start: -1d)
  |> filter(fn: (r) => r._field == "adc_value" or r._field == "collection_id")
  |> sort(columns: ["_time"], desc: true)
  |> limit(n: 60)
  '''
    result = query_api.query(org=org, query=query)
    print(result)
    res_arr = []
    id_val = None

# Process and print the results
    for table in result:
        for record in table.records:
            # Extract fields from the records
            if record.get_field() == "adc_value":
                res_arr.append(record.get_value())
            elif record.get_field() == "collection_id" and id_val is None:
                id_val = record.get_value()
    return res_arr, id_val
def interpolate(arr):
    sampling_rate_original = 20
    num_points = len(arr)

    duration = num_points / sampling_rate_original  # duration in seconds

    # Create original timestamps
    t_original = np.linspace(0, duration, num_points, endpoint=False)

    # Desired sampling rate
    sampling_rate_new = 360  # Hz
    num_points_new = int(duration * sampling_rate_new)

    # Create new timestamps
    t_new = np.linspace(0, duration, num_points_new, endpoint=False)

    # Interpolate the data
    data_interpolated = np.interp(t_new, t_original, arr)

    print("data interpolated", data_interpolated)
    return data_interpolated
def process_data(model):
    res, id_val = read()
    interpolated_data = interpolate(res)
    # # Z score the data
    # mean = np.mean(interpolated_data)
    # std = np.std(interpolated_data)
    # z_score_data = (interpolated_data - mean) / std

    # assert len(z_score_data) == 1080
    print("z_score_data", len(interpolated_data))

    print("Performing inference")
    label = inference.forward(model, interpolated_data)
    print("writing data")
    write(label, id_val)
    print("written data")



if __name__ == "__main__":
    model = inference.load_model()
    process_data(model)

# with open(path, "a") as file:
#     file.write(CSV_HEADER+"\n")

# with open("desired_headers.txt","r") as file:
#     desired_headers = list(map(lambda x:x.strip("\n "),file.readlines()))
# print(f"Printing: {desired_headers}")
# while True:
#     dataDict = {}
#     data, addr = sock.recvfrom(2048) # buffer size is 2048 bytes

#     strdata = data.decode("utf-8")
    
#     with open(path, "a") as file:
#         file.write(strdata)
#     print("\n"*25)
#     # cleaner print if one string printed all at once, then carriage return
#     headers = CSV_HEADER.split(",")
#     datas = strdata.split(",")
#     printstr = ""
#     for i in range(len(headers)):
#         dataDict[headers[i]] = datas[i]
#         data = dataDict[headers[i]]
#         try:
#             data = float(data)
#         except:
#             pass
#         if "CellVoltages_" in headers[i]:
#             point = (
#                 Point(headers[i])
#                 .tag("cell_number",int(headers[i][13:]))
#                 .field("output_value", data)
#             )
#         elif "Thermistor_Temperature" in headers[i]:
#             point = (
#                 Point(headers[i])
#                 .tag("thermistor_number",int(headers[i][22:]))
#                 .field("output_value", data)
#             )
#         else:
#             point = (
#                 Point(headers[i])
#                 .field("output_value", data)
#             )
#         write_api.write(bucket=bucket, org="Stanford Solar Car Project", record=point)

#     for i in range(len(desired_headers)):
#         header = desired_headers[i]
#         blank = ' ' * (30 - len(header))   
#         blank2 = ' ' * (6 - len(dataDict[header]))    
#         printstr = printstr + f"{header}:{blank}{dataDict[header]}{blank2}"
#         if (i%3 == 0):
#             printstr+="\n"
#         else:
#             printstr+="\t"
#     print(printstr, end="\r")

```

### utils/__init__.py

```python
from .plot_utils import plot_logs
```

### datasets/__init__.py

```python
from .MIT_BIH_dataset import MIT_BIH_dataset, collate_fn

def build_dataset(rootpath, samples):
    return MIT_BIH_dataset(rootpath, samples)
```

### models/__init__.py

```python
from .ECG_DETR import build

def build_model(in_chan, d_model, num_class, num_queries, aux_loss=True):
    return build(in_chan, d_model, num_class, num_queries, aux_loss=aux_loss)
```

### utils/plot_utils.py

```python
import torch, os, pdb
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns

def plot_logs(log, fields=("loss", "loss_ce", "loss_bbox", "loss_giou"), log_name="log.txt", ewm_col=1):
    func_name = "plot_utils.py::plot_logs"
    # pdb.set_trace()
    assert os.path.isdir(log), f"{func_name} - log must be a dir..."
    if not os.path.exists(log):
        raise ValueError(f"{func_name} - logs not exist...")

    df = pd.read_json(os.path.join(log, log_name), lines=True)
    fig, axs = plt.subplots(1, len(fields), figsize=(16, 5))

    for df, color in zip([df], sns.color_palette(n_colors=1)):
        for j, field in enumerate(fields):
            df.interpolate().ewm(com=ewm_col).mean().plot(
                y=[f"train_{field}", f"test_{field}"],
                ax=axs[j],
                color=[color] * 2,
                style=["-", "--"]
            )
    for ax, field in zip(axs, fields):
        ax.legend([f"train_{field}", f"test_{field}"])
        ax.set_title(field)
        ax.set_xlabel("Epoch")
    log_name = log_name.split(".")[0]
    plt.savefig(os.path.join(log, f"{log_name}.png"), dpi=300)
    print(f"log figure is saved in ", os.path.join(log, f"{log_name}.png"), "...")

if __name__ == "__main__":
    log = "D:\\Desktop\\ECG分类研究\\code\\outputs"
    plot_logs(log, log_name="log01.txt")

```

[7 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]