<jupyter_start><jupyter_text>SpikeForest analysis This notebook represents a complete spikeforest analysis of the bionet studies. You should execute the first few cells and then skip down to the section of interest below.<jupyter_code># Only run this cell if you are running this on a hosted runtime that does not have these packages installed
# %%capture is used to suppress the output... this should take up to a minute to complete
%%capture
!pip install spikeforest
!pip install git+https://github.com/magland/spikeforest_batch_run
# Import the python packages -- autoreload is used for development purposes
%load_ext autoreload
%autoreload 2

import os
os.environ['VDOMR_MODE']='COLAB'

import spikeforest as sf
from kbucket import client as kb
import vdomr as vd
## Configure readonly access to kbucket -- use this if you only want to browse the results ---
sf.kbucketConfigRemote(name='spikeforest1-readonly')
## Configure read/write access to kbucket -- use this if you are preparing the studies or the processing batches
sf.kbucketConfigRemote(name='spikeforest1-readwrite',ask_password=True)<jupyter_output>Enter password: ··········
Pairio user set to spikeforest. Test succeeded.<jupyter_text>Prepare recordings<jupyter_code>def read_text_file(path):
  path2=kb.realizeFile(path)
  if path2 is None:
    raise Exception('Unable to realize file: '+path)
  with open(path2,'r') as f:
    return f.read()
  
def prepare_bionet_studies(*,basedir, channels, study_set_name='bionet', suffix=''):
  studies=[]
  recordings=[]
  names=['bionet_drift','bionet_shuffle','bionet_static']
  for name in names:
    study_name=name+suffix
    study_dir=basedir+'/bionet/'+name
    description=read_text_file(study_dir+'/readme.txt')
    study0=dict(
        name=study_name,
        study_set=study_set_name,
        directory=study_dir,
        description=description
    )
    studies.append(study0)
    dd=kb.readDir(study_dir)
    for dsname in dd['dirs']:
        dsdir='{}/{}'.format(study_dir,dsname)
        rec0=dict(
            name=dsname,
            study=study_name,
            description='',
            directory=dsdir,
            channels=channels
        )
        if len(rec0['channels'])>0:
          units=sf.sf_batch.select_units_on_channels(
              recording_dir=dsdir,
              firings=dsdir+'/firings_true.mda',
              channels=rec0['channels']
          )
          rec0['units_true']=units
        recordings.append(rec0)
  return studies, recordings

def prepare_bionet8c_studies(*,basedir):
  channels = list(range(8))
  
  studies, recordings = prepare_bionet_studies(basedir=basedir, channels=channels, study_set_name='bionet', suffix='_8c')
  return studies, recordings


def prepare_bionet32c_studies(*,basedir):
  channels = list(range(32))
  
  studies, recordings = prepare_bionet_studies(basedir=basedir, channels=channels, study_set_name='bionet', suffix='_32c')
  return studies, recordings


def prepare_magland_synth_studies(*,basedir):
  study_set_name='magland_synth'
  studies=[]
  recordings=[]
  names=[]
  names=names+['datasets_noise10_K10_C4','datasets_noise10_K10_C8']
  names=names+['datasets_noise10_K20_C4','datasets_noise10_K20_C8']
  names=names+['datasets_noise20_K10_C4','datasets_noise20_K10_C8']
  names=names+['datasets_noise20_K20_C4','datasets_noise20_K20_C8']
  description=read_text_file(basedir+'/magland_synth/readme.txt')
  for name in names:
    study_name='magland_synth_'+name[9:]
    study_dir=basedir+'/magland_synth/'+name
    study0=dict(
        name=study_name,
        study_set=study_set_name,
        directory=study_dir,
        description=description
    )
    studies.append(study0)
    dd=kb.readDir(study_dir)
    for dsname in dd['dirs']:
        dsdir='{}/{}'.format(study_dir,dsname)
        recordings.append(dict(
            name=dsname,
            study=study_name,
            directory=dsdir,
            description='One of the recordings in the {} study'.format(study_name)
        ))
  return studies, recordings

def prepare_mearec_tetrode_studies(*,basedir):
  study_set_name='mearec_tetrode'
  studies=[]
  recordings=[]
  names=[]
  names=names+['datasets_noise10_K10_C4','datasets_noise10_K20_C4']
  names=names+['datasets_noise20_K10_C4','datasets_noise20_K20_C4']
  description=read_text_file(basedir+'/mearec_synth/tetrode/readme.txt')
  for name in names:
    study_name='mearec_tetrode_'+name[9:]
    study_dir=basedir+'/mearec_synth/tetrode/'+name
    study0=dict(
        name=study_name,
        study_set=study_set_name,
        directory=study_dir,
        description=description
    )
    studies.append(study0)
    dd=kb.readDir(study_dir)
    for dsname in dd['dirs']:
        dsdir='{}/{}'.format(study_dir,dsname)
        recordings.append(dict(
            name=dsname,
            study=study_name,
            directory=dsdir,
            description='One of the recordings in the {} study'.format(study_name)
        ))
  return studies, recordings

def prepare_mearec_neuronexus_studies(*,basedir):
  study_set_name='mearec_neuronexus'
  studies=[]
  recordings=[]
  names=[]
  names=names+['datasets_noise10_K10_C32','datasets_noise10_K20_C32','datasets_noise10_K40_C32']
  names=names+['datasets_noise20_K10_C32','datasets_noise20_K20_C32','datasets_noise20_K40_C32']
  description=read_text_file(basedir+'/mearec_synth/neuronexus/readme.txt')
  for name in names:
    study_name='mearec_neuronexus_'+name[9:]
    study_dir=basedir+'/mearec_synth/neuronexus/'+name
    study0=dict(
        name=study_name,
        study_set=study_set_name,
        directory=study_dir,
        description=description
    )
    studies.append(study0)
    dd=kb.readDir(study_dir)
    for dsname in dd['dirs']:
        dsdir='{}/{}'.format(study_dir,dsname)
        recordings.append(dict(
            name=dsname,
            study=study_name,
            directory=dsdir,
            description='One of the recordings in the {} study'.format(study_name)
        ))
  return studies, recordings
basedir='kbucket://15734439d8cf/groundtruth'
#basedir='/mnt/ceph/users/jjun/groundtruth'
studies,recordings=prepare_bionet8c_studies(basedir=basedir)
kb.saveObject(dict(studies=studies,recordings=recordings),key=dict(name='spikeforest_bionet8c_recordings'))
studies,recordings=prepare_bionet32c_studies(basedir=basedir)
kb.saveObject(dict(studies=studies,recordings=recordings),key=dict(name='spikeforest_bionet32c_recordings'))
studies,recordings=prepare_mearec_neuronexus_studies(basedir=basedir)
kb.saveObject(dict(studies=studies,recordings=recordings),key=dict(name='spikeforest_mearec_neuronexus_recordings'))
studies,recordings=prepare_magland_synth_studies(basedir=basedir)
kb.saveObject(dict(studies=studies,recordings=recordings),key=dict(name='spikeforest_magland_synth_recordings'))<jupyter_output>Already on server.<jupyter_text>Create summarize recordings batches<jupyter_code>def create_summarize_recordings_batch(*,recordings_name,batch_name):
  print('Creating summarize_recordings batch: '+batch_name)
  SF=sf.SFData()
  SF.loadRecordings(key=dict(name=recordings_name))
  
  jobs=[]
  for name in SF.studyNames():
    study=SF.study(name)
    for recname in study.recordingNames():
      R=study.recording(recname)
      job=dict(
          command='summarize_recording',
          label=R.name(),
          recording=R.getObject()
      )
      jobs.append(job)
  batch=dict(jobs=jobs)
  print('Number of jobs: {}'.format(len(jobs)))
  kb.saveObject(key=dict(batch_name=batch_name),object=batch)
create_summarize_recordings_batch(recordings_name='spikeforest_bionet8c_recordings',batch_name='summarize_recordings_bionet8c')
create_summarize_recordings_batch(recordings_name='spikeforest_bionet32c_recordings',batch_name='summarize_recordings_bionet32c')
create_summarize_recordings_batch(recordings_name='spikeforest_magland_synth_recordings',batch_name='summarize_recordings_magland_synth')
create_summarize_recordings_batch(recordings_name='spikeforest_mearec_tetrode_recordings',batch_name='summarize_recordings_mearec_tetrode')
create_summarize_recordings_batch(recordings_name='spikeforest_mearec_neuronexus_recordings',batch_name='summarize_recordings_mearec_neuronexus')<jupyter_output>Creating summarize_recordings batch: summarize_recordings_bionet8c
Number of jobs: 36
Creating summarize_recordings batch: summarize_recordings_bionet32c
Number of jobs: 36
Creating summarize_recordings batch: summarize_recordings_magland_synth
Number of jobs: 80
Already on server.
Creating summarize_recordings batch: summarize_recordings_mearec_tetrode
Number of jobs: 40
Already on server.
Creating summarize_recordings batch: summarize_recordings_mearec_neuronexus
Number of jobs: 60
Already on server.<jupyter_text>To run these batches, go to a computer with resources somewhere and run something like:```bin/sf_run_batch [name_of_batch] --run_prefix "srun -c 2 -n 40"```"srun" in Flatiron cluster reqiures you to run the following before```module load srunmodule load matlab```To use GPU, run```bin/sf_run_batch [name_of_batch] --run_prefix "srun -c 2 -n 40 --gres=gpu:2 -p gpu"```  where bin/sf_run_batch is found in the spikeforest_batch_run repository.Alternatively, you can test run it in this notebook using the following commands: To clear previous batch result, run```bin/sf_run_batch_command clear [name_of_batch]```<jupyter_code>## Note: usually you would not run this cell -- see the note above.

import spikeforest_batch_run as sbr
# Execute prepareBatch once (serially)
sbr.prepareBatch(batch_name='summarize_recordings_bionet')

# Execute runBatch many times in parallel
sbr.runBatch(batch_name='summarize_recordings_bionet')

# Execute assembleBatchResults once (serially)
sbr.assembleBatchResults(batch_name='summarize_recordings_bionet')<jupyter_output><empty_output><jupyter_text>Browse recordings<jupyter_code>SF=sf.SFData()
SF.loadRecordings(key=dict(name='spikeforest_bionet_recordings'))
SF.loadRecordings(key=dict(name='spikeforest_magland_synth_recordings'))
SF.loadProcessingBatch(key=dict(batch_name='summarize_recordings_bionet',name='job_results'))
SF.loadProcessingBatch(key=dict(batch_name='summarize_recordings_magland_synth',name='job_results'))
X=sf.SFSelectWidget(sfdata=SF,mode='recording')
display(X)
R=X.recording()
display(R.plot('timeseries'))
display(R.plot('waveforms_true'))
display(R.trueUnitsInfo())
R.plotNames()<jupyter_output><empty_output><jupyter_text>Create spike sorting batches<jupyter_code>SF=sf.SFData()
SF.loadRecordings(key=dict(name='spikeforest_bionet8c_recordings'))
SF.loadRecordings(key=dict(name='spikeforest_bionet32c_recordings'))
SF.loadRecordings(key=dict(name='spikeforest_magland_synth_recordings'))
SF.loadRecordings(key=dict(name='spikeforest_mearec_tetrode_recordings'))
SF.loadRecordings(key=dict(name='spikeforest_mearec_neuronexus_recordings'))
SF.loadProcessingBatch(key=dict(batch_name='summarize_recordings_bionet8c',name='job_results'))
SF.loadProcessingBatch(key=dict(batch_name='summarize_recordings_bionet32c',name='job_results'))
SF.loadProcessingBatch(key=dict(batch_name='summarize_recordings_magland_synth',name='job_results'))
SF.loadProcessingBatch(key=dict(batch_name='summarize_recordings_mearec_tetrode',name='job_results'))
SF.loadProcessingBatch(key=dict(batch_name='summarize_recordings_mearec_neuronexus',name='job_results'))
sorter_ms4_thr3=dict(
    name='MountainSort4-thr3',
    processor_name='MountainSort4',
    params=dict(
        detect_sign=-1,
        adjacency_radius=50,
        detect_threshold=3
    )
)

sorter_irc_tetrode=dict(
    name='IronClust-tetrode',
    processor_name='IronClust',
    params=dict(
        detect_sign=-1,
        adjacency_radius=50,
        detect_threshold=5,
        prm_template_name="tetrode_template.prm"
    )
)

sorter_irc_drift=dict(
    name='IronClust-drift',
    processor_name='IronClust',
    params=dict(
        detect_sign=-1,
        adjacency_radius=50,
        prm_template_name="template_drift.prm"
    )
)

sorter_sc=dict(
    name='SpykingCircus',
    processor_name='SpykingCircus',
    params=dict(
        detect_sign=-1,
        adjacency_radius=50
    )
)

sorter_ks=dict(
    name='KiloSort',
    processor_name='KiloSort',
    params=dict(
        detect_sign=-1,
        adjacency_radius=50
    )
)
def create_sorting_batch(*,recordings_name,batch_name,sorters):
  print('Creating sorting batch: '+batch_name)
  SF=sf.SFData()
  SF.loadRecordings(key=dict(name=recordings_name))
  
  jobs=[]
  for name in SF.studyNames():
    study=SF.study(name)
    for rname in study.recordingNames():
      R=study.recording(rname)
      for sorter in sorters:
        job=dict(
          command='sort_recording',
          label=sorter['name']+': '+R.name(),
          recording=R.getObject(),
          sorter=sorter
        )
        jobs.append(job)

  batch=dict(jobs=jobs)
  print('Number of jobs: {}'.format(len(jobs)))
  kb.saveObject(key=dict(batch_name=batch_name),object=batch)
#create_sorting_batch(recordings_name='spikeforest_magland_synth_recordings',batch_name='ms4_magland_synth',sorters=[sorter_ms4_thr3])
#create_sorting_batch(recordings_name='spikeforest_magland_synth_recordings',batch_name='irc_magland_synth',sorters=[sorter_irc_tetrode])
#create_sorting_batch(recordings_name='spikeforest_magland_synth_recordings',batch_name='sc_magland_synth',sorters=[sorter_sc])
#create_sorting_batch(recordings_name='spikeforest_magland_synth_recordings',batch_name='ks_magland_synth',sorters=[sorter_ks])

#create_sorting_batch(recordings_name='spikeforest_bionet_recordings',batch_name='ms4_bionet',sorters=[sorter_ms4_thr3])
#create_sorting_batch(recordings_name='spikeforest_bionet_recordings',batch_name='irc_bionet',sorters=[sorter_irc_drift])
#create_sorting_batch(recordings_name='spikeforest_bionet_recordings',batch_name='sc_bionet',sorters=[sorter_sc])
#create_sorting_batch(recordings_name='spikeforest_bionet_recordings',batch_name='ks_bionet',sorters=[sorter_ks])

#create_sorting_batch(recordings_name='spikeforest_bionet32c_recordings',batch_name='ms4_bionet32c',sorters=[sorter_ms4_thr3])
#create_sorting_batch(recordings_name='spikeforest_bionet32c_recordings',batch_name='irc_bionet32c',sorters=[sorter_irc_drift])
#create_sorting_batch(recordings_name='spikeforest_bionet32c_recordings',batch_name='sc_bionet32c',sorters=[sorter_sc])
#create_sorting_batch(recordings_name='spikeforest_bionet32c_recordings',batch_name='ks_bionet32c',sorters=[sorter_ks])

#create_sorting_batch(recordings_name='spikeforest_mearec_tetrode_recordings',batch_name='ms4_mearec_tetrode',sorters=[sorter_ms4_thr3])
#create_sorting_batch(recordings_name='spikeforest_mearec_tetrode_recordings',batch_name='irc_mearec_tetrode',sorters=[sorter_irc_drift])
#create_sorting_batch(recordings_name='spikeforest_mearec_tetrode_recordings',batch_name='sc_mearec_tetrode',sorters=[sorter_sc])
#create_sorting_batch(recordings_name='spikeforest_mearec_tetrode_recordings',batch_name='ks_mearec_tetrode',sorters=[sorter_ks])

#create_sorting_batch(recordings_name='spikeforest_mearec_neuronexus_recordings',batch_name='ms4_mearec_neuronexus',sorters=[sorter_ms4_thr3])
#create_sorting_batch(recordings_name='spikeforest_mearec_neuronexus_recordings',batch_name='irc_mearec_neuronexus',sorters=[sorter_irc_drift])
#create_sorting_batch(recordings_name='spikeforest_mearec_neuronexus_recordings',batch_name='sc_mearec_neuronexus',sorters=[sorter_sc])
#create_sorting_batch(recordings_name='spikeforest_mearec_neuronexus_recordings',batch_name='ks_mearec_neuronexus',sorters=[sorter_ks])
# create batch programatically, same effect as the above cell

vs_sorters = ['ms4', 'irc', 'sc', 'ks']
v_sorters_tetrode = [sorter_ms4_thr3, sorter_irc_tetrode, sorter_sc, sorter_ks]
v_sorters_siprobe = [sorter_ms4_thr3, sorter_irc_drift, sorter_sc, sorter_ks]
vs_recordings_tetrode = ['magland_synth', 'mearec_tetrode']  
vs_recordings_siprobe = ['bionet8c', 'bionet32c', 'mearec_neuronexus']

for recording in vs_recordings_tetrode:
  for sorter, s_sorter in zip(v_sorters_tetrode, vs_sorters):
    recordings_name = 'spikeforest_{}_recordings'.format(recording)
    batch_name = '{}_{}'.format(s_sorter, recording)
    #print(recordings_name, batch_name)
    create_sorting_batch(recordings_name=recordings_name, batch_name=batch_name, sorters=[sorter])
  print()
    
for recording in vs_recordings_siprobe:
  for sorter, s_sorter in zip(v_sorters_siprobe, vs_sorters):
    recordings_name = 'spikeforest_{}_recordings'.format(recording)
    batch_name = '{}_{}'.format(s_sorter, recording)
    #print(recordings_name, batch_name)    
    create_sorting_batch(recordings_name=recordings_name, batch_name=batch_name, sorters=[sorter])
  print()<jupyter_output>Creating sorting batch: ms4_magland_synth
Number of jobs: 80
Creating sorting batch: irc_magland_synth
Number of jobs: 80
Creating sorting batch: sc_magland_synth
Number of jobs: 80
Creating sorting batch: ks_magland_synth
Number of jobs: 80

Creating sorting batch: ms4_mearec_tetrode
Number of jobs: 40
Creating sorting batch: irc_mearec_tetrode
Number of jobs: 40
Creating sorting batch: sc_mearec_tetrode
Number of jobs: 40
Creating sorting batch: ks_mearec_tetrode
Number of jobs: 40

Creating sorting batch: ms4_bionet8c
Number of jobs: 36
Creating sorting batch: irc_bionet8c
Number of jobs: 36
Creating sorting batch: sc_bionet8c
Number of jobs: 36
Creating sorting batch: ks_bionet8c
Number of jobs: 36

Creating sorting batch: ms4_bionet32c
Number of jobs: 36
Creating sorting batch: irc_bionet32c
Number of jobs: 36
Creating sorting batch: sc_bionet32c
Number of jobs: 36
Creating sorting batch: ks_bionet32c
Number of jobs: 36

Creating sorting batch: ms4_mearec_neuronexus
Number of jobs[...]<jupyter_text>To run these sorting batches, follow the instructions above.  Browse sorting results<jupyter_code>SF=sf.SFData()
SF.loadRecordings(key=dict(name='spikeforest_bionet_recordings'))
SF.loadRecordings(key=dict(name='spikeforest_magland_synth_recordings'))
SF.loadRecordings(key=dict(name='spikeforest_mearec_tetrode_recordings'))

SF.loadProcessingBatch(key=dict(batch_name='summarize_recordings_bionet',name='job_results'))
SF.loadProcessingBatch(key=dict(batch_name='summarize_recordings_magland_synth',name='job_results'))
SF.loadProcessingBatch(key=dict(batch_name='summarize_recordings_mearec_tetrode',name='job_results'))

SF.loadProcessingBatch(key=dict(batch_name='ms4_magland_synth',name='job_results'))
SF.loadProcessingBatch(key=dict(batch_name='sc_magland_synth',name='job_results'))
SF.loadProcessingBatch(key=dict(batch_name='irc_magland_synth',name='job_results'))
SF.loadProcessingBatch(key=dict(batch_name='ks_magland_synth',name='job_results'))

SF.loadProcessingBatch(key=dict(batch_name='ms4_mearec_tetrode',name='job_results'))
#SF.loadProcessingBatch(key=dict(batch_name='sc_mearec_tetrode',name='job_results'))
SF.loadProcessingBatch(key=dict(batch_name='irc_mearec_tetrode',name='job_results'))
SF.loadProcessingBatch(key=dict(batch_name='ks_mearec_tetrode',name='job_results'))

SF.loadProcessingBatch(key=dict(batch_name='ms4_bionet',name='job_results'))
SF.loadProcessingBatch(key=dict(batch_name='sc_bionet',name='job_results')) ## Spyking circus not working yet -- need to put into singularity container
SF.loadProcessingBatch(key=dict(batch_name='irc_bionet',name='job_results'))
#SF.loadProcessingBatch(key=dict(batch_name='ks_bionet',name='job_results'))


X=sf.SFSelectWidget(sfdata=SF,mode='sorting_result')
display(X)
R=X.sortingResult()
display(R.plot('unit_waveforms'))
display(R.plot('autocorrelograms'))
display(R.comparisonWithTruth())<jupyter_output><empty_output><jupyter_text>Aggregate sorting results<jupyter_code>SF=sf.SFData()
SF.loadRecordings(key=dict(name='spikeforest_bionet8c_recordings'))
SF.loadRecordings(key=dict(name='spikeforest_bionet32c_recordings'))
SF.loadRecordings(key=dict(name='spikeforest_magland_synth_recordings'))
SF.loadRecordings(key=dict(name='spikeforest_mearec_tetrode_recordings'))
SF.loadRecordings(key=dict(name='spikeforest_mearec_neuronexus_recordings'))
SF.loadProcessingBatch(key=dict(batch_name='summarize_recordings_bionet8c',name='job_results'))
SF.loadProcessingBatch(key=dict(batch_name='summarize_recordings_bionet32c',name='job_results'))
SF.loadProcessingBatch(key=dict(batch_name='summarize_recordings_magland_synth',name='job_results'))
SF.loadProcessingBatch(key=dict(batch_name='summarize_recordings_mearec_tetrode',name='job_results'))
SF.loadProcessingBatch(key=dict(batch_name='summarize_recordings_mearec_neuronexus',name='job_results'))
SF.loadProcessingBatch(key=dict(batch_name='ms4_magland_synth',name='job_results'))
SF.loadProcessingBatch(key=dict(batch_name='irc_magland_synth',name='job_results'))
SF.loadProcessingBatch(key=dict(batch_name='sc_magland_synth',name='job_results'))
SF.loadProcessingBatch(key=dict(batch_name='ks_magland_synth',name='job_results'))
SF.loadProcessingBatch(key=dict(batch_name='ms4_bionet8c',name='job_results'))
#SF.loadProcessingBatch(key=dict(batch_name='irc_bionet8c',name='job_results'))
#SF.loadProcessingBatch(key=dict(batch_name='sc_bionet8c',name='job_results'))
#SF.loadProcessingBatch(key=dict(batch_name='ks_bionet8c',name='job_results'))
SF.loadProcessingBatch(key=dict(batch_name='ms4_bionet32c',name='job_results'))
SF.loadProcessingBatch(key=dict(batch_name='irc_bionet32c',name='job_results'))
import pandas as pd
import random
import altair as alt
alt.renderers.enable('colab')
alt.data_transformers.enable('default', max_rows=None)

# Accumulate the sorting results
def accumulate_comparison_with_ground_truth(*,SF,studies,sorter_name,fieldnames):
  ret=[]
  for study in studies:
    recordings=[study.recording(name) for name in study.recordingNames()]
    for R in recordings:
      result=R.sortingResult(sorter_name)
      A=result.comparisonWithTruth(format='json')
      B=R.trueUnitsInfo(format='json')
      snr_by_true_unit=dict()
      for b in B:
        snr_by_true_unit[b['unit_id']]=b['snr']
      for i in A:
        a=A[i]
        rec=dict()
        rec['recording_name']=R.name()
        rec['unit_id']=a.get('unit_id',a.get('Unit ID'))
        rec['snr']=snr_by_true_unit[rec['unit_id']]
        for fieldname in fieldnames:
          rec[fieldname]=float(a.get(fieldname,a.get('Accuracy')))
        ret.append(rec)
  return ret

def show_accuracy_plot(*,SF,study_name,sorter_name,title):
  
  study=SF.study(study_name)
  X=accumulate_comparison_with_ground_truth(
      SF=SF,
      studies=[study],
      sorter_name=sorter_name,
      fieldnames=['accuracy']
  )
  
  # Display the accumulated sorting results
  cc=alt.Chart(pd.DataFrame(X),title=title).mark_point().encode(
      x='snr',
      y='accuracy',
      color='recording_name',
      tooltip='recording_name'
  ).interactive()
  display(cc)
display(vd.h1('Select a result'))
X=sf.SFSelectWidget(sfdata=SF,mode='sorting_result')
display(X)
study_name=X.study().name()
sorter_name=X.sortingResult().sorterName()
show_accuracy_plot(
    SF=SF,
    study_name=study_name,
    sorter_name=sorter_name,
    title=sorter_name+' '+study_name
)
def show_accuracy_comparison_plot(*,SF,study_name,sorter1_name,sorter2_name,title):
  
  study=SF.study(study_name)
  X1=accumulate_comparison_with_ground_truth(
      SF=SF,
      studies=[study],
      sorter_name=sorter1_name,
      fieldnames=['accuracy']
  )
  X2=accumulate_comparison_with_ground_truth(
      SF=SF,
      studies=[study],
      sorter_name=sorter2_name,
      fieldnames=['accuracy']
  )
  X=[]
  for unit in X1:
    unit['sorter_name']=sorter1_name
    X.append(unit)
    
  for unit in X2:
    unit['sorter_name']=sorter2_name
    X.append(unit)
  
  # Display the accumulated sorting results
  cc=alt.Chart(pd.DataFrame(X),title=title).mark_point().encode(
      x='snr',
      y='accuracy',
      color='sorter_name',
      tooltip='sorter_name'
  ).interactive()
  display(cc)
study_name=X.study().name()
sorter1_name=X.sortingResult().sorterName()
sorter2_name='IronClust-drift'
show_accuracy_comparison_plot(
    SF=SF,
    study_name=study_name,
    sorter1_name=sorter1_name,
    sorter2_name=sorter2_name,
    title=sorter1_name+' '+sorter2_name+' '+study_name
)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.11465073150628477, 'fraction_numerical': 0.013352565423449413, 'mean_word_length': 7.156638655462185, 'pattern_counts': {'":': 0, '<': 29, '<?xml version=': 0, '>': 30, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '8', 'dir': 'jupyter-structured-clean-dedup', 'id': '95963', 'license': 'Apache-2.0', 'n_tokens_mistral': 9082, 'n_tokens_neox': 8603, 'n_words': 1100, 'path': 'notebooks/spikeforest_analysis.ipynb', 'repo_name': 'magland/spikeforest_batch_run', 'size': '24265'}