Skip to main content
  • Home
  • Development
  • Documentation
  • Donate
  • Operational login
  • Browse the archive

swh logo
SoftwareHeritage
Software
Heritage
Archive
Features
  • Search

  • Downloads

  • Save code now

  • Add forge now

  • Help

https://doi.org/10.5281/zenodo.3597474
27 August 2025, 14:32:56 UTC
  • Code
  • Branches (0)
  • Releases (7)
  • Visits
    • Branches
    • Releases
      • 7
      • 7
      • 6
      • 5
      • 4
      • 3
      • 2
      • 1
    • b6d380b
    • /
    • MouseLand-Kilosort-8f396c7
    • /
    • kilosort
    • /
    • simulation.py
    Raw File Download

    To reference or cite the objects present in the Software Heritage archive, permalinks based on SoftWare Hash IDentifiers (SWHIDs) must be used.
    Select below a type of object currently browsed in order to display its associated SWHID and permalink.

    • content
    • directory
    • snapshot
    • release
    origin badgecontent badge
    swh:1:cnt:de269c752c9072df3591142cb043aee040144eef
    origin badgedirectory badge
    swh:1:dir:5a94e6584e2d42b7e8e4bdb91a2e2f831f22c766
    origin badgesnapshot badge
    swh:1:snp:cde65436e19c7a8c490b366892685f110e246dcf
    origin badgerelease badge
    swh:1:rel:4aa70d8c6bf4aecf2297faaca70d7dba2d9e7b48

    This interface enables to generate software citations, provided that the root directory of browsed objects contains a citation.cff or codemeta.json file.
    Select below a type of object currently browsed in order to generate citations for them.

    • content
    • directory
    • snapshot
    • release
    Generate software citation in BibTex format (requires biblatex-software package)
    Generating citation ...
    Generate software citation in BibTex format (requires biblatex-software package)
    Generating citation ...
    Generate software citation in BibTex format (requires biblatex-software package)
    Generating citation ...
    Generate software citation in BibTex format (requires biblatex-software package)
    Generating citation ...
    simulation.py
    import numpy as np 
    import os, time 
    import matplotlib.pyplot as plt 
    import torch 
    from scipy.interpolate import interp1d
    from scipy.ndimage import gaussian_filter1d, convolve1d
    from scipy.sparse import csr_matrix 
    from scipy.ndimage import gaussian_filter1d
    from tqdm import trange
    import pandas as pd
    
    from kilosort.datashift import kernel2D
    from kilosort import io
    
    def preprocess_wfs_sts(filename, y_chan, x_chan, y_chan_up=None, x_chan_up=None,
                           ups=10, device=torch.device('cuda')):
        if y_chan_up is None:
            y_chan_up = y_chan
            x_chan_up = x_chan
    
        ### waveforms and spike trains from data
        f = np.load(filename, allow_pickle=True)
        sts_all = f['sts_all']
        cls_all = f['cls_all']
        wfs_all = f['wfs_all']
        contaminations_all = f['contaminations_all']
        wfs_x_all = f['wfs_x_all']
        nst_all = f['nst_all']
    
        ### take all waveforms with at least 50 spikes per drift bin
        contaminations = np.concatenate(contaminations_all, axis=0)
        wfs = np.concatenate(wfs_all, axis=0)
        wfs_x = np.concatenate(wfs_x_all, axis=0)
        nsts = np.concatenate(nst_all, axis=0)
        idr = np.nonzero((nsts>50).sum(axis=1)==nsts.shape[1])[0]
        contaminations = contaminations[idr]
        wfs = wfs[idr]
        wfs_x = wfs_x[idr]
        wfs /= ((wfs**2).sum(axis=(2,3), keepdims=True)**0.5).mean(axis=1, keepdims=True)
    
        ### remove low firing spike trains
        sts = np.zeros(0, 'uint64')
        cls = np.zeros(0, 'uint32')
        ntm = np.array([sts_all[i].max() for i in range(len(sts_all))]).min()
        i0 = 0
        min_fr = 0.5 # min firing rate of 0.5 Hz
        for i in range(len(sts_all)):
            n_max = np.nonzero(sts_all[i] > ntm-1)[0][0]
            st0 = sts_all[i][:n_max]
            cl0 = cls_all[i][:n_max]
            cu, cc = np.unique(cl0, return_counts=True)
            cc = cc.astype('float32')
            cc /= st0[-1]
            cc *= 30000
            ids = cu[np.nonzero(cc>=min_fr)[0]]
            ix = np.isin(cl0, ids)
            clsx = np.unique(cl0[ix], return_inverse=True)[1]
            stsx = st0[ix]
            sts = np.append(sts, stsx)
            cls = np.append(cls, i0 + clsx)
            i0 += clsx.max() + 1
        isort = sts.argsort()
        cls = cls[isort]
        sts = sts[isort]
        cc = np.unique(cls, return_counts=True)[1].astype('float32')
        cc /= sts[-1]
        cc *= 30000
        print('# of spike trains: ', len(cc))
        plt.hist(cc, 100);
        plt.show()
    
        ### denoise waveforms
        # use 3-component PC reconstruction of waveforms
        # keep waveforms that don't vary too much across drift
        npc = 3
        wt = torch.from_numpy(wfs).to(device)
        wt = wt.reshape(wt.shape[0]*wt.shape[1], wt.shape[2], wt.shape[3])
        u,s,v = torch.svd(wt)
        wtt = torch.einsum("nij,nkj->nik", u[:,:,:npc] * s[:,:npc].unsqueeze(1), v[:,:,:npc])
        wfs = wtt.cpu().numpy()
        wfs = wfs.reshape(-1, 20, wfs.shape[-2], wfs.shape[-1])
        wfs /= ((wfs**2).sum(axis=(2,3), keepdims=True)**0.5).mean(axis=1, keepdims=True)
    
        nd = ((wfs[:, 0, :, 4:] - wfs[:, -1, :, :-4])**2).sum(axis=(-2,-1))
        na = (wfs[:, 0, :, 4:]**2).sum(axis=(-2,-1))
        igood = np.nonzero(nd / na < 0.5)[0]
        print('# of good waveforms: ', len(igood))
        wfs = wfs[igood]
        wfs_x = wfs_x[igood]
        contaminations = contaminations[igood]
    
        ### smooth and upsample waveforms
        # smooth across drift positions to reduce variability in waveform shape across drift
        # upsample to allow 10x more drift positions
        sig_interp = 20
        lam = 1e-2
        wfs_smoothed = np.zeros((wfs.shape[0], wfs.shape[1]*ups, *wfs.shape[2:]),'float32')
        for ic in range(4):
            iwc = wfs_x == ic
            nd,nt,nc = wfs.shape[-3:]
            
            xpu = np.vstack((y_chan_up[ic : ic+nc], x_chan_up[ic : ic+nc])).T
            zup = np.arange(0,nd*2,2./ups)[:,np.newaxis,np.newaxis]
            zup = np.concatenate((zup, np.zeros((nd*ups,1,1))), axis=-1)
            xup = (np.tile(xpu[np.newaxis,:], (nd*ups,1,1)) + zup).reshape(-1, 2)
    
            xp = np.vstack((y_chan[ic : ic+nc], x_chan[ic : ic+nc])).T
            zp = np.arange(0,nd*2,2)[:,np.newaxis,np.newaxis]
            zp = np.concatenate((zp, np.zeros((nd,1,1))), axis=-1)
            xp = (np.tile(xp[np.newaxis,:], (nd,1,1)) + zp).reshape(-1, 2)    
            
            Kyx = kernel2D(xup, xp, sig_interp)
            Kxx = kernel2D(xp, xp, sig_interp)
            Kyx = torch.from_numpy(Kyx).float().to(device)
            Kxx = torch.from_numpy(Kxx).float().to(device)
            iKxx = torch.linalg.inv(Kxx + lam * torch.eye(Kxx.shape[0], device=device))
            M = Kyx @ iKxx
    
            wf0 = torch.from_numpy(wfs[iwc]).float().to(device)
            wf0 = wf0.transpose(3,2).reshape(-1,nd*nc, nt)
            wf0s = torch.einsum('nj,ijk->ink', M, wf0) 
            wf0s = wf0s.reshape(-1, nd*ups, nc, nt).transpose(3,2)
            wfs_smoothed[iwc] = wf0s.cpu().numpy()
        wfs_smoothed /= ((wfs_smoothed**2).sum(axis=(2,3), keepdims=True)**0.5).mean(axis=1, keepdims=True)
        wfs = wfs_smoothed
        
        return wfs, wfs_x, contaminations, sts, cls
    
    
    def generate_background(NT, fs=30000, device=torch.device('cuda')):
        #NT = 3000000
        nf = np.arange(NT, dtype = 'float32')
        nf = np.minimum(nf, NT - nf)
        nf = torch.from_numpy(nf).to(device)
    
        ff = nf
        ff[ff < NT*300/fs] = 10000000
        ff = (ff.mean() / (1+ff.unsqueeze(-1).unsqueeze(-1)))**.5
    
        X = torch.zeros((NT, 96, 4))
    
        for j in range(12):
            Xn = torch.randn(NT, 8, 4, device = device)
            Xfft = torch.fft.fft(Xn, dim = 0)
            Xfft = 100 * Xfft / torch.abs(Xfft) * ff
            Xn = torch.fft.ifft(Xfft, dim = 0)
            Xn = torch.real(Xn)
            X[:, 8*j:8*(j+1), :] = Xn.cpu()
    
        X /= X.std()
        X *= 0.95 * 0.8
        X = X.reshape(X.shape[0], -1)
        #X = torch.linalg.solve(whiten_mat.cpu(), X.T).T
        return X
    
    def generate_spikes(st, cl, wfs, wfs_x, contaminations,
                        n_sim=200, n_noise=1000, n_batches=500, tsig=50, 
                        batch_size=60000, twav_min=50, drift=True,
                        drift_range=5, drift_seed=0, fast=False, step=False,
                        n_batches_sim=None, ups=10):
        """ simulate spikes using 
            - random waveforms from wfs with x-pos wfs_x 
            - spike train stats from (st, cl) 
            - drift with smoothing constant of tsig across batches 
            - first n_sim neurons are good neurons, next n_noise neurons are MUA / noise
        WARNING: requires RAM for full simulation as written
    
        """
        
        n_bins, n_twav, nc = wfs.shape[1:] 
        st_sim = np.zeros(0, 'uint64')
        cl_sim = np.zeros(0, 'uint32')
        wf_sim = np.zeros((n_sim + n_noise, n_bins, n_twav, nc), 'float32')
        wfid_sim = np.zeros(n_sim + n_noise, 'uint32')
        cb_sim = np.zeros(n_sim + n_noise, 'int')
    
        # random amplitudes
        # for good neurons
        amp_sim = np.random.exponential(scale=1., size=(n_sim,)) * 7. + 10.
        # for MUA / noise
        if n_noise > 0:
            amp_sim = np.append(amp_sim, 
                                np.random.rand(n_noise) * 6. + 4.,
                                axis=0)
    
        # create good neurons
        n_time = st.max() / 30000.
    
        n_time_sim = n_batches * batch_size
        n_max = np.nonzero(st > n_time_sim-1)[0][0]
        good_neurons = np.nonzero(contaminations < 0.1)[0]
        print('creating good neurons')
        for i in trange(n_sim):
            # random spike train, waveform and channel per neuron
            sp_rand = np.random.randint(cl.max()+1)
            wf_rand = np.random.randint(len(good_neurons))
            # pad to avoid best channel on first 4 or last 4 channels
            cb_rand = np.random.randint(96-2) + 1
            frac_rand = np.random.beta(1, 3) * 0.8 + 0.2
    
            # spike train
            spi = st[cl == sp_rand].copy()
            isi = np.diff(spi)
            isi = isi[np.random.permutation(len(isi))]
            spi = np.cumsum(isi)
            if len(spi) > 0:
                n_max = np.nonzero(spi > n_time_sim-1)[0][0] if spi[-1] > n_time_sim-1 else len(spi)
                spi = spi[:n_max]
                fr = (len(spi) / n_time_sim) * 30000
                if fr > 5:
                    spi = spi[np.random.rand(len(spi)) < frac_rand]
    
                # waveform
                wfi = wfs[good_neurons[wf_rand]]
                cbi = cb_rand * 4 + wfs_x[good_neurons[wf_rand]]
    
                st_sim = np.append(st_sim, spi, axis=0)
                cl_sim = np.append(cl_sim, i * np.ones(len(spi), 'uint32'), axis=0)
                wf_sim[i] = wfi
                cb_sim[i] = cbi
                wfid_sim[i] = good_neurons[wf_rand]
            else:
                i -= 1
    
        # create MUA / noise
        if n_noise > 0:
            bad_neurons = np.nonzero(contaminations >= 0.1)[0]
            _, frs = np.unique(cl, return_counts=True)
            frs = frs.astype('float32')
            frs /= n_time
            print('creating MUA / noise')
            for i in trange(n_noise):
                # random firing rate, waveform and channel per neuron
                fr_rand = np.random.randint(len(frs))
                wf_rand = np.random.randint(len(bad_neurons))
                cb_rand = np.random.randint(96)
                frac_rand = np.random.beta(1, 3) * 0.8 + 0.2
    
                # spike train
                fr = frs[fr_rand]
                isi = (30000 * np.random.exponential(scale=1./fr, size=int(np.ceil((n_time_sim * fr / 30000))))).astype('uint64')
                spi = np.cumsum(isi)
                if fr > 5:
                    spi = spi[np.random.rand(len(spi)) < frac_rand]
    
                # waveform
                wfi = wfs[bad_neurons[wf_rand]]
                cbi = cb_rand * 4 + wfs_x[bad_neurons[wf_rand]]
    
                st_sim = np.append(st_sim, spi, axis=0)
                cl_sim = np.append(cl_sim, (n_sim + i) * np.ones(len(spi), 'uint32'), axis=0)
                wf_sim[n_sim + i] = wfi
                cb_sim[n_sim + i] = cbi
                wfid_sim[n_sim + i] = bad_neurons[wf_rand]
            
        # re-sort by time
        ii = st_sim.argsort()
        cl_sim = cl_sim[ii]
        st_sim = st_sim[ii]
    
        # remove spikes after max recording time
        n_max = np.nonzero(st_sim > n_time_sim-1)[0][0] if st_sim[-1] > n_time_sim-1 else len(st_sim)
        n_min = np.nonzero(st_sim < n_twav)[0][-1] + 1
        st_sim = st_sim[n_min : n_max]
        cl_sim = cl_sim[n_min : n_max]
        print(len(st_sim))
        
        # amplitude multiply waveforms
        wf_sim *= amp_sim[:,np.newaxis,np.newaxis,np.newaxis]
        
        np.random.seed(drift_seed)
        # overall drift
        if drift:
            slow_n = n_batches // 10 if fast else n_batches
    
            drift_sim = np.random.randn(slow_n)
            drift_sim = gaussian_filter1d(drift_sim, tsig)
            drift_sim -= drift_sim.min()
            drift_sim /= drift_sim.max()
            drift_sim *= drift_range
    
            # drift across probe (9 positions)
            drift_chan = np.random.randn(slow_n, 9)
            drift_chan = gaussian_filter1d(drift_chan, tsig, axis=0)
            drift_chan -= drift_chan.min(axis=0) 
            drift_chan /= drift_chan.max(axis=0)
            drift_chan *= 5 - (20-drift_range)/6
            drift_chan = gaussian_filter1d(drift_chan*4, 2, axis=-1)
            drift_chan += drift_sim[:,np.newaxis]
            drift_chan -= drift_chan.min() 
            drift_chan /= drift_chan.max()
            drift_chan *= drift_range
            drift_chan += 10 - drift_range / 2
    
            if fast: 
                fast_drift = np.zeros(n_batches)
                fast_drift[np.random.permutation(n_batches)[:300]] = 1 
                t = np.arange(20)
                kernel = np.exp(-t/2.5) - np.exp(-t)
                kernel /= kernel.max()
                fast_drift = convolve1d(fast_drift, kernel)
                fast_drift *= 5
                drift_chan = np.tile(drift_chan[:,np.newaxis], (1,10,1))
                drift_chan = drift_chan.reshape(n_batches, -1)
                drift_chan -= 5
                drift_chan += fast_drift[:,np.newaxis]
            elif step:
                drift_chan -= 7.5
                drift_chan[n_batches//2:] += 15
    
            plt.plot(drift_chan);
            plt.show()
        else:
            drift_chan = 10 * np.ones((n_batches, 9), 'float32')
    
        # upsample to get drift for all channels
        f = interp1d(np.linspace(0, 383, drift_chan.shape[1]+2)[1:-1], drift_chan, axis=-1, fill_value='extrapolate')
        drift_sim_ups = f(np.arange(0,384))
        drift_sim_ups = np.floor(drift_sim_ups * ups).astype(int)
        print(drift_sim_ups.min(), drift_sim_ups.max())
        print('created drift')
    
        # create spikes
        n_batches = int(np.ceil((st_sim.max() + n_twav) / batch_size)) if n_batches_sim is None else n_batches_sim
        data = np.zeros((batch_size * n_batches, 385), 'float32')
        data_mua = None if n_batches_sim is None else np.zeros((batch_size * n_batches, 385), 'float32')
        st_max = batch_size * n_batches 
        cl_sim = cl_sim[st_sim < st_max - n_twav]
        st_sim = st_sim[st_sim < st_max - n_twav]
        st_batch = np.floor(st_sim / batch_size).astype('int') # batch of each spike
        n_splits = 3 if drift_range>0 else 10
        tic=time.time()
        # loop over neurons
        print('creating data')
        for i in trange(n_sim + n_noise):
            # waveform and channel for waveform
            wfi = wf_sim[i]
            ic0 = cb_sim[i] - nc//2
            iw0 = max(0, -ic0)
            ic1 = min(384, ic0+nc)
            ic0 = max(0, ic0)
            iw1 = ic1 - ic0 + iw0
            # spikes from neuron            
            is0 = cl_sim == i
            sti = st_sim[is0]
            drifti = drift_sim_ups[st_batch[is0], cb_sim[i]]
            # loop over z-positions
            for d in range(n_bins):
                ist = drifti == d
                if ist.sum() > 0:
                    inds = np.nonzero(ist)[0]
                    #print(len(inds))
                    for k in range(n_splits):
                        ki = np.arange(k, len(inds), n_splits)
                        # indices for spike waveform
                        stb = sti[inds[ki]] - int(twav_min)
                        stb = stb[:,np.newaxis].astype(int) + np.arange(0, n_twav, 1, int)
                        if i < n_sim or data_mua is None:
                            data[stb, ic0:ic1] += wfi[d][:, iw0:iw1]
                        else:
                            data_mua[stb, ic0:ic1] += wfi[d][:, iw0:iw1]
    
        return data, data_mua, st_sim, cl_sim, amp_sim, wf_sim, cb_sim, wfid_sim, drift_sim_ups
    
    def create_simulation(filename, st, cl, wfs, wfs_x, contaminations,
                          n_sim=100, n_noise=1000, n_batches=500,
                          batch_size=60000, tsig=50, tpad=100, n_chan_bin=385, drift=True,
                          drift_range=5, drift_seed=0, fast=False, step=False,
                          ups=10, whiten_mat = None,
                                 ):
        """ simulate neuropixels 3B probe recording """
    
        # simulate spikes
        out = generate_spikes(st, cl, wfs, wfs_x, 
                            contaminations, n_sim=n_sim, n_noise=n_noise, 
                            n_batches=n_batches, tsig=tsig, 
                            batch_size=batch_size, drift=drift, 
                            fast=fast, step=step,
                            drift_range=drift_range, drift_seed=drift_seed,
                            ups=ups)
        data, data_mua, st_sim, cl_sim, amp_sim, wf_sim, cb_sim, wfid_sim, drift_sim_ups = out
    
        print(f'created dataset of size {data.shape}')
        
        # simulating background
        tweight = torch.arange(0, tpad, dtype=torch.float32)
        tweight_flip = tweight.flip(dims=(0,))
        tweight /= tweight + tweight_flip
        tweight = tweight.unsqueeze(1)
        tweight_flip = tweight.flip(dims=(0,))
        noise_init = generate_background(batch_size + tpad)
        noise_b = torch.zeros((batch_size, n_chan_bin))
        whiten_mat = torch.from_numpy(whiten_mat)
    
        # writing binary
        with io.BinaryRWFile(filename, n_chan_bin, NT=batch_size, 
                             write=True, n_samples=data.shape[0]) as bfile:
            for ibatch in np.arange(0, n_batches):
                noise_next = generate_background(batch_size + tpad)
                noise_pad = tweight_flip * noise_init[-tpad:] +  tweight * noise_next[:tpad]
                noise = torch.cat((noise_pad, noise_next[tpad : -tpad]), axis=0)
                noise_b[:, :384] = noise
                X = torch.from_numpy(data[ibatch * batch_size : min(n_batches * batch_size, (ibatch+1) * batch_size)]).float()
                
                X = (noise_b + X)
    
                if whiten_mat is not None:
                    X[:,:384] = torch.linalg.solve(whiten_mat, X[:,:384].T).T
    
                X = X.numpy()
                to_write = np.clip(20 * X, -2**15 + 1, 2**15 - 1).astype('int16')
                #to_write = np.clip(X, -2**15 + 1, 2**15 - 1).astype('int16')
    
                if ibatch%100==0:
                    print(f'writing batch {ibatch} out of {n_batches}')
                bfile[ibatch * batch_size : min(n_batches * batch_size, (ibatch+1) * batch_size)] = X
                noise_next = noise_init
    
        # create meta file for probe
        metaname = os.path.splitext(filename)[0] + '.meta'
        meta_dict = {'fileSizeBytes': os.path.getsize(filename),
                    'typeThis': 'imec',
                    'imSampRate': 30000,
                    'imAiRangeMax': 0.6,
                    'imAiRangeMin': -0.6,
                    'nSavedChans': 385,
                    'snsApLfSy': '384,0,1',
                    'snsSaveChanSubset': '0:383,768',
                    '~imroTbl': '(0,384)(0 0 0 500 250 1)(1 0 0 500 250 1)(2 0 0 500 250 1)(3 0 0 500 250 1)(4 0 0 500 250 1)(5 0 0 500 250 1)(6 0 0 500 250 1)(7 0 0 500 250 1)(8 0 0 500 250 1)(9 0 0 500 250 1)(10 0 0 500 250 1)(11 0 0 500 250 1)(12 0 0 500 250 1)(13 0 0 500 250 1)(14 0 0 500 250 1)(15 0 0 500 250 1)(16 0 0 500 250 1)(17 0 0 500 250 1)(18 0 0 500 250 1)(19 0 0 500 250 1)(20 0 0 500 250 1)(21 0 0 500 250 1)(22 0 0 500 250 1)(23 0 0 500 250 1)(24 0 0 500 250 1)(25 0 0 500 250 1)(26 0 0 500 250 1)(27 0 0 500 250 1)(28 0 0 500 250 1)(29 0 0 500 250 1)(30 0 0 500 250 1)(31 0 0 500 250 1)(32 0 0 500 250 1)(33 0 0 500 250 1)(34 0 0 500 250 1)(35 0 0 500 250 1)(36 0 0 500 250 1)(37 0 0 500 250 1)(38 0 0 500 250 1)(39 0 0 500 250 1)(40 0 0 500 250 1)(41 0 0 500 250 1)(42 0 0 500 250 1)(43 0 0 500 250 1)(44 0 0 500 250 1)(45 0 0 500 250 1)(46 0 0 500 250 1)(47 0 0 500 250 1)(48 0 0 500 250 1)(49 0 0 500 250 1)(50 0 0 500 250 1)(51 0 0 500 250 1)(52 0 0 500 250 1)(53 0 0 500 250 1)(54 0 0 500 250 1)(55 0 0 500 250 1)(56 0 0 500 250 1)(57 0 0 500 250 1)(58 0 0 500 250 1)(59 0 0 500 250 1)(60 0 0 500 250 1)(61 0 0 500 250 1)(62 0 0 500 250 1)(63 0 0 500 250 1)(64 0 0 500 250 1)(65 0 0 500 250 1)(66 0 0 500 250 1)(67 0 0 500 250 1)(68 0 0 500 250 1)(69 0 0 500 250 1)(70 0 0 500 250 1)(71 0 0 500 250 1)(72 0 0 500 250 1)(73 0 0 500 250 1)(74 0 0 500 250 1)(75 0 0 500 250 1)(76 0 0 500 250 1)(77 0 0 500 250 1)(78 0 0 500 250 1)(79 0 0 500 250 1)(80 0 0 500 250 1)(81 0 0 500 250 1)(82 0 0 500 250 1)(83 0 0 500 250 1)(84 0 0 500 250 1)(85 0 0 500 250 1)(86 0 0 500 250 1)(87 0 0 500 250 1)(88 0 0 500 250 1)(89 0 0 500 250 1)(90 0 0 500 250 1)(91 0 0 500 250 1)(92 0 0 500 250 1)(93 0 0 500 250 1)(94 0 0 500 250 1)(95 0 0 500 250 1)(96 0 0 500 250 1)(97 0 0 500 250 1)(98 0 0 500 250 1)(99 0 0 500 250 1)(100 0 0 500 250 1)(101 0 0 500 250 1)(102 0 0 500 250 1)(103 0 0 500 250 1)(104 0 0 500 250 1)(105 0 0 500 250 1)(106 0 0 500 250 1)(107 0 0 500 250 1)(108 0 0 500 250 1)(109 0 0 500 250 1)(110 0 0 500 250 1)(111 0 0 500 250 1)(112 0 0 500 250 1)(113 0 0 500 250 1)(114 0 0 500 250 1)(115 0 0 500 250 1)(116 0 0 500 250 1)(117 0 0 500 250 1)(118 0 0 500 250 1)(119 0 0 500 250 1)(120 0 0 500 250 1)(121 0 0 500 250 1)(122 0 0 500 250 1)(123 0 0 500 250 1)(124 0 0 500 250 1)(125 0 0 500 250 1)(126 0 0 500 250 1)(127 0 0 500 250 1)(128 0 0 500 250 1)(129 0 0 500 250 1)(130 0 0 500 250 1)(131 0 0 500 250 1)(132 0 0 500 250 1)(133 0 0 500 250 1)(134 0 0 500 250 1)(135 0 0 500 250 1)(136 0 0 500 250 1)(137 0 0 500 250 1)(138 0 0 500 250 1)(139 0 0 500 250 1)(140 0 0 500 250 1)(141 0 0 500 250 1)(142 0 0 500 250 1)(143 0 0 500 250 1)(144 0 0 500 250 1)(145 0 0 500 250 1)(146 0 0 500 250 1)(147 0 0 500 250 1)(148 0 0 500 250 1)(149 0 0 500 250 1)(150 0 0 500 250 1)(151 0 0 500 250 1)(152 0 0 500 250 1)(153 0 0 500 250 1)(154 0 0 500 250 1)(155 0 0 500 250 1)(156 0 0 500 250 1)(157 0 0 500 250 1)(158 0 0 500 250 1)(159 0 0 500 250 1)(160 0 0 500 250 1)(161 0 0 500 250 1)(162 0 0 500 250 1)(163 0 0 500 250 1)(164 0 0 500 250 1)(165 0 0 500 250 1)(166 0 0 500 250 1)(167 0 0 500 250 1)(168 0 0 500 250 1)(169 0 0 500 250 1)(170 0 0 500 250 1)(171 0 0 500 250 1)(172 0 0 500 250 1)(173 0 0 500 250 1)(174 0 0 500 250 1)(175 0 0 500 250 1)(176 0 0 500 250 1)(177 0 0 500 250 1)(178 0 0 500 250 1)(179 0 0 500 250 1)(180 0 0 500 250 1)(181 0 0 500 250 1)(182 0 0 500 250 1)(183 0 0 500 250 1)(184 0 0 500 250 1)(185 0 0 500 250 1)(186 0 0 500 250 1)(187 0 0 500 250 1)(188 0 0 500 250 1)(189 0 0 500 250 1)(190 0 0 500 250 1)(191 0 0 500 250 1)(192 0 0 500 250 1)(193 0 0 500 250 1)(194 0 0 500 250 1)(195 0 0 500 250 1)(196 0 0 500 250 1)(197 0 0 500 250 1)(198 0 0 500 250 1)(199 0 0 500 250 1)(200 0 0 500 250 1)(201 0 0 500 250 1)(202 0 0 500 250 1)(203 0 0 500 250 1)(204 0 0 500 250 1)(205 0 0 500 250 1)(206 0 0 500 250 1)(207 0 0 500 250 1)(208 0 0 500 250 1)(209 0 0 500 250 1)(210 0 0 500 250 1)(211 0 0 500 250 1)(212 0 0 500 250 1)(213 0 0 500 250 1)(214 0 0 500 250 1)(215 0 0 500 250 1)(216 0 0 500 250 1)(217 0 0 500 250 1)(218 0 0 500 250 1)(219 0 0 500 250 1)(220 0 0 500 250 1)(221 0 0 500 250 1)(222 0 0 500 250 1)(223 0 0 500 250 1)(224 0 0 500 250 1)(225 0 0 500 250 1)(226 0 0 500 250 1)(227 0 0 500 250 1)(228 0 0 500 250 1)(229 0 0 500 250 1)(230 0 0 500 250 1)(231 0 0 500 250 1)(232 0 0 500 250 1)(233 0 0 500 250 1)(234 0 0 500 250 1)(235 0 0 500 250 1)(236 0 0 500 250 1)(237 0 0 500 250 1)(238 0 0 500 250 1)(239 0 0 500 250 1)(240 0 0 500 250 1)(241 0 0 500 250 1)(242 0 0 500 250 1)(243 0 0 500 250 1)(244 0 0 500 250 1)(245 0 0 500 250 1)(246 0 0 500 250 1)(247 0 0 500 250 1)(248 0 0 500 250 1)(249 0 0 500 250 1)(250 0 0 500 250 1)(251 0 0 500 250 1)(252 0 0 500 250 1)(253 0 0 500 250 1)(254 0 0 500 250 1)(255 0 0 500 250 1)(256 0 0 500 250 1)(257 0 0 500 250 1)(258 0 0 500 250 1)(259 0 0 500 250 1)(260 0 0 500 250 1)(261 0 0 500 250 1)(262 0 0 500 250 1)(263 0 0 500 250 1)(264 0 0 500 250 1)(265 0 0 500 250 1)(266 0 0 500 250 1)(267 0 0 500 250 1)(268 0 0 500 250 1)(269 0 0 500 250 1)(270 0 0 500 250 1)(271 0 0 500 250 1)(272 0 0 500 250 1)(273 0 0 500 250 1)(274 0 0 500 250 1)(275 0 0 500 250 1)(276 0 0 500 250 1)(277 0 0 500 250 1)(278 0 0 500 250 1)(279 0 0 500 250 1)(280 0 0 500 250 1)(281 0 0 500 250 1)(282 0 0 500 250 1)(283 0 0 500 250 1)(284 0 0 500 250 1)(285 0 0 500 250 1)(286 0 0 500 250 1)(287 0 0 500 250 1)(288 0 0 500 250 1)(289 0 0 500 250 1)(290 0 0 500 250 1)(291 0 0 500 250 1)(292 0 0 500 250 1)(293 0 0 500 250 1)(294 0 0 500 250 1)(295 0 0 500 250 1)(296 0 0 500 250 1)(297 0 0 500 250 1)(298 0 0 500 250 1)(299 0 0 500 250 1)(300 0 0 500 250 1)(301 0 0 500 250 1)(302 0 0 500 250 1)(303 0 0 500 250 1)(304 0 0 500 250 1)(305 0 0 500 250 1)(306 0 0 500 250 1)(307 0 0 500 250 1)(308 0 0 500 250 1)(309 0 0 500 250 1)(310 0 0 500 250 1)(311 0 0 500 250 1)(312 0 0 500 250 1)(313 0 0 500 250 1)(314 0 0 500 250 1)(315 0 0 500 250 1)(316 0 0 500 250 1)(317 0 0 500 250 1)(318 0 0 500 250 1)(319 0 0 500 250 1)(320 0 0 500 250 1)(321 0 0 500 250 1)(322 0 0 500 250 1)(323 0 0 500 250 1)(324 0 0 500 250 1)(325 0 0 500 250 1)(326 0 0 500 250 1)(327 0 0 500 250 1)(328 0 0 500 250 1)(329 0 0 500 250 1)(330 0 0 500 250 1)(331 0 0 500 250 1)(332 0 0 500 250 1)(333 0 0 500 250 1)(334 0 0 500 250 1)(335 0 0 500 250 1)(336 0 0 500 250 1)(337 0 0 500 250 1)(338 0 0 500 250 1)(339 0 0 500 250 1)(340 0 0 500 250 1)(341 0 0 500 250 1)(342 0 0 500 250 1)(343 0 0 500 250 1)(344 0 0 500 250 1)(345 0 0 500 250 1)(346 0 0 500 250 1)(347 0 0 500 250 1)(348 0 0 500 250 1)(349 0 0 500 250 1)(350 0 0 500 250 1)(351 0 0 500 250 1)(352 0 0 500 250 1)(353 0 0 500 250 1)(354 0 0 500 250 1)(355 0 0 500 250 1)(356 0 0 500 250 1)(357 0 0 500 250 1)(358 0 0 500 250 1)(359 0 0 500 250 1)(360 0 0 500 250 1)(361 0 0 500 250 1)(362 0 0 500 250 1)(363 0 0 500 250 1)(364 0 0 500 250 1)(365 0 0 500 250 1)(366 0 0 500 250 1)(367 0 0 500 250 1)(368 0 0 500 250 1)(369 0 0 500 250 1)(370 0 0 500 250 1)(371 0 0 500 250 1)(372 0 0 500 250 1)(373 0 0 500 250 1)(374 0 0 500 250 1)(375 0 0 500 250 1)(376 0 0 500 250 1)(377 0 0 500 250 1)(378 0 0 500 250 1)(379 0 0 500 250 1)(380 0 0 500 250 1)(381 0 0 500 250 1)(382 0 0 500 250 1)(383 0 0 500 250 1)',
                    '~snsChanMap': '(384,384,1)(AP0;0:0)(AP1;1:1)(AP2;2:2)(AP3;3:3)(AP4;4:4)(AP5;5:5)(AP6;6:6)(AP7;7:7)(AP8;8:8)(AP9;9:9)(AP10;10:10)(AP11;11:11)(AP12;12:12)(AP13;13:13)(AP14;14:14)(AP15;15:15)(AP16;16:16)(AP17;17:17)(AP18;18:18)(AP19;19:19)(AP20;20:20)(AP21;21:21)(AP22;22:22)(AP23;23:23)(AP24;24:24)(AP25;25:25)(AP26;26:26)(AP27;27:27)(AP28;28:28)(AP29;29:29)(AP30;30:30)(AP31;31:31)(AP32;32:32)(AP33;33:33)(AP34;34:34)(AP35;35:35)(AP36;36:36)(AP37;37:37)(AP38;38:38)(AP39;39:39)(AP40;40:40)(AP41;41:41)(AP42;42:42)(AP43;43:43)(AP44;44:44)(AP45;45:45)(AP46;46:46)(AP47;47:47)(AP48;48:48)(AP49;49:49)(AP50;50:50)(AP51;51:51)(AP52;52:52)(AP53;53:53)(AP54;54:54)(AP55;55:55)(AP56;56:56)(AP57;57:57)(AP58;58:58)(AP59;59:59)(AP60;60:60)(AP61;61:61)(AP62;62:62)(AP63;63:63)(AP64;64:64)(AP65;65:65)(AP66;66:66)(AP67;67:67)(AP68;68:68)(AP69;69:69)(AP70;70:70)(AP71;71:71)(AP72;72:72)(AP73;73:73)(AP74;74:74)(AP75;75:75)(AP76;76:76)(AP77;77:77)(AP78;78:78)(AP79;79:79)(AP80;80:80)(AP81;81:81)(AP82;82:82)(AP83;83:83)(AP84;84:84)(AP85;85:85)(AP86;86:86)(AP87;87:87)(AP88;88:88)(AP89;89:89)(AP90;90:90)(AP91;91:91)(AP92;92:92)(AP93;93:93)(AP94;94:94)(AP95;95:95)(AP96;96:96)(AP97;97:97)(AP98;98:98)(AP99;99:99)(AP100;100:100)(AP101;101:101)(AP102;102:102)(AP103;103:103)(AP104;104:104)(AP105;105:105)(AP106;106:106)(AP107;107:107)(AP108;108:108)(AP109;109:109)(AP110;110:110)(AP111;111:111)(AP112;112:112)(AP113;113:113)(AP114;114:114)(AP115;115:115)(AP116;116:116)(AP117;117:117)(AP118;118:118)(AP119;119:119)(AP120;120:120)(AP121;121:121)(AP122;122:122)(AP123;123:123)(AP124;124:124)(AP125;125:125)(AP126;126:126)(AP127;127:127)(AP128;128:128)(AP129;129:129)(AP130;130:130)(AP131;131:131)(AP132;132:132)(AP133;133:133)(AP134;134:134)(AP135;135:135)(AP136;136:136)(AP137;137:137)(AP138;138:138)(AP139;139:139)(AP140;140:140)(AP141;141:141)(AP142;142:142)(AP143;143:143)(AP144;144:144)(AP145;145:145)(AP146;146:146)(AP147;147:147)(AP148;148:148)(AP149;149:149)(AP150;150:150)(AP151;151:151)(AP152;152:152)(AP153;153:153)(AP154;154:154)(AP155;155:155)(AP156;156:156)(AP157;157:157)(AP158;158:158)(AP159;159:159)(AP160;160:160)(AP161;161:161)(AP162;162:162)(AP163;163:163)(AP164;164:164)(AP165;165:165)(AP166;166:166)(AP167;167:167)(AP168;168:168)(AP169;169:169)(AP170;170:170)(AP171;171:171)(AP172;172:172)(AP173;173:173)(AP174;174:174)(AP175;175:175)(AP176;176:176)(AP177;177:177)(AP178;178:178)(AP179;179:179)(AP180;180:180)(AP181;181:181)(AP182;182:182)(AP183;183:183)(AP184;184:184)(AP185;185:185)(AP186;186:186)(AP187;187:187)(AP188;188:188)(AP189;189:189)(AP190;190:190)(AP191;191:191)(AP192;192:192)(AP193;193:193)(AP194;194:194)(AP195;195:195)(AP196;196:196)(AP197;197:197)(AP198;198:198)(AP199;199:199)(AP200;200:200)(AP201;201:201)(AP202;202:202)(AP203;203:203)(AP204;204:204)(AP205;205:205)(AP206;206:206)(AP207;207:207)(AP208;208:208)(AP209;209:209)(AP210;210:210)(AP211;211:211)(AP212;212:212)(AP213;213:213)(AP214;214:214)(AP215;215:215)(AP216;216:216)(AP217;217:217)(AP218;218:218)(AP219;219:219)(AP220;220:220)(AP221;221:221)(AP222;222:222)(AP223;223:223)(AP224;224:224)(AP225;225:225)(AP226;226:226)(AP227;227:227)(AP228;228:228)(AP229;229:229)(AP230;230:230)(AP231;231:231)(AP232;232:232)(AP233;233:233)(AP234;234:234)(AP235;235:235)(AP236;236:236)(AP237;237:237)(AP238;238:238)(AP239;239:239)(AP240;240:240)(AP241;241:241)(AP242;242:242)(AP243;243:243)(AP244;244:244)(AP245;245:245)(AP246;246:246)(AP247;247:247)(AP248;248:248)(AP249;249:249)(AP250;250:250)(AP251;251:251)(AP252;252:252)(AP253;253:253)(AP254;254:254)(AP255;255:255)(AP256;256:256)(AP257;257:257)(AP258;258:258)(AP259;259:259)(AP260;260:260)(AP261;261:261)(AP262;262:262)(AP263;263:263)(AP264;264:264)(AP265;265:265)(AP266;266:266)(AP267;267:267)(AP268;268:268)(AP269;269:269)(AP270;270:270)(AP271;271:271)(AP272;272:272)(AP273;273:273)(AP274;274:274)(AP275;275:275)(AP276;276:276)(AP277;277:277)(AP278;278:278)(AP279;279:279)(AP280;280:280)(AP281;281:281)(AP282;282:282)(AP283;283:283)(AP284;284:284)(AP285;285:285)(AP286;286:286)(AP287;287:287)(AP288;288:288)(AP289;289:289)(AP290;290:290)(AP291;291:291)(AP292;292:292)(AP293;293:293)(AP294;294:294)(AP295;295:295)(AP296;296:296)(AP297;297:297)(AP298;298:298)(AP299;299:299)(AP300;300:300)(AP301;301:301)(AP302;302:302)(AP303;303:303)(AP304;304:304)(AP305;305:305)(AP306;306:306)(AP307;307:307)(AP308;308:308)(AP309;309:309)(AP310;310:310)(AP311;311:311)(AP312;312:312)(AP313;313:313)(AP314;314:314)(AP315;315:315)(AP316;316:316)(AP317;317:317)(AP318;318:318)(AP319;319:319)(AP320;320:320)(AP321;321:321)(AP322;322:322)(AP323;323:323)(AP324;324:324)(AP325;325:325)(AP326;326:326)(AP327;327:327)(AP328;328:328)(AP329;329:329)(AP330;330:330)(AP331;331:331)(AP332;332:332)(AP333;333:333)(AP334;334:334)(AP335;335:335)(AP336;336:336)(AP337;337:337)(AP338;338:338)(AP339;339:339)(AP340;340:340)(AP341;341:341)(AP342;342:342)(AP343;343:343)(AP344;344:344)(AP345;345:345)(AP346;346:346)(AP347;347:347)(AP348;348:348)(AP349;349:349)(AP350;350:350)(AP351;351:351)(AP352;352:352)(AP353;353:353)(AP354;354:354)(AP355;355:355)(AP356;356:356)(AP357;357:357)(AP358;358:358)(AP359;359:359)(AP360;360:360)(AP361;361:361)(AP362;362:362)(AP363;363:363)(AP364;364:364)(AP365;365:365)(AP366;366:366)(AP367;367:367)(AP368;368:368)(AP369;369:369)(AP370;370:370)(AP371;371:371)(AP372;372:372)(AP373;373:373)(AP374;374:374)(AP375;375:375)(AP376;376:376)(AP377;377:377)(AP378;378:378)(AP379;379:379)(AP380;380:380)(AP381;381:381)(AP382;382:382)(AP383;383:383)(SY0;768:768)',
                    '~snsShankMap': '(1,2,480)(0:0:0:1)(0:1:0:1)(0:0:1:1)(0:1:1:1)(0:0:2:1)(0:1:2:1)(0:0:3:1)(0:1:3:1)(0:0:4:1)(0:1:4:1)(0:0:5:1)(0:1:5:1)(0:0:6:1)(0:1:6:1)(0:0:7:1)(0:1:7:1)(0:0:8:1)(0:1:8:1)(0:0:9:1)(0:1:9:1)(0:0:10:1)(0:1:10:1)(0:0:11:1)(0:1:11:1)(0:0:12:1)(0:1:12:1)(0:0:13:1)(0:1:13:1)(0:0:14:1)(0:1:14:1)(0:0:15:1)(0:1:15:1)(0:0:16:1)(0:1:16:1)(0:0:17:1)(0:1:17:1)(0:0:18:1)(0:1:18:1)(0:0:19:1)(0:1:19:1)(0:0:20:1)(0:1:20:1)(0:0:21:1)(0:1:21:1)(0:0:22:1)(0:1:22:1)(0:0:23:1)(0:1:23:1)(0:0:24:1)(0:1:24:1)(0:0:25:1)(0:1:25:1)(0:0:26:1)(0:1:26:1)(0:0:27:1)(0:1:27:1)(0:0:28:1)(0:1:28:1)(0:0:29:1)(0:1:29:1)(0:0:30:1)(0:1:30:1)(0:0:31:1)(0:1:31:1)(0:0:32:1)(0:1:32:1)(0:0:33:1)(0:1:33:1)(0:0:34:1)(0:1:34:1)(0:0:35:1)(0:1:35:1)(0:0:36:1)(0:1:36:1)(0:0:37:1)(0:1:37:1)(0:0:38:1)(0:1:38:1)(0:0:39:1)(0:1:39:1)(0:0:40:1)(0:1:40:1)(0:0:41:1)(0:1:41:1)(0:0:42:1)(0:1:42:1)(0:0:43:1)(0:1:43:1)(0:0:44:1)(0:1:44:1)(0:0:45:1)(0:1:45:1)(0:0:46:1)(0:1:46:1)(0:0:47:1)(0:1:47:1)(0:0:48:1)(0:1:48:1)(0:0:49:1)(0:1:49:1)(0:0:50:1)(0:1:50:1)(0:0:51:1)(0:1:51:1)(0:0:52:1)(0:1:52:1)(0:0:53:1)(0:1:53:1)(0:0:54:1)(0:1:54:1)(0:0:55:1)(0:1:55:1)(0:0:56:1)(0:1:56:1)(0:0:57:1)(0:1:57:1)(0:0:58:1)(0:1:58:1)(0:0:59:1)(0:1:59:1)(0:0:60:1)(0:1:60:1)(0:0:61:1)(0:1:61:1)(0:0:62:1)(0:1:62:1)(0:0:63:1)(0:1:63:1)(0:0:64:1)(0:1:64:1)(0:0:65:1)(0:1:65:1)(0:0:66:1)(0:1:66:1)(0:0:67:1)(0:1:67:1)(0:0:68:1)(0:1:68:1)(0:0:69:1)(0:1:69:1)(0:0:70:1)(0:1:70:1)(0:0:71:1)(0:1:71:1)(0:0:72:1)(0:1:72:1)(0:0:73:1)(0:1:73:1)(0:0:74:1)(0:1:74:1)(0:0:75:1)(0:1:75:1)(0:0:76:1)(0:1:76:1)(0:0:77:1)(0:1:77:1)(0:0:78:1)(0:1:78:1)(0:0:79:1)(0:1:79:1)(0:0:80:1)(0:1:80:1)(0:0:81:1)(0:1:81:1)(0:0:82:1)(0:1:82:1)(0:0:83:1)(0:1:83:1)(0:0:84:1)(0:1:84:1)(0:0:85:1)(0:1:85:1)(0:0:86:1)(0:1:86:1)(0:0:87:1)(0:1:87:1)(0:0:88:1)(0:1:88:1)(0:0:89:1)(0:1:89:1)(0:0:90:1)(0:1:90:1)(0:0:91:1)(0:1:91:1)(0:0:92:1)(0:1:92:1)(0:0:93:1)(0:1:93:1)(0:0:94:1)(0:1:94:1)(0:0:95:1)(0:1:95:0)(0:0:96:1)(0:1:96:1)(0:0:97:1)(0:1:97:1)(0:0:98:1)(0:1:98:1)(0:0:99:1)(0:1:99:1)(0:0:100:1)(0:1:100:1)(0:0:101:1)(0:1:101:1)(0:0:102:1)(0:1:102:1)(0:0:103:1)(0:1:103:1)(0:0:104:1)(0:1:104:1)(0:0:105:1)(0:1:105:1)(0:0:106:1)(0:1:106:1)(0:0:107:1)(0:1:107:1)(0:0:108:1)(0:1:108:1)(0:0:109:1)(0:1:109:1)(0:0:110:1)(0:1:110:1)(0:0:111:1)(0:1:111:1)(0:0:112:1)(0:1:112:1)(0:0:113:1)(0:1:113:1)(0:0:114:1)(0:1:114:1)(0:0:115:1)(0:1:115:1)(0:0:116:1)(0:1:116:1)(0:0:117:1)(0:1:117:1)(0:0:118:1)(0:1:118:1)(0:0:119:1)(0:1:119:1)(0:0:120:1)(0:1:120:1)(0:0:121:1)(0:1:121:1)(0:0:122:1)(0:1:122:1)(0:0:123:1)(0:1:123:1)(0:0:124:1)(0:1:124:1)(0:0:125:1)(0:1:125:1)(0:0:126:1)(0:1:126:1)(0:0:127:1)(0:1:127:1)(0:0:128:1)(0:1:128:1)(0:0:129:1)(0:1:129:1)(0:0:130:1)(0:1:130:1)(0:0:131:1)(0:1:131:1)(0:0:132:1)(0:1:132:1)(0:0:133:1)(0:1:133:1)(0:0:134:1)(0:1:134:1)(0:0:135:1)(0:1:135:1)(0:0:136:1)(0:1:136:1)(0:0:137:1)(0:1:137:1)(0:0:138:1)(0:1:138:1)(0:0:139:1)(0:1:139:1)(0:0:140:1)(0:1:140:1)(0:0:141:1)(0:1:141:1)(0:0:142:1)(0:1:142:1)(0:0:143:1)(0:1:143:1)(0:0:144:1)(0:1:144:1)(0:0:145:1)(0:1:145:1)(0:0:146:1)(0:1:146:1)(0:0:147:1)(0:1:147:1)(0:0:148:1)(0:1:148:1)(0:0:149:1)(0:1:149:1)(0:0:150:1)(0:1:150:1)(0:0:151:1)(0:1:151:1)(0:0:152:1)(0:1:152:1)(0:0:153:1)(0:1:153:1)(0:0:154:1)(0:1:154:1)(0:0:155:1)(0:1:155:1)(0:0:156:1)(0:1:156:1)(0:0:157:1)(0:1:157:1)(0:0:158:1)(0:1:158:1)(0:0:159:1)(0:1:159:1)(0:0:160:1)(0:1:160:1)(0:0:161:1)(0:1:161:1)(0:0:162:1)(0:1:162:1)(0:0:163:1)(0:1:163:1)(0:0:164:1)(0:1:164:1)(0:0:165:1)(0:1:165:1)(0:0:166:1)(0:1:166:1)(0:0:167:1)(0:1:167:1)(0:0:168:1)(0:1:168:1)(0:0:169:1)(0:1:169:1)(0:0:170:1)(0:1:170:1)(0:0:171:1)(0:1:171:1)(0:0:172:1)(0:1:172:1)(0:0:173:1)(0:1:173:1)(0:0:174:1)(0:1:174:1)(0:0:175:1)(0:1:175:1)(0:0:176:1)(0:1:176:1)(0:0:177:1)(0:1:177:1)(0:0:178:1)(0:1:178:1)(0:0:179:1)(0:1:179:1)(0:0:180:1)(0:1:180:1)(0:0:181:1)(0:1:181:1)(0:0:182:1)(0:1:182:1)(0:0:183:1)(0:1:183:1)(0:0:184:1)(0:1:184:1)(0:0:185:1)(0:1:185:1)(0:0:186:1)(0:1:186:1)(0:0:187:1)(0:1:187:1)(0:0:188:1)(0:1:188:1)(0:0:189:1)(0:1:189:1)(0:0:190:1)(0:1:190:1)(0:0:191:1)(0:1:191:1)',
        }
        with open(metaname, 'w') as f:
            for k in meta_dict.keys():
                f.write(f'{k}={meta_dict[k]}\n')
    
        np.savez(os.path.splitext(filename)[0] + '_params.npz', 
                 st=st_sim, cl=cl_sim, amp=amp_sim, 
                 wfid=wfid_sim, drift=drift_sim_ups)
    
        return st_sim, cl_sim, amp_sim, wf_sim, cb_sim, wfid_sim, drift_sim_ups
    
    # time constants from hybrid
    NT = 60000
    fs = 30000
    nt = 61
    twav_min = 20
    tw = nt + 60 
    ntw = 30 + twav_min
    ptw = 30 + nt
    
    def waveforms_from_recording(filename_bg, NT, n_chan_bin, nt, twav_min, chan_map):
        
        # load kilosort4 output
        data_folder = os.path.split(filename_bg)[0]
        ks4_folder = os.path.join(data_folder, 'kilosort4/')   
        ops = np.load(os.path.join(ks4_folder, 'ops.npy'), allow_pickle=True).item()
        st = np.load(os.path.join(ks4_folder, 'spike_times.npy'))
        cl = np.load(os.path.join(ks4_folder, 'spike_clusters.npy'))
        templates = np.load(os.path.join(ks4_folder, 'templates.npy'))
        wf_cb = ((templates**2).sum(axis=-2)**0.5).argmax(axis=1)
        m = pd.read_csv(os.path.join(ks4_folder, 'cluster_KSLabel.tsv'), sep='\t')   
        is_ref = m['KSLabel'].values=="good"
        nc = np.unique(cl, return_counts=True)[1]
        # 0.5 hz firing rate or higher
        cinds = np.nonzero(is_ref & (nc>ops["Nbatches"]))[0]
        
        # remove any spikes before padding window for waveform computation
        cls = cl[st > ntw]
        sts = st[st > ntw]
        iref_c = np.isin(cls, cinds)
        cls = cls[iref_c].astype("int64")
        sts = sts[iref_c].astype("int64")
        n_neurons = len(cinds)
        print('n_neurons = ', len(cinds))
        wf_cb = wf_cb[cinds]
    
        ncm = len(chan_map)
        wfa = np.zeros((n_neurons, tw, ncm))
        nst = np.zeros(n_neurons, "int")
        tic = time.time()
                
        print('computing waveforms from original recording')
        with io.BinaryRWFile(filename_bg, n_chan_bin, NT=NT) as bfile:
            n_batches = bfile.n_batches
            for ibatch in trange(n_batches):
                bstart = max(0, ibatch * NT - ntw)
                bend = min(bfile.n_samples, (ibatch+1) * NT + ptw)
                slc = slice(bstart, bend)
                data = np.asarray(bfile[slc].T)
                data = data[chan_map]
                ist = np.logical_and(sts >= bstart + ntw, sts < bend - (tw+twav_min))
                j=0
                sts_sub = sts[ist]
                cls_sub = cls[ist]
                for i in cinds:
                    ic = cls_sub == i
                    if ic.sum() > 0:
                        stb = sts_sub[ic] - bstart - twav_min
                        stb = stb[:,np.newaxis] + np.arange(-ntw+twav_min, ptw)[np.newaxis,:]
                        spks = data[:, stb] 
                        wfa[j] += spks.sum(axis=1).T 
                        nst[j] += ic.sum()
                    j+=1
                #if ibatch%100 == 0:
                #    print(f"{ibatch} / {n_batches} batches, time {time.time()-tic:.2f}s")
        
        wfs = np.zeros((n_neurons, tw, n_chan_bin))
        # normalize waveforms by number of spikes
        wfs[:,:,chan_map] = (wfa.copy() / nst[:, np.newaxis, np.newaxis])
        # subtract by baseline per channel
        wfs -= wfs[:,0:10].mean(axis=-2, keepdims=True)
        wfs = wfs.astype("int16")
        # best channel
        wfs_c = chan_map[wf_cb]
        # center waveform on best channel
        xr = wfs_c[:,np.newaxis] + np.arange(-8, 8)
        print(xr.shape)
        xr = xr - np.minimum(0, xr[:,:1])
        xr = xr + np.minimum(0, ncm - 1 - xr[:,-1:])
        # get 1st channel
        chan0 = xr[:,0]
        wfs = wfs[np.arange(0,wfs.shape[0])[:,np.newaxis],:,xr]
    
        return wfs, cinds, chan0
    
    
    def generate_hybrid_spikes(filename_bg, chan_map, wfs, cinds, chan0, 
                      n_batches_sim=1350, neuron_seed=11, n_sim=100):
        # firing rates in background dataset
        data_folder = os.path.split(filename_bg)[0]
        ks4_folder = os.path.join(data_folder, 'kilosort4/')   
        templates = np.load(os.path.join(ks4_folder, 'templates.npy'))
        st_bg = np.load(os.path.join(ks4_folder, 'spike_times.npy'))
        clu_bg = np.load(os.path.join(ks4_folder, 'spike_clusters.npy'))
        chan_best = chan_map[((templates**2).sum(axis=-2)**0.5).argmax(axis=1)]
        clu_bg = chan_best[clu_bg].astype("int")
    
        
        sp_bg = csr_matrix((np.ones_like(clu_bg), (clu_bg, (st_bg/3000).astype("int"))))
        sp_bg = sp_bg.toarray().astype("float32")
        sp_bg = gaussian_filter1d(sp_bg, 10, axis=0)
        sp_bg /= sp_bg.mean(axis=1, keepdims=True)
        plt.imshow(sp_bg, vmin=0, vmax=2, aspect="auto")
    
        print('creating spike trains')
        # create spikes to add
        np.random.seed(neuron_seed)
        # waveforms
        n_neurons, nch, tw = wfs.shape
        i_sim = np.random.permutation(n_neurons)[:n_sim]
        ci_sim = cinds[i_sim]
        wf_sim = wfs[i_sim].copy()
        cb_sim = chan0[i_sim].copy() 
    
        # move original waveform up or down 8 channels
        cb_sim += 8 * (2*(np.random.rand(n_sim)>0.5) - 1)
        cb_sim[cb_sim < 0] += 16
        cb_sim[cb_sim > len(chan_map)-17] -= 16
    
        # firing rates and spike trains
        st_sim = np.zeros(0, 'uint64')
        cl_sim = np.zeros(0, 'uint32')
        frs = np.maximum(0.05, 2.5 * sp_bg[cb_sim])
        t_max = NT * n_batches_sim
        for i in range(n_sim):
            # spike train
            fr = frs[i]
            r = np.random.exponential(1, 10000)
            j = 0
            t = 0
            spi = np.zeros(10000, "uint32")
            while t < t_max:
                isi = int(r[j] * 30000 / fr[int(t//3000)])
                # refractory period
                isi += 60        
                t += isi
                spi[j] = t
                j += 1
            spi = spi[:j]
            st_sim = np.append(st_sim, spi, axis=0)
            cl_sim = np.append(cl_sim, i * np.ones(len(spi), 'uint32'), axis=0)
            
        # re-sort by time
        ii = st_sim.argsort()
        cl_sim = cl_sim[ii]
        st_sim = st_sim[ii]
    
        # remove spikes after max recording time
        n_max = np.nonzero(st_sim > t_max-ptw-1)[0][0] if st_sim[-1] > t_max-1 else len(st_sim)
        n_min = np.nonzero(st_sim < ntw)[0] 
        n_min = 0 if len(n_min) == 0 else n_min[-1] + 1
        st_sim = st_sim[n_min : n_max]
        cl_sim = cl_sim[n_min : n_max]
        print(len(st_sim))
    
        # create spikes with waveforms
        n_batches_sim = min(n_batches_sim, int(np.ceil((st_sim.max() + tw) / NT)))
        print(f"n_batches_sim = {n_batches_sim}")
        data = np.zeros((NT * n_batches_sim, 385), "int16")
        n_splits = 3
        tic=time.time()
        # loop over neurons
        print(f'creating spikes from {n_sim} neurons to add')
        for i in trange(n_sim):
            # channels for waveform
            slc_c = slice(cb_sim[i], cb_sim[i]+nch)
            # spikes
            sti = st_sim[cl_sim == i]
            wfi = wf_sim[i].T
            for k in range(n_splits):
                ki = np.arange(k, len(sti), n_splits)
                # indices for spike waveform
                stb = sti[ki] - int(ntw)
                stb = stb[:,np.newaxis].astype(int) + np.arange(0, tw, 1, int)
                data[stb, slc_c] += wfi
                   
        print(f'created spikes of size {data.shape}')
    
        frsim = np.unique(cl_sim, return_counts=True)[1]
        print(f'mean firing rate = {frsim.mean()/(st_sim.max()/fs):.2f}')
    
        return data, (st_sim, cl_sim, ci_sim, wf_sim, cb_sim)
    
    def hybrid_simulation(filename_bg, sim_name, n_batches_sim=1350, 
                            wfs=None, cinds=None, chan0=None):
        
        n_chan_bin = 385
        probe = io.load_probe("/home/carsen/.kilosort/probes/neuropixPhase3B1_kilosortChanMap.prb")
        chan_map = probe["chanMap"]
        
        filename = f'/media/carsen/ssd1/spikesorting/sim_{sim_name}/sim.imec0.ap.bin'
        os.makedirs(os.path.split(filename)[0], exist_ok=True)
        print("writing to file:")
        print(filename)
    
        ### compute waveforms in ground truth
        if wfs is None:
            wfs, cinds, chan0 = waveforms_from_recording(filename_bg, NT, n_chan_bin, nt, twav_min, chan_map)
    
        ### generate spike trains with similar firing profile moved +/- 8 channels
        data, params = generate_hybrid_spikes(filename_bg, chan_map, wfs, cinds, chan0, 
                                                 n_batches_sim=n_batches_sim)
        st_sim, cl_sim, ci_sim, wf_sim, cb_sim = params
    
        print("using original recording as background for new spikes")
        # writing binary
        with io.BinaryRWFile(filename_bg, n_chan_bin, NT=NT) as bfile_bg:
            with io.BinaryRWFile(filename, n_chan_bin, NT=NT, 
                                    write=True, n_samples=data.shape[0]) as bfile:
                for ibatch in trange(n_batches_sim):
                    slc = slice(ibatch * NT, 
                            min(n_batches_sim * NT, (ibatch+1) * NT))
                    bg = np.asarray(bfile_bg[slc]).copy()
                    #X = np.zeros((slc.stop - slc.start, n_chan_bin), "float32")
                    X = np.asarray(data[slc]).copy()#
                    X = X + bg
                    to_write = np.clip(X, -2**15 + 1, 2**15 - 1).astype('int16')
                    bfile[slc] = to_write
                    
        # create meta file for probe
        metaname = os.path.splitext(filename)[0] + '.meta'
        meta_dict = {'fileSizeBytes': os.path.getsize(filename),
                    'typeThis': 'imec',
                    'imSampRate': 30000,
                    'imAiRangeMax': 0.6,
                    'imAiRangeMin': -0.6,
                    'nSavedChans': 385,
                    'snsApLfSy': '384,0,1',
                    'snsSaveChanSubset': '0:383,768',
                    '~imroTbl': '(0,384)(0 0 0 500 250 1)(1 0 0 500 250 1)(2 0 0 500 250 1)(3 0 0 500 250 1)(4 0 0 500 250 1)(5 0 0 500 250 1)(6 0 0 500 250 1)(7 0 0 500 250 1)(8 0 0 500 250 1)(9 0 0 500 250 1)(10 0 0 500 250 1)(11 0 0 500 250 1)(12 0 0 500 250 1)(13 0 0 500 250 1)(14 0 0 500 250 1)(15 0 0 500 250 1)(16 0 0 500 250 1)(17 0 0 500 250 1)(18 0 0 500 250 1)(19 0 0 500 250 1)(20 0 0 500 250 1)(21 0 0 500 250 1)(22 0 0 500 250 1)(23 0 0 500 250 1)(24 0 0 500 250 1)(25 0 0 500 250 1)(26 0 0 500 250 1)(27 0 0 500 250 1)(28 0 0 500 250 1)(29 0 0 500 250 1)(30 0 0 500 250 1)(31 0 0 500 250 1)(32 0 0 500 250 1)(33 0 0 500 250 1)(34 0 0 500 250 1)(35 0 0 500 250 1)(36 0 0 500 250 1)(37 0 0 500 250 1)(38 0 0 500 250 1)(39 0 0 500 250 1)(40 0 0 500 250 1)(41 0 0 500 250 1)(42 0 0 500 250 1)(43 0 0 500 250 1)(44 0 0 500 250 1)(45 0 0 500 250 1)(46 0 0 500 250 1)(47 0 0 500 250 1)(48 0 0 500 250 1)(49 0 0 500 250 1)(50 0 0 500 250 1)(51 0 0 500 250 1)(52 0 0 500 250 1)(53 0 0 500 250 1)(54 0 0 500 250 1)(55 0 0 500 250 1)(56 0 0 500 250 1)(57 0 0 500 250 1)(58 0 0 500 250 1)(59 0 0 500 250 1)(60 0 0 500 250 1)(61 0 0 500 250 1)(62 0 0 500 250 1)(63 0 0 500 250 1)(64 0 0 500 250 1)(65 0 0 500 250 1)(66 0 0 500 250 1)(67 0 0 500 250 1)(68 0 0 500 250 1)(69 0 0 500 250 1)(70 0 0 500 250 1)(71 0 0 500 250 1)(72 0 0 500 250 1)(73 0 0 500 250 1)(74 0 0 500 250 1)(75 0 0 500 250 1)(76 0 0 500 250 1)(77 0 0 500 250 1)(78 0 0 500 250 1)(79 0 0 500 250 1)(80 0 0 500 250 1)(81 0 0 500 250 1)(82 0 0 500 250 1)(83 0 0 500 250 1)(84 0 0 500 250 1)(85 0 0 500 250 1)(86 0 0 500 250 1)(87 0 0 500 250 1)(88 0 0 500 250 1)(89 0 0 500 250 1)(90 0 0 500 250 1)(91 0 0 500 250 1)(92 0 0 500 250 1)(93 0 0 500 250 1)(94 0 0 500 250 1)(95 0 0 500 250 1)(96 0 0 500 250 1)(97 0 0 500 250 1)(98 0 0 500 250 1)(99 0 0 500 250 1)(100 0 0 500 250 1)(101 0 0 500 250 1)(102 0 0 500 250 1)(103 0 0 500 250 1)(104 0 0 500 250 1)(105 0 0 500 250 1)(106 0 0 500 250 1)(107 0 0 500 250 1)(108 0 0 500 250 1)(109 0 0 500 250 1)(110 0 0 500 250 1)(111 0 0 500 250 1)(112 0 0 500 250 1)(113 0 0 500 250 1)(114 0 0 500 250 1)(115 0 0 500 250 1)(116 0 0 500 250 1)(117 0 0 500 250 1)(118 0 0 500 250 1)(119 0 0 500 250 1)(120 0 0 500 250 1)(121 0 0 500 250 1)(122 0 0 500 250 1)(123 0 0 500 250 1)(124 0 0 500 250 1)(125 0 0 500 250 1)(126 0 0 500 250 1)(127 0 0 500 250 1)(128 0 0 500 250 1)(129 0 0 500 250 1)(130 0 0 500 250 1)(131 0 0 500 250 1)(132 0 0 500 250 1)(133 0 0 500 250 1)(134 0 0 500 250 1)(135 0 0 500 250 1)(136 0 0 500 250 1)(137 0 0 500 250 1)(138 0 0 500 250 1)(139 0 0 500 250 1)(140 0 0 500 250 1)(141 0 0 500 250 1)(142 0 0 500 250 1)(143 0 0 500 250 1)(144 0 0 500 250 1)(145 0 0 500 250 1)(146 0 0 500 250 1)(147 0 0 500 250 1)(148 0 0 500 250 1)(149 0 0 500 250 1)(150 0 0 500 250 1)(151 0 0 500 250 1)(152 0 0 500 250 1)(153 0 0 500 250 1)(154 0 0 500 250 1)(155 0 0 500 250 1)(156 0 0 500 250 1)(157 0 0 500 250 1)(158 0 0 500 250 1)(159 0 0 500 250 1)(160 0 0 500 250 1)(161 0 0 500 250 1)(162 0 0 500 250 1)(163 0 0 500 250 1)(164 0 0 500 250 1)(165 0 0 500 250 1)(166 0 0 500 250 1)(167 0 0 500 250 1)(168 0 0 500 250 1)(169 0 0 500 250 1)(170 0 0 500 250 1)(171 0 0 500 250 1)(172 0 0 500 250 1)(173 0 0 500 250 1)(174 0 0 500 250 1)(175 0 0 500 250 1)(176 0 0 500 250 1)(177 0 0 500 250 1)(178 0 0 500 250 1)(179 0 0 500 250 1)(180 0 0 500 250 1)(181 0 0 500 250 1)(182 0 0 500 250 1)(183 0 0 500 250 1)(184 0 0 500 250 1)(185 0 0 500 250 1)(186 0 0 500 250 1)(187 0 0 500 250 1)(188 0 0 500 250 1)(189 0 0 500 250 1)(190 0 0 500 250 1)(191 0 0 500 250 1)(192 0 0 500 250 1)(193 0 0 500 250 1)(194 0 0 500 250 1)(195 0 0 500 250 1)(196 0 0 500 250 1)(197 0 0 500 250 1)(198 0 0 500 250 1)(199 0 0 500 250 1)(200 0 0 500 250 1)(201 0 0 500 250 1)(202 0 0 500 250 1)(203 0 0 500 250 1)(204 0 0 500 250 1)(205 0 0 500 250 1)(206 0 0 500 250 1)(207 0 0 500 250 1)(208 0 0 500 250 1)(209 0 0 500 250 1)(210 0 0 500 250 1)(211 0 0 500 250 1)(212 0 0 500 250 1)(213 0 0 500 250 1)(214 0 0 500 250 1)(215 0 0 500 250 1)(216 0 0 500 250 1)(217 0 0 500 250 1)(218 0 0 500 250 1)(219 0 0 500 250 1)(220 0 0 500 250 1)(221 0 0 500 250 1)(222 0 0 500 250 1)(223 0 0 500 250 1)(224 0 0 500 250 1)(225 0 0 500 250 1)(226 0 0 500 250 1)(227 0 0 500 250 1)(228 0 0 500 250 1)(229 0 0 500 250 1)(230 0 0 500 250 1)(231 0 0 500 250 1)(232 0 0 500 250 1)(233 0 0 500 250 1)(234 0 0 500 250 1)(235 0 0 500 250 1)(236 0 0 500 250 1)(237 0 0 500 250 1)(238 0 0 500 250 1)(239 0 0 500 250 1)(240 0 0 500 250 1)(241 0 0 500 250 1)(242 0 0 500 250 1)(243 0 0 500 250 1)(244 0 0 500 250 1)(245 0 0 500 250 1)(246 0 0 500 250 1)(247 0 0 500 250 1)(248 0 0 500 250 1)(249 0 0 500 250 1)(250 0 0 500 250 1)(251 0 0 500 250 1)(252 0 0 500 250 1)(253 0 0 500 250 1)(254 0 0 500 250 1)(255 0 0 500 250 1)(256 0 0 500 250 1)(257 0 0 500 250 1)(258 0 0 500 250 1)(259 0 0 500 250 1)(260 0 0 500 250 1)(261 0 0 500 250 1)(262 0 0 500 250 1)(263 0 0 500 250 1)(264 0 0 500 250 1)(265 0 0 500 250 1)(266 0 0 500 250 1)(267 0 0 500 250 1)(268 0 0 500 250 1)(269 0 0 500 250 1)(270 0 0 500 250 1)(271 0 0 500 250 1)(272 0 0 500 250 1)(273 0 0 500 250 1)(274 0 0 500 250 1)(275 0 0 500 250 1)(276 0 0 500 250 1)(277 0 0 500 250 1)(278 0 0 500 250 1)(279 0 0 500 250 1)(280 0 0 500 250 1)(281 0 0 500 250 1)(282 0 0 500 250 1)(283 0 0 500 250 1)(284 0 0 500 250 1)(285 0 0 500 250 1)(286 0 0 500 250 1)(287 0 0 500 250 1)(288 0 0 500 250 1)(289 0 0 500 250 1)(290 0 0 500 250 1)(291 0 0 500 250 1)(292 0 0 500 250 1)(293 0 0 500 250 1)(294 0 0 500 250 1)(295 0 0 500 250 1)(296 0 0 500 250 1)(297 0 0 500 250 1)(298 0 0 500 250 1)(299 0 0 500 250 1)(300 0 0 500 250 1)(301 0 0 500 250 1)(302 0 0 500 250 1)(303 0 0 500 250 1)(304 0 0 500 250 1)(305 0 0 500 250 1)(306 0 0 500 250 1)(307 0 0 500 250 1)(308 0 0 500 250 1)(309 0 0 500 250 1)(310 0 0 500 250 1)(311 0 0 500 250 1)(312 0 0 500 250 1)(313 0 0 500 250 1)(314 0 0 500 250 1)(315 0 0 500 250 1)(316 0 0 500 250 1)(317 0 0 500 250 1)(318 0 0 500 250 1)(319 0 0 500 250 1)(320 0 0 500 250 1)(321 0 0 500 250 1)(322 0 0 500 250 1)(323 0 0 500 250 1)(324 0 0 500 250 1)(325 0 0 500 250 1)(326 0 0 500 250 1)(327 0 0 500 250 1)(328 0 0 500 250 1)(329 0 0 500 250 1)(330 0 0 500 250 1)(331 0 0 500 250 1)(332 0 0 500 250 1)(333 0 0 500 250 1)(334 0 0 500 250 1)(335 0 0 500 250 1)(336 0 0 500 250 1)(337 0 0 500 250 1)(338 0 0 500 250 1)(339 0 0 500 250 1)(340 0 0 500 250 1)(341 0 0 500 250 1)(342 0 0 500 250 1)(343 0 0 500 250 1)(344 0 0 500 250 1)(345 0 0 500 250 1)(346 0 0 500 250 1)(347 0 0 500 250 1)(348 0 0 500 250 1)(349 0 0 500 250 1)(350 0 0 500 250 1)(351 0 0 500 250 1)(352 0 0 500 250 1)(353 0 0 500 250 1)(354 0 0 500 250 1)(355 0 0 500 250 1)(356 0 0 500 250 1)(357 0 0 500 250 1)(358 0 0 500 250 1)(359 0 0 500 250 1)(360 0 0 500 250 1)(361 0 0 500 250 1)(362 0 0 500 250 1)(363 0 0 500 250 1)(364 0 0 500 250 1)(365 0 0 500 250 1)(366 0 0 500 250 1)(367 0 0 500 250 1)(368 0 0 500 250 1)(369 0 0 500 250 1)(370 0 0 500 250 1)(371 0 0 500 250 1)(372 0 0 500 250 1)(373 0 0 500 250 1)(374 0 0 500 250 1)(375 0 0 500 250 1)(376 0 0 500 250 1)(377 0 0 500 250 1)(378 0 0 500 250 1)(379 0 0 500 250 1)(380 0 0 500 250 1)(381 0 0 500 250 1)(382 0 0 500 250 1)(383 0 0 500 250 1)',
                    '~snsChanMap': '(384,384,1)(AP0;0:0)(AP1;1:1)(AP2;2:2)(AP3;3:3)(AP4;4:4)(AP5;5:5)(AP6;6:6)(AP7;7:7)(AP8;8:8)(AP9;9:9)(AP10;10:10)(AP11;11:11)(AP12;12:12)(AP13;13:13)(AP14;14:14)(AP15;15:15)(AP16;16:16)(AP17;17:17)(AP18;18:18)(AP19;19:19)(AP20;20:20)(AP21;21:21)(AP22;22:22)(AP23;23:23)(AP24;24:24)(AP25;25:25)(AP26;26:26)(AP27;27:27)(AP28;28:28)(AP29;29:29)(AP30;30:30)(AP31;31:31)(AP32;32:32)(AP33;33:33)(AP34;34:34)(AP35;35:35)(AP36;36:36)(AP37;37:37)(AP38;38:38)(AP39;39:39)(AP40;40:40)(AP41;41:41)(AP42;42:42)(AP43;43:43)(AP44;44:44)(AP45;45:45)(AP46;46:46)(AP47;47:47)(AP48;48:48)(AP49;49:49)(AP50;50:50)(AP51;51:51)(AP52;52:52)(AP53;53:53)(AP54;54:54)(AP55;55:55)(AP56;56:56)(AP57;57:57)(AP58;58:58)(AP59;59:59)(AP60;60:60)(AP61;61:61)(AP62;62:62)(AP63;63:63)(AP64;64:64)(AP65;65:65)(AP66;66:66)(AP67;67:67)(AP68;68:68)(AP69;69:69)(AP70;70:70)(AP71;71:71)(AP72;72:72)(AP73;73:73)(AP74;74:74)(AP75;75:75)(AP76;76:76)(AP77;77:77)(AP78;78:78)(AP79;79:79)(AP80;80:80)(AP81;81:81)(AP82;82:82)(AP83;83:83)(AP84;84:84)(AP85;85:85)(AP86;86:86)(AP87;87:87)(AP88;88:88)(AP89;89:89)(AP90;90:90)(AP91;91:91)(AP92;92:92)(AP93;93:93)(AP94;94:94)(AP95;95:95)(AP96;96:96)(AP97;97:97)(AP98;98:98)(AP99;99:99)(AP100;100:100)(AP101;101:101)(AP102;102:102)(AP103;103:103)(AP104;104:104)(AP105;105:105)(AP106;106:106)(AP107;107:107)(AP108;108:108)(AP109;109:109)(AP110;110:110)(AP111;111:111)(AP112;112:112)(AP113;113:113)(AP114;114:114)(AP115;115:115)(AP116;116:116)(AP117;117:117)(AP118;118:118)(AP119;119:119)(AP120;120:120)(AP121;121:121)(AP122;122:122)(AP123;123:123)(AP124;124:124)(AP125;125:125)(AP126;126:126)(AP127;127:127)(AP128;128:128)(AP129;129:129)(AP130;130:130)(AP131;131:131)(AP132;132:132)(AP133;133:133)(AP134;134:134)(AP135;135:135)(AP136;136:136)(AP137;137:137)(AP138;138:138)(AP139;139:139)(AP140;140:140)(AP141;141:141)(AP142;142:142)(AP143;143:143)(AP144;144:144)(AP145;145:145)(AP146;146:146)(AP147;147:147)(AP148;148:148)(AP149;149:149)(AP150;150:150)(AP151;151:151)(AP152;152:152)(AP153;153:153)(AP154;154:154)(AP155;155:155)(AP156;156:156)(AP157;157:157)(AP158;158:158)(AP159;159:159)(AP160;160:160)(AP161;161:161)(AP162;162:162)(AP163;163:163)(AP164;164:164)(AP165;165:165)(AP166;166:166)(AP167;167:167)(AP168;168:168)(AP169;169:169)(AP170;170:170)(AP171;171:171)(AP172;172:172)(AP173;173:173)(AP174;174:174)(AP175;175:175)(AP176;176:176)(AP177;177:177)(AP178;178:178)(AP179;179:179)(AP180;180:180)(AP181;181:181)(AP182;182:182)(AP183;183:183)(AP184;184:184)(AP185;185:185)(AP186;186:186)(AP187;187:187)(AP188;188:188)(AP189;189:189)(AP190;190:190)(AP191;191:191)(AP192;192:192)(AP193;193:193)(AP194;194:194)(AP195;195:195)(AP196;196:196)(AP197;197:197)(AP198;198:198)(AP199;199:199)(AP200;200:200)(AP201;201:201)(AP202;202:202)(AP203;203:203)(AP204;204:204)(AP205;205:205)(AP206;206:206)(AP207;207:207)(AP208;208:208)(AP209;209:209)(AP210;210:210)(AP211;211:211)(AP212;212:212)(AP213;213:213)(AP214;214:214)(AP215;215:215)(AP216;216:216)(AP217;217:217)(AP218;218:218)(AP219;219:219)(AP220;220:220)(AP221;221:221)(AP222;222:222)(AP223;223:223)(AP224;224:224)(AP225;225:225)(AP226;226:226)(AP227;227:227)(AP228;228:228)(AP229;229:229)(AP230;230:230)(AP231;231:231)(AP232;232:232)(AP233;233:233)(AP234;234:234)(AP235;235:235)(AP236;236:236)(AP237;237:237)(AP238;238:238)(AP239;239:239)(AP240;240:240)(AP241;241:241)(AP242;242:242)(AP243;243:243)(AP244;244:244)(AP245;245:245)(AP246;246:246)(AP247;247:247)(AP248;248:248)(AP249;249:249)(AP250;250:250)(AP251;251:251)(AP252;252:252)(AP253;253:253)(AP254;254:254)(AP255;255:255)(AP256;256:256)(AP257;257:257)(AP258;258:258)(AP259;259:259)(AP260;260:260)(AP261;261:261)(AP262;262:262)(AP263;263:263)(AP264;264:264)(AP265;265:265)(AP266;266:266)(AP267;267:267)(AP268;268:268)(AP269;269:269)(AP270;270:270)(AP271;271:271)(AP272;272:272)(AP273;273:273)(AP274;274:274)(AP275;275:275)(AP276;276:276)(AP277;277:277)(AP278;278:278)(AP279;279:279)(AP280;280:280)(AP281;281:281)(AP282;282:282)(AP283;283:283)(AP284;284:284)(AP285;285:285)(AP286;286:286)(AP287;287:287)(AP288;288:288)(AP289;289:289)(AP290;290:290)(AP291;291:291)(AP292;292:292)(AP293;293:293)(AP294;294:294)(AP295;295:295)(AP296;296:296)(AP297;297:297)(AP298;298:298)(AP299;299:299)(AP300;300:300)(AP301;301:301)(AP302;302:302)(AP303;303:303)(AP304;304:304)(AP305;305:305)(AP306;306:306)(AP307;307:307)(AP308;308:308)(AP309;309:309)(AP310;310:310)(AP311;311:311)(AP312;312:312)(AP313;313:313)(AP314;314:314)(AP315;315:315)(AP316;316:316)(AP317;317:317)(AP318;318:318)(AP319;319:319)(AP320;320:320)(AP321;321:321)(AP322;322:322)(AP323;323:323)(AP324;324:324)(AP325;325:325)(AP326;326:326)(AP327;327:327)(AP328;328:328)(AP329;329:329)(AP330;330:330)(AP331;331:331)(AP332;332:332)(AP333;333:333)(AP334;334:334)(AP335;335:335)(AP336;336:336)(AP337;337:337)(AP338;338:338)(AP339;339:339)(AP340;340:340)(AP341;341:341)(AP342;342:342)(AP343;343:343)(AP344;344:344)(AP345;345:345)(AP346;346:346)(AP347;347:347)(AP348;348:348)(AP349;349:349)(AP350;350:350)(AP351;351:351)(AP352;352:352)(AP353;353:353)(AP354;354:354)(AP355;355:355)(AP356;356:356)(AP357;357:357)(AP358;358:358)(AP359;359:359)(AP360;360:360)(AP361;361:361)(AP362;362:362)(AP363;363:363)(AP364;364:364)(AP365;365:365)(AP366;366:366)(AP367;367:367)(AP368;368:368)(AP369;369:369)(AP370;370:370)(AP371;371:371)(AP372;372:372)(AP373;373:373)(AP374;374:374)(AP375;375:375)(AP376;376:376)(AP377;377:377)(AP378;378:378)(AP379;379:379)(AP380;380:380)(AP381;381:381)(AP382;382:382)(AP383;383:383)(SY0;768:768)',
                    '~snsShankMap': '(1,2,480)(0:0:0:1)(0:1:0:1)(0:0:1:1)(0:1:1:1)(0:0:2:1)(0:1:2:1)(0:0:3:1)(0:1:3:1)(0:0:4:1)(0:1:4:1)(0:0:5:1)(0:1:5:1)(0:0:6:1)(0:1:6:1)(0:0:7:1)(0:1:7:1)(0:0:8:1)(0:1:8:1)(0:0:9:1)(0:1:9:1)(0:0:10:1)(0:1:10:1)(0:0:11:1)(0:1:11:1)(0:0:12:1)(0:1:12:1)(0:0:13:1)(0:1:13:1)(0:0:14:1)(0:1:14:1)(0:0:15:1)(0:1:15:1)(0:0:16:1)(0:1:16:1)(0:0:17:1)(0:1:17:1)(0:0:18:1)(0:1:18:1)(0:0:19:1)(0:1:19:1)(0:0:20:1)(0:1:20:1)(0:0:21:1)(0:1:21:1)(0:0:22:1)(0:1:22:1)(0:0:23:1)(0:1:23:1)(0:0:24:1)(0:1:24:1)(0:0:25:1)(0:1:25:1)(0:0:26:1)(0:1:26:1)(0:0:27:1)(0:1:27:1)(0:0:28:1)(0:1:28:1)(0:0:29:1)(0:1:29:1)(0:0:30:1)(0:1:30:1)(0:0:31:1)(0:1:31:1)(0:0:32:1)(0:1:32:1)(0:0:33:1)(0:1:33:1)(0:0:34:1)(0:1:34:1)(0:0:35:1)(0:1:35:1)(0:0:36:1)(0:1:36:1)(0:0:37:1)(0:1:37:1)(0:0:38:1)(0:1:38:1)(0:0:39:1)(0:1:39:1)(0:0:40:1)(0:1:40:1)(0:0:41:1)(0:1:41:1)(0:0:42:1)(0:1:42:1)(0:0:43:1)(0:1:43:1)(0:0:44:1)(0:1:44:1)(0:0:45:1)(0:1:45:1)(0:0:46:1)(0:1:46:1)(0:0:47:1)(0:1:47:1)(0:0:48:1)(0:1:48:1)(0:0:49:1)(0:1:49:1)(0:0:50:1)(0:1:50:1)(0:0:51:1)(0:1:51:1)(0:0:52:1)(0:1:52:1)(0:0:53:1)(0:1:53:1)(0:0:54:1)(0:1:54:1)(0:0:55:1)(0:1:55:1)(0:0:56:1)(0:1:56:1)(0:0:57:1)(0:1:57:1)(0:0:58:1)(0:1:58:1)(0:0:59:1)(0:1:59:1)(0:0:60:1)(0:1:60:1)(0:0:61:1)(0:1:61:1)(0:0:62:1)(0:1:62:1)(0:0:63:1)(0:1:63:1)(0:0:64:1)(0:1:64:1)(0:0:65:1)(0:1:65:1)(0:0:66:1)(0:1:66:1)(0:0:67:1)(0:1:67:1)(0:0:68:1)(0:1:68:1)(0:0:69:1)(0:1:69:1)(0:0:70:1)(0:1:70:1)(0:0:71:1)(0:1:71:1)(0:0:72:1)(0:1:72:1)(0:0:73:1)(0:1:73:1)(0:0:74:1)(0:1:74:1)(0:0:75:1)(0:1:75:1)(0:0:76:1)(0:1:76:1)(0:0:77:1)(0:1:77:1)(0:0:78:1)(0:1:78:1)(0:0:79:1)(0:1:79:1)(0:0:80:1)(0:1:80:1)(0:0:81:1)(0:1:81:1)(0:0:82:1)(0:1:82:1)(0:0:83:1)(0:1:83:1)(0:0:84:1)(0:1:84:1)(0:0:85:1)(0:1:85:1)(0:0:86:1)(0:1:86:1)(0:0:87:1)(0:1:87:1)(0:0:88:1)(0:1:88:1)(0:0:89:1)(0:1:89:1)(0:0:90:1)(0:1:90:1)(0:0:91:1)(0:1:91:1)(0:0:92:1)(0:1:92:1)(0:0:93:1)(0:1:93:1)(0:0:94:1)(0:1:94:1)(0:0:95:1)(0:1:95:0)(0:0:96:1)(0:1:96:1)(0:0:97:1)(0:1:97:1)(0:0:98:1)(0:1:98:1)(0:0:99:1)(0:1:99:1)(0:0:100:1)(0:1:100:1)(0:0:101:1)(0:1:101:1)(0:0:102:1)(0:1:102:1)(0:0:103:1)(0:1:103:1)(0:0:104:1)(0:1:104:1)(0:0:105:1)(0:1:105:1)(0:0:106:1)(0:1:106:1)(0:0:107:1)(0:1:107:1)(0:0:108:1)(0:1:108:1)(0:0:109:1)(0:1:109:1)(0:0:110:1)(0:1:110:1)(0:0:111:1)(0:1:111:1)(0:0:112:1)(0:1:112:1)(0:0:113:1)(0:1:113:1)(0:0:114:1)(0:1:114:1)(0:0:115:1)(0:1:115:1)(0:0:116:1)(0:1:116:1)(0:0:117:1)(0:1:117:1)(0:0:118:1)(0:1:118:1)(0:0:119:1)(0:1:119:1)(0:0:120:1)(0:1:120:1)(0:0:121:1)(0:1:121:1)(0:0:122:1)(0:1:122:1)(0:0:123:1)(0:1:123:1)(0:0:124:1)(0:1:124:1)(0:0:125:1)(0:1:125:1)(0:0:126:1)(0:1:126:1)(0:0:127:1)(0:1:127:1)(0:0:128:1)(0:1:128:1)(0:0:129:1)(0:1:129:1)(0:0:130:1)(0:1:130:1)(0:0:131:1)(0:1:131:1)(0:0:132:1)(0:1:132:1)(0:0:133:1)(0:1:133:1)(0:0:134:1)(0:1:134:1)(0:0:135:1)(0:1:135:1)(0:0:136:1)(0:1:136:1)(0:0:137:1)(0:1:137:1)(0:0:138:1)(0:1:138:1)(0:0:139:1)(0:1:139:1)(0:0:140:1)(0:1:140:1)(0:0:141:1)(0:1:141:1)(0:0:142:1)(0:1:142:1)(0:0:143:1)(0:1:143:1)(0:0:144:1)(0:1:144:1)(0:0:145:1)(0:1:145:1)(0:0:146:1)(0:1:146:1)(0:0:147:1)(0:1:147:1)(0:0:148:1)(0:1:148:1)(0:0:149:1)(0:1:149:1)(0:0:150:1)(0:1:150:1)(0:0:151:1)(0:1:151:1)(0:0:152:1)(0:1:152:1)(0:0:153:1)(0:1:153:1)(0:0:154:1)(0:1:154:1)(0:0:155:1)(0:1:155:1)(0:0:156:1)(0:1:156:1)(0:0:157:1)(0:1:157:1)(0:0:158:1)(0:1:158:1)(0:0:159:1)(0:1:159:1)(0:0:160:1)(0:1:160:1)(0:0:161:1)(0:1:161:1)(0:0:162:1)(0:1:162:1)(0:0:163:1)(0:1:163:1)(0:0:164:1)(0:1:164:1)(0:0:165:1)(0:1:165:1)(0:0:166:1)(0:1:166:1)(0:0:167:1)(0:1:167:1)(0:0:168:1)(0:1:168:1)(0:0:169:1)(0:1:169:1)(0:0:170:1)(0:1:170:1)(0:0:171:1)(0:1:171:1)(0:0:172:1)(0:1:172:1)(0:0:173:1)(0:1:173:1)(0:0:174:1)(0:1:174:1)(0:0:175:1)(0:1:175:1)(0:0:176:1)(0:1:176:1)(0:0:177:1)(0:1:177:1)(0:0:178:1)(0:1:178:1)(0:0:179:1)(0:1:179:1)(0:0:180:1)(0:1:180:1)(0:0:181:1)(0:1:181:1)(0:0:182:1)(0:1:182:1)(0:0:183:1)(0:1:183:1)(0:0:184:1)(0:1:184:1)(0:0:185:1)(0:1:185:1)(0:0:186:1)(0:1:186:1)(0:0:187:1)(0:1:187:1)(0:0:188:1)(0:1:188:1)(0:0:189:1)(0:1:189:1)(0:0:190:1)(0:1:190:1)(0:0:191:1)(0:1:191:1)',
        }
        with open(metaname, 'w') as f:
            for k in meta_dict.keys():
                f.write(f'{k}={meta_dict[k]}\n')
    
        np.savez(os.path.splitext(filename)[0] + '_params.npz', 
                    st=st_sim, cl=cl_sim, ci=ci_sim, 
                    wfs=wf_sim, cb=cb_sim+8)
    
        print('hybrid ground truth completed')
    
        return data, params
    
    

    back to top

    Software Heritage — Copyright (C) 2015–2025, The Software Heritage developers. License: GNU AGPLv3+.
    The source code of Software Heritage itself is available on our development forge.
    The source code files archived by Software Heritage are available under their own copyright and licenses.
    Terms of use: Archive access, API— Content policy— Contact— JavaScript license information— Web API