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

pinneylab / htbam_analysis / 20628501082

31 Dec 2025 10:45PM UTC coverage: 15.26% (-0.6%) from 15.836%
20628501082

push

github

Nicholas-Freitas
feat: Add `max_reaction_percent` parameter to concentration fitting, and these can be used to show fit points in plot_initial_rates_chip()

0 of 117 new or added lines in 2 files covered. (0.0%)

3 existing lines in 2 files now uncovered.

470 of 3080 relevant lines covered (15.26%)

0.46 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
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
14
from htbam_db_api.data import Data4D, Data3D, Data2D
×
15

16
# Plotting
17
import seaborn as sns
×
18

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.
59
    zmin, zmax = np.nanpercentile(img_array, [5, 95])
×
60

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
    )
150
    def update_highlights(hoverData, clickData, search_value, fig):
×
151
        # clear old shapes
152
        fig["layout"]["shapes"] = []
×
153

154
        # search => cyan outlines
155
        if search_value:
×
156
            search_str = search_value.lower()
×
157
            for cid, name in chamber_names.items():
×
158
                if name and search_str in name.lower():
×
159
                    i, j = map(int, cid.split(","))
×
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

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
    
251
    analysis_data = experiment.get_run(analysis_name)     # Analysis data (to show slopes/intercepts)
×
252
    
253
    plotting_var = variable
×
254
    plotting_var_unit = analysis_data.dep_var_units[analysis_data.dep_var_type.index(plotting_var)]
×
255

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

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()
266
    chamber_names = analysis_data.indep_vars.chamber_IDs # (n_chambers,)
×
267
    sample_names =  analysis_data.indep_vars.sample_IDs # (n_chambers,)
×
268

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

274
    # Create dictionary mapping chamber_id -> concentration:
275
    concentration_dict = {}
×
276
    for i, chamber_id in enumerate(chamber_names):
×
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.
281
    def plot_chamber_variable(chamber_id, ax):
×
282
        #parameters:
283
        # get the sample_ID for this chamber:
284
        sample_id = sample_names_dict[chamber_id]
×
285
        # Get all replicates with this sample_id:
286
        repl_indices = [i for i, s in enumerate(sample_names) if s == sample_id]
×
287
        # Get the concentration values for these replicates:
288
        repl_concentrations = concentration[repl_indices]
×
289

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

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

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

331
    luminance_unit = experiment_data.dep_var_units[experiment_data.dep_var_type.index('luminance')]
×
332
    
333
    # Extract luminance and concentration from experiment data
334
    luminance_idx = experiment_data.dep_var_type.index('luminance')  # index of luminance in dep_vars
×
335
    luminance = experiment_data.dep_var[..., luminance_idx] * luminance_unit  # (n_chambers, n_timepoints, n_conc)
×
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:
339
    chamber_names = experiment_data.indep_vars.chamber_IDs # (n_chambers,)
×
340
    sample_names =  experiment_data.indep_vars.sample_IDs # (n_chambers,)
×
341

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

347
    # Create dictionary mapping chamber_id -> slopes:
348
    slopes_dict = {}
×
349
    for i, chamber_id in enumerate(chamber_names):
×
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.
354
    def plot_chamber_slopes(chamber_id, ax):
×
355
        #parameters:
356

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

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

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

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

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

412
    # Extract product_concentration (Y) from experiment data
413
    product_conc_idx = experiment_data.dep_var_type.index('concentration')  # index of luminance in dep_vars
×
414
    product_conc_unit = experiment_data.dep_var_units[product_conc_idx]
×
415
    product_conc = experiment_data.dep_var[..., product_conc_idx] * product_conc_unit  # (n_chambers, n_timepoints, n_conc)
×
NEW
416
    if fit_points_mask is not None:
×
NEW
417
        points_to_plot = fit_points_data.dep_var[:,:,:,0] # (n_chambers, n_timepoints, n_conc)
×
418
    else:
NEW
419
        points_to_plot = np.ones(product_conc.shape, dtype=bool)
×
420

421
    substrate_conc = experiment_data.indep_vars.concentration # (n_conc,)
×
422
    time_data = experiment_data.indep_vars.time # (n_conc, n_timepoints)
×
423

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

440
    # Create dictionary mapping chamber_id -> mean slopes:
441
    slopes_dict = {}
×
442
    for i, chamber_id in enumerate(chamber_names):
×
443
        slopes_dict[chamber_id] = np.nanmean(slopes_to_plot[:, i])
×
444

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

448
    def plot_chamber_initial_rates(chamber_id, ax):#, time_to_plot=time_to_plot):
×
449
        #N.B. Every so often, slope and line colors don't match up. Not sure why.
450
        
451
        #convert from 'x,y' to integer index in the array:
452
        #data_index = list(experiment._run_data[run_name]["chamber_idxs"]).index(chamber_id)
453
        x_data = time_data # same for all chambers              (n_timepoints, n_conc)
×
454
        y_data = product_conc[:, :, chamber_names == chamber_id]  #(n_timepoints, n_conc)
×
455

NEW
456
        y_data_mask = points_to_plot[:, :, chamber_names == chamber_id][:,:,0] # (n_timepoints, n_conc)
×
457
        # Make y_data_mask a boolean array:
NEW
458
        y_data_mask = y_data_mask > 0
×
NEW
459
        x_data_masked = np.where(y_data_mask, x_data, np.nan)
×
NEW
460
        y_data_masked = np.where(y_data_mask[..., np.newaxis], y_data, np.nan)
×
461
    
462
        m = slopes_to_plot[:, chamber_names == chamber_id]
×
463
        b = intercepts_to_plot[:, chamber_names == chamber_id]
×
464
        
465
        colors = sns.color_palette('husl', n_colors=y_data.shape[0])
×
466

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

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

471
            # Plot the fit points with alpha=1
NEW
472
            ax.scatter(x_data_masked[i], y_data_masked[i,:].flatten(), color=colors[i], alpha=1)
×
473
            
UNCOV
474
            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
×
475

476
        # Set axis limits if provided
477
        if plot_xmax is not None:
×
478
            ax.set_xlim(right=plot_xmax)
×
479
        if plot_ymax is not None:
×
480
            ax.set_ylim(top=plot_ymax)
×
481
        if plot_xmin is not None:
×
482
            ax.set_xlim(left=plot_xmin)
×
483
        if plot_ymin is not None:
×
484
            ax.set_ylim(bottom=plot_ymin)
×
485
        
486
        ax.set_xlabel(f'Time ({time_data.units:~})')
×
487
        ax.set_ylabel(f'Product Concentration ({product_conc.units:~})')
×
488

489
        ax.legend()
×
490

491
        return ax
×
492
    
493
    plot_chip(slopes_dict, sample_names_dict, graphing_function=plot_chamber_initial_rates, title='Kinetics: Initial Rates')
×
494

495
def plot_initial_rates_vs_concentration_chip(experiment: 'HTBAMExperiment',
×
496
                                             analysis_name: str,
497
                                             model_fit_name: str = None,
498
                                             model_pred_data_name: str = None,
499
                                             x_log: bool = False,
500
                                             y_log: bool = False):
501
    """
502
    Plot initial rates vs substrate concentration for each chamber.
503
    Optionally overlay fitted curve from `model_pred_data_name` and
504
    annotate fit parameters from `model_fit_name`.
505
    """
506
    analysis: Data3D = experiment.get_run(analysis_name)
×
507
    si = analysis.dep_var_type.index("slope")
×
508
    slope_unit = analysis.dep_var_units[si]
×
509
    slopes = analysis.dep_var[..., si] * slope_unit  # (n_conc, n_chambers)
×
510
    conc   = analysis.indep_vars.concentration       # (n_conc,)
×
511

512
    if model_pred_data_name:
×
513
        mf_pred: Data3D = experiment.get_run(model_pred_data_name)
×
514
        yi = mf_pred.dep_var_type.index("y_pred")
×
515
        y_pred_unit = mf_pred.dep_var_units[yi]
×
516
        preds = mf_pred.dep_var[..., yi] * y_pred_unit          # (n_conc, n_chambers)
×
517

518
    if model_fit_name:
×
519
        mf_fit: Data2D = experiment.get_run(model_fit_name)
×
520
        fit_types = mf_fit.dep_var_type           # e.g. ["v_max","K_m","r_squared"]
×
521
        fit_vals = mf_fit.dep_var                # shape (n_chamb, len(fit_types))
×
522
        fit_units = mf_fit.dep_var_units
×
523

524
    chambers = analysis.indep_vars.chamber_IDs        # (n_chambers,)
×
525
    samples  = analysis.indep_vars.sample_IDs         # (n_chambers,)
×
526
    sample_names = {cid: samples[i] for i, cid in enumerate(chambers)}
×
527
    mean_rates   = {cid: np.nanmean(slopes[:, i]) for i, cid in enumerate(chambers)}
×
528

529
    def plot_rates_vs_conc(cid, ax):
×
530
        idx = (chambers == cid)
×
531
        x   = conc
×
532
        y   = slopes[:, idx].flatten()
×
533
        ax.scatter(x, y, alpha=0.7)
×
534

535
        if model_pred_data_name:
×
536
            y_p = preds[:, idx].flatten()
×
537
            ax.plot(x, y_p, color="red", label="model")
×
538

539
        if model_fit_name:
×
540
            # extract this chamber's fit row
541
            chamb_idx = np.where(chambers == cid)[0][0]
×
542
            vals = fit_vals[chamb_idx]
×
543
            txt = "".join(f"{nm}={v:.2f} {u:~}\n" for nm,v,u in zip(fit_types, vals, fit_units))
×
544
            ax.text(0.05, 0.95, txt, transform=ax.transAxes,
×
545
                    va="top", fontsize=8, bbox=dict(boxstyle="round", fc="white", alpha=0.7))
546

547
        if model_pred_data_name or model_fit_name:
×
548
            ax.legend()
×
549
        ax.set_title(f"{cid}: {sample_names[cid]}")
×
550
        ax.set_xlabel(f"Concentration ({conc.units:~})")
×
551
        ax.set_ylabel(f"Initial Rate ({slopes.units:~})")
×
552
        if x_log: ax.set_xscale("log")
×
553
        if y_log: ax.set_yscale("log")
×
554
        return ax
×
555

556
    plot_chip(mean_rates, sample_names,
×
557
              graphing_function=plot_rates_vs_conc,
558
              title="Initial Rates vs Concentration")
559

560
def plot_MM_chip(experiment: 'HTBAMExperiment',
×
561
                analysis_name: str,
562
                model_fit_name: str,
563
                model_pred_data_name: str = None,
564
                x_log: bool = False,
565
                y_log: bool = False):
566
    """
567
    Plot MM values, with inset initial rates vs substrate concentration for each chamber.
568
    Optionally overlay fitted curve from `model_pred_data_name` and
569
    annotate fit parameters from `model_fit_name`.
570
    """
571
    analysis: Data3D = experiment.get_run(analysis_name)
×
572
    si = analysis.dep_var_type.index("slope")
×
573
    slope_unit = analysis.dep_var_units[si]
×
574
    slopes = analysis.dep_var[..., si] * slope_unit  # (n_conc, n_chambers)
×
575
    conc   = analysis.indep_vars.concentration       # (n_conc,)
×
576

577
    mf_fit: Data2D = experiment.get_run(model_fit_name)
×
578
    fit_types = mf_fit.dep_var_type           # e.g. ["v_max","K_m","r_squared"]
×
579
    fit_vals = mf_fit.dep_var                # shape (n_chamb, len(fit_types))
×
580
    fit_units = mf_fit.dep_var_units
×
581

582
    mm_idx = mf_fit.dep_var_type.index("v_max")
×
583
    mms = mf_fit.dep_var[..., mm_idx] * fit_units[mm_idx]   # (n_chambers,)
×
584

585
    if model_pred_data_name:
×
586
        mf_pred: Data3D = experiment.get_run(model_pred_data_name)
×
587
        yi = mf_pred.dep_var_type.index("y_pred")
×
588
        y_pred_unit = mf_pred.dep_var_units[yi]
×
589
        preds = mf_pred.dep_var[..., yi] * y_pred_unit          # (n_conc, n_chambers)
×
590

591
    chambers = analysis.indep_vars.chamber_IDs        # (n_chambers,)
×
592
    samples  = analysis.indep_vars.sample_IDs         # (n_chambers,)
×
593
    sample_names = {cid: samples[i] for i, cid in enumerate(chambers)}
×
594
    #mean_rates   = {cid: np.nanmean(slopes[:, i]) for i, cid in enumerate(chambers)}
595
    mms_to_plot = {cid: mms[i] for i, cid in enumerate(chambers)}
×
596

597
    def plot_rates_vs_conc(cid, ax):
×
598
        idx = (chambers == cid)
×
599
        x   = conc
×
600
        y   = slopes[:, idx].flatten()
×
601
        ax.scatter(x, y, alpha=0.7, label="current well")
×
602

603
        if model_pred_data_name:
×
604
            # show envelope of model fits for all wells with this sample
605
            sample = sample_names[cid]
×
606
            same_idxs = [i for i, s in enumerate(samples) if s == sample]
×
607
            y_all = preds[:, same_idxs]               # (n_conc, n_same)
×
608
            
609
            # Show the 95% confidence interval:
610
            y_min = np.nanpercentile(y_all, 2.5, axis=1)
×
611
            y_max = np.nanpercentile(y_all, 97.5, axis=1)
×
612
            
613
            # then overplot this chamber’s model fit
614
            y_p = preds[:, idx].flatten()
×
615
            ax.plot(x, y_p, color="red", label="current well fit")
×
616
            ax.fill_between(x, y_min, y_max, color="gray", alpha=0.3, label='95% CI')
×
617

618
        if model_fit_name:
×
619
            # extract this chamber's fit row
620
            chamb_idx = np.where(chambers == cid)[0][0]
×
621
            vals = fit_vals[chamb_idx]
×
622
            txt = "".join(f"{nm}={v:.2f} {u:~}\n" for nm,v,u in zip(fit_types, vals, fit_units))
×
623
            ax.text(0.05, 0.95, txt, transform=ax.transAxes,
×
624
                    va="top", fontsize=8, bbox=dict(boxstyle="round", fc="white", alpha=0.7))
625
        
626
        if model_pred_data_name or model_fit_name:
×
627
            ax.legend()
×
628
        ax.set_title(f"{cid}: {sample_names[cid]}")
×
629
        ax.set_xlabel(f"Concentration ({conc.units:~})")
×
630
        ax.set_ylabel(f"Initial Rate ({slopes.units:~})")
×
631
        if x_log: ax.set_xscale("log")
×
632
        if y_log: ax.set_yscale("log")
×
633
        return ax
×
634

635
    plot_chip(mms_to_plot, sample_names,
×
636
              graphing_function=plot_rates_vs_conc,
637
              title="Initial Rates vs Concentration")   
638

639
def plot_MM_div_E_chip(experiment: 'HTBAMExperiment',
×
640
                analysis_name: str,
641
                model_fit_name: str,
642
                dep_var_name='slope',
643
                x_log: bool = False,
644
                y_log: bool = False):
645
    """
646
    Plot MM values, with inset initial rates vs substrate concentration for each chamber.
647
    Optionally overlay fitted curve from `model_pred_data_name` and
648
    annotate fit parameters from `model_fit_name`.
649
    """
650
    analysis: Data3D = experiment.get_run(analysis_name)
×
651
    si = analysis.dep_var_type.index(dep_var_name)
×
652
    slope_unit = analysis.dep_var_units[si]
×
653
    slopes = analysis.dep_var[..., si] * slope_unit  # (n_conc, n_chambers)
×
654
    conc   = analysis.indep_vars.concentration       # (n_conc,)
×
655

656
    mf_fit: Data2D = experiment.get_run(model_fit_name)
×
657
    fit_types = mf_fit.dep_var_type           # e.g. ["v_max","K_m","r_squared", "kcat"]
×
658
    fit_vals = mf_fit.dep_var                # shape (n_chamb, len(fit_types))
×
659
    fit_units = mf_fit.dep_var_units
×
660

661
    kcat_idx = mf_fit.dep_var_type.index("kcat")
×
662
    all_kcats = mf_fit.dep_var[..., kcat_idx] * fit_units[kcat_idx]   # (n_chambers,)
×
663
    kM_idx = mf_fit.dep_var_type.index("K_m")
×
664
    all_kMs = mf_fit.dep_var[..., kM_idx] * fit_units[kM_idx]   # (n_chambers,)
×
665

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

668
    chambers = analysis.indep_vars.chamber_IDs        # (n_chambers,)
×
669
    samples  = analysis.indep_vars.sample_IDs         # (n_chambers,)
×
670
    sample_names = {cid: samples[i] for i, cid in enumerate(chambers)}
×
671
    #mean_rates   = {cid: np.nanmean(slopes[:, i]) for i, cid in enumerate(chambers)}
672
    kcats_to_plot = {cid: all_kcats[i] for i, cid in enumerate(chambers)}
×
673
    kMs_to_plot = {cid: all_kMs[i] for i, cid in enumerate(chambers)}
×
674

675
    def plot_rates_vs_conc(cid, ax):
×
676
        idx = (chambers == cid)
×
677
        x   = conc
×
678
        y   = slopes[:, idx].flatten()
×
679
        ax.scatter(x, y, alpha=0.7, label="current well")
×
680

681

682
        sample = sample_names[cid]
×
683
        same_idxs = [i for i, s in enumerate(samples) if s == sample]
×
684
        
685
        sample_kcats = all_kcats[same_idxs]
×
686
        sample_kMs = all_kMs[same_idxs]
×
687

688
        current_kcat = kcats_to_plot[cid]
×
689
        current_km = kMs_to_plot[cid]
×
690

691
        mean_kcat = np.nanmean(sample_kcats)
×
692
        mean_km = np.nanmean(sample_kMs)
×
693

694
        # stdev of kcat?
695
        kcat_stdev = np.nanstd(sample_kcats)
×
696
        km_stdev = np.nanstd(sample_kMs)
×
697
        
698
        # push kcat up and down one stdev
699
        kcat_up = mean_kcat + kcat_stdev
×
700
        kcat_down = mean_kcat - kcat_stdev
×
701

702
        # calculate new slopes
703
        from htbam_analysis.analysis.fit import mm_model
×
704

705
        pred_y_mean = mm_model(conc, mean_kcat, mean_km)
×
706
        pred_y_up = mm_model(conc, kcat_up, mean_km)
×
707
        pred_y_down = mm_model(conc, kcat_down, mean_km)
×
708
        pred_y_current = mm_model(conc, current_kcat, current_km)
×
709

710
        ax.plot(x, pred_y_current, color="blue", label="current well fit")
×
711

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

715
        ax.text(0.05, 0.95, 
×
716
                f"$\\overline{{k_{{cat}}}}$ = {mean_kcat.magnitude:.2f} ± {kcat_stdev:.2f~}\n"
717
                f"$\\overline{{K_{{M}}}}$ = {mean_km.magnitude:.2f} ± {km_stdev:.2f~}", 
718
                transform=ax.transAxes,
719
                va="top", fontsize=8, zorder=10,
720
                bbox=dict(boxstyle="round", fc="white", alpha=0.7))
721

722
        # if model_fit_name:
723
        #     # extract this chamber's fit row
724
        #     chamb_idx = np.where(chambers == cid)[0][0]
725
        #     vals = fit_vals[chamb_idx]
726
        #     txt = "".join(f"{nm}={v:.2f} {u:~}\n" for nm,v,u in zip(fit_types, vals, fit_units))
727
        #     ax.text(0.05, 0.95, txt, transform=ax.transAxes,
728
        #             va="top", fontsize=8, bbox=dict(boxstyle="round", fc="white", alpha=0.7))
729
        
730
        #if model_pred_data_name or model_fit_name:
731
        ax.legend()
×
732
        ax.set_title(f"{cid}: {sample_names[cid]}")
×
733
        ax.set_xlabel(f"$[S]$ ({conc.units:~})")
×
734
        ax.set_ylabel(f"$V_0/[E]$ ({slopes.units:~})")
×
735
        if x_log: ax.set_xscale("log")
×
736
        if y_log: ax.set_yscale("log")
×
737
        return ax
×
738

739
    plot_chip(kcats_to_plot, sample_names,
×
740
              graphing_function=plot_rates_vs_conc,
741
              title="V_0/[E] vs [S]")       
742

743
def plot_ic50_chip(experiment: 'HTBAMExperiment',
×
744
                analysis_name: str,
745
                model_fit_name: str,
746
                model_pred_data_name: str = None,
747
                x_log: bool = False,
748
                y_log: bool = False):
749
    """
750
    Plot ic50 values, with inset initial rates vs substrate concentration for each chamber.
751
    Optionally overlay fitted curve from `model_pred_data_name` and
752
    annotate fit parameters from `model_fit_name`.
753
    """
754
    analysis: Data3D = experiment.get_run(analysis_name)
×
755
    si = analysis.dep_var_type.index("slope")
×
756
    slope_unit = analysis.dep_var_units[si]
×
757
    slopes = analysis.dep_var[..., si] * slope_unit  # (n_conc, n_chambers)
×
758
    conc   = analysis.indep_vars.concentration       # (n_conc,)
×
759

760
    mf_fit: Data2D = experiment.get_run(model_fit_name)
×
761
    fit_types = mf_fit.dep_var_type           # e.g. ["v_max","K_m","r_squared"]
×
762
    fit_vals = mf_fit.dep_var                # shape (n_chamb, len(fit_types))
×
763
    fit_units = mf_fit.dep_var_units
×
764

765
    ic50s_idx = mf_fit.dep_var_type.index("ic50")
×
766
    ic50s = mf_fit.dep_var[..., ic50s_idx] * fit_units[ic50s_idx]    # (n_chambers,)
×
767

768
    if model_pred_data_name:
×
769
        mf_pred: Data3D = experiment.get_run(model_pred_data_name)
×
770
        yi = mf_pred.dep_var_type.index("y_pred")
×
771
        y_pred_unit = mf_pred.dep_var_units[yi]
×
772
        preds = mf_pred.dep_var[..., yi] * y_pred_unit          # (n_conc, n_chambers)
×
773

774
    chambers = analysis.indep_vars.chamber_IDs        # (n_chambers,)
×
775
    samples  = analysis.indep_vars.sample_IDs         # (n_chambers,)
×
776
    sample_names = {cid: samples[i] for i, cid in enumerate(chambers)}
×
777
    #mean_rates   = {cid: np.nanmean(slopes[:, i]) for i, cid in enumerate(chambers)}
778
    ic50s_to_plot = {cid: ic50s[i] for i, cid in enumerate(chambers)}
×
779

780
    def plot_rates_vs_conc(cid, ax):
×
781
        idx = (chambers == cid)
×
782
        x   = conc
×
783
        y   = slopes[:, idx].flatten()
×
784
        ax.scatter(x, y, alpha=0.7, label="current well")
×
785

786
        if model_pred_data_name:
×
787
            # show envelope of model fits for all wells with this sample
788
            sample = sample_names[cid]
×
789
            same_idxs = [i for i, s in enumerate(samples) if s == sample]
×
790
            y_all = preds[:, same_idxs]               # (n_conc, n_same)
×
791

792
            # Show the 95% confidence interval:
793
            y_min = np.nanpercentile(y_all, 2.5, axis=1)
×
794
            y_max = np.nanpercentile(y_all, 97.5, axis=1)
×
795
            
796
            # then overplot this chamber’s model fit
797
            y_p = preds[:, idx].flatten()
×
798
            ax.plot(x, y_p, color="red", label="current well fit")
×
799
            ax.fill_between(x, y_min, y_max, color="gray", alpha=0.3, label='95% CI')
×
800

801
        if model_fit_name:
×
802
            # extract this chamber's fit row
803
            chamb_idx = np.where(chambers == cid)[0][0]
×
804
            vals = fit_vals[chamb_idx]
×
805
            txt = "".join(f"{nm}={v:.2f} {u:~}\n" for nm,v,u in zip(fit_types, vals, fit_units))
×
806
            ax.text(0.05, 0.95, txt, transform=ax.transAxes,
×
807
                    va="top", fontsize=8, bbox=dict(boxstyle="round", fc="white", alpha=0.7))
808
        
809
        if model_pred_data_name or model_fit_name:
×
810
            ax.legend()
×
811
        ax.set_title(f"{cid}: {sample_names[cid]}")
×
812
        ax.set_xlabel(f"Concentration ({conc.units:~})")
×
813
        ax.set_ylabel(f"Initial Rate ({slopes.units:~})")
×
814
        if x_log: ax.set_xscale("log")
×
815
        if y_log: ax.set_yscale("log")
×
816
        return ax
×
817

818
    plot_chip(ic50s_to_plot, sample_names,
×
819
              graphing_function=plot_rates_vs_conc,
820
              title="Initial Rates vs Concentration")
821

822
def plot_enzyme_concentration_chip(experiment: 'HTBAMExperiment', analysis_name: str, skip_start_timepoint: bool = True):
×
823
    '''
824
    Plot a full chip with raw data and fit initial rates.
825

826
    Parameters:
827
        experiment ('HTBAMExperiment'): the experiment object.
828
        analysis_name (str): the name of the analysis to be plotted.
829
        skip_start_timepoint (bool): whether to skip the first timepoint in the analysis (Sometimes are unusually low). Default is True.
830

831
    Returns:
832
        None
833
    '''
834
    #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)
835
    analysis_data = experiment.get_run(analysis_name)     # Analysis data (to show slopes/intercepts)
×
836
    
837
    plotting_var = 'concentration'
×
838

839
    # Verify we have the variable:
840
    if plotting_var not in analysis_data.dep_var_type:
×
841
        raise ValueError(f"'{plotting_var}' not found in analysis data. Available variables: {analysis_data.dep_vars_types}")
×
842
    else:
843
        plotting_var_index = analysis_data.dep_var_type.index(plotting_var)
×
844

845
    conc_units = analysis_data.dep_var_units[plotting_var_index]
×
846
    concentration = analysis_data.dep_var[..., plotting_var_index] * conc_units # (n_chambers, n_conc, 1)
×
847
    
848
    #chamber_names: We'll provide the name of the sample in each chamber as well, in the same way:
849
    #chamber_names_dict = experiment._db_conn.get_chamber_name_dict()
850
    chamber_names = analysis_data.indep_vars.chamber_IDs # (n_chambers,)
×
851
    sample_names =  analysis_data.indep_vars.sample_IDs # (n_chambers,)
×
852

853
    # Create dictionary mapping chamber_id -> sample_name:
854
    sample_names_dict = {}
×
855
    for i, chamber_id in enumerate(chamber_names):
×
856
        sample_names_dict[chamber_id] = sample_names[i]
×
857

858
    # Create dictionary mapping chamber_id -> mean slopes:
859
    conc_dict = {}
×
860
    for i, chamber_id in enumerate(chamber_names):
×
861
        conc_dict[chamber_id] = np.nanmean(concentration[i])
×
862

863
    def graphing_function(chamber_id, ax):
×
864
        # plot histogram of the concentration from all chambers with the same sample name as the chamber_id
865
        sample_name = sample_names_dict[chamber_id]
×
866
        conc = concentration[sample_names == sample_name]
×
867
        ax.hist(conc, bins=10)
×
868
        # Make the bin for the current chamber red:
869
        # What bin has the max count?
870
        bin_max_count = np.max(np.histogram(conc.magnitude, bins=10)[0])
×
871
        ax.vlines(conc_dict[chamber_id].magnitude, 0, bin_max_count, colors='red', linestyles='--')
×
872
        ax.set_title(f'Concentration of {sample_name}')
×
873
        ax.set_xlabel(f'Concentration {conc_units:~}')
×
874
        ax.set_ylabel('Count')
×
875
        # print legend current chamber with concentration:
876
        ax.legend([f'Current Chamber: {conc_dict[chamber_id].magnitude:.2f} {conc_units:~}'])
×
877
        return ax
×
878
    
879
    plot_chip(conc_dict, sample_names_dict, 
×
880
            graphing_function=graphing_function,
881
            title=f'Enzyme Concentration')
882

883
def plot_mask_chip(experiment: 'HTBAMExperiment', mask_name: str):
×
884
    '''
885
    Plot a full chip with raw data and fit initial rates.
886

887
    Parameters:
888
        experiment ('HTBAMExperiment'): the experiment object.
889
        mask_name (str): the name of the mask to be plotted. (Data3D or Data2D)
890

891
    Returns:
892
        None
893
    '''
894
    #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)
895
    mask_data = experiment.get_run(mask_name)     # Analysis data (to show slopes/intercepts)
×
896
    
897
    dtype = type(mask_data)
×
898
    assert dtype in [Data3D, Data2D], "mask_data must be of type Data3D or Data2D."
×
899

900
    mask_idx = mask_data.dep_var_type.index('mask')
×
901

902
    # If we're using a data2D
903
    mask = mask_data.dep_var[..., mask_idx] # (n_conc, n_chambers,)
×
904

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

908
    #passed_conc = np.sum(mask, axis=0)
909
    #print(mask.shape)
910

911
    #chamber_names: We'll provide the name of the sample in each chamber as well, in the same way:
912
    chamber_names = mask_data.indep_vars.chamber_IDs # (n_chambers,)
×
913
    sample_names =  mask_data.indep_vars.sample_IDs # (n_chambers,)
×
914

915
    # Create dictionary mapping chamber_id -> sample_name:
916
    sample_names_dict = {}
×
917
    for i, chamber_id in enumerate(chamber_names):
×
918
        sample_names_dict[chamber_id] = sample_names[i]
×
919

920
    # Create dictionary mapping chamber_id -> mean slopes:
921
    mask_sum = {}
×
922
    for i, chamber_id in enumerate(chamber_names):
×
923
        if dtype == Data3D:
×
924
            mask_sum[chamber_id] = mask[:, i].sum()  # sum across concentrations for each chamber
×
925
        elif dtype == Data2D:
×
926
            mask_sum[chamber_id] = mask[i].sum()
×
927

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

933
def plot_chip_by_variable(experiment: 'HTBAMExperiment', analysis_name: str, variable: str):
×
934
    '''
935
    Plot a chip using arbitrary DataND object, by specifying the variable to plot.
936

937
    Parameters:
938
        experiment ('HTBAMExperiment'): the experiment object.
939
        analysis_name (str): the name of the analysis to be plotted. (Data3D or Data2D)
940
        variable (str): the variable to plot.
941

942
    Returns:
943
        None
944
    '''
945

946
    #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)
947
    data = experiment.get_run(analysis_name)     # Analysis data (to show slopes/intercepts)
×
948
    
949
    dtype = type(data)
×
950
    assert dtype in [Data3D, Data2D], "data must be of type Data3D or Data2D."
×
951

952
    mask_idx = data.dep_var_type.index(variable)
×
953

954
    # If we're using a data2D
955
    mask = data.dep_var[..., mask_idx] # (n_conc, n_chambers,)
×
956

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

960
    #passed_conc = np.sum(mask, axis=0)
961
    #print(mask.shape)
962

963
    #chamber_names: We'll provide the name of the sample in each chamber as well, in the same way:
964
    chamber_names = data.indep_vars.chamber_IDs # (n_chambers,)
×
965
    sample_names =  data.indep_vars.sample_IDs # (n_chambers,)
×
966

967
    # Create dictionary mapping chamber_id -> sample_name:
968
    sample_names_dict = {}
×
969
    for i, chamber_id in enumerate(chamber_names):
×
970
        sample_names_dict[chamber_id] = sample_names[i]
×
971

972
    # Create dictionary mapping chamber_id -> mean slopes:
973
    mask_sum = {}
×
974
    for i, chamber_id in enumerate(chamber_names):
×
975
        if dtype == Data3D:
×
976
            mask_sum[chamber_id] = mask[:, i].sum()  # sum across concentrations for each chamber
×
977
        elif dtype == Data2D:
×
978
            mask_sum[chamber_id] = mask[i].sum()
×
979

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