Skip to content

Commit

Permalink
Merge pull request #179 from AllenInstitute/feature/nwbv2
Browse files Browse the repository at this point in the history
Feature support for NWBv2
  • Loading branch information
t-b authored Jun 5, 2020
2 parents b9bf714 + 33b63e3 commit 1424f9f
Show file tree
Hide file tree
Showing 28 changed files with 1,664 additions and 180 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
# Documentation files
Packages/doc/html
Packages/doc/IPNWB
Packages/doc/doctrees
Packages/doc/sphinx-output.log

Packages/MIES/backup
*.orig
*.rej
Expand Down
361 changes: 361 additions & 0 deletions Packages/Conversion/MIES_MassExperimentProcessing.ipf
Original file line number Diff line number Diff line change
@@ -0,0 +1,361 @@
#pragma rtGlobals=3 // Use modern global access method.

/// @file MIES_MassExperimentProcessing.ipf
/// @brief __MEP__ Process multiple MIES pxps to convert data into NWBv2
///
/// Installation:
/// - Stop Igor Pro
/// - Create a shortcut to this file and place it in the `Igor Procedures` folder
/// - Ensure that only MIES is installed and no other Igor Pro packages
/// - In the MIES installation folder (All Users: `C:\Program Files\MIES`, User: `C:\Users\$User\Documents\MIES`)
/// create an empty file named `UserConfig.txt`.
///
/// Running:
/// - Start Igor Pro
/// - Select "Macros" -> "Mass convert PXPs to NWBv2"
/// - Enter an input and output folder for the conversion
/// - Wait until it's done
///
/// In the output folder there will be a `conversion.json` file with results of
/// the conversion process. Search for the `error` key for failed conversions.

// #define MEP_DEBUGGING

#ifdef MEP_DEBUGGING

static StrConstant INPUT_FOLDER = "C:tim-data:pxp_examples_for_nwb_2:"
static StrConstant OUTPUT_FOLDER = "C:tim-data:output:"

#else

static StrConstant INPUT_FOLDER = ""
static StrConstant OUTPUT_FOLDER = ""

#endif

Menu "Macros"
"Mass convert PXPs to NWBv2", /Q, StartMultiExperimentProcess()
End

// NOTE: If you use these procedures for your own purposes, change the package name
// to a distinctive name so that you don't clash with other people's preferences.
static StrConstant kPackageName = "MIES PXP to NWBv2"
static StrConstant kPreferencesFileName = "ProcessPrefsMIESNWBv2.bin"
static Constant kPrefsRecordID = 0 // The recordID is a unique number identifying a record within the preference file.
// In this example we store only one record in the preference file.

// The structure stored in preferences to keep track of what experiment to load next.
// If you add, remove or change fields you must delete your old prefs file. See the help
// topic "Saving Package Preferences" for details.
static Structure MultiExperimentProcessPrefs
uint32 version // Prefs version
uint32 processRunning // Truth that we are running the mult-experiment process
char settingsFile[256]
EndStructure

// In version 101 of the prefs structure we increased folderPath from 100 to 256 bytes
static Constant kPrefsVersionNumber = 102

// Loads preferences into our structure.
static Function LoadPackagePrefs(prefs)
STRUCT MultiExperimentProcessPrefs &prefs

Variable currentPrefsVersion = kPrefsVersionNumber

// This loads preferences from disk if they exist on disk.
LoadPackagePreferences /MIS=1 kPackageName, kPreferencesFileName, kPrefsRecordID, prefs
// Printf "%d byte loaded\r", V_bytesRead

// If error or prefs not found or not valid, initialize them.
if (V_flag!=0 || V_bytesRead==0 || prefs.version!=currentPrefsVersion)
prefs.version = currentPrefsVersion

prefs.processRunning = 0
prefs.settingsFile = ""

SavePackagePrefs(prefs) // Create default prefs file.
endif
End

// Saves our structure to preferences.
static Function SavePackagePrefs(prefs)
STRUCT MultiExperimentProcessPrefs &prefs

SavePackagePreferences kPackageName, kPreferencesFileName, kPrefsRecordID, prefs
End

// This is the routine that you would need to change to use this procedure file for your own purposes.
// See comments about labeled "TO USE FOR YOUR OWN PURPOSES".
static Function ProcessCurrentExperiment(prefs)
STRUCT MultiExperimentProcessPrefs &prefs

variable jsonID, index
string outputFilePath, inputFile, outputFolder

jsonID = GetJSON(prefs)

if(IsAppropriateExperiment())

outputFolder = JSON_GetString(jsonID, "/outputFolder")

PathInfo home
inputFile = S_path + GetExperimentName() + ".pxp"

outputFilePath = outputFolder + S_path + GetExperimentName() + ".nwb"

index = JSON_GetVariable(jsonID, "/index")
JSON_AddString(jsonID, "/log/" + num2str(index) + "/from", inputFile)
JSON_AddString(jsonID, "/log/" + num2str(index) + "/to", outputFilePath)

DoWindow/K HistoryCarbonCopy
NewNotebook/V=0/F=0 /N=HistoryCarbonCopy

try
PerformMiesTasks(outputFilePath); AbortOnRTE
catch
print "Caught an RTE"
JSON_AddBoolean(jsonID, "/log/" + num2str(index) + "/error", 1)
JSON_SetVariable(jsonID, "/errors", JSON_GetVariable(jsonID, "/errors") + 1)
HDF5CloseFile/A/Z 0
DeleteFile/Z outputFilePath
endtry

Notebook HistoryCarbonCopy getData=1
JSON_AddString(jsonID, "/log/" + num2str(index) + "/output", trimstring(S_Value))

JSON_SetVariable(jsonID, "/processed", JSON_GetVariable(jsonID, "/processed") + 1)
else
JSON_SetVariable(jsonID, "/skipped", JSON_GetVariable(jsonID, "/skipped") + 1)
endif

JSON_SetVariable(jsonID, "/index", JSON_GetVariable(jsonID, "/index") + 1)

StoreJSON(prefs, jsonID)
End

static Function PerformMiesTasks(outputFilePath)
string outputFilePath

string folder, message
variable nwbVersion, error

printf "Free Memory: %g GB\r", GetFreeMemory()

if(FileExists(outputFilePath))
print "Output file already exists, skipping!"
return 0
endif

folder = GetFolder(outputFilePath)

if(!FolderExists(folder))
CreateFolderOnDisk(folder)
endif

ClearRTError()

nwbVersion = 2
NWB_ExportAllData(nwbVersion, overrideFilePath=outputFilePath)
HDF5CloseFile/A/Z 0

message = GetRTErrMessage()
error = GetRTError(1)
ASSERT(error == 0, "Encountered lingering RTE of " + num2str(error) + "(message: " + message + ") after executing NWB_ExportAllData.")
End

static Function IsAppropriateExperiment()

return ItemsInList(GetAllDevicesWithContent()) > 0
End

// Returns full path to the next experiment file to be loaded or "" if we are finished.
static Function/S FindNextExperiment(prefs)
STRUCT MultiExperimentProcessPrefs &prefs

variable jsonID, index

jsonID = GetJSON(prefs)

WAVE/T inputFiles = JSON_GetTextWave(jsonID, "inputFiles")
index = JSON_GetVariable(jsonID, "/index")
JSON_Release(jsonID)

if(!(index >= DimSize(inputFiles, ROWS)))
return inputFiles[index]
endif

return ""
End

// Caller needs to release json
static Function GetJSON(prefs)
STRUCT MultiExperimentProcessPrefs &prefs

string data, fname

[data, fname] = LoadTextFile(prefs.settingsFile)

return JSON_Parse(data)
End

// json will be released
static Function StoreJSON(prefs, jsonID)
STRUCT MultiExperimentProcessPrefs &prefs
variable jsonID

string data = JSON_Dump(jsonID, indent=2)

SaveTextFile(data, prefs.settingsFile)

ASSERT(!JSON_Release(jsonID), "Could not release json")
End

// Posts commands to Igor's operation queue to close the current experiment and open the next one.
// Igor executes operation queue commands when it is idling - that is, when it is not running a
// function or operation.
static Function PostLoadNextExperiment(nextExperimentFullPath)
String nextExperimentFullPath

ASSERT(FileExists(nextExperimentFullPath), "Experiment must exist")

Execute/P/Q "NEWEXPERIMENT " // Post command to close this experiment.

Execute/P/Q "SetIgorOption poundDefine=MIES_PXP_NWB_CONVERSION_SKIP_SAVING"

// Post command to open next experiment.
String cmd
sprintf cmd "Execute/P/Q \"LOADFILE %s\"", nextExperimentFullPath
Execute/Q cmd
End

// This is the hook function that Igor calls whenever a file is opened. We use it to
// detect the opening of an experiment and to call our ProcessCurrentExperiment function.
static Function AfterFileOpenHook(refNum,file,pathName,type,creator,kind)
Variable refNum,kind
String file,pathName,type,creator

STRUCT MultiExperimentProcessPrefs prefs

LoadPackagePrefs(prefs) // Load our prefs into our structure
if (prefs.processRunning == 0)
return 0 // Process not yet started.
endif

// Check file type
if (CmpStr(type,"IGsU") != 0)
return 0 // This is not a packed experiment file
endif

ProcessCurrentExperiment(prefs)

// See if there are more experiments to process.
String nextExperimentFullPath = FindNextExperiment(prefs)
if (strlen(nextExperimentFullPath) == 0)
// Process is finished
prefs.processRunning = 0 // Flag process is finished.
Execute/P "NEWEXPERIMENT " // Post command to close this experiment.
print "Multi-experiment process is finished."
else
// Load the next experiment in the designated folder, if any.
PostLoadNextExperiment(nextExperimentFullPath) // Post operation queue commands to load next experiment
endif

SavePackagePrefs(prefs)

return 0 // Tell Igor to handle file in default fashion.
End

// This function enables our special Igor hooks which skip saving the experiment
Function StartMultiExperimentProcess()

Execute/P/Q "SetIgorOption poundDefine=MIES_PXP_NWB_CONVERSION_SKIP_SAVING"
Execute/P/Q "COMPILEPROCEDURES "
Execute/P/Q "StartMultiExperimentProcessWrapper()"
End

// Allow user to choose the folder containing the experiment files and start the process.
Function StartMultiExperimentProcessWrapper()

string message, settingsFile, inputFolder, outputFolder, files
variable jsonID

STRUCT MultiExperimentProcessPrefs prefs
LoadPackagePrefs(prefs)

message = "Choose input folder with MIES pxps"
if(!cmpstr(INPUT_FOLDER, ""))
NewPath/O/Q/M=message MultiExperimentInputFolder
else
NewPath/O/Q/M=message MultiExperimentInputFolder, INPUT_FOLDER
endif

if (V_flag != 0)
return -1 // User canceled from New Path dialog
endif

PathInfo MultiExperimentInputFolder
inputFolder = S_Path
ASSERT(V_flag, "Invalid path")

message = "Choose output folder for NWBv2 files"
if(!cmpstr(OUTPUT_FOLDER, ""))
NewPath/O/Q/M=message MultiExperimentOutputFolder
else
NewPath/O/Q/M=message MultiExperimentOutputFolder, OUTPUT_FOLDER
endif

if (V_flag != 0)
return -1 // User canceled from New Path dialog
endif

PathInfo MultiExperimentOutputFolder
outputFolder = S_Path
ASSERT(V_flag, "Invalid path")

files = GetAllFilesRecursivelyFromPath("MultiExperimentInputFolder", extension=".pxp")

// 16: Case-insensitive alphanumeric sort that sorts wave0 and wave9 before wave10.
// ...
// 64: Ignore + and - in the alphanumeric sort so that "Text-09" sorts before "Text-10". Set options to 80 or 81.
files = SortList(files, "|", 80)

WAVE/T/Z inputPXPs = ListToTextWave(files, "|")

jsonID = JSON_New()
JSON_AddWave(jsonID, "/inputFiles", inputPXPs)
JSON_AddString(jsonID, "/inputFolder", inputFolder)
JSON_AddString(jsonID, "/outputFolder", outputFolder)
JSON_AddVariable(jsonID, "/index", 0)
JSON_AddVariable(jsonID, "/processed", 0)
JSON_AddVariable(jsonID, "/errors", 0)
JSON_AddVariable(jsonID, "/skipped", 0)
JSON_AddVariable(jsonID, "/total", DimSize(inputPXPs, ROWS))

JSON_AddTreeArray(jsonID, "/log")
JSON_AddObjects(jsonID, "/log", objCount = DimSize(inputPXPs, ROWS))

prefs.settingsFile = outputFolder + "conversion.json"
StoreJSON(prefs, jsonID)

prefs.processRunning = 1 // Flag process is started.

// Start the process off by loading the first experiment.
String nextExperimentFullPath = FindNextExperiment(prefs)
PostLoadNextExperiment(nextExperimentFullPath) // Start the process off

SavePackagePrefs(prefs)

return 0
End

#ifdef MEP_DEBUGGING

Function TestMe()

STRUCT MultiExperimentProcessPrefs prefs

LoadPackagePrefs(prefs)
ProcessCurrentExperiment(prefs)
End

#endif
2 changes: 1 addition & 1 deletion Packages/IPNWB
Submodule IPNWB updated 61 files
+2 −0 .gitattributes
+1 −0 .gitignore
+6 −0 .gitmodules
+98 −1 IPNWB_Constants.ipf
+22 −0 IPNWB_Debugging.ipf
+138 −73 IPNWB_HDF5Helpers.ipf
+197 −145 IPNWB_Reader.ipf
+79 −13 IPNWB_Structures.ipf
+702 −63 IPNWB_Utils.ipf
+401 −113 IPNWB_Writer.ipf
+20 −0 Makefile
+0 −334 Readme.rst
+52 −0 conf.py
+14 −0 doc/nwb1.rst
+37 −0 doc/nwb2.rst
+98 −0 doc/schema.diff
+113 −0 doc/specifications_core_1_nwb.yaml
+ doc/sweep_table.png
+77 −0 examples/IPNWB_Examples_Reader.ipf
+79 −0 examples/IPNWB_Examples_Writer.ipf
+91 −0 index.rst
+35 −0 make.bat
+1 −0 namespace/core/json/nwb.base.json
+1 −0 namespace/core/json/nwb.behavior.json
+1 −0 namespace/core/json/nwb.device.json
+1 −0 namespace/core/json/nwb.ecephys.json
+1 −0 namespace/core/json/nwb.epoch.json
+1 −0 namespace/core/json/nwb.file.json
+1 −0 namespace/core/json/nwb.icephys.json
+1 −0 namespace/core/json/nwb.image.json
+1 −0 namespace/core/json/nwb.misc.json
+1 −0 namespace/core/json/nwb.namespace.json
+1 −0 namespace/core/json/nwb.ogen.json
+1 −0 namespace/core/json/nwb.ophys.json
+1 −0 namespace/core/json/nwb.retinotopy.json
+206 −0 namespace/core/yaml/nwb.base.yaml
+116 −0 namespace/core/yaml/nwb.behavior.yaml
+14 −0 namespace/core/yaml/nwb.device.yaml
+324 −0 namespace/core/yaml/nwb.ecephys.yaml
+44 −0 namespace/core/yaml/nwb.epoch.yaml
+439 −0 namespace/core/yaml/nwb.file.yaml
+247 −0 namespace/core/yaml/nwb.icephys.yaml
+192 −0 namespace/core/yaml/nwb.image.yaml
+279 −0 namespace/core/yaml/nwb.misc.yaml
+60 −0 namespace/core/yaml/nwb.namespace.yaml
+43 −0 namespace/core/yaml/nwb.ogen.yaml
+326 −0 namespace/core/yaml/nwb.ophys.yaml
+234 −0 namespace/core/yaml/nwb.retinotopy.yaml
+1 −0 namespace/hdmf-common/json/namespace.json
+1 −0 namespace/hdmf-common/json/sparse.json
+1 −0 namespace/hdmf-common/json/table.json
+21 −0 namespace/hdmf-common/yaml/namespace.yaml
+24 −0 namespace/hdmf-common/yaml/sparse.yaml
+155 −0 namespace/hdmf-common/yaml/table.yaml
+1 −0 namespace/ndx-mies/json/namespace.json
+1 −0 namespace/ndx-mies/json/ndx-mies.extensions.json
+12 −0 namespace/ndx-mies/yaml/namespace.yaml
+208 −0 namespace/ndx-mies/yaml/ndx-mies.extensions.yaml
+1 −0 ndx-MIES
+1 −0 specifications
+84 −0 update_specifications.sh
Loading

0 comments on commit 1424f9f

Please sign in to comment.