Reproducing Kim et al. (2023), Figure 3, from dandiset 000632¶

Paper: Kim V, Semenov I, Kiester AS, Keppler MA, Ibey BL, Bixler JN, Colunga Biancatelli RML, Pakhomov AG (2023). Control of the Electroporation Efficiency of Nanosecond Pulses by Swinging the Electric Field Vector Direction. Int J Mol Sci 24, 10921. doi:10.3390/ijms241310921.

Dataset: DANDI:000632, "Electroporation efficiency of co-directional and cross-directional paired pulses", version 0.231012.1930 (doi:10.48324/dandi.000632/0.231012.1930). The paper cites no version of the dataset (its Data Availability Statement says the data are available on request). 0.231012.1930 is the only published version, and the draft lists the same number of assets with the same total size (printed below), so it is pinned.

Claims: claims/000632/10.3390-ijms241310921.yaml, written from the version of record as served by Europe PMC (PMC10341945), with no supplement and no PDF.

What the dataset holds: 24 NWB files, each one stitched whole-dish image (two channels, MemBrite and YO-PRO-1) of a BPAE monolayer after four paired pulses in the square electrode array of Figure 3. It holds none of the triangular-array experiments (Figures 2 and 4) and none of the strobe imaging of Figure 5. Those are public in three other dandisets that name this paper (000631, 000633 and 000634, listed under "Blocked claims"), but the claims file scopes every claim to 000632, so they are not tested here and a reader is asked whether to mark them as data held elsewhere.

claim tested here
fig3-1hz-co-equals-cross tested
fig3-770khz-co-enhanced tested
fig3-770khz-cross-same-as-1hz tested
fig3-770khz-co-above-cross tested
fig3-no-area-dependence tested (follows fig3-1hz-co-equals-cross)
fig2-180-cancellation blocked, other (data in another dandiset)
fig2-crossover-angle blocked, other (data in another dandiset)
fig2-facilitation-plateau blocked, other (data in another dandiset)
fig2-60-fold-co-counter blocked, other (data in another dandiset)
fig4-unipolar-pattern blocked, other (data in another dandiset)
fig5-base-sawtooth blocked, other (data in another dandiset)
fig5-apex-larger-polarization blocked, other (data in another dandiset)

Method, in short. For each dish the module epimg.py reads the YO-PRO-1 (YP) channel as a reduced overview, finds the four dark electrode footprints, and takes the array center as the mean of their centers. It then reads full-resolution pixels for the 200 x 200 um ROI at the center (Methods 4.4) and for a 200 x 200 um ROI at the center of each footprint, where cells were shielded from the field (Methods 4.4, "To account for 'spontaneous' YP uptake, measurements were also performed within the hollow electrodes' footprints"). Background reading (a) is the raw mean YP in the center ROI; reading (b) subtracts the mean of the four footprint ROIs. Groups are compared by two-tailed Student's t-tests (Figure 3 legend). No step uses random numbers.

In [1]:
import os
os.environ.setdefault("OMP_NUM_THREADS", "1")
import time
import multiprocessing as mp
from concurrent.futures import ProcessPoolExecutor

import numpy as np
import pandas as pd
import requests
import matplotlib.pyplot as plt
from scipy import stats

import epimg
print("dandiset", epimg.DANDISET, "version", epimg.VERSION)
for v in requests.get(f"{epimg.API}/dandisets/{epimg.DANDISET}/versions/", timeout=60).json()["results"]:
    print(f"  listed version {v['version']}: {v['asset_count']} assets, {v['size']} bytes")
assets = epimg.list_assets()
print(len(assets), "assets")
t0 = time.time()
# each dish is read in its own process; within it, chunks are fetched in parallel threads
with ProcessPoolExecutor(8, mp_context=mp.get_context("spawn")) as ex:
    dishes = list(ex.map(epimg.measure_dish, assets))
print(f"read {len(dishes)} dishes in {time.time() - t0:.0f} s")
dandiset 000632 version 0.231012.1930
  listed version draft: 24 assets, 42744120707 bytes
  listed version 0.231012.1930: 24 assets, 42744120707 bytes
24 assets
read 24 dishes in 375 s

Data check¶

In [2]:
pd.set_option("display.width", 200)
pd.set_option("display.max_columns", 30)
rows = []
for d in dishes:
    fp = d["footprints"]
    rows.append(dict(
        subject=d["path"].split("/")[0].removeprefix("sub-"),
        session_id=d["session_id"], direction=d["direction"], freq=d["freq"],
        day=d["session_start_time"][:10], start=d["session_start_time"][11:16],
        shape=str(d["shape"]), unit=d["unit"],
        center_yp=d["center_yp"], fp_yp=np.mean(d["fp_yp"]), fp_yp_min=np.min(d["fp_yp"]),
        fp_yp_max=np.max(d["fp_yp"]), fp_disc_yp=np.mean(d["fp_disc_yp"]),
        center_mb=d["center_mb"], fp_mb=np.mean(d["fp_mb"]),
        fp_radius_um=np.mean([f["radius_um"] for f in fp]),
        parallelogram_dev_um=fp[0]["parallelogram_dev_um"],
        side_um=np.mean([np.hypot((fp[k]["row"] - fp[(k + 1) % 4]["row"]) * epimg.ov_px_um()[0],
                                  (fp[k]["col"] - fp[(k + 1) % 4]["col"]) * epimg.ov_px_um()[1])
                         for k in range(4)]),
        refined_offset_um=np.hypot((d["refined_center_px"][0] - d["center_px"][0]) * epimg.UM_PER_PX,
                                   (d["refined_center_px"][1] - d["center_px"][1]) * epimg.UM_PER_PX),
        refined_diam_um=np.mean([f["diameter_um"] for f in d["refined"]]),
        fingerprint=d["chunk_fingerprint"]))
df = pd.DataFrame(rows).sort_values(["direction", "freq", "day", "start"]).reset_index(drop=True)

print("Counts by condition as labelled (session_id) and by experiment day:")
print(pd.crosstab([df.direction, df.freq], df.day, margins=True))
print()
print("session_id matches the condition in the file name for every file:",
      all(s.lower().startswith(dr) and s.endswith(f) for s, dr, f in zip(df.session_id, df.direction, df.freq)))
print("image shapes:", df["shape"].unique(), " data unit:", df.unit.unique())
print()
print("ImageSeries description (identical in all files:",
      len({d["image_description"] for d in dishes}) == 1, "):")
print(" ", dishes[0]["image_description"])
print("Session descriptions, one per condition:")
for s in sorted({d["session_description"] for d in dishes}):
    print(" ", s)
Counts by condition as labelled (session_id) and by experiment day:
day               2022-11-29  2022-12-04  All
direction freq                               
co        1Hz              4           2    6
          770kHz           3           2    5
cross     1Hz              3           3    6
          770kHz           3           4    7
All                       13          11   24

session_id matches the condition in the file name for every file: True
image shapes: <StringArray>
['(27853, 29696, 2)']
Length: 1, dtype: str  data unit: <StringArray>
['n.a.']
Length: 1, dtype: str

ImageSeries description (identical in all files: True ):
  IX83 microscope used; MS-2000 scanning stage; X-Cite 110LED illuminator; ORCA-Flash4 sCMOS camera;CellSens software for automation; Channel 1 ex/em 350/460nm for MemBrite dye; ex/em 480/535nm for YOPRO-1; dark circles indicate electrode footprints; 144 images captured in a 12x12 grid from a six-well plate; 216 additional images with DAPI and FITC filter cubes; all images automatically stitched for a final composite; 10×, 0.38NA objective used for high-resolution imaging.
Session descriptions, one per condition:
  Cells were electroporated by 4 co-directional paired-pulse (600 + 600 ns) trains, trains were applied at frequency of 1 Hz, amplitude 10 kV/cm. The stainless-steel electrodes were aranged in triangular array; The electric field direction stayed the same in each pair.
  Cells were electroporated by 4 co-directional paired-pulse (600 + 600 ns) trains, trains were applied at frequency of 770 kHz, amplitude 10 kV/cm. The stainless-steel electrodes were aranged in triangular array; The electric field direction stayed the same in each pair.
  Cells were electroporated by 4 cross-directional paired-pulse (600 + 600 ns) trains, trains were applied at frequency of 1 Hz, amplitude 10 kV/cm. The stainless-steel electrodes were aranged in triangular array; The electric field direction was changed by 90° in each pair.
  Cells were electroporated by 4 cross-directional paired-pulse (600 + 600 ns) trains, trains were applied at frequency of 770 kHz, amplitude 10 kV/cm. The stainless-steel electrodes were aranged in triangular array; The electric field direction was changed by 90° in each pair.

The files hold no pixel size and no channel names beyond the ImageSeries description, which lists the MemBrite channel first and YO-PRO-1 second. The checks below test both against the images. The session descriptions say the electrodes were in a triangular array; the images show four footprints in a square, as the paper describes for Figure 3 (Methods 4.2).

In [3]:
# Channel order: YP (channel 1) should be high near the array center and low in the shielded
# footprints; MemBrite (channel 0, all cells) should differ much less between the two.
print("center / footprint ratio, median over dishes (range):")
for name, c, f in [("channel 1, YP", df.center_yp, df.fp_yp), ("channel 0, MemBrite", df.center_mb, df.fp_mb)]:
    r = c / f
    print(f"  {name:20s} {r.median():.2f} ({r.min():.2f} to {r.max():.2f})")
r, p = stats.pearsonr(df.center_yp, df.center_mb)
print(f"Pearson r of center YP with center MemBrite across dishes: {r:.2f} (p = {p:.2g})")

# Pixel size: the files give none. Under 0.65 um per pixel (camera pixel 6.5 um, 10x objective),
# the dark footprints should measure 1.5 mm across (Methods 4.2).
print()
print(f"footprint radius under {epimg.UM_PER_PX} um/px: median {df.fp_radius_um.median():.0f} um "
      f"(range {df.fp_radius_um.min():.0f} to {df.fp_radius_um.max():.0f}); nominal outer radius "
      f"{epimg.FOOTPRINT_OD_UM / 2:.0f} um, profile step 50 um")
print(f"array side (center to center of adjacent footprints): median {df.side_um.median():.0f} um "
      f"(range {df.side_um.min():.0f} to {df.side_um.max():.0f})")
print(f"image width under {epimg.UM_PER_PX} um/px: {29696 * epimg.UM_PER_PX / 1000:.1f} mm")
print()
print("Ranges of the variables used (camera counts, unit given as 'n.a.'):")
print(df[["center_yp", "fp_yp", "fp_yp_min", "fp_yp_max", "fp_disc_yp", "center_mb", "fp_mb"]]
      .describe().loc[["min", "50%", "max"]].round(0))
center / footprint ratio, median over dishes (range):
  channel 1, YP        3.81 (1.77 to 9.30)
  channel 0, MemBrite  1.70 (0.98 to 2.82)
Pearson r of center YP with center MemBrite across dishes: 0.94 (p = 1.2e-11)

footprint radius under 0.65 um/px: median 794 um (range 750 to 838); nominal outer radius 750 um, profile step 50 um
array side (center to center of adjacent footprints): median 3751 um (range 3347 to 3872)
image width under 0.65 um/px: 19.3 mm

Ranges of the variables used (camera counts, unit given as 'n.a.'):
     center_yp  fp_yp  fp_yp_min  fp_yp_max  fp_disc_yp  center_mb  fp_mb
min      584.0  260.0      230.0      273.0       258.0      384.0  304.0
50%     1072.0  317.0      290.0      355.0       316.0      793.0  454.0
max     2912.0  354.0      326.0      418.0       353.0     1793.0  668.0

Files deposited twice¶

The chunk index of each file (offsets and compressed sizes of all 16384 chunks) is compared between files, and so are the YP overviews.

In [4]:
dup = df[df.duplicated("fingerprint", keep=False)]
print("files with identical chunk indexes:")
print(dup[["subject", "session_id", "day", "start"]].to_string())
by_subj = {d["path"].split("/")[0].removeprefix("sub-"): d for d in dishes}
if len(dup):
    a, b = dup.subject.tolist()
    print("YP overviews identical:", np.array_equal(by_subj[a]["overview"], by_subj[b]["overview"]))
    print("start times identical:", by_subj[a]["session_start_time"] == by_subj[b]["session_start_time"])
# the largest correlation between overviews of any other pair, for reference
names = df.subject.tolist()
X = np.array([by_subj[n]["overview"][64:192, 64:192].ravel() for n in names])
Cm = np.corrcoef(X)
np.fill_diagonal(Cm, np.nan)
mask = ~df.subject.isin(dup.subject).values
sub = Cm[np.ix_(mask, mask)]
print(f"largest overview correlation between any two other files: {np.nanmax(sub):.3f}")
files with identical chunk indexes:
                       subject             session_id         day  start
1        BPAE-8unipolar-1Hz-04     Co-directional_1Hz  2022-11-29  16:57
11  BPAE-8bipolar-1Hz-600ns-04  Cross-directional_1Hz  2022-11-29  16:57
YP overviews identical: True
start times identical: True
largest overview correlation between any two other files: 0.794

Do the condition labels match the images?¶

With co-directional pulses one diagonal pair of electrodes carries the field, so uptake should be stronger along that diagonal than along the other; with cross-directional pulses both diagonals carry it in turn. The anisotropy index compares, after subtracting the footprint background, mean YP half-way from the center to the two electrodes of each diagonal: |D1 - D2| / (D1 + D2). This index is this attempt's check of the labels, not a measure from the paper.

In [5]:
def aniso(d):
    x = np.array(d["diag_yp"]) - np.mean(d["fp_yp"])
    return abs((x[0] + x[2]) - (x[1] + x[3])) / x.sum()
df["anisotropy"] = [aniso(by_subj[n]) for n in df.subject]
print(df[["subject", "direction", "freq", "day", "anisotropy"]].round(2).to_string())
co = df[(df.direction == "co") & ~df.subject.isin(dup.subject)].anisotropy
cr = df[(df.direction == "cross") & ~df.subject.isin(dup.subject)].anisotropy
u = stats.mannwhitneyu(co, cr, alternative="greater")
print(f"\nwithout the duplicated file: co median {co.median():.2f} (range {co.min():.2f} to {co.max():.2f}, n = {len(co)}), "
      f"cross median {cr.median():.2f} (range {cr.min():.2f} to {cr.max():.2f}, n = {len(cr)}); "
      f"Mann-Whitney U = {u.statistic:.0f}, one-sided p = {u.pvalue:.2g}")
if len(dup):
    print(f"duplicated image: anisotropy {df[df.subject == dup.subject.iloc[0]].anisotropy.iloc[0]:.2f}")
                           subject direction    freq         day  anisotropy
0      BPAE-8unipolar-1Hz-600ns-03        co     1Hz  2022-11-29        0.35
1            BPAE-8unipolar-1Hz-04        co     1Hz  2022-11-29        0.13
2      BPAE-8unipolar-1Hz-600ns-08        co     1Hz  2022-11-29        0.62
3      BPAE-8unipolar-1Hz-600ns-09        co     1Hz  2022-11-29        0.75
4            BPAE-8unipolar-1Hz-07        co     1Hz  2022-12-04        0.60
5            BPAE-8unipolar-1Hz-11        co     1Hz  2022-12-04        0.37
6   BPAE-8unipolar-770kHz-600ns-02        co  770kHz  2022-11-29        0.53
7   BPAE-8unipolar-770kHz-600ns-05        co  770kHz  2022-11-29        0.38
8   BPAE-8unipolar-770kHz-600ns-12        co  770kHz  2022-11-29        0.55
9         BPAE-8unipolar-770kHz-01        co  770kHz  2022-12-04        0.31
10        BPAE-8unipolar-770kHz-06        co  770kHz  2022-12-04        0.45
11      BPAE-8bipolar-1Hz-600ns-04     cross     1Hz  2022-11-29        0.13
12      BPAE-8bipolar-1Hz-600ns-07     cross     1Hz  2022-11-29        0.17
13      BPAE-8bipolar-1Hz-600ns-11     cross     1Hz  2022-11-29        0.14
14            BPAE-8bipolar-1Hz-03     cross     1Hz  2022-12-04        0.27
15            BPAE-8bipolar-1Hz-05     cross     1Hz  2022-12-04        0.14
16            BPAE-8bipolar-1Hz-12     cross     1Hz  2022-12-04        0.10
17   BPAE-8bipolar-770kHz-600ns-01     cross  770kHz  2022-11-29        0.17
18   BPAE-8bipolar-770kHz-600ns-06     cross  770kHz  2022-11-29        0.34
19   BPAE-8bipolar-770kHz-600ns-10     cross  770kHz  2022-11-29        0.15
20         BPAE-8bipolar-770kHz-02     cross  770kHz  2022-12-04        0.06
21         BPAE-8bipolar-770kHz-08     cross  770kHz  2022-12-04        0.08
22         BPAE-8bipolar-770kHz-09     cross  770kHz  2022-12-04        0.18
23         BPAE-8bipolar-770kHz-10     cross  770kHz  2022-12-04        0.07

without the duplicated file: co median 0.49 (range 0.31 to 0.75, n = 10), cross median 0.15 (range 0.06 to 0.34, n = 12); Mann-Whitney U = 119, one-sided p = 5.7e-05
duplicated image: anisotropy 0.13

The duplicated image carries two labels. Its start time (2022-11-29, 16:57) falls in the series of 2022-11-29, whose files are numbered 01 to 12 in order of start time and where number 04 is otherwise missing; the series of 2022-12-04 lacks a number 04 of its own. Its anisotropy is in the range of the cross-directional dishes. It is counted once, as the cross-directional dish BPAE-8bipolar-1Hz-600ns-04, and BPAE-8unipolar-1Hz-04 is dropped. The deposit as labelled is shown as a variant.

In [6]:
print("Files of each day in order of start time (number from the subject name: condition):")
for day, g in df.sort_values("start").groupby("day"):
    print(" ", day, ", ".join(f"{s[-2:]} {t}: {dr} {f}" for s, t, dr, f in zip(g.subject, g.start, g.direction, g.freq)))
DROP = "BPAE-8unipolar-1Hz-04"
dd = df[df.subject != DROP].copy()
print("\nAfter counting the duplicated recording once:")
print(pd.crosstab([dd.direction, dd.freq], dd.day, margins=True))
Files of each day in order of start time (number from the subject name: condition):
  2022-11-29 01 15:59: cross 770kHz, 02 16:17: co 770kHz, 03 16:37: co 1Hz, 04 16:57: cross 1Hz, 04 16:57: co 1Hz, 05 17:17: co 770kHz, 06 17:37: cross 770kHz, 07 17:57: cross 1Hz, 08 18:18: co 1Hz, 09 18:37: co 1Hz, 10 18:57: cross 770kHz, 11 19:17: cross 1Hz, 12 19:37: co 770kHz
  2022-12-04 01 15:30: co 770kHz, 02 15:52: cross 770kHz, 03 16:09: cross 1Hz, 05 16:51: cross 1Hz, 06 17:11: co 770kHz, 07 17:31: co 1Hz, 08 17:51: cross 770kHz, 09 18:11: cross 770kHz, 10 18:31: cross 770kHz, 11 18:55: co 1Hz, 12 19:11: cross 1Hz

After counting the duplicated recording once:
day               2022-11-29  2022-12-04  All
direction freq                               
co        1Hz              3           2    5
          770kHz           3           2    5
cross     1Hz              3           3    6
          770kHz           3           4    7
All                       12          11   23

With the copy counted once the groups hold 5, 6, 5 and 7 dishes (co 1 Hz, cross 1 Hz, co 770 kHz, cross 770 kHz), where the paper reports n = 6 for each. Besides the copy, the 770 kHz groups differ from the paper's: the series of 2022-12-04 has four cross-directional and two co-directional dishes at 770 kHz, so the deposit holds one cross-directional dish more and one co-directional dish fewer than the paper's n = 6. A dish labelled cross-directional that the paper counted as co-directional would explain both. The anisotropy check above does not support relabelling: the four cross-directional 770 kHz dishes of 2022-12-04, the day with the extra dish, have indexes of 0.06 to 0.18, below every co-directional dish (0.31 to 0.75; table above). The labels are therefore used as deposited, and the deposit cannot say whether the paper used a different set of dishes.

In [7]:
# The located footprints and the center ROI in every dish
fig, axes = plt.subplots(4, 6, figsize=(18, 12.5))
pr, pc = epimg.ov_px_um()
for ax, (_, row) in zip(axes.flat, df.iterrows()):
    d = by_subj[row.subject]
    ov = d["overview"]
    core = ov[64:192, 64:192]
    ax.imshow(ov, cmap="gray", vmin=np.percentile(core, 2), vmax=np.percentile(core, 99))
    for f in d["footprints"]:
        ax.add_patch(plt.Circle((f["col"], f["row"]), f["radius_um"] / pc, fill=False, color="r", lw=0.8))
    rr, cc = np.mean([f["row"] for f in d["footprints"]]), np.mean([f["col"] for f in d["footprints"]])
    ax.add_patch(plt.Rectangle((cc - 100 / pc, rr - 100 / pr), 200 / pc, 200 / pr, fill=False, color="c", lw=1))
    ax.set_xlim(cc - 55, cc + 55); ax.set_ylim(rr + 55, rr - 55)
    ax.set_title(f"{row.subject}\n{row.direction} {row.freq}", fontsize=8)
    ax.axis("off")
plt.suptitle("YP overviews (109 x 116 px blocks, about 71 x 75 um); red: located footprints; cyan: 200 um center ROI", fontsize=10)
plt.tight_layout()
plt.show()
print("Display range per panel: 2nd to 99th percentile of the central overview; each panel shows 7.8 x 8.3 mm "
      "around the array center.")
print("parallelogram deviation of the four footprint centers, um:")
print(df.set_index("subject").parallelogram_dev_um.round(0).to_string())
No description has been provided for this image
Display range per panel: 2nd to 99th percentile of the central overview; each panel shows 7.8 x 8.3 mm around the array center.
parallelogram deviation of the four footprint centers, um:
subject
BPAE-8unipolar-1Hz-600ns-03       135.0
BPAE-8unipolar-1Hz-04             310.0
BPAE-8unipolar-1Hz-600ns-08       239.0
BPAE-8unipolar-1Hz-600ns-09       383.0
BPAE-8unipolar-1Hz-07             498.0
BPAE-8unipolar-1Hz-11             333.0
BPAE-8unipolar-770kHz-600ns-02    481.0
BPAE-8unipolar-770kHz-600ns-05    305.0
BPAE-8unipolar-770kHz-600ns-12    282.0
BPAE-8unipolar-770kHz-01          251.0
BPAE-8unipolar-770kHz-06          375.0
BPAE-8bipolar-1Hz-600ns-04        310.0
BPAE-8bipolar-1Hz-600ns-07        304.0
BPAE-8bipolar-1Hz-600ns-11        683.0
BPAE-8bipolar-1Hz-03              410.0
BPAE-8bipolar-1Hz-05              368.0
BPAE-8bipolar-1Hz-12              252.0
BPAE-8bipolar-770kHz-600ns-01     307.0
BPAE-8bipolar-770kHz-600ns-06     369.0
BPAE-8bipolar-770kHz-600ns-10     307.0
BPAE-8bipolar-770kHz-02           250.0
BPAE-8bipolar-770kHz-08           265.0
BPAE-8bipolar-770kHz-09           357.0
BPAE-8bipolar-770kHz-10           441.0

Checking where the center ROI falls¶

The footprints are located on the overview, whose blocks are about 71 x 75 um. As a check, each footprint is located again on a finer image of the array (8 x 8 pixel blocks, 5.2 um) by epimg.refine_footprints, and the array center taken again as the mean of the four refined centers. The table gives, per dish, how far the refined center is from the center used, the mean refined footprint diameter, and the YP inside the 0.5 mm lumen (within 250 um of the footprint center) over the YP from 250 to 600 um, which would differ from 1 if the lumen could be seen and used as a second check of the pixel size. The figure shows the finer image of each array with the 200 um center ROI (cyan) and the refined footprint outlines (red). The refined positions are also run as a variant of every test below.

In [8]:
chk = df.set_index("subject")[["direction", "freq", "parallelogram_dev_um", "refined_offset_um", "refined_diam_um"]].copy()
chk["lumen_over_annulus"] = [np.mean([f["lumen_yp"] / f["annulus_yp"] for f in by_subj[n]["refined"]]) for n in chk.index]
print(chk.round(2).to_string())
chk = chk.drop(DROP)  # the statistics below count the duplicated dish once
print(f"\nstatistics over the {len(chk)} distinct dishes:")
print(f"refined center offset from the center used: median {chk.refined_offset_um.median():.0f} um, "
      f"range {chk.refined_offset_um.min():.0f} to {chk.refined_offset_um.max():.0f} um; "
      f"{(chk.refined_offset_um > 100).sum()} dishes over 100 um (half the ROI side)")
print(f"refined footprint diameter under {epimg.UM_PER_PX} um/px: median {chk.refined_diam_um.median():.0f} um "
      f"(range {chk.refined_diam_um.min():.0f} to {chk.refined_diam_um.max():.0f}); needle outer diameter "
      f"{epimg.FOOTPRINT_OD_UM:.0f} um")
print(f"YP in the lumen over YP 250 to 600 um from the center: median {chk.lumen_over_annulus.median():.2f} "
      f"(range {chk.lumen_over_annulus.min():.2f} to {chk.lumen_over_annulus.max():.2f})")

fig, axes = plt.subplots(4, 6, figsize=(18, 12.5))
for ax, (_, row) in zip(axes.flat, df.iterrows()):
    d = by_subj[row.subject]
    cp = d["crop"]
    b, r0, c0 = cp["block"], cp["r0"], cp["c0"]
    im_ = cp["crop"]
    ax.imshow(im_, cmap="gray", vmin=np.percentile(im_, 2), vmax=np.percentile(im_, 99))
    for f in d["refined"]:
        ax.add_patch(plt.Circle(((f["c"] - c0) / b, (f["r"] - r0) / b), f["diameter_um"] / 2 / epimg.UM_PER_PX / b,
                                fill=False, color="r", lw=0.8))
    h = 100 / epimg.UM_PER_PX / b
    rr, cc = (d["center_px"][0] - r0) / b, (d["center_px"][1] - c0) / b
    ax.add_patch(plt.Rectangle((cc - h, rr - h), 2 * h, 2 * h, fill=False, color="c", lw=1))
    ax.set_title(f"{row.subject}\n{row.direction} {row.freq}, offset {row.refined_offset_um:.0f} um", fontsize=8)
    ax.axis("off")
plt.suptitle("YP, 10.4 um blocks, 7.6 x 7.6 mm around the array center; red: refined footprints; cyan: 200 um center ROI as used", fontsize=10)
plt.tight_layout()
plt.show()
print("Display range per panel: 2nd to 99th percentile of the panel; the whole crop is shown.")
                               direction    freq  parallelogram_dev_um  refined_offset_um  refined_diam_um  lumen_over_annulus
subject                                                                                                                       
BPAE-8unipolar-1Hz-600ns-03           co     1Hz                135.24             112.83          1663.37                0.99
BPAE-8unipolar-1Hz-04                 co     1Hz                309.96              44.86          1611.13                1.00
BPAE-8unipolar-1Hz-600ns-08           co     1Hz                239.08              49.11          1590.47                0.98
BPAE-8unipolar-1Hz-600ns-09           co     1Hz                382.99              70.19          1704.96                1.02
BPAE-8unipolar-1Hz-07                 co     1Hz                498.49              95.87          1695.58                1.05
BPAE-8unipolar-1Hz-11                 co     1Hz                333.28              55.39          1691.73                1.04
BPAE-8unipolar-770kHz-600ns-02        co  770kHz                481.08             101.46          1745.59                0.98
BPAE-8unipolar-770kHz-600ns-05        co  770kHz                305.10             160.64          1802.82                1.01
BPAE-8unipolar-770kHz-600ns-12        co  770kHz                281.51              96.45          1687.51                1.06
BPAE-8unipolar-770kHz-01              co  770kHz                250.89              30.07          1683.01                1.02
BPAE-8unipolar-770kHz-06              co  770kHz                375.41              56.70          1677.52                1.01
BPAE-8bipolar-1Hz-600ns-04         cross     1Hz                309.96              44.86          1611.13                1.00
BPAE-8bipolar-1Hz-600ns-07         cross     1Hz                304.10              59.46          1618.13                0.97
BPAE-8bipolar-1Hz-600ns-11         cross     1Hz                682.75              55.60          1599.86                0.97
BPAE-8bipolar-1Hz-03               cross     1Hz                409.75              31.83          1632.27                1.00
BPAE-8bipolar-1Hz-05               cross     1Hz                367.50              50.45          1631.49                1.03
BPAE-8bipolar-1Hz-12               cross     1Hz                252.25               5.90          1700.30                0.99
BPAE-8bipolar-770kHz-600ns-01      cross  770kHz                307.34              31.75          1625.59                1.01
BPAE-8bipolar-770kHz-600ns-06      cross  770kHz                368.64              52.84          1630.10                1.02
BPAE-8bipolar-770kHz-600ns-10      cross  770kHz                306.83              64.88          1616.71                0.97
BPAE-8bipolar-770kHz-02            cross  770kHz                250.17              41.33          1683.90                0.97
BPAE-8bipolar-770kHz-08            cross  770kHz                264.95              32.02          1655.10                1.02
BPAE-8bipolar-770kHz-09            cross  770kHz                356.96              10.21          1643.27                1.13
BPAE-8bipolar-770kHz-10            cross  770kHz                441.46             192.77          1763.82                1.02

statistics over the 23 distinct dishes:
refined center offset from the center used: median 55 um, range 6 to 193 um; 4 dishes over 100 um (half the ROI side)
refined footprint diameter under 0.65 um/px: median 1663 um (range 1590 to 1803); needle outer diameter 1500 um
YP in the lumen over YP 250 to 600 um from the center: median 1.01 (range 0.97 to 1.13)
No description has been provided for this image
Display range per panel: 2nd to 99th percentile of the panel; the whole crop is shown.

Over the 23 distinct dishes the refined array center lies a median 55 um from the center used (6 to 193 um); in 4 of them it is more than 100 um away, half the ROI side, with the largest offsets in BPAE-8bipolar-770kHz-10 (193 um) and BPAE-8unipolar-770kHz-600ns-05 (161 um). The refined footprints measure a median 1663 um across under 0.65 um per pixel, 11% more than the 1500 um needles; the dark region may extend past the needle wall, so this bounds the pixel size only loosely. If the footprints were exactly 1500 um across, the pixel would be about 0.59 um and the 308-pixel center ROI would cover about 180 to 190 um, smaller than the paper's 200 um; the 150 and 250 um variants cover either direction. The lumen cannot be seen: YP within 250 um of a footprint center is a median 1.01 times the YP from 250 to 600 um (0.97 to 1.13), so it gives no second check of the pixel size.

The tests of Figure 3¶

Every test uses the per-dish values below. The MemBrite intensity in the center ROI rises with the YP intensity across dishes (data check above), so the YP / MemBrite variant divides by a quantity that itself depends on YP uptake. Center ROIs of 150 and 250 um sides (with the footprint background), 25% smaller and larger, test the assumed pixel size. The main analysis counts the duplicated recording once (as cross-directional, 1 Hz). Variants: Welch's test; the footprint background taken over the whole disc inside 0.6 of the footprint radius; YP divided by MemBrite in the center ROI (variant (b) of the claims file's opening comment, after background subtraction); without dishes whose footprint centers are more than 400 um from a parallelogram, where the center ROI may be misplaced; the center and footprint ROIs placed at the refined footprint centers of the check above; and the deposit as labelled, with the duplicated image in both 1 Hz groups. Below the table, each comparison gives for reading (b) the 95% interval of the difference of means and the smallest difference the test would find with 80% power, and the power the test would have if the observed difference were the true one, so that a p >= 0.05 can be read against what the test could have detected.

In [9]:
df["a"] = df.center_yp
df["b"] = df.center_yp - df.fp_yp
df["b_disc"] = df.center_yp - df.fp_disc_yp
df["b_over_mb"] = (df.center_yp - df.fp_yp) / df.center_mb
df["b_roi150"] = [by_subj[n]["center_yp_150"] for n in df.subject] - df.fp_yp
df["b_roi250"] = [by_subj[n]["center_yp_250"] for n in df.subject] - df.fp_yp
df["b_refined"] = [by_subj[n]["center_yp_refined"] - np.mean(by_subj[n]["fp_yp_refined"]) for n in df.subject]
dd = df[df.subject != DROP].copy()

def groups(data, col):
    return {(dr, f): data[(data.direction == dr) & (data.freq == f)][col].to_numpy()
            for dr in ("co", "cross") for f in ("1Hz", "770kHz")}

print("Per-group mean +- s.e. (n), main analysis:")
for col in ("a", "b"):
    g = groups(dd, col)
    print(f"  reading ({col}):  " + ";  ".join(
        f"{dr} {f}: {x.mean():.0f} +- {x.std(ddof=1) / np.sqrt(len(x)):.0f} ({len(x)})" for (dr, f), x in g.items()))

def compare(x_key, y_key, title):
    out = []
    variants = [("main", dd, "a"), ("main", dd, "b"),
                ("whole-disc footprint", dd, "b_disc"), ("YP / MemBrite", dd, "b_over_mb"),
                ("ROI 150 um", dd, "b_roi150"), ("ROI 250 um", dd, "b_roi250"),
                ("refined centers", dd, "b_refined"),
                ("dev <= 400 um", dd[dd.parallelogram_dev_um <= 400], "a"),
                ("dev <= 400 um", dd[dd.parallelogram_dev_um <= 400], "b"),
                ("as deposited", df, "a"), ("as deposited", df, "b")]
    for label, data, col in variants:
        # "as deposited" uses df, where the duplicated image is under both of its labels
        g = groups(data, col)
        x, y = g[x_key], g[y_key]
        t = stats.ttest_ind(x, y)
        w = stats.ttest_ind(x, y, equal_var=False)
        out.append(dict(variant=label, reading=col, n1=len(x), n2=len(y), mean1=x.mean(), mean2=y.mean(),
                        ratio=x.mean() / y.mean(), diff=x.mean() - y.mean(),
                        t=t.statistic, p_student=t.pvalue, p_welch=w.pvalue))
    res = pd.DataFrame(out)
    print(title)
    print(res.round({"mean1": 3, "mean2": 3, "ratio": 2, "diff": 3, "t": 2, "p_student": 4, "p_welch": 4}).to_string(index=False))
    # bounds for the main analysis, reading (b): 95% interval of the difference of means (Student),
    # as a ratio to the second group's mean, and the smallest ratio a two-tailed test at 0.05 would
    # detect with 80% power given the pooled s.d. of these dishes
    g = groups(dd, "b")
    x, y = g[x_key], g[y_key]
    n1, n2 = len(x), len(y)
    sp = np.sqrt(((n1 - 1) * x.var(ddof=1) + (n2 - 1) * y.var(ddof=1)) / (n1 + n2 - 2))
    se = sp * np.sqrt(1 / n1 + 1 / n2)
    dfree = n1 + n2 - 2
    tc = stats.t.ppf(0.975, dfree)
    lo, hi = x.mean() - y.mean() - tc * se, x.mean() - y.mean() + tc * se
    ncs = np.linspace(0, 10, 2001)
    power = stats.nct.sf(tc, dfree, ncs) + stats.nct.cdf(-tc, dfree, ncs)
    mde = ncs[np.argmax(power >= 0.8)] * se
    obs = abs(x.mean() - y.mean()) / se
    pow_obs = stats.nct.sf(tc, dfree, obs) + stats.nct.cdf(-tc, dfree, obs)
    print(f"\nreading (b): 95% interval of the difference {lo:.0f} to {hi:.0f} counts, as a ratio to the "
          f"second group's mean {1 + lo / y.mean():.2f} to {1 + hi / y.mean():.2f}; pooled s.d. {sp:.0f}; "
          f"difference detectable with 80% power {mde:.0f} counts ({mde / y.mean():.0%} of the second "
          "group's mean)")
    print(f"power of the test if the observed difference ({x.mean() - y.mean():.0f} counts, s.e. {se:.0f}, "
          f"df {dfree}) were the true one: {pow_obs:.0%}")
    return res

def by_day(x_key, y_key, col="b"):
    for day, g0 in dd.groupby("day"):
        g = groups(g0, col)
        x, y = g[x_key], g[y_key]
        p = stats.ttest_ind(x, y).pvalue if min(len(x), len(y)) > 1 else np.nan
        print(f"  {day}: reading ({col}) means {x.mean():.0f} (n = {len(x)}) and {y.mean():.0f} (n = {len(y)}), "
              f"ratio {x.mean() / y.mean():.2f}, Student p = {p:.3f}")

def strip(keys, col="b"):
    fig, ax = plt.subplots(figsize=(5, 3.2))
    for i, k in enumerate(keys):
        sub = dd[(dd.direction == k[0]) & (dd.freq == k[1])]
        for day, mk in (("2022-11-29", "o"), ("2022-12-04", "s")):
            v = sub[sub.day == day][col]
            ax.plot(np.full(len(v), i) + (0.08 if mk == "s" else -0.08), v, mk, mfc="none", color="k")
        ax.errorbar(i + 0.25, sub[col].mean(), sub[col].std(ddof=1) / np.sqrt(len(sub)), fmt="_", color="r", ms=14)
    ax.set_xticks(range(len(keys)), [f"{k[0]} {k[1]}" for k in keys])
    ax.set_ylabel(f"YP, reading ({col}), counts"); ax.set_xlim(-0.5, len(keys) - 0.4)
    plt.tight_layout(); plt.show()
    vals = dd[dd.apply(lambda r: (r.direction, r.freq) in keys, axis=1)][col]
    print(f"plotted values range {vals.min():.0f} to {vals.max():.0f}; all points inside the axes; "
          "circles 2022-11-29, squares 2022-12-04, red mean +- s.e.")
Per-group mean +- s.e. (n), main analysis:
  reading (a):  co 1Hz: 974 +- 113 (5);  co 770kHz: 1629 +- 398 (5);  cross 1Hz: 1293 +- 255 (6);  cross 770kHz: 1122 +- 115 (7)
  reading (b):  co 1Hz: 665 +- 107 (5);  co 770kHz: 1301 +- 398 (5);  cross 1Hz: 972 +- 250 (6);  cross 770kHz: 819 +- 102 (7)

fig3-1hz-co-equals-cross¶

At 1 Hz, co-directional and cross-directional (90 degree) paired pulses in the square array have the same electroporation efficiency, measured as YP uptake at the array center.

A claim of absence: reproduced when the two-tailed Student's t-test of co- against cross-directional dishes at 1 Hz gives p >= 0.05 under background reading (b).

In [10]:
r1 = compare(("co", "1Hz"), ("cross", "1Hz"), "co 1 Hz (1) against cross 1 Hz (2)")
print("\nBy experiment day:")
by_day(("co", "1Hz"), ("cross", "1Hz"))
strip([("co", "1Hz"), ("cross", "1Hz")])
co 1 Hz (1) against cross 1 Hz (2)
             variant   reading  n1  n2    mean1    mean2  ratio     diff     t  p_student  p_welch
                main         a   5   6  974.126 1293.271   0.75 -319.145 -1.07     0.3145   0.2907
                main         b   5   6  665.492  971.725   0.68 -306.234 -1.05     0.3225   0.2984
whole-disc footprint    b_disc   5   6  669.298  975.163   0.69 -305.865 -1.05     0.3207   0.2967
       YP / MemBrite b_over_mb   5   6    1.000    0.943   1.06    0.058  0.45     0.6650   0.6564
          ROI 150 um  b_roi150   5   6  674.881  951.917   0.71 -277.036 -0.94     0.3709   0.3457
          ROI 250 um  b_roi250   5   6  667.279  983.087   0.68 -315.808 -1.09     0.3043   0.2807
     refined centers b_refined   5   6  701.580  974.306   0.72 -272.726 -0.88     0.4042   0.3785
       dev <= 400 um         a   4   4  991.098 1256.327   0.79 -265.229 -0.64     0.5475   0.5603
       dev <= 400 um         b   4   4  689.936  933.136   0.74 -243.201 -0.60     0.5717   0.5844
        as deposited         a   6   6 1151.144 1293.271   0.89 -142.127 -0.44     0.6699   0.6704
        as deposited         b   6   6  838.493  971.725   0.86 -133.233 -0.42     0.6824   0.6829

reading (b): 95% interval of the difference -968 to 356 counts, as a ratio to the second group's mean 0.00 to 1.37; pooled s.d. 483; difference detectable with 80% power 922 counts (95% of the second group's mean)
power of the test if the observed difference (-306 counts, s.e. 293, df 9) were the true one: 16%

By experiment day:
  2022-11-29: reading (b) means 669 (n = 3) and 1481 (n = 3), ratio 0.45, Student p = 0.023
  2022-12-04: reading (b) means 660 (n = 2) and 462 (n = 3), ratio 1.43, Student p = 0.494
No description has been provided for this image
plotted values range 254 to 1703; all points inside the axes; circles 2022-11-29, squares 2022-12-04, red mean +- s.e.

Result: reproduced. Under reading (b) the co-directional dishes at 1 Hz (n = 5) average 0.68 of the cross-directional ones (n = 6), and the Student's t-test gives p = 0.32, so the test does not find a difference. Reading (a) (ratio 0.75, p = 0.31), Welch's test and every other variant agree (p from 0.28 to 0.68). The two days disagree: on 2022-11-29 the co-directional mean is 0.45 of the cross-directional one (p = 0.023, n = 3 and 3), on 2022-12-04 it is 1.43 times it (p = 0.49, n = 2 and 3). The group sizes differ from the paper's n = 6, as one co-directional file is a copy of a cross-directional one. The test has little power here: the 95% interval of the difference runs from -968 to 356 counts (a co/cross ratio of 0.00 to 1.37), and with these dishes it would detect with 80% power only a difference of 922 counts, 95% of the cross-directional mean; if the observed difference were the true one, the test would find it with a power of 16%. The outcome follows the criterion; p >= 0.05 here shows that no difference was found, not that the groups are equal. With the center and footprint ROIs at the refined footprint centers, the ratio is 0.72 and p = 0.40.

fig3-770khz-co-enhanced¶

Raising the pulse frequency from 1 Hz to 770 kHz enhances electroporation by co-directional paired pulses, attributed to temporal summation of the induced membrane potential.

Reproduced when the two-tailed Student's t-test of co-directional dishes at 770 kHz against 1 Hz gives p < 0.05 with the 770 kHz mean higher, under each background reading.

In [11]:
r2 = compare(("co", "770kHz"), ("co", "1Hz"), "co 770 kHz (1) against co 1 Hz (2)")
print("\nBy experiment day:")
by_day(("co", "770kHz"), ("co", "1Hz"))
strip([("co", "1Hz"), ("co", "770kHz")])
co 770 kHz (1) against co 1 Hz (2)
             variant   reading  n1  n2    mean1    mean2  ratio    diff    t  p_student  p_welch
                main         a   5   5 1629.415  974.126   1.67 655.289 1.58     0.1517   0.1783
                main         b   5   5 1301.245  665.492   1.96 635.753 1.54     0.1615   0.1890
whole-disc footprint    b_disc   5   5 1301.172  669.298   1.94 631.874 1.53     0.1643   0.1915
       YP / MemBrite b_over_mb   5   5    1.296    1.000   1.30   0.295 1.95     0.0870   0.0941
          ROI 150 um  b_roi150   5   5 1284.177  674.881   1.90 609.296 1.42     0.1936   0.2209
          ROI 250 um  b_roi250   5   5 1278.590  667.279   1.92 611.311 1.58     0.1523   0.1799
     refined centers b_refined   5   5 1163.605  701.580   1.66 462.025 1.39     0.2027   0.2243
       dev <= 400 um         a   4   4 1531.245  991.098   1.54 540.147 1.04     0.3373   0.3637
       dev <= 400 um         b   4   4 1203.933  689.936   1.74 513.998 1.00     0.3577   0.3843
        as deposited         a   5   6 1629.415 1151.144   1.42 478.271 1.13     0.2858   0.3240
        as deposited         b   5   6 1301.245  838.493   1.55 462.752 1.11     0.2973   0.3372

reading (b): 95% interval of the difference -315 to 1586 counts, as a ratio to the second group's mean 0.53 to 3.38; pooled s.d. 652; difference detectable with 80% power 1321 counts (199% of the second group's mean)
power of the test if the observed difference (636 counts, s.e. 412, df 8) were the true one: 27%

By experiment day:
  2022-11-29: reading (b) means 1835 (n = 3) and 669 (n = 3), ratio 2.74, Student p = 0.060
  2022-12-04: reading (b) means 501 (n = 2) and 660 (n = 2), ratio 0.76, Student p = 0.468
No description has been provided for this image
plotted values range 349 to 2599; all points inside the axes; circles 2022-11-29, squares 2022-12-04, red mean +- s.e.

Result: not reproduced. The 770 kHz co-directional mean is higher than the 1 Hz one, 1.67-fold under reading (a) and 1.96-fold under reading (b), but the Student's t-test does not find the difference (p = 0.15 and 0.16, n = 5 and 5), and no variant reaches p < 0.05 (smallest p = 0.087, YP / MemBrite). On 2022-11-29 the 770 kHz mean is 2.74 times the 1 Hz one (p = 0.060, n = 3 and 3); on 2022-12-04 it is 0.76 times it (p = 0.47, n = 2 and 2). The 95% interval of the difference is -315 to 1586 counts (a ratio of 0.53 to 3.38), and the test would detect with 80% power only a difference of 1321 counts, 199% of the 1 Hz mean. If the observed difference (636 counts, s.e. 412) were the true one, the test would find it with a power of 27%, so a p >= 0.05 was the likely result with these dishes even if the effect is as large as observed. At the refined footprint centers the ratio is 1.66, p = 0.20.

fig3-770khz-cross-same-as-1hz¶

Cross-directional paired pulses at 770 kHz are just as effective as at 1 Hz; charging two different regions of the membrane does not cause summation.

A claim of absence: reproduced when the Student's t-test of cross-directional dishes at 770 kHz against 1 Hz gives p >= 0.05 under reading (b).

In [12]:
r3 = compare(("cross", "770kHz"), ("cross", "1Hz"), "cross 770 kHz (1) against cross 1 Hz (2)")
print("\nBy experiment day:")
by_day(("cross", "770kHz"), ("cross", "1Hz"))
strip([("cross", "1Hz"), ("cross", "770kHz")])
cross 770 kHz (1) against cross 1 Hz (2)
             variant   reading  n1  n2    mean1    mean2  ratio     diff     t  p_student  p_welch
                main         a   7   6 1122.139 1293.271   0.87 -171.132 -0.64     0.5323   0.5598
                main         b   7   6  818.815  971.725   0.84 -152.910 -0.60     0.5613   0.5899
whole-disc footprint    b_disc   7   6  820.155  975.163   0.84 -155.008 -0.61     0.5541   0.5828
       YP / MemBrite b_over_mb   7   6    1.074    0.943   1.14    0.131  0.92     0.3758   0.3707
          ROI 150 um  b_roi150   7   6  778.153  951.917   0.82 -173.764 -0.67     0.5182   0.5473
          ROI 250 um  b_roi250   7   6  835.945  983.087   0.85 -147.142 -0.57     0.5830   0.6077
     refined centers b_refined   7   6  764.582  974.306   0.78 -209.724 -0.77     0.4593   0.4930
       dev <= 400 um         a   6   4 1143.832 1256.327   0.91 -112.495 -0.32     0.7568   0.7996
       dev <= 400 um         b   6   4  833.241  933.136   0.89  -99.896 -0.29     0.7755   0.8175
        as deposited         a   7   6 1122.139 1293.271   0.87 -171.132 -0.64     0.5323   0.5598
        as deposited         b   7   6  818.815  971.725   0.84 -152.910 -0.60     0.5613   0.5899

reading (b): 95% interval of the difference -715 to 409 counts, as a ratio to the second group's mean 0.26 to 1.42; pooled s.d. 459; difference detectable with 80% power 786 counts (81% of the second group's mean)
power of the test if the observed difference (-153 counts, s.e. 255, df 11) were the true one: 9%

By experiment day:
  2022-11-29: reading (b) means 1051 (n = 3) and 1481 (n = 3), ratio 0.71, Student p = 0.073
  2022-12-04: reading (b) means 645 (n = 4) and 462 (n = 3), ratio 1.40, Student p = 0.362
No description has been provided for this image
plotted values range 254 to 1703; all points inside the axes; circles 2022-11-29, squares 2022-12-04, red mean +- s.e.

Result: reproduced. Under reading (b) the cross-directional dishes at 770 kHz (n = 7) average 0.84 of those at 1 Hz (n = 6), Student's p = 0.56; the test does not find a difference, and no variant does (p from 0.37 to 0.82). By day the ratio is 0.71 on 2022-11-29 (p = 0.073) and 1.40 on 2022-12-04 (p = 0.36). The 95% interval of the difference is -715 to 409 counts (a ratio of 0.26 to 1.42); the test would detect with 80% power only a difference of 786 counts, 81% of the 1 Hz mean (power 9% at the observed difference), so p >= 0.05 shows that no difference was found, not that the groups are equal. At the refined footprint centers the ratio is 0.78, p = 0.46.

fig3-770khz-co-above-cross¶

At 770 kHz, co-directional paired pulses electroporate more than cross-directional ones.

Reproduced when the Student's t-test of co- against cross-directional dishes at 770 kHz gives p < 0.05 with the co-directional mean higher, under each background reading.

In [13]:
r4 = compare(("co", "770kHz"), ("cross", "770kHz"), "co 770 kHz (1) against cross 770 kHz (2)")
print("\nBy experiment day:")
by_day(("co", "770kHz"), ("cross", "770kHz"))
strip([("co", "770kHz"), ("cross", "770kHz")])
co 770 kHz (1) against cross 770 kHz (2)
             variant   reading  n1  n2    mean1    mean2  ratio    diff    t  p_student  p_welch
                main         a   5   7 1629.415 1122.139   1.45 507.277 1.42     0.1859   0.2786
                main         b   5   7 1301.245  818.815   1.59 482.430 1.37     0.2004   0.2986
whole-disc footprint    b_disc   5   7 1301.172  820.155   1.59 481.017 1.37     0.2021   0.3001
       YP / MemBrite b_over_mb   5   7    1.296    1.074   1.21   0.222 1.37     0.2016   0.2119
          ROI 150 um  b_roi150   5   7 1284.177  778.153   1.65 506.024 1.37     0.2002   0.2971
          ROI 250 um  b_roi250   5   7 1278.590  835.945   1.53 442.645 1.31     0.2193   0.3106
     refined centers b_refined   5   7 1163.605  764.582   1.52 399.023 1.37     0.2021   0.2848
       dev <= 400 um         a   4   6 1531.245 1143.832   1.34 387.413 0.91     0.3912   0.5003
       dev <= 400 um         b   4   6 1203.933  833.241   1.44 370.693 0.88     0.4050   0.5169
        as deposited         a   5   7 1629.415 1122.139   1.45 507.277 1.42     0.1859   0.2786
        as deposited         b   5   7 1301.245  818.815   1.59 482.430 1.37     0.2004   0.2986
reading (b): 95% interval of the difference -302 to 1267 counts, as a ratio to the second group's mean 0.63 to 2.55; pooled s.d. 601; difference detectable with 80% power 1094 counts (134% of the second group's mean)
power of the test if the observed difference (482 counts, s.e. 352, df 10) were the true one: 24%

By experiment day:
  2022-11-29: reading (b) means 1835 (n = 3) and 1051 (n = 3), ratio 1.75, Student p = 0.139
  2022-12-04: reading (b) means 501 (n = 2) and 645 (n = 4), ratio 0.78, Student p = 0.382
No description has been provided for this image
plotted values range 349 to 2599; all points inside the axes; circles 2022-11-29, squares 2022-12-04, red mean +- s.e.

Result: not reproduced. The co-directional mean at 770 kHz is the higher, 1.45-fold under reading (a) and 1.59-fold under reading (b), but the Student's t-test does not find the difference (p = 0.19 and 0.20, n = 5 and 7), nor does any variant (p from 0.19 to 0.52). By day the ratio is 1.75 on 2022-11-29 (p = 0.14) and 0.78 on 2022-12-04 (p = 0.38). The 95% interval of the difference is -302 to 1267 counts (a ratio of 0.63 to 2.55), and the test would detect with 80% power only a difference of 1094 counts, 134% of the cross-directional mean; at the observed difference (482 counts, s.e. 352) its power is 24%. At the refined footprint centers the ratio is 1.52, p = 0.20.

fig3-no-area-dependence¶

Unlike with long pulses, YP uptake after nsEP does not depend on whether the same or a larger area of the membrane is electroporated.

Judged by the outcome of fig3-1hz-co-equals-cross. The Figure 2 evidence the Discussion also cites (fig2-facilitation-plateau) is not in this dataset and is not tested here.

In [14]:
row = r1[(r1.variant == "main") & (r1.reading == "b")].iloc[0]
print(f"fig3-1hz-co-equals-cross, reading (b): co/cross ratio {row.ratio:.2f}, Student p = {row.p_student:.3f}, "
      f"n = {row.n1} and {row.n2}")
row3 = r3[(r3.variant == "main") & (r3.reading == "b")].iloc[0]
print(f"for reference, cross 770 kHz against cross 1 Hz: ratio {row3.ratio:.2f}, p = {row3.p_student:.3f}")
fig3-1hz-co-equals-cross, reading (b): co/cross ratio 0.68, Student p = 0.323, n = 5 and 6
for reference, cross 770 kHz against cross 1 Hz: ratio 0.84, p = 0.561

Result: reproduced. It follows fig3-1hz-co-equals-cross, where the test does not find a difference between co- and cross-directional pulses at 1 Hz (ratio 0.68, p = 0.32). It carries the same limit: the test would detect with 80% power only a difference of 95% of the cross-directional mean. The Figure 2 evidence the Discussion also cites was not tested.

Blocked claims¶

The dataset holds only the square-array experiment of Figure 3. The triangular-array experiments of Figure 2 (32 ROIs along the bisecting line, single and paired pulses at 1 Hz and 833 kHz) and Figure 4, and the FluoVolt strobe imaging of CHO-K1 cells in Figure 5, are not in it. The paper's Data Availability Statement says the data are available on request, but three other dandisets with the same contributors as 000632 name this paper's DOI in their metadata and match those experiments by title and size (all printed below). The claims file scopes every claim to dandiset 000632 (data: dataset), so these claims are not tested on the other dandisets here; they are blocked as other, and a reader is asked (questions/000632.yaml) whether to mark them data: elsewhere with these dandisets as where. Even then the Figure 2 claims may stay blocked, as the angle of each ROI comes from a field simulation the paper does not give (opening comment of the claims file).

In [15]:
import requests
for ds in ("000632", "000631", "000633", "000634"):
    j = requests.get(f"{epimg.API}/dandisets/{ds}/", timeout=60).json()
    v = j["most_recent_published_version"]
    meta = requests.get(f"{epimg.API}/dandisets/{ds}/versions/{v['version']}/", timeout=60).json()
    print(f"DANDI:{ds} version {v['version']}: {v['name']!r}, {v['asset_count']} assets, {v['size'] / 1e9:.1f} GB")
    print("   contributors:", "; ".join(c.get("name", "").strip() for c in meta.get("contributor", [])))
    for r in meta.get("relatedResource", []):
        print(f"   {r.get('relation')}: {r.get('url')}")
    print("   ", meta.get("description", "")[:240].replace("\n", " "), "...")
DANDI:000632 version 0.231012.1930: 'Electroporation efficiency of co-directional and cross-directional paired pulses', 24 assets, 42.7 GB
   contributors: Silkuniene, Giedre; Kim, Vitalii; Semenov, Iurii; Pakhomov, Andrei; National Institutes of Health
   dcite:IsPublishedIn: https://doi.org/10.3390/ijms241310921
    The efficiency of electroporation was evaluated in an experiment involving co-directional and cross-directional paired pulses using a triangular electrode array. BPAE cell monolayers were exposed to trains of four paired pulses, each consis ...
DANDI:000631 version 0.231012.1508: 'Effect of the electric field vector change on the electroporation efficiency of paired-pulse trains compared to single-pulse trains', 15 assets, 18.4 GB
   contributors: Silkuniene, Giedre; Kim, Vitalii; Semenov, Iurii; Pakhomov, Andrei; National Institutes of Health
   dcite:IsPublishedIn: https://doi.org/10.3390/ijms241310921
    In the study, the impact of varying the electric field vector on the effectiveness of electroporation was examined, utilizing both paired-pulse trains, consisting of 5 pulses at 600 + 600 ns, and single-pulse trains with 5 pulses at 600 ns. ...
DANDI:000633 version 0.231013.2226: 'The difference in electroporation patterns produced by a train of single pulses and a train of paired pulses', 2 assets, 2.1 GB
   contributors: Silkuniene, Giedre; Kim, Vitalii; Semenov, Iurii; Pakhomov, Andrei; National Institutes of Health
   dcite:IsPublishedIn: https://doi.org/10.3390/ijms241310921
    The difference in electroporation patterns produced by a train of single pulses and a train of paired pulses. Supported by NIH 1R21EY034258 ...
DANDI:000634 version 0.231005.1820: 'Cell Membrane Charging by Co- and Counter-Directional ns electrical pulses (nsEP)', 10 assets, 1.0 GB
   contributors: Silkuniene, Giedre; Kim, Vitalii; Semenov, Iurii; Pakhomov, Andrei; National Institutes of Health
   dcite:IsPublishedIn: https://www.mdpi.com/1422-0067/24/13/10921
    Strobe photography, used at nanosecond resolution, captures the dynamic process of cell membrane charging and relaxation kinetics in cells treated with FluoVolt dye and exposed to electric pulses. This study uses a triangular electrode arra ...

Summary¶

claim outcome
fig3-1hz-co-equals-cross reproduced
fig3-770khz-co-enhanced not reproduced
fig3-770khz-cross-same-as-1hz reproduced
fig3-770khz-co-above-cross not reproduced
fig3-no-area-dependence reproduced
fig2-180-cancellation blocked (other: data in another dandiset)
fig2-crossover-angle blocked (other: data in another dandiset)
fig2-facilitation-plateau blocked (other: data in another dandiset)
fig2-60-fold-co-counter blocked (other: data in another dandiset)
fig4-unipolar-pattern blocked (other: data in another dandiset)
fig5-base-sawtooth blocked (other: data in another dandiset)
fig5-apex-larger-polarization blocked (other: data in another dandiset)

A failure to reproduce here may be a failure of this attempt, not of the paper: see the reasoning and the differences from the paper's method.

How the analysis differs from the paper's. The paper measured intensities with MetaMorph; here the center ROI is placed automatically at the mean of the four located footprint centers, and the footprint background is a 200 um ROI at each footprint center, a choice the paper does not state. The pixel size is not in the files and 0.65 um is assumed (the located footprints measure a median 1.59 mm across under it, against the needles' 1.5 mm; if the footprints are 1.5 mm, the center ROI covers somewhat less than 200 um, and ROIs of 150 and 250 um give the same outcomes). Uptake differs between the two experiment days (for example co-directional 770 kHz, reading (b), 1835 on 2022-11-29 against 501 on 2022-12-04), and in each of the four comparisons the two days point in opposite directions. The groups have 5, 6, 5 and 7 dishes (co 1 Hz, cross 1 Hz, co 770 kHz, cross 770 kHz) where the paper reports n = 6 for each. With so few dishes and this spread the tests have little power: a difference would be found with 80% power only when it is 81% to 199% of the reference group's mean, so the two absence claims are reproduced in the sense that no difference was found. For the two not reproduced differences (1.96-fold and 1.59-fold), the tests would have found differences as large as those observed with a power of only 27% and 24%.

What was found about the dataset.

  • One recording is deposited twice under two conditions: sub-BPAE-8unipolar-1Hz-04 (Co-directional 1 Hz) and sub-BPAE-8bipolar-1Hz-600ns-04 (Cross-directional 1 Hz) have identical chunk indexes, images and start times.
  • By label, the groups hold 6, 6, 5 and 7 files (co 1 Hz, cross 1 Hz, co 770 kHz, cross 770 kHz), not 6 each as in the paper; with the copy counted once, 5, 6, 5 and 7.
  • The dataset description and every session description say the electrodes were in a triangular array; the images and the paper show a square array of four.
  • The files give no pixel size, and the channels are named only in a free-text description.
  • The subject names carry undocumented tokens ("8bipolar", "8unipolar", "600ns"); "600ns" marks the files of 2022-11-29.
  • The dataset holds only Figure 3. The data of Figures 2, 4 and 5 are in dandisets 000631, 000633 and 000634, which name the paper; 000632 does not point to them.