labda 0.0.2__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.
Files changed (46) hide show
  1. CHANGELOG.md +16 -0
  2. labda/__init__.py +1 -0
  3. labda/assets/__init__.py +2 -0
  4. labda/assets/counts_cut_points.py +35 -0
  5. labda/assets/transportation_cut_points.py +16 -0
  6. labda/expanders/__init__.py +8 -0
  7. labda/expanders/accelerometer.py +29 -0
  8. labda/expanders/extras.py +33 -0
  9. labda/expanders/spatial.py +242 -0
  10. labda/parsers/__init__.py +4 -0
  11. labda/parsers/actigraph.py +232 -0
  12. labda/parsers/bulk.py +31 -0
  13. labda/parsers/gadgetbridge.py +232 -0
  14. labda/parsers/garmin.py +89 -0
  15. labda/parsers/qstarz.py +366 -0
  16. labda/processing/__init__.py +1 -0
  17. labda/processing/accelerometer/__init__.py +2 -0
  18. labda/processing/accelerometer/activity_intensity.py +57 -0
  19. labda/processing/accelerometer/steps.py +24 -0
  20. labda/processing/accelerometer/wear.py +38 -0
  21. labda/processing/bouts.py +85 -0
  22. labda/processing/domains.py +32 -0
  23. labda/processing/manipulations.py +22 -0
  24. labda/processing/spatial/__init__.py +4 -0
  25. labda/processing/spatial/environment.py +50 -0
  26. labda/processing/spatial/manipulations.py +186 -0
  27. labda/processing/spatial/timeline.py +99 -0
  28. labda/processing/spatial/transportation.py +80 -0
  29. labda/processing/spatial/trips.py +551 -0
  30. labda/processing/wear_validity.py +69 -0
  31. labda/structure/__init__.py +6 -0
  32. labda/structure/collection.py +103 -0
  33. labda/structure/domains.py +29 -0
  34. labda/structure/linkage.py +28 -0
  35. labda/structure/merging.py +133 -0
  36. labda/structure/resampling.py +82 -0
  37. labda/structure/subject.py +294 -0
  38. labda/structure/validation/__init__.py +0 -0
  39. labda/structure/validation/domains.py +37 -0
  40. labda/structure/validation/linkage.py +28 -0
  41. labda/structure/validation/subject.py +323 -0
  42. labda/utils.py +240 -0
  43. labda/visualisation/__init__.py +0 -0
  44. labda-0.0.2.dist-info/METADATA +59 -0
  45. labda-0.0.2.dist-info/RECORD +46 -0
  46. labda-0.0.2.dist-info/WHEEL +4 -0
@@ -0,0 +1,103 @@
1
+ import multiprocessing as mp
2
+ import secrets
3
+ from functools import partial
4
+ from pathlib import Path
5
+ from typing import Optional
6
+
7
+ from pydantic import BaseModel, Field
8
+
9
+ from .subject import Subject
10
+
11
+
12
+ def _process_subject(subject, func, **kwargs):
13
+ func(subject, **kwargs)
14
+
15
+ return subject
16
+
17
+
18
+ class Collection(BaseModel):
19
+ id: Optional[str] = Field(default_factory=lambda: secrets.token_hex(4))
20
+ subjects: list[Subject] = Field(default_factory=list)
21
+
22
+ def __repr__(self):
23
+ subjects_repr = ", ".join([p.metadata.id for p in self.subjects]) # type: ignore
24
+ return f"Collection(id={self.id}, subjects=[{subjects_repr}])"
25
+
26
+ def add_subject(self, subject: Subject):
27
+ subjects_ids = [s.metadata.id for s in self.subjects]
28
+
29
+ if subject.metadata.id in subjects_ids:
30
+ raise ValueError(
31
+ f"Subject with id '{subject.metadata.id}' already exists in collection."
32
+ )
33
+
34
+ subject.collection = self.id # type: ignore
35
+ self.subjects.append(subject) # type: ignore
36
+
37
+ def get_subject(self, id: str) -> Subject:
38
+ for subject in self.subjects:
39
+ if subject.metadata.id == id:
40
+ return subject
41
+ raise ValueError(f"Subject with id '{id}' not found.")
42
+
43
+ @classmethod
44
+ def from_folder(cls, path: str | Path, id: str | None = None) -> "Collection":
45
+ if isinstance(path, str):
46
+ path = Path(path)
47
+
48
+ subjects = [Subject.from_parquet(file) for file in path.glob("*.parquet")]
49
+ return cls(id=id, subjects=subjects)
50
+
51
+ def to_folder(self, path: str | Path, overwrite: bool = False):
52
+ if isinstance(path, str):
53
+ path = Path(path)
54
+
55
+ path.mkdir(parents=True, exist_ok=True)
56
+ for subject in self.subjects:
57
+ subject.to_parquet(
58
+ path / f"{subject.metadata.id}.parquet", overwrite=overwrite
59
+ )
60
+
61
+ def detect_trips(self, **kwargs):
62
+ with mp.Pool(mp.cpu_count()) as pool:
63
+ self.subjects = pool.map(
64
+ partial(_process_subject, func=Subject.detect_trips, **kwargs),
65
+ self.subjects,
66
+ )
67
+
68
+ def detect_activity_intensity(self, **kwargs) -> None:
69
+ with mp.Pool(mp.cpu_count()) as pool:
70
+ self.subjects = pool.map(
71
+ partial(
72
+ _process_subject, func=Subject.detect_activity_intensity, **kwargs
73
+ ),
74
+ self.subjects,
75
+ )
76
+
77
+ # TODO: Rework whole consistency check
78
+ # TODO: Add also check columns consistency
79
+ def _check_consistency(self, attribute, error_message):
80
+ # TODO: Add docstring, better name for method
81
+ values = [getattr(subject.metadata, attribute) for subject in self.subjects]
82
+ unique = set(values)
83
+
84
+ if len(unique) != 1:
85
+ print(f"{error_message}: {unique}")
86
+
87
+ def _check_consistent_sampling_frequencies(self):
88
+ self._check_consistency(
89
+ "sampling_frequency", "Sampling frequencies are not consistent"
90
+ )
91
+
92
+ def _check_consistent_crs(self):
93
+ self._check_consistency("crs", "CRS are not consistent")
94
+
95
+ def _check_consistent_timezones(self):
96
+ self._check_consistency("timezone", "Timezones are not consistent")
97
+
98
+ def check_consistency(self):
99
+ self._check_consistent_sampling_frequencies()
100
+ self._check_consistent_crs()
101
+ self._check_consistent_timezones()
102
+
103
+ print("Consistency check finished.")
@@ -0,0 +1,29 @@
1
+ from pathlib import Path
2
+
3
+ import pandas as pd
4
+ from shapely import wkt
5
+
6
+ from .validation.domains import SCHEMA
7
+
8
+
9
+ def from_csv(path: str | Path) -> pd.DataFrame:
10
+ # TODO: Check datetime overlap.
11
+ # TODO: Add column for "importance" in case of overlapping domains.
12
+ if isinstance(path, str):
13
+ path = Path(path)
14
+
15
+ domains = pd.read_csv(
16
+ path,
17
+ dtype={
18
+ "subject_id": "string",
19
+ "domain": "string",
20
+ "geometry": "string",
21
+ },
22
+ delimiter=";",
23
+ parse_dates=["start", "end"],
24
+ date_format="%d-%m-%Y %H:%M:%S",
25
+ )
26
+
27
+ domains["geometry"] = domains["geometry"].apply(wkt.loads)
28
+
29
+ return SCHEMA.validate(domains)
@@ -0,0 +1,28 @@
1
+ from pathlib import Path
2
+
3
+ import pandas as pd
4
+ from shapely import wkt
5
+
6
+ from .validation.linkage import SCHEMA
7
+
8
+
9
+ def from_csv(path: str | Path) -> pd.DataFrame:
10
+ if isinstance(path, str):
11
+ path = Path(path)
12
+
13
+ df = pd.read_csv(
14
+ path,
15
+ dtype={
16
+ "subject_id": "string",
17
+ "sensor_id": "string",
18
+ },
19
+ delimiter=";",
20
+ )
21
+
22
+ if "start" in df.columns:
23
+ df["start"] = pd.to_datetime(df["start"])
24
+
25
+ if "end" in df.columns:
26
+ df["end"] = pd.to_datetime(df["end"])
27
+
28
+ return SCHEMA.validate(df)
@@ -0,0 +1,133 @@
1
+ import pandas as pd
2
+
3
+ from .collection import Collection
4
+ from .subject import Metadata, Subject
5
+
6
+
7
+ def _check_duplicate_cols(left: pd.DataFrame, right: pd.DataFrame) -> None:
8
+ duplicate_cols = set(left.columns) & set(right.columns)
9
+ if duplicate_cols:
10
+ raise ValueError(f"Before merging, remove duplicate columns: {duplicate_cols}")
11
+
12
+
13
+ def _check_ids(left: Subject, right: Subject) -> str:
14
+ if left.metadata.id != right.metadata.id:
15
+ raise ValueError(f"IDs do not match: {left.metadata.id} != {right.metadata.id}")
16
+
17
+ return left.metadata.id
18
+
19
+
20
+ def _check_sampling_frequency(left: Subject, right: Subject) -> float:
21
+ if left.metadata.sampling_frequency != right.metadata.sampling_frequency:
22
+ raise ValueError(
23
+ f"Sampling frequency mismatch: {left.metadata.sampling_frequency} != {right.metadata.sampling_frequency}"
24
+ )
25
+
26
+ return left.metadata.sampling_frequency
27
+
28
+
29
+ def _check_timezones(left: Subject, right: Subject) -> str | None:
30
+ if (
31
+ (left.metadata.timezone)
32
+ and (right.metadata.timezone)
33
+ and left.metadata.timezone != right.metadata.timezone
34
+ ):
35
+ raise ValueError(
36
+ f"Timezone mismatch: {left.metadata.timezone} != {right.metadata.timezone}"
37
+ )
38
+
39
+ return left.metadata.timezone or right.metadata.timezone
40
+
41
+
42
+ def _check_crs(left: Subject, right: Subject) -> str | None:
43
+ if (
44
+ (left.metadata.crs)
45
+ and (right.metadata.crs)
46
+ and left.metadata.crs != right.metadata.crs
47
+ ):
48
+ raise ValueError(f"CRS mismatch: {left.metadata.crs} != {right.metadata.crs}")
49
+
50
+ return left.metadata.crs or right.metadata.crs
51
+
52
+
53
+ def _get_subjects(collection: Collection) -> list[str]:
54
+ return [subject.metadata.id for subject in collection.subjects]
55
+
56
+
57
+ def merge_subjects(left: Subject, right: Subject, **kwargs) -> Subject:
58
+ if not isinstance(left, Subject) and not isinstance(right, Subject):
59
+ raise ValueError(f"Unsupported types: {type(left)} and {type(right)}")
60
+
61
+ id = _check_ids(left, right)
62
+ sf = _check_sampling_frequency(left, right)
63
+ tz = _check_timezones(left, right)
64
+ crs = _check_crs(left, right)
65
+
66
+ metadata = Metadata(
67
+ id=id,
68
+ sensor=left.metadata.sensor + right.metadata.sensor,
69
+ sampling_frequency=sf,
70
+ timezone=tz,
71
+ crs=crs,
72
+ )
73
+
74
+ # FIXME: Add suffixes to columns, even if not duplicated
75
+ if not kwargs.get("suffixes"):
76
+ _check_duplicate_cols(left.df, right.df)
77
+ merged = pd.merge(left.df, right.df, left_index=True, right_index=True, **kwargs)
78
+
79
+ return Subject(metadata=metadata, df=merged)
80
+
81
+
82
+ def merge_collections(
83
+ left: Collection,
84
+ right: Collection,
85
+ *,
86
+ keep: bool = False,
87
+ **kwargs,
88
+ ) -> Collection:
89
+ if not isinstance(left, Collection) and not isinstance(right, Collection):
90
+ raise ValueError(f"Unsupported types: {type(left)} and {type(right)}")
91
+
92
+ left_ids = _get_subjects(left)
93
+ right_ids = _get_subjects(right)
94
+ subjects = set(left_ids + right_ids)
95
+
96
+ collection = Collection()
97
+
98
+ for subject in subjects:
99
+ try:
100
+ left_subject = left.get_subject(subject)
101
+ except Exception:
102
+ left_subject = None
103
+
104
+ try:
105
+ right_subject = right.get_subject(subject)
106
+ except Exception:
107
+ right_subject = None
108
+
109
+ if left_subject and right_subject:
110
+ merged_subject = merge_subjects(left_subject, right_subject, **kwargs)
111
+ collection.add_subject(merged_subject)
112
+ else:
113
+ print(
114
+ f"Error while merging | {subject} | Subject not found in both collections."
115
+ )
116
+
117
+ # FIXME: Fix, it is not working
118
+ # FIXME: Suffix to check if exists
119
+ # FIXME: Native pd.merge function adds suffixes to columns with the same name otherwise not (no suffixe and we don't know the origin of the column)
120
+
121
+ if keep:
122
+ if left_subject:
123
+ # suffix = kwargs.get("suffixes")
124
+ # if suffix:
125
+ # left_subject.df = left_subject.df.add_suffix(suffix[0])
126
+ collection.add_subject(left_subject)
127
+ elif right_subject:
128
+ # suffix = kwargs.get("suffixes")
129
+ # if suffix:
130
+ # right_subject.df = right_subject.df.add_suffix(suffix[1])
131
+ collection.add_subject(right_subject)
132
+
133
+ return collection
@@ -0,0 +1,82 @@
1
+ from typing import Any
2
+
3
+ import pandas as pd
4
+
5
+ from ..structure.validation.subject import Column
6
+
7
+ # TODO: Create one big configuration with SCHEMA (validation) and RESAMPLING
8
+
9
+
10
+ def mode(x):
11
+ vals = x.to_list()
12
+ return max(vals, key=vals.count)
13
+
14
+
15
+ UPSAMPLING = [
16
+ {"column": Column.SUBJECT_ID, "method": "first"},
17
+ {"column": Column.WEAR, "method": mode},
18
+ {"column": Column.TIMEDELTA, "method": "sum"},
19
+ # ---
20
+ {"column": Column.VERTICAL_COUNTS, "method": "sum"},
21
+ {"column": Column.HORIZONTAL_COUNTS, "method": "sum"},
22
+ {"column": Column.PERPENDICULAR_COUNTS, "method": "sum"},
23
+ {"column": Column.VM_COUNTS, "method": "mean"},
24
+ {"column": Column.POSITION, "method": mode},
25
+ {"column": Column.STEPS, "method": "sum"},
26
+ # ---
27
+ {"column": Column.LATITUDE, "method": "first"},
28
+ {"column": Column.LONGITUDE, "method": "first"},
29
+ {"column": Column.NSAT_VIEWED, "method": "first"},
30
+ {"column": Column.NSAT_USED, "method": "first"},
31
+ {"column": Column.NSAT_RATIO, "method": "first"},
32
+ {"column": Column.SNR_VIEWED, "method": "first"},
33
+ {"column": Column.SNR_USED, "method": "first"},
34
+ {"column": Column.PDOP, "method": "first"},
35
+ {"column": Column.HDOP, "method": "first"},
36
+ {"column": Column.VDOP, "method": "first"},
37
+ # ---
38
+ {"column": Column.DISTANCE, "method": "sum"},
39
+ {"column": Column.ELEVATION, "method": "first"},
40
+ {"column": Column.SPEED, "method": "mean"},
41
+ {"column": Column.ACCELERATION, "method": "mean"},
42
+ {"column": Column.DIRECTION, "method": "first"},
43
+ {"column": Column.ENVIRONMENT, "method": mode},
44
+ # ---
45
+ {"column": Column.HEART_RATE, "method": "mean"},
46
+ {"column": Column.LUX, "method": "mean"},
47
+ {"column": Column.ACTIVITY_INTENSITY, "method": mode},
48
+ ]
49
+
50
+
51
+ def upsample(
52
+ df: pd.DataFrame,
53
+ from_sf: float,
54
+ to_sf: float,
55
+ mapper: list[dict[str, Any]] | None = None,
56
+ ) -> pd.DataFrame:
57
+ if from_sf > to_sf:
58
+ raise ValueError(f"Downsampling is not supported | {from_sf} > {to_sf}")
59
+ elif from_sf == to_sf:
60
+ return df
61
+
62
+ df = df.copy()
63
+
64
+ if not mapper:
65
+ mapper = UPSAMPLING
66
+
67
+ resampling = {v["column"]: v["method"] for v in mapper if v["column"] in df.columns}
68
+
69
+ dropped = [
70
+ colname for colname in df.columns if colname not in resampling.keys()
71
+ ] # TODO: Check that it works properly.
72
+ if dropped:
73
+ print(
74
+ f"Columns that are not in the resampling configuration will be dropped | Dropping: {dropped}"
75
+ ) # TODO: Write warning what columns are dropped.
76
+
77
+ df = df.resample(f"{to_sf}s").agg(resampling) # type: ignore
78
+ df.dropna(
79
+ axis=0, how="all", inplace=True
80
+ ) # Dropping rows with all NaN values - they were created by resampling.
81
+
82
+ return df
@@ -0,0 +1,294 @@
1
+ from datetime import timedelta
2
+ from enum import StrEnum
3
+ from pathlib import Path
4
+ from typing import TYPE_CHECKING, Any, Optional
5
+
6
+ if TYPE_CHECKING:
7
+ # This is a workaround to avoid circular imports.
8
+ # TODO: Is there a better way to do this?
9
+ from .collection import Collection
10
+
11
+ import pandas as pd
12
+ import pyarrow as pa
13
+ import pyarrow.parquet as pq
14
+ from pydantic import BaseModel, Field
15
+
16
+ from ..expanders import (
17
+ add_acceleration,
18
+ add_direction,
19
+ add_distance,
20
+ add_speed,
21
+ add_timedelta,
22
+ )
23
+ from ..processing import detect_domains
24
+ from ..processing.accelerometer import detect_activity_intensity, detect_wear
25
+ from ..processing.spatial import detect_transportation, detect_trips, get_timeline
26
+ from ..structure.resampling import upsample
27
+ from ..utils import columns_exists, columns_not_exists
28
+ from .validation.domains import SCHEMA as DOMAINS_SCHEMA
29
+ from .validation.subject import SCHEMA, Column
30
+
31
+
32
+ class Vendor(StrEnum):
33
+ ACTIGRAPH = "ActiGraph"
34
+ XIAOMI = "Xiaomi"
35
+ SENS = "SENS"
36
+ GARMIN = "Garmin"
37
+ QSTARZ = "Qstarz"
38
+ GGIR = "GGIR"
39
+ SENSECAP = "SenseCap"
40
+ ANDROID = "Android"
41
+ IOS = "iOS"
42
+
43
+
44
+ class Sensor(BaseModel):
45
+ id: str
46
+ serial_number: str | None = None
47
+ model: str | None = None
48
+ vendor: Vendor | None = None
49
+ firmware_version: str | None = None
50
+ extra: dict[str, Any] | None = None
51
+
52
+ class Config:
53
+ coerce_numbers_to_str = True
54
+
55
+
56
+ class Metadata(BaseModel):
57
+ id: str
58
+ sensor: list[Sensor] = Field(default_factory=list)
59
+ sampling_frequency: float = Field(ge=0, description="Sampling frequency in seconds")
60
+ crs: str | None = None
61
+ timezone: str | None = None
62
+
63
+ class Config:
64
+ coerce_numbers_to_str = True
65
+
66
+
67
+ class Subject(BaseModel):
68
+ metadata: Metadata
69
+ collection: Optional["Collection"] = None
70
+ df: pd.DataFrame
71
+
72
+ class Config:
73
+ arbitrary_types_allowed = True
74
+
75
+ def __str__(self):
76
+ return str(f"{self.metadata}\n{self.df}")
77
+
78
+ @property
79
+ def timeline(self):
80
+ if not self.metadata.crs:
81
+ raise ValueError("Records object does not have a crs defined.")
82
+
83
+ columns_exists(
84
+ self.df,
85
+ [Column.TRIP_ID, Column.TRIP_STATUS, Column.STATIONARY_ID],
86
+ )
87
+
88
+ return get_timeline(self.df, crs=self.metadata.crs)
89
+
90
+ @property
91
+ def domains(self):
92
+ # TODO: Implement this property.
93
+ raise NotImplementedError("This method is not implemented yet.")
94
+
95
+ def to_parquet(
96
+ self,
97
+ path: str | Path,
98
+ *,
99
+ overwrite: bool = False,
100
+ ) -> None:
101
+ if isinstance(path, str):
102
+ path = Path(path)
103
+
104
+ if path.exists() and not overwrite:
105
+ raise FileExistsError(
106
+ f"The file '{path}' already exists. If you want to overwrite it, set the 'overwrite' argument to 'True'."
107
+ )
108
+ else:
109
+ path.parent.mkdir(parents=True, exist_ok=True)
110
+
111
+ custom_metadata = {"labda".encode(): self.metadata.model_dump_json().encode()}
112
+ self.validate()
113
+ table = pa.Table.from_pandas(self.df)
114
+
115
+ existing_metadata = table.schema.metadata
116
+ combined_meta = {**custom_metadata, **existing_metadata}
117
+
118
+ table = table.replace_schema_metadata(combined_meta)
119
+ pq.write_table(table, path)
120
+
121
+ @classmethod
122
+ def from_parquet(cls, path: str | Path) -> "Subject":
123
+ if isinstance(path, str):
124
+ path = Path(path)
125
+
126
+ table = pq.read_table(path)
127
+ df = table.to_pandas()
128
+ custom_metadata = Metadata.model_validate_json(
129
+ table.schema.metadata["labda".encode()]
130
+ )
131
+ cls = cls(metadata=custom_metadata, df=df)
132
+ cls.validate()
133
+
134
+ return cls
135
+
136
+ def validate(
137
+ self,
138
+ *,
139
+ extra_columns: bool = False,
140
+ ):
141
+ self.df = SCHEMA.validate(self.df)
142
+
143
+ # Order columns as defined in Column
144
+ records_columns = [col.value for col in Column]
145
+ ordered_columns = [col for col in records_columns if col in self.df.columns]
146
+
147
+ # Append extra columns that are not in Column at the end, alphabetically
148
+ if extra_columns:
149
+ extra = sorted(set(self.df.columns) - set(records_columns))
150
+ ordered_columns.extend(extra)
151
+
152
+ self.df = self.df[ordered_columns]
153
+
154
+ def add_timedelta(
155
+ self,
156
+ *,
157
+ name: str = Column.TIMEDELTA,
158
+ overwrite: bool = False,
159
+ ):
160
+ self.df = add_timedelta(self.df, name=name, overwrite=overwrite)
161
+
162
+ def add_distance(
163
+ self,
164
+ *,
165
+ name: str = Column.DISTANCE,
166
+ overwrite: bool = False,
167
+ ):
168
+ self.df = add_distance(
169
+ self.df, crs=self.metadata.crs, name=name, overwrite=overwrite
170
+ )
171
+
172
+ def add_speed(
173
+ self,
174
+ *,
175
+ name: str = Column.SPEED,
176
+ overwrite: bool = False,
177
+ ):
178
+ self.df = add_speed(
179
+ self.df, crs=self.metadata.crs, name=name, overwrite=overwrite
180
+ )
181
+
182
+ def add_acceleration(
183
+ self,
184
+ *,
185
+ name: str = Column.ACCELERATION,
186
+ overwrite: bool = False,
187
+ ):
188
+ self.df = add_acceleration(
189
+ self.df, crs=self.metadata.crs, name=name, overwrite=overwrite
190
+ )
191
+
192
+ def add_direction(
193
+ self,
194
+ *,
195
+ name: str = Column.DIRECTION,
196
+ overwrite: bool = False,
197
+ ):
198
+ self.df = add_direction(self.df, name=name, overwrite=overwrite)
199
+
200
+ def detect_trips(
201
+ self,
202
+ cut_points: dict[str, Any] | None = None,
203
+ *,
204
+ gap_duration: timedelta,
205
+ stop_radius: int | float,
206
+ stop_duration: timedelta,
207
+ window: int | None = 3,
208
+ pause_radius: int | float | None = None,
209
+ pause_duration: timedelta | None = None,
210
+ min_duration: timedelta | None = None,
211
+ min_length: int | float | None = None,
212
+ min_distance: int | float | None = None,
213
+ max_speed: int | float | None = None,
214
+ overwrite: bool = False,
215
+ ) -> None:
216
+ if not self.metadata.crs:
217
+ raise ValueError("Records object does not have a CRS defined.")
218
+
219
+ self.df = detect_trips(
220
+ self.df,
221
+ crs=self.metadata.crs,
222
+ sampling_frequency=self.metadata.sampling_frequency,
223
+ overwrite=overwrite,
224
+ gap_duration=gap_duration,
225
+ stop_radius=stop_radius,
226
+ stop_duration=stop_duration,
227
+ pause_radius=pause_radius,
228
+ pause_duration=pause_duration,
229
+ min_duration=min_duration,
230
+ min_length=min_length,
231
+ min_distance=min_distance,
232
+ max_speed=max_speed,
233
+ )
234
+
235
+ if cut_points:
236
+ self.df = detect_transportation(
237
+ self.df,
238
+ self.metadata.crs,
239
+ cut_points,
240
+ window=window,
241
+ overwrite=overwrite,
242
+ )
243
+
244
+ def detect_domains(
245
+ self,
246
+ domains: pd.DataFrame,
247
+ *,
248
+ overwrite: bool = False,
249
+ ) -> None:
250
+ domains = DOMAINS_SCHEMA.validate(domains)
251
+ subject_domains = domains[
252
+ (domains[Column.SUBJECT_ID] == self.metadata.id)
253
+ | (domains[Column.SUBJECT_ID].isna())
254
+ ]
255
+ self.df = detect_domains(self.df, subject_domains, overwrite=overwrite)
256
+
257
+ def detect_activity_intensity(
258
+ self,
259
+ cut_points: dict[str, Any],
260
+ *,
261
+ overwrite: bool = False,
262
+ ) -> None:
263
+ self.df = detect_activity_intensity(
264
+ self.df,
265
+ cut_points,
266
+ self.metadata.sampling_frequency,
267
+ overwrite=overwrite,
268
+ )
269
+
270
+ def detect_wear(
271
+ self,
272
+ min_duration: timedelta,
273
+ interruption_duration: timedelta,
274
+ *,
275
+ overwrite: bool = False,
276
+ ) -> None:
277
+ self.df = detect_wear(
278
+ self.df,
279
+ self.metadata.sampling_frequency,
280
+ min_duration,
281
+ interruption_duration=interruption_duration,
282
+ overwrite=overwrite,
283
+ )
284
+
285
+ def upsample(
286
+ self,
287
+ sampling_frequency: float,
288
+ *,
289
+ mapper: list[dict[str, Any]] | None = None,
290
+ ) -> None:
291
+ self.df = upsample(
292
+ self.df, self.metadata.sampling_frequency, sampling_frequency, mapper
293
+ )
294
+ self.metadata.sampling_frequency = sampling_frequency
File without changes