AI-ready dynamic activity from the astrocyte network
Maxine Levesque
Kira Poskanzer
Latest (v1.2)
November 23, 2025
tl;dr
Astrocytes make up about a third of the cells in your brain, and form a second, distinct brain network from the more-studied neuronal network. As neuroscience has discovered in only the last few years, the astrocyte network carries out extremely important computations, integrating information from across different brain networks, and using this information to drive feedback to neurons that controls physiological state and maintains long-term homeostasis.
To help push the field’s understanding of astrocytes’ unique role forward, we’re releasing OpenAstrocytes—the largest repository of structured astrocyte calcium dynamics to date, drop-in ready for use in AI workflows. Try it out with:
The human brain is about one-third neurons. But two-thirds of it is made from other, non-neuronal cells; and, in recent years, the neuroscience field has become more acutely aware of the incredible importance of the roles played by those other players.
One of those other thirds is made up of astrocytes—a crucially important subdivision of the brain’s little-understood glia1. Astrocytes form a cellular network of their own, woven between the neurons (Figure 1); and, as recent work has demonstrated, this network encodes rich information about both local and global biochemical milieu and physiological state (1).
1 From “glue”, referencing old theories of their supposed passivity in brain function.
Figure 1: Schematic diagram of the tripartite synapse. Astrocytic processes (green) wrap around most cortical synaptic connections between neurons, allowing astrocytes to integrate the myriad chemical signals sensed in the neuronal chatter, and downstream, to incorporate that information with other signals about the local brain network sensed from other parts of the extracellular space.
All biological cells must incorporate a massive number of data-points, coming both from the outside world and also from the internal processes of the cell itself; indeed, this binding-together of disparate interactions into a shared, coherent, soliton-like packet is, perhaps, what makes life life(2). While all the diverse kinds of cells across the entire tree of life have unique approaches to sensing and making sense of these various signals, because of shared evolutionary history, a few common structural threads span vast swaths of this diversity. One of those central commonalities is the use of intracellular calcium signaling: because of the calcium ion’s unique physical properties, cells very early in the evolutionary history of life developed machinery that allowed them to maintain extremely low concentrations of calcium within themselves, thereby giving bursts of calcium inside the cell a very robust signal-to-noise ratio. And, like many contingent solutions of evolution, the machinery stuck around!
But astrocytes have a unique—and incredibly rich—way of signaling with calcium; the best way to appreciate it is to look at it by eye:
Figure 2: (Left) Dynamics of astrocyte calcium, as recorded with two-photon imaging of mouse cortical slices expressing the calcium-binding fluorescent indicator GCaMP. (Right) Segmentation of this activity into discrete events using the AQuA toolbox. Reproduced from (3).
As is evident in the video above, naturalistic astrocyte calcium activity is composed of discrete calcium events—high-dimensional, complex, activity that is dynamically coupled to both neuronal activity and activity within the rest of the astrocyte network. These sparkles constantly fly across the entire connected astrocyte network, and amazingly, the full details of what lies in the essential chatter of this third of the brain is still under active investigation.
While recent work using more classical ML techniques has gotten us closer to cracking this “calcium code” in the astrocyte network, the rise of today’s AI tools raises the possibility of being able to find so many more features in this rich imaging that we, by eye, could never have even conceived of. Unfortunately, much of the data that would be useful in attacking this problem is either locked away or formatted in such a fashion to strongly obfuscate the relevant features for AI training and inference for any non-subspecialist. What’s needed to accelerate the field’s ability to dig deeper into the structure of this unique form of biological signaling is a data repository designed from the ground up to take advantage of these new tools.
Architecture
The best biological data science today is heavily leveraging the advances that have been made in AI over the last few years, and taking advantage of these tools to understand the latent structure of new biological datasets. We have previously used advances in machine learning to get at new features of our data that have led us to exciting new biology (1); but harnessing the full power of the methods that have risen to prominence over the last few years—and the new infrastructure and logistical challenges they present—has necessitated a ground-up reconceptualization of how we work with data at Forecast.
In particular, we wanted to take advantage of both:
the ease and flexibility provided by streaming data for prototyping and deploying AI workflows; and,
the richness of structured data that allows us to quickly make sense of the different metadata elements for samples or batches streamed in.
The former of these points is crucial for the nuts and bolts of scaling model training and inference, and has motivated advances in data tooling for AI like WebDatasets (4). The latter, we believe strongly, is a crucial point of departure for biomedical data; while immense progress has been made across domains by taking advantage of the incredible empirical power that lies in scaling, we believe architecting workflows at their foundation around keeping track of the richness of data’s explicit structure—and, most crucially, the structured relationships between those explicit data structures (5)—is a central part of how to leverage the power of these methods for biological applications, where the intrinsic squishy messiness makes purely tabula rasa inference of latent structure much more of an uphill battle.
Running this pub
If you’re running through the examples in this pub yourself, you’ll need additional scripts in separate modules from the snippets below with some bonus content; the full reproducible source for this pub can be found in this repo.
You’ll find each cell of code folded above the figure that cell generates, like so:
Code
## Required imports across the pubimport numpy as npimport matplotlib.pyplot as pltfrom forecast_style import HouseStyleimport atdataimport astrocytes### As noted later, there is still intrinsic randomness imparted by the random# shuffling for `webdataset`'s streaming that is difficult to address w/o# support for `wids` in `atdata`RANDOM_SEED =89rng = np.random.RandomState( RANDOM_SEED )### For progress bars during developmentfrom tqdm import tqdmDEV =False"Set to true to generate progress bars during individual cell development"
Backbone: Distributed, typed WebDatasets with atdata
To realize this, we built the OpenAstrocytes dataset on atdata, a new open-source package for structured, distributed, and interoperable streaming datasets we’re developing at Forecast to aid in AI training and inference, both in our own work and for the larger investigative community.
The original motivation for atdata internally was to have fast infrastructure for speeding up our AI iteration cycle when putting together the rich, multimodal datasets we encounter in biomedical R&D; we’re excited about the features we’re working on rolling out to the community soon!
atdata is built on the open WebDataset format, designed for use as a high-performance AI I/O layer used by many other research groups, and so is able to take advantage of their streaming, caching, smart batching, and drop-in PyTorch connections. atdata adds functionality that makes it straightforward to understand structured data from remote repositories:
from oa_pub.figures import plot_micrographfrom astrocytes.schema import BathApplicationFrame##dataset = astrocytes.data.bath_application \ .as_type( BathApplicationFrame )# Pop off a single sampleexemplar =next( x for x in dataset.ordered( batch_size =None ) )# Pop off and z-project a single batchbatch =next( x for x in dataset.ordered( batch_size =60) )batch_summary = np.quantile( batch.image[:, 0], 0.75, axis =0 )##with HouseStyle() as s: fig, ax = plt.subplots( figsize = (8, 8) ) plot_micrograph( s, batch_summary, ax, scale_x = exemplar.scale_x, scale_y = exemplar.scale_y, scale_bar =100., clim = (0, 140), )
Figure 3: Example data from the OpenAstrocytes dataset, showing mean calcium fluorescence in an ex vivo brain slice from a mouse over 60 frames (about one minute).
Here, the as_type method is hiding logic under the hood that takes advantage of pre-implemented bidirectional lenses that are opinionated about the way that generic microscopy images should be interpreted, if possible, as data coming from the specific experiments in the OpenAstrocytes data. These lenses have a rich compositional structure, and we are excited about the possibilities that follow downstream from starting at bedrock by keeping track of the full semantics of underlying datasets (that humans worked hard to put together!), as well as the ways they fit together.
The full documentation for atdata can be found on GitHub.
Datasets
Our initial release of OpenAstrocytes is built from two large collections of previously-published calcium imaging from mouse astrocytes, originally analyzed in (6) and (1). In total, these datasets comprise close to 1M images, making this release among the the largest open datasets of astrocyte calcium dynamics to date, to our knowledge.
The images in this first release of the OpenAstrocytes corpus were obtained using two-photon imaging of astrocytes expressing a genetically-encoded calcium sensor. These data were recorded during two different experimental paradigms:
Bath application (astrocytes.data.bath_application): in these experiments, ex vivo visual cortical slices from mice were exposed to one of two different agents (baclofen or t-ACPD) after a baseline period, with the agents added to the solution bathing the full slice after the onset time.
Two-photon photo-uncaging (astrocytes.data.uncaging): in these experiments, ex vivo visual cortical slices from mice were exposed to one of two caged neurotransmitters (RuBi-GABA or RuBi-glutamate), with a pulse (or pulses) of a second laser applied after a baseline period to “free” the GABA or glutamate from the ruthenium bipyridine cage rendering it inert initially, but only within the small, ~10µm locus directly stimulated by the second laser (7).
If you want to play around with OpenAstrocytes, you can add it to your Python project with
Let’s take a look at some of the fun structure that we have in the OpenAstrocytes data. Here, we’ll be digging into one of the experiments from (1), in which ex vivo slices from mouse visual cortex had two drugs—baclofen and t-ACPD2—bath-applied across the slice. Here, we’ll use the OpenAstrocytes dataset with contemporary tools to give views into the differences between these two compounds’ effects on brain networks.
2 Baclofen acts as an agonist of the GABAB receptor; t-ACPD acts as an agonist of the metabotropic glutamate receptor subtypes mGluR2 and mGluR3
At the outset, we’ll tuck away any parameters for this experimental setup that will come up across analyses.
Click any of the chevrons to expand the code that generates figure panels in this pub.
Code
# You found me, the parameter!T_INTERVENE =300.# s"The applied compound begins to enter the bath at 300s after recording onset"
Approach
In previous work (1), we identified the differences in astrocyte networks’ responses to baclofen and t-ACPD by using a spatial-temporal segmentation technique known as graphical time warping (8), as implemented in the AQuA toolbox (3). However, recent advances in large, self-supervised image models like DINOv3 (9) have greatly expanded our ability to pull out salient, highly-nontrivial latent structures within image data in an unbiased way. As a simple demonstration of this paradigm as applied to the OpenAstrocytes dataset, let’s investigate the extent to which this model is able to represent physiologically-relevant information in these specialized biological images—even without any post-training.
The overall approach presented in this demo is to run inference on the OpenAstrocytes bath application data using one of the vanilla DINOv3 models (dinov3-vit7b16-pretrain-lvd1689m), and leverage the fact that this model’s internal architecture separates out its image feature representations into patch embeddings. Each microscopy image is separated by the model into a 14-by-14 grid of image patches, with each patch corresponding to a ~60µm square portion of the imaged slice. At each time point over the course of an individual recorded movie, DINOv3 provides each one of these patches with a time series of 4096-dimensional embeddings, corresponding to its decomposition of the image into representative features as learned from its self-supervision over a vast swath of (principally non-biological, but with some exceptions) images.
Here, we use a simple linear basis change (via principal component analysis) to find a 64-dimensional subspace of this 4096-dimensional space already learned within the DINOv3 self-supervision objective that captures the subset of overall image features that appear in individual astrocyte calcium patches. This subspace captures both the image feature variability across different local astrocyte network morphologies as imaged in individual patches (as in Figure 9), as well as the image feature variability induced by the specific changes of the applied pharmacology in the bath application experiment. As it turns out, the richness in these feature representations—even in the absence of any biology-specific fine-tuning!—is enough to decode the identity of the applied compound from the calcium fluorescence pattern within a single ~60µm patch at a single time-point.
DINOv3 patch embeddings capture rich, dynamic responses in astrocyte networks
First, we’ll examine the latent feature structure of local patches of astrocyte network calcium activity, and how those features unfold after application of pharmacology. We’re particularly interested in looking at local patches to start because of our prior on the biology: because of a significant and growing body of work elucidating the information content of astrocyte calcium activity on these spatial scales (10), we have a hint that these features in our observables are most likely paint a generalizable readout of physiologic changes induced by pharmacology.
Individual patch responses
Let’s download a representative movie from the OpenAstrocytes bath application dataset—the same recording we projected above in Figure 3 to take a peek at the overall anatomy of the slice—and inspect the behavior of a couple representative patches from it:
Code
from oa_pub.analysis import ( get_movie, movie_times,# N_PATCHES_Y, N_PATCHES_X,)# Snag a convenient smoother for image preprocessingfrom scipy.ndimage import gaussian_filter## Demo example parametersFIRST_MOVIE_ID ='d22f6a65-b3e2-46c2-ba5c-551920af1fe3'"Microscope-supplied UUID of the demo movie to show"DEMO_PATCHES = [ (10, 12), (3, 7),]"Indices (vertical, horizontal) of the demo patches to show"IDX_FRAMES_COMPARE = (250, 350, 450, 550, 650, 750)"Indices of time-slices to display for patch snapshot figure"DEMO_PATCH_IMAGE_SMOOTHING_KERNEL = (3., 0.6, 0.6)"S.D. of gaussian smoothing kernel used for visualization (t, y, x)"#n_patches =len( DEMO_PATCHES )n_frame_compare =len( IDX_FRAMES_COMPARE )## Figure parametersPATCH_CMAX =110"Maximum intensity value for patch snapshot figure"##first_movie_frames = get_movie( FIRST_MOVIE_ID, verbose = DEV )first_movie_times, movie_dt = movie_times( first_movie_frames )# The current `OpenAstrocytes` data architecture has imaging channels in the# first axis; we're refactoring the frame schemas to make channel contents# explicit now that lenses easily handle conversion. Channel 0 here is GCaMP.# => see https://github.com/forecast-bio/open-astrocytes/issues/12first_frame = first_movie_frames[0]first_image = first_frame.image[0]#patch_size_y = first_image.shape[0] / N_PATCHES_Ypatch_size_x = first_image.shape[1] / N_PATCHES_Xtest_patch_frames = []for cur_i, cur_j in DEMO_PATCHES: patch_idx_y = ( int( np.floor( patch_size_y * cur_i ) ),int( np.ceil( patch_size_y * (cur_i +1) ) ) ) patch_idx_x = ( int( np.floor( patch_size_y * cur_j ) ),int( np.ceil( patch_size_y * (cur_j +1) ) ) ) test_patch_frames.append( np.array( [ frame.image[0][patch_idx_y[0]:patch_idx_y[1], :][:, patch_idx_x[0]:patch_idx_x[1]]for frame in first_movie_frames ] ) )test_patch_frames_filtered = [ gaussian_filter( fs, sigma = DEMO_PATCH_IMAGE_SMOOTHING_KERNEL )for fs in test_patch_frames]##with HouseStyle() as s: fig, axs = plt.subplots( n_patches, n_frame_compare, figsize = (10, 4), sharey =True, )for i_patch inrange( n_patches ): cur_patch_frames = test_patch_frames_filtered[i_patch]for i_t inrange( n_frame_compare ): ax = axs[i_patch, i_t] cur_image = cur_patch_frames[IDX_FRAMES_COMPARE[i_t]] ax.imshow( cur_image, clim = (0, PATCH_CMAX), cmap ='afmhot', )if i_patch ==0:# Show timings on top row t_disp = first_movie_times[IDX_FRAMES_COMPARE[i_t]] - T_INTERVENE ax.set_title( f'{t_disp:+0.1f}s', fontsize =12, )if i_t ==0:# Show patch label on left column axs[i_patch, i_t].set_ylabel( f'Patch {i_patch+1}' ) axs[i_patch, i_t].tick_params( axis ='x', length =0, ) axs[i_patch, i_t].set_xticks( [-0.5, patch_size_x +0.5], ['', ''] ) axs[i_patch, i_t].set_yticks( [] )# Set up display ticks ax_xticks = axs[-1, 0] ax_xticks.tick_params( axis ='x', length =3, ) ax_xticks.set_xticks( [-0.5, patch_size_x +0.5], ['0', f'{first_frame.scale_x * patch_size_x:0.0f} µm'], ha ='left', )# Plot a nice lil' highlight behind the post-onset images fig.patches.extend( [ plt.Rectangle( (0.382, 0.103), 0.53, 0.818, figure = fig, transform = fig.transFigure,# fill =True, linewidth =0, color ='k', alpha =0.05, zorder =-1_000, ) ] )# Make the spacing nice and ~tight~. plt.subplots_adjust( wspace =0.04, hspace =0., )
Figure 4: Two example patches sub-sampled from the same recording as above in Figure 3, showing evolution of calcium activity over time relative to bath application of baclofen at \(t = 0\). (A small Gaussian smoothing kernel in space and time has been applied here, to aid visualization.)
These two patches are good illustrations of the “median” structure of this astrocyte two-photon imaging data. Both patches do exhibit some changes after compound application; in comparing, for example, the right four columns with the left two, a close inspection reveals some parts of the patch with evoked activations. But, these changes can be subtle to the unaided or non-specialist eye—particularly here with Patch 2, shown in the bottom row of panels, which we picked to show what a very subtle case looks like in raw form.
However, the large image model is remarkably able to pull out a good deal of structure in its latent feature embeddings. We see this best if we first reduce to looking at the particular dimensions of that embedding space maximally capturing the features exhibited in the OpenAstrocytes corpus3. Specifically, we find that there are axes (PCs) within the patch-embedding space that capture robust changes induced in the astrocyte network as seen through our imaging data—even though those changes, as we saw before, were at first quite subtle to the unaided eye:
3I.e., the top few PCs of the embedding features.
Code
from oa_pub.analysis import ( baseline_normalize, is_between,)# => see https://github.com/forecast-bio/open-astrocytes/issues/14from astrocytes._datasets._embeddings import ( PatchEmbeddingTrace,)## Demo paramsDEMO_PCS = [0,1,4,]PC_WINDOW =84"The smoothing window used to postprocess the PCs was 84 frames (~60s)"## Figure paramsDEMO_PC_COLORS = {0: 'C0',1: 'C2',4: 'C3',}BASELINE_WINDOW = (-90, -30)"The baseline window (in s relative to onset) used for normalization"PLOT_WINDOW = (-100, 240)"The window (in s relative to onset) to focus on for plotting"CAUSAL_OFFSET_FRACTION =0.25"The fraction of the full smoothing window width to use as a causal offset"PANEL_YLIM = (-8, 13)PANEL_XTICKS = [-90, 0, 60, 120, 180, 240]# Migration of the PCA results to the full typed schema in `astrocytes` is# in-progress!# => see https://github.com/forecast-bio/open-astrocytes/issues/13PATCH_EMBEDDING_TRACE_WDS_URL = ('https://data.forecastbio.cloud'+'/testing/patch-pc-traces/bath-application/'+'bath_app-dinov3_vit7b16-pca64-smooth84.tar')"URL of the atdata blob for the demo pre-computed `PatchEmbeddingTrace`s"##ds = atdata.Dataset[PatchEmbeddingTrace]( PATCH_EMBEDDING_TRACE_WDS_URL )it = ds.ordered( batch_size =None )if DEV: it = tqdm( it )##test_traces = [ Nonefor _ in DEMO_PATCHES ]for trace in it:try:# Ensure traces have the needed metadataassert trace.metadata isnotNoneand'uuid'in trace.metadataassert trace.metadata['uuid'] == FIRST_MOVIE_IDfor i_patch, (cur_i, cur_j) inenumerate( DEMO_PATCHES ):if trace.i_patch == cur_i and trace.j_patch == cur_j: test_traces[i_patch] = traceif DEV:print( f'Snagged trace for {cur_i, cur_j}') should_finish =Truefor x in test_traces:if x isNone: should_finish =Falseif should_finish:breakexcept:# Skip traces without needed metadatacontinue##causal_offset = (PC_WINDOW * movie_dt) * CAUSAL_OFFSET_FRACTIONwith HouseStyle( grids =True ) as s: fig, axs = plt.subplots( n_patches, 1, figsize = (7, 7), sharex =True, )for i_patch inrange( n_patches ): ax = axs[i_patch] cur_trace = test_traces[i_patch] t_rel = cur_trace.ts - T_INTERVENE + causal_offset filter_plot = is_between( t_rel, PLOT_WINDOW )#for i_seq, cur_pc inenumerate( DEMO_PCS ): cur_color = DEMO_PC_COLORS[cur_pc] pc_values_norm, _, _ = baseline_normalize( t_rel, cur_trace.values[cur_pc, :], BASELINE_WINDOW ) ax.plot( t_rel[filter_plot], pc_values_norm[filter_plot],f'{cur_color}-', label =f'PC {cur_pc +1}', )# Drug application overlay ax.fill_between( [0, PLOT_WINDOW[1]], PANEL_YLIM[0], PANEL_YLIM[1], color ='k', alpha =0.05, linewidth =0, )# Baseline ax.plot( PLOT_WINDOW, [0, 0],'k-', linewidth =1, )# Left y-axis ax.plot( [PLOT_WINDOW[0], PLOT_WINDOW[0]], PANEL_YLIM,'k-', linewidth =1, ) ax.set_xticks( PANEL_XTICKS ) ax.set_xlim( PLOT_WINDOW ) ax.set_ylim( PANEL_YLIM ) ax.set_ylabel( f'Patch {i_patch +1}' )# Bottom plot gets x label and legendif i_patch == (n_patches -1): ax.set_xlabel( 'Time (s)' ) plt.legend( fontsize =12, loc ='upper left', )# "+ Drug" text on one sample axis axs[0].text( 5, -6.5, '+ Drug', va ='center', alpha =0.5, )# Make the spacing nice and ~tight~. plt.subplots_adjust( hspace =0.08 )
Figure 5: For each of the two astrocyte network patches shown above in Figure 4, each trace shows the time-evolution of one representative embedding PC, relative to the onset of bath application of baclofen at t = 0. Time shown as seconds relative to compound entering the bath (shaded box); PC changes shown as multiples of the standard deviation of fluctuations in the pre-application baseline period. (n.b.: a 60s boxcar filter in time has been applied to each PC in order to compensate for intrinsic imaging noise, leading to blurring of activation edges.)
Response heterogeneity across the imaging field
The traces above depict only three of the 64 PCs we computed as our bath application–specific subspace4—and, showed only two of the 196 (14-by-14) patches obtained from inference on this one experimental session alone. Already, we are getting a sense for the detail that the embedding model can pull out from these data.
4 These 64 PCs captured a large majority of the observed variance in the 4096-dimensional DINOv3 patch-embedding features. Hence, while our astrocyte two-photon calcium dynamics data is quite rich, it is also the case, as expected, that the statistical structure of these specific sorts of biological images occupies a very small subspace of the full space of naturalistic images.
Indeed, when we look at the feature dynamics across all patches within the imaging field, the full richness of this method in characterizing astrocyte calcium becomes apparent:
# For typing assistsfrom matplotlib.axes import Axesfrom numpy.typing import NDArrayfrom oa_pub.analysis import get_movie_tracesfrom oa_pub.figures import plot_trace_grid## Plot paramsGRIDS_XTICKS = [-90, 0, 240]plot_pc =0cur_grid_ylim = (-12.5, 12.5)cur_grid_yticks = [-10, 0, 10]### Collate full datasetfirst_movie_traces = get_movie_traces( FIRST_MOVIE_ID, verbose = DEV,)# Do the grid plot itselfwith HouseStyle() as s: fig, axs = plt.subplots( N_PATCHES_Y, N_PATCHES_X, figsize = (16, 12), ) ts_grid = first_movie_traces[0][0].ts ts_grid_rel = ts_grid - T_INTERVENE + causal_offset filter_grid = is_between( ts_grid_rel, PLOT_WINDOW )# For each location in the grid, the y-values to plot are created by# taking this specific PC's values from the structured data, normalizing# them (the first return value of baseline_normalize), and then filtering for the# time-window we want to plot ys_grid = [ [ baseline_normalize( t_rel, first_movie_traces[i][j].values[plot_pc, :], BASELINE_WINDOW )[0][filter_grid]for j inrange( N_PATCHES_Y ) ]for i inrange( N_PATCHES_X ) ] plot_trace_grid( axs, ts_grid_rel[filter_grid], ys_grid, ylim = cur_grid_ylim, xticks = GRIDS_XTICKS, yticks = cur_grid_yticks, color =f'{DEMO_PC_COLORS[plot_pc]}', highlight = DEMO_PATCHES, )# Make spacing nice and ~tight~. fig.subplots_adjust( wspace =0.1, hspace =0.1 )
Figure 6: Baseline-normalized changes in DINOv3 embedding PC 1 features after bath application of baclofen for each of the 196 astrocyte network patches, derived from the same slice depicted above in Figure 3. The locations of the two representative patches shown in greater detail in Figure 4 and Figure 5 are highlighted in black boxes. Note the heterogeneity of responses across the imaging field, as well as the correspondence between portions of the network exhibiting PC1 activation and anatomical features of the local astrocyte network in Figure 3. Time shown as seconds relative to compound entering the bath (shaded box); changes shown as multiples of the baseline distribution standard deviation.
Figure 7: As in Figure 6, but for embedding PC 2 features. Note that while the distribution of loci that exhibit modulation post-application still respects the underlying anatomy of Figure 3, the spatial and temporal distribution is distinct from the changes seen for PC 1. Time shown as seconds relative to compound entering the bath (shaded box); changes shown as multiples of the baseline distribution standard deviation.