blinklinmult 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,12 @@
1
+ import toml
2
+ from os import PathLike
3
+ from pathlib import Path
4
+
5
+ # Module level constants
6
+ PROJECT_ROOT = Path(__file__).parents[1]
7
+ WEIGHTS_DIR = Path().home() / '.cache' / 'torch' / 'hub' / 'checkpoints' / 'blink'
8
+
9
+ __version__ = toml.load(PROJECT_ROOT / 'pyproject.toml')['project']['version']
10
+
11
+ # Type aliases
12
+ PathType = str | PathLike
@@ -0,0 +1,164 @@
1
+ from abc import abstractmethod
2
+ from pathlib import Path
3
+ import logging
4
+ import torch
5
+ import torch.nn as nn
6
+ from torchvision import models, transforms
7
+ from linmult import LinMulT, LinT
8
+ from exordium.utils.ckpt import download_file
9
+ from blinklinmult import PathType, WEIGHTS_DIR
10
+
11
+
12
+ logging.basicConfig(level=logging.INFO,
13
+ format="%(asctime)s %(levelname)s %(message)s",
14
+ datefmt="%Y-%m-%d %H:%M:%S")
15
+
16
+
17
+ PRETRAINED_WEIGHTS = {
18
+ 'densenet121-union': 'https://github.com/fodorad/LinMulT/releases/download/v1.0.0/densenet121-union-64.pt',
19
+ 'blinklint-union': 'https://github.com/fodorad/LinMulT/releases/download/v1.0.0/densenetlint-union-64.pt',
20
+ 'blinklinmult-union': 'https://github.com/fodorad/LinMulT/releases/download/v1.0.0/blinklinmult-union.pt'
21
+ }
22
+
23
+
24
+ preprocess_eye_fcn = transforms.Compose([
25
+ transforms.ToPILImage(),
26
+ transforms.Resize((64, 64), transforms.InterpolationMode.BICUBIC),
27
+ transforms.ToTensor(),
28
+ transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
29
+ ])
30
+
31
+
32
+ class AbstractModule(nn.Module):
33
+
34
+ def __init__(self):
35
+ super().__init__()
36
+ self.features = nn.Identity()
37
+ self.classifier = nn.Identity()
38
+
39
+ @staticmethod
40
+ def _init_weights(modules):
41
+ for m in modules:
42
+ if isinstance(m, (nn.Linear, nn.LazyLinear)):
43
+ nn.init.kaiming_uniform_(m.weight, mode="fan_in", nonlinearity="relu")
44
+ nn.init.zeros_(m.bias)
45
+
46
+ def freeze_feature_extractor(self):
47
+ for layer in self.features.parameters():
48
+ layer.requires_grad = False
49
+
50
+ def unfreeze_feature_extractor(self):
51
+ for layer in self.features.parameters():
52
+ layer.requires_grad = True
53
+
54
+ def forward(self, x):
55
+ x = self.features(x)
56
+ x = self.classifier(x)
57
+ return x
58
+
59
+ def load_weights(self, weights_path: PathType) -> None:
60
+ local_path = Path(weights_path)
61
+
62
+ if local_path.name in PRETRAINED_WEIGHTS.keys():
63
+ remote_path = PRETRAINED_WEIGHTS[local_path.name]
64
+ local_path = WEIGHTS_DIR / Path(remote_path).name
65
+ download_file(remote_path, local_path)
66
+
67
+ state_dict = torch.load(str(local_path), map_location="cpu")
68
+ self.load_state_dict(state_dict)
69
+ logging.info(f'Weights are loaded from {local_path}')
70
+
71
+
72
+ class DenseNet121(AbstractModule):
73
+
74
+ def __init__(self, output_dim: int = 1,
75
+ weights: PathType | None = 'densenet121-union',
76
+ freeze: bool = False):
77
+ super().__init__()
78
+ densenet121 = models.densenet121(weights=models.DenseNet121_Weights.DEFAULT)
79
+ features = [module for module in densenet121.features]
80
+ features.append(nn.ReLU(inplace=True))
81
+ features.append(nn.AdaptiveAvgPool2d(output_size=(1, 1)))
82
+ features.append(nn.Flatten())
83
+ self.features = nn.Sequential(*features)
84
+
85
+ self.classifier = nn.Sequential(
86
+ nn.LazyLinear(512),
87
+ nn.BatchNorm1d(512, momentum=0.999, eps=1e-3),
88
+ nn.GELU(),
89
+ nn.Dropout(p=0.6),
90
+ nn.Linear(512, output_dim),
91
+ )
92
+
93
+ if freeze:
94
+ self.freeze_feature_extractor()
95
+
96
+ if weights is not None:
97
+ self.load_weights(weights)
98
+
99
+
100
+ class BlinkLinMulT(AbstractModule):
101
+
102
+ def __init__(self, input_dim: int = 160,
103
+ output_dim: int = 1,
104
+ weights: PathType | None = 'blinklinmult-union',
105
+ weights_backbone: PathType | None = 'densenet121-union',
106
+ **kwargs):
107
+ super().__init__()
108
+ self.img_backbone = DenseNet121(output_dim=output_dim, weights=weights_backbone)
109
+ self.img_backbone = self.img_backbone.features
110
+ logging.info(self.img_backbone)
111
+
112
+ self.rnn_backbone = LinMulT(
113
+ input_modality_channels=[1024, input_dim],
114
+ output_dim=output_dim,
115
+ projected_modality_dim=32,
116
+ number_of_layers=5,
117
+ add_projection_fusion=False,
118
+ aggregation='meanpooling',
119
+ **kwargs,
120
+ )
121
+ logging.info(self.rnn_backbone)
122
+
123
+ if weights is not None:
124
+ self.load_weights(weights)
125
+
126
+ def forward(self, x):
127
+ time_dim = x[0].size(1)
128
+ rgb_texture = x[0] # (B, L, C, H, W)
129
+ high_level_features = x[1] # (B, L, C)
130
+
131
+ eyes_x = []
132
+ for t in range(time_dim):
133
+ eyes_x.append(torch.flatten(self.img_backbone(rgb_texture[:, t, :, :, :]), 1))
134
+
135
+ x0 = torch.stack(eyes_x, dim=1)
136
+ x_seq = self.rnn_backbone([x0, high_level_features])
137
+ return x_seq
138
+
139
+
140
+ class BlinkLinT(AbstractModule):
141
+
142
+ def __init__(self, output_dim: int = 1,
143
+ weights: str | Path | None = 'blinklint-union'):
144
+ super().__init__()
145
+ self.img_backbone = DenseNet121(output_dim=output_dim, weights='densenet121-union')
146
+ self.img_backbone = self.img_backbone.features
147
+ self.rnn_backbone = LinT(1024, projected_modality_dim=32, number_of_layers=5, output_dim=output_dim)
148
+
149
+ logging.info(self.img_backbone)
150
+ logging.info(self.rnn_backbone)
151
+
152
+ if weights is not None:
153
+ self.load_weights(weights)
154
+
155
+ def forward(self, rgb_texture):
156
+ time_dim = rgb_texture.size(1) # (B, L, C, H, W)
157
+
158
+ eyes_x = []
159
+ for t in range(time_dim):
160
+ eyes_x.append(torch.flatten(self.img_backbone(rgb_texture[:, t, :, :, :]), 1))
161
+
162
+ x0 = torch.stack(eyes_x, dim=1)
163
+ x_seq = self.rnn_backbone(x0)
164
+ return x_seq
@@ -0,0 +1,23 @@
1
+ from .BlinkLinMulT import (AbstractModule,
2
+ DenseNet121,
3
+ BlinkLinT,
4
+ LinT,
5
+ BlinkLinMulT,
6
+ preprocess_eye_fcn)
7
+
8
+ from .backbone import (EyeNet,
9
+ ResNet50,
10
+ Dense)
11
+
12
+ SEQUENCE_MODELS = {
13
+ "lint": LinT,
14
+ "blinklint": BlinkLinT,
15
+ "blinklinmult": BlinkLinMulT,
16
+ }
17
+
18
+ BACKBONE_MODELS = {
19
+ "dense": Dense,
20
+ "eyenet": EyeNet,
21
+ "resnet50": ResNet50,
22
+ "densenet121": DenseNet121
23
+ }
@@ -0,0 +1,94 @@
1
+ import torch.nn as nn
2
+ from torchvision import models
3
+ from blinklinmult.models import AbstractModule
4
+
5
+
6
+ class Dense(AbstractModule):
7
+
8
+ def __init__(self, input_dim: int, output_dim: int = 1):
9
+ super().__init__()
10
+ self.features = nn.Identity()
11
+ self.classifier = nn.Sequential(
12
+ nn.Linear(input_dim, 128),
13
+ nn.ReLU(inplace=True),
14
+ nn.Dropout(p=0.6),
15
+ nn.Linear(128, output_dim),
16
+ )
17
+
18
+
19
+ class EyeNet(AbstractModule):
20
+ """EyeNet: An Improved Eye States Classification System using Convolutional Neural Network (2020)
21
+
22
+ Paper: https://www.researchgate.net/publication/340757067
23
+ """
24
+ def __init__(self, output_dim: int = 1, freeze: bool = False):
25
+ super().__init__()
26
+
27
+ self.features = nn.Sequential(
28
+ nn.Conv2d(3, 32, 5, bias=False),
29
+ nn.BatchNorm2d(32),
30
+ nn.ReLU(),
31
+ nn.Conv2d(32, 64, 5, bias=False),
32
+ nn.BatchNorm2d(64),
33
+ nn.ReLU(),
34
+ nn.Conv2d(64, 128, 3, bias=False),
35
+ nn.BatchNorm2d(128),
36
+ nn.ReLU(),
37
+ nn.MaxPool2d(2),
38
+ nn.Conv2d(128, 256, 3, bias=False),
39
+ nn.BatchNorm2d(256),
40
+ nn.ReLU(),
41
+ nn.MaxPool2d(2),
42
+ nn.Conv2d(256, 384, 3, bias=False),
43
+ nn.BatchNorm2d(384),
44
+ nn.ReLU(),
45
+ nn.MaxPool2d(2),
46
+ nn.Conv2d(384, 512, 3, bias=False),
47
+ nn.BatchNorm2d(512),
48
+ nn.ReLU(),
49
+ nn.AdaptiveAvgPool2d((1, 1)),
50
+ nn.Flatten(),
51
+ )
52
+
53
+ self.classifier = nn.Sequential(
54
+ nn.LazyLinear(84),
55
+ nn.ReLU(),
56
+ nn.Dropout(0.25),
57
+ nn.Linear(84, 32),
58
+ nn.ReLU(),
59
+ nn.Linear(32, output_dim),
60
+ )
61
+
62
+ if freeze:
63
+ self.freeze_feature_extractor()
64
+
65
+
66
+ class ResNet50(AbstractModule):
67
+
68
+ def __init__(self, output_dim: int = 1, freeze: bool = False):
69
+ super().__init__()
70
+
71
+ resnet50 = models.resnet50(weights=models.ResNet50_Weights.DEFAULT)
72
+ self.features = nn.Sequential(
73
+ resnet50.conv1,
74
+ resnet50.bn1,
75
+ resnet50.relu,
76
+ resnet50.maxpool,
77
+ resnet50.layer1,
78
+ resnet50.layer2,
79
+ resnet50.layer3,
80
+ resnet50.layer4,
81
+ resnet50.avgpool,
82
+ nn.Flatten(),
83
+ )
84
+
85
+ self.classifier = nn.Sequential(
86
+ nn.LazyLinear(512),
87
+ nn.BatchNorm1d(512, momentum=0.999, eps=1e-3),
88
+ nn.ReLU(inplace=True),
89
+ nn.Dropout(p=0.6),
90
+ nn.Linear(512, output_dim),
91
+ )
92
+
93
+ if freeze:
94
+ self.freeze_feature_extractor()
@@ -0,0 +1,37 @@
1
+ import torch
2
+ from torch import nn
3
+ import clip
4
+ from blinklinmult.models.backbone import AbstractModule
5
+
6
+
7
+ class CLIP(AbstractModule):
8
+
9
+ def __init__(self, backbone: str = "ViT-B/32",
10
+ output_dim: int = 1,
11
+ freeze: bool = False):
12
+ super().__init__()
13
+
14
+ model, preprocess = clip.load(backbone, jit=False, device=torch.device("cpu"))
15
+
16
+ self.preprocess = preprocess
17
+ self.features = model.visual
18
+
19
+ self.classifier = nn.Sequential(
20
+ nn.LazyLinear(512),
21
+ nn.BatchNorm1d(512, momentum=0.999, eps=1e-3),
22
+ nn.ReLU(inplace=True),
23
+ nn.Dropout(p=0.6),
24
+ nn.Linear(512, output_dim),
25
+ )
26
+
27
+ if freeze:
28
+ self.freeze_feature_extractor()
29
+
30
+
31
+ if __name__ == '__main__':
32
+
33
+ x = torch.zeros((32, 3, 224, 224)).to("cuda:0")
34
+ for backbone in ["RN50", "ViT-B/32", "ViT-L/14"]:
35
+ model = CLIP(backbone=backbone).to("cuda:0")
36
+ y_pred = model(x)
37
+ assert y_pred.shape == (32, 1)
@@ -0,0 +1,27 @@
1
+ from torch import nn
2
+ from torchvision import models
3
+ from blinklinmult.models.backbone import AbstractModule
4
+
5
+
6
+ class EfficientNetV2(AbstractModule):
7
+
8
+ def __init__(self, output_dim: int = 1, freeze: bool = False):
9
+ super().__init__()
10
+ self.model = models.efficientnet_v2_m(weights=models.EfficientNet_V2_M_Weights.DEFAULT)
11
+
12
+ self.features = nn.Sequential(
13
+ self.model.features,
14
+ nn.AdaptiveAvgPool2d(output_size=(1, 1)),
15
+ nn.Flatten()
16
+ )
17
+
18
+ self.classifier = nn.Sequential(
19
+ nn.Linear(1280, 512),
20
+ nn.BatchNorm1d(512, momentum=0.999, eps=1e-3),
21
+ nn.ReLU(inplace=True),
22
+ nn.Dropout(p=0.6),
23
+ nn.Linear(512, output_dim),
24
+ )
25
+
26
+ if freeze:
27
+ self.freeze_feature_extractor()
@@ -0,0 +1,40 @@
1
+ import torch
2
+ from torch import nn
3
+
4
+
5
+ class GRU(nn.Module):
6
+
7
+ def __init__(self, input_dim,
8
+ hidden_dim: int = 256,
9
+ layer_dim: int = 3,
10
+ dropout_prob: float = 0.2,
11
+ return_sequences: bool = True):
12
+ super().__init__()
13
+ self.layer_dim = layer_dim
14
+ self.hidden_dim = hidden_dim
15
+ self.return_sequences = return_sequences
16
+ self.gru = nn.GRU(
17
+ input_dim, hidden_dim, layer_dim, batch_first=True, dropout=dropout_prob
18
+ )
19
+ # self.fc = nn.Linear(hidden_dim, output_dim)
20
+
21
+ def forward(self, x):
22
+
23
+ # Initializing hidden state for first input with zeros
24
+ h0 = torch.zeros(self.layer_dim, x.size(0), self.hidden_dim).requires_grad_()
25
+
26
+ h0 = h0.to(x.device)
27
+ self.gru = self.gru.to(x.device)
28
+
29
+ # Forward propagation by passing in the input and hidden state into the model
30
+ out, _ = self.gru(x, h0)
31
+
32
+ # Reshaping the outputs in the shape of (batch_size, seq_length, hidden_size)
33
+ # so that it can fit into the fully connected layer
34
+ if not self.return_sequences:
35
+ out = out[:, -1, :]
36
+
37
+ # Convert the final state to our desired output shape (batch_size, output_dim)
38
+ # out = self.fc(out)
39
+
40
+ return out
File without changes
@@ -0,0 +1,84 @@
1
+ import pickle
2
+ from pathlib import Path
3
+ import cv2
4
+ import numpy as np
5
+ from tqdm import tqdm
6
+ from exordium.video.iris import IrisWrapper
7
+ from exordium.video.tddfa_v2 import TDDFA_V2
8
+ from exordium.utils.decorator import timer
9
+ from blinklinmult import PathType
10
+
11
+
12
+ DB_DIR = Path('data/db/CEW')
13
+ DB_DIR_OUT = Path('data/db_processed/cew')
14
+
15
+
16
+ @timer
17
+ def save_eye_crops(output_path: PathType, bb_size: int = 40) -> None:
18
+ # read closed eye filenames and eye coordinates
19
+ with open(DB_DIR / 'dataset_B_FacialImages' / 'EyeCoordinatesInfo_ClosedFace.txt', 'r') as f:
20
+ lines = f.readlines()
21
+ lines = [line.replace('\n', '').split(' ') for line in lines]
22
+ closed_file_coords = {
23
+ elem[0]: (1, np.array(elem[1:]).astype(int))
24
+ for elem in lines
25
+ }
26
+
27
+ # read open eye filenames and eye coordinates
28
+ with open(DB_DIR / 'dataset_B_FacialImages' / 'EyeCoordinatesInfo_OpenFace.txt', 'r') as f:
29
+ lines = f.readlines()
30
+ lines = [line.replace('\n', '').split(' ') for line in lines]
31
+ open_file_coords = {
32
+ elem[0]: (0, np.array(elem[1:]).astype(int))
33
+ for elem in lines
34
+ }
35
+
36
+ file_coords = open_file_coords | closed_file_coords
37
+
38
+ face_model = TDDFA_V2()
39
+ eye_model = IrisWrapper()
40
+
41
+ closed_face_dir = DB_DIR / 'dataset_B_FacialImages' / 'ClosedFace'
42
+ open_face_dir = DB_DIR / 'dataset_B_FacialImages' / 'OpenFace'
43
+ eye_dir = DB_DIR_OUT / 'eyes'
44
+ eye_dir.mkdir(parents=True, exist_ok=True)
45
+ (eye_dir / 'left').mkdir(parents=True, exist_ok=True)
46
+ (eye_dir / 'right').mkdir(parents=True, exist_ok=True)
47
+
48
+ samples = []
49
+ for id, (name, (label, coords)) in enumerate(tqdm(file_coords.items(), total=len(file_coords), desc='[CEW] eye crops')):
50
+ face_dir = closed_face_dir if label else open_face_dir
51
+ face_path = str(face_dir / name)
52
+ face = cv2.imread(face_path)
53
+
54
+ sample = {'id': id, 'name': name, 'path': face_path, 'label': label, 'headpose': face_model(face)['headpose']}
55
+ left_eye_path = str(eye_dir / 'left' / f'{id:06d}.png')
56
+ right_eye_path = str(eye_dir / 'right' / f'{id:06d}.png')
57
+
58
+ # eye crop rgb
59
+ y_min = max(coords[1] - bb_size // 2, 0)
60
+ y_max = min(100, coords[1] + bb_size // 2)
61
+ x_min = max(coords[0] - bb_size // 2, 0)
62
+ x_max = min(100, coords[0] + bb_size // 2)
63
+ left_eye = face[y_min:y_max, x_min:x_max, :]
64
+ y_min = max(coords[3] - bb_size // 2, 0)
65
+ y_max = min(100, coords[3] + bb_size // 2)
66
+ x_min = max(coords[2] - bb_size // 2, 0)
67
+ x_max = min(100, coords[2] + bb_size // 2)
68
+ right_eye = face[y_min:y_max, x_min:x_max, :]
69
+ cv2.imwrite(left_eye_path, left_eye)
70
+ cv2.imwrite(right_eye_path, right_eye)
71
+
72
+ left_eye_features = eye_model.eye_to_features(left_eye_path)
73
+ right_eye_features = eye_model.eye_to_features(right_eye_path)
74
+ sample |= {'left_eye': left_eye_features, 'right_eye': right_eye_features}
75
+ samples.append(sample)
76
+
77
+ with open(str(output_path), 'wb') as f:
78
+ pickle.dump(samples, f)
79
+
80
+ print(f'[CEW] feature extraction is done: {str(output_path)}')
81
+
82
+
83
+ if __name__ == '__main__':
84
+ save_eye_crops(output_path=DB_DIR_OUT / '0_data.pkl')
@@ -0,0 +1,117 @@
1
+ import os
2
+ import pickle
3
+ from pathlib import Path
4
+ from tqdm import tqdm
5
+ from exordium.video.io import video2frames
6
+ from exordium.utils.decorator import timer
7
+ from exordium.video.tddfa_v2 import TDDFA_V2
8
+ from exordium.video.iris import IrisWrapper
9
+ from blinklinmult.preprocess.reader import Tag
10
+
11
+
12
+ DB_DIR = Path('data/db/eyeblink8')
13
+ DB_DIR_OUT = Path('data/db_processed/eyeblink8')
14
+ IDS = os.listdir(DB_DIR)
15
+
16
+
17
+ def extract_frames():
18
+ videos = sorted(list(DB_DIR.rglob('*/*.avi')))
19
+
20
+ for video in videos:
21
+ id = video.parent.name
22
+ output_dir = DB_DIR_OUT / 'frames' / id
23
+ video2frames(video, output_dir, fps=30)
24
+
25
+
26
+ def save_samples():
27
+ tag_paths = list(Path(DB_DIR).glob('*/*.tag'))
28
+ samples = []
29
+
30
+ for tag_path in tqdm(tag_paths, desc='Save mp4'):
31
+ id = tag_path.parent.name
32
+ frames_dir = DB_DIR_OUT / 'frames' / id
33
+ output_dir = DB_DIR_OUT / 'visualize'
34
+ tag = Tag(tag_path=tag_path, frames_dir=frames_dir)
35
+ samples += tag.generate_positive_samples(output_dir=output_dir, fps=30)
36
+
37
+ return samples
38
+
39
+
40
+ def save_face_crops():
41
+ tag_paths = list(Path(DB_DIR).glob('*/*.tag'))
42
+ for tag_path in tqdm(tag_paths, desc='Save faces'):
43
+ id = tag_path.parent.name
44
+ frames_dir = DB_DIR_OUT / 'frames' / id
45
+ faces_dir = DB_DIR_OUT / 'faces' / id
46
+ tag = Tag(tag_path=tag_path, frames_dir=frames_dir)
47
+ tag.save_annotated_face_crops(faces_dir)
48
+
49
+
50
+ def save_eye_crops():
51
+ tag_paths = list(Path(DB_DIR).glob('*/*.tag'))
52
+ for tag_path in tqdm(tag_paths, desc='Save faces'):
53
+ id = tag_path.parent.name
54
+ frames_dir = DB_DIR_OUT / 'frames' / id
55
+ eyes_dir = DB_DIR_OUT / 'eyes' / id
56
+ tag = Tag(tag_path=tag_path, frames_dir=frames_dir)
57
+ tag.save_annotated_eye_crops(eyes_dir)
58
+
59
+
60
+ @timer
61
+ def extract_features(tag_path: str | Path,
62
+ face_dir: str | Path,
63
+ left_eye_dir: str | Path,
64
+ right_eye_dir: str | Path,
65
+ output_path: str | Path):
66
+
67
+ face_paths = [str(Path(face_dir) / elem) for elem in sorted(os.listdir(face_dir))]
68
+ left_eye_paths = [str(Path(left_eye_dir) / elem) for elem in sorted(os.listdir(left_eye_dir))]
69
+ right_eye_paths = [str(Path(right_eye_dir) / elem) for elem in sorted(os.listdir(right_eye_dir))]
70
+
71
+ face_model = TDDFA_V2()
72
+ eye_model = IrisWrapper()
73
+
74
+ headposes = []
75
+ for face_path in tqdm(face_paths, desc='Extract headpose feature'):
76
+ headposes.append({'id': int(Path(face_path).stem), 'headpose': face_model.inference(face_path)['headpose']})
77
+
78
+ eyes = []
79
+ for left_eye_path, right_eye_path in tqdm(zip(left_eye_paths, right_eye_paths), total=len(left_eye_paths), desc='Extract eye features'):
80
+ eyes.append({'id': int(Path(left_eye_path).stem),
81
+ 'left_eye': eye_model.eye_to_features(left_eye_path),
82
+ 'right_eye': eye_model.eye_to_features(right_eye_path)})
83
+
84
+ tag_path = Path(tag_path)
85
+ tag = Tag(tag_path=tag_path, frames_dir=DB_DIR_OUT / 'frames' / tag_path.parent.name)
86
+ ids = sorted([elem['id'] for elem in headposes])
87
+
88
+ features = []
89
+ for id in tqdm(ids, total=len(ids), desc='Merge headpose and eye features, then save to pickle'):
90
+ label = tag.blink_label(id)
91
+ headpose = next((elem for elem in headposes if elem['id'] == id))
92
+ eye = next((elem for elem in eyes if elem['id'] == id))
93
+ features.append({'label': label} | headpose | eye)
94
+
95
+ with open(output_path, 'wb') as f:
96
+ pickle.dump(features, f)
97
+
98
+ print(f'Feature extraction is done: {output_path}')
99
+
100
+
101
+ if __name__ == '__main__':
102
+ extract_frames()
103
+ save_samples()
104
+ save_face_crops()
105
+ save_eye_crops()
106
+
107
+ tag_paths = list(Path(DB_DIR).glob('*/*.tag'))
108
+
109
+ for id in IDS:
110
+ print(f'Started id {id}')
111
+ tag_path = next((elem for elem in tag_paths if elem.parent.name == id))
112
+ extract_features(tag_path=tag_path,
113
+ face_dir=DB_DIR_OUT / 'faces' / id,
114
+ left_eye_dir=DB_DIR_OUT / 'eyes'/ id / 'left',
115
+ right_eye_dir=DB_DIR_OUT / 'eyes'/ id / 'right',
116
+ output_path=DB_DIR_OUT / f'{id}_data.pkl')
117
+ print('EyeBlink8 is done.')
@@ -0,0 +1,53 @@
1
+ import os
2
+ from tqdm import tqdm
3
+ from pathlib import Path
4
+ import pickle
5
+ from exordium.video.iris import IrisWrapper
6
+ from exordium.utils.decorator import timer
7
+
8
+
9
+ DB_DIR = Path('data/db/mrl_eye')
10
+ DB_DIR_OUT = Path('data/db_processed/mrl')
11
+ IDS = [elem for elem in os.listdir('data/db/mrl_eye/mrlEyes_2018_01')
12
+ if len(elem) == 5 and elem[0] == 's']
13
+
14
+
15
+ @timer
16
+ def save_features(input_path: str | Path, output_path: str | Path):
17
+ paths = [Path(input_path) / elem for elem in os.listdir(str(input_path))]
18
+ eye_model = IrisWrapper()
19
+
20
+ samples = []
21
+ for path in tqdm(paths, total=len(paths), desc=f'[MRL] {Path(input_path).name} features'):
22
+
23
+ data = Path(path).stem.split('_')
24
+ participant_id = int(data[0][1:]) # participant id
25
+ sample = {
26
+ 'participant_id': participant_id,
27
+ 'id': int(data[1]), # image number
28
+ 'name': Path(path).stem, # image name
29
+ 'gender': int(data[2]), # 0=male, 1=female
30
+ 'glasses': int(data[3]), # 0=no, 1=yes
31
+ 'label': int(not bool(int(data[4]))), # switch original blink label from 0=close, 1=open to 1=close, 0=open
32
+ 'reflection': int(data[5]), # 0=none, 1=low, 2=high
33
+ 'lightning': int(data[6]), # 0=bad, 1=good
34
+ 'sensor': int(data[7]), # 1=RealSense SR300 640x480, 2=IDS Imaging, 1280x1024, 3=Aptina Imagin 752x480
35
+ }
36
+
37
+ eye_features = eye_model.eye_to_features(path)
38
+ sample |= eye_features
39
+ samples.append(sample)
40
+
41
+ output_path = str(output_path)
42
+
43
+ with open(output_path, 'wb') as f:
44
+ pickle.dump(samples, f)
45
+
46
+ print(f'[MRL] feature extraction is done: {output_path}')
47
+
48
+
49
+ if __name__ == '__main__':
50
+
51
+ for id in IDS:
52
+ save_features(input_path=DB_DIR / 'mrlEyes_2018_01' / id,
53
+ output_path=DB_DIR_OUT / f'{int(id[1:])}_data.pkl')