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

pinneylab / htbam_analysis / 20404609504

21 Dec 2025 04:22AM UTC coverage: 16.382% (-1.5%) from 17.844%
20404609504

push

github

Nicholas-Freitas
feat: Improve Pint quantity handling in `transform` module for `GroupedField` and `DeviceField`.  Refactor plotting, and add new publication-style PDF export function.

0 of 535 new or added lines in 3 files covered. (0.0%)

6 existing lines in 3 files now uncovered.

470 of 2869 relevant lines covered (16.38%)

0.66 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
×
7

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

13
# HTBAM Data
NEW
14
from htbam_db_api.data import Data4D, Data3D, Data2D
×
15

16
# Plotting
NEW
17
import seaborn as sns
×
18

UNCOV
19
def plot_chip(plotting_var, chamber_names, graphing_function=None, title=None):
×
20
    ''' This function creates a Dash visualization of a chip, based on a certain Run (run_name)
21
        Inputs:
22
            plotting_var: a dictionary mapping chamber_id to the variable to be plotted for that chamber
23
            chamber_names: a dictionary mapping chamber_id to the name of the sample in the chamber (e.g. '1,1': ecADK_XYZ')
24
            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.
25
            title: a string to be used as the title of the plot
26
        TODO: make all the variables stored in Dash properly...
27
    '''
28

29
    # Make the image array
30
    #NB: eventually, store width/height in DB and reference!
31
    img_array = np.zeros([56,32])
×
32

33
    # Here we're plotting to value for each chamber (e.g. coloring by std curve slope)
34
    units = ''
×
35
    for chamber_id, value in plotting_var.items():
×
36
        x = int(chamber_id.split(',')[0])
×
37
        y = int(chamber_id.split(',')[1])
×
38
        
39
        # Check if value has units (Pint quantity)
40
        if hasattr(value, 'magnitude') and hasattr(value, 'units'):
×
41
            img_array[y-1,x-1] = float(value.magnitude)
×
42
            if not units:
×
43
                units = str(f'{value.units:~}')
×
44
        else:
45
            img_array[y-1,x-1] = float(value) 
×
46
    
47
    #generate title
48
    if title is None:
×
49
        title = ''
×
50
    
51
    #Create the figure
52
    layout = go.Layout()
×
53

54
    # create 1‐indexed axes
55
    x_vals = list(range(1, img_array.shape[1] + 1))
×
56
    y_vals = list(range(1, img_array.shape[0] + 1))
×
57

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

UNCOV
61
    heatmap = go.Heatmap(
×
62
        x=x_vals,
63
        y=y_vals,
64
        z=img_array,
65
        zmin=zmin,
66
        zmax=zmax,
67
        colorscale='Viridis',
68
        colorbar=dict(title=units),
69
        hovertemplate='x=%{x}<br>y=%{y}<br>z=%{z} ' + units +'<extra></extra>'
70
    )
71
    
72
    fig = go.Figure(layout=layout, data=heatmap)
×
73

74
    #center title in fig
75
    fig.update_layout(title=title,
×
76
                        title_x=0.5, 
77
                        yaxis=dict(scaleanchor="x", scaleratio=1, autorange='reversed'), 
78
                        xaxis=dict(scaleratio=1),
79
                        plot_bgcolor='rgba(0,0,0,0)',
80
                        width=600, height=600,
81
                        hovermode='x')
82
    fig.update_xaxes(showticklabels=False)
×
83
    fig.update_yaxes(showticklabels=False)
×
84

85
    #create dash app:
86
    app = Dash(__name__)
×
87
    app.layout = html.Div(
×
88
        style={'display': 'flex', 'backgroundColor': 'white', 'minHeight': '100vh'},
89
        children=[
90
            html.Div(
91
                style={'flex': '1'},
92
                children=[
93
                    dcc.Graph(id="graph", figure=fig, clear_on_unhover=True),
94
                    dcc.Input(id="search-input", type="text", placeholder="Search for sample...", style={'width': '100%', 'marginTop': '10px'}),
95
                    dcc.Tooltip(id="graph-tooltip"),
96
                ]
97
            ),
98
            html.Div(id="side-panel", style={'flex': '1', 'paddingLeft': '20px', 'backgroundColor': 'white'})
99
        ]
100
    )
101

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

140
            return True, bbox, children
×
141

142
    ### HIGHLIGHT ON HOVER / CLICK / SEARCH ###
143
    @app.callback(
×
144
        Output("graph", "figure"),
145
        Input("graph", "hoverData"),
146
        Input("graph", "clickData"),
147
        Input("search-input", "value"),
148
        State("graph", "figure"),
149
    )
NEW
150
    def update_highlights(hoverData, clickData, search_value, fig):
×
151
        # clear old shapes
152
        fig["layout"]["shapes"] = []
×
153

154
        # search => cyan outlines
NEW
155
        if search_value:
×
NEW
156
            search_str = search_value.lower()
×
NEW
157
            for cid, name in chamber_names.items():
×
NEW
158
                if name and search_str in name.lower():
×
NEW
159
                    i, j = map(int, cid.split(","))
×
NEW
160
                    fig["layout"]["shapes"].append({
×
161
                        "type": "rect",
162
                        "x0": i-0.4, "x1": i+0.4,
163
                        "y0": j-0.4, "y1": j+0.4,
164
                        "line": {"color": "cyan", "width": 3},
165
                        "fillcolor": "rgba(0,0,0,0)",
166
                        "name": "search"
167
                    })
168

169
        # hover => red outlines
170
        if hoverData:
×
171
            pt = hoverData["points"][0]
×
172
            sample = chamber_names.get(f"{pt['x']},{pt['y']}")
×
173
            if sample:
×
174
                for cid, name in chamber_names.items():
×
175
                    if name == sample:
×
176
                        i, j = map(int, cid.split(","))
×
177
                        fig["layout"]["shapes"].append({
×
178
                            "type": "rect",
179
                            "x0": i-0.4, "x1": i+0.4,
180
                            "y0": j-0.4, "y1": j+0.4,
181
                            "line": {"color": "red", "width": 2},
182
                            "fillcolor": "rgba(0,0,0,0)",
183
                            "name": "hover"
184
                        })
185

186
        # click => magenta outlines
187
        if clickData:
×
188
            pt = clickData["points"][0]
×
189
            sample = chamber_names.get(f"{pt['x']},{pt['y']}")
×
190
            if sample:
×
191
                for cid, name in chamber_names.items():
×
192
                    if name == sample:
×
193
                        i, j = map(int, cid.split(","))
×
194
                        fig["layout"]["shapes"].append({
×
195
                            "type": "rect",
196
                            "x0": i-0.4, "x1": i+0.4,
197
                            "y0": j-0.4, "y1": j+0.4,
198
                            "line": {"color": "magenta", "width": 3},
199
                            "fillcolor": "rgba(0,0,0,0)",
200
                            "name": "selected"
201
                        })
202

203
        return fig
×
204

205
    ### GRAPHING FUNCTION ON CLICK (side‐panel) ###
206
    if graphing_function is not None:
×
207
        @app.callback(
×
208
            Output("side-panel", "children"),
209
            Input("graph", "clickData"),
210
        )
211
        def display_click_side(clickData):
×
212
            if clickData is None:
×
213
                return no_update
×
214
            # identify clicked chamber
215
            pt = clickData["points"][0]
×
216
            cid = f"{pt['x']},{pt['y']}"
×
217
            sample = chamber_names.get(cid, "")
×
218

219
            # generate inset plot PNG
220
            fig2, ax2 = plt.subplots()
×
221
            ax2 = graphing_function(cid, ax2)
×
222
            fig2.subplots_adjust(left=0.1, bottom=0.1, right=0.9, top=0.9)
×
223
            tmp = tempfile.NamedTemporaryFile().name + ".png"
×
224
            plt.savefig(tmp)
×
225
            plt.close(fig2)
×
226
            with open(tmp, "rb") as f:
×
227
                img_src = "data:image/png;base64," + base64.b64encode(f.read()).decode("utf-8")
×
228

229
            # return side‐panel contents
230
            return html.Div([
×
231
                html.H3(f"{cid}: {sample}", style={"textAlign": "center"}),
232
                html.Img(src=img_src, style={"width": "100%"})
233
            ])
234

235
    app.run_server()
×
236

NEW
237
def plot_chip_by_variable(experiment: 'HTBAMExperiment', analysis_name: str, variable: str):
×
238
    '''
239
    Plot a full chip with raw data and a specified variable.
240

241
    Parameters:
242
        experiment ('HTBAMExperiment'): the experiment object.
243
        analysis_name (str): the name of the analysis to be plotted.
244
        variable (str): the name of the variable to be plotted.
245

246
    Returns:
247
        None
248
    '''
249
    #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)
250
    
NEW
251
    analysis_data = experiment.get_run(analysis_name)     # Analysis data (to show slopes/intercepts)
×
252
    
NEW
253
    plotting_var = variable
×
NEW
254
    plotting_var_unit = analysis_data.dep_var_units[analysis_data.dep_var_type.index(plotting_var)]
×
255

256
    # Verify we have the variable:
NEW
257
    if plotting_var not in analysis_data.dep_var_type:
×
NEW
258
        raise ValueError(f"'{plotting_var}' not found in analysis data. Available variables: {analysis_data.dep_vars_types}")
×
259
    else:
NEW
260
        plotting_var_index = analysis_data.dep_var_type.index(plotting_var)
×
261

NEW
262
    concentration = analysis_data.dep_var[..., plotting_var_index] * plotting_var_unit # (n_chambers, n_conc, 1)
×
263
    
264
    #chamber_names: We'll provide the name of the sample in each chamber as well, in the same way:
265
    #chamber_names_dict = experiment._db_conn.get_chamber_name_dict()
NEW
266
    chamber_names = analysis_data.indep_vars.chamber_IDs # (n_chambers,)
×
NEW
267
    sample_names =  analysis_data.indep_vars.sample_IDs # (n_chambers,)
×
268

269
    # Create dictionary mapping chamber_id -> sample_name:
NEW
270
    sample_names_dict = {}
×
NEW
271
    for i, chamber_id in enumerate(chamber_names):
×
NEW
272
        sample_names_dict[chamber_id] = sample_names[i]
×
273

274
    # Create dictionary mapping chamber_id -> concentration:
NEW
275
    concentration_dict = {}
×
NEW
276
    for i, chamber_id in enumerate(chamber_names):
×
NEW
277
        concentration_dict[chamber_id] = concentration[i]
×
278
        
279

280
    #plotting function: We'll generate a subplot for each chamber, showing the histogram across replicates with our chosen chamber in red.
NEW
281
    def plot_chamber_variable(chamber_id, ax):
×
282
        #parameters:
283
        # get the sample_ID for this chamber:
NEW
284
        sample_id = sample_names_dict[chamber_id]
×
285
        # Get all replicates with this sample_id:
NEW
286
        repl_indices = [i for i, s in enumerate(sample_names) if s == sample_id]
×
287
        # Get the concentration values for these replicates:
NEW
288
        repl_concentrations = concentration[repl_indices]
×
289

290
        #x_data = concentration_dict[chamber_id]
NEW
291
        ax.hist(repl_concentrations, bins=10)
×
292
        # add the current datapoint as a red bar:
NEW
293
        ax.axvline(concentration_dict[chamber_id], color='red', linestyle='dashed', linewidth=2)
×
NEW
294
        ax.set_title(f'{chamber_id}: {sample_names_dict[chamber_id]}')
×
NEW
295
        ax.set_xlabel(f'{plotting_var}')
×
NEW
296
        ax.set_ylabel('Count')
×
NEW
297
        return ax
×
298

NEW
299
    plot_chip(concentration_dict, sample_names_dict, title=f'Analysis: {plotting_var}', graphing_function=plot_chamber_variable)
×
300

NEW
301
def plot_standard_curve_chip(experiment: 'HTBAMExperiment', analysis_name: str, experiment_name: str):
×
302
    '''
303
    Plot a full chip with raw data and std curve slopes.
304

305
    Parameters:
306
        experiment ('HTBAMExperiment'): the experiment object.
307
        analysis_name (str): the name of the analysis to be plotted.
308
        experiment_name (str): the name of the raw experiment data to be plotted.
309

310
    Returns:
311
        None
312
    '''
313
    #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)
314
    
NEW
315
    experiment_data = experiment.get_run(experiment_name) # Raw data from experiment (to show datapoints)
×
NEW
316
    analysis_data = experiment.get_run(analysis_name)     # Analysis data (to show slopes/intercepts)
×
317
    
NEW
318
    slope_idx = analysis_data.dep_var_type.index('slope')          # index of slope in dep_vars
×
NEW
319
    intercept_idx = analysis_data.dep_var_type.index('intercept')  # index of intercept in dep_vars
×
NEW
320
    r_squared_idx = analysis_data.dep_var_type.index('r_squared')  # index of r_squared in dep_vars
×
321
    
NEW
322
    slope_unit = analysis_data.dep_var_units[slope_idx]
×
NEW
323
    intercept_unit = analysis_data.dep_var_units[intercept_idx]
×
NEW
324
    r_squared_unit = analysis_data.dep_var_units[r_squared_idx]
×
325
    
326
    # Extract slopes and intercepts from analysis data
NEW
327
    slopes_to_plot = analysis_data.dep_var[..., slope_idx] * slope_unit          # (n_chambers,)
×
NEW
328
    intercepts_to_plot = analysis_data.dep_var[..., intercept_idx] * intercept_unit  # (n_chambers,)
×
NEW
329
    r_squared = analysis_data.dep_var[..., r_squared_idx] * r_squared_unit  # (n_chambers,)
×
330

NEW
331
    luminance_unit = experiment_data.dep_var_units[experiment_data.dep_var_type.index('luminance')]
×
332
    
333
    # Extract luminance and concentration from experiment data
NEW
334
    luminance_idx = experiment_data.dep_var_type.index('luminance')  # index of luminance in dep_vars
×
NEW
335
    luminance = experiment_data.dep_var[..., luminance_idx] * luminance_unit  # (n_chambers, n_timepoints, n_conc)
×
NEW
336
    concentration = experiment_data.indep_vars.concentration # (n_conc,)
×
337
    
338
    #chamber_names: We'll provide the name of the sample in each chamber as well, in the same way:
NEW
339
    chamber_names = experiment_data.indep_vars.chamber_IDs # (n_chambers,)
×
NEW
340
    sample_names =  experiment_data.indep_vars.sample_IDs # (n_chambers,)
×
341

342
    # Create dictionary mapping chamber_id -> sample_name:
NEW
343
    sample_names_dict = {}
×
NEW
344
    for i, chamber_id in enumerate(chamber_names):
×
NEW
345
        sample_names_dict[chamber_id] = sample_names[i]
×
346

347
    # Create dictionary mapping chamber_id -> slopes:
NEW
348
    slopes_dict = {}
×
NEW
349
    for i, chamber_id in enumerate(chamber_names):
×
NEW
350
        slopes_dict[chamber_id] = slopes_to_plot[i]
×
351

352
    #plotting function: We'll generate a subplot for each chamber, showing the raw data and the linear regression line.
353
    # 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.
NEW
354
    def plot_chamber_slopes(chamber_id, ax):
×
355
        #parameters:
356

NEW
357
        x_data = concentration
×
NEW
358
        y_data = luminance[:, -1, chamber_names == chamber_id] # using last timepoint
×
359
        
NEW
360
        m = slopes_to_plot[chamber_names == chamber_id]
×
NEW
361
        b = intercepts_to_plot[chamber_names == chamber_id]
×
362
        
363
        #make a simple matplotlib plot
NEW
364
        ax.scatter(x_data, y_data)
×
NEW
365
        if not (np.isnan(m) or np.isnan(b)):
×
NEW
366
            ax.plot(x_data, m*x_data + b)
×
NEW
367
            ax.set_title(f'{chamber_id}: {sample_names_dict[chamber_id]}')
×
NEW
368
            ax.set_xlabel(f'Concentration ({concentration.units:~})')
×
NEW
369
            ax.set_ylabel(f'Luminance ({luminance.units:~})')
×
NEW
370
            ax.legend([f'Current Chamber Slope: {slopes_dict[chamber_id].magnitude:.2f} {slope_unit:~}'])
×
NEW
371
        return ax
×
372
    
NEW
373
    plot_chip(slopes_dict, sample_names_dict, graphing_function=plot_chamber_slopes, title='Standard Curve: Slope')
×
374

NEW
375
def plot_initial_rates_chip(experiment: 'HTBAMExperiment', analysis_name: str, experiment_name: str, skip_start_timepoint: bool = True,
×
376
                            plot_xmax: float = None, plot_ymax: float = None,
377
                            plot_xmin: float = None, plot_ymin: float = None):
378
    '''
379
    Plot a full chip with raw data and fit initial rates.
380

381
    Parameters:
382
        experiment ('HTBAMExperiment'): the experiment object.
383
        analysis_name (str): the name of the analysis to be plotted.
384
        experiment_name (str): the name of the experiment to be plotted.
385
        skip_start_timepoint (bool): whether to skip the first timepoint in the analysis (Sometimes are unusually low). Default is True.
386

387
    Returns:
388
        None
389
    '''
390
    #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)
391
    
NEW
392
    experiment_data = experiment.get_run(experiment_name) # Raw data from experiment (to show datapoints)
×
NEW
393
    analysis_data = experiment.get_run(analysis_name)     # Analysis data (to show slopes/intercepts)
×
394
    
395
    # Extract slopes and intercepts from analysis data
NEW
396
    slopes_idx = analysis_data.dep_var_type.index('slope')          # index of slope in dep_vars
×
NEW
397
    intercepts_idx = analysis_data.dep_var_type.index('intercept')  # index of intercept in dep_vars
×
NEW
398
    r_squared_idx = analysis_data.dep_var_type.index('r_squared')  # index of r_squared in dep_vars
×
399
    
NEW
400
    slope_unit = analysis_data.dep_var_units[slopes_idx]
×
NEW
401
    intercept_unit = analysis_data.dep_var_units[intercepts_idx]
×
NEW
402
    r_squared_unit = analysis_data.dep_var_units[r_squared_idx]
×
403

404
    # Extract slopes and intercepts from analysis data
NEW
405
    slopes_to_plot = analysis_data.dep_var[..., slopes_idx] * slope_unit          # (n_chambers, n_conc)
×
NEW
406
    intercepts_to_plot = analysis_data.dep_var[..., intercepts_idx] * intercept_unit  # (n_chambers, n_conc)
×
NEW
407
    r_squared = analysis_data.dep_var[..., r_squared_idx] * r_squared_unit          # (n_chambers, n_conc)
×
408

409
    # Extract product_concentration (Y) from experiment data
NEW
410
    product_conc_idx = experiment_data.dep_var_type.index('concentration')  # index of luminance in dep_vars
×
NEW
411
    product_conc_unit = experiment_data.dep_var_units[product_conc_idx]
×
NEW
412
    product_conc = experiment_data.dep_var[..., product_conc_idx] * product_conc_unit  # (n_chambers, n_timepoints, n_conc)
×
NEW
413
    substrate_conc = experiment_data.indep_vars.concentration # (n_conc,)
×
NEW
414
    time_data = experiment_data.indep_vars.time # (n_conc, n_timepoints)
×
415

416
    # If skip_start_timepoint is True, we'll skip the first timepoint in the analysis
NEW
417
    if skip_start_timepoint:
×
NEW
418
        product_conc = product_conc[:, 1:, :] # (n_chambers, n_timepoints-1, n_conc)
×
NEW
419
        time_data = time_data[:, 1:] # (n_conc, n_timepoints-1)
×
420
        #slopes_to_plot = slopes_to_plot[:, 1:]
421
    
422
    #chamber_names: We'll provide the name of the sample in each chamber as well, in the same way:
423
    #chamber_names_dict = experiment._db_conn.get_chamber_name_dict()
NEW
424
    chamber_names = experiment_data.indep_vars.chamber_IDs # (n_chambers,)
×
NEW
425
    sample_names =  experiment_data.indep_vars.sample_IDs # (n_chambers,)
×
426
    
427
    # Create dictionary mapping chamber_id -> sample_name:
NEW
428
    sample_names_dict = {}
×
NEW
429
    for i, chamber_id in enumerate(chamber_names):
×
NEW
430
        sample_names_dict[chamber_id] = sample_names[i]
×
431

432
    # Create dictionary mapping chamber_id -> mean slopes:
NEW
433
    slopes_dict = {}
×
NEW
434
    for i, chamber_id in enumerate(chamber_names):
×
NEW
435
        slopes_dict[chamber_id] = np.nanmean(slopes_to_plot[:, i])
×
436

437
    #plotting function: We'll generate a subplot for each chamber, showing the raw data and the linear regression line.
438
    # 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.
439

NEW
440
    def plot_chamber_initial_rates(chamber_id, ax):#, time_to_plot=time_to_plot):
×
441
        #N.B. Every so often, slope and line colors don't match up. Not sure why.
442
        
443
        #convert from 'x,y' to integer index in the array:
444
        #data_index = list(experiment._run_data[run_name]["chamber_idxs"]).index(chamber_id)
NEW
445
        x_data = time_data # same for all chambers              (n_timepoints, n_conc)
×
NEW
446
        y_data = product_conc[:, :, chamber_names == chamber_id]  #(n_timepoints, n_conc)
×
447
    
NEW
448
        m = slopes_to_plot[:, chamber_names == chamber_id]
×
NEW
449
        b = intercepts_to_plot[:, chamber_names == chamber_id]
×
450
        
NEW
451
        colors = sns.color_palette('husl', n_colors=y_data.shape[0])
×
452

NEW
453
        for i in range(y_data.shape[0]): #over each substrate concentration:
×
454

NEW
455
            ax.scatter(x_data[i], y_data[i,:].flatten(), color=colors[i], alpha=0.3) # raw data
×
NEW
456
            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
×
457

458
        # Set axis limits if provided
NEW
459
        if plot_xmax is not None:
×
NEW
460
            ax.set_xlim(right=plot_xmax)
×
NEW
461
        if plot_ymax is not None:
×
NEW
462
            ax.set_ylim(top=plot_ymax)
×
NEW
463
        if plot_xmin is not None:
×
NEW
464
            ax.set_xlim(left=plot_xmin)
×
NEW
465
        if plot_ymin is not None:
×
NEW
466
            ax.set_ylim(bottom=plot_ymin)
×
467
        
NEW
468
        ax.set_xlabel(f'Time ({time_data.units:~})')
×
NEW
469
        ax.set_ylabel(f'Product Concentration ({product_conc.units:~})')
×
470

NEW
471
        ax.legend()
×
472

NEW
473
        return ax
×
474
    
NEW
475
    plot_chip(slopes_dict, sample_names_dict, graphing_function=plot_chamber_initial_rates, title='Kinetics: Initial Rates')
×
476

NEW
477
def plot_initial_rates_vs_concentration_chip(experiment: 'HTBAMExperiment',
×
478
                                             analysis_name: str,
479
                                             model_fit_name: str = None,
480
                                             model_pred_data_name: str = None,
481
                                             x_log: bool = False,
482
                                             y_log: bool = False):
483
    """
484
    Plot initial rates vs substrate concentration for each chamber.
485
    Optionally overlay fitted curve from `model_pred_data_name` and
486
    annotate fit parameters from `model_fit_name`.
487
    """
NEW
488
    analysis: Data3D = experiment.get_run(analysis_name)
×
NEW
489
    si = analysis.dep_var_type.index("slope")
×
NEW
490
    slope_unit = analysis.dep_var_units[si]
×
NEW
491
    slopes = analysis.dep_var[..., si] * slope_unit  # (n_conc, n_chambers)
×
NEW
492
    conc   = analysis.indep_vars.concentration       # (n_conc,)
×
493

NEW
494
    if model_pred_data_name:
×
NEW
495
        mf_pred: Data3D = experiment.get_run(model_pred_data_name)
×
NEW
496
        yi = mf_pred.dep_var_type.index("y_pred")
×
NEW
497
        y_pred_unit = mf_pred.dep_var_units[yi]
×
NEW
498
        preds = mf_pred.dep_var[..., yi] * y_pred_unit          # (n_conc, n_chambers)
×
499

NEW
500
    if model_fit_name:
×
NEW
501
        mf_fit: Data2D = experiment.get_run(model_fit_name)
×
NEW
502
        fit_types = mf_fit.dep_var_type           # e.g. ["v_max","K_m","r_squared"]
×
NEW
503
        fit_vals = mf_fit.dep_var                # shape (n_chamb, len(fit_types))
×
NEW
504
        fit_units = mf_fit.dep_var_units
×
505

NEW
506
    chambers = analysis.indep_vars.chamber_IDs        # (n_chambers,)
×
NEW
507
    samples  = analysis.indep_vars.sample_IDs         # (n_chambers,)
×
NEW
508
    sample_names = {cid: samples[i] for i, cid in enumerate(chambers)}
×
NEW
509
    mean_rates   = {cid: np.nanmean(slopes[:, i]) for i, cid in enumerate(chambers)}
×
510

NEW
511
    def plot_rates_vs_conc(cid, ax):
×
NEW
512
        idx = (chambers == cid)
×
NEW
513
        x   = conc
×
NEW
514
        y   = slopes[:, idx].flatten()
×
NEW
515
        ax.scatter(x, y, alpha=0.7)
×
516

NEW
517
        if model_pred_data_name:
×
NEW
518
            y_p = preds[:, idx].flatten()
×
NEW
519
            ax.plot(x, y_p, color="red", label="model")
×
520

NEW
521
        if model_fit_name:
×
522
            # extract this chamber's fit row
NEW
523
            chamb_idx = np.where(chambers == cid)[0][0]
×
NEW
524
            vals = fit_vals[chamb_idx]
×
NEW
525
            txt = "".join(f"{nm}={v:.2f} {u:~}\n" for nm,v,u in zip(fit_types, vals, fit_units))
×
NEW
526
            ax.text(0.05, 0.95, txt, transform=ax.transAxes,
×
527
                    va="top", fontsize=8, bbox=dict(boxstyle="round", fc="white", alpha=0.7))
528

NEW
529
        if model_pred_data_name or model_fit_name:
×
NEW
530
            ax.legend()
×
NEW
531
        ax.set_title(f"{cid}: {sample_names[cid]}")
×
NEW
532
        ax.set_xlabel(f"Concentration ({conc.units:~})")
×
NEW
533
        ax.set_ylabel(f"Initial Rate ({slopes.units:~})")
×
NEW
534
        if x_log: ax.set_xscale("log")
×
NEW
535
        if y_log: ax.set_yscale("log")
×
NEW
536
        return ax
×
537

NEW
538
    plot_chip(mean_rates, sample_names,
×
539
              graphing_function=plot_rates_vs_conc,
540
              title="Initial Rates vs Concentration")
541

NEW
542
def plot_MM_chip(experiment: 'HTBAMExperiment',
×
543
                analysis_name: str,
544
                model_fit_name: str,
545
                model_pred_data_name: str = None,
546
                x_log: bool = False,
547
                y_log: bool = False):
548
    """
549
    Plot MM values, with inset initial rates vs substrate concentration for each chamber.
550
    Optionally overlay fitted curve from `model_pred_data_name` and
551
    annotate fit parameters from `model_fit_name`.
552
    """
NEW
553
    analysis: Data3D = experiment.get_run(analysis_name)
×
NEW
554
    si = analysis.dep_var_type.index("slope")
×
NEW
555
    slope_unit = analysis.dep_var_units[si]
×
NEW
556
    slopes = analysis.dep_var[..., si] * slope_unit  # (n_conc, n_chambers)
×
NEW
557
    conc   = analysis.indep_vars.concentration       # (n_conc,)
×
558

NEW
559
    mf_fit: Data2D = experiment.get_run(model_fit_name)
×
NEW
560
    fit_types = mf_fit.dep_var_type           # e.g. ["v_max","K_m","r_squared"]
×
NEW
561
    fit_vals = mf_fit.dep_var                # shape (n_chamb, len(fit_types))
×
NEW
562
    fit_units = mf_fit.dep_var_units
×
563

NEW
564
    mm_idx = mf_fit.dep_var_type.index("v_max")
×
NEW
565
    mms = mf_fit.dep_var[..., mm_idx] * fit_units[mm_idx]   # (n_chambers,)
×
566

NEW
567
    if model_pred_data_name:
×
NEW
568
        mf_pred: Data3D = experiment.get_run(model_pred_data_name)
×
NEW
569
        yi = mf_pred.dep_var_type.index("y_pred")
×
NEW
570
        y_pred_unit = mf_pred.dep_var_units[yi]
×
NEW
571
        preds = mf_pred.dep_var[..., yi] * y_pred_unit          # (n_conc, n_chambers)
×
572

NEW
573
    chambers = analysis.indep_vars.chamber_IDs        # (n_chambers,)
×
NEW
574
    samples  = analysis.indep_vars.sample_IDs         # (n_chambers,)
×
NEW
575
    sample_names = {cid: samples[i] for i, cid in enumerate(chambers)}
×
576
    #mean_rates   = {cid: np.nanmean(slopes[:, i]) for i, cid in enumerate(chambers)}
NEW
577
    mms_to_plot = {cid: mms[i] for i, cid in enumerate(chambers)}
×
578

NEW
579
    def plot_rates_vs_conc(cid, ax):
×
NEW
580
        idx = (chambers == cid)
×
NEW
581
        x   = conc
×
NEW
582
        y   = slopes[:, idx].flatten()
×
NEW
583
        ax.scatter(x, y, alpha=0.7, label="current well")
×
584

NEW
585
        if model_pred_data_name:
×
586
            # show envelope of model fits for all wells with this sample
NEW
587
            sample = sample_names[cid]
×
NEW
588
            same_idxs = [i for i, s in enumerate(samples) if s == sample]
×
NEW
589
            y_all = preds[:, same_idxs]               # (n_conc, n_same)
×
590
            
591
            # Show the 95% confidence interval:
NEW
592
            y_min = np.nanpercentile(y_all, 2.5, axis=1)
×
NEW
593
            y_max = np.nanpercentile(y_all, 97.5, axis=1)
×
594
            
595
            # then overplot this chamber’s model fit
NEW
596
            y_p = preds[:, idx].flatten()
×
NEW
597
            ax.plot(x, y_p, color="red", label="current well fit")
×
NEW
598
            ax.fill_between(x, y_min, y_max, color="gray", alpha=0.3, label='95% CI')
×
599

NEW
600
        if model_fit_name:
×
601
            # extract this chamber's fit row
NEW
602
            chamb_idx = np.where(chambers == cid)[0][0]
×
NEW
603
            vals = fit_vals[chamb_idx]
×
NEW
604
            txt = "".join(f"{nm}={v:.2f} {u:~}\n" for nm,v,u in zip(fit_types, vals, fit_units))
×
NEW
605
            ax.text(0.05, 0.95, txt, transform=ax.transAxes,
×
606
                    va="top", fontsize=8, bbox=dict(boxstyle="round", fc="white", alpha=0.7))
607
        
NEW
608
        if model_pred_data_name or model_fit_name:
×
NEW
609
            ax.legend()
×
NEW
610
        ax.set_title(f"{cid}: {sample_names[cid]}")
×
NEW
611
        ax.set_xlabel(f"Concentration ({conc.units:~})")
×
NEW
612
        ax.set_ylabel(f"Initial Rate ({slopes.units:~})")
×
NEW
613
        if x_log: ax.set_xscale("log")
×
NEW
614
        if y_log: ax.set_yscale("log")
×
NEW
615
        return ax
×
616

NEW
617
    plot_chip(mms_to_plot, sample_names,
×
618
              graphing_function=plot_rates_vs_conc,
619
              title="Initial Rates vs Concentration")   
620

NEW
621
def plot_MM_div_E_chip(experiment: 'HTBAMExperiment',
×
622
                analysis_name: str,
623
                model_fit_name: str,
624
                dep_var_name='slope',
625
                x_log: bool = False,
626
                y_log: bool = False):
627
    """
628
    Plot MM values, with inset initial rates vs substrate concentration for each chamber.
629
    Optionally overlay fitted curve from `model_pred_data_name` and
630
    annotate fit parameters from `model_fit_name`.
631
    """
NEW
632
    analysis: Data3D = experiment.get_run(analysis_name)
×
NEW
633
    si = analysis.dep_var_type.index(dep_var_name)
×
NEW
634
    slope_unit = analysis.dep_var_units[si]
×
NEW
635
    slopes = analysis.dep_var[..., si] * slope_unit  # (n_conc, n_chambers)
×
NEW
636
    conc   = analysis.indep_vars.concentration       # (n_conc,)
×
637

NEW
638
    mf_fit: Data2D = experiment.get_run(model_fit_name)
×
NEW
639
    fit_types = mf_fit.dep_var_type           # e.g. ["v_max","K_m","r_squared", "kcat"]
×
NEW
640
    fit_vals = mf_fit.dep_var                # shape (n_chamb, len(fit_types))
×
NEW
641
    fit_units = mf_fit.dep_var_units
×
642

NEW
643
    kcat_idx = mf_fit.dep_var_type.index("kcat")
×
NEW
644
    all_kcats = mf_fit.dep_var[..., kcat_idx] * fit_units[kcat_idx]   # (n_chambers,)
×
NEW
645
    kM_idx = mf_fit.dep_var_type.index("K_m")
×
NEW
646
    all_kMs = mf_fit.dep_var[..., kM_idx] * fit_units[kM_idx]   # (n_chambers,)
×
647

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

NEW
650
    chambers = analysis.indep_vars.chamber_IDs        # (n_chambers,)
×
NEW
651
    samples  = analysis.indep_vars.sample_IDs         # (n_chambers,)
×
NEW
652
    sample_names = {cid: samples[i] for i, cid in enumerate(chambers)}
×
653
    #mean_rates   = {cid: np.nanmean(slopes[:, i]) for i, cid in enumerate(chambers)}
NEW
654
    kcats_to_plot = {cid: all_kcats[i] for i, cid in enumerate(chambers)}
×
NEW
655
    kMs_to_plot = {cid: all_kMs[i] for i, cid in enumerate(chambers)}
×
656

NEW
657
    def plot_rates_vs_conc(cid, ax):
×
NEW
658
        idx = (chambers == cid)
×
NEW
659
        x   = conc
×
NEW
660
        y   = slopes[:, idx].flatten()
×
NEW
661
        ax.scatter(x, y, alpha=0.7, label="current well")
×
662

663

NEW
664
        sample = sample_names[cid]
×
NEW
665
        same_idxs = [i for i, s in enumerate(samples) if s == sample]
×
666
        
NEW
667
        sample_kcats = all_kcats[same_idxs]
×
NEW
668
        sample_kMs = all_kMs[same_idxs]
×
669

NEW
670
        current_kcat = kcats_to_plot[cid]
×
NEW
671
        current_km = kMs_to_plot[cid]
×
672

NEW
673
        mean_kcat = np.nanmean(sample_kcats)
×
NEW
674
        mean_km = np.nanmean(sample_kMs)
×
675

676
        # stdev of kcat?
NEW
677
        kcat_stdev = np.nanstd(sample_kcats)
×
NEW
678
        km_stdev = np.nanstd(sample_kMs)
×
679
        
680
        # push kcat up and down one stdev
NEW
681
        kcat_up = mean_kcat + kcat_stdev
×
NEW
682
        kcat_down = mean_kcat - kcat_stdev
×
683

684
        # calculate new slopes
NEW
685
        from htbam_analysis.analysis.fit import mm_model
×
686

NEW
687
        pred_y_mean = mm_model(conc, mean_kcat, mean_km)
×
NEW
688
        pred_y_up = mm_model(conc, kcat_up, mean_km)
×
NEW
689
        pred_y_down = mm_model(conc, kcat_down, mean_km)
×
NEW
690
        pred_y_current = mm_model(conc, current_kcat, current_km)
×
691

NEW
692
        ax.plot(x, pred_y_current, color="blue", label="current well fit")
×
693

NEW
694
        ax.plot(x, pred_y_mean, color="gray", label="mean well fit")
×
NEW
695
        ax.fill_between(x, pred_y_down, pred_y_up, color="gray", alpha=0.3, label='$k_{cat}$ ± 1 stdev')
×
696

NEW
697
        ax.text(0.05, 0.95, 
×
698
                f"$\\overline{{k_{{cat}}}}$ = {mean_kcat.magnitude:.2f} ± {kcat_stdev:.2f~}\n"
699
                f"$\\overline{{K_{{M}}}}$ = {mean_km.magnitude:.2f} ± {km_stdev:.2f~}", 
700
                transform=ax.transAxes,
701
                va="top", fontsize=8, zorder=10,
702
                bbox=dict(boxstyle="round", fc="white", alpha=0.7))
703

704
        # if model_fit_name:
705
        #     # extract this chamber's fit row
706
        #     chamb_idx = np.where(chambers == cid)[0][0]
707
        #     vals = fit_vals[chamb_idx]
708
        #     txt = "".join(f"{nm}={v:.2f} {u:~}\n" for nm,v,u in zip(fit_types, vals, fit_units))
709
        #     ax.text(0.05, 0.95, txt, transform=ax.transAxes,
710
        #             va="top", fontsize=8, bbox=dict(boxstyle="round", fc="white", alpha=0.7))
711
        
712
        #if model_pred_data_name or model_fit_name:
NEW
713
        ax.legend()
×
NEW
714
        ax.set_title(f"{cid}: {sample_names[cid]}")
×
NEW
715
        ax.set_xlabel(f"$[S]$ ({conc.units:~})")
×
NEW
716
        ax.set_ylabel(f"$V_0/[E]$ ({slopes.units:~})")
×
NEW
717
        if x_log: ax.set_xscale("log")
×
NEW
718
        if y_log: ax.set_yscale("log")
×
NEW
719
        return ax
×
720

NEW
721
    plot_chip(kcats_to_plot, sample_names,
×
722
              graphing_function=plot_rates_vs_conc,
723
              title="V_0/[E] vs [S]")       
724

NEW
725
def plot_ic50_chip(experiment: 'HTBAMExperiment',
×
726
                analysis_name: str,
727
                model_fit_name: str,
728
                model_pred_data_name: str = None,
729
                x_log: bool = False,
730
                y_log: bool = False):
731
    """
732
    Plot ic50 values, with inset initial rates vs substrate concentration for each chamber.
733
    Optionally overlay fitted curve from `model_pred_data_name` and
734
    annotate fit parameters from `model_fit_name`.
735
    """
NEW
736
    analysis: Data3D = experiment.get_run(analysis_name)
×
NEW
737
    si = analysis.dep_var_type.index("slope")
×
NEW
738
    slope_unit = analysis.dep_var_units[si]
×
NEW
739
    slopes = analysis.dep_var[..., si] * slope_unit  # (n_conc, n_chambers)
×
NEW
740
    conc   = analysis.indep_vars.concentration       # (n_conc,)
×
741

NEW
742
    mf_fit: Data2D = experiment.get_run(model_fit_name)
×
NEW
743
    fit_types = mf_fit.dep_var_type           # e.g. ["v_max","K_m","r_squared"]
×
NEW
744
    fit_vals = mf_fit.dep_var                # shape (n_chamb, len(fit_types))
×
NEW
745
    fit_units = mf_fit.dep_var_units
×
746

NEW
747
    ic50s_idx = mf_fit.dep_var_type.index("ic50")
×
NEW
748
    ic50s = mf_fit.dep_var[..., ic50s_idx] * fit_units[ic50s_idx]    # (n_chambers,)
×
749

NEW
750
    if model_pred_data_name:
×
NEW
751
        mf_pred: Data3D = experiment.get_run(model_pred_data_name)
×
NEW
752
        yi = mf_pred.dep_var_type.index("y_pred")
×
NEW
753
        y_pred_unit = mf_pred.dep_var_units[yi]
×
NEW
754
        preds = mf_pred.dep_var[..., yi] * y_pred_unit          # (n_conc, n_chambers)
×
755

NEW
756
    chambers = analysis.indep_vars.chamber_IDs        # (n_chambers,)
×
NEW
757
    samples  = analysis.indep_vars.sample_IDs         # (n_chambers,)
×
NEW
758
    sample_names = {cid: samples[i] for i, cid in enumerate(chambers)}
×
759
    #mean_rates   = {cid: np.nanmean(slopes[:, i]) for i, cid in enumerate(chambers)}
NEW
760
    ic50s_to_plot = {cid: ic50s[i] for i, cid in enumerate(chambers)}
×
761

NEW
762
    def plot_rates_vs_conc(cid, ax):
×
NEW
763
        idx = (chambers == cid)
×
NEW
764
        x   = conc
×
NEW
765
        y   = slopes[:, idx].flatten()
×
NEW
766
        ax.scatter(x, y, alpha=0.7, label="current well")
×
767

NEW
768
        if model_pred_data_name:
×
769
            # show envelope of model fits for all wells with this sample
NEW
770
            sample = sample_names[cid]
×
NEW
771
            same_idxs = [i for i, s in enumerate(samples) if s == sample]
×
NEW
772
            y_all = preds[:, same_idxs]               # (n_conc, n_same)
×
773

774
            # Show the 95% confidence interval:
NEW
775
            y_min = np.nanpercentile(y_all, 2.5, axis=1)
×
NEW
776
            y_max = np.nanpercentile(y_all, 97.5, axis=1)
×
777
            
778
            # then overplot this chamber’s model fit
NEW
779
            y_p = preds[:, idx].flatten()
×
NEW
780
            ax.plot(x, y_p, color="red", label="current well fit")
×
NEW
781
            ax.fill_between(x, y_min, y_max, color="gray", alpha=0.3, label='95% CI')
×
782

NEW
783
        if model_fit_name:
×
784
            # extract this chamber's fit row
NEW
785
            chamb_idx = np.where(chambers == cid)[0][0]
×
NEW
786
            vals = fit_vals[chamb_idx]
×
NEW
787
            txt = "".join(f"{nm}={v:.2f} {u:~}\n" for nm,v,u in zip(fit_types, vals, fit_units))
×
NEW
788
            ax.text(0.05, 0.95, txt, transform=ax.transAxes,
×
789
                    va="top", fontsize=8, bbox=dict(boxstyle="round", fc="white", alpha=0.7))
790
        
NEW
791
        if model_pred_data_name or model_fit_name:
×
NEW
792
            ax.legend()
×
NEW
793
        ax.set_title(f"{cid}: {sample_names[cid]}")
×
NEW
794
        ax.set_xlabel(f"Concentration ({conc.units:~})")
×
NEW
795
        ax.set_ylabel(f"Initial Rate ({slopes.units:~})")
×
NEW
796
        if x_log: ax.set_xscale("log")
×
NEW
797
        if y_log: ax.set_yscale("log")
×
NEW
798
        return ax
×
799

NEW
800
    plot_chip(ic50s_to_plot, sample_names,
×
801
              graphing_function=plot_rates_vs_conc,
802
              title="Initial Rates vs Concentration")
803

NEW
804
def plot_enzyme_concentration_chip(experiment: 'HTBAMExperiment', analysis_name: str, skip_start_timepoint: bool = True):
×
805
    '''
806
    Plot a full chip with raw data and fit initial rates.
807

808
    Parameters:
809
        experiment ('HTBAMExperiment'): the experiment object.
810
        analysis_name (str): the name of the analysis to be plotted.
811
        skip_start_timepoint (bool): whether to skip the first timepoint in the analysis (Sometimes are unusually low). Default is True.
812

813
    Returns:
814
        None
815
    '''
816
    #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)
NEW
817
    analysis_data = experiment.get_run(analysis_name)     # Analysis data (to show slopes/intercepts)
×
818
    
NEW
819
    plotting_var = 'concentration'
×
820

821
    # Verify we have the variable:
NEW
822
    if plotting_var not in analysis_data.dep_var_type:
×
NEW
823
        raise ValueError(f"'{plotting_var}' not found in analysis data. Available variables: {analysis_data.dep_vars_types}")
×
824
    else:
NEW
825
        plotting_var_index = analysis_data.dep_var_type.index(plotting_var)
×
826

NEW
827
    conc_units = analysis_data.dep_var_units[plotting_var_index]
×
NEW
828
    concentration = analysis_data.dep_var[..., plotting_var_index] * conc_units # (n_chambers, n_conc, 1)
×
829
    
830
    #chamber_names: We'll provide the name of the sample in each chamber as well, in the same way:
831
    #chamber_names_dict = experiment._db_conn.get_chamber_name_dict()
NEW
832
    chamber_names = analysis_data.indep_vars.chamber_IDs # (n_chambers,)
×
NEW
833
    sample_names =  analysis_data.indep_vars.sample_IDs # (n_chambers,)
×
834

835
    # Create dictionary mapping chamber_id -> sample_name:
NEW
836
    sample_names_dict = {}
×
NEW
837
    for i, chamber_id in enumerate(chamber_names):
×
NEW
838
        sample_names_dict[chamber_id] = sample_names[i]
×
839

840
    # Create dictionary mapping chamber_id -> mean slopes:
NEW
841
    conc_dict = {}
×
NEW
842
    for i, chamber_id in enumerate(chamber_names):
×
NEW
843
        conc_dict[chamber_id] = np.nanmean(concentration[i])
×
844

NEW
845
    def graphing_function(chamber_id, ax):
×
846
        # plot histogram of the concentration from all chambers with the same sample name as the chamber_id
NEW
847
        sample_name = sample_names_dict[chamber_id]
×
NEW
848
        conc = concentration[sample_names == sample_name]
×
NEW
849
        ax.hist(conc, bins=10)
×
850
        # Make the bin for the current chamber red:
851
        # What bin has the max count?
NEW
852
        bin_max_count = np.max(np.histogram(conc.magnitude, bins=10)[0])
×
NEW
853
        ax.vlines(conc_dict[chamber_id].magnitude, 0, bin_max_count, colors='red', linestyles='--')
×
NEW
854
        ax.set_title(f'Concentration of {sample_name}')
×
NEW
855
        ax.set_xlabel(f'Concentration {conc_units:~}')
×
NEW
856
        ax.set_ylabel('Count')
×
857
        # print legend current chamber with concentration:
NEW
858
        ax.legend([f'Current Chamber: {conc_dict[chamber_id].magnitude:.2f} {conc_units:~}'])
×
NEW
859
        return ax
×
860
    
NEW
861
    plot_chip(conc_dict, sample_names_dict, 
×
862
            graphing_function=graphing_function,
863
            title=f'Enzyme Concentration')
864

NEW
865
def plot_mask_chip(experiment: 'HTBAMExperiment', mask_name: str):
×
866
    '''
867
    Plot a full chip with raw data and fit initial rates.
868

869
    Parameters:
870
        experiment ('HTBAMExperiment'): the experiment object.
871
        mask_name (str): the name of the mask to be plotted. (Data3D or Data2D)
872

873
    Returns:
874
        None
875
    '''
876
    #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)
NEW
877
    mask_data = experiment.get_run(mask_name)     # Analysis data (to show slopes/intercepts)
×
878
    
NEW
879
    dtype = type(mask_data)
×
NEW
880
    assert dtype in [Data3D, Data2D], "mask_data must be of type Data3D or Data2D."
×
881

NEW
882
    mask_idx = mask_data.dep_var_type.index('mask')
×
883

884
    # If we're using a data2D
NEW
885
    mask = mask_data.dep_var[..., mask_idx] # (n_conc, n_chambers,)
×
886

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

890
    #passed_conc = np.sum(mask, axis=0)
891
    #print(mask.shape)
892

893
    #chamber_names: We'll provide the name of the sample in each chamber as well, in the same way:
NEW
894
    chamber_names = mask_data.indep_vars.chamber_IDs # (n_chambers,)
×
NEW
895
    sample_names =  mask_data.indep_vars.sample_IDs # (n_chambers,)
×
896

897
    # Create dictionary mapping chamber_id -> sample_name:
NEW
898
    sample_names_dict = {}
×
NEW
899
    for i, chamber_id in enumerate(chamber_names):
×
NEW
900
        sample_names_dict[chamber_id] = sample_names[i]
×
901

902
    # Create dictionary mapping chamber_id -> mean slopes:
NEW
903
    mask_sum = {}
×
NEW
904
    for i, chamber_id in enumerate(chamber_names):
×
NEW
905
        if dtype == Data3D:
×
NEW
906
            mask_sum[chamber_id] = mask[:, i].sum()  # sum across concentrations for each chamber
×
NEW
907
        elif dtype == Data2D:
×
NEW
908
            mask_sum[chamber_id] = mask[i].sum()
×
909

910
    #plotting function: We'll generate a subplot for each chamber, showing the raw data and the linear regression line.
911
    # 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.
912
    
NEW
913
    plot_chip(mask_sum, sample_names_dict, title=f'# Concentrations that pass filter: {mask_name}')
×
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