-
Notifications
You must be signed in to change notification settings - Fork 1
feature/added signals transformation and meaviewer #13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
igorcantele
wants to merge
22
commits into
Helveg:main
Choose a base branch
from
igorcantele:transform
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
83f7c81
setup slicing
igorcantele 6efeebe
testing completed
igorcantele 0043887
more tests on slicing and docs
igorcantele f7720bc
docs changes
igorcantele 67efa26
test changes
igorcantele a15c94b
black formatting + test slicing eliminated prints
igorcantele e238215
docs suggestions
igorcantele 1dd42b5
added examples in slicing function
igorcantele 80faa33
Merge branch 'slicing' of https://github.com/Igor10798/bwpy into slicing
igorcantele 0153146
Transformation added
igorcantele 573ff02
transformation added to data
igorcantele 342a49a
fixed slicing shaping, mea viewer class,
igorcantele b492a12
artifact detection
igorcantele ae3e770
created shutter, fixed architecture
igorcantele 11633c4
changed slice.data structure from [rows, cols, frames] to [frames, ro…
igorcantele c6ae332
meaviewer now accepts slice or np.arrays to display
igorcantele 2bc15db
shutter now automatically displays artifacts.
igorcantele e62eda7
Update bwpy/__init__.py
igorcantele 1415741
Apply suggestions from code review
igorcantele 030ce99
deleted raw (no necessary because slice now have the same structure o…
igorcantele c8d8d6f
merge
igorcantele 55971d3
Merge branch 'main' into transform
Helveg File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| from . import signal | ||
| import abc | ||
| from scipy.stats import norm | ||
|
|
||
|
|
||
| class Viewer(abc.ABC): | ||
| @abc.abstractmethod | ||
| def build_view(self, slice, wiew_method, window_size): | ||
| pass | ||
|
|
||
|
|
||
| class MEAViewer(Viewer): | ||
| colorscale = [[0, "#ebe834"], [1.0, "#eb4034"]] | ||
|
|
||
| def build_view( | ||
| self, file, slice=None, view_method="amplitude", window_size=100, data=None | ||
| ): | ||
| try: | ||
| import plotly.graph_objects as go | ||
| except: | ||
| raise ModuleNotFoundError( | ||
| "You have to install plotly in order to use MEAViewer." | ||
| ) | ||
|
|
||
| fig = go.Figure() | ||
| if slice and data: | ||
| raise ValueError("slice and data arguments are mutually exclusives.") | ||
| if slice: | ||
| apply_transformation = getattr(signal, view_method) | ||
| signals = apply_transformation(slice, window_size).data | ||
| else: | ||
| signals = data | ||
|
|
||
| max_val = self.get_up_bound(signals, file) | ||
| for signal_frame in signals: | ||
| fig.add_trace( | ||
| go.Heatmap( | ||
| visible=False, | ||
| z=signal_frame, | ||
| zmin=0, | ||
| zmax=max_val, | ||
| colorscale=self.colorscale, | ||
| ) | ||
| ) | ||
| return self.format_plot(fig) | ||
|
|
||
| def get_up_bound(self, data, file): | ||
| up_limit = file.convert(file.max_volt) * 0.98 | ||
| no_artifacts = data[data < up_limit] | ||
| mu, sd = norm.fit(no_artifacts.reshape(-1)) | ||
| return mu + 2 * sd | ||
|
|
||
| def format_plot(self, fig): | ||
| # Create and add slider | ||
| fig.data[0].visible = True | ||
| steps = [] | ||
| for i in range(len(fig.data)): | ||
| step = dict( | ||
| method="update", | ||
| args=[ | ||
| {"visible": [False] * len(fig.data)}, | ||
| {"title": "Time slice: " + str(i)}, | ||
| ], # layout attribute | ||
| ) | ||
| step["args"][0]["visible"][i] = True # Toggle i'th trace to "visible" | ||
| steps.append(step) | ||
|
|
||
| sliders = [ | ||
| dict(active=0, currentvalue={"prefix": "Time: "}, pad={"t": 50}, steps=steps) | ||
| ] | ||
|
|
||
| fig.update_layout( | ||
| yaxis=dict(scaleanchor="x", autorange="reversed"), sliders=sliders | ||
| ) | ||
| return fig | ||
|
|
||
| def show(self, fig): | ||
| fig.show() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,133 @@ | ||
| from . import mea_viewer | ||
| import re | ||
| import abc | ||
| import functools | ||
| import numpy as np | ||
| import numpy.lib.stride_tricks as np_tricks | ||
|
|
||
|
|
||
| __all__ = [] | ||
|
|
||
|
|
||
| def _transformer_factory(cls): | ||
| @functools.wraps(cls) | ||
| def transformer_factory(slice, *args, **kwargs): | ||
| return slice._transform(cls(*args, **kwargs)) | ||
|
|
||
| return transformer_factory | ||
|
|
||
|
|
||
| class Transformer(abc.ABC): | ||
| def __init_subclass__(cls, operator=None, **kwargs) -> None: | ||
| super().__init_subclass__(**kwargs) | ||
| name = operator or re.sub("([a-z0-9])([A-Z])", r"\1_\2", cls.__name__).lower() | ||
| globals()[name] = _transformer_factory(cls) | ||
| __all__.append(name) | ||
|
|
||
| @abc.abstractmethod | ||
| def __call__(self, data, file): | ||
| pass | ||
|
|
||
|
|
||
| class WindowedTransformer(Transformer): | ||
| def get_signal_window(self, data, window_size): | ||
| rows = data.shape[1] | ||
| cols = data.shape[2] | ||
| window_shape = (window_size, rows, cols) | ||
| # sliding window returns more complex shape like (num_windows, 1, 1, rows, cols, window_size) | ||
| # with the reshape we get rid of the unnecessary complexity (1, 1) | ||
igorcantele marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| return np_tricks.sliding_window_view(data, window_shape).reshape( | ||
| -1, window_size, rows, cols | ||
| ) | ||
|
|
||
|
|
||
| class Variation(WindowedTransformer): | ||
| def __init__(self, window_size): | ||
| self.window_size = window_size | ||
|
|
||
| def __call__(self, data, slice, file): | ||
| # If data have only 2 dimensions windowing is not necessary | ||
| if data.ndim == 2: | ||
| return data | ||
igorcantele marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| else: | ||
| windows = self.get_signal_window(data, self.window_size) | ||
| return np.max(np.abs(windows), axis=1) - np.min(np.abs(windows), axis=1) | ||
|
|
||
|
|
||
| class Amplitude(WindowedTransformer): | ||
| def __init__(self, window_size): | ||
| self.window_size = window_size | ||
|
|
||
| def __call__(self, data, slice, file): | ||
| # If data have only 2 dimensions windowing is not necessary | ||
| if data.ndim == 2: | ||
| return data | ||
| else: | ||
| windows = self.get_signal_window(data, self.window_size) | ||
| return np.max(np.abs(windows), axis=1) | ||
|
|
||
|
|
||
| class Energy(WindowedTransformer): | ||
| def __init__(self, window_size): | ||
| self.window_size = window_size | ||
|
|
||
| def __call__(self, data, slice, file): | ||
| if data.ndim == 2: | ||
| return data | ||
| else: | ||
| windows = self.get_signal_window(data, self.window_size) | ||
| return np.sum(np.square(windows), axis=1) | ||
|
|
||
|
|
||
| class NoMethod(Transformer): | ||
| def __call__(self, data, slice, file): | ||
| return np.moveaxis(data, 2, 0) | ||
|
|
||
|
|
||
| class Noop(Transformer): | ||
| """Noop that doesn't transform the data at all.""" | ||
|
|
||
| def __call__(self, data, slice, file): | ||
| return data | ||
|
|
||
|
|
||
| class DetectArtifacts(WindowedTransformer): | ||
| def __call__(self, data, slice, file): | ||
| # If data have only 2 dimensions windowing is not necessary | ||
| if data.ndim == 2: | ||
| return data | ||
| else: | ||
| up_limit = file.convert(file.max_volt) * 0.98 | ||
| out_bounds = data > up_limit | ||
| mask = np.sum(out_bounds, axis=(1, 2)) > 80 | ||
| return mask | ||
|
|
||
|
|
||
| class Shutter(Transformer): | ||
| def __init__(self, data, delay_ms, callable=None): | ||
| self.delay_ms = delay_ms | ||
| self.data = data | ||
| self.callable = callable | ||
|
|
||
| def __call__(self, mask, slice, file): | ||
| if mask.ndim > 1: | ||
| raise ValueError("mask must be a 1dim array.") | ||
|
|
||
| delay = self.ms_to_idx(file, self.delay_ms) | ||
| for i in range(len(mask) - 1, 0, -1): | ||
| if mask[i]: | ||
| mask[i : i + delay] = 1 | ||
| masked_data = self.data[mask] | ||
|
|
||
| window_size = self.ms_to_idx(file, 100) | ||
| mea_viewer.MEAViewer().build_view( | ||
| file, data=masked_data, view_method="no_method", window_size=window_size | ||
| ).show() | ||
|
|
||
| if self.callable: | ||
| return self.callable(self.data, mask) | ||
| else: | ||
| return masked_data | ||
|
|
||
| def ms_to_idx(self, file, delay_ms): | ||
| return int(delay_ms * file.sampling_rate * 1000) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This import can be removed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why?
I use it in the
Shutterfunction because it has to display by default.