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.
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¶
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).
# 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.
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.
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.
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.
# 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())
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.
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)
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.
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).
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
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.
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
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).
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
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.
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
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.
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).
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) andsub-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.