def test_time_data(fs: float, n_samples: int, first_time: str, time_data: TimeData): """Test time data""" from resistics.sampling import to_datetime, to_timedelta chans = ["Ex", "Ey", "Hx", "Hy"] first_rstime = to_datetime(first_time) last_rstime = first_rstime + to_timedelta(1 / fs) * (n_samples - 1) # check metadata assert time_data.metadata.fs == fs assert time_data.metadata.chans == chans assert time_data.metadata.n_samples == n_samples assert time_data.metadata.n_chans == len(chans) assert time_data.metadata.first_time == first_rstime assert time_data.metadata.last_time == last_rstime # check the arrays and chan indexing for idx, chan in enumerate(time_data.metadata.chans): assert idx == time_data.get_chan_index(chan) np.testing.assert_equal(time_data[chan], time_data.data[idx, :]) np.testing.assert_equal(time_data.get_chan(chan), time_data.data[idx, :]) # try setting a channel time_data["Ex"] = np.ones(shape=(n_samples), dtype=np.float32) np.testing.assert_equal(time_data["Ex"], np.ones(shape=(n_samples))) # check the timestamps and plotting functions timestamps = pd.date_range( start=first_time, periods=n_samples, freq=pd.Timedelta(1 / fs, "s") ) pd.testing.assert_index_equal(time_data.get_timestamps(estimate=True), timestamps) pd.testing.assert_index_equal( time_data.get_timestamps(samples=np.array([1, 5]), estimate=True), timestamps[np.array([1, 5])], ) # copy time_data_copy = time_data.copy() assert time_data_copy.metadata == time_data.metadata np.testing.assert_equal(time_data_copy.data, time_data.data) # check to make sure getting an unknown chan raises an error with pytest.raises(ChannelNotFoundError): time_data["unknown"] with pytest.raises(ChannelNotFoundError): time_data["unknown"] = np.ones(shape=(n_samples), dtype=np.float32)
def plot(self, max_pts: Optional[int] = 10_000) -> go.Figure: """ Plot the decimated data Parameters ---------- max_pts : Optional[int], optional The maximum number of points in any individual plot before applying lttbc downsampling, by default 10_000. If set to None, no downsampling will be applied. Returns ------- go.Figure Plotly Figure """ from resistics.plot import get_time_fig import plotly.express as px if len(self.data) == 0: logger.error("Data is empty, no decimation levels to plot") chans = self.metadata.chans y_labels = {x: self.metadata.chans_metadata[x].chan_type for x in chans} fig = get_time_fig(chans, y_labels) colors = iter(px.colors.qualitative.Plotly) for ilevel in range(0, self.metadata.n_levels): logger.info(f"Plotting decimation level {ilevel}") metadata_dict = self.metadata.levels_metadata[ilevel].dict() metadata_dict["chans"] = self.metadata.chans metadata_dict["chans_metadata"] = self.metadata.chans_metadata time_data = TimeData(TimeMetadata(**metadata_dict), self.data[ilevel]) legend = f"{ilevel} - {time_data.metadata.fs:.4f} Hz" time_data.plot( fig=fig, chans=chans, color=next(colors), legend=legend, max_pts=max_pts ) return fig
def time_data_periodic( frequencies: List[float], fs: float = 50, first_time: str = "2020-01-01 00:00:00", n_samples: int = 100, dtype: Optional[Type] = None, ) -> TimeData: """ Get period TimeData Parameters ---------- frequencies : List[float] Frequencies to include fs : float, optional Sampling frequency, by default 50 first_time : str, optional The first time, by default "2020-01-01 00:00:00" n_samples : int, optional The number of samples, by default 100 dtype : Optional[Type], optional The data type for the values, by default None Returns ------- TimeData Periodic TimeData """ if dtype is None: dtype = DEFAULT_TIME_DATA_DTYPE metadata = time_metadata_1chan(fs, first_time, n_samples) times = np.arange(0, n_samples) * (1 / fs) data = np.zeros(shape=(1, n_samples), dtype=dtype) for freq in frequencies: data += np.sin(times * 2 * np.pi * freq) creator = { "name": "time_data_periodic", "frequencies": frequencies, "fs": fs, "first_time": first_time, "n_samples": n_samples, } messages = ["Generated time data with periodic values"] record = get_record(creator, messages) metadata.history.add_record(record) return TimeData(metadata, data)
def time_data_with_offset( offset=0.05, fs: float = 10, first_time: str = "2020-01-01 00:00:00", n_samples: int = 11, dtype: Optional[Type] = None, ) -> TimeData: """ Get TimeData with an offset on the sampling Parameters ---------- offset : float, optional The offset on the sampling in seconds, by default 0.05 fs : float, optional The sampling frequency, by default 10 first_time : str, optional The first time of the TimeData, by default "2020-01-01 00:00:00" n_samples : int, optional The number of samples, by default 11 dtype : Optional[Type], optional The data type for the values, by default None Returns ------- TimeData The TimeData """ if dtype is None: dtype = DEFAULT_TIME_DATA_DTYPE first_time = (pd.to_datetime(first_time) + pd.Timedelta(offset, "s")).isoformat() metadata = time_metadata_1chan(fs, first_time, n_samples) data = np.arange(0, n_samples).reshape(1, n_samples) creator = { "name": "time_data_with_offset", "offset": offset, "fs": fs, "first_time": first_time, "n_samples": n_samples, } messages = ["Generated time data with an offset"] record = get_record(creator, messages) metadata.history.add_record(record) return TimeData(metadata, data)
def time_data_with_nans( fs: float = 10, first_time: str = "2020-01-01 00:00:00", dtype: Optional[Type] = None, ) -> TimeData: """ TimeData with 16 samples and some nan values Parameters ---------- fs : float, optional Sampling frequency, by default 10 first_time : str, optional The time of the first sample, by default "2020-01-01 00:00:00" dtype : Optional[Type], optional The data type for the values, by default None Returns ------- TimeData The TimeData """ if dtype is None: dtype = DEFAULT_TIME_DATA_DTYPE data = np.array( [ [1, 1, 1, 0, np.nan, 0, 1, 1, 1, np.nan, 0, 0, 0, 0, 1, 1], [1, 2, np.nan, np.nan, 5, 6, np.nan, 8, 9, 1, 2, 3, 4, 5, 6, 7], [np.nan, 2, 3, 5, 1, 2, 3, 4, 2, 6, 7, np.nan, np.nan, 4, 3, 2], [2, 0, 0, 1, 2, 3, np.nan, np.nan, np.nan, 0, 0, 1, 3, 3, 3, 3], ], dtype=dtype, ) n_samples = data.shape[1] metadata = time_metadata_mt(fs, first_time, n_samples) creator = { "name": "time_data_with_nans", "fs": fs, "first_time": first_time } messages = ["Generated time data with some nan values"] record = get_record(creator, messages) metadata.history.add_record(record) return TimeData(metadata, data)
def time_data_linear( fs: float = 10, first_time: str = "2020-01-01 00:00:00", n_samples: int = 10, dtype: Optional[Type] = None, ) -> TimeData: """ Get TimeData with linear data Parameters ---------- fs : float, optional The sampling frequency, by default 10 first_time : str, optional Time of first sample, by default "2020-01-01 00:00:00" n_samples : int, optional The number of samples, by default 10 dtype : Optional[Type], optional The data type for the values, by default None Returns ------- TimeData TimeData with linear values """ if dtype is None: dtype = DEFAULT_TIME_DATA_DTYPE metadata = time_metadata_mt(fs, first_time, n_samples) data = np.empty(shape=(metadata.n_chans, n_samples), dtype=dtype) for idx in range(metadata.n_chans): data[idx, :] = np.arange(n_samples) creator = { "name": "time_data_linear", "fs": fs, "first_time": first_time, "n_samples": n_samples, } messages = ["Generated time data with linear values"] record = get_record(creator, messages) metadata.history.add_record(record) return TimeData(metadata, data)
def time_data_random( fs: float = 10, first_time: str = "2020-01-01 00:00:00", n_samples: int = 10, dtype: Optional[Type] = None, ) -> TimeData: """ TimeData with random values and specifiable number of samples Parameters ---------- fs : float, optional The sampling frequency, by default 10 first_time : str, optional Time of first sample, by default "2020-01-01 00:00:00" n_samples : int, optional The number of samples, by default 10 dtype : Optional[Type], optional The data type for the values, by default None Returns ------- TimeData The TimeData """ if dtype is None: dtype = DEFAULT_TIME_DATA_DTYPE metadata = time_metadata_mt(fs, first_time, n_samples) data = np.random.normal(0, 3, size=(metadata.n_chans, n_samples)).astype(dtype) creator = { "name": "time_data_random", "fs": fs, "first_time": first_time, "n_samples": n_samples, } messages = ["Generated time data with random values"] record = get_record(creator, messages) metadata.history.add_record(record) return TimeData(metadata, data)
def time_data_ones( fs: float = 10, first_time: str = "2020-01-01 00:00:00", n_samples: int = 10, dtype: Optional[Type] = None, ) -> TimeData: """ TimeData with all ones Parameters ---------- fs : float, optional The sampling frequency, by default 10 first_time : str, optional The time of the first sample, by default "2020-01-01 00:00:00" n_samples : int, optional The number of samples, by default 10 dtype : Optional[Type], optional The data type for the values, by default None Returns ------- TimeData The TimeData """ if dtype is None: dtype = DEFAULT_TIME_DATA_DTYPE metadata = time_metadata_mt(fs, first_time, n_samples) data = np.ones(shape=(len(metadata.chans), n_samples), dtype=dtype) creator = { "name": "time_data_ones", "fs": fs, "first_time": first_time, "n_samples": n_samples, } messages = ["Generated time data with fixed values"] record = get_record(creator, messages) metadata.history.add_record(record) return TimeData(metadata, data)