sarc-asm 0.2.2__py2.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.

Potentially problematic release.


This version of sarc-asm might be problematic. Click here for more details.

Files changed (64) hide show
  1. contraction_net/__init__.py +4 -0
  2. contraction_net/annotation.py +148 -0
  3. contraction_net/contraction_net.py +81 -0
  4. contraction_net/data.py +158 -0
  5. contraction_net/losses.py +97 -0
  6. contraction_net/prediction.py +41 -0
  7. contraction_net/training.py +171 -0
  8. contraction_net/utils.py +248 -0
  9. sarc_asm-0.2.2.dist-info/LICENSE +27 -0
  10. sarc_asm-0.2.2.dist-info/METADATA +18 -0
  11. sarc_asm-0.2.2.dist-info/RECORD +64 -0
  12. sarc_asm-0.2.2.dist-info/WHEEL +4 -0
  13. sarcasm/.DS_Store +0 -0
  14. sarcasm/__init__.py +25 -0
  15. sarcasm/_version.py +20 -0
  16. sarcasm/core.py +624 -0
  17. sarcasm/core_omezarr.py +801 -0
  18. sarcasm/exceptions.py +17 -0
  19. sarcasm/export.py +464 -0
  20. sarcasm/feature_dict.py +743 -0
  21. sarcasm/ioutils.py +112 -0
  22. sarcasm/meta_data_handler.py +120 -0
  23. sarcasm/meta_data_handler_omezarr.py +243 -0
  24. sarcasm/models/model_ContractionNet.pt +0 -0
  25. sarcasm/models/model_sarcomeres_generalist.pt +0 -0
  26. sarcasm/models/model_z_bands_unet3d.pt +0 -0
  27. sarcasm/motion.py +938 -0
  28. sarcasm/plot_utils.py +302 -0
  29. sarcasm/plots.py +1615 -0
  30. sarcasm/py.typed +0 -0
  31. sarcasm/structure.py +2668 -0
  32. sarcasm/structure_xarray.py +2899 -0
  33. sarcasm/training_data_generation.py +1041 -0
  34. sarcasm/type_utils.py +34 -0
  35. sarcasm/utils.py +1276 -0
  36. sarcasm_app/__init__.py +256 -0
  37. sarcasm_app/__main__.py +24 -0
  38. sarcasm_app/control/__init__.py +17 -0
  39. sarcasm_app/control/application_control.py +579 -0
  40. sarcasm_app/control/batch_processing_control.py +495 -0
  41. sarcasm_app/control/chain_execution.py +89 -0
  42. sarcasm_app/control/file_selection_control.py +322 -0
  43. sarcasm_app/control/loi_analysis_control.py +202 -0
  44. sarcasm_app/control/motion_analysis_control.py +320 -0
  45. sarcasm_app/control/popup_export.py +295 -0
  46. sarcasm_app/control/structure_analysis_control.py +505 -0
  47. sarcasm_app/icons/sarcasm.icns +0 -0
  48. sarcasm_app/icons/sarcasm.ico +0 -0
  49. sarcasm_app/model/__init__.py +356 -0
  50. sarcasm_app/model/parameter.py +148 -0
  51. sarcasm_app/model/parameters.py +60 -0
  52. sarcasm_app/py.typed +0 -0
  53. sarcasm_app/ui-files/file_selection.ui +212 -0
  54. sarcasm_app/ui-files/parameters_batch_processing.ui +276 -0
  55. sarcasm_app/ui-files/parameters_loi_analysis.ui +680 -0
  56. sarcasm_app/ui-files/parameters_motion_analysis.ui +740 -0
  57. sarcasm_app/ui-files/parameters_structure_analysis.ui +1199 -0
  58. sarcasm_app/ui-files/update_py_files.bat +5 -0
  59. sarcasm_app/view/__init__.py +0 -0
  60. sarcasm_app/view/file_selection.py +146 -0
  61. sarcasm_app/view/parameters_batch_processing.py +174 -0
  62. sarcasm_app/view/parameters_loi_analysis.py +371 -0
  63. sarcasm_app/view/parameters_motion_analysis.py +404 -0
  64. sarcasm_app/view/parameters_structure_analysis.py +643 -0
@@ -0,0 +1,4 @@
1
+ from .contraction_net import ContractionNet
2
+ from .training import Trainer
3
+ from .data import DataProcess
4
+ from .prediction import predict_contractions
@@ -0,0 +1,148 @@
1
+ import random
2
+ import os
3
+ import numpy as np
4
+ import matplotlib.pyplot as plt
5
+ from matplotlib.widgets import Button
6
+
7
+
8
+ class TimeSeriesAnnotator:
9
+ """
10
+ Class to annotate contraction intervals in multiple time-series files and save the results.
11
+
12
+ This class allows users to interactively annotate the start and end of contractions
13
+ in time-series data by clicking on the plot. The user can press the left mouse button
14
+ at the start of a contraction interval and release at the end of the contraction interval.
15
+ The results are saved to a specified output directory.
16
+
17
+ Parameters
18
+ ----------
19
+ file_list : list of str
20
+ List of file paths to the time-series data files to be annotated.
21
+ output_dir : str
22
+ Directory where the output files with annotated contraction intervals will be saved.
23
+ figsize : tuple, optional
24
+ Figure size in inches.
25
+
26
+ Methods
27
+ -------
28
+ load_next_file()
29
+ Load the next time-series file for annotation.
30
+ press_callback(event)
31
+ Record the x-coordinate (time) where the mouse button is pressed.
32
+ release_callback(event)
33
+ Record the x-coordinate (time) where the mouse button is released.
34
+ save_and_load_next(event)
35
+ Save the current annotations and load the next time-series file.
36
+ reset_annotations(event)
37
+ Reset the current annotations for the current time-series file.
38
+
39
+ Notes
40
+ -----
41
+ In an interactive Jupyter notebook, add the following before running the TimeSeriesAnnotator:
42
+
43
+ ```
44
+ matplotlib.use('nbagg')
45
+ %matplotlib notebook
46
+ ```
47
+ """
48
+
49
+ def __init__(self, file_list, output_dir, figsize=(11, 3.5)):
50
+ self.file_list = [f for f in file_list if not f.endswith('_contr.txt')]
51
+ self.output_dir = output_dir
52
+ self.current_file_index = 0
53
+ self.start_contraction = []
54
+ self.end_contraction = []
55
+ self.fig, self.ax = plt.subplots(figsize=figsize, constrained_layout=True)
56
+ self.load_next_file()
57
+
58
+ self.fig.canvas.mpl_connect('button_press_event', self.press_callback)
59
+ self.fig.canvas.mpl_connect('button_release_event', self.release_callback)
60
+
61
+ save_ax = self.fig.add_axes([0.85, 0.01, 0.1, 0.075])
62
+ self.save_button = Button(save_ax, 'Save & Next', color='lightgoldenrodyellow', hovercolor='0.975')
63
+ self.save_button.on_clicked(self.save_and_load_next)
64
+
65
+ reset_ax = self.fig.add_axes([0.75, 0.01, 0.1, 0.075])
66
+ self.reset_button = Button(reset_ax, 'Reset', color='lightcoral', hovercolor='0.975')
67
+ self.reset_button.on_clicked(self.reset_annotations)
68
+
69
+ plt.show()
70
+
71
+ def load_next_file(self):
72
+ """Load the next time-series file for annotation."""
73
+ if self.current_file_index < len(self.file_list):
74
+ file_path = self.file_list[self.current_file_index]
75
+ self.data = np.loadtxt(file_path)
76
+ self.ax.clear()
77
+ self.ax.plot(self.data, c='k')
78
+ self.ax.set_title(f'Annotating {os.path.basename(file_path)}')
79
+ self.start_contraction = []
80
+ self.end_contraction = []
81
+ plt.draw()
82
+ else:
83
+ plt.close(self.fig)
84
+ print('All files annotated.')
85
+
86
+ def press_callback(self, event):
87
+ """Record the x-coordinate (time) where the mouse button is pressed."""
88
+ self.ax.axvline(event.xdata, c='r', linestyle=':')
89
+ self.start_contraction.append(event.xdata)
90
+
91
+ def release_callback(self, event):
92
+ """Record the x-coordinate (time) where the mouse button is released."""
93
+ self.ax.axvline(event.xdata, c='r', linestyle=':')
94
+ self.ax.axvspan(self.start_contraction[-1], event.xdata, alpha=0.5, color='red')
95
+ self.end_contraction.append(event.xdata)
96
+
97
+ def save_and_load_next(self, event):
98
+ """Save the current annotations and load the next time-series file."""
99
+ if self.current_file_index < len(self.file_list):
100
+ file_path = self.file_list[self.current_file_index]
101
+ output_file = os.path.join(self.output_dir, f'{os.path.basename(file_path).split(".")[0]}_contr.txt')
102
+ start_end_contraction = np.asarray([self.start_contraction, self.end_contraction])
103
+ np.savetxt(output_file, start_end_contraction.T, header='start end', comments='', fmt='%f')
104
+ print(f'Saved contractions for {os.path.basename(file_path)} to {output_file}')
105
+
106
+ self.current_file_index += 1
107
+ self.load_next_file()
108
+
109
+ def reset_annotations(self, event):
110
+ """Reset the current annotations for the current time-series file."""
111
+ self.start_contraction = []
112
+ self.end_contraction = []
113
+ self.ax.clear()
114
+ self.ax.plot(self.data, c='k')
115
+ self.ax.set_title(f'Annotating {os.path.basename(self.file_list[self.current_file_index])}')
116
+ plt.draw()
117
+
118
+
119
+ def split_trace(filepath, output_dir, chunk_size=512, p=1):
120
+ """
121
+ Split a time-series trace into smaller chunks and save them to separate files.
122
+
123
+ Parameters
124
+ ----------
125
+ filepath : str
126
+ Path to the input time-series data file.
127
+ output_dir : str
128
+ Directory where the output chunk files will be saved.
129
+ chunk_size : int, optional
130
+ The size of each chunk, by default 512.
131
+ p : float, optional
132
+ Probability of saving each chunk, by default 1 (save all chunks).
133
+ """
134
+ # Read the time-series data
135
+ data = np.loadtxt(filepath)
136
+
137
+ # Create the output directory if it doesn't exist
138
+ if not os.path.exists(output_dir):
139
+ os.makedirs(output_dir)
140
+
141
+ # Calculate the number of chunks
142
+ num_chunks = len(data) // chunk_size + (len(data) % chunk_size > 0)
143
+
144
+ # Split the data into chunks of size `chunk_size` and save each chunk to a separate file
145
+ for i in range(num_chunks):
146
+ if random.random() < p:
147
+ chunk = data[i * chunk_size:(i + 1) * chunk_size]
148
+ np.savetxt(os.path.join(output_dir, f'{os.path.basename(filepath).split(".")[0]}_{i}.txt'), chunk)
@@ -0,0 +1,81 @@
1
+ import torch
2
+ import torch.nn as nn
3
+ import torch.nn.functional as F
4
+
5
+
6
+ class ContractionNet(nn.Module):
7
+ """
8
+ ContractionNet model for detecting contraction intervals from time-series data of individual Z-band positions
9
+ and sarcomere lengths of beating cardiomyocytes.
10
+
11
+ This neural network is designed to handle noisy data and distinguish between contracting and non-contracting intervals.
12
+ The network first extracts various features from a single input time-series by two convolutional layers with kernel size 5, followed by a dilated convolution in the third layer
13
+ to capture broader temporal patterns. Each convolution is followed by instance normalization and ReLU activation.
14
+ A self-attention layer enhances focus on salient features. The processed signal then undergoes two further
15
+ convolutions before being outputted through a sigmoid activation function.
16
+
17
+ Methods
18
+ -------
19
+ forward(x)
20
+ Forward pass through the network.
21
+ """
22
+ def __init__(self, n_filter=64, in_channels=1, out_channels=2, dropout_rate=0.5):
23
+ """
24
+ Parameters
25
+ ----------
26
+ n_filter : int, optional
27
+ Number of filters in the convolutional layers (default is 64).
28
+ in_channels : int, optional
29
+ Number of input channels (default is 1).
30
+ out_channels : int, optional
31
+ Number of output channels (default is 2).
32
+ dropout_rate : float, optional
33
+ Dropout rate (default is 0.5).
34
+ """
35
+ super(ContractionNet, self).__init__()
36
+ self.conv1 = nn.Conv1d(in_channels=in_channels, out_channels=n_filter, kernel_size=5, padding=2)
37
+ self.in1 = nn.InstanceNorm1d(n_filter)
38
+ self.conv2 = nn.Conv1d(in_channels=n_filter, out_channels=n_filter * 2, kernel_size=5, padding=2)
39
+ self.bn2 = nn.InstanceNorm1d(n_filter * 2)
40
+ self.conv3 = nn.Conv1d(in_channels=n_filter * 2, out_channels=n_filter * 4, kernel_size=5, padding=4,
41
+ dilation=2)
42
+ self.bn3 = nn.InstanceNorm1d(n_filter * 4)
43
+ self.attention = nn.MultiheadAttention(embed_dim=n_filter * 4, num_heads=4, batch_first=True)
44
+ self.norm1 = nn.LayerNorm(n_filter * 4)
45
+ self.dropout_attention = nn.Dropout(dropout_rate)
46
+ self.conv4 = nn.Conv1d(in_channels=n_filter * 4, out_channels=n_filter * 2, kernel_size=5, padding=2,
47
+ dilation=1)
48
+ self.bn4 = nn.InstanceNorm1d(n_filter * 2)
49
+ self.dropout_pre_output = nn.Dropout(dropout_rate)
50
+ self.conv_out = nn.Conv1d(in_channels=n_filter * 2, out_channels=out_channels, kernel_size=1)
51
+
52
+ def forward(self, x):
53
+ """
54
+ Forward pass through the network.
55
+
56
+ Parameters
57
+ ----------
58
+ x : torch.Tensor
59
+ Input tensor of shape (batch_size, in_channels, sequence_length).
60
+
61
+ Returns
62
+ -------
63
+ torch.Tensor
64
+ Output tensor of shape (batch_size, out_channels, sequence_length) after sigmoid activation.
65
+ torch.Tensor
66
+ Raw output tensor of shape (batch_size, out_channels, sequence_length).
67
+ """
68
+ x = F.relu(self.in1(self.conv1(x)))
69
+ x = F.relu(self.bn2(self.conv2(x)))
70
+ x = F.relu(self.bn3(self.conv3(x)))
71
+ x = x.transpose(1, 2)
72
+ residual = x
73
+ x, attention_weights = self.attention(x, x, x)
74
+ x = x + residual
75
+ x = self.norm1(x)
76
+ x = self.dropout_attention(x)
77
+ x = x.transpose(1, 2)
78
+ x = F.relu(self.bn4(self.conv4(x)))
79
+ x = self.dropout_pre_output(x)
80
+ x = self.conv_out(x)
81
+ return torch.sigmoid(x), x
@@ -0,0 +1,158 @@
1
+ import glob
2
+ import os
3
+
4
+ import numpy as np
5
+ import torch
6
+ from torch.utils.data import Dataset
7
+
8
+ from .utils import distance_transform
9
+
10
+
11
+ class DataProcess(Dataset):
12
+ """
13
+ A Dataset class for creating training data objects for ContractionNet training.
14
+
15
+ Parameters
16
+ ----------
17
+ source_dir : Tuple[str, str]
18
+ Tuple containing paths to the directories of training data [images, labels]. Images should be in .tif format.
19
+ input_len : int, optional
20
+ Length of the input sequences (default is 512).
21
+ normalize : bool, optional
22
+ Whether to normalize each time-series (default is False).
23
+ aug_factor : int, optional
24
+ Factor for image augmentation (default is 10).
25
+ val_split : float, optional
26
+ Validation split for training (default is 0.2).
27
+ noise_amp : float, optional
28
+ Amplitude of Gaussian noise for image augmentation (default is 0.2).
29
+ aug_p : float, optional
30
+ Probability of applying augmentation (default is 0.5).
31
+ random_offset : float, optional
32
+ Amplitude of random offset applied to the input sequences (default is 0.25).
33
+ random_outlier : float, optional
34
+ Amplitude of random outliers added to the input sequences (default is 0.5).
35
+ random_drift : Tuple[float, float], optional
36
+ Parameters for random drift: (frequency, amplitude) (default is (0.01, 0.2)).
37
+ random_swap : float, optional
38
+ Probability of randomly swapping the sign of the input sequences (default is 0.5).
39
+ random_subsampling : Tuple[int, int], optional
40
+ Range for random subsampling intervals (default is None).
41
+
42
+ Methods
43
+ -------
44
+ __len__()
45
+ Returns the total number of samples.
46
+ __getitem__(idx)
47
+ Generates one sample of data.
48
+
49
+ """
50
+ def __init__(self, source_dir, input_len=512, normalize=False, val_split=0.2, aug_factor=10, aug_p=0.5,
51
+ noise_amp=0.2, random_offset=0.25, random_outlier=0.5, random_drift=(0.01, 0.2), random_swap=0.5,
52
+ random_subsampling=None):
53
+ self.source_dir = source_dir
54
+ self.data = []
55
+ self.is_real = []
56
+ self.input_len = input_len
57
+ self.val_split = val_split
58
+ self.normalize = normalize
59
+ self.aug_factor = aug_factor
60
+ self.aug_p = aug_p
61
+ self.noise_amp = noise_amp
62
+ self.random_offset = random_offset
63
+ self.random_drift = random_drift
64
+ self.random_outlier = random_outlier
65
+ self.random_subsampling = random_subsampling
66
+ self.random_swap = random_swap
67
+ self.mode = 'train'
68
+ self.__load_and_edit()
69
+ if self.aug_factor is not None:
70
+ self.__augment()
71
+
72
+ def __load_and_edit(self):
73
+ """
74
+ Loads and preprocesses the input data files from the source directory.
75
+ """
76
+ files = glob.glob(self.source_dir + '*.txt')
77
+ print(f'{len(files)} files found')
78
+ files_input = [f for f in files if 'peaks' not in f and 'contr' not in os.path.basename(f)]
79
+ for file_i in files_input:
80
+ input_i = np.loadtxt(file_i)[: self.input_len]
81
+ if self.normalize:
82
+ input_i -= np.mean(input_i)
83
+ input_i /= np.std(input_i)
84
+ start_end_systole_i = np.loadtxt(file_i[:-4] + '_contr.txt')[: self.input_len]
85
+ if len(start_end_systole_i) == len(input_i):
86
+ contr_i = start_end_systole_i
87
+ else:
88
+ contr_i = np.zeros_like(input_i)
89
+ start_end_systole_i = np.clip(start_end_systole_i, a_min=0, a_max=self.input_len)
90
+ if start_end_systole_i.size != 0:
91
+ if len(start_end_systole_i.shape) == 1:
92
+ contr_i[int(start_end_systole_i[0]): int(start_end_systole_i[1])] = 1
93
+ elif len(start_end_systole_i.shape) == 2:
94
+ for start, end in start_end_systole_i.T:
95
+ contr_i[int(start): int(end)] = 1
96
+ if 'sim' in file_i or 'noise' in file_i:
97
+ is_real_i = False
98
+ else:
99
+ is_real_i = True
100
+ self.data.append([input_i, contr_i, distance_transform(input_i, contr_i)])
101
+ self.is_real.append(is_real_i)
102
+ self.data = np.asarray(self.data)
103
+
104
+ def __augment(self):
105
+ """
106
+ Applies data augmentation techniques to the loaded data.
107
+ """
108
+ _data = self.data.copy()
109
+ self.data = []
110
+ for i, d_i in enumerate(_data):
111
+ for j in range(self.aug_factor):
112
+ d_ij = d_i.copy()
113
+ std_ij = np.std(d_ij[0])
114
+ d_ij[0] = d_ij[0] / std_ij
115
+ d_ij[0] = np.random.choice([-1, 1], p=(1-self.random_swap, self.random_swap)) * d_ij[0]
116
+ if self.random_subsampling is not None and np.random.binomial(1, self.aug_p):
117
+ d_ij = np.zeros_like(d_ij)
118
+ d_ij_short = np.delete(d_i, np.arange(0, 512, np.random.randint(self.random_subsampling[0],
119
+ self.random_subsampling[1])),
120
+ axis=1)
121
+ d_ij[:, :d_ij_short.shape[1]] = d_ij_short
122
+ if not self.is_real[i] and np.random.binomial(1, self.aug_p):
123
+ d_ij[0] += np.random.normal(0, self.noise_amp, size=d_i.shape[1])
124
+ if np.random.binomial(1, self.aug_p):
125
+ d_ij[0] += np.random.normal(0, self.random_offset, size=1)
126
+ if np.random.binomial(1, self.aug_p):
127
+ n_outliers = np.random.randint(0, 10)
128
+ t_outliers = np.random.randint(0, self.input_len, n_outliers)
129
+ d_ij[0, t_outliers] += np.random.normal(0, self.random_outlier, n_outliers)
130
+ if np.random.binomial(1, self.aug_p):
131
+ d_ij[0] += self.random_drift[1] * np.cos(self.random_drift[0] * np.arange(self.input_len))
132
+ d_ij[0] = d_ij[0] * std_ij
133
+ self.data.append(d_ij)
134
+ self.data = np.asarray(self.data)
135
+
136
+ def __len__(self):
137
+ """
138
+ Returns the total number of samples.
139
+ """
140
+ return len(self.data)
141
+
142
+ def __getitem__(self, idx):
143
+ """
144
+ Generates one sample of data.
145
+
146
+ Parameters
147
+ ----------
148
+ idx : int
149
+ Index of the sample to retrieve.
150
+
151
+ Returns
152
+ -------
153
+ sample : dict
154
+ Dictionary containing 'input', 'target', and 'distance' tensors.
155
+ """
156
+ data_torch = torch.from_numpy(self.data[idx]).float()
157
+ sample = {'input': data_torch[0], 'target': data_torch[1], 'distance': data_torch[2]}
158
+ return sample
@@ -0,0 +1,97 @@
1
+ import torch
2
+ from torch import nn
3
+
4
+
5
+ class BCEDiceLoss(nn.Module):
6
+ """
7
+ A combination of Binary Cross Entropy (BCE) and Dice Loss for binary segmentation tasks.
8
+
9
+ Parameters
10
+ ----------
11
+ loss_params : tuple, optional
12
+ A tuple containing the weights for BCE and Dice losses respectively. Default is (1, 1).
13
+
14
+ Methods
15
+ -------
16
+ dice_loss(inputs, targets, epsilon=1e-6)
17
+ Computes the Dice loss.
18
+
19
+ forward(inputs, targets)
20
+ Computes the combined BCE and Dice loss.
21
+ """
22
+
23
+ def __init__(self, loss_params=(1, 1)):
24
+ super(BCEDiceLoss, self).__init__()
25
+ self.bce_loss = nn.BCEWithLogitsLoss()
26
+ self.loss_params = loss_params
27
+
28
+ def dice_loss(self, inputs, targets, epsilon=1e-6):
29
+ inputs = torch.sigmoid(inputs)
30
+ intersection = (inputs * targets).sum()
31
+ dice_coeff = (2. * intersection + epsilon) / (inputs.sum() + targets.sum() + epsilon)
32
+ return 1 - dice_coeff
33
+
34
+ def forward(self, inputs, targets):
35
+ bce = self.bce_loss(inputs, targets)
36
+ dice = self.dice_loss(inputs, targets)
37
+ return self.loss_params[0] * bce + self.loss_params[1] * dice
38
+
39
+
40
+ class SmoothnessLoss(nn.Module):
41
+ """
42
+ Computes the smoothness loss for a sequence of predictions.
43
+
44
+ Parameters
45
+ ----------
46
+ alpha : float, optional
47
+ Weight of the smoothness loss component. Default is 10.
48
+
49
+ Methods
50
+ -------
51
+ forward(predictions)
52
+ Computes the smoothness loss for a sequence of predictions.
53
+ """
54
+
55
+ def __init__(self, alpha=10):
56
+ super(SmoothnessLoss, self).__init__()
57
+ self.alpha = alpha
58
+
59
+ def forward(self, predictions):
60
+ if predictions.dim() < 3:
61
+ raise ValueError("The input tensor must be 3-dimensional.")
62
+ diffs = predictions[:, :, 1:] - predictions[:, :, :-1]
63
+ loss = torch.sum(diffs ** 2) / predictions.size(0)
64
+ return self.alpha * loss
65
+
66
+
67
+ def f1_score(logits, true_labels, threshold=0.5, epsilon=1e-7):
68
+ """
69
+ Computes the F1 score for binary classification.
70
+
71
+ Parameters
72
+ ----------
73
+ logits : torch.Tensor
74
+ The raw output from the model (before applying sigmoid).
75
+ true_labels : torch.Tensor
76
+ The ground truth binary labels.
77
+ threshold : float, optional
78
+ The threshold to convert probabilities to binary predictions. Default is 0.5.
79
+ epsilon : float, optional
80
+ A small value to avoid division by zero. Default is 1e-7.
81
+
82
+ Returns
83
+ -------
84
+ float
85
+ The computed F1 score.
86
+ """
87
+ probabilities = torch.sigmoid(logits)
88
+ predictions = probabilities > threshold
89
+ predictions = predictions.float()
90
+ true_labels = true_labels.float()
91
+ tp = (predictions * true_labels).sum().item()
92
+ fp = ((1 - true_labels) * predictions).sum().item()
93
+ fn = (true_labels * (1 - predictions)).sum().item()
94
+ precision = tp / (tp + fp + epsilon)
95
+ recall = tp / (tp + fn + epsilon)
96
+ f1_score = 2 * (precision * recall) / (precision + recall + epsilon)
97
+ return f1_score
@@ -0,0 +1,41 @@
1
+ import numpy as np
2
+ import torch
3
+
4
+ from .contraction_net import ContractionNet
5
+ from .utils import get_device
6
+
7
+ # select device
8
+ device = get_device()
9
+
10
+
11
+ def predict_contractions(data, model, network=ContractionNet):
12
+ """predict contraction intervals time-series with neural network
13
+
14
+ Parameters
15
+ ----------
16
+ data : ndarray
17
+ 1D array with time-series of contraction
18
+ model : str
19
+ trained model weights (.pt file)
20
+ network : nn.Module
21
+ Network to predict contractions from time-series
22
+ standard_normalizer : bool, ndarray
23
+ If False, each data is normalized by its mean and std. If True, the mean and std from the training data set are
24
+ applied. If ndarray, the data is normalized with the entered args [[input_mean, input_std], [vel_mean, vel_std]]
25
+ """
26
+ # load model state
27
+ state_dict = torch.load(model, map_location=device)
28
+ # initiate model
29
+ model = network(state_dict['n_filter'], in_channels=state_dict['in_channels'],
30
+ out_channels=state_dict['out_channels']).to(device)
31
+ model.load_state_dict(state_dict['state_dict'])
32
+ # resize data
33
+ len_data = data.shape[0]
34
+ contr = np.pad(data, (0, 32 - np.mod(len_data, 32)), mode='reflect')
35
+ # convert to torch
36
+ data = torch.from_numpy(contr.astype('float32')).view(1, 1, -1).to(device)
37
+ # predict data
38
+ res = model(data)[0][0]
39
+ # resize data and convert to numpy
40
+ res = res.detach().cpu().numpy()
41
+ return res[:, :len_data]