Skip to content
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

Refactor Biosignal (1) #64

Open
wants to merge 47 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
8c1d4d8
Add derived biosignals: ACCMAG, IBI, RRI, HR
jomy-kk Feb 7, 2023
1a58476
Fix bug in Segment._partition
jomy-kk Feb 11, 2023
7db9af7
Add some quality indexes for EDA
jomy-kk Feb 12, 2023
26816e0
Add some quality indexes for HR
jomy-kk Feb 12, 2023
5f79c03
Add some quality indexes for PPG
jomy-kk Feb 12, 2023
0320e2a
Add some quality indexes for TEMP
jomy-kk Feb 12, 2023
6b7dc0c
Add Timeline
jomy-kk Feb 12, 2023
a29c76b
Remove segment copies on making new Segment and Timeseries
jomy-kk Feb 12, 2023
e827943
Fix tests regarding previous commit 1a58476a
jomy-kk Feb 12, 2023
1eb77cd
Fix visibility of 'aux_date' from E4
jomy-kk Feb 12, 2023
9a75e12
Add Biosignal.when and shortcuts for binary logical operations
jomy-kk Feb 12, 2023
983e2dd
Add Biosignal indexing with Timeline
jomy-kk Feb 12, 2023
37651ff
Fix Biosignal indexing with tuple
jomy-kk Feb 12, 2023
34682d1
Fix Biosignal.doamin_timeline (temporarly)
jomy-kk Feb 12, 2023
ca2ec85
Fix 'source' in binary Biosignal operations
jomy-kk Feb 12, 2023
21e8784
Add Biosignal.acquisition_scores and onbody score example for E4
jomy-kk Feb 12, 2023
d7f668e
Add DateTimeRange operability to Timeseries._indices_to_timepoints
jomy-kk Feb 12, 2023
84e0166
Remove compression on saving Biosignals (v. 2023.0)
jomy-kk Feb 12, 2023
c8ea213
Add union and intersection methods to Timeline
jomy-kk Mar 7, 2023
5467c50
Fix semantic bug in E4.onbody
jomy-kk Mar 7, 2023
5fa6871
Refactoring modules (1)
jomy-kk Mar 27, 2023
6a5dba3
Add BOXEN option
jomy-kk Mar 27, 2023
500a40c
Update docs and changelog
jomy-kk Mar 27, 2023
7308e56
Remove processing, features, ml, decision and pipeline packages
jomy-kk Jun 7, 2023
ef87b4a
Major refactor (1)
jomy-kk Jun 21, 2023
be1e15d
More tests
jomy-kk Aug 30, 2023
b6df3cd
Add biosignals and clinical to public interface
jomy-kk Oct 23, 2023
77c4037
Moved old test suits
jomy-kk Oct 23, 2023
41164af
Fix Segment test set properties
jomy-kk Oct 23, 2023
7112e71
Fix Segment test serialization
jomy-kk Oct 23, 2023
a7d2a7f
Add Segment test statistics
jomy-kk Oct 23, 2023
179c470
Add 2 Segment processing tests
jomy-kk Oct 23, 2023
464e653
Fix Segment logics tests
jomy-kk Oct 23, 2023
1a3c858
Add Segment joining tests
jomy-kk Oct 23, 2023
71b4232
Add 1 Segment initializer tests
jomy-kk Oct 23, 2023
fe6ac45
Fix Segment arithmetic tests
jomy-kk Oct 23, 2023
30362ad
Add more custom Exceptions
jomy-kk Oct 23, 2023
75a02e0
Fix units -> unit
jomy-kk Oct 23, 2023
e589415
Fix Segment + add statistics
jomy-kk Oct 23, 2023
50dc343
Fix Timeseries + add default constructor
jomy-kk Oct 23, 2023
6cf64a3
Add Timeseries initializers tests
jomy-kk Oct 23, 2023
4b42d12
Fix get properties tests for Timeseries
jomy-kk Oct 28, 2023
96a089e
Add get properties tests for Timeseries
jomy-kk Oct 28, 2023
dda5168
Add tests for set properties in Timeseries
jomy-kk Oct 28, 2023
1cfad16
Fix tests for set properties in Timeseries
jomy-kk Oct 30, 2023
be64716
Add tests for Timeseries built-ins
jomy-kk Oct 30, 2023
ce2f573
Add tests for Timeseries indexing
jomy-kk Nov 30, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions docs/changelog/pythonversions/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,10 @@ _Released: 31-05-2022 | Created: 01-02-2022 | **Not Public**_

### Added

* Abstract classes `Biosignal` and `BiosignalSource`, and some concrete implementations in the sub-packages
* Abstract classes `Biosignal` and `__BiosignalSource`, and some concrete implementations in the sub-packages
`modalities` and `sources`, respectively.
* Classes `Timeseries`, `Segment`, `Unit`, `Event`.
* Packages `clinical`: classes `Patient`, `BodyLocation`, `MedicalCondition`, `Medication`, and `SurgicalProcedure`.
* Package `processing`: classes `Segmenter` and `Filter`.
* Package `features`: classes `FeatureExtractor` and `FeatureSelector`.
* Package `ml`: classes `SupervisedModel`, `SupervisedTrainConditions`, `SurpervisingTrainer`, `SupervisedTrainResults`.

18 changes: 0 additions & 18 deletions docs/changelog/serialversions/Biosignal.md
Original file line number Diff line number Diff line change
@@ -1,18 +0,0 @@
# Biosignal

## Serial Version 1

_Date Created: 01-06-2022_

```
(SERIALVERSION, name, source, patient, acquisition_location, associated_events, timeseries)
```

* `SERIALVERSION` equals 1.
* `name` is a `str` with the value of the biosignal's `__name` attribute.
* `source` is a `BiosignalSource` class, or the state of a `BiosignalSource` object, based on the value of the biosignal's `__source` attribute.
* `patient` is the state of the `Patient` referenced in the biosignal's `__patient` attribute.
* `acquisition_location` is a `BodyLocation` with the value of the biosignal's `__acquisition_location` attribute.
* `associated_events` is a tuple of the states of all `Event`s' referenced in the biosignal's `__associated_events` attribute.
* `timeseries` is a dictionary of the states of all `Timeseries`s' referenced in the biosignal's `__timeseries` attribute.

16 changes: 0 additions & 16 deletions docs/changelog/serialversions/BiosignalSource.md
Original file line number Diff line number Diff line change
@@ -1,16 +0,0 @@
# BiosignalSource

`BiosignalSource` is usually not instantiated as an object, so there are no states to serialize.
However, there are some sources that are instantiated, e.g., `Sense`, `Bitalino`. In these cases, the following serial versions apply.

## Serial Version 1

_Date Created: 01-06-2022_

```
(SERIALVERSION, others)
```

* `SERIALVERSION` equals 1.
* `others` is a dictionary of properties an instantiated `BiosignalSource` object may have.

4 changes: 2 additions & 2 deletions docs/changelog/serialversions/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Below you can check the current and past versions of the .biosignal files (first
+------------------------------+------------+------------+------------+---------------------+
| :code:`Biosignal` | 1 | 1 | 1 | **2** |
+------------------------------+------------+------------+------------+---------------------+
| :code:`BiosignalSource` | 1 | 1 | 1 | 1 |
| :code:`__BiosignalSource` | 1 | 1 | 1 | 1 |
+------------------------------+------------+------------+------------+---------------------+
| :code:`Timeseries` | 1 | **2** | 2 | 2 |
+------------------------------+------------+------------+------------+---------------------+
Expand Down Expand Up @@ -37,7 +37,7 @@ Any Biosignal and associated objects are stateful, so that they can be serialize
:maxdepth: 1

Biosignal
BiosignalSource
__BiosignalSource

.. tip::
How this structure is created can be inspected in more detail in the methods :code:`__getstate__` and :code:`__setstate__` of each of these class.
18 changes: 9 additions & 9 deletions docs/learn/basic/ltbio101.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,16 @@ For example, to instantiate an `ECG` from a file, one could use the following in
ecg = ECG('pathToFile', HSM, name='My First Biosignal')
```

In the above example, `HSM` is the `BiosignalSource` representing "Hospital de Santa Maria" 🏥, a hospital in Lisbon, Portugal.
In the above example, `HSM` is the `__BiosignalSource` representing "Hospital de Santa Maria" 🏥, a hospital in Lisbon, Portugal.
This class _knows_ how to read biosignals from EDF files collected at HSM.
As depicted in the above Figure, there can be as many `BiosignalSource` subclasses as the user needs. The ones you see are just examples,
As depicted in the above Figure, there can be as many `__BiosignalSource` subclasses as the user needs. The ones you see are just examples,
like [ScientISST](scientisst.com/sense) and [BITalino](https://www.pluxbiosignals.com/collections/bitalino) devices, public databases like
MITDB and Seer, and many others. A `BiosignalSource` is an entity with knowledge about where (devices, hospitals, databases, etc.) and how
MITDB and Seer, and many others. A `__BiosignalSource` is an entity with knowledge about where (devices, hospitals, databases, etc.) and how
biosignals are acquired. It has static procedures to ease the reading of biosignals from files of that source, and the respective patient
metadata, clinical records, and event annotations. These have their own classes as well, as shall be described ahead.
Other sources can be easily implemented by deriving `BiosignalSource`. This scalable property is of vital importance, since biosignal
Other sources can be easily implemented by deriving `__BiosignalSource`. This scalable property is of vital importance, since biosignal
researchers get data from a large variety of sources that increases by the day. Hence, you have the possibility of working with data from
new sources only by creating your own `BiosignalSource`, therefore personalising the framework to your research needs.
new sources only by creating your own `__BiosignalSource`, therefore personalising the framework to your research needs.

------------

Expand All @@ -58,7 +58,7 @@ _Biosignals_ to modify themselves, without you having to remember their metadata

**The Concept as a Class:** A `Biosignal` object is a non-empty set of channels measuring one biological or physiological variable. Each
channel is represented by a `Timeseries` object (see UML above). Optionally, it may also have an associated `Patient`, an associated
`BodyLocation`, one ore more associated `Event`s, an associated `BiosignalSource`, and a name. All this metadata is introduced later; for now it's crucial you understand how
`BodyLocation`, one ore more associated `Event`s, an associated `__BiosignalSource`, and a name. All this metadata is introduced later; for now it's crucial you understand how
channels and samples are organised.

------------
Expand Down Expand Up @@ -96,7 +96,7 @@ endevours. If you want to have it all in one place regarding one biosignal (and
with more properties:

* 🤕 **Patient**: An object `Patient` where you can drop any information regarding the
patient that can be useful to process the biosignal. If reading from a file or fetching a database, `BiosignalSource` might fill this property
patient that can be useful to process the biosignal. If reading from a file or fetching a database, `__BiosignalSource` might fill this property
automatically for you.

* 🧍‍♀️ **Location**: An object `BodyLocation` to remember where the sensors were placed.
Expand Down Expand Up @@ -128,7 +128,7 @@ You have three ways 🤯 of instantiating a `Biosignal`.

### Way 1: Instantiate from files

Give the path to the directory where the files are located and specify the source (`BiosignalSource`) from where the files come from:
Give the path to the directory where the files are located and specify the source (`__BiosignalSource`) from where the files come from:
```
biosignal = ECG("path_to_files", HSM)
```
Expand All @@ -139,7 +139,7 @@ you'll want to create your own source.

### Way 2: Instantiate from a Database [comming 🔜]

Give the patient code, the source (`BiosignalSource`) corresponding to a database, and the interval of time (in tuple) you want to fetch from the database:
Give the patient code, the source (`__BiosignalSource`) corresponding to a database, and the interval of time (in tuple) you want to fetch from the database:
```
biosignal = ECG(patient_code=101, source=HSM, ('2022-01-01 16:00', '2022-01-01 17:30'))
```
Expand Down
4 changes: 2 additions & 2 deletions docs/learn/basic/properties.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Enrich your Biosignals

You have noticed by now that ``Biosignal`` objects gather all information about a biosignal in an holistic way, and not just the data
samples. But ``Biosignal`` objects will only hold the information you give or that their `BiosignalSource` deducted.
samples. But ``Biosignal`` objects will only hold the information you give or that their `__BiosignalSource` deducted.

## Print to see me

Expand Down Expand Up @@ -145,7 +145,7 @@ You can **get** any of the following properties of a `Biosignal`:
* `channel_names` returns a set with the channel labels (in `string` or `BodyLocation`).
* `sampling_frequency` returns the the sampling frequency of every channel, if equal (in `float`).
* `acquisition_location` returns the body location where the biosignal was acquired (in `BodyLocation`).
* `source` returns the source where the Biosignal was acquired: hospital, device, etc. (in `BiosignalSource`).
* `source` returns the source where the Biosignal was acquired: hospital, device, etc. (in `__BiosignalSource`).
* `patient_code` returns the code of the patient whose the biosignal belongs (in `int` or `string`).
* `type` returns the biosignal modality (in any `Biosignal` subclass).
* `initial_datetime` returns the initial datetime of the channel that starts the earliest (in `datetime`).
Expand Down
2 changes: 0 additions & 2 deletions resources/config.ini
Original file line number Diff line number Diff line change
@@ -1,2 +0,0 @@
[DEFAULT]
Sense = /Users/saraiva/Desktop/LongTermBiosignals/resources/Sense_CSV_tests/sense_defaults.json
2 changes: 2 additions & 0 deletions src/ltbio/__init__.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

__all__ = ["biosignals", "clinical"]
12 changes: 12 additions & 0 deletions src/ltbio/_core/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# -- encoding: utf-8 --
# ===================================
# ScientISST LTBio | Long-Term Biosignals

# Package:
# Module:
# Description:

# Contributors: João Saraiva
# Created:
# Last Updated:
# ===================================
145 changes: 145 additions & 0 deletions src/ltbio/_core/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
# -- encoding: utf-8 --
# ===================================
# ScientISST LTBio | Long-Term Biosignals
from datetime import datetime

from datetimerange import DateTimeRange

#from ltbio.biosignals._Timeline import Timeline
#from ltbio.biosignals._Timeseries import Timeseries
#from ltbio.biosignals.units import Unit, Frequency
#from ltbio.clinical import BodyLocation


# Package:
# Module:
# Description:

# Contributors: João Saraiva
# Created:
# Last Updated:
# ===================================


class TimeseriesError(Exception):
def __init__(self, why: str):
super().__init__(why)


class EmptyTimeseriesError(TimeseriesError):
def __init__(self):
super().__init__(f"Trying to create a Timeseries with no samples.")




class IncompatibleTimeseriesError(Exception):
def __init__(self, why: str):
super().__init__(f"These Timeseries are incompatible because {why}")


class DifferentSamplingFrequenciesError(IncompatibleTimeseriesError):
def __init__(self, *frequencies):
super().__init__(f"these different sampling frequencies were found: {','.join(frequencies)}. "
f"Try to resample first.")


class DifferentUnitsError(IncompatibleTimeseriesError):
def __init__(self, *units):
super().__init__(f"these different units were found: {','.join(units)}. "
f"Try to convert first.")


class DifferentDomainsError(IncompatibleTimeseriesError):
def __init__(self, *timelines):
note = "they have different domains: "
note += '; '.join([f"({i+1}): {domain}" for i, domain in enumerate(timelines)])
super().__init__(note)


class IncompatibleBiosignalsError(Exception):
def __init__(self, why: str):
super().__init__(f"These Biosignals are incompatible because {why}")


class DifferentPatientsError(IncompatibleTimeseriesError):
def __init__(self, first, second):
super().__init__(f"at least two different patients were found: {first} and {second}. "
f"Try to drop the patients first.")

class SegmentError(Exception):
def __intit__(self, description: str):
super().__init__(description)

class NotASegmentError(SegmentError):
def __init__(self, segment, intend_use=""):
super().__init__(f"{type(segment)} is not a segment. {intend_use}")

class SamplesNotValidError(SegmentError):
def __init__(self, samples, why):
super().__init__(f"Samples are not valid, because {why}.")

class EmptySegmentError(SegmentError):
def __init__(self):
super().__init__(f"Trying to create a Segment with no samples.")

class IncompatibleSegmentsError(Exception):
def __init__(self, why: str):
super().__init__(f"These Segments are incompatible because {why}")

class DifferentLengthsError(IncompatibleSegmentsError):
def __init__(self, first: int, second: int):
super().__init__(f"the first has length {first} and the second has length {second}.")


class TimeError(Exception):
...


class ChannelsWithDifferentStartTimepointsError(TimeError):
def __init__(self, first_name, first_start, second_name, second_start, additional: str = ''):
super().__init__(f"{first_name} starts at {first_start} and {second_name} starts at {second_start}. " + additional)


class OverlappingError(TimeError):
def __init__(self, what: str):
super().__init__(f"There is an overlap between {what}")


class OverlappingSegmentsError(TimeseriesError, OverlappingError):
def __init__(self, first_start: datetime, first_end: datetime, second_start: datetime, second_end: datetime):
super().__init__(f"two segments to be added to the Timeseries. "
f"First Segment starts at {first_start} and ends at {first_end}. "
f"Second Segment starts at {second_start} and ends at {second_end}.")

class TimeseriesOverlappingError(OverlappingError):
def __init__(self, first, second, *overlap: DateTimeRange):
super().__init__(f"Timeseries {first} and Timeseries {second}" + f" on {overlap}." if overlap else ".")


class OperationError(Exception):
...


class UndoableOperationError(OperationError):
def __init__(self, operation, by_nature: bool):
note = f"Operation {operation} is undoable"
if by_nature:
note += " by nature, i.e. there is no mathematical way of reversing it or, at least, it's not implemented."
else:
note += ", most likely because this operation was what created this object."
super().__init__(note)


class BiosignalError(Exception):
...


class ChannelNotFoundError(BiosignalError, IndexError, AttributeError):
def __init__(self, name):
super().__init__(f"There is no channel named '{name}'.")


class EventNotFoundError(BiosignalError, IndexError, AttributeError):
def __init__(self, name: str):
super().__init__(f"There is no event named '{name}'.")
Loading
Loading