tdtr is an R
interface to Tucker-Davis Technologies (TDT) tank/block data. It uses
the Python tdt package through reticulate,
then gives R users a compact set of functions for the common
workflow:
Loading tdtr declares its Python requirement through
reticulate with
reticulate::py_require("tdt>=0.7.3"). Python is not
initialized just because the package is attached. For ordinary R use,
reticulate can create and manage a Python environment that contains
tdt when Python is first needed.
Before starting an analysis, confirm that Python and tdt
resolve in the current R session.
cfg <- tdt_config(initialize = TRUE)
list(
reticulate = cfg$reticulate,
tdt_available = cfg$tdt_available,
python_version = as.character(cfg$python$version)
)
#> $reticulate
#> [1] TRUE
#>
#> $tdt_available
#> [1] TRUE
#>
#> $python_version
#> [1] "3.12"Reticulate chooses and initializes Python once per R session. If you
want to use a specific Python environment, configure that before calling
tdt_config(initialize = TRUE) or read_block().
For example:
reticulate::use_python("/path/to/python", required = TRUE)
library(tdtr)
tdt_config(initialize = TRUE)When you force a specific Python environment with reticulate, Conda,
Pixi, or environment variables, that environment must already provide
tdt>=0.7.3.
The package includes one small raw TDT block from the official TDT example data. It is stored as TDT files, not as pre-collected R data.
example_path <- tdtr_example_block_path()
list(
block = basename(example_path),
files = list.files(example_path, pattern = "\\.(tsq|tev|Tbk|Tdx|tin|tnt)$")
)
#> $block
#> [1] "Subject1-211115-094936"
#>
#> $files
#> [1] "VidOverlay-211115-093859_Subject1-211115-094936.Tbk"
#> [2] "VidOverlay-211115-093859_Subject1-211115-094936.Tdx"
#> [3] "VidOverlay-211115-093859_Subject1-211115-094936.tev"
#> [4] "VidOverlay-211115-093859_Subject1-211115-094936.tin"
#> [5] "VidOverlay-211115-093859_Subject1-211115-094936.tnt"
#> [6] "VidOverlay-211115-093859_Subject1-211115-094936.tsq"Read the first five seconds. read_block() returns a
Python-backed object, so large stream arrays are not copied into R just
because the block was opened.
block <- read_block(
example_path,
evtype = c("epocs", "streams"),
t1 = 0,
t2 = 5,
verbose = 0
)
class(block)
#> [1] "tdt_block_py" "tdt_py"Inspect the block without printing the local library path.
info <- block_info(block)
list(
blockname = info$blockname,
duration = as.character(info$duration),
streams = stream_names(block),
epocs = epoc_names(block)
)
#> $blockname
#> [1] "Subject1-211115-094936"
#>
#> $duration
#> [1] "0:00:37.103101"
#>
#> $streams
#> [1] "_405A" "_465A" "Fi1r"
#>
#> $epocs
#> [1] "Cam1" "Tick"summary() gives a compact stream overview.
summary(block)$streams
#> # A tibble: 3 × 4
#> stream fs shape nbytes
#> <chr> <dbl> <chr> <dbl>
#> 1 _405A 1017. 5087 20348
#> 2 _465A 1017. 5087 20348
#> 3 Fi1r 6104. 30518 122072The example contains three streams:
_405A and _465A, lower-rate single-channel
fiber photometry streams;Fi1r, a higher-rate raw/reference stream.It also contains two event stores:
Cam1, camera frame events;Tick, one-second tick events.Events collect into a tibble with one row per event interval.
events <- collect_epocs(block)
head(events)
#> # A tibble: 6 × 4
#> store onset offset value
#> <chr> <dbl> <dbl> <dbl>
#> 1 Cam1 0.112 0.150 1
#> 2 Cam1 0.150 0.189 2
#> 3 Cam1 0.189 0.229 3
#> 4 Cam1 0.229 0.269 4
#> 5 Cam1 0.269 0.309 5
#> 6 Cam1 0.309 0.349 6Filter by store when you only need one event source.
ticks <- collect_epocs(block, store = "Tick")
ticks
#> # A tibble: 5 × 4
#> store onset offset value
#> <chr> <dbl> <dbl> <dbl>
#> 1 Tick 0.000164 1.00 0
#> 2 Tick 1.00 2.00 1
#> 3 Tick 2.00 3.00 2
#> 4 Tick 3.00 4.00 3
#> 5 Tick 4.00 5 4Events use seconds relative to block start. You can build analysis
windows from event onsets with ranges_from_epocs().
Collect one selected stream when you want signal values in R.
signal <- collect_stream(block, "_465A")
dim(signal)
#> [1] 5087 1
head(signal)
#> [,1]
#> [1,] 0.3687995
#> [2,] 0.3711895
#> [3,] 0.3735043
#> [4,] 0.3757292
#> [5,] 0.3778504
#> [6,] 0.3798554Stream matrices are always samples by channels.
Use include_time = TRUE when you want sample times
alongside the data.
signal_with_time <- collect_stream(
block,
"_465A",
as = "list",
include_time = TRUE
)
head(signal_with_time$time)
#> [1] 0.00000000 0.00098304 0.00196608 0.00294912 0.00393216 0.00491520
head(signal_with_time$data)
#> [,1]
#> [1,] 0.3687995
#> [2,] 0.3711895
#> [3,] 0.3735043
#> [4,] 0.3757292
#> [5,] 0.3778504
#> [6,] 0.3798554For small or bounded views, a tibble can be convenient.
as_tibble_stream(block, "_465A", max_rows = 8)
#> # A tibble: 8 × 3
#> time channel value
#> <dbl> <chr> <dbl>
#> 1 0 channel_1 0.369
#> 2 0.000983 channel_1 0.371
#> 3 0.00197 channel_1 0.374
#> 4 0.00295 channel_1 0.376
#> 5 0.00393 channel_1 0.378
#> 6 0.00492 channel_1 0.380
#> 7 0.00590 channel_1 0.382
#> 8 0.00688 channel_1 0.383Avoid turning long high-frequency streams into large tibbles by
accident. Use a time window, downsampling, or max_rows for
table views.
as_tibble_stream(
block,
"Fi1r",
window = c(1, 1.01),
max_rows = 8
)
#> # A tibble: 8 × 3
#> time channel value
#> <dbl> <chr> <dbl>
#> 1 1.00 channel_1 0.0558
#> 2 1.00 channel_1 0.0626
#> 3 1.00 channel_1 0.0577
#> 4 1.00 channel_1 0.0684
#> 5 1.00 channel_1 0.0894
#> 6 1.00 channel_1 0.116
#> 7 1.00 channel_1 0.114
#> 8 1.00 channel_1 0.123The high-level interface is intentionally focused on common
inspect-and-collect workflows. When you need reader controls that map
directly to Python tdt, read-time store filtering,
large-block memory checks, save/reload guidance, or direct access to
live Python objects, see the advanced usage vignette.