Browse Source

Clean up and harmonize group names

Michael Denker 11 months ago
parent
commit
0bfe80ed31
1 changed files with 57 additions and 23 deletions
  1. 57 23
      code/convert_to_nix.py

+ 57 - 23
code/convert_to_nix.py

@@ -1,8 +1,16 @@
 """
-This script loads a complete session in the blackrock format and converts it to a single nix file
+This script loads a complete session in the blackrock format and converts it to
+a single nix file.
+
+For sessions without online recorded LFP (i.e., 1000Hz neural
+signal in the ns2 files), this LFP is generated offline by filtering and
+downsampling.
+
+Two versions of the nix files are saved: one containing the complete data, and
+a smaller one containing everything except the AnalogSignal object with the
+raw data records.
 """
 import os
-import copy
 
 import numpy as np
 import quantities as pq
@@ -15,10 +23,11 @@ from elephant.signal_processing import butter
 
 from reachgraspio import reachgraspio
 
+##### INITIAL SETUP ############
 
 # Choose which session you want to convert into a nix file
-#session = "i140703-001"
-session = "l101210-001"
+session = "i140703-001"
+#session = "l101210-001"
 
 # Input data. i.e., original Blackrock files and odML
 dataset_dir = '../datasets_blackrock'
@@ -38,22 +47,20 @@ session = reachgraspio.ReachGraspIO(
 
 block = session.read_block(lazy=False, load_waveforms=True)
 
-# =============================================================================
-# Create offline filtered LFP
-#
+
+##### CREATE LFP IF MISSING ############
 # Here, we construct one offline filtered LFP from each ns5 (monkey L) or ns6
 # (monkey N) raw recording trace. For monkey N, this filtered LFP can be
 # compared to the LFPs in the ns2 file (note that monkey L contains only
 # behavioral signals in the ns2 file). Also, we assign telling names to each
 # Neo AnalogSignal, which is used for plotting later on in this script.
-# =============================================================================
 
 # this factor was heuristically determined as an approximate shift introduced
 # by the online filtering. Here, the offline filtering does not introduce a
-# noticable shift, so we set it to zero.
+# noticeable shift, so we set it to zero.
 time_shift_factor = 0*pq.ms
 
-# identify neuronal signals and provide labels for plotting
+# Identify neuronal signals and provide labels for plotting
 filtered_anasig = None
 raw_anasig = None
 for anasig in block.segments[0].analogsignals:
@@ -61,7 +68,7 @@ for anasig in block.segments[0].analogsignals:
     if not anasig.annotations['neural_signal']:
         continue
 
-    # identify nsx source of signals in this AnalogSignal object
+    # Identify nsx source of signals in this AnalogSignal object
     if 'nsx' in anasig.annotations:
         nsx = anasig.annotations['nsx']
     else:
@@ -76,11 +83,12 @@ for anasig in block.segments[0].analogsignals:
         # AnalogSignal is raw signal from ns5 or ns6
         raw_anasig = anasig
 
+# Does the file contain a filtered signal, i.e., an LFP?
 if filtered_anasig is None:
     print("Filtering raw time series to obtain LFP")
+
+    # Filtering must be done channel by channel for memory reasons (requires approx. 32 GB RAM)
     for f in range(raw_anasig.shape[1]):
-        # filtering must be done channel by channel for memory reasons (requires approx. 32 GB RAM)
-        print(f"Processing channel {f}")
 
         filtered_signal = butter(
             raw_anasig[:, f],
@@ -89,13 +97,13 @@ if filtered_anasig is None:
             filter_function='sosfiltfilt',
             order=4)
 
-        # For other filters that may introduce a time shift, here would be the
-        # place to correct for this shift using:
-        # ... .time_shift(time_shift_factor)
         # Downsampling 30-fold here to get to 1000Hz from 30kHz
+        # Note: For filters that may introduce a time shift, here would be the
+        # place to correct for this shift using:
+        # [...] .time_shift(time_shift_factor)
         downsampled_signal=filtered_signal.downsample(30)
 
-        # first run? Create a new Analogsignal
+        # First run? Create a new Analogsignal
         if f == 0:
             offline_filtered_anasig = neo.AnalogSignal(
                 np.zeros((downsampled_signal.shape[0], raw_anasig.shape[1]), dtype=np.float32) *\
@@ -114,8 +122,6 @@ if filtered_anasig is None:
 
             # all array annotations of the raw signal also apply to the filtered
             # signal
-            # offline_filtered_anasig.array_annotations = copy.copy(
-            #     raw_anasig.array_annotations)
             offline_filtered_anasig.array_annotate(
                 **raw_anasig.array_annotations)
 
@@ -127,21 +133,34 @@ if filtered_anasig is None:
         nsx = anasig.array_annotations["nsx"][0]
 
     offline_filtered_anasig.name = f"NeuralTimeSeriesDownsampled"
-    offline_filtered_anasig.description = "Downsampled continuous neuronal recordings, where the downsampling was " \
-                           "performed off-line during post-processing"
+    offline_filtered_anasig.description = "Downsampled continuous neuronal " \
+            "recordings, where the downsampling was " \
+            "performed off-line during post-processing"
+
+    offline_filtered_group = neo.Group(
+        name="offline",
+        stream_id=''
+    )
+    offline_filtered_group.analogsignals.append(offline_filtered_anasig)
 
-    # Attach all offline filtered LFPs to the segment of data
+    # Attach all offline filtered LFPs to the segment of data and the groups
     block.segments[0].analogsignals.insert(0, offline_filtered_anasig)
+    block.groups.insert(0, offline_filtered_group)
 
 
 ##### MISC FIXES ###################
 
-# for lilou, behavior channels were not set with nice names in the setup
+# For lilou, behavior channels were not set with nice names in the setup.
 # Here, we hard code these handwritten annotations to make both recordings
 # more similar
 block.segments[0].analogsignals[1].array_annotate(
     channel_names=['GFpr1', 'GFside1', 'GFpr2', 'GFside2', 'LF', 'Displ'])
 
+# Unify group names of ns2 for Nikos session
+if session == "i140703-001":
+    block.groups[0].name = 'nsx2'
+    block.groups[1].name = 'nsx2'
+
 
 ##### SAVE NIX FILE ###################
 nix_filename = nix_session_path + '.nix'
@@ -152,6 +171,7 @@ else:
         print(f'Saving nix file at {nix_filename}')
         io.write_block(block)
 
+
 ##### VALIDATION OF FILE CONTENT ######
 with neo.NixIO(nix_filename, mode='ro') as io:
     blocks = io.read_all_blocks()
@@ -198,3 +218,17 @@ with neo.NixIO(nix_filename, mode='ro') as io:
             assert_same_annotations(ep_old, ep_new)
             assert_same_array_annotations(ep_old, ep_new)
         print(f'Epochs are equivalent.')
+
+
+##### SAVE SMALL NIX FILE ###################
+# remove raw signal
+del block.segments[0].analogsignals[2]
+del block.groups[2]
+
+nix_filename = nix_session_path + '_no_raw.nix'
+if os.path.exists(nix_filename):
+    print('Nix file already exists and will not be overwritten.')
+else:
+    with neo.NixIO(nix_filename) as io:
+        print(f'Saving nix file at {nix_filename}')
+        io.write_block(block)