Full Code of eugenelet/Meta-rPPG for AI

master fe9d526fdd9c cached
13 files
55.8 KB
15.0k tokens
85 symbols
1 requests
Download .txt
Repository: eugenelet/Meta-rPPG
Branch: master
Commit: fe9d526fdd9c
Files: 13
Total size: 55.8 KB

Directory structure:
gitextract_uds3fg4s/

├── LICENSE
├── README.md
├── data/
│   ├── __init__.py
│   ├── data_utils.py
│   ├── dataload.py
│   └── pre_dataload.py
├── model/
│   ├── __init__.py
│   ├── loss.py
│   ├── main_model.py
│   └── sub_model.py
├── requirements.txt
├── settings.py
└── train.py

================================================
FILE CONTENTS
================================================

================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2020 Eugene Lee

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
[![License CC BY-NC-SA 4.0](https://img.shields.io/badge/license-MIT-blue)](https://github.com/eugenelet/NeuralScale-Private/blob/master/LICENSE)
![Python 3.6](https://img.shields.io/badge/python-3.6-green.svg)

# Meta-rPPG: Remote Heart Rate Estimation Using a Transductive Meta-Learner

This repository is the official implementation of *Meta-rPPG: Remote Heart Rate Estimation Using a Transductive Meta-Learner* that has been accepted to ECCV 2020. 

<img src="rppg-overview.png" width="600">

## Heatmap Visualization

Left to right: 

1. Cropped input image
2. End-to-end trained model (baseline)
3. Meta-rPPG (transducive inference)
4. Top to down: rPPG signal, Power Spectral Density (PSD), Predicted and ground truth heart rate

<img src="demo1.gif" width="600">

<img src="demo2.gif" width="600">

<img src="demo3.gif" width="600">


## Requirements

To install requirements:

```setup
pip install -r requirements.txt
```

All experiments can be run on a single NVIDIA GTX1080Ti GPU.


The code was tested with python3.6 the following software versions:

| Software        | version | 
| ------------- |-------------| 
| cuDNN         | 7.6.5 |
| Pytorch      | 1.5.0  |
| CUDA | 10.2    |


## Training

### Training Data Preparation

Download training data ([example.pth](https://drive.google.com/file/d/1Z4GWiYjoQSXMYBhxBRZK9gUa1mYP0JsN/view?usp=sharing)) from Google Drive. Due to privacy issue (face images), provided data contains only a subset of the entire training data, i.e. contains faces of the authors of this paper.

Move `example.pth` to `data/` directory:
```
mv example.pth data/
```

### Begin Training

To begin training, run:

```
python3 train.py
```


## Validation Data

Validation data can be requested from:

[MAHNOB-HCI](https://mahnob-db.eu/hci-tagging/)

[UBFC-rPPG](https://sites.google.com/view/ybenezeth/ubfcrppg)



## Contributing

If you find this work useful, consider citing our work using the following bibTex:
```
@inproceedings{lee2020meta,
  title={Meta-rPPG: Remote Heart Rate Estimation Using a Transductive Meta-Learner},
  author={Lee, Eugene and Chen, Evan and Lee, Chen-Yi},
  booktitle={European Conference on Computer Vision (ECCV)},
  year={2020}
}
```


================================================
FILE: data/__init__.py
================================================
"""This package includes all the modules related to data loading and preprocessing

 To add a custom dataset class called 'dummy', you need to add a file called 'dummy_dataset.py' and define a subclass 'DummyDataset' inherited from BaseDataset.
 You need to implement four functions:
    -- <__init__>:                      initialize the class, first call BaseDataset.__init__(self, opt).
    -- <__len__>:                       return the size of dataset.
    -- <__getitem__>:                   get a data point from data loader.
    -- <modify_commandline_options>:    (optionally) add dataset-specific options and set default options.

Now you can use the dataset class by specifying flag '--dataset_mode dummy'.
See our template dataset class 'template_dataset.py' for more details.
"""
from .data_utils import testing
from .dataload import SlideWindowDataLoader


================================================
FILE: data/data_utils.py
================================================
from __future__ import print_function
import numpy as np
import os
import matplotlib.pyplot as plt
import pickle
import itertools
import torch
from scipy import signal
from scipy.signal import butter, lfilter

class FunctionSet():
   def __init__(self, sample_rate=30.0, display_port=8093):
      self.fps = sample_rate

   def CHROM_method(self, data):
      '''CHROM matrix'''
      project_matrix = np.array([[3, -2, 0], [1.5, 1, -1.5]]) 
      frames = data['frame'].copy()
      mask = data['mask'].copy()
      mask /= 255
      mask = mask.astype(float)

      rgb_mean = self.spatial_mean(frames, mask)
      rgb_mean = rgb_mean.transpose()
      rgb_mean = rgb_mean[[2, 1, 0], :]
      
      win_size = rgb_mean.shape[1]
      C_norm = np.zeros([3, win_size])
      for i in range(win_size):
         C_norm[:, i] = rgb_mean[:, i] / np.mean(rgb_mean, axis=1)
      S = np.matmul(project_matrix, C_norm)
      S1 = S[0,:]
      S2 = S[1,:]
      alpha = np.std(S1)/np.std(S2)
      h = S1 + alpha*S2  # POS
      h = butter_bandpass_filter(h, 0.4, 5, self.fps, order=6)

      return h - np.mean(h)



   def POS_method(self, data):
      '''POS matrix'''
      project_matrix = np.array([[0, 1, -1], [-2, 1, 1]]) 
      frames = data['frame'].copy()
      mask = data['mask'].copy()
      mask /= 255
      mask = mask.astype(float)

      rgb_mean = self.spatial_mean(frames, mask)
      rgb_mean = rgb_mean.transpose()
      rgb_mean = rgb_mean[[2, 1, 0], :]
      
      win_size = rgb_mean.shape[1]
      C_norm = np.zeros([3, win_size])
      for i in range(win_size):
         C_norm[:, i] = rgb_mean[:, i] / np.mean(rgb_mean, axis=1)
      S = np.matmul(project_matrix, C_norm)
      S1 = S[0,:]
      S2 = S[1,:]
      alpha = np.std(S1)/np.std(S2)
      h = S1 + alpha*S2  # POS
      h = butter_bandpass_filter(h, 0.4, 5, self.fps, order=6)
      return h - np.mean(h)
   
   def spatial_mean(self, frames, mask):
      t0 = np.sum(frames, axis=(0, 2, 3))
      t1 = np.sum(mask, axis=(0,2,3))

      mean = t0/t1
      return mean
      # pdb.set_trace()


def butter_bandpass(lowcut, highcut, fs, order=5):
   nyq = 0.5 * fs
   low = lowcut / nyq
   high = highcut / nyq
   b, a = butter(order, [low, high], btype='band')
   return b, a


def butter_bandpass_filter(data, lowcut, highcut, fs, order=5):
   b, a = butter_bandpass(lowcut, highcut, fs, order=order)
   # y = lfilter(b, a, data)
   y = signal.filtfilt(b, a, data, method="pad")
   return y


def normed(a):
   amin, amax = np.min(a), np.max(a)
   t = a.copy()
   # pdb.set_trace()
   for i in range(a.shape[0]):
      t[i] = (a[i]-amin) / (amax-amin)
   return t


def testing(opt, model, testset, data_idx, epoch):
   results, true_rPPG = model.get_current_results(0)
   loss = model.get_current_losses(0)
   test_data = testset[0, 0]

   # model.eval() rnn can't be adapted in eval mode
   model.set_input(test_data)
   model.fewshot_test(epoch)

   t_results, t_true_rPPG = model.get_current_results(1)
   test_loss = model.get_current_losses(1)

   model.train()

   return loss[2], test_loss


def amp_equalize(sig):
   # sig = Sig.clone()
   mean = sig.mean()
   min = sig.min()
   max = sig.max()
   ans = (sig - mean)/(max-min)*10
   yhat = torch.from_numpy(signal.savgol_filter(ans, 11, 5))
   # pdb.set_trace()
   return yhat

         
def get_bpm(Sig, rate= 30.0):
   sig = Sig.copy()
   n = len(sig)
   # print(n)
   fps = rate

   win = signal.hann(sig.size)
   sig = sig - np.expand_dims(np.mean(sig, -1), -1)
   sig = sig * win

   filtered_sig = butter_bandpass_filter(sig, 0.4, 4, fps, order=3)

   f, Pxx_den = signal.welch(sig, fps, nperseg=n)
   index = np.argmax(Pxx_den)
   HR_estimate = round(f[index]*60.0)

   return HR_estimate


================================================
FILE: data/dataload.py
================================================
import torch
from data.pre_dataload import BaselineDataset
# from Visualize.visualizer import Visualizer
import random

from scipy import signal
import numpy as np
import pdb
# pdb.set_trace()

class SlideWindowDataLoader():
   """Wrapper class of Dataset class that performs multi-threaded data loading.
      The class is only a container of the dataset.

      There are two ways to get a data out of the Loader. 

         1) feed in a list of videos: input = dataset[[0,3,5,10], 2020]. This gets the data starting at 2020 frame from 0, 3, 5, 10th video.
         2) feed a single value of videos:  input = dataset[0, 2020]. This gets a batch of data starting at 2020 from the 0th video.
   """

   def __init__(self, opt, isTrain):
      """Initialize this class
      """
      # self.visualizer = Visualizer(opt, isTrain=True)
      # self.visualizer.reset()
      self.opt = opt
      self.isTrain = isTrain

      self.dataset = BaselineDataset(opt, isTrain)
      if self.isTrain:
         print("dataset [%s-%s] was created" % ('rPPGDataset', 'train'))
      else:
         print("dataset [%s-%s] was created" % ('rPPGDataset', 'test'))
      self.length = int(len(self.dataset))

      self.num_tasks = self.dataset.num_tasks
      self.task_len = self.dataset.task_len

   def load_data(self):
      return self

   def __len__(self):
      """Return the number of data in the dataset"""
      return self.length

   def __getitem__(self, items):
      """Return a batch of data
         items -- [task_num, index of data for specified task]
      """

      inputs = []
      ppg = []
      frame = []
      mask = []

      if self.isTrain:
         batch = self.opt.batch_size
      else:
         batch = self.opt.batch_size + self.opt.fewshots

      if not isinstance(items[0], list):
         for i in range(batch):
            dat = self.dataset[items[0], items[1]+60*i]
            inputs.append(dat['input'])
            ppg.append(dat['PPG'])
      else:
         for idx in items[0]:
            dat = self.dataset[idx, items[1]]
            inputs.append(dat['input'])
            ppg.append(dat['PPG'])

         # pdb.set_trace()

      inputs = torch.stack(inputs)
      ppg = torch.stack(ppg)
      return {'input': inputs, 'rPPG': ppg}


   def quantify(self,rppg):
      quantified = torch.empty(rppg.shape[0], dtype=torch.long)
      binary = torch.ones(rppg.shape[0], dtype=torch.long)
      tmax = rppg.max()
      tmin = rppg.min()
      interval = (tmax - tmin)/39
      for i in range(len(quantified)):
         quantified[i] = ((rppg[i] - tmin)/interval).round().long()
      return quantified

   def __call__(self):
      output_list = []
      for idx in range(self.num_tasks):
         tmp = self.dataset(idx)
         tmp['rPPG'] = tmp.pop('PPG')
         output_list.append(tmp)
      return output_list
      # pdb.set_trace()



================================================
FILE: data/pre_dataload.py
================================================
from __future__ import print_function
import torch
import os
# import pickle
import numpy as np
import sys

from sklearn.preprocessing import normalize
from scipy import signal
import matplotlib.pyplot as plt
from scipy.signal import butter, lfilter
from data.data_utils import butter_bandpass_filter
import pdb



class BaselineDataset():
   """Preprocessing class of Dataset class that performs multi-threaded data loading

   """
   def __init__(self, opt, isTrain):
      """Initialize this dataset class.

      Parameters:
         opt (Option class) -- stores all the experiment flags; needs to be a subclass of BaseOptions

         The self.dataset is a list of facial data, the length of the list is 18, and each element is a torch tensor of shape [2852, 3, 64, 64]
         The self.maskset is the corresponding mask data, constructed of 0 and 255, so it determines the landmarks we're using in self.dataset  

      """
      # get the image directory

      self.isTrain = isTrain
      self.opt = opt

      temp_data = torch.load('data/example.pth')
      if self.isTrain:
         self.maskset = temp_data['mask'][:5]
         self.dataset = temp_data['image'][:5]
         self.ppg_dataset = temp_data['ppg'][:5]
         self.num_tasks = len(self.dataset)
         self.task_len = [self.dataset[i].shape[0]
                          for i in range(len(self.dataset))]
      else:
         self.maskset = temp_data['mask'][-1:]
         self.dataset = temp_data['image'][-1:]
         self.ppg_dataset = temp_data['ppg'][-1:]
         self.num_tasks = 1
         self.task_len = self.dataset[0].shape[0]
         # pdb.set_trace()

      self.length = 0
      for i in range(len(self.ppg_dataset)):
         self.length += self.ppg_dataset[i].shape[0] - self.opt.win_size


   def __getitem__(self, items):
      """Return a data point and its metadata information.

      Parameters:
         items -- [task_number, index of data for specified task]
         items[0] -- a integer in range 0 to 4 in train mode, only 0 available in test mode
         items[1] -- determined by the length of the video

      Returns a dictionary that contains input, PPG, diff and orig
         input - - a set of frames from the pickle file (60 x 3 x 64 x 64)
         PPG - - the corresponding signal (60)
      """

      inputs = []
      masks = []
      if not self.isTrain:
         # pdb.set_trace()
         for i in range(items[1], items[1] + self.opt.win_size):
            frame = self.dataset[items[0]][i].clone()
            mask = self.maskset[items[0]][i].clone()
            inputs.append(frame)
            masks.append(mask)
         ppg = self.ppg_dataset[items[0]][items[1]: items[1] + self.opt.win_size].clone()
      else:
         for i in range(items[1], items[1] + self.opt.win_size):
            frame = self.dataset[items[0]][i].clone()
            mask = self.maskset[items[0]][i].clone()
            inputs.append(frame)
            masks.append(mask)
         ppg = self.ppg_dataset[items[0]][items[1]
            : items[1] + self.opt.win_size].clone()


     

      inputs = np.stack(inputs)
      inputs = torch.from_numpy(inputs)
      masks = np.stack(masks)
      masks = torch.from_numpy(masks)

      self.baseline_procress(inputs, masks.clone())
      ppg = self.quantify(ppg)

      return {'input': inputs, 'PPG': ppg}

   def __len__(self):
      """Return the total number of images in the dataset."""

      return self.length

   def quantify(self, rppg):
      quantified = torch.empty(rppg.shape[0], dtype=torch.long)

      tmax = rppg.max()
      tmin = rppg.min()
      interval = (tmax - tmin)/39
      for i in range(len(quantified)):
         quantified[i] = ((rppg[i] - tmin)/interval).round().long()

      return quantified
   
   def baseline_procress(self, data, mask):

      mask /= 255
      mask = mask.float()

      # pdb.set_trace()
      input_mean = data.sum(dim=(0, 2, 3), keepdim=False) / \
          mask.sum(dim=(0, 2, 3), keepdim=False)  # mean of W H T
      for i in range(data.shape[1]):
         data[:, i, :, :] = data[:, i, :, :] - input_mean[i]  # minus the total mean
      data = data*mask
      
      x_hat = data.sum(dim=(2, 3), keepdim=False)/ \
               mask.sum(dim=(2, 3), keepdim=False)  # mean of H T
      G_x = np.empty(x_hat.size())  # filtered x_hat

      for i in range(data.shape[1]):  # shape 1 is RGB channels
         # pdb.set_trace()
         G_x[:, i] = butter_bandpass_filter(x_hat[:, i], 1, 8, 30, order=3)
         for j in range(data.shape[0]):
            data[j, i, :, :] = data[j, i, :, :] - \
                  (x_hat[j, i] - G_x[j, i])
      data = data*mask
      # pdb.set_trace()
      return data

   def __call__(self, idx):
      inputs = []
      masks = []
      items = [idx, 0]

      if not self.isTrain:
         # pdb.set_trace()
         decision = 0
         new_index = items[1] % (
             self.task_len - (self.opt.batch_size + self.opt.fewshots)*self.opt.win_size)
         for i in range(new_index, new_index + 15*self.opt.win_size):
            frame = self.dataset[items[0]][i].clone()
            mask = self.maskset[items[0]][i].clone()
            inputs.append(frame)
            masks.append(mask)
         ppg = self.ppg_dataset[items[0]
                                ][new_index: new_index + 15*self.opt.win_size].clone()
         orig = self.original[items[0]
                              ][new_index: new_index + 15*self.opt.win_size].clone()
      else:
         for i in range(items[1], items[1] + 15*self.opt.win_size):
            frame = self.dataset[items[0]][i].clone()
            mask = self.maskset[items[0]][i].clone()
            inputs.append(frame)
            masks.append(mask)
         ppg = self.ppg_dataset[items[0]][items[1]: items[1] + 15*self.opt.win_size].clone()
         orig = self.original[items[0]][items[1]: items[1] + 15*self.opt.win_size].clone()

      inputs = np.stack(inputs)
      inputs = torch.from_numpy(inputs)
      masks = np.stack(masks)
      masks = torch.from_numpy(masks)

      self.baseline_procress(inputs, masks.clone())
      ppg = self.quantify(ppg)

      return {'input': inputs, 'PPG': ppg}


================================================
FILE: model/__init__.py
================================================
"""This package includes all the modules related to data loading and preprocessing

 To add a custom dataset class called 'dummy', you need to add a file called 'dummy_dataset.py' and define a subclass 'DummyDataset' inherited from BaseDataset.
 You need to implement four functions:
    -- <__init__>:                      initialize the class, first call BaseDataset.__init__(self, opt).
    -- <__len__>:                       return the size of dataset.
    -- <__getitem__>:                   get a data point from data loader.
    -- <modify_commandline_options>:    (optionally) add dataset-specific options and set default options.

Now you can use the dataset class by specifying flag '--dataset_mode dummy'.
See our template dataset class 'template_dataset.py' for more details.
"""

from .main_model import meta_rPPG


================================================
FILE: model/loss.py
================================================
import torch
import numpy as np
import torch.nn as nn
from torch.nn import init
import torch.optim as optim
import os
from torch.autograd import Variable
from torch.nn.functional import conv1d

from scipy import signal
import torch.nn.functional as F
import pdb


class ordLoss(nn.Module):
   """
   Ordinal loss is defined as the average of pixelwise ordinal loss F(h, w, X, O)
   over the entire image domain:
   """

   def __init__(self):
      super(ordLoss, self).__init__()
      self.loss = 0.0

   def forward(self, orig_ord_labels, orig_target):
      """
      :param ord_labels: ordinal labels for each position of Image I.
      :param target:     the ground_truth discreted using SID strategy.
      :return: ordinal loss
      """
      device = orig_ord_labels.device
      ord_labels = orig_ord_labels.clone()
      # ord_labels = ord_labels.unsqueeze(0)
      ord_labels = torch.transpose(ord_labels, 1, 2)

      N, C, W = ord_labels.size()
      ord_num = C 

      self.loss = 0.0

      # faster version
      if torch.cuda.is_available():
         K = torch.zeros((N, C, W), dtype=torch.int).to(device)
         for i in range(ord_num):
               K[:, i, :] = K[:, i, :] + i * \
                  torch.ones((N, W), dtype=torch.int).to(device)
      else:
         K = torch.zeros((N, C, W), dtype=torch.int)
         for i in range(ord_num):
               K[:, i, :] = K[:, i, :] + i * \
                  torch.ones((N, W), dtype=torch.int)
      # pdb.set_trace()

      # target = orig_target.clone().type(torch.DoubleTensor)
      if device == torch.device('cpu'):
         target = orig_target.clone().type(torch.IntTensor)
      else:
         target = orig_target.clone().type(torch.cuda.IntTensor)

      mask_0 = torch.zeros((N, C, W), dtype=torch.bool)
      mask_1 = torch.zeros((N, C, W), dtype=torch.bool)
      for i in range(N):
         mask_0[i] = (K[i] <= target[i]).detach()
         mask_1[i] = (K[i] > target[i]).detach()


      one = torch.ones(ord_labels[mask_1].size())
      if torch.cuda.is_available():
         one = one.to(device)

      self.loss += torch.sum(torch.log(torch.clamp(ord_labels[mask_0], min=1e-8, max=1e8))) \
         + torch.sum(torch.log(torch.clamp(one - ord_labels[mask_1], min=1e-8, max=1e8)))

      N = N * W
      self.loss /= (-N)  # negative
      # pdb.set_trace()
      return self.loss

class customLoss(nn.Module):
   """
   This customize loss is contained of Ordloss and MSELoss of the frequency magnitude
   """
   def __init__(self, device):
      super(customLoss, self).__init__()
      self.loss = 0.0
      self.ord = ordLoss()

      self.vis = Visdom(port=8093, env='main')

      # self.cross = torch.nn.CrossEntropyLoss()
      # self.cross = torch.nn.NLLLoss()
      # self.cross = torch.nn.MSELoss()

      self.reg = regressLoss()
      # self.weight = torch.autograd.Variable(torch.tensor(1.0), requires_grad=True).to(device)
      self.weight = nn.Linear(2,1).to(device)
      with torch.no_grad():
         self.weight.weight.copy_(torch.tensor([1.0,1.0]))
      pdb.set_trace()
      self.t = torch.tensor([2.0,2.0]).to(device)
      self.device = device

   def forward(self, predict, true_rPPG):

      self.loss1 = self.ord(predict[0], true_rPPG)
      self.true_fft = self.torch_style_fft(true_rPPG)  # (batch size x 60)
      self.predict_fft = self.torch_style_fft(predict[1])  # (batch size x 60)

      self.loss2 = self.reg(self.predict_fft, self.true_fft)
      if torch.isnan(self.loss2):
         pdb.set_trace()

      # self.loss = self.loss1 + self.weight * self.loss2
      # pdb.set_trace()
      self.t1 = self.weight(self.t)
      self.loss = self.weight(torch.stack([self.loss1, self.loss2]))
      pdb.set_trace()

      return self.loss
      # pdb.set_trace()

   def torch_style_fft(self, sig):
      # pdb.set_trace()
      S, _ = torch_welch(sig, fps = 30)

      return S


class regressLoss(nn.Module):
    def __init__(self):
        super(regressLoss, self).__init__()
        self.softmax = nn.Softmax(dim=1)
      #   self.weight = weight

    def forward(self, outputs, targets):

      preoutput = outputs.clone()
      if torch.isnan(preoutput.cpu().detach()).any():
         pdb.set_trace()
      # small_number = torch.tensor(1e-45).to(targets.get_device())
      targets = self.softmax(targets)
      outputs = self.softmax(outputs)
      if torch.isnan(outputs.cpu().detach()).any():
         pdb.set_trace()
      # outputs = outputs + small_number

      loss = -targets.float() * torch.log(outputs) 
      # if np.isnan(torch.mean(loss).cpu().detach().numpy()):
      #    pdb.set_trace()
      return torch.mean(loss)


class KLDivLoss(nn.Module):
    def __init__(self, reduction="mean"):
        super(KLDivLoss, self).__init__()
        self.criterion = torch.nn.KLDivLoss(reduction=reduction)
      #   self.weight = weight

    def forward(self, outputs, targets):
      out = outputs.clone()
      tar = targets.clone()
      out.uniform_(0, 1)
      tar.uniform_(0, 1)
      # loss = self.criterion(F.log_softmax(out, -1), tar)
      loss = self.criterion(F.log_softmax(outputs, dim=1), F.softmax(targets, dim=1))

      return loss


def torch_welch(sig, fps):
   nperseg = sig.size(1)
   nfft = sig.size(1)
   noverlap = nperseg//2
   # pdb.set_trace()

   sig = sig.type(torch.cuda.FloatTensor)
   win = torch.from_numpy(signal.hann(sig.size(1))).to(sig.get_device()).type(torch.cuda.FloatTensor)
   sig = sig.unsqueeze(1)
   # pdb.set_trace()

   '''detrend'''
   sig = sig - torch.from_numpy(np.expand_dims(np.mean(sig.detach().cpu().numpy(), -1), -1)).to(sig.get_device())
   sig = sig * win
   S = torch.rfft(sig, 1, normalized=True, onesided=True)
   S = torch.sqrt(S[..., 0]**2 + S[..., 1]**2)   
   freqs = torch.from_numpy(np.fft.rfftfreq(nfft, 1/float(fps)))

   S = S.squeeze(1)

   return S, freqs



================================================
FILE: model/main_model.py
================================================
import torch
import numpy as np
import torch.nn as nn
from torch.nn import init
import torch.optim as optim
import os
import itertools
from model.sub_model import rPPG_Estimator, Convolutional_Encoder, Synthetic_Gradient_Generator
from model.loss import ordLoss, KLDivLoss
from scipy import signal
import pickle
from data.data_utils import butter_bandpass_filter
import time
import pdb


class meta_rPPG(nn.Module):
   """
   You can name your own checkpoint directory (opt.checkpoints_dir).

   A_net refers to Conv_Encoder, B_net refers to rPPG_Estimator, Grad_net refers to Synth_Grad_Gen.
   The loading directory can be changed to opt.checkpoints_dir if some other checkpoints are in need.

   """

   def __init__(self, opt, isTrain, continue_train=False, norm_layer=nn.BatchNorm2d):
      """
      Attention_ResNet -- using EfficientNet with LSTM
      AttentionNet -- using a attention strcture without a LSTM
      """
      super(meta_rPPG, self).__init__()
      self.save_dir = os.path.join(os.getcwd(), opt.checkpoints_dir)
      self.load_dir = os.path.join(os.getcwd(), opt.checkpoints_dir)
      if os.path.exists(self.save_dir) == False:
         os.makedirs(self.save_dir)
      self.isTrain = isTrain
      self.opt = opt
      self.gpu_ids = opt.gpu_ids
      self.thres = 0.5
      self.continue_train = continue_train
      self.device = torch.device('cuda:{}'.format(self.gpu_ids[0])) if self.gpu_ids else torch.device('cpu') 
      # self.device = torch.device('cpu') 

      self.prototype = torch.zeros(120)
      self.h = torch.zeros(2*opt.lstm_num_layers, opt.batch_size, 60).to(self.device)
      self.c = torch.zeros(2*opt.lstm_num_layers, opt.batch_size, 60).to(self.device)

      self.A_net = Convolutional_Encoder(input_channel=3, isTrain=self.isTrain, device=self.device)

      self.B_net = rPPG_Estimator(input_channel=120, num_layers=opt.lstm_num_layers, 
            isTrain=self.isTrain, device=self.device, h=self.h, c=self.c)

      self.Grad_net = Synthetic_Gradient_Generator(input_channel=120, isTrain=self.isTrain, device=self.device)
      
      
      self.A_net.to(self.device)
      self.B_net.to(self.device)
      self.Grad_net.to(self.device)
      self.model = [self.A_net, self.B_net, self.Grad_net]
      self.fewloss = 0.0
      self.ordloss = 0.0
      self.gradloss = 0.0

      self.criterion1 = torch.nn.MSELoss()
      self.criterion2 = ordLoss()
      self.criterion3 = torch.nn.MSELoss()

      self.optimizerA = torch.optim.SGD(self.A_net.parameters(), opt.lr, momentum=0.9, weight_decay=5e-4)
      self.optimizerB = torch.optim.SGD(self.B_net.parameters(), opt.lr, momentum=0.9, weight_decay=5e-4)
      self.optimizerGrad = torch.optim.SGD(self.Grad_net.parameters(), opt.lr, momentum=0.9, weight_decay=5e-4)
      if self.opt.adapt_position == "extractor":
         self.optimizerPsi = torch.optim.SGD(self.A_net.parameters(), opt.lr*1e-2, momentum=0.9, weight_decay=5e-4)
      elif self.opt.adapt_position == "estimator":
         self.optimizerPsi = torch.optim.SGD(self.B_net.parameters(), opt.lr*1e-2, momentum=0.9, weight_decay=5e-4)
      elif self.opt.adapt_position == "both":
         self.optimizerPsi = torch.optim.SGD(itertools.chain(self.A_net.parameters(),
                           self.B_net.parameters()), opt.lr*1e-2, momentum=0.9, weight_decay=5e-4)

      self.schedulerA = optim.lr_scheduler.CosineAnnealingLR(self.optimizerA, T_max=5, eta_min=0.1*opt.lr)
      self.schedulerB = optim.lr_scheduler.CosineAnnealingLR(self.optimizerB, T_max=5, eta_min=0.1*opt.lr)
      self.schedulerGrad = optim.lr_scheduler.CosineAnnealingLR(self.optimizerGrad, T_max=5, eta_min=0.1*opt.lr)
      self.schedulerPsi = optim.lr_scheduler.CosineAnnealingLR(self.optimizerPsi, T_max=5, eta_min=0.1*1e-2*opt.lr)



      # pdb.set_trace()
   def print_networks(self, print_net):
      """Print the total number of parameters in the network and (if verbose) network architecture

      Parameters:
      verbose (bool) -- if verbose: print the network architecture
      """
      print('----------- Networks initialized -------------')
      num_params = 0
      for param in self.A_net.parameters():
         num_params += param.numel()
      for param in self.B_net.parameters():
         num_params += param.numel()
      for param in self.Grad_net.parameters():
         num_params += param.numel()
      if print_net:
         print(self.model)
      print('Total number of parameters : %.3f M' %
            (num_params / 1e6))
      # pdb.set_trace()
      print('---------------------end----------------------')

   def set_input(self, input):

      self.input = input['input']
      self.true_rPPG = input['rPPG']
      if 'center' in input:
         self.center = input['center']

   def set_input_for_test(self, input):
      self.input = input.to(self.device)
      # if self.opt.lstm_hc_usage:
      self.B_net.feed_hc([self.h, self.c])

   def forward(self, x):
      """Run forward pass; called by both functions <optimize_parameters> and <test>."""
      # if not self.opt.branch:
      self.inter = self.A_net(x)
      self.decision, self.predict = self.B_net(self.inter)
      if self.opt.adapt_position == "extractor":
         self.gradient = self.Grad_net(self.inter.detach())
      elif self.opt.adapt_position == "estimator":
         self.gradient = self.Grad_net(self.predict.detach())
      elif self.opt.adapt_position == "both":
         self.gradient1 = self.Grad_net(self.inter.detach())
         self.gradient2 = self.Grad_net(self.predict.detach())
   
   def new_theta_update(self, epoch):
      inter = self.A_net(self.input.to(self.device))
      decision, predict = self.B_net(inter)

      fewloss = self.criterion1(self.prototype.expand(self.opt.batch_size,60,120), inter)
      ordloss = self.criterion2(predict, self.true_rPPG.to(self.device))

      self.optimizerA.zero_grad()
      loss = fewloss + ordloss
      loss.backward()
      self.optimizerA.step()

      if self.opt.adapt_position == "extractor":
         for i in range(self.opt.fewshots):
            inter = self.A_net(self.input.to(self.device))
            decision, predict = self.B_net(inter)
            inter_grad = self.Grad_net(inter.detach())
            # self.optimizerA.zero_grad()
            self.optimizerPsi.zero_grad()
            grad = torch.autograd.grad(outputs=inter, inputs=self.A_net.parameters(),
                                       grad_outputs=inter_grad, create_graph=False, retain_graph=False)
            torch.autograd.backward(self.A_net.parameters(), grad_tensors=grad, retain_graph=False, create_graph=False)

            self.optimizerPsi.step()
         self.gradient = inter_grad.detach().clone()
      elif self.opt.adapt_position == "estimator":
         for i in range(self.opt.fewshots):
            inter = self.A_net(self.input.to(self.device))
            decision, predict = self.B_net(inter)
            predict_grad = self.Grad_net(predict.detach())
            # self.optimizerA.zero_grad()
            self.optimizerPsi.zero_grad()
            grad = torch.autograd.grad(outputs=predict, inputs=self.B_net.parameters(),
                                       grad_outputs=predict_grad, create_graph=False, retain_graph=False)
            torch.autograd.backward(self.B_net.parameters(), grad_tensors=grad, retain_graph=False, create_graph=False)
            self.optimizerPsi.step()
         self.gradient = predict_grad.detach().clone()

      elif self.opt.adapt_position == "both":
         for i in range(self.opt.fewshots):
            inter = self.A_net(self.input.to(self.device))
            decision, predict = self.B_net(inter)
            inter_grad = self.Grad_net(inter.detach())
            predict_grad = self.Grad_net(predict.detach())

            self.optimizerPsi.zero_grad()
            grad = torch.autograd.grad(outputs=inter, inputs=self.A_net.parameters(),
                                       grad_outputs=inter_grad, create_graph=False, retain_graph=False)
            torch.autograd.backward(self.A_net.parameters(), grad_tensors=grad, retain_graph=False, create_graph=False)

            grad = torch.autograd.grad(outputs=predict, inputs=self.B_net.parameters(),
                                       grad_outputs=predict_grad, create_graph=False, retain_graph=False)
            torch.autograd.backward(self.B_net.parameters(), grad_tensors=grad, retain_graph=False, create_graph=False)

            self.optimizerPsi.step()
         self.gradient = predict_grad.detach().clone()

      '''release the retained graph, free all the variables'''
      self.fewloss = fewloss.detach().clone()
      self.ordloss = ordloss.detach().clone()
      self.inter = inter.detach().clone()

   def new_psi_phi_update(self, epoch):
      if self.opt.adapt_position == "extractor":
         inter = self.A_net(self.input.to(self.device))
         decision, predict = self.B_net(inter)
         inter_grad = self.Grad_net(inter.detach())

         inter.retain_grad()
         ordloss = self.criterion2(predict, self.true_rPPG.to(self.device))
         fewloss = self.criterion1(self.prototype.expand(self.opt.batch_size,60,120), inter)
         loss = ordloss + fewloss

         self.optimizerB.zero_grad()
         self.optimizerA.zero_grad()
         loss.backward()
         self.optimizerA.step()
         self.optimizerB.step()

         # pdb.set_trace()
         gradloss = self.criterion3(inter_grad, inter.grad)
         self.optimizerGrad.zero_grad()
         gradloss.backward()
         self.optimizerGrad.step()
         self.gradloss = gradloss.detach().clone()

      elif self.opt.adapt_position == "estimator":
         inter = self.A_net(self.input.to(self.device))
         decision, predict = self.B_net(inter)
         predict_grad = self.Grad_net(predict.detach())

         predict.retain_grad()
         ordloss = self.criterion2(predict, self.true_rPPG.to(self.device))
         fewloss = self.criterion1(self.prototype.expand(
             self.opt.batch_size, 60, 120), inter)
         loss = ordloss + fewloss

         self.optimizerB.zero_grad()
         self.optimizerA.zero_grad()
         loss.backward()
         self.optimizerA.step()
         self.optimizerB.step()

         gradloss = self.criterion3(predict_grad, predict.grad)
         self.optimizerGrad.zero_grad()
         gradloss.backward()
         self.optimizerGrad.step()
         self.gradloss = gradloss.detach().clone()
      
      elif self.opt.adapt_position == "both":
         inter = self.A_net(self.input.to(self.device))
         decision, predict = self.B_net(inter)
         predict_grad = self.Grad_net(predict.detach())
         inter_grad = self.Grad_net(inter.detach())

         predict.retain_grad()
         inter.retain_grad()
         ordloss = self.criterion2(predict, self.true_rPPG.to(self.device))
         fewloss = self.criterion1(self.prototype.expand(
             self.opt.batch_size, 60, 120), inter)
         loss = ordloss + fewloss

         self.optimizerB.zero_grad()
         self.optimizerA.zero_grad()
         loss.backward()
         self.optimizerA.step()
         self.optimizerB.step()

         gradloss = self.criterion3(
               predict_grad, predict.grad) + self.criterion3(inter_grad, inter.grad)
         self.optimizerGrad.zero_grad()
         gradloss.backward()
         self.optimizerGrad.step()
         self.gradloss = gradloss.detach().clone()

      self.decision = decision.detach().clone()
      self.predict = predict.detach().clone()
      self.ordloss = ordloss.detach().clone()


   def update_prototype(self):
      proto_tmp = torch.zeros(120).to(self.device)
      h_tmp = torch.zeros(2*self.opt.lstm_num_layers, self.opt.batch_size, 60).to(self.device)
      c_tmp = torch.zeros(2*self.opt.lstm_num_layers, self.opt.batch_size, 60).to(self.device)
      self.B_net.feed_hc([self.h, self.c])
      # pdb.set_trace()

      self.forward(self.input.to(self.device))
      # pdb.set_trace()
      proto_tmp += self.inter.data.mean(axis=[0,1])
      h_tmp += self.B_net.h.data
      c_tmp += self.B_net.c.data

      if torch.sum(self.prototype) == 0: # first update
         self.prototype = proto_tmp
         (self.h, self.c) = (h_tmp, c_tmp)
      else:
         self.prototype = 0.8*self.prototype + 0.2*proto_tmp
         (self.h, self.c) = (0.8*self.h + 0.2*h_tmp, 0.8*self.c + 0.2*c_tmp)



   def setup(self, opt):
      self.init_weights(self.A_net, self.B_net)
      # pdb.set_trace()
      if self.continue_train:
         self.load_networks(opt.load_file)
         self.thres = 0.01
      if not self.isTrain:
         # load_suffix = 'latest'
         # load_suffix = 'iter_%d' % opt.load_iter if opt.load_iter > 0 else opt.epoch
         self.load_networks(opt.load_file)
         # self.progress = 1.45
      # pdb.set_trace()
      self.print_networks(opt.print_net)

   def init_weights(net1, net2, init_type='normal', init_gain=0.02):
      net1.apply(init_func)
      net2.apply(init_func)

   def save_networks(self, suffix):
      """Save all the networks to the disk.

      Parameters:
         epoch (int) -- current epoch; used in the file name '%s_%s.pth' % (epoch, name)
      """
      save_filename1 = '%s_%s.pth' % (suffix, self.opt.name)
      save_path1 = os.path.join(self.save_dir, save_filename1)
      # pdb.set_trace()
      torch.save({'A': self.A_net.state_dict(), 
                  'B': self.B_net.state_dict(),
                  'Grad': self.Grad_net.state_dict(),
                  'proto': self.prototype.cpu(),
                  'h': self.h.data.cpu(), 
                  'c': self.c.data.cpu()},
                   save_path1)


   def get_current_losses(self, istest):
      # return [self.criterion.loss1.clone(), self.criterion.loss2.clone()]
      if istest:
         return self.t_ordloss
      else:
         return [self.fewloss, self.gradloss, self.ordloss]

   def eval(self):
      """Make models eval mode during test time"""
      self.A_net.eval()
      self.B_net.eval()
      self.Grad_net.eval()
      # self.attention.eval()
      # self.lstm.eval()
      # self.fc.eval()

   def train(self):
      """Make models train mode after test time"""
      self.A_net.train()
      self.B_net.train()
      self.Grad_net.train()
      # self.attention.train()
      # self.lstm.train()
      # self.fc.train()

   def test(self):
      """Forward function used in test time. """
      with torch.no_grad():
         self.forward(self.input[len(self.input)-1].unsqueeze(0).to(self.device))

      self.t_ordloss = self.criterion2(self.predict, self.true_rPPG[len(self.true_rPPG)-1].unsqueeze(0).to(self.device))

   def fewshot_test(self, epoch):
      A = pickle.loads(pickle.dumps(self.A_net))
      optim = torch.optim.SGD(A.parameters(), self.opt.lr*1e-2, momentum=0.9, weight_decay=5e-4)
      
      for i in range(self.opt.fewshots):
         optim.zero_grad()
         inter = A(self.input[i].unsqueeze(0).to(self.device))
         inter_grad = self.Grad_net(inter)
         grad = torch.autograd.grad(outputs=inter, inputs=A.parameters(),
                     grad_outputs=inter_grad, create_graph=False, retain_graph=False)
         torch.autograd.backward(A.parameters(), grad_tensors=grad, retain_graph=False, create_graph=False)
         optim.step()

      for i in range(self.opt.fewshots):
         optim.zero_grad()
         inter = A(self.input[i].unsqueeze(0).to(self.device))
         loss = self.criterion1(inter, self.prototype.expand(1, 60, 120))
         loss.backward()
         optim.step()

      with torch.no_grad():
         tmp_h = self.B_net.h
         tmp_c = self.B_net.c
         # if self.opt.lstm_hc_usage:
         self.B_net.feed_hc([self.h, self.c])

         data = self.input[self.opt.fewshots:]
         inter = A(data.to(self.device))
         self.decision, self.predict = self.B_net(inter)
         self.B_net.feed_hc([tmp_h, tmp_c])

      self.t_ordloss = self.criterion2(self.predict[0].unsqueeze(0), self.true_rPPG[0].unsqueeze(0).to(self.device))


   def get_current_results(self, istest):
      if istest:
         return self.decision[-1].cpu().clone(), self.true_rPPG[-1].cpu().clone()
      else:
         return self.decision[-1].cpu().clone(), self.true_rPPG[-1].cpu().clone()
         # return self.decision[0].cpu().clone(), self.true_rPPG[len(self.input)-1][0].cpu().clone()
         
   # def get_freq_results(self):
   #    return self.criterion.true_fft[0].cpu().clone(), self.criterion.predict_fft[0].detach().cpu().clone()

   def get_current_results_of_test(self):
      # pdb.set_trace()
      return self.decision[0].cpu().clone()

   def load_networks(self, suffix):
      """Load all the networks from the disk.

      Parameters:
         suffix (str) -- current epoch; used in the file name '%s_%s.pth' % (suffix, name)
      """

      load_filename1 = '%s_%s.pth' % (suffix, self.opt.name)
      load_path1 = os.path.join(self.load_dir, load_filename1)

      print('loading model from %s' % load_path1)
      model_dict = torch.load(load_path1)
      self.A_net.load_state_dict(model_dict['A'])
      self.B_net.load_state_dict(model_dict['B'])
      self.Grad_net.load_state_dict(model_dict['Grad'])

      self.prototype = model_dict['proto'].to(self.device)
      self.h = model_dict['h'].to(self.device)
      self.c = model_dict['c'].to(self.device)

      # self.A_net.eval()
      # self.B_net.eval()
      # self.Grad_net.eval()

      

      


   def __patch_instance_norm_state_dict(self, state_dict, module, keys, i=0):
      """Fix InstanceNorm checkpoints incompatibility (prior to 0.4)"""
      key = keys[i]
      if i + 1 == len(keys):  # at the end, pointing to a parameter/buffer
         if module.__class__.__name__.startswith('InstanceNorm') and \
                 (key == 'running_mean' or key == 'running_var'):
            if getattr(module, key) is None:
               state_dict.pop('.'.join(keys))
         if module.__class__.__name__.startswith('InstanceNorm') and \
            (key == 'num_batches_tracked'):
               state_dict.pop('.'.join(keys))
      else:
         self.__patch_instance_norm_state_dict(
             state_dict, getattr(module, key), keys, i + 1)

   def get_param(self):
      return [self.A_net.get_param(), self.B_net.get_param()]

   def update_learning_rate(self, epoch):
      """Update learning rates for all the networks; called at the end of every epoch"""

      self.schedulerA.step()
      self.schedulerB.step()
      self.schedulerGrad.step()
      self.schedulerPsi.step()
      

      # pdb.set_trace()
      lr = self.optimizerB.param_groups[0]['lr']
      return lr
      # print('\nlearning rate = %.7f' % lr)

def init_func(m):  # define the initialization function
   classname = m.__class__.__name__
   if hasattr(m, 'weight') and (classname.find('Conv') != -1 or classname.find('Linear') != -1):
      init.normal_(m.weight.data, 0.0, 0.02)
   # if hasattr(m, 'bias') and m.bias is not None:
   #    init.constant_(m.bias.data, 0.0)
   # BatchNorm Layer's weight is not a matrix; only normal distribution applies.
   elif classname.find('BatchNorm2d') != -1:
      init.normal_(m.weight.data, 1.0, 0.02)
      init.constant_(m.bias.data, 0.0)



================================================
FILE: model/sub_model.py
================================================
import torch
import numpy as np
import torch.nn as nn
from torch.nn import init
import torch.optim as optim
import os
import math
# from model.sub_models import ResNet, BasicBlock
# from model.sub_models import OrdinalRegressionLayer
import itertools
from collections import OrderedDict
import torch.nn.functional as F

import pdb



class Synthetic_Gradient_Generator(nn.Module):
   def __init__(self, input_channel, isTrain, device):
      super(Synthetic_Gradient_Generator, self).__init__()
      self.layer1 = nn.Sequential(
          nn.Conv1d(60, 40, kernel_size=3, padding=1),
          nn.BatchNorm1d(40),
          nn.ReLU()
      )
      self.layer2 = nn.Sequential(
          nn.Conv1d(40, 20, kernel_size=3, padding=1),
          nn.BatchNorm1d(20),
          nn.ReLU()
      )
      self.layer3 = nn.Sequential(
          nn.ConvTranspose1d(20, 40, kernel_size=3, padding=1),
          nn.BatchNorm1d(40),
          nn.ReLU()
      )
      self.layer4 = nn.Sequential(
          nn.ConvTranspose1d(40, 60, kernel_size=3, padding=1)
      )

   def forward(self, x): 
      # x's shape = [6, 60, 120]
      res_x1 = self.layer1(x)  # res_x1's shape = [6, 40, 120]
      res_x2 = self.layer2(res_x1)  # res_x2's shape = [6, 20, 120]
      res_x3 = self.layer3(res_x2) + res_x1 # res_x3's shape = [6, 40, 120]
      out = self.layer4(res_x3) # out's shape = [6, 60, 120]
      # pdb.set_trace()

      return out


class Convolutional_Encoder(nn.Module):
   def __init__(self, input_channel, isTrain, device):
      super(Convolutional_Encoder, self).__init__()
      self.conv = nn.Conv3d
      self.conv1 = self.conv(input_channel, 32, kernel_size=(1, 3, 3), stride=(1, 1, 1), padding=(0, 1, 1))
      self.conv2 = self.conv(32, 48, kernel_size=(1, 3, 3), stride=(1, 1, 1), padding=(0, 1, 1))
      self.conv3 = self.conv(48, 64, kernel_size=(1, 3, 3), stride=(1, 1, 1), padding=(0, 1, 1))
      self.conv4 = self.conv(64, 80, kernel_size=(1, 3, 3), stride=(1, 1, 1), padding=(0, 1, 1))
      self.conv5 = self.conv(80, 120, kernel_size=(1, 3, 3), stride=(1, 1, 1), padding=(0, 1, 1))

      self.bn1 = nn.BatchNorm3d(32)
      self.bn2 = nn.BatchNorm3d(48)
      self.bn3 = nn.BatchNorm3d(64)
      self.bn4 = nn.BatchNorm3d(80)
      self.bn5 = nn.BatchNorm3d(120)

      self.cnn = {'c1': self.conv1, 'c2': self.conv2, 'c3': self.conv3, 'c4': self.conv4,
                  'c5': self.conv5, 'b1': self.bn1, 'b2': self.bn2, 'b3': self.bn3, 
                  'b4': self.bn4, 'b5': self.bn5}

      self.relu = nn.ReLU(inplace=True)
   
   def forward(self, x):
      win_size = x.shape[1]
      x = x.permute(0, 2, 1, 3, 4)
      
      x = self.conv1(x)
      # pdb.set_trace()
      x = self.bn1(x)
      x = F.avg_pool3d(x,(1,2,2))
      x = self.relu(x)
      x = self.conv2(x)
      x = self.bn2(x)
      x = F.avg_pool3d(x,(1,2,2))
      x = self.relu(x)
      x = self.conv3(x)
      x = self.bn3(x)
      x = F.avg_pool3d(x,(1,2,2))
      x = self.relu(x)
      x = self.conv4(x)
      x = self.bn4(x)
      x = F.avg_pool3d(x,(1,2,2))
      x = self.relu(x)
      x = self.conv5(x)
      x = self.bn5(x)
      x = F.avg_pool3d(x,(1,2,2))
      x = self.relu(x)

      x = F.adaptive_avg_pool3d(x, (win_size, 1, 1))
      x = x.permute(0, 2, 1, 3, 4)
      x = x.reshape(x.size(0), x.size(1),  - 1)

      return x
   
   def return_grad(self):
      # pdb.set_trace()
      c1 = self.conv1.weight.grad.data.clone()
      c2 = self.conv2.weight.grad.data.clone()
      c3 = self.conv3.weight.grad.data.clone()
      c4 = self.conv4.weight.grad.data.clone()
      c5 = self.conv5.weight.grad.data.clone()
      b1 = self.bn1.weight.grad.data.clone()
      b2 = self.bn2.weight.grad.data.clone()
      b3 = self.bn3.weight.grad.data.clone()
      b4 = self.bn4.weight.grad.data.clone()
      b5 = self.bn5.weight.grad.data.clone()

      return {'c1': c1, 'c2': c2, 'c3': c3, 'c4': c4, 'c5': c5,
              'b1': b1, 'b2': b2, 'b3': b3, 'b4': b4, 'b5': b5}
   

class rPPG_Estimator(nn.Module):
   def __init__(self, input_channel, num_layers, isTrain, device, num_classes=40, h=None, c=None):
      super(rPPG_Estimator, self).__init__()
      self.lstm = nn.LSTM(input_size=120, hidden_size=60,
                          num_layers=num_layers, batch_first=True, bidirectional=True)
      self.fc = nn.Linear(120, 80)
      self.h, self.c = h, c
      self.orl = OrdinalRegressionLayer()
   
   def forward(self, x):
      self.lstm.flatten_parameters()
      # pdb.set_trace()
      if self.h is not None:
         x, (self.h, self.c) = self.lstm(x, (self.h.data, self.c.data))
      else:
         x, _ = self.lstm(x)
      # pdb.set_trace()

      x = self.fc(x)
      decision, prob = self.orl(x)
      decision = decision.squeeze(2)
      # pdb.set_trace()

      return decision, prob
   def feed_hc(self, data):
      # pdb.set_trace()
      self.h = data[0].data
      self.c = data[1].data
      # pdb.set_trace()

   def return_grad(self):
      fc_grad = self.fc.weight.grad.data.clone()
      lstm_list = self.lstm._all_weights
      lstm_dict = {}
      for sublist in lstm_list:
         for name in sublist:
            # pdb.set_trace()
            lstm_dict[name] = self.lstm._parameters[name].grad.data.clone()
      return {'fc': fc_grad, 'lstm': lstm_dict}



class OrdinalRegressionLayer(nn.Module):
   def __init__(self):
      super(OrdinalRegressionLayer, self).__init__()

   def forward(self, x):
      """
      :param x: N X H X W X C, N is batch_size, C is channels of features
      :return: ord_labels is ordinal outputs for each spatial locations , size is N x H X W X C (C = 2K, K is interval of SID)
               decode_label is the ordinal labels for each position of Image I
      """
      # pdb.set_trace()
      x = x.permute(0, 2, 1)
      N, C, W = x.size()
      # N, W, C = x.size()
      ord_num = C // 2

      """
      replace iter with matrix operation
      fast speed methods
      """
      A = x[:, ::2, :].clone()
      B = x[:, 1::2, :].clone()
      # pdb.set_trace()
      A = A.view(N, 1, ord_num * W)
      B = B.view(N, 1, ord_num * W)
      # pdb.set_trace()
      C = torch.cat((A, B), dim=1)
      C = torch.clamp(C, min=1e-8, max=1e8)  # prevent nans
      # pdb.set_trace()
      ord_c = nn.functional.softmax(C, dim=1)

      # pdb.set_trace()
      ord_c1 = ord_c[:, 1, :].clone()
      ord_c1 = ord_c1.view(-1, ord_num, W)
      decode_c = torch.sum((ord_c1 > 0.5), dim=1).view(-1, 1, W)
      ord_c1 = ord_c1.permute(0, 2, 1)
      decode_c = decode_c.permute(0, 2, 1)
      # pdb.set_trace()
      return decode_c, ord_c1

   
   




================================================
FILE: requirements.txt
================================================
tensorboardX
easydict
tqdm
bypy


================================================
FILE: settings.py
================================================
import argparse
import torch.nn as nn
import torch
from torch.optim import lr_scheduler
import numpy as np
import random

import pdb

class TrainOptions():
   def __init__(self):
      self.parser = argparse.ArgumentParser(
         formatter_class=argparse.ArgumentDefaultsHelpFormatter)
      self.parser.add_argument('--name', type=str, default='meta_rPPG')
      self.parser.add_argument('--network', type=str, default='MAML')
      self.parser.add_argument('--continue_train', action="store_true")
      self.parser.add_argument('--load_file', type=str, default='smallest')
      self.parser.add_argument("--delay", type=int, default=48)
      self.parser.add_argument('--fewshots', type=int, default=1)
      self.parser.add_argument('--lr_ratio', type=float, default=0.1)



      self.parser.add_argument('--per_iter_task', type=int, default=3)
      self.parser.add_argument('--lstm_num_layers', type=int, default=2)
      self.parser.add_argument('--valid_ratio', type=float, default=0.75)

      self.parser.add_argument('--batch_size', type=int, default=3)
      self.parser.add_argument('--lr', type=float, default=1e-3)
      self.parser.add_argument('--train_epoch', type=int, default=1)
      self.parser.add_argument('--gpu_ids', type=str, default='0')
      self.parser.add_argument('--print_net', action="store_true")
      self.parser.add_argument('--epoch_count', type=int, default=1)
      # self.parser.add_argument('--lr_policy', type=str, default='cosine')
      # self.parser.add_argument('--lr_decay_iters', type=int, default=1)
      # self.parser.add_argument('--lr_update_iter', type=int, default=5000)

      self.parser.add_argument('--print_freq', type=int, default=10)
      self.parser.add_argument('--save_latest_freq', type=int, default=100)
      self.parser.add_argument('--save_epoch_freq', type=int, default=50)
      self.parser.add_argument('--save_by_iter', action="store_true")


      self.parser.add_argument('--display_id', type=int, default=1)
      self.parser.add_argument(
         '--display_server', type=str, default="http://localhost")
      self.parser.add_argument('--display_env', type=str, default='main')
      self.parser.add_argument('--display_port', type=int, default=8800)
      self.parser.add_argument('--display_winsize', type=int, default=256)
      self.parser.add_argument('--verbose', type=bool, default=True)
      self.parser.add_argument('--no_html', type=bool, default=True)
      self.parser.add_argument(
         '--checkpoints_dir', type=str, default='checkpoints')
      self.parser.add_argument('--save_dir', type=str, default='save')
      self.parser.add_argument('--max_dataset_size',type=int, default=float("inf"))

      self.parser.add_argument('--num_threads', type=int, default=4)
      self.parser.add_argument('--phase', type=str, default='train')

      self.parser.add_argument('--load_iter', type=int, default='0')
      self.parser.add_argument('--epoch', type=str, default='latest')
      self.parser.add_argument('--win_size', type=int, default=60)
      self.parser.add_argument('--adapt_position', type=str, default="extractor")

   def get_options(self):
      return self.parser.parse_args()
   
   def get_parser(self):
      return self.parser



class custom_scheduler():
   def __init__(self, optimizer, Tmax):
      self.optimizer = optimizer
      self.Tmax = Tmax
      self.Max = optimizer.param_groups[0]['lr']
      self.Min = self.Max*0.01
      self.Tcur = 1

   def step(self):
      pi = torch.Tensor([np.pi])
      for param_group in self.optimizer.param_groups:
         param_group['lr'] = float(self.Min + 0.5*(self.Max - self.Min)*(1 + torch.cos(pi*self.Tcur/self.Tmax)))

      if self.Tcur == 10 or self.Tcur == 30 or self.Tcur == 50 or self.Tcur == 70 or self.Tcur == 90:
         self.Max = 10*self.optimizer.param_groups[0]['lr']
      elif self.Tcur == 20 or self.Tcur == 40 or self.Tcur == 60 or self.Tcur == 80 or self.Tcur == 100:
         self.Min = 0.01*self.optimizer.param_groups[0]['lr']
      self.Tcur += 1








================================================
FILE: train.py
================================================
import torch
import torch.nn as nn
import torch.utils.data as Data
import numpy as np
import time
import os
import random
import matplotlib.pyplot as plt
from data import SlideWindowDataLoader, testing
from model import meta_rPPG
from settings import TrainOptions

import pdb


opt = TrainOptions().get_options()
iter_num = opt.batch_size

model = meta_rPPG(opt, isTrain=True, continue_train=opt.continue_train)
model.setup(opt)

dataset = SlideWindowDataLoader(opt, isTrain=True)
testset = SlideWindowDataLoader(opt, isTrain=False)

per_idx = opt.per_iter_task
dataset_size = dataset.num_tasks * (dataset.task_len[0] - (opt.win_size))
task_len = (dataset.task_len[0] - per_idx*opt.win_size)


total_iters = 0

print("Data Size: %d ||||| Batch Size: %d ||||| initial lr: %f" %
      (dataset_size, opt.batch_size, opt.lr))
# pdb.set_trace()

task_list = random.sample(range(5), opt.batch_size)
model.dataset = dataset
data = dataset[task_list, 0]
# pdb.set_trace()
model.set_input(data)
model.update_prototype()
min_mae = [10, 10]
min_rmse = [10, 10]
min_merate = [10, 10]
saving = 1


for epoch in range(opt.epoch_count, opt.train_epoch + 1):
   epoch_start_time = time.time()
   epoch_iter = 0
   i = 0
   
   

   for data_idx in range(0, task_len, 1):
      task_list = random.sample(range(5), opt.batch_size)
      model.B_net.feed_hc([model.h, model.c])

      model.progress = epoch + float(data_idx)/float(task_len)


      for i in range(per_idx):
         # pdb.set_trace()
         data = dataset[task_list, data_idx + i*opt.win_size]
         iter_start_time = time.time()
         total_iters += opt.win_size
         model.set_input(data)
         if i == 0:
            model.new_theta_update(epoch) # Adaptation phase
         else:
            model.new_psi_phi_update(epoch) # Learning phase
      # pdb.set_trace()
      loss, test_loss = testing(opt, model, testset, data_idx, epoch)
      
      epoch_iter += 1
      data = dataset[task_list, np.random.randint(task_len)]
      model.set_input(data)
      model.update_prototype()


   model.save_networks('latest')
   model.save_networks(epoch)

   # pdb.set_trace()
   new_lr = model.update_learning_rate(epoch)
   print('Epoch %d/%d ||||| Time: %d sec ||||| Lr: %.7f ||||| Loss: %.3f/%.3f' %
         (epoch, opt.train_epoch, time.time() - epoch_start_time, new_lr,
          loss, test_loss))
Download .txt
gitextract_uds3fg4s/

├── LICENSE
├── README.md
├── data/
│   ├── __init__.py
│   ├── data_utils.py
│   ├── dataload.py
│   └── pre_dataload.py
├── model/
│   ├── __init__.py
│   ├── loss.py
│   ├── main_model.py
│   └── sub_model.py
├── requirements.txt
├── settings.py
└── train.py
Download .txt
SYMBOL INDEX (85 symbols across 7 files)

FILE: data/data_utils.py
  class FunctionSet (line 11) | class FunctionSet():
    method __init__ (line 12) | def __init__(self, sample_rate=30.0, display_port=8093):
    method CHROM_method (line 15) | def CHROM_method(self, data):
    method POS_method (line 42) | def POS_method(self, data):
    method spatial_mean (line 66) | def spatial_mean(self, frames, mask):
  function butter_bandpass (line 75) | def butter_bandpass(lowcut, highcut, fs, order=5):
  function butter_bandpass_filter (line 83) | def butter_bandpass_filter(data, lowcut, highcut, fs, order=5):
  function normed (line 90) | def normed(a):
  function testing (line 99) | def testing(opt, model, testset, data_idx, epoch):
  function amp_equalize (line 116) | def amp_equalize(sig):
  function get_bpm (line 127) | def get_bpm(Sig, rate= 30.0):

FILE: data/dataload.py
  class SlideWindowDataLoader (line 11) | class SlideWindowDataLoader():
    method __init__ (line 21) | def __init__(self, opt, isTrain):
    method load_data (line 39) | def load_data(self):
    method __len__ (line 42) | def __len__(self):
    method __getitem__ (line 46) | def __getitem__(self, items):
    method quantify (line 79) | def quantify(self,rppg):
    method __call__ (line 89) | def __call__(self):

FILE: data/pre_dataload.py
  class BaselineDataset (line 17) | class BaselineDataset():
    method __init__ (line 21) | def __init__(self, opt, isTrain):
    method __getitem__ (line 57) | def __getitem__(self, items):
    method __len__ (line 102) | def __len__(self):
    method quantify (line 107) | def quantify(self, rppg):
    method baseline_procress (line 118) | def baseline_procress(self, data, mask):
    method __call__ (line 144) | def __call__(self, idx):

FILE: model/loss.py
  class ordLoss (line 15) | class ordLoss(nn.Module):
    method __init__ (line 21) | def __init__(self):
    method forward (line 25) | def forward(self, orig_ord_labels, orig_target):
  class customLoss (line 79) | class customLoss(nn.Module):
    method __init__ (line 83) | def __init__(self, device):
    method forward (line 103) | def forward(self, predict, true_rPPG):
    method torch_style_fft (line 122) | def torch_style_fft(self, sig):
  class regressLoss (line 129) | class regressLoss(nn.Module):
    method __init__ (line 130) | def __init__(self):
    method forward (line 135) | def forward(self, outputs, targets):
  class KLDivLoss (line 153) | class KLDivLoss(nn.Module):
    method __init__ (line 154) | def __init__(self, reduction="mean"):
    method forward (line 159) | def forward(self, outputs, targets):
  function torch_welch (line 170) | def torch_welch(sig, fps):

FILE: model/main_model.py
  class meta_rPPG (line 17) | class meta_rPPG(nn.Module):
    method __init__ (line 26) | def __init__(self, opt, isTrain, continue_train=False, norm_layer=nn.B...
    method print_networks (line 87) | def print_networks(self, print_net):
    method set_input (line 108) | def set_input(self, input):
    method set_input_for_test (line 115) | def set_input_for_test(self, input):
    method forward (line 120) | def forward(self, x):
    method new_theta_update (line 133) | def new_theta_update(self, epoch):
    method new_psi_phi_update (line 195) | def new_psi_phi_update(self, epoch):
    method update_prototype (line 273) | def update_prototype(self):
    method setup (line 295) | def setup(self, opt):
    method init_weights (line 309) | def init_weights(net1, net2, init_type='normal', init_gain=0.02):
    method save_networks (line 313) | def save_networks(self, suffix):
    method get_current_losses (line 331) | def get_current_losses(self, istest):
    method eval (line 338) | def eval(self):
    method train (line 347) | def train(self):
    method test (line 356) | def test(self):
    method fewshot_test (line 363) | def fewshot_test(self, epoch):
    method get_current_results (line 397) | def get_current_results(self, istest):
    method get_current_results_of_test (line 407) | def get_current_results_of_test(self):
    method load_networks (line 411) | def load_networks(self, suffix):
    method __patch_instance_norm_state_dict (line 440) | def __patch_instance_norm_state_dict(self, state_dict, module, keys, i...
    method get_param (line 455) | def get_param(self):
    method update_learning_rate (line 458) | def update_learning_rate(self, epoch):
  function init_func (line 472) | def init_func(m):  # define the initialization function

FILE: model/sub_model.py
  class Synthetic_Gradient_Generator (line 18) | class Synthetic_Gradient_Generator(nn.Module):
    method __init__ (line 19) | def __init__(self, input_channel, isTrain, device):
    method forward (line 40) | def forward(self, x):
  class Convolutional_Encoder (line 51) | class Convolutional_Encoder(nn.Module):
    method __init__ (line 52) | def __init__(self, input_channel, isTrain, device):
    method forward (line 73) | def forward(self, x):
    method return_grad (line 105) | def return_grad(self):
  class rPPG_Estimator (line 122) | class rPPG_Estimator(nn.Module):
    method __init__ (line 123) | def __init__(self, input_channel, num_layers, isTrain, device, num_cla...
    method forward (line 131) | def forward(self, x):
    method feed_hc (line 146) | def feed_hc(self, data):
    method return_grad (line 152) | def return_grad(self):
  class OrdinalRegressionLayer (line 164) | class OrdinalRegressionLayer(nn.Module):
    method __init__ (line 165) | def __init__(self):
    method forward (line 168) | def forward(self, x):

FILE: settings.py
  class TrainOptions (line 10) | class TrainOptions():
    method __init__ (line 11) | def __init__(self):
    method get_options (line 65) | def get_options(self):
    method get_parser (line 68) | def get_parser(self):
  class custom_scheduler (line 73) | class custom_scheduler():
    method __init__ (line 74) | def __init__(self, optimizer, Tmax):
    method step (line 81) | def step(self):
Condensed preview — 13 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (61K chars).
[
  {
    "path": "LICENSE",
    "chars": 1067,
    "preview": "MIT License\n\nCopyright (c) 2020 Eugene Lee\n\nPermission is hereby granted, free of charge, to any person obtaining a copy"
  },
  {
    "path": "README.md",
    "chars": 2211,
    "preview": "[![License CC BY-NC-SA 4.0](https://img.shields.io/badge/license-MIT-blue)](https://github.com/eugenelet/NeuralScale-Pri"
  },
  {
    "path": "data/__init__.py",
    "chars": 869,
    "preview": "\"\"\"This package includes all the modules related to data loading and preprocessing\n\n To add a custom dataset class calle"
  },
  {
    "path": "data/data_utils.py",
    "chars": 3740,
    "preview": "from __future__ import print_function\nimport numpy as np\nimport os\nimport matplotlib.pyplot as plt\nimport pickle\nimport "
  },
  {
    "path": "data/dataload.py",
    "chars": 2971,
    "preview": "import torch\r\nfrom data.pre_dataload import BaselineDataset\r\n# from Visualize.visualizer import Visualizer\r\nimport rando"
  },
  {
    "path": "data/pre_dataload.py",
    "chars": 6207,
    "preview": "from __future__ import print_function\nimport torch\nimport os\n# import pickle\nimport numpy as np\nimport sys\n\nfrom sklearn"
  },
  {
    "path": "model/__init__.py",
    "chars": 828,
    "preview": "\"\"\"This package includes all the modules related to data loading and preprocessing\n\n To add a custom dataset class calle"
  },
  {
    "path": "model/loss.py",
    "chars": 6064,
    "preview": "import torch\r\nimport numpy as np\r\nimport torch.nn as nn\r\nfrom torch.nn import init\r\nimport torch.optim as optim\r\nimport "
  },
  {
    "path": "model/main_model.py",
    "chars": 19769,
    "preview": "import torch\r\nimport numpy as np\r\nimport torch.nn as nn\r\nfrom torch.nn import init\r\nimport torch.optim as optim\r\nimport "
  },
  {
    "path": "model/sub_model.py",
    "chars": 6836,
    "preview": "import torch\r\nimport numpy as np\r\nimport torch.nn as nn\r\nfrom torch.nn import init\r\nimport torch.optim as optim\r\nimport "
  },
  {
    "path": "requirements.txt",
    "chars": 32,
    "preview": "tensorboardX\neasydict\ntqdm\nbypy\n"
  },
  {
    "path": "settings.py",
    "chars": 4147,
    "preview": "import argparse\r\nimport torch.nn as nn\r\nimport torch\r\nfrom torch.optim import lr_scheduler\r\nimport numpy as np\r\nimport r"
  },
  {
    "path": "train.py",
    "chars": 2369,
    "preview": "import torch\nimport torch.nn as nn\nimport torch.utils.data as Data\nimport numpy as np\nimport time\nimport os\nimport rando"
  }
]

About this extraction

This page contains the full source code of the eugenelet/Meta-rPPG GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 13 files (55.8 KB), approximately 15.0k tokens, and a symbol index with 85 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!