Reproducing Pakhomov et al. (2023) from dandiset 001025¶

Paper. Pakhomov, Gudvangen, Mangalanathan, Kondratiev, Redondo and Semenov (2023), "Next generation CANCAN focusing for remote stimulation by nanosecond electric pulses", Bioelectrochemistry 152, 108437, doi:10.1016/j.bioelechem.2023.108437.

Dataset. DANDI:001025, "The impact of voltage on CANCAN focusing of electroporation using four-electrode arrays of varying sizes", version 0.240605.1826 (doi:10.48324/dandi.001025/0.240605.1826). The paper cites no version of the dataset. This is the only published version, and its 21 assets are the same as the draft's (same count and size), so it is the most recent published version and the one pinned.

Text read. The claims file was written from the accepted author manuscript in PubMed Central (PMC10247516), with no supplement and no linked code. This attempt read the same text.

What the dataset holds. One stitched YO-PRO-1 fluorescence image per dish, taken after the nsEP exposure, with the first-pulse voltage and the number of packets per electrode in the session description. There are no ROI measurements, electrode positions or pixel size, so the notebook finds the electrodes in each image and places the paper's ROIs itself (helper cancan.py). The images are the experiments of Fig. 7: Packet 2 rotated once (1Px4) at 2.4 to 5.6 kV, and rotated ten times (10Px4) at 4.8 and 6.4 kV.

Claim Tested here
fig4-unipolar-strongest blocked, data_missing (single-cell time-lapse data not in the dandiset)
fig4-bipolar-cancellation blocked, data_missing (as above)
fig5a-center-enhanced-without-rotation blocked, other (no 40-packet or 40-train dishes here; the Fig. 5 images appear to be in dandiset 000686)
fig5bc-rotation-focuses-center blocked, other (no 1Px40 or 1Tx40 dishes here; dandiset 000686)
fig6a-more-packets-more-uptake blocked, other (no 5Px4 dishes; 1Px4 and 10Px4 are on different arrays here; dandiset 000875)
fig6b-normalized-center-by-packets blocked, other (as above)
fig7a-amplitude-scales-uptake tested
fig7b-targeting-independent-of-amplitude tested
fig7cd-holds-for-large-array tested (two of the paper's voltages, see below)
discussion-independent-of-array-size tested (follows from the two above)
fig8a-packets-differ-near-electrodes blocked, other (no Packet 1 or Packet 3 dishes here; dandiset 001027)
fig8b-packet2-best-targeting blocked, other (as above)
fig9-gap-boosts-targeting blocked, other (no dishes with the array above the monolayer here; dandiset 001028)
fig9c-restricted-to-center blocked, other (as above)

The images of Figs. 5, 6, 8 and 9 appear, from their titles and descriptions (printed below), to be in four other dandisets that cite the paper (000686, 000875, 001027 and 001028); the claims file gives every claim as data from this dandiset, so those claims are blocked here with other. The 4.0 kV 1Px4 and 6.4 kV 10Px4 dishes here match the Packet 2 conditions of Figs. 8 and 9 (for Fig. 8, if it used the small array, which its legend does not say); their values are set against the paper's in a section after the tested claims, without deciding those claims.

Random numbers are drawn only in the fig7b section, for a simulation of the chance of a false positive among its tests, from its own generator with a fixed seed (2023).

In [1]:
import itertools
import time

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

import cancan as cc

pd.set_option("display.width", 250)
pd.set_option("display.max_rows", 300)
pd.set_option("display.max_columns", 40)
print("dandiset", cc.DANDISET, "version", cc.VERSION)
dandiset 001025 version 0.240605.1826

Loading¶

Each image is streamed from the archive: the compressed HDF5 chunks of the YP channel (channel 0, "FITC") are fetched with range requests and reduced, as they arrive, to sums and counts of non-zero pixels in 4 x 4 blocks; nothing is written to disk. For each dish the electrodes are located on a 16 x 16 block image, and pixel sums are binned along the two diagonals of the array, from which every ROI layout below is computed, and the radius of each needle footprint is measured. This cell takes about 12 minutes.

In [2]:
assets = cc.list_assets()
res = {}
t0 = time.time()
for a in assets:
    url = cc.s3_url(a["asset_id"])
    f = h5py.File(remfile.File(url), "r")
    ims = f["acquisition/ScanningImaging"]
    info = dict(
        path=a["path"],
        subject=f["general/subject/subject_id"][()].decode(),
        start=f["session_start_time"][()].decode()[:19],
        description=f["session_description"][()].decode(),
        imaging=ims.attrs["description"],
        resolution_attr=float(ims["data"].attrs["resolution"]),
        unit=ims["data"].attrs["unit"],
        n_channels=ims["data"].shape[0],
    )
    f.close()
    info["kV"], info["reps"] = cc.parse_description(info["description"])
    s, n, st = cc.read_image(url, channel=0)
    img, zero = cc.coarse(s, n)
    size, pts, scores = cc.find_electrodes(img, zero)
    # strips wide and long enough for every ROI layout and pixel-size reading used below
    span = cc.SPAN_MM[size] * 1000 / cc.CAMERA_UM_PER_PX / cc.F
    fp = cc.footprint_radius(s, n, pts)  # dark footprint of each needle, to check the pixel size
    hists, bin_px = cc.strip_histograms(s, n, pts, 1.25 * span, 1.25 * span / (cc.N_ROI[size] / 2) + 2)
    res[a["path"]] = dict(info, size=size, pts=pts, scores=scores, hists=hists, fp=fp, img=img, zero=zero, stats=st,
                          nonzero_mean=s.sum() / max(n.sum(), 1))
    del s, n
print(f"{len(res)} images streamed and reduced in {(time.time() - t0) / 60:.1f} min")
21 images streamed and reduced in 11.5 min

Data check¶

In [3]:
rows = []
for p, r in res.items():
    d1 = np.hypot(*(r["pts"][0] - r["pts"][2])) * cc.FD
    d2 = np.hypot(*(r["pts"][1] - r["pts"][3])) * cc.FD
    rows.append(dict(
        file=p.split("/")[1].replace("_image.nwb", ""), subject=r["subject"], start=r["start"],
        kV=r["kV"], packets_per_electrode=r["reps"], channels=r["n_channels"],
        shape=r["stats"]["shape"], zero_frac=round(r["stats"]["zero_px"] / np.prod(r["stats"]["shape"][1:]), 3),
        max=r["stats"]["max"], mean_nonzero=round(r["nonzero_mean"], 1),
        array=r["size"], fit_small=round(r["scores"]["small"], 2), fit_large=round(r["scores"]["large"], 2),
        diag1_px=round(d1), diag2_px=round(d2),
    ))
dishes = pd.DataFrame(rows).sort_values(["packets_per_electrode", "kV", "start"]).reset_index(drop=True)
print(dishes.to_string())

print("\nDishes per condition (array found from the electrode spacing):")
print(dishes.groupby(["packets_per_electrode", "array", "kV"]).size().rename("dishes").to_string())
print("\nFiles per subject id:", dishes.subject.value_counts().to_dict())
print("Distinct subject ids:", dishes.subject.nunique(), "for", len(dishes), "files")
print("\nImaging descriptions:")
for k, v in pd.Series([r["imaging"].split("Additional metadata: ")[-1] for r in res.values()]).value_counts().items():
    print(f"  {v} files: {k}")
print("ImageSeries unit:", {r["unit"] for r in res.values()}, "; 'resolution' attribute:", {r["resolution_attr"] for r in res.values()})

# the array size is not in the metadata; check the geometric classification against the protocol
ok = ((dishes.packets_per_electrode == 1) & (dishes.array == "small")) | ((dishes.packets_per_electrode == 10) & (dishes.array == "large"))
print("\nEvery 1Px4 dish is on the small array and every 10Px4 dish on the large one (as Fig. 7 legend):", bool(ok.all()))

# pixel size: two readings
large = dishes[dishes.array == "large"]
small = dishes[dishes.array == "small"]
large_diag_px = large[["diag1_px", "diag2_px"]].values.mean()
small_diag_px = small[["diag1_px", "diag2_px"]].values.mean()
um = {"camera": cc.CAMERA_UM_PER_PX, "diag8.4": cc.LARGE_DIAGONAL_MM * 1000 / large_diag_px}
print(f"\nMean centre-to-centre diagonal: large {large_diag_px:.0f} px (sd {large[['diag1_px','diag2_px']].values.std():.0f}), "
      f"small {small_diag_px:.0f} px (sd {small[['diag1_px','diag2_px']].values.std():.0f}); ratio {large_diag_px / small_diag_px:.2f} "
      "(paper: 'approximately a 2-fold size difference')")
for k, v in um.items():
    print(f"  pixel size {k}: {v:.3f} um -> large diagonal {large_diag_px * v / 1000:.2f} mm, small {small_diag_px * v / 1000:.2f} mm, "
          f"image width {20480 * v / 1000:.1f} mm")
print("\nWhere the paper's ROI span ends against the needles (radius 0.75 mm, Section 2.3) and the paper's exclusion zone"
      " (5% of the diagonal, Section 2.4):")
for k, v in um.items():
    for size, dpx in (("small", small_diag_px), ("large", large_diag_px)):
        diag_mm = dpx * v / 1000
        edge = diag_mm / 2 - cc.NEEDLE_RADIUS_MM
        print(f"  {k}, {size} array: needle edge {edge:.2f} mm from the centre; span ends at {cc.SPAN_MM[size]} mm, "
              f"{edge - cc.SPAN_MM[size]:+.2f} mm from the needle edge (negative: on the needle); "
              f"exclusion zone {cc.EXCLUDE_FRAC * diag_mm:.2f} mm")

# needle footprints: radius of the dark disk around each electrode centre, against the 0.75 mm needle radius
fpr = pd.DataFrame([dict(file=p.split("/")[1].replace("_image.nwb", ""), array=r["size"], kV=r["kV"], electrode=k,
                         r_mm_camera=x["r_px"] * cc.F * cc.CAMERA_UM_PER_PX / 1000, inside=x["inside"], outside=x["outside"])
                    for p, r in res.items() for k, x in enumerate(r["fp"])])
print("\nNeedle footprint radius at the camera scale (mm), per dish (4 electrodes), with the median a.u. inside and around:")
print(fpr.groupby(["array", "kV", "file"]).agg(r_min=("r_mm_camera", "min"), r_median=("r_mm_camera", "median"),
                                              r_max=("r_mm_camera", "max"), inside=("inside", "median"),
                                              outside=("outside", "median")).round(3).to_string())
print("Not found:", int(fpr.r_mm_camera.isna().sum()), "of", len(fpr))
for size, g in fpr.groupby("array"):
    med = g.r_mm_camera.median()
    print(f"  {size} array: median radius {med:.3f} mm (IQR {g.r_mm_camera.quantile(0.25):.3f} to {g.r_mm_camera.quantile(0.75):.3f}), "
          f"so a 0.75 mm needle radius implies {cc.CAMERA_UM_PER_PX * cc.NEEDLE_RADIUS_MM / med:.3f} um per pixel "
          f"(camera reading 0.650, 8.4 mm reading {um['diag8.4']:.3f})")
# per-array scale implied by the footprints, used only as a variant below
um["footprint"] = {size: cc.CAMERA_UM_PER_PX * cc.NEEDLE_RADIUS_MM / g.r_mm_camera.median() for size, g in fpr.groupby("array")}
for size, dpx in (("small", small_diag_px), ("large", large_diag_px)):
    v = um["footprint"][size]
    diag_mm = dpx * v / 1000
    edge = diag_mm / 2 - cc.NEEDLE_RADIUS_MM
    print(f"  footprint scale {v:.3f} um, {size} array: diagonal {diag_mm:.2f} mm (paper, large array: 8.4 mm); needle edge {edge:.2f} mm "
          f"from the centre, span ends {edge - cc.SPAN_MM[size]:+.2f} mm from it; exclusion zone {cc.EXCLUDE_FRAC * diag_mm:.2f} mm")

# duplicates: correlation of the 16x16 block images over the area imaged in both
names = list(res)
cmax = []
for i, j in itertools.combinations(range(len(names)), 2):
    a, b = res[names[i]], res[names[j]]
    m = (a["zero"] < 0.01) & (b["zero"] < 0.01)
    cmax.append((np.corrcoef(a["img"][m], b["img"][m])[0, 1], names[i].split("/")[1], names[j].split("/")[1]))
cmax.sort(reverse=True)
print("\nHighest correlations between two dishes' images (1 would be a duplicate):")
for c in cmax[:3]:
    print(f"  {c[0]:.3f} {c[1]} {c[2]}")
print("Identical mean intensities:", dishes.mean_nonzero.duplicated().sum())

# dark floor by imaging session (0.1st percentile of imaged pixels, 16x16 blocks)
dishes["floor_p0.1"] = [round(float(np.percentile(res[p]["img"][res[p]["zero"] < 0.01], 0.1)), 1) for p in
                        [next(q for q in res if q.split("/")[1].replace("_image.nwb", "") == f) for f in dishes.file]]
dishes["date"] = dishes.start.str[:10]
print("\nDark floor of the images (0.1st percentile, a.u.) by imaging date and condition:")
print(dishes.groupby(["date", "packets_per_electrode", "kV"])["floor_p0.1"].agg(["count", "min", "max"]).to_string())
print("\nNo data stream has a clock: each file is one image at one time point, so there is no timing to check.")
                  file subject                start   kV  packets_per_electrode  channels              shape  zero_frac    max  mean_nonzero  array  fit_small  fit_large  diag1_px  diag2_px
0    sub-15_obj-t7o4u3      15  2021-04-02T14:07:47  2.4                      1         1  (1, 20480, 20480)      0.259  65438         167.6  small      -0.25      -0.21      7904      7306
1               sub-16      16  2021-04-02T14:09:23  2.4                      1         1  (1, 20480, 20480)      0.259  63143         176.0  small      -0.18      -0.14      7431      7457
2               sub-17      17  2021-04-02T14:10:48  2.4                      1         1  (1, 20480, 20480)      0.259  65435         185.7  small      -0.21      -0.12      7976      7538
3               sub-21      21  2021-04-02T14:12:17  2.4                      1         1  (1, 20480, 20480)      0.259  65441         154.3  small      -0.26      -0.17      7740      7333
4               sub-23      23  2021-02-16T17:03:39  3.2                      1         1  (1, 20480, 20480)      0.259  65443         317.5  small      -0.38      -0.20      7850      7423
5               sub-24      24  2021-02-16T17:09:42  3.2                      1         1  (1, 20480, 20480)      0.259  65442         334.3  small      -0.43      -0.23      7831      7707
6   sub-25_obj-1of0oop      25  2021-02-16T17:15:04  3.2                      1         1  (1, 20480, 20480)      0.259  65438         263.7  small      -0.38      -0.23      7920      8070
7               sub-03      03  2021-02-16T13:25:04  4.0                      1         1  (1, 20480, 20480)      0.259  65440         275.6  small      -0.50      -0.28      7804      7579
8               sub-08      08  2021-02-16T14:04:37  4.0                      1         1  (1, 20480, 20480)      0.259  65447         346.4  small      -0.47      -0.35      7835      7501
9               sub-14      14  2021-02-16T15:06:41  4.0                      1         1  (1, 20480, 20480)      0.259  65444         371.3  small      -0.42      -0.34      7902      7781
10              sub-05      05  2021-02-17T14:44:18  4.8                      1         1  (1, 20480, 20480)      0.259  65442         350.0  small      -0.55      -0.24      7852      7692
11              sub-12      12  2021-04-02T14:02:07  4.8                      1         1  (1, 20480, 20480)      0.259  65443         431.3  small      -0.58      -0.28      7750      7920
12              sub-13      13  2021-04-02T14:03:26  4.8                      1         1  (1, 20480, 20480)      0.259  65443         434.3  small      -0.60      -0.33      7899      7989
13  sub-25_obj-17bqgcb      25  2021-03-11T16:29:39  5.6                      1         1  (1, 20480, 20480)      0.259  65444         508.7  small      -0.60      -0.34      7785      7844
14              sub-26      26  2021-03-11T16:34:15  5.6                      1         1  (1, 20480, 20480)      0.259  65443         505.4  small      -0.52      -0.24      7879      7830
15              sub-27      27  2021-03-11T16:40:11  5.6                      1         1  (1, 20480, 20480)      0.258  65444         452.4  small      -0.63      -0.43      7852      8236
16              sub-02      02  2021-05-20T12:49:16  4.8                     10         2  (2, 20480, 20480)      0.259  65445         605.5  large      -0.19      -0.29     14296     14578
17   sub-15_obj-znj27w      15  2021-05-20T15:49:35  4.8                     10         2  (2, 20480, 20480)      0.259  65438         763.0  large      -0.30      -0.67     14240     14069
18              sub-04      04  2021-05-20T13:17:02  6.4                     10         2  (2, 20480, 20480)      0.258  65445        1320.8  large      -0.37      -0.81     14211     14011
19              sub-07      07  2021-05-20T14:40:14  6.4                     10         2  (2, 20480, 20480)      0.257  65446        1488.2  large      -0.37      -0.83     14244     14035
20              sub-11      11  2021-05-20T15:15:58  6.4                     10         2  (2, 20480, 20480)      0.258  65438        1347.3  large      -0.35      -0.80     14260     14099

Dishes per condition (array found from the electrode spacing):
packets_per_electrode  array  kV 
1                      small  2.4    4
                              3.2    3
                              4.0    3
                              4.8    3
                              5.6    3
10                     large  4.8    2
                              6.4    3

Files per subject id: {'15': 2, '25': 2, '16': 1, '17': 1, '21': 1, '23': 1, '24': 1, '03': 1, '08': 1, '14': 1, '05': 1, '12': 1, '13': 1, '26': 1, '27': 1, '02': 1, '04': 1, '07': 1, '11': 1}
Distinct subject ids: 19 for 21 files

Imaging descriptions:
  9 files: Microscope: Unknown microscope, Detector: Hamamatsu Hamamatsu ORCA-Flash4.0, Objective: Olympus IX3 Nosepiece NA=0.40000000000000002 Mag=10, Channels: FITC
  7 files: Microscope: Unknown microscope, Detector: Unknown detector, Objective: Unknown objective, Channels: Unknown channels
  5 files: Microscope: Unknown microscope, Detector: Hamamatsu Hamamatsu ORCA-Flash4.0, Objective: Olympus IX3 Nosepiece NA=0.40000000000000002 Mag=10, Channels: FITC, DAPI
ImageSeries unit: {'n.a.'} ; 'resolution' attribute: {96.0, 1.0}

Every 1Px4 dish is on the small array and every 10Px4 dish on the large one (as Fig. 7 legend): True

Mean centre-to-centre diagonal: large 14204 px (sd 158), small 7763 px (sd 213); ratio 1.83 (paper: 'approximately a 2-fold size difference')
  pixel size camera: 0.650 um -> large diagonal 9.23 mm, small 5.05 mm, image width 13.3 mm
  pixel size diag8.4: 0.591 um -> large diagonal 8.40 mm, small 4.59 mm, image width 12.1 mm

Where the paper's ROI span ends against the needles (radius 0.75 mm, Section 2.3) and the paper's exclusion zone (5% of the diagonal, Section 2.4):
  camera, small array: needle edge 1.77 mm from the centre; span ends at 1.65 mm, +0.12 mm from the needle edge (negative: on the needle); exclusion zone 0.25 mm
  camera, large array: needle edge 3.87 mm from the centre; span ends at 3.65 mm, +0.22 mm from the needle edge (negative: on the needle); exclusion zone 0.46 mm
  diag8.4, small array: needle edge 1.55 mm from the centre; span ends at 1.65 mm, -0.10 mm from the needle edge (negative: on the needle); exclusion zone 0.23 mm
  diag8.4, large array: needle edge 3.45 mm from the centre; span ends at 3.65 mm, -0.20 mm from the needle edge (negative: on the needle); exclusion zone 0.42 mm

Needle footprint radius at the camera scale (mm), per dish (4 electrodes), with the median a.u. inside and around:
                              r_min  r_median  r_max   inside   outside
array kV  file                                                         
large 4.8 sub-02              0.707     0.853  0.926  306.609   466.719
          sub-15_obj-znj27w   0.614     0.629  0.707  371.711   667.906
      6.4 sub-04              0.634     0.650  0.728  387.078  1111.406
          sub-07              0.468     0.614  0.645  406.023  1343.766
          sub-11              0.634     0.660  0.666  307.688  1364.719
small 2.4 sub-15_obj-t7o4u3   0.634     0.676  0.863  148.547   176.594
          sub-16              0.291     0.676  0.874  148.820   171.703
          sub-17              0.468     0.603  0.718  165.648   196.812
          sub-21              0.707     0.749  0.894  137.836   160.125
      3.2 sub-23              0.697     0.712  0.728  294.102   409.578
          sub-24              0.655     0.707  0.738  261.531   421.906
          sub-25_obj-1of0oop  0.686     0.738  0.780  238.000   340.469
      4.0 sub-03              0.676     0.733  0.759  216.547   388.453
          sub-08              0.645     0.728  0.780  275.750   466.578
          sub-14              0.707     0.728  0.759  295.664   456.594
      4.8 sub-05              0.666     0.744  0.770  246.234   518.969
          sub-12              0.603     0.702  0.738  316.070   530.500
          sub-13              0.697     0.712  0.738  308.031   596.953
      5.6 sub-25_obj-17bqgcb  0.686     0.759  0.822  382.328   709.594
          sub-26              0.676     0.707  0.718  422.938   704.156
          sub-27              0.655     0.712  0.780  313.148   518.719
Not found: 0 of 84
  large array: median radius 0.650 mm (IQR 0.632 to 0.707), so a 0.75 mm needle radius implies 0.750 um per pixel (camera reading 0.650, 8.4 mm reading 0.591)
  small array: median radius 0.718 mm (IQR 0.686 to 0.741), so a 0.75 mm needle radius implies 0.679 um per pixel (camera reading 0.650, 8.4 mm reading 0.591)
  footprint scale 0.679 um, small array: diagonal 5.27 mm (paper, large array: 8.4 mm); needle edge 1.89 mm from the centre, span ends +0.24 mm from it; exclusion zone 0.26 mm
  footprint scale 0.750 um, large array: diagonal 10.65 mm (paper, large array: 8.4 mm); needle edge 4.58 mm from the centre, span ends +0.93 mm from it; exclusion zone 0.53 mm
Highest correlations between two dishes' images (1 would be a duplicate):
  0.387 sub-07_image.nwb sub-11_image.nwb
  0.225 sub-11_image.nwb sub-15_obj-znj27w_image.nwb
  0.219 sub-02_image.nwb sub-26_image.nwb
Identical mean intensities: 0

Dark floor of the images (0.1st percentile, a.u.) by imaging date and condition:
                                      count   min   max
date       packets_per_electrode kV                    
2021-02-16 1                     3.2      3  60.5  60.7
                                 4.0      3  61.2  62.3
2021-02-17 1                     4.8      1  62.1  62.1
2021-03-11 1                     5.6      3  53.5  53.8
2021-04-02 1                     2.4      4  33.4  46.8
                                 4.8      2  61.7  61.9
2021-05-20 10                    4.8      2  77.7  79.3
                                 6.4      3  79.3  81.0

No data stream has a clock: each file is one image at one time point, so there is no timing to check.

The metadata against the data:

  • The session descriptions give the first-pulse voltage and the number of packets per electrode, but not the array. The electrode spacing separates the images into two groups whose diagonals differ by the factor printed above, and the group with the wider spacing holds exactly the 10Px4 dishes, as the Fig. 7 legend has it (1Px4 on the small array, 10Px4 on the large one).
  • No pixel size is given; the ImageSeries resolution attribute is 96 or 1, which is not a pixel size, and 7 files name no detector, objective or channel. With the 0.65 um pixel that the named camera and objective give, the large-array diagonal is the value printed above, against the paper's 8.4 mm. The needles give a check: the dark footprint around each needle has the radius printed above, against the 0.75 mm of the 1.5 mm needles. The footprint edge is soft, so this check is approximate. It rules out the 8.4 mm scale, at which the needle edge would lie inside the paper's ROI span on both arrays (printed above), so that scale is not used as a reading. It does not confirm the camera scale either: the footprints imply about 0.68 um per pixel on the small array and 0.75 um on the large one (printed above), and at 0.75 um the large diagonal would be still further from the paper's 8.4 mm. The camera scale is kept for the main reading, since it rests on the hardware the files name and puts the large diagonal closer to 8.4 mm than the footprint scale does; the footprint scale is shown as a variant. At the camera scale the span ends within the paper's 5% exclusion zone; a variant below drops the end ROIs that lie in it.
  • Subject ids are reused: two subject ids each hold two dishes of different days and protocols, so a subject id does not identify a dish ("Each dish was used in one experiment", Section 2.1).
  • No two images are duplicates (the highest correlation between any two is printed above).
  • The dark floor of the images is not a fixed camera offset: the 2.4 kV dishes have a floor of 33 to 47 a.u. against 62 for the 4.8 kV dishes imaged on the same day, and the large-array session 78 to 81. The paper does not say whether a background was removed (see fig7a).
  • Fig. 7 states n = 3 for every voltage; the dandiset has 4 dishes at 2.4 kV, 3 at the other small array voltages, 2 at 4.8 kV and 3 at 6.4 kV on the large array.

Other dandisets that cite the paper¶

The claims of Figs. 5, 6, 8 and 9 need dishes this dandiset does not hold. Four other dandisets cite the paper; their titles and descriptions are read here from the archive (their files are not opened), to say where those images appear to be.

In [4]:
PAPER_DOI = "10.1016/j.bioelechem.2023.108437"
for d in ("000686", "000875", "001027", "001028"):
    info = cc.requests.get(f"{cc.API}/dandisets/{d}/", timeout=60).json()
    v = (info.get("most_recent_published_version") or info["draft_version"])["version"]
    meta = cc.requests.get(f"{cc.API}/dandisets/{d}/versions/{v}/", timeout=60).json()
    cites = any(PAPER_DOI in (r.get("identifier", "") + r.get("url", "")).lower() for r in meta.get("relatedResource", []))
    n = cc.requests.get(f"{cc.API}/dandisets/{d}/versions/{v}/assets/?page_size=1", timeout=60).json()["count"]
    print(f"{d} version {v}, {n} assets, cites the paper: {cites}\n  title: {meta['name']}\n  description: {meta.get('description', '')[:300]}\n")
000686 version 0.240515.1909, 18 assets, cites the paper: True
  title: Focusing electroporation to the center of a quadrupole electrode array by interference of unipolar pulses (NextGeneration-CANCAN)
  description: These experimental trials demonstrate the feasibility of achieving remote targeting in electroporation by utilizing sequences of unipolar nanosecond electric pulses (nsEP), with the rotation of these packets further enhancing the process. Electroporation of the cells was evaluated using 1 uM YoPro-1

000875 version 0.240515.1925, 9 assets, cites the paper: True
  title: Influence of unipolar pulse packet quantity on centering the electroporation effect within a quadrupole electrode array.
  description: This data provides images of Yo-Pro uptake when different numbers of nanosecond electric pulse packets were applied in a four-electrode array. Increasing the total number of applied packets from 4 to 20 and 40 significantly enhanced the electroporative effect across the electrode array. The peak of 

001027 version 0.240605.1859, 10 assets, cites the paper: True
  title: CANCAN focusing of electroporation in a quadrupole array by different pulsing protocols
  description: Protocols were applied once, starting from each of the electrodes, to a total of four protocols delivered. The 1st pulse amplitude in all protocols was 4 kV. Electroporation of the cells was evaluated using 1 uM YoPro-1 cell impermeable fluorescent dye. The study was supported in part by R21EY034258

001028 version 0.240605.1954, 12 assets, cites the paper: True
  title: CANCAN focusing of electroporation in 3D
  description: Tips of electrodes in a quadrupole array were either in contact with cell monolayer (0 mm distance) or were placed 1, 2, or 3 mm above it. Packet 2 was applied 10 times, starting from each electrode, to a total of 40 packets delivered. The amplitude of the first pulse in the packet was 6.4 kV. Elect

Electrode positions and ROI lines¶

The fitted electrode footprints (red circles) and the ROI span along each diagonal under the main pixel-size reading (diagonal 1 red, diagonal 2 cyan), on the 16 x 16 block images. Brightness is clipped at the 1st and 99th percentiles of each image.

In [5]:
order = [next(q for q in res if q.split("/")[1].replace("_image.nwb", "") == f) for f in dishes.file]
fig, axs = plt.subplots(4, 6, figsize=(24, 16))
for ax, p in zip(axs.ravel(), order):
    r = res[p]
    v = r["img"][r["zero"] < 0.5]
    ax.imshow(r["img"], cmap="gray", vmin=np.percentile(v, 1), vmax=np.percentile(v, 99))
    pts = r["pts"]
    ctr = pts.mean(0)
    for q in pts:
        ax.add_patch(plt.Circle(q, cc.DISK_R[r["size"]], fill=False, color="r"))
    span = cc.SPAN_MM[r["size"]] * 1000 / um["camera"] / cc.FD
    for (i, j), col in (((0, 2), "r"), ((1, 3), "c")):
        e = (pts[j] - pts[i]) / np.hypot(*(pts[j] - pts[i]))
        ax.plot([ctr[0] - span * e[0], ctr[0] + span * e[0]], [ctr[1] - span * e[1], ctr[1] + span * e[1]], col, lw=1)
    m = np.ptp(pts, 0).max() * 0.75
    ax.set_xlim(ctr[0] - m, ctr[0] + m)
    ax.set_ylim(ctr[1] + m, ctr[1] - m)
    ax.set_title(f"{p.split('/')[1].replace('_image.nwb', '')}  {r['kV']} kV  {r['reps']}Px4", fontsize=10)
    ax.axis("off")
for ax in axs.ravel()[len(order):]:
    ax.axis("off")
plt.tight_layout()
plt.show()
No description has been provided for this image

Readings of the ROI layout¶

The claims file's opening comment names the readings the paper allows; one more is added here, the pixel size, which the dataset does not give (helper docstring). Each combination is run:

  • pixel size: camera (0.65 um, from the camera and objective the files name). The scale at which the large array's mean diagonal is 8.4 mm was a reading in round 1 and is dropped: it puts the end ROIs on the needles (data check). The scale implied by the needle footprints (about 0.68 um on the small array, 0.75 um on the large) is shown as a variant of the main reading;
  • ROI count: both (92 or 224 ROIs over both diagonals, so 46 or 112 per diagonal) or per (92 or 224 per diagonal);
  • diagonals: both averaged, or d1 or d2 alone (the files do not say which electrode is e1);
  • centre ROI: a one ROI centred on the array centre, b the mean of the two grid ROIs beside it;
  • source: raw ROI means or a loess fit (span 0.75) along each diagonal (for LOESS the two centre readings coincide, so only a is listed);
  • normalisation to the most peripheral ROI: a the mean of the end ROIs, b each half-diagonal to its own end ROI, then averaged.

This gives 18 layouts, and 36 readings with the two normalisations. The main reading is camera, both, both, a, raw, a. Its reasons: the camera and objective are named in the files, and it puts the large diagonal closer to the paper's 8.4 mm than the footprint scale does; the Fig. 5B legend averages both diagonals for rotated packets, and all Fig. 7 treatments are rotated; the Methods give one count for the measurement; the paper's numbers are measurements, not fits; and "in percent to the most peripheral ROI" names one reference value. It is an assumption wherever the readings disagree.

Three variants are shown beside the readings, none part of the criteria: the footprint background subtracted from every ROI, the end ROIs that fall inside the paper's 5% exclusion zone dropped (cc.inner_values, main reading otherwise), and the pixel size implied by the needle footprints (main reading otherwise, FP).

In [6]:
def measures(scale, count):
    out = {}
    for p, r in res.items():
        size = r["size"]
        u = um[scale][size] if isinstance(um[scale], dict) else um[scale]
        span = cc.SPAN_MM[size] * 1000 / u / cc.F
        n_diag = cc.N_ROI[size] // 2 if count == "both" else cc.N_ROI[size]
        out[p] = [cc.diagonal_measures(h, 1.0, span, n_diag) for h in r["hists"]]
    return out


M = {(s, c): measures(s, c) for s in cc.SCALES for c in cc.COUNTS}
READINGS = [r for r in itertools.product(cc.SCALES, cc.COUNTS, cc.DIAGONALS, cc.CENTERS, cc.SOURCES)
            if not (r[4] == "loess" and r[3] == "b")]
MAIN = ("camera", "both", "both", "a", "raw")
# variant: main reading at the per-array scale implied by the needle footprints (data check)
M[("footprint", "both")] = measures("footprint", "both")
FP = ("footprint", "both", "both", "a", "raw")
RCOLS = ["scale", "count", "diagonals", "centre", "source"]


def table(reps, reading, background=None):
    # per-dish values under one reading; background: dict path -> a.u. to subtract (variant only)
    scale, count, diag, center, source = reading
    rows = []
    for p, dm in M[(scale, count)].items():
        r = res[p]
        if r["reps"] != reps:
            continue
        if background is not None:
            b = background[p]
            dm = [{**d, source: {k: v - b for k, v in d[source].items()}} for d in dm]
        rows.append(dict(file=p.split("/")[1].replace("_image.nwb", ""), kV=r["kV"], **cc.summarize(dm, diag, center, source)))
    return pd.DataFrame(rows).sort_values(["kV", "file"]).reset_index(drop=True)


# background variant: median of the 16x16 blocks inside the electrode footprints (half radius),
# where the needles stood on the glass; a choice made here, used only as a variant
background = {}
for p, r in res.items():
    yy, xx = np.mgrid[: r["img"].shape[0], : r["img"].shape[1]]
    inside = np.zeros(r["img"].shape, bool)
    for q in r["pts"]:
        inside |= np.hypot(xx - q[0], yy - q[1]) < 0.5 * cc.DISK_R[r["size"]]
    background[p] = float(np.median(r["img"][inside & (r["zero"] < 0.01)]))
# exclusion-zone variant: per dish, values with the end ROIs inside the 5% zone dropped (main reading otherwise)
INNER, INNER_FP = {}, {}
for p, r in res.items():
    hd = [np.hypot(*(r["pts"][j] - r["pts"][i])) * cc.FD / cc.F / 2 for i, j in ((0, 2), (1, 3))]
    INNER[p] = cc.inner_values(r["hists"], M[MAIN[:2]][p], hd, um["camera"])
    INNER_FP[p] = cc.inner_values(r["hists"], M[FP[:2]][p], hd, um["footprint"][r["size"]])
print("Exclusion-zone variant, ROIs dropped at each end (both diagonals) by array:",
      pd.DataFrame([dict(array=res[p]["size"], dropped=str(v["dropped"])) for p, v in INNER.items()])
      .groupby("array").dropped.agg(lambda s: s.value_counts().to_dict()).to_dict())
print("At the footprint scale:",
      pd.DataFrame([dict(array=res[p]["size"], dropped=str(v["dropped"])) for p, v in INNER_FP.items()])
      .groupby("array").dropped.agg(lambda s: s.value_counts().to_dict()).to_dict())


def inner_table(reps, background=None, inner=None):
    rows = []
    for p, v in (INNER if inner is None else inner).items():
        r = res[p]
        if r["reps"] != reps:
            continue
        b = 0.0 if background is None else background[p]
        c, h, e = v["center"] - b, v["half"] - b, v["periph"] - b
        rows.append(dict(file=p.split("/")[1].replace("_image.nwb", ""), kV=r["kV"], periph=e, half=h, center=c,
                         nc_a=100 * c / e, nh_a=100 * h / e))
    return pd.DataFrame(rows).sort_values(["kV", "file"]).reset_index(drop=True)


print("Footprint background (a.u.) by condition:")
print(pd.DataFrame([dict(reps=r["reps"], kV=r["kV"], bg=background[p]) for p, r in res.items()])
      .groupby(["reps", "kV"]).bg.agg(["count", "mean", "min", "max"]).round(1).to_string())
Exclusion-zone variant, ROIs dropped at each end (both diagonals) by array: {'large': {'[4, 5]': 4, '[4, 3]': 1}, 'small': {'[2, 3]': 4, '[2, 2]': 4, '[2, 4]': 3, '[2, 1]': 2, '[4, 4]': 1, '[1, 3]': 1, '[2, 0]': 1}}
At the footprint scale: {'large': {'[0, 0]': 5}, 'small': {'[1, 2]': 3, '[0, 1]': 3, '[0, 0]': 3, '[1, 1]': 2, '[1, 0]': 1, '[0, 3]': 1, '[2, 2]': 1, '[0, 2]': 1, '[1, 3]': 1}}
Footprint background (a.u.) by condition:
          count   mean    min    max
reps kV                             
1    2.4      4  150.7  140.3  159.4
     3.2      3  260.1  232.0  294.2
     4.0      3  261.1  218.0  288.4
     4.8      3  286.6  239.2  314.0
     5.6      3  375.9  314.6  424.6
10   4.8      2  332.8  293.7  372.0
     6.4      3  355.7  308.0  380.4

Profiles¶

Mean normalised profiles (main reading, both diagonals averaged and folded about the centre so that 0 is the centre and 1 the most peripheral ROI), mean +/- s.e. over dishes, for each voltage.

In [7]:
def folded_profiles(reps, scale="camera", count="both"):
    # per dish: mean over the 4 half-diagonals of the raw ROI profile, from centre to periphery
    out = {}
    for p, dm in M[(scale, count)].items():
        r = res[p]
        if r["reps"] != reps:
            continue
        halves = []
        for d in dm:
            prof = d["raw"]["profile"]
            h = len(prof) // 2
            halves += [prof[:h][::-1], prof[h:]]
        out[p] = np.nanmean(halves, axis=0)
    return out


fig, axs = plt.subplots(2, 2, figsize=(13, 9))
for col, reps in enumerate((1, 10)):
    prof = folded_profiles(reps)
    for kv in sorted({res[p]["kV"] for p in prof}):
        ps = [p for p in prof if res[p]["kV"] == kv]
        P = np.array([prof[p] for p in ps])
        x = np.linspace(0, 1, P.shape[1])
        Pn = 100 * P / P[:, -1:]
        for row, Q in enumerate((P, Pn)):
            m, se = Q.mean(0), Q.std(0, ddof=1) / np.sqrt(len(Q))
            axs[row, col].errorbar(x, m, se, label=f"{kv} kV (n={len(ps)})", lw=1, capsize=0)
            print(f"{reps}Px4 {kv} kV {'a.u.' if row == 0 else '% periphery'}: range of means {m.min():.1f} to {m.max():.1f}")
    axs[0, col].set_title(f"{'small' if reps == 1 else 'large'} array, {reps}Px4, a.u.")
    axs[1, col].set_title(f"{'small' if reps == 1 else 'large'} array, {reps}Px4, % of most peripheral ROI")
    for row in (0, 1):
        axs[row, col].set_xlabel("distance from centre, fraction of ROI span")
        axs[row, col].legend(fontsize=8)
plt.tight_layout()
plt.show()
1Px4 2.4 kV a.u.: range of means 192.6 to 249.9
1Px4 2.4 kV % periphery: range of means 87.3 to 113.1
1Px4 3.2 kV a.u.: range of means 481.7 to 708.8
1Px4 3.2 kV % periphery: range of means 90.4 to 132.4
1Px4 4.0 kV a.u.: range of means 513.5 to 862.9
1Px4 4.0 kV % periphery: range of means 84.8 to 143.3
1Px4 4.8 kV a.u.: range of means 751.9 to 1438.7
1Px4 4.8 kV % periphery: range of means 82.0 to 155.9
1Px4 5.6 kV a.u.: range of means 1000.3 to 1779.8
1Px4 5.6 kV % periphery: range of means 75.4 to 134.5
10Px4 4.8 kV a.u.: range of means 723.3 to 3186.9
10Px4 4.8 kV % periphery: range of means 75.4 to 372.5
10Px4 6.4 kV a.u.: range of means 1498.8 to 5727.9
10Px4 6.4 kV % periphery: range of means 40.9 to 156.8
No description has been provided for this image

fig7a-amplitude-scales-uptake¶

On the small array with 1Px4, raising the first pulse amplitude from 2.4 to 5.6 kV increases YP fluorescence 15.5 times at the most peripheral point, 16.2 times half-way to the center and 16.8 times in the center.

Criterion: ratios of the 5.6 kV mean to the 2.4 kV mean over dishes, in a.u., with no background subtracted; each within 10% (15.5 +/- 1.55, 16.2 +/- 1.62, 16.8 +/- 1.68) under a reading of the opening comment; partially reproduced when one or two are.

In [8]:
TOL = {"periph": (15.5, 1.55), "half": (16.2, 1.62), "center": (16.8, 1.68)}


def ratios(reading, background=None, drop=None):
    df = table(1, reading, background)
    if drop is not None:
        df = df[df.file != drop]
    g = df.groupby("kV")[["periph", "half", "center"]].mean()
    return g.loc[5.6] / g.loc[2.4], df


rows = []
for rd in READINGS:
    rat, _ = ratios(rd)
    rows.append(dict(zip(RCOLS, rd), **{f"ratio_{k}": round(rat[k], 2) for k in TOL},
                     n_within=int(sum(abs(rat[k] - v) <= t for k, (v, t) in TOL.items()))))
r7a = pd.DataFrame(rows)
print(r7a.to_string())
print("\nReadings with at least one ratio within tolerance:", int((r7a.n_within > 0).sum()), "of", len(r7a))
print("Range over readings: periphery", r7a.ratio_periph.min(), "to", r7a.ratio_periph.max(),
      "; half-way", r7a.ratio_half.min(), "to", r7a.ratio_half.max(),
      "; centre", r7a.ratio_center.min(), "to", r7a.ratio_center.max())
     scale count diagonals centre source  ratio_periph  ratio_half  ratio_center  n_within
0   camera  both      both      a    raw          5.99        5.65          7.25         0
1   camera  both      both      a  loess          5.57        5.90          6.09         0
2   camera  both      both      b    raw          5.99        5.65          7.31         0
3   camera  both        d1      a    raw          5.62        5.69          7.25         0
4   camera  both        d1      a  loess          5.11        5.85          6.25         0
5   camera  both        d1      b    raw          5.62        5.69          7.16         0
6   camera  both        d2      a    raw          6.35        5.62          7.25         0
7   camera  both        d2      a  loess          6.03        5.94          5.93         0
8   camera  both        d2      b    raw          6.35        5.62          7.46         0
9   camera   per      both      a    raw          5.48        5.34          7.51         0
10  camera   per      both      a  loess          5.53        5.90          6.10         0
11  camera   per      both      b    raw          5.48        5.34          7.37         0
12  camera   per        d1      a    raw          5.21        5.38          7.43         0
13  camera   per        d1      a  loess          5.11        5.78          6.12         0
14  camera   per        d1      b    raw          5.21        5.38          7.19         0
15  camera   per        d2      a    raw          5.74        5.31          7.59         0
16  camera   per        d2      a  loess          5.95        6.01          6.09         0
17  camera   per        d2      b    raw          5.74        5.31          7.55         0

Readings with at least one ratio within tolerance: 0 of 18
Range over readings: periphery 5.11 to 6.35 ; half-way 5.31 to 6.01 ; centre 5.93 to 7.59
In [9]:
rat, df = ratios(MAIN)
print("Main reading, per dish (a.u.):")
print(df[df.kV.isin([2.4, 5.6])][["file", "kV", "periph", "half", "center"]].round(1).to_string())
g = df.groupby("kV")[["periph", "half", "center"]].agg(["mean", "sem", "count"]).round(1)
print("\nMean, s.e. and n by voltage (all five voltages):")
print(g.to_string())
print("\nMain reading ratios 5.6/2.4 kV:", rat.round(2).to_dict())

print("\n2.4 kV has 4 dishes where the paper states n = 3; ratios with each one left out:")
for f in df[df.kV == 2.4].file:
    r_, _ = ratios(MAIN, drop=f)
    print(f"  without {f}: {r_.round(2).to_dict()}")

print("\nVariant, footprint background subtracted from every ROI (not the criterion):")
rows = []
for rd in READINGS:
    r_, _ = ratios(rd, background)
    rows.append(dict(zip(RCOLS, rd), **{f"ratio_{k}": round(r_[k], 2) for k in TOL},
                     n_within=int(sum(abs(r_[k] - v) <= t for k, (v, t) in TOL.items()))))
r7a_bg = pd.DataFrame(rows)
print(r7a_bg[r7a_bg[RCOLS].apply(tuple, axis=1) == MAIN].to_string())
print("Readings with all three within tolerance:", int((r7a_bg.n_within == 3).sum()), "of", len(r7a_bg),
      "; with at least one:", int((r7a_bg.n_within > 0).sum()))
print("Range over readings: periphery", r7a_bg.ratio_periph.min(), "to", r7a_bg.ratio_periph.max(),
      "; half-way", r7a_bg.ratio_half.min(), "to", r7a_bg.ratio_half.max(),
      "; centre", r7a_bg.ratio_center.min(), "to", r7a_bg.ratio_center.max())
print("\nVariant, end ROIs inside the 5% exclusion zone dropped (main reading otherwise):")
for bgname, bg in (("no background", None), ("footprint background subtracted", background)):
    d = inner_table(1, bg)
    gi = d.groupby("kV")[["periph", "half", "center"]].mean()
    ri = gi.loc[5.6] / gi.loc[2.4]
    print(f"  {bgname}: ratios {ri.round(2).to_dict()}, within tolerance: "
          f"{int(sum(abs(ri[k] - v) <= t for k, (v, t) in TOL.items()))} of 3")

print("\nVariant, pixel size from the needle footprints (main reading otherwise; not the criterion):")
for bgname, bg in (("no background", None), ("footprint background subtracted", background)):
    r_, _ = ratios(FP, bg)
    d = inner_table(1, bg, INNER_FP)
    gi = d.groupby("kV")[["periph", "half", "center"]].mean()
    ri = gi.loc[5.6] / gi.loc[2.4]
    print(f"  {bgname}: ratios {r_.round(2).to_dict()}, within tolerance {int(sum(abs(r_[k] - v) <= t for k, (v, t) in TOL.items()))} of 3; "
          f"with the exclusion-zone ROIs dropped {ri.round(2).to_dict()}, within tolerance {int(sum(abs(ri[k] - v) <= t for k, (v, t) in TOL.items()))} of 3")
Main reading, per dish (a.u.):
                  file   kV  periph    half  center
0    sub-15_obj-t7o4u3  2.4   210.8   205.3   232.7
1               sub-16  2.4   218.6   200.1   209.8
2               sub-17  2.4   261.1   198.5   295.5
3               sub-21  2.4   198.4   185.6   227.5
13  sub-25_obj-17bqgcb  5.6  1437.8  1103.3  1603.5
14              sub-26  5.6  1255.9  1206.7  1866.2
15              sub-27  5.6  1301.2  1037.5  1780.4

Mean, s.e. and n by voltage (all five voltages):
     periph                half              center             
       mean   sem count    mean   sem count    mean    sem count
kV                                                              
2.4   222.2  13.6     4   197.4   4.2     4   241.4   18.7     4
3.2   533.2  24.0     3   520.5  54.9     3   661.1   87.5     3
4.0   614.9  94.8     3   540.4  70.3     3   718.8   70.8     3
4.8   922.7  64.1     3   772.2  81.4     3  1266.2  109.7     3
5.6  1331.6  54.7     3  1115.8  49.3     3  1750.0   77.3     3

Main reading ratios 5.6/2.4 kV: {'periph': 5.99, 'half': 5.65, 'center': 7.25}

2.4 kV has 4 dishes where the paper states n = 3; ratios with each one left out:
  without sub-15_obj-t7o4u3: {'periph': 5.89, 'half': 5.73, 'center': 7.16}
  without sub-16: {'periph': 5.96, 'half': 5.68, 'center': 6.95}
  without sub-17: {'periph': 6.36, 'half': 5.66, 'center': 7.84}
  without sub-21: {'periph': 5.79, 'half': 5.54, 'center': 7.11}

Variant, footprint background subtracted from every ROI (not the criterion):
    scale count diagonals centre source  ratio_periph  ratio_half  ratio_center  n_within
0  camera  both      both      a    raw         13.37       15.87         15.16         2
Readings with all three within tolerance: 2 of 18 ; with at least one: 17
Range over readings: periphery 10.58 to 17.77 ; half-way 12.51 to 16.1 ; centre 12.97 to 16.22

Variant, end ROIs inside the 5% exclusion zone dropped (main reading otherwise):
  no background: ratios {'periph': 5.77, 'half': 5.84, 'center': 7.25}, within tolerance: 0 of 3
  footprint background subtracted: ratios {'periph': 14.88, 'half': 17.1, 'center': 15.16}, within tolerance: 3 of 3

Variant, pixel size from the needle footprints (main reading otherwise; not the criterion):
  no background: ratios {'periph': 5.71, 'half': 5.45, 'center': 7.32}, within tolerance 0 of 3; with the exclusion-zone ROIs dropped {'periph': 5.78, 'half': 5.45, 'center': 7.32}, within tolerance 0 of 3
  footprint background subtracted: ratios {'periph': 14.17, 'half': 14.82, 'center': 15.39}, within tolerance 3 of 3; with the exclusion-zone ROIs dropped {'periph': 15.01, 'half': 15.3, 'center': 15.39}, within tolerance 3 of 3

Result: not reproduced. Without background subtraction, as the criterion fixes, the 5.6 kV over 2.4 kV ratios under the main reading are 5.99 at the most peripheral ROI, 5.65 half-way and 7.25 at the centre, against 15.5, 16.2 and 16.8, and none of the 18 readings brings any ratio within tolerance (the largest is 7.59). Leaving out any one of the four 2.4 kV dishes changes the ratios by at most 0.59, and dropping the end ROIs that lie in the paper's exclusion zone gives 5.77, 5.84 and 7.25. Fluorescence rises with voltage at every position. With the median inside the needle footprints subtracted as a background (150.7 a.u. on average at 2.4 kV), a variant the criterion excludes, the main reading gives 13.37, 15.87 and 15.16, two of three within tolerance, 2 of 18 readings put all three within it, and with the exclusion-zone ROIs also dropped all three are within it (14.88, 17.10 and 15.16). At the pixel size implied by the needle footprints the ratios are 5.71, 5.45 and 7.32 without background (none within tolerance) and 14.17, 14.82 and 15.39 with it (all three within). The outcome turns on whether an offset was removed. The claims file says none was, as in the Methods, but the Methods do not say so; this is asked of a reader in questions/001025.yaml.

fig7b-targeting-independent-of-amplitude¶

Normalized to the most peripheral ROI, the profiles for different first pulse amplitudes fully overlap, so the spatial distribution of electroporation, and NG-CANCAN targeting, does not depend on amplitude within the studied range.

Criterion: a claim of absence. Small array, 1Px4, every voltage (2.4, 3.2, 4.0, 4.8 and 5.6 kV, from the dataset). The normalised values at the centre ROI and at the half-way ROI are compared between every pair of voltages by the two-sided Student's t-test, no correction. Reproduced when no pair differs at p < 0.05; not reproduced when any does.

In [10]:
def pair_tests(reps, reading, norm="a", background=None):
    df = table(reps, reading, background)
    kvs = sorted(df.kV.unique())
    out = []
    for pos, lab in (("nc", "centre"), ("nh", "half-way")):
        col = f"{pos}_{norm}"
        for a, b in itertools.combinations(kvs, 2):
            x, y = df[df.kV == a][col], df[df.kV == b][col]
            t, p = stats.ttest_ind(x, y)
            out.append(dict(position=lab, kV_a=a, kV_b=b, n_a=len(x), n_b=len(y), mean_a=x.mean(), mean_b=y.mean(),
                            diff=y.mean() - x.mean(), t=t, p=p))
    return pd.DataFrame(out)


def readings_summary(reps, background=None):
    rows = []
    for rd in READINGS:
        for norm in "ab":
            t = pair_tests(reps, rd, norm, background)
            rows.append(dict(zip(RCOLS, rd), norm=norm, n_tests=len(t), n_p_below_05=int((t.p < 0.05).sum()),
                             min_p=t.p.min(), max_abs_diff=t["diff"].abs().max()))
    return pd.DataFrame(rows)


t7b = pair_tests(1, MAIN, "a")
print("Main reading (normalisation a), every pair of voltages, % of the most peripheral ROI:")
print(t7b.round(4).to_string())
print(f"\nPairs with p < 0.05: {(t7b.p < 0.05).sum()} of {len(t7b)}")
df = table(1, MAIN)
print("\nPer dish, normalised centre and half-way (%):")
print(df[["file", "kV", "nc_a", "nh_a", "nc_b", "nh_b"]].round(1).to_string())

print("\n2.4 kV has 4 dishes where the paper states n = 3; each left out (main reading, normalisation a):")
for f in df[df.kV == 2.4].file:
    d = df[df.file != f]
    sig = []
    for pos, lab in (("nc_a", "centre"), ("nh_a", "half-way")):
        for a, b in itertools.combinations(sorted(d.kV.unique()), 2):
            _, p = stats.ttest_ind(d[d.kV == a][pos], d[d.kV == b][pos])
            if p < 0.05:
                sig.append(f"{lab} {a} vs {b} kV p = {p:.4f}")
    print(f"  without {f}: {len(sig)} pairs at p < 0.05: {sig}")
Main reading (normalisation a), every pair of voltages, % of the most peripheral ROI:
    position  kV_a  kV_b  n_a  n_b    mean_a    mean_b     diff       t       p
0     centre   2.4   3.2    4    3  108.5578  122.9758  14.4180 -1.3374  0.2387
1     centre   2.4   4.0    4    3  108.5578  118.7606  10.2029 -1.4183  0.2153
2     centre   2.4   4.8    4    3  108.5578  136.8757  28.3179 -5.1192  0.0037
3     centre   2.4   5.6    4    3  108.5578  132.3157  23.7579 -2.2704  0.0724
4     centre   3.2   4.0    3    3  122.9758  118.7606  -4.2151  0.3268  0.7602
5     centre   3.2   4.8    3    3  122.9758  136.8757  13.8999 -1.1911  0.2995
6     centre   3.2   5.6    3    3  122.9758  132.3157   9.3399 -0.5920  0.5857
7     centre   4.0   4.8    3    3  118.7606  136.8757  18.1150 -2.7285  0.0525
8     centre   4.0   5.6    3    3  118.7606  132.3157  13.5551 -1.0825  0.3399
9     centre   4.8   5.6    3    3  136.8757  132.3157  -4.5600  0.4053  0.7060
10  half-way   2.4   3.2    4    3   89.6222   97.0735   7.4513 -0.9798  0.3722
11  half-way   2.4   4.0    4    3   89.6222   88.5111  -1.1110  0.1901  0.8567
12  half-way   2.4   4.8    4    3   89.6222   83.2555  -6.3666  1.0150  0.3567
13  half-way   2.4   5.6    4    3   89.6222   84.1843  -5.4379  0.7258  0.5005
14  half-way   3.2   4.0    3    3   97.0735   88.5111  -8.5623  1.3003  0.2634
15  half-way   3.2   4.8    3    3   97.0735   83.2555 -13.8180  1.9395  0.1244
16  half-way   3.2   5.6    3    3   97.0735   84.1843 -12.8892  1.4908  0.2103
17  half-way   4.0   4.8    3    3   88.5111   83.2555  -5.2556  1.2769  0.2707
18  half-way   4.0   5.6    3    3   88.5111   84.1843  -4.3268  0.6763  0.5359
19  half-way   4.8   5.6    3    3   83.2555   84.1843   0.9288 -0.1336  0.9002

Pairs with p < 0.05: 1 of 20

Per dish, normalised centre and half-way (%):
                  file   kV   nc_a   nh_a   nc_b   nh_b
0    sub-15_obj-t7o4u3  2.4  110.4   97.4  110.8   97.5
1               sub-16  2.4   96.0   91.5   96.6   92.0
2               sub-17  2.4  113.2   76.0  116.2   77.8
3               sub-21  2.4  114.7   93.6  116.6   94.5
4               sub-23  3.2  136.3  105.8  136.6  106.2
5               sub-24  3.2  132.2  100.4  132.7  100.8
6   sub-25_obj-1of0oop  3.2  100.4   85.0  102.2   86.7
7               sub-03  4.0  121.6   88.4  128.3   93.1
8               sub-08  4.0  107.1   84.8  110.3   85.1
9               sub-14  4.0  127.6   92.3  130.1   93.7
10              sub-05  4.8  131.9   76.9  133.5   77.0
11              sub-12  4.8  140.9   84.0  145.5   87.5
12              sub-13  4.8  137.8   88.9  144.6   92.4
13  sub-25_obj-17bqgcb  5.6  111.5   76.7  117.4   80.3
14              sub-26  5.6  148.6   96.1  152.1   96.8
15              sub-27  5.6  136.8   79.7  141.8   80.2

2.4 kV has 4 dishes where the paper states n = 3; each left out (main reading, normalisation a):
  without sub-15_obj-t7o4u3: 1 pairs at p < 0.05: ['centre 2.4 vs 4.8 kV p = 0.0116']
  without sub-16: 1 pairs at p < 0.05: ['centre 2.4 vs 4.8 kV p = 0.0012']
  without sub-17: 2 pairs at p < 0.05: ['centre 2.4 vs 4.8 kV p = 0.0087', 'half-way 2.4 vs 4.8 kV p = 0.0485']
  without sub-21: 1 pairs at p < 0.05: ['centre 2.4 vs 4.8 kV p = 0.0070']
In [11]:
s7b = readings_summary(1)
print(f"Every reading ({len(s7b)} = {len(READINGS)} layouts x 2 normalisations):")
print(s7b.round(4).to_string())
print("\nReadings with no pair at p < 0.05:", int((s7b.n_p_below_05 == 0).sum()), "of", len(s7b))
print("Number of significant pairs over readings:", s7b.n_p_below_05.value_counts().sort_index().to_dict())

# chance of at least one p < 0.05 among the 20 tests when no effect exists. The 10 pairs share groups and
# the two positions are measured on the same dishes, so the tests are not independent: simulate the design
# (group sizes of the dataset, normal values) with the two positions independent, identical, and correlated
# as in the data (within-voltage correlation of the normalised centre and half-way values, main reading)
rng = np.random.default_rng(2023)
d0 = table(1, MAIN)
ns = d0.groupby("kV").size().to_dict()
resid = d0[["nc_a", "nh_a"]] - d0.groupby("kV")[["nc_a", "nh_a"]].transform("mean")
rho = float(np.corrcoef(resid.nc_a, resid.nh_a)[0, 1])
NSIM = 20000


def fwer(rho_):
    z = {kv: rng.multivariate_normal([0, 0], [[1, rho_], [rho_, 1]], size=(NSIM, n)) for kv, n in ns.items()}
    anyp = np.zeros(NSIM, bool)
    for a, b in itertools.combinations(sorted(ns), 2):
        for k in (0, 1):
            anyp |= stats.ttest_ind(z[a][:, :, k], z[b][:, :, k], axis=1).pvalue < 0.05
    return anyp.mean()


print(f"Chance of at least one p < 0.05 among the 20 tests with no effect (group sizes {ns}, {NSIM} simulations, seed 2023):")
print(f"  20 independent tests (formula): {1 - 0.95 ** 20:.2f}")
for lab, r_ in (("positions independent", 0.0), (f"positions correlated as in the data (r = {rho:.2f})", rho), ("positions identical (10 tests)", 1.0)):
    print(f"  simulated, {lab}: {fwer(min(r_, 0.999999)):.2f}")
print(f"Bonferroni threshold for 20 tests: {0.05 / 20:.4f} (the criterion applies no correction); smallest p, main reading: {t7b.p.min():.4f}")

# variant: test at every ROI position of the folded normalised profile
prof = folded_profiles(1)
kvs = sorted({res[p]["kV"] for p in prof})
P = {kv: np.array([100 * prof[p] / prof[p][-1] for p in prof if res[p]["kV"] == kv]) for kv in kvs}
npos = P[kvs[0]].shape[1]
sig = np.zeros(npos, int)
for a, b in itertools.combinations(kvs, 2):
    _, p = stats.ttest_ind(P[a][:, :-1], P[b][:, :-1], axis=0)
    sig[:-1] += p < 0.05
print(f"\nVariant over all ROIs (folded profile, {npos - 1} positions x 10 pairs = {(npos - 1) * 10} tests; the last position is the 100% reference): "
      f"{sig.sum()} at p < 0.05; positions with any: {int((sig > 0).sum())}")

s7b_bg = readings_summary(1, background)
print("\nVariant with the footprint background subtracted, every reading:")
print(s7b_bg.round(4).to_string())
m = s7b_bg[(s7b_bg[RCOLS].apply(tuple, axis=1) == MAIN) & (s7b_bg.norm == "a")]
print("Main reading:", m[["n_p_below_05", "min_p"]].to_dict("records"),
      "; readings with no pair at p < 0.05:", int((s7b_bg.n_p_below_05 == 0).sum()), "of", len(s7b_bg))
print("\nMain reading with background subtracted, every pair:")
print(pair_tests(1, MAIN, "a", background).round(4).to_string())
print("\nPer dish, normalised centre and half-way with background subtracted (%):")
print(table(1, MAIN, background)[["file", "kV", "nc_a", "nh_a", "nc_b", "nh_b"]].round(1).to_string())


def inner_pairs(reps, bg=None, inner=None):
    d = inner_table(reps, bg, inner)
    out = []
    for pos, lab in (("nc_a", "centre"), ("nh_a", "half-way")):
        for a, b in itertools.combinations(sorted(d.kV.unique()), 2):
            x, y = d[d.kV == a][pos], d[d.kV == b][pos]
            t, p = stats.ttest_ind(x, y)
            out.append(dict(position=lab, kV_a=a, kV_b=b, mean_a=x.mean(), mean_b=y.mean(), p=p))
    return pd.DataFrame(out)


for bgname, bg in (("no background", None), ("footprint background subtracted", background)):
    t = pair_tests(1, FP, "a", bg)
    print(f"\nVariant, pixel size from the needle footprints, {bgname}: {(t.p < 0.05).sum()} of {len(t)} pairs at p < 0.05; "
          f"smallest p {t.p.min():.4f}")
    print(t[t.p < 0.05].round(4).to_string())
    t = inner_pairs(1, bg, INNER_FP)
    print(f"  and with the exclusion-zone ROIs dropped: {(t.p < 0.05).sum()} of {len(t)} pairs at p < 0.05; smallest p {t.p.min():.4f}")

for bgname, bg in (("no background", None), ("footprint background subtracted", background)):
    t = inner_pairs(1, bg)
    print(f"\nVariant, end ROIs inside the exclusion zone dropped, {bgname}: {(t.p < 0.05).sum()} of {len(t)} pairs at p < 0.05; "
          f"smallest p {t.p.min():.4f}; normalised centre means by kV "
          f"{inner_table(1, bg).groupby('kV').nc_a.mean().round(1).to_dict()}")
    print(t[t.p < 0.05].round(4).to_string())
Every reading (36 = 18 layouts x 2 normalisations):
     scale count diagonals centre source norm  n_tests  n_p_below_05   min_p  max_abs_diff
0   camera  both      both      a    raw    a       20             1  0.0037       28.3179
1   camera  both      both      a    raw    b       20             2  0.0046       31.1388
2   camera  both      both      a  loess    a       20             2  0.0145       27.3957
3   camera  both      both      a  loess    b       20             4  0.0112       30.2369
4   camera  both      both      b    raw    a       20             2  0.0014       30.6075
5   camera  both      both      b    raw    b       20             2  0.0026       33.2919
6   camera  both        d1      a    raw    a       20             2  0.0263       32.8797
7   camera  both        d1      a    raw    b       20             2  0.0334       36.6575
8   camera  both        d1      a  loess    a       20             4  0.0035       37.3274
9   camera  both        d1      a  loess    b       20             4  0.0051       36.7919
10  camera  both        d1      b    raw    a       20             2  0.0062       31.4659
11  camera  both        d1      b    raw    b       20             2  0.0068       35.1352
12  camera  both        d2      a    raw    a       20             5  0.0050       29.4571
13  camera  both        d2      a    raw    b       20             5  0.0090       33.8909
14  camera  both        d2      a  loess    a       20             0  0.2140       20.3525
15  camera  both        d2      a  loess    b       20             0  0.1271       23.6819
16  camera  both        d2      b    raw    a       20             5  0.0068       29.9300
17  camera  both        d2      b    raw    b       20             5  0.0098       34.3671
18  camera   per      both      a    raw    a       20             0  0.0609       43.6138
19  camera   per      both      a    raw    b       20             1  0.0383       46.3235
20  camera   per      both      a  loess    a       20             4  0.0149       28.2418
21  camera   per      both      a  loess    b       20             5  0.0053       31.4019
22  camera   per      both      b    raw    a       20             0  0.0640       40.9401
23  camera   per      both      b    raw    b       20             1  0.0431       43.5546
24  camera   per        d1      a    raw    a       20             1  0.0271       48.5725
25  camera   per        d1      a    raw    b       20             1  0.0396       49.4609
26  camera   per        d1      a  loess    a       20             4  0.0059       41.1300
27  camera   per        d1      a  loess    b       20             4  0.0053       40.4837
28  camera   per        d1      b    raw    a       20             1  0.0353       42.8223
29  camera   per        d1      b    raw    b       20             1  0.0487       43.5423
30  camera   per        d2      a    raw    a       20             0  0.0688       38.6754
31  camera   per        d2      a    raw    b       20             0  0.0712       43.1860
32  camera   per        d2      a  loess    a       20             0  0.2912       16.6310
33  camera   per        d2      a  loess    b       20             0  0.1832       22.3201
34  camera   per        d2      b    raw    a       20             0  0.1064       38.8342
35  camera   per        d2      b    raw    b       20             0  0.1067       43.5669

Readings with no pair at p < 0.05: 10 of 36
Number of significant pairs over readings: {0: 10, 1: 7, 2: 8, 4: 6, 5: 5}
Chance of at least one p < 0.05 among the 20 tests with no effect (group sizes {2.4: 4, 3.2: 3, 4.0: 3, 4.8: 3, 5.6: 3}, 20000 simulations, seed 2023):
  20 independent tests (formula): 0.64
  simulated, positions independent: 0.50
  simulated, positions correlated as in the data (r = 0.64): 0.47
  simulated, positions identical (10 tests): 0.29
Bonferroni threshold for 20 tests: 0.0025 (the criterion applies no correction); smallest p, main reading: 0.0037

Variant over all ROIs (folded profile, 22 positions x 10 pairs = 220 tests; the last position is the 100% reference): 23 at p < 0.05; positions with any: 14
Variant with the footprint background subtracted, every reading:
     scale count diagonals centre source norm  n_tests  n_p_below_05   min_p  max_abs_diff
0   camera  both      both      a    raw    a       20             0  0.1802       26.0230
1   camera  both      both      a    raw    b       20             0  0.2134       22.4587
2   camera  both      both      a  loess    a       20             0  0.2289       29.6831
3   camera  both      both      a  loess    b       20             0  0.3377       48.6562
4   camera  both      both      b    raw    a       20             0  0.0707       29.5791
5   camera  both      both      b    raw    b       20             0  0.2134       22.4587
6   camera  both        d1      a    raw    a       20             2  0.0415       30.0640
7   camera  both        d1      a    raw    b       20             0  0.0695       37.5830
8   camera  both        d1      a  loess    a       20             0  0.3646       26.9790
9   camera  both        d1      a  loess    b       20             0  0.3055       44.3812
10  camera  both        d1      b    raw    a       20             2  0.0415       30.0640
11  camera  both        d1      b    raw    b       20             0  0.0695       32.5476
12  camera  both        d2      a    raw    a       20             2  0.0098       40.1558
13  camera  both        d2      a    raw    b       20             2  0.0150       43.5068
14  camera  both        d2      a  loess    a       20             0  0.1453       60.1223
15  camera  both        d2      a  loess    b       20             0  0.1589       52.9312
16  camera  both        d2      b    raw    a       20             2  0.0098       40.1558
17  camera  both        d2      b    raw    b       20             2  0.0150       40.9230
18  camera   per      both      a    raw    a       20             0  0.0903       53.6972
19  camera   per      both      a    raw    b       20             0  0.1864       50.5258
20  camera   per      both      a  loess    a       20             0  0.3374       28.8101
21  camera   per      both      a  loess    b       20             0  0.2025       43.4346
22  camera   per      both      b    raw    a       20             0  0.0903       48.6549
23  camera   per      both      b    raw    b       20             0  0.1864       36.3645
24  camera   per        d1      a    raw    a       20             0  0.0623       56.4497
25  camera   per        d1      a    raw    b       20             0  0.0765       41.5076
26  camera   per        d1      a  loess    a       20             0  0.3500       39.8150
27  camera   per        d1      a  loess    b       20             0  0.1708       61.7005
28  camera   per        d1      b    raw    a       20             0  0.0623       47.6119
29  camera   per        d1      b    raw    b       20             0  0.0765       48.5409
30  camera   per        d2      a    raw    a       20             0  0.2052       46.7107
31  camera   per        d2      a    raw    b       20             0  0.2062       66.7062
32  camera   per        d2      a  loess    a       20             0  0.3418       46.1127
33  camera   per        d2      a  loess    b       20             0  0.3111       40.5040
34  camera   per        d2      b    raw    a       20             0  0.2295       51.2980
35  camera   per        d2      b    raw    b       20             0  0.2286       66.8134
Main reading: [{'n_p_below_05': 0, 'min_p': 0.1802027903657868}] ; readings with no pair at p < 0.05: 30 of 36

Main reading with background subtracted, every pair:
    position  kV_a  kV_b  n_a  n_b    mean_a    mean_b     diff       t       p
0     centre   2.4   3.2    4    3  127.4764  145.5970  18.1206 -0.7180  0.5049
1     centre   2.4   4.0    4    3  127.4764  137.3300   9.8536 -0.4705  0.6578
2     centre   2.4   4.8    4    3  127.4764  153.4995  26.0230 -1.5570  0.1802
3     centre   2.4   5.6    4    3  127.4764  145.9270  18.4505 -0.8582  0.4300
4     centre   3.2   4.0    3    3  145.5970  137.3300  -8.2670  0.2973  0.7810
5     centre   3.2   4.8    3    3  145.5970  153.4995   7.9025 -0.3384  0.7521
6     centre   3.2   5.6    3    3  145.5970  145.9270   0.3300 -0.0116  0.9913
7     centre   4.0   4.8    3    3  137.3300  153.4995  16.1695 -0.9986  0.3745
8     centre   4.0   5.6    3    3  137.3300  145.9270   8.5970 -0.3758  0.7262
9     centre   4.8   5.6    3    3  153.4995  145.9270  -7.5725  0.4403  0.6825
10  half-way   2.4   3.2    4    3   69.9994   94.7540  24.7545 -1.4890  0.1967
11  half-way   2.4   4.0    4    3   69.9994   79.7692   9.7698 -0.7394  0.4929
12  half-way   2.4   4.8    4    3   69.9994   75.7843   5.7848 -0.4200  0.6919
13  half-way   2.4   5.6    4    3   69.9994   78.4921   8.4926 -0.5765  0.5892
14  half-way   3.2   4.0    3    3   94.7540   79.7692 -14.9847  1.2223  0.2887
15  half-way   3.2   4.8    3    3   94.7540   75.7843 -18.9697  1.4470  0.2214
16  half-way   3.2   5.6    3    3   94.7540   78.4921 -16.2619  1.1199  0.3255
17  half-way   4.0   4.8    3    3   79.7692   75.7843  -3.9849  0.7750  0.4816
18  half-way   4.0   5.6    3    3   79.7692   78.4921  -1.2772  0.1579  0.8822
19  half-way   4.8   5.6    3    3   75.7843   78.4921   2.7078 -0.2903  0.7860

Per dish, normalised centre and half-way with background subtracted (%):
                  file   kV   nc_a   nh_a   nc_b   nh_b
0    sub-15_obj-t7o4u3  2.4  138.5   90.2  146.1   90.5
1               sub-16  2.4   87.3   73.3   94.5   77.3
2               sub-17  2.4  133.9   38.4  164.0   43.8
3               sub-21  2.4  150.2   78.0  188.7   88.6
4               sub-23  3.2  176.5  112.2  178.3  113.8
5               sub-24  3.2  159.6  100.7  161.7  102.1
6   sub-25_obj-1of0oop  3.2  100.7   71.4  108.0   76.6
7               sub-03  4.0  136.2   80.6  158.6   93.9
8               sub-08  4.0  110.8   76.7  119.2   77.2
9               sub-14  4.0  165.0   82.0  188.1   90.2
10              sub-05  4.8  145.7   66.9  149.3   66.9
11              sub-12  4.8  159.9   76.6  171.5   83.9
12              sub-13  4.8  154.9   83.8  174.1   91.9
13  sub-25_obj-17bqgcb  5.6  115.8   68.1  128.4   74.6
14              sub-26  5.6  173.4   94.1  182.4   95.6
15              sub-27  5.6  148.6   73.3  158.8   73.9

Variant, pixel size from the needle footprints, no background: 1 of 20 pairs at p < 0.05; smallest p 0.0279
  position  kV_a  kV_b  n_a  n_b    mean_a    mean_b     diff       t       p
2   centre   2.4   4.8    4    3  115.4842  157.5198  42.0357 -3.0661  0.0279
  and with the exclusion-zone ROIs dropped: 4 of 20 pairs at p < 0.05; smallest p 0.0055

Variant, pixel size from the needle footprints, footprint background subtracted: 0 of 20 pairs at p < 0.05; smallest p 0.1445
Empty DataFrame
Columns: [position, kV_a, kV_b, n_a, n_b, mean_a, mean_b, diff, t, p]
Index: []
  and with the exclusion-zone ROIs dropped: 1 of 20 pairs at p < 0.05; smallest p 0.0398
Variant, end ROIs inside the exclusion zone dropped, no background: 4 of 20 pairs at p < 0.05; smallest p 0.0075; normalised centre means by kV {2.4: 117.5, 3.2: 122.4, 4.0: 114.2, 4.8: 148.9, 5.6: 147.9}
    position  kV_a  kV_b    mean_a    mean_b       p
2     centre   2.4   4.8  117.4656  148.8657  0.0298
3     centre   2.4   5.6  117.4656  147.8602  0.0075
8     centre   4.0   5.6  114.2127  147.8602  0.0164
14  half-way   3.2   4.0   98.1740   86.1356  0.0459

Variant, end ROIs inside the exclusion zone dropped, footprint background subtracted: 2 of 20 pairs at p < 0.05; smallest p 0.0426; normalised centre means by kV {2.4: 166.8, 3.2: 142.8, 4.0: 126.9, 4.8: 175.1, 5.6: 170.0}
    position  kV_a  kV_b    mean_a    mean_b       p
8     centre   4.0   5.6  126.8782  170.0271  0.0426
14  half-way   3.2   4.0   96.4259   76.1155  0.0432

Result: not reproduced, resting on the main reading. Under the main reading 1 of 20 pairs differs at p < 0.05: the normalised centre, 108.6% at 2.4 kV against 136.9% at 4.8 kV (p = 0.0037). The half-way values do not differ (smallest p = 0.12). The pair stays significant whichever 2.4 kV dish is left out (p = 0.0012 to 0.0116). The readings disagree: 10 of 36 give no significant pair and 26 give one to five. With no effect, the chance of at least one p below 0.05 among the 20 uncorrected tests is 0.64 if the tests were independent; they share groups and dishes, and the simulation of this design gives 0.47 with the two positions correlated as in the data (0.50 if they were independent). So a single significant pair is common by chance, though less common than 0.64 suggests. The p = 0.0037 would not pass a Bonferroni threshold for 20 tests (0.0025), which the criterion does not apply. The normalised centre means lie between 108.6% and 136.9% across voltages. The result is fragile in the other direction too: with the footprint background subtracted (a variant the claims file excludes), the main reading finds no pair (smallest p = 0.18) and 30 of 36 readings find none. With the end ROIs inside the exclusion zone dropped, 4 of 20 pairs differ without background and 2 of 20 with it. At the pixel size implied by the needle footprints, 1 of 20 pairs differs without background (the same pair, p = 0.0279) and none with it (smallest p = 0.14).

fig7cd-holds-for-large-array¶

The independence of the normalized distribution from amplitude also holds when rotating multiple packets (10Px4) on the larger array.

Criterion: as fig7b, on the large array with 10Px4 (n = 3), with the same test, positions and rule; the rise of a.u. values with voltage at the three positions is reported. The dataset holds two voltages for this array, 4.8 kV (2 dishes) and 6.4 kV (3 dishes); the paper's other voltages for Fig. 7C and 7D are not in it, so one pair is tested.

In [12]:
t7cd = pair_tests(10, MAIN, "a")
print("Main reading (normalisation a):")
print(t7cd.round(4).to_string())
df = table(10, MAIN)
print("\nPer dish:")
print(df[["file", "kV", "periph", "half", "center", "nc_a", "nh_a", "nc_b", "nh_b"]].round(1).to_string())
g = df.groupby("kV")[["periph", "half", "center"]].mean()
print("\nRise in a.u. from 4.8 to 6.4 kV (ratio of means):", (g.loc[6.4] / g.loc[4.8]).round(2).to_dict())
s7cd = readings_summary(10)
print("\nEvery reading:")
print(s7cd.round(4).to_string())
print("\nReadings with no pair at p < 0.05:", int((s7cd.n_p_below_05 == 0).sum()), "of", len(s7cd))
s7cd_bg = readings_summary(10, background)
print("\nVariant with the footprint background subtracted, every reading:")
print(s7cd_bg.round(4).to_string())
print("Readings with no pair at p < 0.05:", int((s7cd_bg.n_p_below_05 == 0).sum()), "of", len(s7cd_bg))
print("\nMain reading with background subtracted:")
print(pair_tests(10, MAIN, "a", background).round(4).to_string())
print("Per dish, with background subtracted:")
print(table(10, MAIN, background)[["file", "kV", "periph", "half", "center", "nc_a", "nh_a", "nc_b", "nh_b"]].round(1).to_string())
for bgname, bg in (("no background", None), ("footprint background subtracted", background)):
    t = inner_pairs(10, bg)
    print(f"\nVariant, end ROIs inside the exclusion zone dropped, {bgname}:")
    print(t.round(4).to_string())
for bgname, bg in (("no background", None), ("footprint background subtracted", background)):
    t = pair_tests(10, FP, "a", bg)
    ti = inner_pairs(10, bg, INNER_FP)
    print(f"\nVariant, pixel size from the needle footprints, {bgname}: centre p = {t.p.iloc[0]:.4f} "
          f"({t.mean_a.iloc[0]:.1f}% against {t.mean_b.iloc[0]:.1f}%), half-way p = {t.p.iloc[1]:.4f}; "
          f"with the exclusion-zone ROIs dropped, centre p = {ti.p.iloc[0]:.4f}, half-way p = {ti.p.iloc[1]:.4f}")
Main reading (normalisation a):
   position  kV_a  kV_b  n_a  n_b    mean_a    mean_b      diff       t       p
0    centre   4.8   6.4    2    3  232.6258  125.9528 -106.6730  3.1995  0.0494
1  half-way   4.8   6.4    2    3   94.7837   46.7986  -47.9851  1.8625  0.1594

Per dish:
                file   kV  periph    half  center   nc_a   nh_a   nc_b   nh_b
0             sub-02  4.8   684.5   853.8  1852.0  270.6  124.7  274.1  125.3
1  sub-15_obj-znj27w  4.8  1461.7   947.9  2845.9  194.7   64.8  208.2   66.9
2             sub-04  6.4  3229.8  2140.9  4059.6  125.7   66.3  139.7   69.0
3             sub-07  6.4  4575.0  1524.7  4683.3  102.4   33.3  141.5   43.7
4             sub-11  6.4  3566.1  1454.4  5342.0  149.8   40.8  206.2   56.8

Rise in a.u. from 4.8 to 6.4 kV (ratio of means): {'periph': 3.53, 'half': 1.89, 'center': 2.0}
Every reading:
     scale count diagonals centre source norm  n_tests  n_p_below_05   min_p  max_abs_diff
0   camera  both      both      a    raw    a        2             1  0.0494      106.6730
1   camera  both      both      a    raw    b        2             0  0.1262       78.6885
2   camera  both      both      a  loess    a        2             0  0.2773      142.1865
3   camera  both      both      a  loess    b        2             0  0.3881      117.7976
4   camera  both      both      b    raw    a        2             0  0.1139       67.3669
5   camera  both      both      b    raw    b        2             0  0.1938       39.5998
6   camera  both        d1      a    raw    a        2             1  0.0027      157.4427
7   camera  both        d1      a    raw    b        2             1  0.0067      127.5610
8   camera  both        d1      a  loess    a        2             0  0.2064      325.1204
9   camera  both        d1      a  loess    b        2             0  0.2615      254.4350
10  camera  both        d1      b    raw    a        2             1  0.0007      116.7293
11  camera  both        d1      b    raw    b        2             1  0.0053       82.1037
12  camera  both        d2      a    raw    a        2             0  0.2233       63.2350
13  camera  both        d2      a    raw    b        2             0  0.3298       30.8881
14  camera  both        d2      a  loess    a        2             0  0.9552        6.4182
15  camera  both        d2      a  loess    b        2             0  0.8603       18.8398
16  camera  both        d2      b    raw    a        2             0  0.2233       40.7155
17  camera  both        d2      b    raw    b        2             0  0.3298       30.8881
18  camera   per      both      a    raw    a        2             0  0.1161      223.1895
19  camera   per      both      a    raw    b        2             0  0.0984      221.6592
20  camera   per      both      a  loess    a        2             0  0.2525      223.7742
21  camera   per      both      a  loess    b        2             0  0.3129      271.9355
22  camera   per      both      b    raw    a        2             0  0.0970      153.1493
23  camera   per      both      b    raw    b        2             0  0.0869      150.5094
24  camera   per        d1      a    raw    a        2             1  0.0237      328.4575
25  camera   per        d1      a    raw    b        2             1  0.0196      336.5575
26  camera   per        d1      a  loess    a        2             0  0.2151      562.3975
27  camera   per        d1      a  loess    b        2             0  0.2318      548.0606
28  camera   per        d1      b    raw    a        2             1  0.0017      244.6615
29  camera   per        d1      b    raw    b        2             1  0.0049      249.6098
30  camera   per        d2      a    raw    a        2             0  0.2665      148.8533
31  camera   per        d2      a    raw    b        2             0  0.4275      106.7609
32  camera   per        d2      a  loess    a        2             0  0.9338       12.6254
33  camera   per        d2      a  loess    b        2             0  0.9781        4.1896
34  camera   per        d2      b    raw    a        2             0  0.2665       97.0395
35  camera   per        d2      b    raw    b        2             0  0.6325       51.4091

Readings with no pair at p < 0.05: 27 of 36
Variant with the footprint background subtracted, every reading:
     scale count diagonals centre source norm  n_tests  n_p_below_05   min_p  max_abs_diff
0   camera  both      both      a    raw    a        2             0  0.0705      184.1337
1   camera  both      both      a    raw    b        2             0  0.1004      160.1306
2   camera  both      both      a  loess    a        2             0  0.2389      818.0569
3   camera  both      both      a  loess    b        2             0  0.3375     5208.1020
4   camera  both      both      b    raw    a        2             0  0.1077      130.2326
5   camera  both      both      b    raw    b        2             0  0.1859       98.0844
6   camera  both        d1      a    raw    a        2             1  0.0145      284.1408
7   camera  both        d1      a    raw    b        2             1  0.0205      252.8736
8   camera  both        d1      a  loess    a        2             0  0.3032     1649.1133
9   camera  both        d1      a  loess    b        2             0  0.3405    10487.2466
10  camera  both        d1      b    raw    a        2             1  0.0065      222.1608
11  camera  both        d1      b    raw    b        2             1  0.0116      183.4442
12  camera  both        d2      a    raw    a        2             0  0.2328      112.6595
13  camera  both        d2      a    raw    b        2             0  0.3705       67.3875
14  camera  both        d2      a  loess    a        2             0  0.7319       92.0013
15  camera  both        d2      a  loess    b        2             0  0.7810       71.0426
16  camera  both        d2      b    raw    a        2             0  0.2606       64.7042
17  camera  both        d2      b    raw    b        2             0  0.3705       36.7621
18  camera   per      both      a    raw    a        2             0  0.1423      377.6489
19  camera   per      both      a    raw    b        2             0  0.0743      452.3725
20  camera   per      both      a  loess    a        2             0  0.2686    11595.6466
21  camera   per      both      a  loess    b        2             2  0.0367     1264.4575
22  camera   per      both      b    raw    a        2             0  0.1148      259.4286
23  camera   per      both      b    raw    b        2             1  0.0361      319.5774
24  camera   per        d1      a    raw    a        2             1  0.0411      612.6347
25  camera   per        d1      a    raw    b        2             1  0.0220      722.1836
26  camera   per        d1      a  loess    a        2             0  0.3795      646.8981
27  camera   per        d1      a  loess    b        2             2  0.0083     2701.5782
28  camera   per        d1      b    raw    a        2             1  0.0025      451.9588
29  camera   per        d1      b    raw    b        2             1  0.0008      545.2047
30  camera   per        d2      a    raw    a        2             0  0.2620      246.3245
31  camera   per        d2      a    raw    b        2             0  0.3835      182.5613
32  camera   per        d2      a  loess    a        2             0  0.6406      213.5707
33  camera   per        d2      a  loess    b        2             0  0.7008      172.6632
34  camera   per        d2      b    raw    a        2             0  0.3024      165.7902
35  camera   per        d2      b    raw    b        2             0  0.5678       93.9501
Readings with no pair at p < 0.05: 25 of 36

Main reading with background subtracted:
   position  kV_a  kV_b  n_a  n_b    mean_a    mean_b      diff       t       p
0    centre   4.8   6.4    2    3  312.8647  128.7311 -184.1337  2.7548  0.0705
1  half-way   4.8   6.4    2    3   98.0704   41.4244  -56.6460  1.5604  0.2166
Per dish, with background subtracted:
                file   kV  periph    half  center   nc_a   nh_a   nc_b   nh_b
0             sub-02  4.8   390.9   560.1  1558.4  398.7  143.3  416.1  146.2
1  sub-15_obj-znj27w  4.8  1089.7   575.9  2473.9  227.0   52.8  258.2   54.8
2             sub-04  6.4  2851.1  1762.2  3680.9  129.1   61.8  148.1   64.9
3             sub-07  6.4  4194.6  1144.3  4302.9  102.6   27.3  152.7   37.5
4             sub-11  6.4  3258.1  1146.4  5034.0  154.5   35.2  230.3   53.4

Variant, end ROIs inside the exclusion zone dropped, no background:
   position  kV_a  kV_b    mean_a    mean_b       p
0    centre   4.8   6.4  283.7001  185.3961  0.0173
1  half-way   4.8   6.4  114.4439   66.6179  0.1248
Variant, end ROIs inside the exclusion zone dropped, footprint background subtracted:
   position  kV_a  kV_b    mean_a    mean_b       p
0    centre   4.8   6.4  415.1495  199.1151  0.0089
1  half-way   4.8   6.4  128.5018   61.1629  0.1714

Variant, pixel size from the needle footprints, no background: centre p = 0.5736 (249.5% against 226.6%), half-way p = 0.9624; with the exclusion-zone ROIs dropped, centre p = 0.5736, half-way p = 0.9624

Variant, pixel size from the needle footprints, footprint background subtracted: centre p = 0.2531 (326.2% against 254.6%), half-way p = 0.9974; with the exclusion-zone ROIs dropped, centre p = 0.2531, half-way p = 0.9974

Result: not reproduced, resting on the main reading. Under the main reading the normalised centre is 232.6% at 4.8 kV (2 dishes) and 126.0% at 6.4 kV (3 dishes), p = 0.0494; the half-way values do not differ (p = 0.16). Both 4.8 kV dishes (270.6% and 194.7%) lie above all three 6.4 kV dishes (102.4% to 149.8%). By the criterion the absence is not shown. 27 of 36 readings find no difference, and with the background subtracted the main reading gives p = 0.0705 (25 of 36 readings find none), so the outcome rests on the main reading and on a two-dish group; with two dishes against three a non-significant reading could not show absence either. With the end ROIs inside the exclusion zone dropped the centre differs more clearly (p = 0.0173, and 0.0089 with background subtracted). At the pixel size implied by the needle footprints the difference is gone: 249.5% against 226.6%, p = 0.57 (p = 0.25 with background subtracted), so the outcome rests on the camera scale as well. The a.u. values rise from 4.8 to 6.4 kV at all three positions (ratios 3.53 at the periphery, 1.89 half-way and 2.00 at the centre).

discussion-independent-of-array-size¶

Remote targeting, within the studied limits, depends neither on pulse amplitude nor on array size, so findings from small arrays can be extrapolated to larger inter-electrode distances.

Criterion: reproduced when fig7b-targeting-independent-of-amplitude and fig7cd-holds-for-large-array are both reproduced. The normalised profiles of the two arrays are shown side by side, on distance as a fraction of the ROI span; the small array received 1Px4 and the large array 10Px4, so the comparison mixes array size with packet number.

In [13]:
fig, ax = plt.subplots(figsize=(7, 4.5))
for reps, ls in ((1, "-"), (10, "--")):
    prof = folded_profiles(reps)
    for kv in sorted({res[p]["kV"] for p in prof}):
        Q = np.array([100 * prof[p] / prof[p][-1] for p in prof if res[p]["kV"] == kv])
        x = np.linspace(0, 1, Q.shape[1])
        ax.plot(x, Q.mean(0), ls, label=f"{'small' if reps == 1 else 'large'} {reps}Px4 {kv} kV")
        print(f"{'small' if reps == 1 else 'large'} {kv} kV: centre {Q.mean(0)[0]:.1f}%, minimum {Q.mean(0).min():.1f}% at "
              f"{x[np.argmin(Q.mean(0))]:.2f} of the span")
ax.set_xlabel("distance from centre, fraction of ROI span")
ax.set_ylabel("% of most peripheral ROI")
ax.legend(fontsize=7)
plt.show()
print("fig7b pairs at p < 0.05 (main):", int((t7b.p < 0.05).sum()), "; fig7cd pairs at p < 0.05 (main):", int((t7cd.p < 0.05).sum()))
small 2.4 kV: centre 109.5%, minimum 87.3% at 0.55 of the span
small 3.2 kV: centre 129.9%, minimum 90.4% at 0.68 of the span
small 4.0 kV: centre 123.8%, minimum 84.8% at 0.68 of the span
small 4.8 kV: centre 140.1%, minimum 82.0% at 0.59 of the span
small 5.6 kV: centre 134.5%, minimum 75.4% at 0.59 of the span
large 4.8 kV: centre 212.3%, minimum 75.4% at 0.78 of the span
large 6.4 kV: centre 145.0%, minimum 40.9% at 0.76 of the span
No description has been provided for this image
fig7b pairs at p < 0.05 (main): 1 ; fig7cd pairs at p < 0.05 (main): 1

Result: not reproduced, resting on the main reading. Both claims it depends on are not reproduced under the main reading, each by one comparison near or below p = 0.05, so by its criterion this claim is not reproduced. The side-by-side profiles cannot isolate array size, since the arrays received different packet numbers.

Packet 2 dishes against Figs. 8 and 9 (not a test of those claims)¶

The fig8 and fig9 claims are blocked here, because their criteria need Packets 1 and 3 and the dishes with gaps, which are in other dandisets. Two conditions of this dandiset match conditions of those figures: 4.0 kV 1Px4 is the Packet 2 group of Fig. 8 (if Fig. 8 used the small array, which its legend does not say), and 6.4 kV 10Px4 on the large array is the 0 mm group of Fig. 9. Their values are set against the paper's numbers for Packet 2 in Section 3.4 (about 120 a.u. near the electrodes, 215 to 235 a.u. in the centre, 183% normalised centre), as a check of whether the dataset's a.u. are on the paper's scale. The text gives no a.u. for the 0 mm group of Fig. 9, only ratios to the gap groups, so that profile is printed without a reference.

In [14]:
PAPER8 = dict(periph=120, center=(215, 235), nc=183)
for bgname, bg in (("no background", None), ("footprint background subtracted", background)):
    d = table(1, MAIN, bg)
    d = d[d.kV == 4.0]
    print(f"4.0 kV 1Px4, main reading, {bgname}:")
    print(d[["file", "periph", "half", "center", "nc_a", "nc_b"]].round(1).to_string())
    print(f"  mean periphery {d.periph.mean():.1f} a.u. (paper about {PAPER8['periph']}), centre {d.center.mean():.1f} a.u. "
          f"(paper {PAPER8['center'][0]} to {PAPER8['center'][1]}), normalised centre {d.nc_a.mean():.1f}% (a) / "
          f"{d.nc_b.mean():.1f}% (b), ratio of means {100 * d.center.mean() / d.periph.mean():.1f}% (paper {PAPER8['nc']}%)")
    di = inner_table(1, bg)
    di = di[di.kV == 4.0]
    print(f"  exclusion-zone variant: periphery {di.periph.mean():.1f}, centre {di.center.mean():.1f}, normalised centre {di.nc_a.mean():.1f}%")

for bgname, bg in (("no background", None), ("footprint background subtracted", background)):
    d = table(10, MAIN, bg)
    d = d[d.kV == 6.4]
    print(f"\n6.4 kV 10Px4 (Fig. 9, 0 mm), main reading, {bgname}: periphery {d.periph.mean():.1f}, half-way {d.half.mean():.1f}, "
          f"centre {d.center.mean():.1f} a.u.; normalised centre {d.nc_a.mean():.1f}% (per dish {d.nc_a.round(1).tolist()})")
prof = folded_profiles(10)
Q = np.array([prof[p] for p in prof if res[p]["kV"] == 6.4])
x = np.linspace(0, 1, Q.shape[1])
dbb = table(10, ("camera", "both", "both", "b", "raw"))
print("\n6.4 kV 10Px4, two definitions of the centre, % of the mean of the end ROIs, mean over dishes:\n"
      f"  centre reading a (one ROI centred on the array centre; the main reading): {table(10, MAIN).query('kV == 6.4').nc_a.mean():.1f}%\n"
      f"  centre reading b (mean of the two grid ROIs beside the centre, the first position of the folded profile): "
      f"{dbb[dbb.kV == 6.4].nc_a.mean():.1f}%")
Pn = 100 * Q / Q[:, -1:]
print(f"Folded normalised profile (each dish to its own end ROIs, as in the discussion section): first position {Pn.mean(0)[0]:.1f}%, "
      f"minimum {Pn.mean(0).min():.1f}% at {x[np.argmin(Pn.mean(0))]:.2f} of the span")
print("\n6.4 kV 10Px4 folded profile (no background), mean over 3 dishes at tenths of the span (0 centre, 1 periphery):")
print(pd.DataFrame(dict(position=x, au=Q.mean(0), pct=100 * Q.mean(0) / Q.mean(0)[-1])).iloc[np.searchsorted(x, np.linspace(0, 1, 11)).clip(0, len(x) - 1)]
      .round(2).to_string(index=False))
4.0 kV 1Px4, main reading, no background:
     file  periph   half  center   nc_a   nc_b
7  sub-03   539.8  477.4   656.3  121.6  128.3
8  sub-08   803.2  680.8   860.1  107.1  110.3
9  sub-14   501.5  463.0   640.0  127.6  130.1
  mean periphery 614.9 a.u. (paper about 120), centre 718.8 a.u. (paper 215 to 235), normalised centre 118.8% (a) / 122.9% (b), ratio of means 116.9% (paper 183%)
  exclusion-zone variant: periphery 639.0, centre 718.8, normalised centre 114.2%
4.0 kV 1Px4, main reading, footprint background subtracted:
     file  periph   half  center   nc_a   nc_b
7  sub-03   321.8  259.4   438.3  136.2  158.6
8  sub-08   526.3  403.9   583.2  110.8  119.2
9  sub-14   213.2  174.7   351.7  165.0  188.1
  mean periphery 353.8 a.u. (paper about 120), centre 457.7 a.u. (paper 215 to 235), normalised centre 137.3% (a) / 155.3% (b), ratio of means 129.4% (paper 183%)
  exclusion-zone variant: periphery 377.9, centre 457.7, normalised centre 126.9%

6.4 kV 10Px4 (Fig. 9, 0 mm), main reading, no background: periphery 3790.3, half-way 1706.7, centre 4695.0 a.u.; normalised centre 126.0% (per dish [125.7, 102.4, 149.8])

6.4 kV 10Px4 (Fig. 9, 0 mm), main reading, footprint background subtracted: periphery 3434.6, half-way 1351.0, centre 4339.3 a.u.; normalised centre 128.7% (per dish [129.1, 102.6, 154.5])

6.4 kV 10Px4, two definitions of the centre, % of the mean of the end ROIs, mean over dishes:
  centre reading a (one ROI centred on the array centre; the main reading): 126.0%
  centre reading b (mean of the two grid ROIs beside the centre, the first position of the folded profile): 145.0%
Folded normalised profile (each dish to its own end ROIs, as in the discussion section): first position 145.0%, minimum 40.9% at 0.76 of the span

6.4 kV 10Px4 folded profile (no background), mean over 3 dishes at tenths of the span (0 centre, 1 periphery):
 position      au    pct
     0.00 5390.17 142.21
     0.11 4427.53 116.81
     0.22 2793.91  73.71
     0.31 2089.82  55.14
     0.42 2093.73  55.24
     0.51 1814.66  47.88
     0.62 1817.59  47.95
     0.71 1686.21  44.49
     0.82 2077.18  54.80
     0.91 2389.79  63.05
     1.00 3790.30 100.00

The 4.0 kV 1Px4 dishes are not on the paper's Fig. 8 scale: without background their periphery is 614.9 a.u. and their centre 718.8 a.u., against about 120 and 215 to 235 a.u., and their normalised centre is 118.8% (ratio of means 116.9%) against 183%. With the footprint background subtracted they are 353.8 and 457.7 a.u. and 137.3%, still far from the paper's values. Either Fig. 8's Packet 2 dishes are not these dishes, or the paper's a.u. were scaled or corrected in a way the text does not describe. For the 6.4 kV 10Px4 dishes the centre ROI of the main reading is 126.0% of the end ROIs; the first position of the folded profile, which is the mean of the two grid ROIs beside the centre (centre reading b) and is the value given in the discussion section, is 145.0%. The folded profile has its minimum, 40.9%, at 0.76 of the span. The text gives no a.u. for this group to compare with. These numbers decide none of the claims.

Summary¶

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.

Claim Outcome
fig4-unipolar-strongest blocked (data_missing)
fig4-bipolar-cancellation blocked (data_missing)
fig5a-center-enhanced-without-rotation blocked (other: data in dandiset 000686)
fig5bc-rotation-focuses-center blocked (other: data in dandiset 000686)
fig6a-more-packets-more-uptake blocked (other: data in dandiset 000875)
fig6b-normalized-center-by-packets blocked (other: data in dandiset 000875)
fig7a-amplitude-scales-uptake not reproduced
fig7b-targeting-independent-of-amplitude not reproduced (assumed: main reading)
fig7cd-holds-for-large-array not reproduced (assumed: main reading)
discussion-independent-of-array-size not reproduced (assumed: main reading)
fig8a-packets-differ-near-electrodes blocked (other: data in dandiset 001027)
fig8b-packet2-best-targeting blocked (other: data in dandiset 001027)
fig9-gap-boosts-targeting blocked (other: data in dandiset 001028)
fig9c-restricted-to-center blocked (other: data in dandiset 001028)

How the analysis differs from the paper's:

  • The paper measured ROIs in MetaMorph on images whose electrode positions the experimenters knew; here the electrodes are found by fitting four dark disks, and the ROIs placed from them.
  • The pixel size is not in the dataset; the camera scale (0.65 um) is used. The needle footprints rule out the 8.4 mm scale of round 1, which would put the end ROIs on the needles, but do not confirm the camera scale: they imply about 0.68 um (small array) and 0.75 um (large array). At that scale, a variant, fig7cd's centre difference disappears (p = 0.57).
  • At the camera scale the paper's ROI spans end inside its own 5% exclusion zone; the spans are kept as stated, and a variant drops the end ROIs inside the zone.
  • ROI width across the diagonal is not given; square ROIs are used.
  • Whether a background was subtracted is not stated; the claims file fixes none, and the footprint background is shown only as a variant. It changes fig7a from about 6-fold to near the paper's 16-fold and removes the one significant pair of fig7b. A reader is asked whether the claims file reads the Methods correctly here.
  • 2.4 kV has four dishes here, against n = 3 in the paper; all four are used, with leave-one-out shown for fig7a and fig7b.
  • The large array has two voltages here (4.8 kV with 2 dishes and 6.4 kV with 3).
  • The packet is not named in the files; Packet 2 is assumed, as in Fig. 7 and the dandiset's title.

What was found about the dataset:

  • It holds only the images of Fig. 7: no single-cell time-lapse data (Fig. 4) and none of the experiments of Figs. 5, 6, 8 and 9 (other than, possibly, dishes shared with Fig. 7), which appear, from their titles and descriptions, to be in other dandisets.
  • The array size, pixel size, packet, electrode positions and ROI measurements are not in the files.
  • Subject ids are reused for different dishes, and the archive counts 19 subjects for 21 dishes.
  • The dark floor of the images differs between dishes and sessions (33 to 81 a.u.), and the paper does not say whether a background was removed; the fold changes of fig7a depend on it.
  • The 4.0 kV 1Px4 dishes, the Packet 2 condition of Fig. 8 if Fig. 8 used the small array (its legend does not say), read about 615 a.u. at the periphery and 719 a.u. in the centre, against about 120 and 215 to 235 a.u. in the paper.
  • Of the 21 files, 7 name no detector, objective or channel.