• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pinneylab / htbam_analysis / 20761362628

06 Jan 2026 08:37PM UTC coverage: 15.021% (-0.2%) from 15.26%
20761362628

push

github

Nicholas-Freitas
Users can alter aspect ratio of sample plots, for easy publication-ready plots

0 of 3 new or added lines in 1 file covered. (0.0%)

391 existing lines in 4 files now uncovered.

470 of 3129 relevant lines covered (15.02%)

0.6 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/htbam_analysis/analysis/plot.py
1
from dash import Dash, dcc, html, Input, Output, no_update, State
×
2
import plotly.graph_objs as go
×
3
import base64
×
4
import tempfile
×
5
import matplotlib.pyplot as plt
×
6
import numpy as np
×
UNCOV
7
import socket
×
8

9
# Utilities
UNCOV
10
from typing import TYPE_CHECKING, List, Dict, Optional, Any
×
11
if TYPE_CHECKING:
12
    from htbam_analysis.analysis.experiment import HTBAMExperiment
13

14
def find_free_port(start_port=8050):
×
UNCOV
15
    port = start_port
×
UNCOV
16
    attempts = 0
×
17
    while attempts < 50:
×
UNCOV
18
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
×
19
            # Check if port is in use
UNCOV
20
            if s.connect_ex(('localhost', port)) != 0:
×
UNCOV
21
                return port
×
UNCOV
22
            port += 1
×
UNCOV
23
            attempts += 1
×
UNCOV
24
    raise Exception("No free ports found in range 8050-8100")
×
25

26
# HTBAM Data
UNCOV
27
from htbam_db_api.data import Data4D, Data3D, Data2D
×
28

29
# Plotting
UNCOV
30
import seaborn as sns
×
31

UNCOV
32
def plot_chip(plotting_var, chamber_names, graphing_function=None, title=None):
×
33
    ''' This function creates a Dash visualization of a chip, based on a certain Run (run_name)
34
        Inputs:
35
            plotting_var: a dictionary mapping chamber_id to the variable to be plotted for that chamber
36
            chamber_names: a dictionary mapping chamber_id to the name of the sample in the chamber (e.g. '1,1': ecADK_XYZ')
37
            graphing_function: a function that takes in a single chamber_id (e.g. '1,1') and matplotlib axis and returns the axis object after plotting.
38
            title: a string to be used as the title of the plot
39
        TODO: make all the variables stored in Dash properly...
40
    '''
41

42
    # Make the image array
43
    #NB: eventually, store width/height in DB and reference!
UNCOV
44
    img_array = np.zeros([56,32])
×
45

46
    # Here we're plotting to value for each chamber (e.g. coloring by std curve slope)
UNCOV
47
    units = ''
×
48
    for chamber_id, value in plotting_var.items():
×
49
        x = int(chamber_id.split(',')[0])
×
UNCOV
50
        y = int(chamber_id.split(',')[1])
×
51
        
52
        # Check if value has units (Pint quantity)
UNCOV
53
        if hasattr(value, 'magnitude') and hasattr(value, 'units'):
×
UNCOV
54
            img_array[y-1,x-1] = float(value.magnitude)
×
55
            if not units:
×
56
                units = str(f'{value.units:~}')
×
57
        else:
UNCOV
58
            img_array[y-1,x-1] = float(value) 
×
59
    
60
    #generate title
61
    if title is None:
×
UNCOV
62
        title = ''
×
63
    
64
    #Create the figure
UNCOV
65
    layout = go.Layout()
×
66

67
    # create 1‐indexed axes
UNCOV
68
    x_vals = list(range(1, img_array.shape[1] + 1))
×
UNCOV
69
    y_vals = list(range(1, img_array.shape[0] + 1))
×
70

71
    # To discard outliers that mess with the data, we're using the 5th and 95th percentiles.
72
    zmin, zmax = np.nanpercentile(img_array, [5, 95])
×
73

UNCOV
74
    heatmap = go.Heatmap(
×
75
        x=x_vals,
76
        y=y_vals,
77
        z=img_array,
78
        zmin=zmin,
79
        zmax=zmax,
80
        colorscale='Viridis',
81
        colorbar=dict(title=units),
82
        hovertemplate='x=%{x}<br>y=%{y}<br>z=%{z} ' + units +'<extra></extra>'
83
    )
84
    
UNCOV
85
    fig = go.Figure(layout=layout, data=heatmap)
×
86

87
    #center title in fig
UNCOV
88
    fig.update_layout(title=title,
×
89
                        title_x=0.5, 
90
                        yaxis=dict(scaleanchor="x", scaleratio=1, autorange='reversed'), 
91
                        xaxis=dict(scaleratio=1),
92
                        plot_bgcolor='rgba(0,0,0,0)',
93
                        width=600, height=600,
94
                        hovermode='x')
UNCOV
95
    fig.update_xaxes(showticklabels=False)
×
UNCOV
96
    fig.update_yaxes(showticklabels=False)
×
97

98
    #create dash app:
UNCOV
99
    app = Dash(__name__)
×
UNCOV
100
    app.layout = html.Div(
×
101
        style={'display': 'flex', 'backgroundColor': 'white', 'minHeight': '100vh'},
102
        children=[
103
            html.Div(
104
                style={'flex': '1'},
105
                children=[
106
                    dcc.Graph(id="graph", figure=fig, clear_on_unhover=True),
107
                    dcc.Input(id="search-input", type="text", placeholder="Search for sample...", style={'width': '100%', 'marginTop': '10px'}),
108
                    dcc.Tooltip(id="graph-tooltip"),
109
                ]
110
            ),
111
            html.Div(id="side-panel", style={'flex': '1', 'paddingLeft': '20px', 'backgroundColor': 'white'})
112
        ]
113
    )
114

115
    ### GRAPHING FUNCTION ON HOVER:
116
    if graphing_function is not None:
×
117
        @app.callback(
×
118
            Output("graph-tooltip", "show"),
119
            Output("graph-tooltip", "bbox"),
120
            Output("graph-tooltip", "children"),
121
            Input("graph", "hoverData"),
122
        )
UNCOV
123
        def display_hover(hoverData):
×
124
            if hoverData is None:
×
125
                return False, no_update, no_update
×
126
            # demo only shows the first point, but other points may also be available
UNCOV
127
            pt = hoverData["points"][0]
×
128
            chamber_id = str(pt['x']) + ',' + str(pt['y'])
×
129
            bbox = pt["bbox"]
×
130
            chamber_name = chamber_names[chamber_id]
×
131
            #get the data for the point:
UNCOV
132
            fig, ax = plt.subplots()
×
UNCOV
133
            ax = graphing_function(chamber_id, ax)
×
134
            #reduce whitespace on margins of graph:
UNCOV
135
            fig.subplots_adjust(left=0.1, bottom=0.1, right=0.9, top=0.9, wspace=0, hspace=0)
×
136
            #save the figure as a temp file:
UNCOV
137
            tempfile_name = tempfile.NamedTemporaryFile().name+'.png'
×
UNCOV
138
            plt.savefig(tempfile_name)
×
UNCOV
139
            plt.close()
×
140
            # #read in temp file as base64 encoded string:
UNCOV
141
            with open(tempfile_name, "rb") as image_file:
×
UNCOV
142
                img_src = "data:image/png;base64," + str(base64.b64encode(image_file.read()).decode("utf-8"))
×
143
            children = [
×
144
                html.Div(children=[
145
                    #no space after header:
146
                    html.H3('{},{}:  {}'.format(pt['x'], pt['y'], chamber_name), style={"color": 'black', "fontFamily":"Arial", "textAlign": "center", "marginBottom": "0px"}), #1-index
147
                    #add the image with reduced whitespace:
148
                    html.Img(src=img_src, style={"width": "100%"}),
149
                ],
150
                style={'width': '400px', 'white-space': 'none'})
151
            ]
152

UNCOV
153
            return True, bbox, children
×
154

155
    ### HIGHLIGHT ON HOVER / CLICK / SEARCH ###
156
    @app.callback(
×
157
        Output("graph", "figure"),
158
        Input("graph", "hoverData"),
159
        Input("graph", "clickData"),
160
        Input("search-input", "value"),
161
        State("graph", "figure"),
162
    )
UNCOV
163
    def update_highlights(hoverData, clickData, search_value, fig):
×
164
        # clear old shapes
UNCOV
165
        fig["layout"]["shapes"] = []
×
166

167
        # search => cyan outlines
UNCOV
168
        if search_value:
×
UNCOV
169
            search_str = search_value.lower()
×
170
            for cid, name in chamber_names.items():
×
171
                if name and search_str in name.lower():
×
172
                    i, j = map(int, cid.split(","))
×
173
                    fig["layout"]["shapes"].append({
×
174
                        "type": "rect",
175
                        "x0": i-0.4, "x1": i+0.4,
176
                        "y0": j-0.4, "y1": j+0.4,
177
                        "line": {"color": "cyan", "width": 3},
178
                        "fillcolor": "rgba(0,0,0,0)",
179
                        "name": "search"
180
                    })
181

182
        # hover => red outlines
UNCOV
183
        if hoverData:
×
UNCOV
184
            pt = hoverData["points"][0]
×
UNCOV
185
            sample = chamber_names.get(f"{pt['x']},{pt['y']}")
×
UNCOV
186
            if sample:
×
187
                for cid, name in chamber_names.items():
×
188
                    if name == sample:
×
189
                        i, j = map(int, cid.split(","))
×
190
                        fig["layout"]["shapes"].append({
×
191
                            "type": "rect",
192
                            "x0": i-0.4, "x1": i+0.4,
193
                            "y0": j-0.4, "y1": j+0.4,
194
                            "line": {"color": "red", "width": 2},
195
                            "fillcolor": "rgba(0,0,0,0)",
196
                            "name": "hover"
197
                        })
198

199
        # click => magenta outlines
UNCOV
200
        if clickData:
×
UNCOV
201
            pt = clickData["points"][0]
×
UNCOV
202
            sample = chamber_names.get(f"{pt['x']},{pt['y']}")
×
203
            if sample:
×
UNCOV
204
                for cid, name in chamber_names.items():
×
UNCOV
205
                    if name == sample:
×
206
                        i, j = map(int, cid.split(","))
×
207
                        fig["layout"]["shapes"].append({
×
208
                            "type": "rect",
209
                            "x0": i-0.4, "x1": i+0.4,
210
                            "y0": j-0.4, "y1": j+0.4,
211
                            "line": {"color": "magenta", "width": 3},
212
                            "fillcolor": "rgba(0,0,0,0)",
213
                            "name": "selected"
214
                        })
215

216
        return fig
×
217

218
    ### GRAPHING FUNCTION ON CLICK (side‐panel) ###
UNCOV
219
    if graphing_function is not None:
×
220
        @app.callback(
×
221
            Output("side-panel", "children"),
222
            Input("graph", "clickData"),
223
        )
224
        def display_click_side(clickData):
×
225
            if clickData is None:
×
226
                return no_update
×
227
            # identify clicked chamber
UNCOV
228
            pt = clickData["points"][0]
×
UNCOV
229
            cid = f"{pt['x']},{pt['y']}"
×
230
            sample = chamber_names.get(cid, "")
×
231

232
            # generate inset plot PNG
UNCOV
233
            fig2, ax2 = plt.subplots()
×
UNCOV
234
            ax2 = graphing_function(cid, ax2)
×
235
            fig2.subplots_adjust(left=0.1, bottom=0.1, right=0.9, top=0.9)
×
UNCOV
236
            tmp = tempfile.NamedTemporaryFile().name + ".png"
×
237
            plt.savefig(tmp)
×
UNCOV
238
            plt.close(fig2)
×
UNCOV
239
            with open(tmp, "rb") as f:
×
UNCOV
240
                img_src = "data:image/png;base64," + base64.b64encode(f.read()).decode("utf-8")
×
241

242
            # return side‐panel contents
UNCOV
243
            return html.Div([
×
244
                html.H3(f"{cid}: {sample}", style={"textAlign": "center"}),
245
                html.Img(src=img_src, style={"width": "100%"})
246
            ])
247

UNCOV
248
    free_port = find_free_port()
×
UNCOV
249
    print(f"Running on port: {free_port}")
×
UNCOV
250
    app.run_server(port=free_port)
×
251

UNCOV
252
def plot_chip_by_variable(experiment: 'HTBAMExperiment', analysis_name: str, variable: str):
×
253
    '''
254
    Plot a full chip with raw data and a specified variable.
255

256
    Parameters:
257
        experiment ('HTBAMExperiment'): the experiment object.
258
        analysis_name (str): the name of the analysis to be plotted.
259
        variable (str): the name of the variable to be plotted.
260

261
    Returns:
262
        None
263
    '''
264
    #plotting variable: We'll plot by luminance. We need a dictionary mapping chamber id (e.g. '1,1') to the value to be plotted (e.g. slope)
265
    
266
    analysis_data = experiment.get_run(analysis_name)     # Analysis data (to show slopes/intercepts)
×
267
    
UNCOV
268
    plotting_var = variable
×
UNCOV
269
    plotting_var_unit = analysis_data.dep_var_units[analysis_data.dep_var_type.index(plotting_var)]
×
270

271
    # Verify we have the variable:
272
    if plotting_var not in analysis_data.dep_var_type:
×
UNCOV
273
        raise ValueError(f"'{plotting_var}' not found in analysis data. Available variables: {analysis_data.dep_vars_types}")
×
274
    else:
275
        plotting_var_index = analysis_data.dep_var_type.index(plotting_var)
×
276

277
    concentration = analysis_data.dep_var[..., plotting_var_index] * plotting_var_unit # (n_chambers, n_conc, 1)
×
278
    
279
    #chamber_names: We'll provide the name of the sample in each chamber as well, in the same way:
280
    #chamber_names_dict = experiment._db_conn.get_chamber_name_dict()
281
    chamber_names = analysis_data.indep_vars.chamber_IDs # (n_chambers,)
×
UNCOV
282
    sample_names =  analysis_data.indep_vars.sample_IDs # (n_chambers,)
×
283

284
    # Create dictionary mapping chamber_id -> sample_name:
UNCOV
285
    sample_names_dict = {}
×
286
    for i, chamber_id in enumerate(chamber_names):
×
UNCOV
287
        sample_names_dict[chamber_id] = sample_names[i]
×
288

289
    # Create dictionary mapping chamber_id -> concentration:
UNCOV
290
    concentration_dict = {}
×
291
    for i, chamber_id in enumerate(chamber_names):
×
UNCOV
292
        concentration_dict[chamber_id] = concentration[i]
×
293
        
294

295
    #plotting function: We'll generate a subplot for each chamber, showing the histogram across replicates with our chosen chamber in red.
296
    def plot_chamber_variable(chamber_id, ax):
×
297
        #parameters:
298
        # get the sample_ID for this chamber:
299
        sample_id = sample_names_dict[chamber_id]
×
300
        # Get all replicates with this sample_id:
301
        repl_indices = [i for i, s in enumerate(sample_names) if s == sample_id]
×
302
        # Get the concentration values for these replicates:
UNCOV
303
        repl_concentrations = concentration[repl_indices]
×
304

305
        #x_data = concentration_dict[chamber_id]
UNCOV
306
        ax.hist(repl_concentrations, bins=10)
×
307
        # add the current datapoint as a red bar:
UNCOV
308
        ax.axvline(concentration_dict[chamber_id], color='red', linestyle='dashed', linewidth=2)
×
UNCOV
309
        ax.set_title(f'{chamber_id}: {sample_names_dict[chamber_id]}')
×
UNCOV
310
        ax.set_xlabel(f'{plotting_var}')
×
UNCOV
311
        ax.set_ylabel('Count')
×
UNCOV
312
        return ax
×
313

UNCOV
314
    plot_chip(concentration_dict, sample_names_dict, title=f'Analysis: {plotting_var}', graphing_function=plot_chamber_variable)
×
315

316
def plot_standard_curve_chip(experiment: 'HTBAMExperiment', analysis_name: str, experiment_name: str):
×
317
    '''
318
    Plot a full chip with raw data and std curve slopes.
319

320
    Parameters:
321
        experiment ('HTBAMExperiment'): the experiment object.
322
        analysis_name (str): the name of the analysis to be plotted.
323
        experiment_name (str): the name of the raw experiment data to be plotted.
324

325
    Returns:
326
        None
327
    '''
328
    #plotting variable: We'll plot by luminance. We need a dictionary mapping chamber id (e.g. '1,1') to the value to be plotted (e.g. slope)
329
    
UNCOV
330
    experiment_data = experiment.get_run(experiment_name) # Raw data from experiment (to show datapoints)
×
331
    analysis_data = experiment.get_run(analysis_name)     # Analysis data (to show slopes/intercepts)
×
332
    
UNCOV
333
    slope_idx = analysis_data.dep_var_type.index('slope')          # index of slope in dep_vars
×
334
    intercept_idx = analysis_data.dep_var_type.index('intercept')  # index of intercept in dep_vars
×
335
    r_squared_idx = analysis_data.dep_var_type.index('r_squared')  # index of r_squared in dep_vars
×
336
    
UNCOV
337
    slope_unit = analysis_data.dep_var_units[slope_idx]
×
UNCOV
338
    intercept_unit = analysis_data.dep_var_units[intercept_idx]
×
339
    r_squared_unit = analysis_data.dep_var_units[r_squared_idx]
×
340
    
341
    # Extract slopes and intercepts from analysis data
UNCOV
342
    slopes_to_plot = analysis_data.dep_var[..., slope_idx] * slope_unit          # (n_chambers,)
×
343
    intercepts_to_plot = analysis_data.dep_var[..., intercept_idx] * intercept_unit  # (n_chambers,)
×
344
    r_squared = analysis_data.dep_var[..., r_squared_idx] * r_squared_unit  # (n_chambers,)
×
345

UNCOV
346
    luminance_unit = experiment_data.dep_var_units[experiment_data.dep_var_type.index('luminance')]
×
347
    
348
    # Extract luminance and concentration from experiment data
349
    luminance_idx = experiment_data.dep_var_type.index('luminance')  # index of luminance in dep_vars
×
350
    luminance = experiment_data.dep_var[..., luminance_idx] * luminance_unit  # (n_chambers, n_timepoints, n_conc)
×
UNCOV
351
    concentration = experiment_data.indep_vars.concentration # (n_conc,)
×
352
    
353
    #chamber_names: We'll provide the name of the sample in each chamber as well, in the same way:
354
    chamber_names = experiment_data.indep_vars.chamber_IDs # (n_chambers,)
×
UNCOV
355
    sample_names =  experiment_data.indep_vars.sample_IDs # (n_chambers,)
×
356

357
    # Create dictionary mapping chamber_id -> sample_name:
358
    sample_names_dict = {}
×
UNCOV
359
    for i, chamber_id in enumerate(chamber_names):
×
360
        sample_names_dict[chamber_id] = sample_names[i]
×
361

362
    # Create dictionary mapping chamber_id -> slopes:
UNCOV
363
    slopes_dict = {}
×
364
    for i, chamber_id in enumerate(chamber_names):
×
365
        slopes_dict[chamber_id] = slopes_to_plot[i]
×
366

367
    #plotting function: We'll generate a subplot for each chamber, showing the raw data and the linear regression line.
368
    # to do this, we make a function that takes in the chamber_id and the axis object, and returns the axis object after plotting. Do NOT plot.show() in this function.
369
    def plot_chamber_slopes(chamber_id, ax):
×
370
        #parameters:
371

UNCOV
372
        x_data = concentration
×
373
        y_data = luminance[:, -1, chamber_names == chamber_id] # using last timepoint
×
374
        
375
        m = slopes_to_plot[chamber_names == chamber_id]
×
UNCOV
376
        b = intercepts_to_plot[chamber_names == chamber_id]
×
377
        
378
        #make a simple matplotlib plot
UNCOV
379
        ax.scatter(x_data, y_data)
×
UNCOV
380
        if not (np.isnan(m) or np.isnan(b)):
×
UNCOV
381
            ax.plot(x_data, m*x_data + b)
×
UNCOV
382
            ax.set_title(f'{chamber_id}: {sample_names_dict[chamber_id]}')
×
UNCOV
383
            ax.set_xlabel(f'Concentration ({concentration.units:~})')
×
UNCOV
384
            ax.set_ylabel(f'Luminance ({luminance.units:~})')
×
UNCOV
385
            ax.legend([f'Current Chamber Slope: {slopes_dict[chamber_id].magnitude:.2f} {slope_unit:~}'])
×
UNCOV
386
        return ax
×
387
    
UNCOV
388
    plot_chip(slopes_dict, sample_names_dict, graphing_function=plot_chamber_slopes, title='Standard Curve: Slope')
×
389

UNCOV
390
def plot_initial_rates_chip(experiment: 'HTBAMExperiment', analysis_name: str, experiment_name: str, skip_start_timepoint: bool = True,
×
391
                            fit_points_mask: str = None,
392
                            plot_xmax: float = None, plot_ymax: float = None,
393
                            plot_xmin: float = None, plot_ymin: float = None):
394
    '''
395
    Plot a full chip with raw data and fit initial rates.
396

397
    Parameters:
398
        experiment ('HTBAMExperiment'): the experiment object.
399
        analysis_name (str): the name of the analysis to be plotted.
400
        experiment_name (str): the name of the experiment to be plotted.
401
        skip_start_timepoint (bool): whether to skip the first timepoint in the analysis (Sometimes are unusually low). Default is True.
402

403
    Returns:
404
        None
405
    '''
406
    #plotting variable: We'll plot by luminance. We need a dictionary mapping chamber id (e.g. '1,1') to the value to be plotted (e.g. slope)
407
    
408
    experiment_data = experiment.get_run(experiment_name) # Raw data from experiment (to show datapoints)
×
409
    analysis_data = experiment.get_run(analysis_name)     # Analysis data (to show slopes/intercepts)
×
410
    if fit_points_mask is not None:
×
UNCOV
411
        fit_points_data = experiment.get_run(fit_points_mask)
×
412
    
413
    # Extract slopes and intercepts from analysis data
414
    slopes_idx = analysis_data.dep_var_type.index('slope')          # index of slope in dep_vars
×
415
    intercepts_idx = analysis_data.dep_var_type.index('intercept')  # index of intercept in dep_vars
×
416
    r_squared_idx = analysis_data.dep_var_type.index('r_squared')  # index of r_squared in dep_vars
×
417
    
UNCOV
418
    slope_unit = analysis_data.dep_var_units[slopes_idx]
×
419
    intercept_unit = analysis_data.dep_var_units[intercepts_idx]
×
UNCOV
420
    r_squared_unit = analysis_data.dep_var_units[r_squared_idx]
×
421

422
    # Extract slopes and intercepts from analysis data
UNCOV
423
    slopes_to_plot = analysis_data.dep_var[..., slopes_idx] * slope_unit          # (n_chambers, n_conc)
×
UNCOV
424
    intercepts_to_plot = analysis_data.dep_var[..., intercepts_idx] * intercept_unit  # (n_chambers, n_conc)
×
425
    r_squared = analysis_data.dep_var[..., r_squared_idx] * r_squared_unit          # (n_chambers, n_conc)
×
426

427
    # Extract product_concentration (Y) from experiment data
428
    product_conc_idx = experiment_data.dep_var_type.index('concentration')  # index of luminance in dep_vars
×
UNCOV
429
    product_conc_unit = experiment_data.dep_var_units[product_conc_idx]
×
UNCOV
430
    product_conc = experiment_data.dep_var[..., product_conc_idx] * product_conc_unit  # (n_chambers, n_timepoints, n_conc)
×
UNCOV
431
    if fit_points_mask is not None:
×
432
        points_to_plot = fit_points_data.dep_var[:,:,:,0] # (n_chambers, n_timepoints, n_conc)
×
433
    else:
UNCOV
434
        points_to_plot = np.ones(product_conc.shape, dtype=bool)
×
435

436
    substrate_conc = experiment_data.indep_vars.concentration # (n_conc,)
×
437
    time_data = experiment_data.indep_vars.time # (n_conc, n_timepoints)
×
438

439
    # If skip_start_timepoint is True, we'll skip the first timepoint in the analysis
UNCOV
440
    if skip_start_timepoint:
×
441
        product_conc = product_conc[:, 1:, :] # (n_chambers, n_timepoints-1, n_conc)
×
442
        time_data = time_data[:, 1:] # (n_conc, n_timepoints-1)
×
443
        points_to_plot = points_to_plot[:, 1:, :] # (n_chambers, n_timepoints-1, n_conc)
×
444
    
445
    #chamber_names: We'll provide the name of the sample in each chamber as well, in the same way:
446
    #chamber_names_dict = experiment._db_conn.get_chamber_name_dict()
UNCOV
447
    chamber_names = experiment_data.indep_vars.chamber_IDs # (n_chambers,)
×
448
    sample_names =  experiment_data.indep_vars.sample_IDs # (n_chambers,)
×
449
    
450
    # Create dictionary mapping chamber_id -> sample_name:
UNCOV
451
    sample_names_dict = {}
×
UNCOV
452
    for i, chamber_id in enumerate(chamber_names):
×
453
        sample_names_dict[chamber_id] = sample_names[i]
×
454

455
    # Create dictionary mapping chamber_id -> mean slopes:
456
    slopes_dict = {}
×
UNCOV
457
    for i, chamber_id in enumerate(chamber_names):
×
458
        slopes_dict[chamber_id] = np.nanmean(slopes_to_plot[:, i])
×
459

460
    #plotting function: We'll generate a subplot for each chamber, showing the raw data and the linear regression line.
461
    # to do this, we make a function that takes in the chamber_id and the axis object, and returns the axis object after plotting. Do NOT plot.show() in this function.
462

463
    def plot_chamber_initial_rates(chamber_id, ax):#, time_to_plot=time_to_plot):
×
464
        #N.B. Every so often, slope and line colors don't match up. Not sure why.
465
        
466
        #convert from 'x,y' to integer index in the array:
467
        #data_index = list(experiment._run_data[run_name]["chamber_idxs"]).index(chamber_id)
UNCOV
468
        x_data = time_data # same for all chambers              (n_timepoints, n_conc)
×
469
        y_data = product_conc[:, :, chamber_names == chamber_id]  #(n_timepoints, n_conc)
×
470

UNCOV
471
        y_data_mask = points_to_plot[:, :, chamber_names == chamber_id][:,:,0] # (n_timepoints, n_conc)
×
472
        # Make y_data_mask a boolean array:
UNCOV
473
        y_data_mask = y_data_mask > 0
×
474
        x_data_masked = np.where(y_data_mask, x_data, np.nan)
×
UNCOV
475
        y_data_masked = np.where(y_data_mask[..., np.newaxis], y_data, np.nan)
×
476
    
477
        m = slopes_to_plot[:, chamber_names == chamber_id]
×
478
        b = intercepts_to_plot[:, chamber_names == chamber_id]
×
479
        
480
        colors = sns.color_palette('husl', n_colors=y_data.shape[0])
×
481

482
        for i in range(y_data.shape[0]): #over each substrate concentration:
×
483

484
            ax.scatter(x_data[i], y_data[i,:].flatten(), color=colors[i], alpha=0.3) # raw data
×
485

486
            # Plot the fit points with alpha=1
487
            ax.scatter(x_data_masked[i], y_data_masked[i,:].flatten(), color=colors[i], alpha=1)
×
488
            
489
            ax.plot(x_data[i], m[i]*x_data[i] + b[i], color=colors[i], alpha=1, linewidth=2, label=f'{substrate_conc[i]:~}')  # fitted line
×
490

491
        # Set axis limits if provided
UNCOV
492
        if plot_xmax is not None:
×
493
            ax.set_xlim(right=plot_xmax)
×
UNCOV
494
        if plot_ymax is not None:
×
495
            ax.set_ylim(top=plot_ymax)
×
UNCOV
496
        if plot_xmin is not None:
×
UNCOV
497
            ax.set_xlim(left=plot_xmin)
×
UNCOV
498
        if plot_ymin is not None:
×
UNCOV
499
            ax.set_ylim(bottom=plot_ymin)
×
500
        
UNCOV
501
        ax.set_xlabel(f'Time ({time_data.units:~})')
×
UNCOV
502
        ax.set_ylabel(f'Product Concentration ({product_conc.units:~})')
×
503

UNCOV
504
        ax.legend()
×
505

506
        return ax
×
507
    
508
    plot_chip(slopes_dict, sample_names_dict, graphing_function=plot_chamber_initial_rates, title='Kinetics: Initial Rates')
×
509

510
def plot_initial_rates_vs_concentration_chip(experiment: 'HTBAMExperiment',
×
511
                                             analysis_name: str,
512
                                             model_fit_name: str = None,
513
                                             model_pred_data_name: str = None,
514
                                             x_log: bool = False,
515
                                             y_log: bool = False):
516
    """
517
    Plot initial rates vs substrate concentration for each chamber.
518
    Optionally overlay fitted curve from `model_pred_data_name` and
519
    annotate fit parameters from `model_fit_name`.
520
    """
521
    analysis: Data3D = experiment.get_run(analysis_name)
×
522
    si = analysis.dep_var_type.index("slope")
×
UNCOV
523
    slope_unit = analysis.dep_var_units[si]
×
524
    slopes = analysis.dep_var[..., si] * slope_unit  # (n_conc, n_chambers)
×
525
    conc   = analysis.indep_vars.concentration       # (n_conc,)
×
526

527
    if model_pred_data_name:
×
UNCOV
528
        mf_pred: Data3D = experiment.get_run(model_pred_data_name)
×
529
        yi = mf_pred.dep_var_type.index("y_pred")
×
530
        y_pred_unit = mf_pred.dep_var_units[yi]
×
531
        preds = mf_pred.dep_var[..., yi] * y_pred_unit          # (n_conc, n_chambers)
×
532

533
    if model_fit_name:
×
UNCOV
534
        mf_fit: Data2D = experiment.get_run(model_fit_name)
×
535
        fit_types = mf_fit.dep_var_type           # e.g. ["v_max","K_m","r_squared"]
×
536
        fit_vals = mf_fit.dep_var                # shape (n_chamb, len(fit_types))
×
537
        fit_units = mf_fit.dep_var_units
×
538

539
    chambers = analysis.indep_vars.chamber_IDs        # (n_chambers,)
×
UNCOV
540
    samples  = analysis.indep_vars.sample_IDs         # (n_chambers,)
×
541
    sample_names = {cid: samples[i] for i, cid in enumerate(chambers)}
×
542
    mean_rates   = {cid: np.nanmean(slopes[:, i]) for i, cid in enumerate(chambers)}
×
543

544
    def plot_rates_vs_conc(cid, ax):
×
UNCOV
545
        idx = (chambers == cid)
×
UNCOV
546
        x   = conc
×
547
        y   = slopes[:, idx].flatten()
×
548
        ax.scatter(x, y, alpha=0.7)
×
549

550
        if model_pred_data_name:
×
551
            y_p = preds[:, idx].flatten()
×
552
            ax.plot(x, y_p, color="red", label="model")
×
553

554
        if model_fit_name:
×
555
            # extract this chamber's fit row
556
            chamb_idx = np.where(chambers == cid)[0][0]
×
UNCOV
557
            vals = fit_vals[chamb_idx]
×
UNCOV
558
            txt = "".join(f"{nm}={v:.2f} {u:~}\n" for nm,v,u in zip(fit_types, vals, fit_units))
×
UNCOV
559
            ax.text(0.05, 0.95, txt, transform=ax.transAxes,
×
560
                    va="top", fontsize=8, bbox=dict(boxstyle="round", fc="white", alpha=0.7))
561

UNCOV
562
        if model_pred_data_name or model_fit_name:
×
UNCOV
563
            ax.legend()
×
UNCOV
564
        ax.set_title(f"{cid}: {sample_names[cid]}")
×
UNCOV
565
        ax.set_xlabel(f"Concentration ({conc.units:~})")
×
UNCOV
566
        ax.set_ylabel(f"Initial Rate ({slopes.units:~})")
×
UNCOV
567
        if x_log: ax.set_xscale("log")
×
UNCOV
568
        if y_log: ax.set_yscale("log")
×
UNCOV
569
        return ax
×
570

571
    plot_chip(mean_rates, sample_names,
×
572
              graphing_function=plot_rates_vs_conc,
573
              title="Initial Rates vs Concentration")
574

575
def plot_MM_chip(experiment: 'HTBAMExperiment',
×
576
                analysis_name: str,
577
                model_fit_name: str,
578
                model_pred_data_name: str = None,
579
                x_log: bool = False,
580
                y_log: bool = False):
581
    """
582
    Plot MM values, with inset initial rates vs substrate concentration for each chamber.
583
    Optionally overlay fitted curve from `model_pred_data_name` and
584
    annotate fit parameters from `model_fit_name`.
585
    """
586
    analysis: Data3D = experiment.get_run(analysis_name)
×
587
    si = analysis.dep_var_type.index("slope")
×
588
    slope_unit = analysis.dep_var_units[si]
×
589
    slopes = analysis.dep_var[..., si] * slope_unit  # (n_conc, n_chambers)
×
UNCOV
590
    conc   = analysis.indep_vars.concentration       # (n_conc,)
×
591

592
    mf_fit: Data2D = experiment.get_run(model_fit_name)
×
593
    fit_types = mf_fit.dep_var_type           # e.g. ["v_max","K_m","r_squared"]
×
UNCOV
594
    fit_vals = mf_fit.dep_var                # shape (n_chamb, len(fit_types))
×
595
    fit_units = mf_fit.dep_var_units
×
596

597
    mm_idx = mf_fit.dep_var_type.index("v_max")
×
598
    mms = mf_fit.dep_var[..., mm_idx] * fit_units[mm_idx]   # (n_chambers,)
×
599

600
    if model_pred_data_name:
×
601
        mf_pred: Data3D = experiment.get_run(model_pred_data_name)
×
UNCOV
602
        yi = mf_pred.dep_var_type.index("y_pred")
×
603
        y_pred_unit = mf_pred.dep_var_units[yi]
×
UNCOV
604
        preds = mf_pred.dep_var[..., yi] * y_pred_unit          # (n_conc, n_chambers)
×
605

606
    chambers = analysis.indep_vars.chamber_IDs        # (n_chambers,)
×
607
    samples  = analysis.indep_vars.sample_IDs         # (n_chambers,)
×
UNCOV
608
    sample_names = {cid: samples[i] for i, cid in enumerate(chambers)}
×
609
    #mean_rates   = {cid: np.nanmean(slopes[:, i]) for i, cid in enumerate(chambers)}
610
    mms_to_plot = {cid: mms[i] for i, cid in enumerate(chambers)}
×
611

UNCOV
612
    def plot_rates_vs_conc(cid, ax):
×
UNCOV
613
        idx = (chambers == cid)
×
614
        x   = conc
×
615
        y   = slopes[:, idx].flatten()
×
616
        ax.scatter(x, y, alpha=0.7, label="current well")
×
617

618
        if model_pred_data_name:
×
619
            # show envelope of model fits for all wells with this sample
620
            sample = sample_names[cid]
×
621
            same_idxs = [i for i, s in enumerate(samples) if s == sample]
×
622
            y_all = preds[:, same_idxs]               # (n_conc, n_same)
×
623
            
624
            # Show the 95% confidence interval:
UNCOV
625
            y_min = np.nanpercentile(y_all, 2.5, axis=1)
×
626
            y_max = np.nanpercentile(y_all, 97.5, axis=1)
×
627
            
628
            # then overplot this chamber’s model fit
629
            y_p = preds[:, idx].flatten()
×
630
            ax.plot(x, y_p, color="red", label="current well fit")
×
631
            ax.fill_between(x, y_min, y_max, color="gray", alpha=0.3, label='95% CI')
×
632

633
        if model_fit_name:
×
634
            # extract this chamber's fit row
635
            chamb_idx = np.where(chambers == cid)[0][0]
×
UNCOV
636
            vals = fit_vals[chamb_idx]
×
UNCOV
637
            txt = "".join(f"{nm}={v:.2f} {u:~}\n" for nm,v,u in zip(fit_types, vals, fit_units))
×
UNCOV
638
            ax.text(0.05, 0.95, txt, transform=ax.transAxes,
×
639
                    va="top", fontsize=8, bbox=dict(boxstyle="round", fc="white", alpha=0.7))
640
        
UNCOV
641
        if model_pred_data_name or model_fit_name:
×
UNCOV
642
            ax.legend()
×
UNCOV
643
        ax.set_title(f"{cid}: {sample_names[cid]}")
×
UNCOV
644
        ax.set_xlabel(f"Concentration ({conc.units:~})")
×
UNCOV
645
        ax.set_ylabel(f"Initial Rate ({slopes.units:~})")
×
UNCOV
646
        if x_log: ax.set_xscale("log")
×
UNCOV
647
        if y_log: ax.set_yscale("log")
×
UNCOV
648
        return ax
×
649

650
    plot_chip(mms_to_plot, sample_names,
×
651
              graphing_function=plot_rates_vs_conc,
652
              title="Initial Rates vs Concentration")   
653

654
def plot_MM_div_E_chip(experiment: 'HTBAMExperiment',
×
655
                analysis_name: str,
656
                model_fit_name: str,
657
                dep_var_name='slope',
658
                x_log: bool = False,
659
                y_log: bool = False):
660
    """
661
    Plot MM values, with inset initial rates vs substrate concentration for each chamber.
662
    Optionally overlay fitted curve from `model_pred_data_name` and
663
    annotate fit parameters from `model_fit_name`.
664
    """
UNCOV
665
    analysis: Data3D = experiment.get_run(analysis_name)
×
UNCOV
666
    si = analysis.dep_var_type.index(dep_var_name)
×
UNCOV
667
    slope_unit = analysis.dep_var_units[si]
×
668
    slopes = analysis.dep_var[..., si] * slope_unit  # (n_conc, n_chambers)
×
669
    conc   = analysis.indep_vars.concentration       # (n_conc,)
×
670

UNCOV
671
    mf_fit: Data2D = experiment.get_run(model_fit_name)
×
672
    fit_types = mf_fit.dep_var_type           # e.g. ["v_max","K_m","r_squared", "kcat"]
×
673
    fit_vals = mf_fit.dep_var                # shape (n_chamb, len(fit_types))
×
UNCOV
674
    fit_units = mf_fit.dep_var_units
×
675

676
    kcat_idx = mf_fit.dep_var_type.index("kcat")
×
677
    all_kcats = mf_fit.dep_var[..., kcat_idx] * fit_units[kcat_idx]   # (n_chambers,)
×
678
    kM_idx = mf_fit.dep_var_type.index("K_m")
×
679
    all_kMs = mf_fit.dep_var[..., kM_idx] * fit_units[kM_idx]   # (n_chambers,)
×
680

681
    # We'll be generating pred_data on-the-fly by pushing our kcat up and down one stdev.
682

683
    chambers = analysis.indep_vars.chamber_IDs        # (n_chambers,)
×
UNCOV
684
    samples  = analysis.indep_vars.sample_IDs         # (n_chambers,)
×
685
    sample_names = {cid: samples[i] for i, cid in enumerate(chambers)}
×
686
    #mean_rates   = {cid: np.nanmean(slopes[:, i]) for i, cid in enumerate(chambers)}
UNCOV
687
    kcats_to_plot = {cid: all_kcats[i] for i, cid in enumerate(chambers)}
×
688
    kMs_to_plot = {cid: all_kMs[i] for i, cid in enumerate(chambers)}
×
689

UNCOV
690
    def plot_rates_vs_conc(cid, ax):
×
691
        idx = (chambers == cid)
×
692
        x   = conc
×
UNCOV
693
        y   = slopes[:, idx].flatten()
×
UNCOV
694
        ax.scatter(x, y, alpha=0.7, label="current well")
×
695

696

UNCOV
697
        sample = sample_names[cid]
×
UNCOV
698
        same_idxs = [i for i, s in enumerate(samples) if s == sample]
×
699
        
700
        sample_kcats = all_kcats[same_idxs]
×
UNCOV
701
        sample_kMs = all_kMs[same_idxs]
×
702

703
        current_kcat = kcats_to_plot[cid]
×
UNCOV
704
        current_km = kMs_to_plot[cid]
×
705

706
        mean_kcat = np.nanmean(sample_kcats)
×
707
        mean_km = np.nanmean(sample_kMs)
×
708

709
        # stdev of kcat?
710
        kcat_stdev = np.nanstd(sample_kcats)
×
UNCOV
711
        km_stdev = np.nanstd(sample_kMs)
×
712
        
713
        # push kcat up and down one stdev
UNCOV
714
        kcat_up = mean_kcat + kcat_stdev
×
715
        kcat_down = mean_kcat - kcat_stdev
×
716

717
        # calculate new slopes
UNCOV
718
        from htbam_analysis.analysis.fit import mm_model
×
719

UNCOV
720
        pred_y_mean = mm_model(conc, mean_kcat, mean_km)
×
UNCOV
721
        pred_y_up = mm_model(conc, kcat_up, mean_km)
×
UNCOV
722
        pred_y_down = mm_model(conc, kcat_down, mean_km)
×
UNCOV
723
        pred_y_current = mm_model(conc, current_kcat, current_km)
×
724

UNCOV
725
        ax.plot(x, pred_y_current, color="blue", label="current well fit")
×
726

UNCOV
727
        ax.plot(x, pred_y_mean, color="gray", label="mean well fit")
×
UNCOV
728
        ax.fill_between(x, pred_y_down, pred_y_up, color="gray", alpha=0.3, label='$k_{cat}$ ± 1 stdev')
×
729

UNCOV
730
        ax.text(0.05, 0.95, 
×
731
                f"$\\overline{{k_{{cat}}}}$ = {mean_kcat.magnitude:.2f} ± {kcat_stdev:.2f~}\n"
732
                f"$\\overline{{K_{{M}}}}$ = {mean_km.magnitude:.2f} ± {km_stdev:.2f~}", 
733
                transform=ax.transAxes,
734
                va="top", fontsize=8, zorder=10,
735
                bbox=dict(boxstyle="round", fc="white", alpha=0.7))
736

737
        # if model_fit_name:
738
        #     # extract this chamber's fit row
739
        #     chamb_idx = np.where(chambers == cid)[0][0]
740
        #     vals = fit_vals[chamb_idx]
741
        #     txt = "".join(f"{nm}={v:.2f} {u:~}\n" for nm,v,u in zip(fit_types, vals, fit_units))
742
        #     ax.text(0.05, 0.95, txt, transform=ax.transAxes,
743
        #             va="top", fontsize=8, bbox=dict(boxstyle="round", fc="white", alpha=0.7))
744
        
745
        #if model_pred_data_name or model_fit_name:
UNCOV
746
        ax.legend()
×
UNCOV
747
        ax.set_title(f"{cid}: {sample_names[cid]}")
×
UNCOV
748
        ax.set_xlabel(f"$[S]$ ({conc.units:~})")
×
UNCOV
749
        ax.set_ylabel(f"$V_0/[E]$ ({slopes.units:~})")
×
UNCOV
750
        if x_log: ax.set_xscale("log")
×
UNCOV
751
        if y_log: ax.set_yscale("log")
×
UNCOV
752
        return ax
×
753

754
    plot_chip(kcats_to_plot, sample_names,
×
755
              graphing_function=plot_rates_vs_conc,
756
              title="V_0/[E] vs [S]")       
757

758
def plot_ic50_chip(experiment: 'HTBAMExperiment',
×
759
                analysis_name: str,
760
                model_fit_name: str,
761
                model_pred_data_name: str = None,
762
                x_log: bool = False,
763
                y_log: bool = False):
764
    """
765
    Plot ic50 values, with inset initial rates vs substrate concentration for each chamber.
766
    Optionally overlay fitted curve from `model_pred_data_name` and
767
    annotate fit parameters from `model_fit_name`.
768
    """
769
    analysis: Data3D = experiment.get_run(analysis_name)
×
770
    si = analysis.dep_var_type.index("slope")
×
771
    slope_unit = analysis.dep_var_units[si]
×
772
    slopes = analysis.dep_var[..., si] * slope_unit  # (n_conc, n_chambers)
×
UNCOV
773
    conc   = analysis.indep_vars.concentration       # (n_conc,)
×
774

775
    mf_fit: Data2D = experiment.get_run(model_fit_name)
×
776
    fit_types = mf_fit.dep_var_type           # e.g. ["v_max","K_m","r_squared"]
×
UNCOV
777
    fit_vals = mf_fit.dep_var                # shape (n_chamb, len(fit_types))
×
778
    fit_units = mf_fit.dep_var_units
×
779

780
    ic50s_idx = mf_fit.dep_var_type.index("ic50")
×
781
    ic50s = mf_fit.dep_var[..., ic50s_idx] * fit_units[ic50s_idx]    # (n_chambers,)
×
782

783
    if model_pred_data_name:
×
784
        mf_pred: Data3D = experiment.get_run(model_pred_data_name)
×
UNCOV
785
        yi = mf_pred.dep_var_type.index("y_pred")
×
786
        y_pred_unit = mf_pred.dep_var_units[yi]
×
UNCOV
787
        preds = mf_pred.dep_var[..., yi] * y_pred_unit          # (n_conc, n_chambers)
×
788

789
    chambers = analysis.indep_vars.chamber_IDs        # (n_chambers,)
×
790
    samples  = analysis.indep_vars.sample_IDs         # (n_chambers,)
×
UNCOV
791
    sample_names = {cid: samples[i] for i, cid in enumerate(chambers)}
×
792
    #mean_rates   = {cid: np.nanmean(slopes[:, i]) for i, cid in enumerate(chambers)}
793
    ic50s_to_plot = {cid: ic50s[i] for i, cid in enumerate(chambers)}
×
794

UNCOV
795
    def plot_rates_vs_conc(cid, ax):
×
UNCOV
796
        idx = (chambers == cid)
×
797
        x   = conc
×
798
        y   = slopes[:, idx].flatten()
×
799
        ax.scatter(x, y, alpha=0.7, label="current well")
×
800

801
        if model_pred_data_name:
×
802
            # show envelope of model fits for all wells with this sample
803
            sample = sample_names[cid]
×
804
            same_idxs = [i for i, s in enumerate(samples) if s == sample]
×
805
            y_all = preds[:, same_idxs]               # (n_conc, n_same)
×
806

807
            # Show the 95% confidence interval:
UNCOV
808
            y_min = np.nanpercentile(y_all, 2.5, axis=1)
×
809
            y_max = np.nanpercentile(y_all, 97.5, axis=1)
×
810
            
811
            # then overplot this chamber’s model fit
812
            y_p = preds[:, idx].flatten()
×
813
            ax.plot(x, y_p, color="red", label="current well fit")
×
814
            ax.fill_between(x, y_min, y_max, color="gray", alpha=0.3, label='95% CI')
×
815

816
        if model_fit_name:
×
817
            # extract this chamber's fit row
818
            chamb_idx = np.where(chambers == cid)[0][0]
×
UNCOV
819
            vals = fit_vals[chamb_idx]
×
UNCOV
820
            txt = "".join(f"{nm}={v:.2f} {u:~}\n" for nm,v,u in zip(fit_types, vals, fit_units))
×
UNCOV
821
            ax.text(0.05, 0.95, txt, transform=ax.transAxes,
×
822
                    va="top", fontsize=8, bbox=dict(boxstyle="round", fc="white", alpha=0.7))
823
        
UNCOV
824
        if model_pred_data_name or model_fit_name:
×
UNCOV
825
            ax.legend()
×
UNCOV
826
        ax.set_title(f"{cid}: {sample_names[cid]}")
×
UNCOV
827
        ax.set_xlabel(f"Concentration ({conc.units:~})")
×
UNCOV
828
        ax.set_ylabel(f"Initial Rate ({slopes.units:~})")
×
UNCOV
829
        if x_log: ax.set_xscale("log")
×
UNCOV
830
        if y_log: ax.set_yscale("log")
×
UNCOV
831
        return ax
×
832

UNCOV
833
    plot_chip(ic50s_to_plot, sample_names,
×
834
              graphing_function=plot_rates_vs_conc,
835
              title="Initial Rates vs Concentration")
836

837
def plot_enzyme_concentration_chip(experiment: 'HTBAMExperiment', analysis_name: str, skip_start_timepoint: bool = True):
×
838
    '''
839
    Plot a full chip with raw data and fit initial rates.
840

841
    Parameters:
842
        experiment ('HTBAMExperiment'): the experiment object.
843
        analysis_name (str): the name of the analysis to be plotted.
844
        skip_start_timepoint (bool): whether to skip the first timepoint in the analysis (Sometimes are unusually low). Default is True.
845

846
    Returns:
847
        None
848
    '''
849
    #plotting variable: We'll plot by enzyme concentration. We need a dictionary mapping chamber id (e.g. '1,1') to the value to be plotted (e.g. slope)
850
    analysis_data = experiment.get_run(analysis_name)     # Analysis data (to show slopes/intercepts)
×
851
    
UNCOV
852
    plotting_var = 'concentration'
×
853

854
    # Verify we have the variable:
855
    if plotting_var not in analysis_data.dep_var_type:
×
856
        raise ValueError(f"'{plotting_var}' not found in analysis data. Available variables: {analysis_data.dep_vars_types}")
×
857
    else:
UNCOV
858
        plotting_var_index = analysis_data.dep_var_type.index(plotting_var)
×
859

860
    conc_units = analysis_data.dep_var_units[plotting_var_index]
×
861
    concentration = analysis_data.dep_var[..., plotting_var_index] * conc_units # (n_chambers, n_conc, 1)
×
862
    
863
    #chamber_names: We'll provide the name of the sample in each chamber as well, in the same way:
864
    #chamber_names_dict = experiment._db_conn.get_chamber_name_dict()
865
    chamber_names = analysis_data.indep_vars.chamber_IDs # (n_chambers,)
×
866
    sample_names =  analysis_data.indep_vars.sample_IDs # (n_chambers,)
×
867

868
    # Create dictionary mapping chamber_id -> sample_name:
UNCOV
869
    sample_names_dict = {}
×
870
    for i, chamber_id in enumerate(chamber_names):
×
871
        sample_names_dict[chamber_id] = sample_names[i]
×
872

873
    # Create dictionary mapping chamber_id -> mean slopes:
874
    conc_dict = {}
×
UNCOV
875
    for i, chamber_id in enumerate(chamber_names):
×
876
        conc_dict[chamber_id] = np.nanmean(concentration[i])
×
877

UNCOV
878
    def graphing_function(chamber_id, ax):
×
879
        # plot histogram of the concentration from all chambers with the same sample name as the chamber_id
UNCOV
880
        sample_name = sample_names_dict[chamber_id]
×
UNCOV
881
        conc = concentration[sample_names == sample_name]
×
UNCOV
882
        ax.hist(conc, bins=10)
×
883
        # Make the bin for the current chamber red:
884
        # What bin has the max count?
UNCOV
885
        bin_max_count = np.max(np.histogram(conc.magnitude, bins=10)[0])
×
UNCOV
886
        ax.vlines(conc_dict[chamber_id].magnitude, 0, bin_max_count, colors='red', linestyles='--')
×
UNCOV
887
        ax.set_title(f'Concentration of {sample_name}')
×
UNCOV
888
        ax.set_xlabel(f'Concentration {conc_units:~}')
×
UNCOV
889
        ax.set_ylabel('Count')
×
890
        # print legend current chamber with concentration:
UNCOV
891
        ax.legend([f'Current Chamber: {conc_dict[chamber_id].magnitude:.2f} {conc_units:~}'])
×
UNCOV
892
        return ax
×
893
    
UNCOV
894
    plot_chip(conc_dict, sample_names_dict, 
×
895
            graphing_function=graphing_function,
896
            title=f'Enzyme Concentration')
897

898
def plot_mask_chip(experiment: 'HTBAMExperiment', mask_name: str):
×
899
    '''
900
    Plot a full chip with raw data and fit initial rates.
901

902
    Parameters:
903
        experiment ('HTBAMExperiment'): the experiment object.
904
        mask_name (str): the name of the mask to be plotted. (Data3D or Data2D)
905

906
    Returns:
907
        None
908
    '''
909
    #plotting variable: We'll plot by luminance. We need a dictionary mapping chamber id (e.g. '1,1') to the value to be plotted (e.g. slope)
UNCOV
910
    mask_data = experiment.get_run(mask_name)     # Analysis data (to show slopes/intercepts)
×
911
    
912
    dtype = type(mask_data)
×
913
    assert dtype in [Data3D, Data2D], "mask_data must be of type Data3D or Data2D."
×
914

UNCOV
915
    mask_idx = mask_data.dep_var_type.index('mask')
×
916

917
    # If we're using a data2D
918
    mask = mask_data.dep_var[..., mask_idx] # (n_conc, n_chambers,)
×
919

920
    # We want to plot the number of concentrations that pass the mask in each well.
921
    # so, we'll sum across the concentration dimension, leaving an (n_chambers,) array
922

923
    #passed_conc = np.sum(mask, axis=0)
924
    #print(mask.shape)
925

926
    #chamber_names: We'll provide the name of the sample in each chamber as well, in the same way:
UNCOV
927
    chamber_names = mask_data.indep_vars.chamber_IDs # (n_chambers,)
×
UNCOV
928
    sample_names =  mask_data.indep_vars.sample_IDs # (n_chambers,)
×
929

930
    # Create dictionary mapping chamber_id -> sample_name:
931
    sample_names_dict = {}
×
UNCOV
932
    for i, chamber_id in enumerate(chamber_names):
×
933
        sample_names_dict[chamber_id] = sample_names[i]
×
934

935
    # Create dictionary mapping chamber_id -> mean slopes:
UNCOV
936
    mask_sum = {}
×
UNCOV
937
    for i, chamber_id in enumerate(chamber_names):
×
UNCOV
938
        if dtype == Data3D:
×
UNCOV
939
            mask_sum[chamber_id] = mask[:, i].sum()  # sum across concentrations for each chamber
×
UNCOV
940
        elif dtype == Data2D:
×
UNCOV
941
            mask_sum[chamber_id] = mask[i].sum()
×
942

943
    #plotting function: We'll generate a subplot for each chamber, showing the raw data and the linear regression line.
944
    # to do this, we make a function that takes in the chamber_id and the axis object, and returns the axis object after plotting. Do NOT plot.show() in this function.
945
    
UNCOV
946
    plot_chip(mask_sum, sample_names_dict, title=f'# Concentrations that pass filter: {mask_name}')
×
947

UNCOV
948
def plot_chip_by_variable(experiment: 'HTBAMExperiment', analysis_name: str, variable: str):
×
949
    '''
950
    Plot a chip using arbitrary DataND object, by specifying the variable to plot.
951

952
    Parameters:
953
        experiment ('HTBAMExperiment'): the experiment object.
954
        analysis_name (str): the name of the analysis to be plotted. (Data3D or Data2D)
955
        variable (str): the variable to plot.
956

957
    Returns:
958
        None
959
    '''
960

961
    #plotting variable: We'll plot by luminance. We need a dictionary mapping chamber id (e.g. '1,1') to the value to be plotted (e.g. slope)
UNCOV
962
    data = experiment.get_run(analysis_name)     # Analysis data (to show slopes/intercepts)
×
963
    
964
    dtype = type(data)
×
965
    assert dtype in [Data3D, Data2D], "data must be of type Data3D or Data2D."
×
966

UNCOV
967
    mask_idx = data.dep_var_type.index(variable)
×
968

969
    # If we're using a data2D
970
    mask = data.dep_var[..., mask_idx] # (n_conc, n_chambers,)
×
971

972
    # We want to plot the number of concentrations that pass the mask in each well.
973
    # so, we'll sum across the concentration dimension, leaving an (n_chambers,) array
974

975
    #passed_conc = np.sum(mask, axis=0)
976
    #print(mask.shape)
977

978
    #chamber_names: We'll provide the name of the sample in each chamber as well, in the same way:
UNCOV
979
    chamber_names = data.indep_vars.chamber_IDs # (n_chambers,)
×
UNCOV
980
    sample_names =  data.indep_vars.sample_IDs # (n_chambers,)
×
981

982
    # Create dictionary mapping chamber_id -> sample_name:
983
    sample_names_dict = {}
×
UNCOV
984
    for i, chamber_id in enumerate(chamber_names):
×
UNCOV
985
        sample_names_dict[chamber_id] = sample_names[i]
×
986

987
    # Create dictionary mapping chamber_id -> mean slopes:
UNCOV
988
    mask_sum = {}
×
UNCOV
989
    for i, chamber_id in enumerate(chamber_names):
×
UNCOV
990
        if dtype == Data3D:
×
UNCOV
991
            mask_sum[chamber_id] = mask[:, i].sum()  # sum across concentrations for each chamber
×
UNCOV
992
        elif dtype == Data2D:
×
UNCOV
993
            mask_sum[chamber_id] = mask[i].sum()
×
994

995
    #plotting function: We'll generate a subplot for each chamber, showing the raw data and the linear regression line.
996
    # to do this, we make a function that takes in the chamber_id and the axis object, and returns the axis object after plotting. Do NOT plot.show() in this function.
997
    
UNCOV
998
    plot_chip(mask_sum, sample_names_dict, title=f'{variable}')
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc