Repository: hed-standard/hed-python (language: python)

Issue:
Updated the bids file organization to use tabular files



Discussion / hints:

Fix patch:
diff --git a/docs/source/api.rst b/docs/source/api.rst
index d1a038c11..3f013a1f8 100644
--- a/docs/source/api.rst
+++ b/docs/source/api.rst
@@ -13,19 +13,19 @@ HED models
    hed.models.BaseInput
    hed.models.ColumnMapper
    hed.models.ColumnMetadata
-   hed.models.DefDict
-   hed.models.DefEntry
-   hed.models.DefinitionMapper
-   hed.models.EventsInput
+   hed.models.DefinitionDict
+   hed.models.DefinitionEntry
+   hed.models.DefMapper
    hed.models.HedGroup
    hed.models.HedGroupBase
    hed.models.HedGroupFrozen
-   hed.models.HedInput
+   hed.models.SpreadsheetInput
    hed.models.HedOps
    hed.models.HedString
    hed.models.HedTag
    hed.models.OnsetMapper
    hed.models.Sidecar
+   hed.models.TabularInput
    hed.models.hed_ops
 
 .. currentmodule:: hed
@@ -61,22 +61,22 @@ HED tools
 
    hed.tools.BidsDataset
    hed.tools.BidsDatasetSummary
-   hed.tools.BidsEventFile
-   hed.tools.BidsEventFiles
    hed.tools.BidsFile
    hed.tools.BidsFileDictionary
-   hed.tools.BidsJsonFile
+   hed.tools.BidsFileGroup
    hed.tools.BidsSidecarFile
-   hed.tools.BidsTsvDictionary
-   hed.tools.BidsTsvFile
-   hed.tools.BidsTsvSummary
+   hed.tools.BidsTabularDictionary
+   hed.tools.BidsTabularFile
+   hed.tools.BidsTabularSummary
 
    hed.tools.FileDictionary
    hed.tools.HedLogger
    hed.tools.KeyMap
    hed.tools.KeyTemplate
    hed.tools.TagSummary
-   hed.tools.annotation.annotation_util
+   hed.tools.analysis.analysis_util
+   hed.tools.analysis.annotation_util
+   hed.tools.analysis.summary_util
 
 
 .. currentmodule:: hed
diff --git a/docs/source/generated/hed.errors.rst b/docs/source/generated/hed.errors.rst
new file mode 100644
index 000000000..4169973f3
--- /dev/null
+++ b/docs/source/generated/hed.errors.rst
@@ -0,0 +1,53 @@
+hed.errors package
+==================
+
+Submodules
+----------
+
+hed.errors.error\_handlers module
+---------------------------------
+
+.. automodule:: hed.errors.error_handlers
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+hed.errors.error\_messages module
+---------------------------------
+
+.. automodule:: hed.errors.error_messages
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+hed.errors.error\_reporter module
+---------------------------------
+
+.. automodule:: hed.errors.error_reporter
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+hed.errors.error\_types module
+------------------------------
+
+.. automodule:: hed.errors.error_types
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+hed.errors.exceptions module
+----------------------------
+
+.. automodule:: hed.errors.exceptions
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: hed.errors
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/source/generated/hed.models.DefDict.rst b/docs/source/generated/hed.models.DefDict.rst
deleted file mode 100644
index 9d221f0f1..000000000
--- a/docs/source/generated/hed.models.DefDict.rst
+++ /dev/null
@@ -1,35 +0,0 @@
-﻿hed.models.DefDict
-==================
-
-.. currentmodule:: hed.models
-
-.. autoclass:: hed.models.DefDict
-   
-   .. automethod:: __init__
-
-   
-   .. rubric:: Methods
-
-   .. autosummary::
-   
-      ~DefDict.__init__
-      ~DefDict.check_for_definitions
-      ~DefDict.get_definition_issues
-   
-   
-
-   
-   
-   .. rubric:: Attributes
-
-   .. autosummary::
-   
-      ~DefDict.defs
-   
-   
-
-.. autoclass:: hed.models.DefDict
-   :members:
-   :undoc-members:
-   :show-inheritance:
-   :inherited-members:
\ No newline at end of file
diff --git a/docs/source/generated/hed.models.DefMapper.rst b/docs/source/generated/hed.models.DefMapper.rst
new file mode 100644
index 000000000..2d89f2399
--- /dev/null
+++ b/docs/source/generated/hed.models.DefMapper.rst
@@ -0,0 +1,39 @@
+﻿hed.models.DefMapper
+====================
+
+.. currentmodule:: hed.models
+
+.. autoclass:: hed.models.DefMapper
+   
+   .. automethod:: __init__
+
+   
+   .. rubric:: Methods
+
+   .. autosummary::
+   
+      ~DefMapper.__init__
+      ~DefMapper.add_definitions
+      ~DefMapper.add_definitions_from_string_as_temp
+      ~DefMapper.clear_temporary_definitions
+      ~DefMapper.expand_and_remove_definitions
+      ~DefMapper.expand_def_tags
+      ~DefMapper.get_def_entry
+   
+   
+
+   
+   
+   .. rubric:: Attributes
+
+   .. autosummary::
+   
+      ~DefMapper.issues
+   
+   
+
+.. autoclass:: hed.models.DefMapper
+   :members:
+   :undoc-members:
+   :show-inheritance:
+   :inherited-members:
\ No newline at end of file
diff --git a/docs/source/generated/hed.models.DefinitionDict.rst b/docs/source/generated/hed.models.DefinitionDict.rst
new file mode 100644
index 000000000..af709607b
--- /dev/null
+++ b/docs/source/generated/hed.models.DefinitionDict.rst
@@ -0,0 +1,35 @@
+﻿hed.models.DefinitionDict
+=========================
+
+.. currentmodule:: hed.models
+
+.. autoclass:: hed.models.DefinitionDict
+   
+   .. automethod:: __init__
+
+   
+   .. rubric:: Methods
+
+   .. autosummary::
+   
+      ~DefinitionDict.__init__
+      ~DefinitionDict.check_for_definitions
+      ~DefinitionDict.get_definition_issues
+   
+   
+
+   
+   
+   .. rubric:: Attributes
+
+   .. autosummary::
+   
+      ~DefinitionDict.defs
+   
+   
+
+.. autoclass:: hed.models.DefinitionDict
+   :members:
+   :undoc-members:
+   :show-inheritance:
+   :inherited-members:
\ No newline at end of file
diff --git a/docs/source/generated/hed.models.DefinitionEntry.rst b/docs/source/generated/hed.models.DefinitionEntry.rst
new file mode 100644
index 000000000..8afe392ae
--- /dev/null
+++ b/docs/source/generated/hed.models.DefinitionEntry.rst
@@ -0,0 +1,28 @@
+﻿hed.models.DefinitionEntry
+==========================
+
+.. currentmodule:: hed.models
+
+.. autoclass:: hed.models.DefinitionEntry
+   
+   .. automethod:: __init__
+
+   
+   .. rubric:: Methods
+
+   .. autosummary::
+   
+      ~DefinitionEntry.__init__
+      ~DefinitionEntry.get_definition
+   
+   
+
+   
+   
+   
+
+.. autoclass:: hed.models.DefinitionEntry
+   :members:
+   :undoc-members:
+   :show-inheritance:
+   :inherited-members:
\ No newline at end of file
diff --git a/docs/source/generated/hed.models.DefinitionMapper.rst b/docs/source/generated/hed.models.DefinitionMapper.rst
deleted file mode 100644
index fccc73a4d..000000000
--- a/docs/source/generated/hed.models.DefinitionMapper.rst
+++ /dev/null
@@ -1,33 +0,0 @@
-﻿hed.models.DefinitionMapper
-===========================
-
-.. currentmodule:: hed.models
-
-.. autoclass:: hed.models.DefinitionMapper
-   
-   .. automethod:: __init__
-
-   
-   .. rubric:: Methods
-
-   .. autosummary::
-   
-      ~DefinitionMapper.__init__
-      ~DefinitionMapper.add_definitions
-      ~DefinitionMapper.add_definitions_from_string_as_temp
-      ~DefinitionMapper.clear_temporary_definitions
-      ~DefinitionMapper.expand_and_remove_definitions
-      ~DefinitionMapper.expand_def_tags
-      ~DefinitionMapper.get_def_entry
-   
-   
-
-   
-   
-   
-
-.. autoclass:: hed.models.DefinitionMapper
-   :members:
-   :undoc-members:
-   :show-inheritance:
-   :inherited-members:
\ No newline at end of file
diff --git a/docs/source/generated/hed.models.EventsInput.rst b/docs/source/generated/hed.models.EventsInput.rst
deleted file mode 100644
index 742afe433..000000000
--- a/docs/source/generated/hed.models.EventsInput.rst
+++ /dev/null
@@ -1,62 +0,0 @@
-﻿hed.models.EventsInput
-======================
-
-.. currentmodule:: hed.models
-
-.. autoclass:: hed.models.EventsInput
-   
-   .. automethod:: __init__
-
-   
-   .. rubric:: Methods
-
-   .. autosummary::
-   
-      ~EventsInput.__init__
-      ~EventsInput.convert_to_long
-      ~EventsInput.convert_to_short
-      ~EventsInput.create_def_mapper
-      ~EventsInput.extract_definitions
-      ~EventsInput.get_assembled
-      ~EventsInput.get_def_and_mapper_issues
-      ~EventsInput.get_worksheet
-      ~EventsInput.iter_dataframe
-      ~EventsInput.iter_raw
-      ~EventsInput.reset_column_mapper
-      ~EventsInput.reset_mapper
-      ~EventsInput.set_cell
-      ~EventsInput.to_csv
-      ~EventsInput.to_excel
-      ~EventsInput.update_definition_mapper
-      ~EventsInput.validate_file
-      ~EventsInput.validate_file_sidecars
-   
-   
-
-   
-   
-   .. rubric:: Attributes
-
-   .. autosummary::
-   
-      ~EventsInput.COMMA_DELIMITER
-      ~EventsInput.EXCEL_EXTENSION
-      ~EventsInput.FILE_EXTENSION
-      ~EventsInput.FILE_INPUT
-      ~EventsInput.HED_COLUMN_NAME
-      ~EventsInput.STRING_INPUT
-      ~EventsInput.TAB_DELIMITER
-      ~EventsInput.TEXT_EXTENSION
-      ~EventsInput.dataframe
-      ~EventsInput.has_column_names
-      ~EventsInput.loaded_workbook
-      ~EventsInput.name
-      ~EventsInput.worksheet_name
-   
-   
-
-.. autoclass:: hed.models.EventsInput
-   :members:
-   :undoc-members:
-   :show-inheritance:
-   :inherited-members:
\ No newline at end of file
diff --git a/docs/source/generated/hed.models.HedGroup.rst b/docs/source/generated/hed.models.HedGroup.rst
index 43357cb0f..653d3e9a6 100644
--- a/docs/source/generated/hed.models.HedGroup.rst
+++ b/docs/source/generated/hed.models.HedGroup.rst
@@ -14,8 +14,8 @@
    
       ~HedGroup.__init__
       ~HedGroup.append
-      ~HedGroup.cascade_mutable
       ~HedGroup.check_if_in_original
+      ~HedGroup.copy
       ~HedGroup.find_def_tags
       ~HedGroup.find_exact_tags
       ~HedGroup.find_placeholder_tag
@@ -30,7 +30,6 @@
       ~HedGroup.get_original_hed_string
       ~HedGroup.groups
       ~HedGroup.lower
-      ~HedGroup.make_tag_mutable
       ~HedGroup.remove
       ~HedGroup.replace
       ~HedGroup.tags
diff --git a/docs/source/generated/hed.models.HedGroupBase.rst b/docs/source/generated/hed.models.HedGroupBase.rst
new file mode 100644
index 000000000..87769cf11
--- /dev/null
+++ b/docs/source/generated/hed.models.HedGroupBase.rst
@@ -0,0 +1,50 @@
+﻿hed.models.HedGroupBase
+=======================
+
+.. currentmodule:: hed.models
+
+.. autoclass:: hed.models.HedGroupBase
+   
+   .. automethod:: __init__
+
+   
+   .. rubric:: Methods
+
+   .. autosummary::
+   
+      ~HedGroupBase.__init__
+      ~HedGroupBase.copy
+      ~HedGroupBase.find_def_tags
+      ~HedGroupBase.find_exact_tags
+      ~HedGroupBase.find_placeholder_tag
+      ~HedGroupBase.find_tags
+      ~HedGroupBase.find_tags_with_term
+      ~HedGroupBase.get_all_groups
+      ~HedGroupBase.get_all_tags
+      ~HedGroupBase.get_as_form
+      ~HedGroupBase.get_as_long
+      ~HedGroupBase.get_as_short
+      ~HedGroupBase.get_original_hed_string
+      ~HedGroupBase.groups
+      ~HedGroupBase.lower
+      ~HedGroupBase.tags
+   
+   
+
+   
+   
+   .. rubric:: Attributes
+
+   .. autosummary::
+   
+      ~HedGroupBase.children
+      ~HedGroupBase.is_group
+      ~HedGroupBase.span
+   
+   
+
+.. autoclass:: hed.models.HedGroupBase
+   :members:
+   :undoc-members:
+   :show-inheritance:
+   :inherited-members:
\ No newline at end of file
diff --git a/docs/source/generated/hed.models.HedGroupFrozen.rst b/docs/source/generated/hed.models.HedGroupFrozen.rst
new file mode 100644
index 000000000..f041a0547
--- /dev/null
+++ b/docs/source/generated/hed.models.HedGroupFrozen.rst
@@ -0,0 +1,51 @@
+﻿hed.models.HedGroupFrozen
+=========================
+
+.. currentmodule:: hed.models
+
+.. autoclass:: hed.models.HedGroupFrozen
+   
+   .. automethod:: __init__
+
+   
+   .. rubric:: Methods
+
+   .. autosummary::
+   
+      ~HedGroupFrozen.__init__
+      ~HedGroupFrozen.copy
+      ~HedGroupFrozen.find_def_tags
+      ~HedGroupFrozen.find_exact_tags
+      ~HedGroupFrozen.find_placeholder_tag
+      ~HedGroupFrozen.find_tags
+      ~HedGroupFrozen.find_tags_with_term
+      ~HedGroupFrozen.get_all_groups
+      ~HedGroupFrozen.get_all_tags
+      ~HedGroupFrozen.get_as_form
+      ~HedGroupFrozen.get_as_long
+      ~HedGroupFrozen.get_as_short
+      ~HedGroupFrozen.get_frozen
+      ~HedGroupFrozen.get_original_hed_string
+      ~HedGroupFrozen.groups
+      ~HedGroupFrozen.lower
+      ~HedGroupFrozen.tags
+   
+   
+
+   
+   
+   .. rubric:: Attributes
+
+   .. autosummary::
+   
+      ~HedGroupFrozen.children
+      ~HedGroupFrozen.is_group
+      ~HedGroupFrozen.span
+   
+   
+
+.. autoclass:: hed.models.HedGroupFrozen
+   :members:
+   :undoc-members:
+   :show-inheritance:
+   :inherited-members:
\ No newline at end of file
diff --git a/docs/source/generated/hed.models.HedInput.rst b/docs/source/generated/hed.models.HedInput.rst
deleted file mode 100644
index 9d39261f0..000000000
--- a/docs/source/generated/hed.models.HedInput.rst
+++ /dev/null
@@ -1,58 +0,0 @@
-﻿hed.models.HedInput
-===================
-
-.. currentmodule:: hed.models
-
-.. autoclass:: hed.models.HedInput
-   
-   .. automethod:: __init__
-
-   
-   .. rubric:: Methods
-
-   .. autosummary::
-   
-      ~HedInput.__init__
-      ~HedInput.convert_to_long
-      ~HedInput.convert_to_short
-      ~HedInput.extract_definitions
-      ~HedInput.get_assembled
-      ~HedInput.get_def_and_mapper_issues
-      ~HedInput.get_worksheet
-      ~HedInput.iter_dataframe
-      ~HedInput.iter_raw
-      ~HedInput.reset_mapper
-      ~HedInput.set_cell
-      ~HedInput.to_csv
-      ~HedInput.to_excel
-      ~HedInput.update_definition_mapper
-      ~HedInput.validate_file
-   
-   
-
-   
-   
-   .. rubric:: Attributes
-
-   .. autosummary::
-   
-      ~HedInput.COMMA_DELIMITER
-      ~HedInput.EXCEL_EXTENSION
-      ~HedInput.FILE_EXTENSION
-      ~HedInput.FILE_INPUT
-      ~HedInput.STRING_INPUT
-      ~HedInput.TAB_DELIMITER
-      ~HedInput.TEXT_EXTENSION
-      ~HedInput.dataframe
-      ~HedInput.has_column_names
-      ~HedInput.loaded_workbook
-      ~HedInput.name
-      ~HedInput.worksheet_name
-   
-   
-
-.. autoclass:: hed.models.HedInput
-   :members:
-   :undoc-members:
-   :show-inheritance:
-   :inherited-members:
\ No newline at end of file
diff --git a/docs/source/generated/hed.models.DefEntry.rst b/docs/source/generated/hed.models.HedOps.rst
similarity index 52%
rename from docs/source/generated/hed.models.DefEntry.rst
rename to docs/source/generated/hed.models.HedOps.rst
index abd7f615a..3545960d7 100644
--- a/docs/source/generated/hed.models.DefEntry.rst
+++ b/docs/source/generated/hed.models.HedOps.rst
@@ -1,9 +1,9 @@
-﻿hed.models.DefEntry
-===================
+﻿hed.models.HedOps
+=================
 
 .. currentmodule:: hed.models
 
-.. autoclass:: hed.models.DefEntry
+.. autoclass:: hed.models.HedOps
    
    .. automethod:: __init__
 
@@ -12,8 +12,7 @@
 
    .. autosummary::
    
-      ~DefEntry.__init__
-      ~DefEntry.get_definition
+      ~HedOps.__init__
    
    
 
@@ -21,7 +20,7 @@
    
    
 
-.. autoclass:: hed.models.DefEntry
+.. autoclass:: hed.models.HedOps
    :members:
    :undoc-members:
    :show-inheritance:
diff --git a/docs/source/generated/hed.models.HedString.rst b/docs/source/generated/hed.models.HedString.rst
index 1772a689e..4470e0238 100644
--- a/docs/source/generated/hed.models.HedString.rst
+++ b/docs/source/generated/hed.models.HedString.rst
@@ -15,12 +15,12 @@
       ~HedString.__init__
       ~HedString.append
       ~HedString.apply_funcs
-      ~HedString.cascade_mutable
       ~HedString.check_if_in_original
       ~HedString.convert_to_canonical_forms
       ~HedString.convert_to_long
       ~HedString.convert_to_original
       ~HedString.convert_to_short
+      ~HedString.copy
       ~HedString.find_def_tags
       ~HedString.find_exact_tags
       ~HedString.find_placeholder_tag
@@ -36,9 +36,8 @@
       ~HedString.get_original_hed_string
       ~HedString.groups
       ~HedString.lower
-      ~HedString.make_tag_mutable
-      ~HedString.remove_definitions
       ~HedString.remove
+      ~HedString.remove_definitions
       ~HedString.replace
       ~HedString.split_hed_string
       ~HedString.split_into_groups
diff --git a/docs/source/generated/hed.models.Sidecar.rst b/docs/source/generated/hed.models.Sidecar.rst
index 07efa32ef..a9c2d029b 100644
--- a/docs/source/generated/hed.models.Sidecar.rst
+++ b/docs/source/generated/hed.models.Sidecar.rst
@@ -13,11 +13,11 @@
    .. autosummary::
    
       ~Sidecar.__init__
-      ~Sidecar.add_sidecar_file
       ~Sidecar.get_as_json_string
       ~Sidecar.get_def_dicts
       ~Sidecar.hed_string_iter
       ~Sidecar.load_multiple_sidecars
+      ~Sidecar.load_sidecar_file
       ~Sidecar.save_as_json
       ~Sidecar.set_hed_string
       ~Sidecar.validate_entries
diff --git a/docs/source/generated/hed.models.SpreadsheetInput.rst b/docs/source/generated/hed.models.SpreadsheetInput.rst
new file mode 100644
index 000000000..327aea106
--- /dev/null
+++ b/docs/source/generated/hed.models.SpreadsheetInput.rst
@@ -0,0 +1,58 @@
+﻿hed.models.SpreadsheetInput
+===========================
+
+.. currentmodule:: hed.models
+
+.. autoclass:: hed.models.SpreadsheetInput
+   
+   .. automethod:: __init__
+
+   
+   .. rubric:: Methods
+
+   .. autosummary::
+   
+      ~SpreadsheetInput.__init__
+      ~SpreadsheetInput.convert_to_long
+      ~SpreadsheetInput.convert_to_short
+      ~SpreadsheetInput.extract_definitions
+      ~SpreadsheetInput.get_assembled
+      ~SpreadsheetInput.get_def_and_mapper_issues
+      ~SpreadsheetInput.get_worksheet
+      ~SpreadsheetInput.iter_dataframe
+      ~SpreadsheetInput.iter_raw
+      ~SpreadsheetInput.reset_mapper
+      ~SpreadsheetInput.set_cell
+      ~SpreadsheetInput.to_csv
+      ~SpreadsheetInput.to_excel
+      ~SpreadsheetInput.update_definition_mapper
+      ~SpreadsheetInput.validate_file
+   
+   
+
+   
+   
+   .. rubric:: Attributes
+
+   .. autosummary::
+   
+      ~SpreadsheetInput.COMMA_DELIMITER
+      ~SpreadsheetInput.EXCEL_EXTENSION
+      ~SpreadsheetInput.FILE_EXTENSION
+      ~SpreadsheetInput.FILE_INPUT
+      ~SpreadsheetInput.STRING_INPUT
+      ~SpreadsheetInput.TAB_DELIMITER
+      ~SpreadsheetInput.TEXT_EXTENSION
+      ~SpreadsheetInput.dataframe
+      ~SpreadsheetInput.has_column_names
+      ~SpreadsheetInput.loaded_workbook
+      ~SpreadsheetInput.name
+      ~SpreadsheetInput.worksheet_name
+   
+   
+
+.. autoclass:: hed.models.SpreadsheetInput
+   :members:
+   :undoc-members:
+   :show-inheritance:
+   :inherited-members:
\ No newline at end of file
diff --git a/docs/source/generated/hed.models.TabularInput.rst b/docs/source/generated/hed.models.TabularInput.rst
new file mode 100644
index 000000000..d82e1904a
--- /dev/null
+++ b/docs/source/generated/hed.models.TabularInput.rst
@@ -0,0 +1,62 @@
+﻿hed.models.TabularInput
+=======================
+
+.. currentmodule:: hed.models
+
+.. autoclass:: hed.models.TabularInput
+   
+   .. automethod:: __init__
+
+   
+   .. rubric:: Methods
+
+   .. autosummary::
+   
+      ~TabularInput.__init__
+      ~TabularInput.convert_to_long
+      ~TabularInput.convert_to_short
+      ~TabularInput.create_def_mapper
+      ~TabularInput.extract_definitions
+      ~TabularInput.get_assembled
+      ~TabularInput.get_def_and_mapper_issues
+      ~TabularInput.get_worksheet
+      ~TabularInput.iter_dataframe
+      ~TabularInput.iter_raw
+      ~TabularInput.reset_column_mapper
+      ~TabularInput.reset_mapper
+      ~TabularInput.set_cell
+      ~TabularInput.to_csv
+      ~TabularInput.to_excel
+      ~TabularInput.update_definition_mapper
+      ~TabularInput.validate_file
+      ~TabularInput.validate_file_sidecars
+   
+   
+
+   
+   
+   .. rubric:: Attributes
+
+   .. autosummary::
+   
+      ~TabularInput.COMMA_DELIMITER
+      ~TabularInput.EXCEL_EXTENSION
+      ~TabularInput.FILE_EXTENSION
+      ~TabularInput.FILE_INPUT
+      ~TabularInput.HED_COLUMN_NAME
+      ~TabularInput.STRING_INPUT
+      ~TabularInput.TAB_DELIMITER
+      ~TabularInput.TEXT_EXTENSION
+      ~TabularInput.dataframe
+      ~TabularInput.has_column_names
+      ~TabularInput.loaded_workbook
+      ~TabularInput.name
+      ~TabularInput.worksheet_name
+   
+   
+
+.. autoclass:: hed.models.TabularInput
+   :members:
+   :undoc-members:
+   :show-inheritance:
+   :inherited-members:
\ No newline at end of file
diff --git a/docs/source/generated/hed.models.rst b/docs/source/generated/hed.models.rst
index b62606ebe..960030574 100644
--- a/docs/source/generated/hed.models.rst
+++ b/docs/source/generated/hed.models.rst
@@ -1,5 +1,157 @@
-models module
-=============
+hed.models package
+==================
+
+Submodules
+----------
+
+hed.models.base\_input module
+-----------------------------
+
+.. automodule:: hed.models.base_input
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+hed.models.column\_mapper module
+--------------------------------
+
+.. automodule:: hed.models.column_mapper
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+hed.models.column\_metadata module
+----------------------------------
+
+.. automodule:: hed.models.column_metadata
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+hed.models.def\_mapper module
+-----------------------------
+
+.. automodule:: hed.models.def_mapper
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+hed.models.definition\_dict module
+----------------------------------
+
+.. automodule:: hed.models.definition_dict
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+hed.models.expression\_parser module
+------------------------------------
+
+.. automodule:: hed.models.expression_parser
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+hed.models.hed\_group module
+----------------------------
+
+.. automodule:: hed.models.hed_group
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+hed.models.hed\_group\_base module
+----------------------------------
+
+.. automodule:: hed.models.hed_group_base
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+hed.models.hed\_ops module
+--------------------------
+
+.. automodule:: hed.models.hed_ops
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+hed.models.hed\_string module
+-----------------------------
+
+.. automodule:: hed.models.hed_string
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+hed.models.hed\_string\_comb module
+-----------------------------------
+
+.. automodule:: hed.models.hed_string_comb
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+hed.models.hed\_tag module
+--------------------------
+
+.. automodule:: hed.models.hed_tag
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+hed.models.model\_constants module
+----------------------------------
+
+.. automodule:: hed.models.model_constants
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+hed.models.onset\_mapper module
+-------------------------------
+
+.. automodule:: hed.models.onset_mapper
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+hed.models.sidecar module
+-------------------------
+
+.. automodule:: hed.models.sidecar
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+hed.models.spreadsheet\_input module
+------------------------------------
+
+.. automodule:: hed.models.spreadsheet_input
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+hed.models.tabular\_input module
+--------------------------------
+
+.. automodule:: hed.models.tabular_input
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+hed.models.timeseries\_input module
+-----------------------------------
+
+.. automodule:: hed.models.timeseries_input
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Module contents
+---------------
 
 .. automodule:: hed.models
-   :members:
\ No newline at end of file
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/source/generated/hed.rst b/docs/source/generated/hed.rst
new file mode 100644
index 000000000..67afb61a2
--- /dev/null
+++ b/docs/source/generated/hed.rst
@@ -0,0 +1,23 @@
+hed package
+===========
+
+Subpackages
+-----------
+
+.. toctree::
+   :maxdepth: 4
+
+   hed.errors
+   hed.models
+   hed.schema
+   hed.tools
+   hed.util
+   hed.validator
+
+Module contents
+---------------
+
+.. automodule:: hed
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/source/generated/hed.schema.io.rst b/docs/source/generated/hed.schema.io.rst
new file mode 100644
index 000000000..0e03e1bcf
--- /dev/null
+++ b/docs/source/generated/hed.schema.io.rst
@@ -0,0 +1,61 @@
+hed.schema.io package
+=====================
+
+Submodules
+----------
+
+hed.schema.io.schema2wiki module
+--------------------------------
+
+.. automodule:: hed.schema.io.schema2wiki
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+hed.schema.io.schema2xml module
+-------------------------------
+
+.. automodule:: hed.schema.io.schema2xml
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+hed.schema.io.wiki2schema module
+--------------------------------
+
+.. automodule:: hed.schema.io.wiki2schema
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+hed.schema.io.wiki\_constants module
+------------------------------------
+
+.. automodule:: hed.schema.io.wiki_constants
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+hed.schema.io.xml2schema module
+-------------------------------
+
+.. automodule:: hed.schema.io.xml2schema
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+hed.schema.io.xml\_constants module
+-----------------------------------
+
+.. automodule:: hed.schema.io.xml_constants
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: hed.schema.io
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/source/generated/hed.schema.rst b/docs/source/generated/hed.schema.rst
index f0af8b746..eb59f15c5 100644
--- a/docs/source/generated/hed.schema.rst
+++ b/docs/source/generated/hed.schema.rst
@@ -1,5 +1,93 @@
-schema module
-=============
+hed.schema package
+==================
+
+Subpackages
+-----------
+
+.. toctree::
+   :maxdepth: 4
+
+   hed.schema.io
+
+Submodules
+----------
+
+hed.schema.hed\_cache module
+----------------------------
+
+.. automodule:: hed.schema.hed_cache
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+hed.schema.hed\_schema module
+-----------------------------
+
+.. automodule:: hed.schema.hed_schema
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+hed.schema.hed\_schema\_constants module
+----------------------------------------
+
+.. automodule:: hed.schema.hed_schema_constants
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+hed.schema.hed\_schema\_entry module
+------------------------------------
+
+.. automodule:: hed.schema.hed_schema_entry
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+hed.schema.hed\_schema\_group module
+------------------------------------
+
+.. automodule:: hed.schema.hed_schema_group
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+hed.schema.hed\_schema\_io module
+---------------------------------
+
+.. automodule:: hed.schema.hed_schema_io
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+hed.schema.hed\_schema\_section module
+--------------------------------------
+
+.. automodule:: hed.schema.hed_schema_section
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+hed.schema.schema\_compliance module
+------------------------------------
+
+.. automodule:: hed.schema.schema_compliance
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+hed.schema.schema\_validation\_util module
+------------------------------------------
+
+.. automodule:: hed.schema.schema_validation_util
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Module contents
+---------------
 
 .. automodule:: hed.schema
-   :members:
\ No newline at end of file
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/source/generated/hed.tools.BidsEventFile.rst b/docs/source/generated/hed.tools.BidsEventFile.rst
deleted file mode 100644
index 0eece3342..000000000
--- a/docs/source/generated/hed.tools.BidsEventFile.rst
+++ /dev/null
@@ -1,30 +0,0 @@
-﻿hed.tools.BidsEventFile
-=======================
-
-.. currentmodule:: hed.tools
-
-.. autoclass:: hed.tools.BidsEventFile
-   
-   .. automethod:: __init__
-
-   
-   .. rubric:: Methods
-
-   .. autosummary::
-   
-      ~BidsEventFile.__init__
-      ~BidsEventFile.clear_contents
-      ~BidsEventFile.set_contents
-      ~BidsEventFile.set_sidecars
-   
-   
-
-   
-   
-   
-
-.. autoclass:: hed.tools.BidsEventFile
-   :members:
-   :undoc-members:
-   :show-inheritance:
-   :inherited-members:
\ No newline at end of file
diff --git a/docs/source/generated/hed.tools.BidsEventFiles.rst b/docs/source/generated/hed.tools.BidsEventFiles.rst
deleted file mode 100644
index b103d753e..000000000
--- a/docs/source/generated/hed.tools.BidsEventFiles.rst
+++ /dev/null
@@ -1,30 +0,0 @@
-﻿hed.tools.BidsEventFiles
-========================
-
-.. currentmodule:: hed.tools
-
-.. autoclass:: hed.tools.BidsEventFiles
-   
-   .. automethod:: __init__
-
-   
-   .. rubric:: Methods
-
-   .. autosummary::
-   
-      ~BidsEventFiles.__init__
-      ~BidsEventFiles.get_sidecars_from_path
-      ~BidsEventFiles.summarize
-      ~BidsEventFiles.validate
-   
-   
-
-   
-   
-   
-
-.. autoclass:: hed.tools.BidsEventFiles
-   :members:
-   :undoc-members:
-   :show-inheritance:
-   :inherited-members:
\ No newline at end of file
diff --git a/docs/source/generated/hed.tools.BidsFile.rst b/docs/source/generated/hed.tools.BidsFile.rst
index 1ef1ac01e..b27457b27 100644
--- a/docs/source/generated/hed.tools.BidsFile.rst
+++ b/docs/source/generated/hed.tools.BidsFile.rst
@@ -13,11 +13,19 @@
    .. autosummary::
    
       ~BidsFile.__init__
+      ~BidsFile.clear_contents
+      ~BidsFile.set_contents
    
    
 
    
    
+   .. rubric:: Attributes
+
+   .. autosummary::
+   
+      ~BidsFile.get_contents
+   
    
 
 .. autoclass:: hed.tools.BidsFile
diff --git a/docs/source/generated/hed.tools.BidsFileGroup.rst b/docs/source/generated/hed.tools.BidsFileGroup.rst
new file mode 100644
index 000000000..eef41b1cd
--- /dev/null
+++ b/docs/source/generated/hed.tools.BidsFileGroup.rst
@@ -0,0 +1,31 @@
+﻿hed.tools.BidsFileGroup
+=======================
+
+.. currentmodule:: hed.tools
+
+.. autoclass:: hed.tools.BidsFileGroup
+   
+   .. automethod:: __init__
+
+   
+   .. rubric:: Methods
+
+   .. autosummary::
+   
+      ~BidsFileGroup.__init__
+      ~BidsFileGroup.get_sidecars_from_path
+      ~BidsFileGroup.summarize
+      ~BidsFileGroup.validate_datafiles
+      ~BidsFileGroup.validate_sidecars
+   
+   
+
+   
+   
+   
+
+.. autoclass:: hed.tools.BidsFileGroup
+   :members:
+   :undoc-members:
+   :show-inheritance:
+   :inherited-members:
\ No newline at end of file
diff --git a/docs/source/generated/hed.tools.BidsJsonFile.rst b/docs/source/generated/hed.tools.BidsJsonFile.rst
deleted file mode 100644
index 9d3c18599..000000000
--- a/docs/source/generated/hed.tools.BidsJsonFile.rst
+++ /dev/null
@@ -1,29 +0,0 @@
-﻿hed.tools.BidsJsonFile
-======================
-
-.. currentmodule:: hed.tools
-
-.. autoclass:: hed.tools.BidsJsonFile
-   
-   .. automethod:: __init__
-
-   
-   .. rubric:: Methods
-
-   .. autosummary::
-   
-      ~BidsJsonFile.__init__
-      ~BidsJsonFile.clear_contents
-      ~BidsJsonFile.set_contents
-   
-   
-
-   
-   
-   
-
-.. autoclass:: hed.tools.BidsJsonFile
-   :members:
-   :undoc-members:
-   :show-inheritance:
-   :inherited-members:
\ No newline at end of file
diff --git a/docs/source/generated/hed.tools.BidsSidecarFile.rst b/docs/source/generated/hed.tools.BidsSidecarFile.rst
index 41108663a..6b58b4ded 100644
--- a/docs/source/generated/hed.tools.BidsSidecarFile.rst
+++ b/docs/source/generated/hed.tools.BidsSidecarFile.rst
@@ -14,7 +14,6 @@
    
       ~BidsSidecarFile.__init__
       ~BidsSidecarFile.clear_contents
-      ~BidsSidecarFile.get_sidecar
       ~BidsSidecarFile.is_sidecar_for
       ~BidsSidecarFile.set_contents
    
@@ -22,6 +21,12 @@
 
    
    
+   .. rubric:: Attributes
+
+   .. autosummary::
+   
+      ~BidsSidecarFile.get_contents
+   
    
 
 .. autoclass:: hed.tools.BidsSidecarFile
diff --git a/docs/source/generated/hed.tools.BidsTabularDictionary.rst b/docs/source/generated/hed.tools.BidsTabularDictionary.rst
new file mode 100644
index 000000000..597cf0bb3
--- /dev/null
+++ b/docs/source/generated/hed.tools.BidsTabularDictionary.rst
@@ -0,0 +1,50 @@
+﻿hed.tools.BidsTabularDictionary
+===============================
+
+.. currentmodule:: hed.tools
+
+.. autoclass:: hed.tools.BidsTabularDictionary
+   
+   .. automethod:: __init__
+
+   
+   .. rubric:: Methods
+
+   .. autosummary::
+   
+      ~BidsTabularDictionary.__init__
+      ~BidsTabularDictionary.count_diffs
+      ~BidsTabularDictionary.create_file_dict
+      ~BidsTabularDictionary.create_split_dict
+      ~BidsTabularDictionary.get_file_path
+      ~BidsTabularDictionary.get_info
+      ~BidsTabularDictionary.iter_files
+      ~BidsTabularDictionary.iter_tsv_info
+      ~BidsTabularDictionary.key_diffs
+      ~BidsTabularDictionary.make_bids_file_dict
+      ~BidsTabularDictionary.make_file_dict
+      ~BidsTabularDictionary.make_key
+      ~BidsTabularDictionary.make_query
+      ~BidsTabularDictionary.match_query
+      ~BidsTabularDictionary.output_files
+      ~BidsTabularDictionary.split_dict_by_entity
+   
+   
+
+   
+   
+   .. rubric:: Attributes
+
+   .. autosummary::
+   
+      ~BidsTabularDictionary.file_list
+      ~BidsTabularDictionary.key_list
+      ~BidsTabularDictionary.name
+   
+   
+
+.. autoclass:: hed.tools.BidsTabularDictionary
+   :members:
+   :undoc-members:
+   :show-inheritance:
+   :inherited-members:
\ No newline at end of file
diff --git a/docs/source/generated/hed.tools.BidsTabularFile.rst b/docs/source/generated/hed.tools.BidsTabularFile.rst
new file mode 100644
index 000000000..44f8df9ae
--- /dev/null
+++ b/docs/source/generated/hed.tools.BidsTabularFile.rst
@@ -0,0 +1,35 @@
+﻿hed.tools.BidsTabularFile
+=========================
+
+.. currentmodule:: hed.tools
+
+.. autoclass:: hed.tools.BidsTabularFile
+   
+   .. automethod:: __init__
+
+   
+   .. rubric:: Methods
+
+   .. autosummary::
+   
+      ~BidsTabularFile.__init__
+      ~BidsTabularFile.clear_contents
+      ~BidsTabularFile.set_contents
+   
+   
+
+   
+   
+   .. rubric:: Attributes
+
+   .. autosummary::
+   
+      ~BidsTabularFile.get_contents
+   
+   
+
+.. autoclass:: hed.tools.BidsTabularFile
+   :members:
+   :undoc-members:
+   :show-inheritance:
+   :inherited-members:
\ No newline at end of file
diff --git a/docs/source/generated/hed.tools.BidsTabularSummary.rst b/docs/source/generated/hed.tools.BidsTabularSummary.rst
new file mode 100644
index 000000000..eac00f513
--- /dev/null
+++ b/docs/source/generated/hed.tools.BidsTabularSummary.rst
@@ -0,0 +1,33 @@
+﻿hed.tools.BidsTabularSummary
+============================
+
+.. currentmodule:: hed.tools
+
+.. autoclass:: hed.tools.BidsTabularSummary
+   
+   .. automethod:: __init__
+
+   
+   .. rubric:: Methods
+
+   .. autosummary::
+   
+      ~BidsTabularSummary.__init__
+      ~BidsTabularSummary.extract_sidecar_template
+      ~BidsTabularSummary.get_columns_info
+      ~BidsTabularSummary.get_number_unique_values
+      ~BidsTabularSummary.make_combined_dicts
+      ~BidsTabularSummary.update
+      ~BidsTabularSummary.update_summary
+   
+   
+
+   
+   
+   
+
+.. autoclass:: hed.tools.BidsTabularSummary
+   :members:
+   :undoc-members:
+   :show-inheritance:
+   :inherited-members:
\ No newline at end of file
diff --git a/docs/source/generated/hed.tools.BidsTsvDictionary.rst b/docs/source/generated/hed.tools.BidsTsvDictionary.rst
deleted file mode 100644
index 28b5309fc..000000000
--- a/docs/source/generated/hed.tools.BidsTsvDictionary.rst
+++ /dev/null
@@ -1,50 +0,0 @@
-﻿hed.tools.BidsTsvDictionary
-===========================
-
-.. currentmodule:: hed.tools
-
-.. autoclass:: hed.tools.BidsTsvDictionary
-   
-   .. automethod:: __init__
-
-   
-   .. rubric:: Methods
-
-   .. autosummary::
-   
-      ~BidsTsvDictionary.__init__
-      ~BidsTsvDictionary.count_diffs
-      ~BidsTsvDictionary.create_file_dict
-      ~BidsTsvDictionary.create_split_dict
-      ~BidsTsvDictionary.get_file_path
-      ~BidsTsvDictionary.get_info
-      ~BidsTsvDictionary.iter_files
-      ~BidsTsvDictionary.iter_tsv_info
-      ~BidsTsvDictionary.key_diffs
-      ~BidsTsvDictionary.make_bids_file_dict
-      ~BidsTsvDictionary.make_file_dict
-      ~BidsTsvDictionary.make_key
-      ~BidsTsvDictionary.make_query
-      ~BidsTsvDictionary.match_query
-      ~BidsTsvDictionary.output_files
-      ~BidsTsvDictionary.split_dict_by_entity
-   
-   
-
-   
-   
-   .. rubric:: Attributes
-
-   .. autosummary::
-   
-      ~BidsTsvDictionary.file_list
-      ~BidsTsvDictionary.key_list
-      ~BidsTsvDictionary.name
-   
-   
-
-.. autoclass:: hed.tools.BidsTsvDictionary
-   :members:
-   :undoc-members:
-   :show-inheritance:
-   :inherited-members:
\ No newline at end of file
diff --git a/docs/source/generated/hed.tools.BidsTsvFile.rst b/docs/source/generated/hed.tools.BidsTsvFile.rst
deleted file mode 100644
index 6429eb3d1..000000000
--- a/docs/source/generated/hed.tools.BidsTsvFile.rst
+++ /dev/null
@@ -1,29 +0,0 @@
-﻿hed.tools.BidsTsvFile
-=====================
-
-.. currentmodule:: hed.tools
-
-.. autoclass:: hed.tools.BidsTsvFile
-   
-   .. automethod:: __init__
-
-   
-   .. rubric:: Methods
-
-   .. autosummary::
-   
-      ~BidsTsvFile.__init__
-      ~BidsTsvFile.clear_contents
-      ~BidsTsvFile.set_contents
-   
-   
-
-   
-   
-   
-
-.. autoclass:: hed.tools.BidsTsvFile
-   :members:
-   :undoc-members:
-   :show-inheritance:
-   :inherited-members:
\ No newline at end of file
diff --git a/docs/source/generated/hed.tools.BidsTsvSummary.rst b/docs/source/generated/hed.tools.BidsTsvSummary.rst
deleted file mode 100644
index 7969b042e..000000000
--- a/docs/source/generated/hed.tools.BidsTsvSummary.rst
+++ /dev/null
@@ -1,33 +0,0 @@
-﻿hed.tools.BidsTsvSummary
-========================
-
-.. currentmodule:: hed.tools
-
-.. autoclass:: hed.tools.BidsTsvSummary
-   
-   .. automethod:: __init__
-
-   
-   .. rubric:: Methods
-
-   .. autosummary::
-   
-      ~BidsTsvSummary.__init__
-      ~BidsTsvSummary.extract_sidecar_template
-      ~BidsTsvSummary.get_columns_info
-      ~BidsTsvSummary.get_number_unique_values
-      ~BidsTsvSummary.make_combined_dicts
-      ~BidsTsvSummary.update
-      ~BidsTsvSummary.update_summary
-   
-   
-
-   
-   
-   
-
-.. autoclass:: hed.tools.BidsTsvSummary
-   :members:
-   :undoc-members:
-   :show-inheritance:
-   :inherited-members:
\ No newline at end of file
diff --git a/docs/source/generated/hed.tools.analysis.analysis_util.rst b/docs/source/generated/hed.tools.analysis.analysis_util.rst
new file mode 100644
index 000000000..8229f79cc
--- /dev/null
+++ b/docs/source/generated/hed.tools.analysis.analysis_util.rst
@@ -0,0 +1,32 @@
+﻿hed.tools.analysis.analysis\_util
+=================================
+
+.. automodule:: hed.tools.analysis.analysis_util
+
+   
+   
+   
+
+   
+   
+   .. rubric:: Functions
+
+   .. autosummary::
+   
+      assemble_hed
+      filter_events
+      get_assembled_strings
+      search_tabular
+   
+   
+
+   
+   
+   
+
+   
+   
+   
+
+
+
diff --git a/docs/source/generated/hed.tools.annotation.annotation_util.rst b/docs/source/generated/hed.tools.analysis.annotation_util.rst
similarity index 61%
rename from docs/source/generated/hed.tools.annotation.annotation_util.rst
rename to docs/source/generated/hed.tools.analysis.annotation_util.rst
index bf5234a21..434ef4de2 100644
--- a/docs/source/generated/hed.tools.annotation.annotation_util.rst
+++ b/docs/source/generated/hed.tools.analysis.annotation_util.rst
@@ -1,7 +1,7 @@
-﻿hed.tools.annotation.annotation\_util
-=====================================
+﻿hed.tools.analysis.annotation\_util
+===================================
 
-.. automodule:: hed.tools.annotation.annotation_util
+.. automodule:: hed.tools.analysis.annotation_util
 
    
    
diff --git a/docs/source/generated/hed.tools.analysis.rst b/docs/source/generated/hed.tools.analysis.rst
new file mode 100644
index 000000000..51fd14c93
--- /dev/null
+++ b/docs/source/generated/hed.tools.analysis.rst
@@ -0,0 +1,77 @@
+hed.tools.analysis package
+==========================
+
+Submodules
+----------
+
+hed.tools.analysis.analysis\_util module
+----------------------------------------
+
+.. automodule:: hed.tools.analysis.analysis_util
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+hed.tools.analysis.annotation\_util module
+------------------------------------------
+
+.. automodule:: hed.tools.analysis.annotation_util
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+hed.tools.analysis.file\_dictionary module
+------------------------------------------
+
+.. automodule:: hed.tools.analysis.file_dictionary
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+hed.tools.analysis.key\_map module
+----------------------------------
+
+.. automodule:: hed.tools.analysis.key_map
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+hed.tools.analysis.key\_template module
+---------------------------------------
+
+.. automodule:: hed.tools.analysis.key_template
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+hed.tools.analysis.summary\_util module
+---------------------------------------
+
+.. automodule:: hed.tools.analysis.summary_util
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+hed.tools.analysis.tag\_summary module
+--------------------------------------
+
+.. automodule:: hed.tools.analysis.tag_summary
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+hed.tools.analysis.tsv\_reports module
+--------------------------------------
+
+.. automodule:: hed.tools.analysis.tsv_reports
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: hed.tools.analysis
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/source/generated/hed.tools.analysis.summary_util.rst b/docs/source/generated/hed.tools.analysis.summary_util.rst
new file mode 100644
index 000000000..41b76020a
--- /dev/null
+++ b/docs/source/generated/hed.tools.analysis.summary_util.rst
@@ -0,0 +1,33 @@
+﻿hed.tools.analysis.summary\_util
+================================
+
+.. automodule:: hed.tools.analysis.summary_util
+
+   
+   
+   
+
+   
+   
+   .. rubric:: Functions
+
+   .. autosummary::
+   
+      add_tag_list_to_dict
+      breakout_tags
+      extract_dict_values
+      get_schema_entries
+      unfold_tag_list
+   
+   
+
+   
+   
+   
+
+   
+   
+   
+
+
+
diff --git a/docs/source/generated/hed.tools.bids.rst b/docs/source/generated/hed.tools.bids.rst
new file mode 100644
index 000000000..16d9782da
--- /dev/null
+++ b/docs/source/generated/hed.tools.bids.rst
@@ -0,0 +1,93 @@
+hed.tools.bids package
+======================
+
+Submodules
+----------
+
+hed.tools.bids.bids\_dataset module
+-----------------------------------
+
+.. automodule:: hed.tools.bids.bids_dataset
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+hed.tools.bids.bids\_dataset\_summary module
+--------------------------------------------
+
+.. automodule:: hed.tools.bids.bids_dataset_summary
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+hed.tools.bids.bids\_file module
+--------------------------------
+
+.. automodule:: hed.tools.bids.bids_file
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+hed.tools.bids.bids\_file\_dictionary module
+--------------------------------------------
+
+.. automodule:: hed.tools.bids.bids_file_dictionary
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+hed.tools.bids.bids\_file\_group module
+---------------------------------------
+
+.. automodule:: hed.tools.bids.bids_file_group
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+hed.tools.bids.bids\_sidecar\_file module
+-----------------------------------------
+
+.. automodule:: hed.tools.bids.bids_sidecar_file
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+hed.tools.bids.bids\_tabular\_dictionary module
+-----------------------------------------------
+
+.. automodule:: hed.tools.bids.bids_tabular_dictionary
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+hed.tools.bids.bids\_tabular\_file module
+-----------------------------------------
+
+.. automodule:: hed.tools.bids.bids_tabular_file
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+hed.tools.bids.bids\_tabular\_summary module
+--------------------------------------------
+
+.. automodule:: hed.tools.bids.bids_tabular_summary
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+hed.tools.bids.bids\_timeseries\_file module
+--------------------------------------------
+
+.. automodule:: hed.tools.bids.bids_timeseries_file
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: hed.tools.bids
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/source/generated/hed.tools.rst b/docs/source/generated/hed.tools.rst
index f748d902a..8b557d1ad 100644
--- a/docs/source/generated/hed.tools.rst
+++ b/docs/source/generated/hed.tools.rst
@@ -1,5 +1,30 @@
-tools module
-=============
+hed.tools package
+=================
+
+Subpackages
+-----------
+
+.. toctree::
+   :maxdepth: 4
+
+   hed.tools.analysis
+   hed.tools.bids
+
+Submodules
+----------
+
+hed.tools.hed\_logger module
+----------------------------
+
+.. automodule:: hed.tools.hed_logger
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Module contents
+---------------
 
 .. automodule:: hed.tools
-   :members:
\ No newline at end of file
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/source/generated/hed.util.rst b/docs/source/generated/hed.util.rst
index 31ead42cd..7b09615c2 100644
--- a/docs/source/generated/hed.util.rst
+++ b/docs/source/generated/hed.util.rst
@@ -1,5 +1,37 @@
-util module
-=============
+hed.util package
+================
+
+Submodules
+----------
+
+hed.util.data\_util module
+--------------------------
+
+.. automodule:: hed.util.data_util
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+hed.util.file\_util module
+--------------------------
+
+.. automodule:: hed.util.file_util
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+hed.util.io\_util module
+------------------------
+
+.. automodule:: hed.util.io_util
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Module contents
+---------------
 
 .. automodule:: hed.util
-   :members:
\ No newline at end of file
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/source/generated/hed.validator.rst b/docs/source/generated/hed.validator.rst
index b62606ebe..0a11a23e3 100644
--- a/docs/source/generated/hed.validator.rst
+++ b/docs/source/generated/hed.validator.rst
@@ -1,5 +1,37 @@
-models module
-=============
+hed.validator package
+=====================
 
-.. automodule:: hed.models
-   :members:
\ No newline at end of file
+Submodules
+----------
+
+hed.validator.hed\_validator module
+-----------------------------------
+
+.. automodule:: hed.validator.hed_validator
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+hed.validator.tag\_validator module
+-----------------------------------
+
+.. automodule:: hed.validator.tag_validator
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+hed.validator.tag\_validator\_util module
+-----------------------------------------
+
+.. automodule:: hed.validator.tag_validator_util
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: hed.validator
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/source/generated/modules.rst b/docs/source/generated/modules.rst
new file mode 100644
index 000000000..16e6f7238
--- /dev/null
+++ b/docs/source/generated/modules.rst
@@ -0,0 +1,8 @@
+hed-python
+==========
+
+.. toctree::
+   :maxdepth: 4
+
+   hed
+   versioneer
diff --git a/docs/source/generated/versioneer.rst b/docs/source/generated/versioneer.rst
new file mode 100644
index 000000000..63ec2b9f5
--- /dev/null
+++ b/docs/source/generated/versioneer.rst
@@ -0,0 +1,7 @@
+versioneer module
+=================
+
+.. automodule:: versioneer
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/hed/__init__.py b/hed/__init__.py
index 3adb69b30..04831dbf1 100644
--- a/hed/__init__.py
+++ b/hed/__init__.py
@@ -4,8 +4,8 @@
 from hed.errors.exceptions import HedFileError, HedExceptions
 
 from hed.models.base_input import BaseInput
-from hed.models.hed_input import HedInput
-from hed.models.events_input import EventsInput
+from hed.models.spreadsheet_input import SpreadsheetInput
+from hed.models.tabular_input import TabularInput
 from hed.models.sidecar import Sidecar
 
 from hed.schema.hed_schema import HedSchema
diff --git a/hed/errors/error_messages.py b/hed/errors/error_messages.py
index 433871605..6cff6c907 100644
--- a/hed/errors/error_messages.py
+++ b/hed/errors/error_messages.py
@@ -9,11 +9,6 @@
     SidecarErrors, SchemaWarnings, ErrorSeverity, DefinitionErrors, OnsetErrors
 
 
-@hed_error(DefinitionErrors.INVALID_DEFINITION_EXTENSION, actual_code=ValidationErrors.HED_DEFINITION_INVALID)
-def def_error_invalid_def_extension(def_name):
-    return f"Term '{def_name}' has an invalid extension.  Definitions can only have one term.", {}
-
-
 @hed_tag_error(ValidationErrors.HED_UNITS_INVALID)
 def val_error_invalid_unit(tag, unit_class_units):
     units_string = ','.join(sorted(unit_class_units))
@@ -287,6 +282,12 @@ def def_error_tag_already_in_schema(def_name):
     return f"Term '{def_name}' already used as term in schema and cannot be re-used as a definition.", {}
 
 
+@hed_error(DefinitionErrors.INVALID_DEFINITION_EXTENSION, actual_code=ValidationErrors.HED_DEFINITION_INVALID)
+def def_error_invalid_def_extension(def_name):
+    return f"Term '{def_name}' has an invalid extension.  Definitions can only have one term.", {}
+
+
+
 @hed_tag_error(OnsetErrors.ONSET_DEF_UNMATCHED, actual_code=ValidationErrors.HED_ONSET_OFFSET_ERROR)
 def onset_error_def_unmatched(tag):
     return f"The def tag in an onset/offset tag is unmatched.  Def tag: '{tag}'", {}
diff --git a/hed/errors/error_reporter.py b/hed/errors/error_reporter.py
index 6644936f0..d40c29b9d 100644
--- a/hed/errors/error_reporter.py
+++ b/hed/errors/error_reporter.py
@@ -300,18 +300,18 @@ def format_error_from_context(error_type, error_context, *args, actual_error=Non
         """
         error_func = error_functions.get(error_type)
         if not error_func:
-            error_object_list = ErrorHandler.val_error_unknown(*args, **kwargs)
-            error_object_list[0]['code'] = error_type
-            ErrorHandler._add_context_to_errors(error_object_list[0], error_context)
-            return error_object_list
+            error_object = ErrorHandler.val_error_unknown(*args, **kwargs)
+            error_object['code'] = error_type
+            ErrorHandler._add_context_to_errors(error_object, error_context)
+            return [error_object]
 
-        error_object_list = error_func(*args, **kwargs)
+        error_object = error_func(*args, **kwargs)
         if actual_error:
-            error_object_list[0]['code'] = actual_error
+            error_object['code'] = actual_error
 
-        ErrorHandler._add_context_to_errors(error_object_list[0], error_context)
-        ErrorHandler._update_error_with_char_pos(error_object_list[0])
-        return error_object_list
+        ErrorHandler._add_context_to_errors(error_object, error_context)
+        ErrorHandler._update_error_with_char_pos(error_object)
+        return [error_object]
 
     @staticmethod
     def _add_context_to_errors(error_object, error_context_to_add):
diff --git a/hed/models/__init__.py b/hed/models/__init__.py
index 5e91d3fc4..05a8aee41 100644
--- a/hed/models/__init__.py
+++ b/hed/models/__init__.py
@@ -5,13 +5,15 @@
 from .column_metadata import ColumnMetadata, ColumnType
 from .definition_dict import DefinitionDict, DefinitionEntry
 from .def_mapper import DefMapper
-from .events_input import EventsInput
 from .expression_parser import TagExpressionParser
 from .hed_group import HedGroup, HedGroupFrozen
 from .hed_group_base import HedGroupBase
-from .hed_input import HedInput
+from .spreadsheet_input import SpreadsheetInput
 from .hed_ops import HedOps
 from .hed_string import HedString, HedStringFrozen
+from .hed_string_comb import HedStringComb
 from .hed_tag import HedTag
 from .onset_mapper import OnsetMapper
-from .sidecar import Sidecar
\ No newline at end of file
+from .sidecar import Sidecar
+from .tabular_input import TabularInput
+from .timeseries_input import TimeseriesInput
\ No newline at end of file
diff --git a/hed/models/base_input.py b/hed/models/base_input.py
index 40e0f3076..93e77eaec 100644
--- a/hed/models/base_input.py
+++ b/hed/models/base_input.py
@@ -42,7 +42,7 @@ def __init__(self, file, file_type=None, worksheet_name=None, has_column_names=T
 
         Notes:
             The validation will skip over the first line of the file.
-            See HedInput or EventsInput for examples of how to use built-in a ColumnMapper.
+            See SpreadsheetInput or TabularInput for examples of how to use built-in a ColumnMapper.
 
          """
         if mapper is None:
@@ -295,14 +295,10 @@ def iter_dataframe(self, mapper=None, return_row_dict=False, hed_ops=None, run_s
                                                       expand_defs=expand_defs, remove_definitions=remove_definitions,
                                                       error_handler=error_handler, **kwargs)
 
-        start_at_one = 1
-        if self._has_column_names:
-            start_at_one += 1
-
         # Iter tuples is ~ 25% faster compared to iterrows in our use case
         for row_number, text_file_row in enumerate(self._dataframe.itertuples(index=False)):
             error_handler.push_error_context(ErrorContext.ROW, row_number)
-            yield row_number + start_at_one, self._expand_row_internal(text_file_row, tag_funcs, string_funcs,
+            yield row_number, self._expand_row_internal(text_file_row, tag_funcs, string_funcs,
                                                                        error_handler=error_handler,
                                                                        mapper=mapper, return_row_dict=return_row_dict)
             error_handler.pop_error_context()
@@ -339,11 +335,7 @@ def get_assembled(self, row_number, expand_defs, shrink_defs, remove_definitions
                                            expand_defs=expand_defs, shrink_defs=shrink_defs,
                                            remove_definitions=remove_definitions, error_handler=error_handler)
 
-        adj_row_number = 1
-        if self._has_column_names:
-            adj_row_number += 1
-
-        text_file_row = self._dataframe.iloc[row_number - adj_row_number]
+        text_file_row = self._dataframe.iloc[row_number]
         return self._expand_row_internal(text_file_row, tag_funcs, None, error_handler=error_handler,
                                          mapper=mapper, return_row_dict=False)
 
@@ -371,10 +363,7 @@ def set_cell(self, row_number, column_number, new_string_obj, include_column_pre
             transform_func = self._mapper.get_prefix_remove_func(column_number)
 
         new_text = new_string_obj.get_as_form(tag_form, transform_func)
-        adj_row_number = 1
-        if self._has_column_names:
-            adj_row_number += 1
-        self._dataframe.iloc[row_number - adj_row_number, column_number - 1] = new_text
+        self._dataframe.iloc[row_number, column_number] = new_text
 
     def get_worksheet(self, worksheet_name=None):
         """ Get the requested worksheet from the workbook by name or the first worksheet if None.
diff --git a/hed/models/column_mapper.py b/hed/models/column_mapper.py
index 5094491c3..4506cfe2a 100644
--- a/hed/models/column_mapper.py
+++ b/hed/models/column_mapper.py
@@ -15,8 +15,7 @@ class ColumnMapper:
     """ Container class for mapping columns in event files into HED tags.
 
     Notes:
-        Private Functions and variables column and row indexing starts at 0.
-        Public functions and variables indexing starts at 1 (or 2 if has column names)
+        Functions and variables column and row indexing starts at 0.
 
     """
 
@@ -38,7 +37,7 @@ def __init__(self, sidecars=None, tag_columns=None, column_prefix_dictionary=Non
 
         Notes:
             Sidecars later in the list override those earlier in the list.
-
+            All column numbers are 0 based.
 
         Examples:
             column_prefix_dictionary = {3: 'Description/', 4: 'Label/'}
@@ -97,7 +96,7 @@ def set_column_prefix_dict(self, column_prefix_dictionary, finalize_mapping=True
 
         """
         if column_prefix_dictionary:
-            self._column_prefix_dictionary = self._subtract_1_from_dictionary_keys(column_prefix_dictionary)
+            self._column_prefix_dictionary = column_prefix_dictionary
         if finalize_mapping:
             return self._finalize_mapping()
         return []
@@ -115,12 +114,12 @@ def set_tag_columns(self, tag_columns=None, optional_tag_columns=None, finalize_
             list: List of issues that occurred during this process. Each issue is a dictionary.
 
         """
-        self._tag_columns = []
-        self._optional_tag_columns = []
-        if tag_columns:
-            self._tag_columns = self._subtract_1_from_list_elements(tag_columns)
-        if optional_tag_columns:
-            self._optional_tag_columns = self._subtract_1_from_list_elements(optional_tag_columns)
+        if tag_columns is None:
+            tag_columns = []
+        if optional_tag_columns is None:
+            optional_tag_columns = []
+        self._tag_columns = tag_columns
+        self._optional_tag_columns = optional_tag_columns
         if finalize_mapping:
             issues = self._finalize_mapping()
             return issues
@@ -131,14 +130,14 @@ def set_column_map(self, new_column_map=None):
 
         Args:
             new_column_map (list or dict):  Either an ordered list of the column names or column_number:column name
-                dictionary. In both cases column numbers start at 1.
+                dictionary. In both cases column numbers start at 0
 
         Returns:
             list: List of issues. Each issue is a dictionary.
 
         """
         if isinstance(new_column_map, dict):
-            column_map = self._subtract_1_from_dictionary_keys(new_column_map)
+            column_map = new_column_map
         # List like
         else:
             column_map = {column_number: column_name for column_number, column_name in enumerate(new_column_map)}
@@ -212,16 +211,16 @@ def expand_row_tags(self, row_text):
             translated_column, attribute_name_or_error = self._expand_column(column_number, str(cell_text))
             if translated_column is None:
                 if attribute_name_or_error:
-                    if column_number + 1 not in column_issues_dict:
-                        column_issues_dict[column_number + 1] = []
-                    column_issues_dict[column_number + 1] += attribute_name_or_error
-                    column_to_hed_tags_dictionary[column_number + 1] = translated_column
+                    if column_number not in column_issues_dict:
+                        column_issues_dict[column_number] = []
+                    column_issues_dict[column_number] += attribute_name_or_error
+                    column_to_hed_tags_dictionary[column_number] = translated_column
                 continue
             if attribute_name_or_error:
                 result_dict[attribute_name_or_error] = translated_column
                 continue
 
-            column_to_hed_tags_dictionary[column_number + 1] = translated_column
+            column_to_hed_tags_dictionary[column_number] = translated_column
 
         result_dict[model_constants.COLUMN_TO_HED_TAGS] = column_to_hed_tags_dictionary
         if column_issues_dict:
@@ -239,7 +238,6 @@ def get_prefix_remove_func(self, column_number):
             func: A function taking a tag and string, returning a string.
 
         """
-        column_number -= 1
         if column_number not in self._final_column_map:
             return None
 
@@ -270,7 +268,7 @@ def _set_column_prefix(self, column_number, new_required_prefix):
             new_required_prefix (str): The name_prefix to add to the column when loading from a spreadsheet.
 
         Raises:
-            TypeError if column number is passed as a str rather an an int.
+            TypeError if column number is passed as a str rather an int.
 
         Notes:
             If the column is not known to the mapper, it will be added as a HEDTags column.
@@ -324,7 +322,7 @@ def _finalize_mapping(self):
         for column_name, column_entry in self.column_data.items():
             if isinstance(column_name, int):
                 # Convert to internal numbering format
-                column_number = column_name - 1
+                column_number = column_name
                 self._final_column_map[column_number] = column_entry
 
         # Add any tag columns
@@ -366,7 +364,7 @@ def get_column_mapping_issues(self):
         return self._finalize_mapping_issues
 
     def validate_column_data(self, hed_ops, error_handler=None, **kwargs):
-        """ Validate all column definitions that are being used and column definition hed strings
+        """ Validate all column definitions that are being used and column definition hed strings.
 
         Args:
             hed_ops (list, func, or HedOps): A func, a HedOps or a list of these to apply to the
@@ -387,29 +385,3 @@ def validate_column_data(self, hed_ops, error_handler=None, **kwargs):
                                                                  **kwargs)
 
         return all_validation_issues
-
-    @staticmethod
-    def _subtract_1_from_dictionary_keys(int_key_dictionary):
-        """ Subtracts 1 from each dictionary key.
-
-        Args:
-            int_key_dictionary (dict): A dictionary with int keys.
-
-        Returns:
-            dict: A dictionary with the keys subtracted by 1.
-
-        """
-        return {key - 1: value for key, value in int_key_dictionary.items()}
-
-    @staticmethod
-    def _subtract_1_from_list_elements(int_list):
-        """ Subtracts 1 from each int in a list.
-
-        Args:
-            int_list (list): A list of ints.
-
-        Returns:
-            list: A list of ints where corresponding elements are 1 less than int_list.
-
-        """
-        return [x if isinstance(x, str) else x - 1 for x in int_list]
diff --git a/hed/models/column_metadata.py b/hed/models/column_metadata.py
index 61f5f9bb5..2fa446f11 100644
--- a/hed/models/column_metadata.py
+++ b/hed/models/column_metadata.py
@@ -413,7 +413,7 @@ def _validate_pound_sign_count(self, hed_string):
             return []
 
         # Make a copy without definitions to check placeholder count.
-        hed_string_copy = copy.copy(hed_string)
+        hed_string_copy = copy.deepcopy(hed_string)
         hed_string_copy.remove_definitions()
 
         if hed_string_copy.lower().count("#") != expected_pound_sign_count:
@@ -429,6 +429,7 @@ def extract_definitions(self, error_handler=None):
 
         Returns:
             DefinitionDict: Contains all the definitions located in the column.
+            issues: List of issues encountered in extracting the definitions. Each issue is a dictionary.
 
         """
         if error_handler is None:
diff --git a/hed/models/def_mapper.py b/hed/models/def_mapper.py
index 77f5c7785..fe8d95330 100644
--- a/hed/models/def_mapper.py
+++ b/hed/models/def_mapper.py
@@ -2,7 +2,7 @@
 from hed.models.hed_tag import HedTag
 from hed.models.definition_dict import DefinitionDict
 from hed.models.model_constants import DefTagNames
-from hed.errors.error_types import ValidationErrors
+from hed.errors.error_types import ValidationErrors, DefinitionErrors
 from hed.errors.error_reporter import ErrorHandler
 from hed.models.hed_ops import HedOps
 
@@ -20,7 +20,7 @@ def __init__(self, def_dicts=None):
         """ Initialize mapper for definitions in hed strings to fill them in with their values.
 
         Args:
-            def_dicts (list): DefDicts containing the definitions this mapper should initialize with.
+            def_dicts (list or DefDict): DefDicts containing the definitions this mapper should initialize with.
 
         Notes:
             More definitions can be added later.
@@ -32,10 +32,15 @@ def __init__(self, def_dicts=None):
         self._temporary_def_names = set()
         self._def_tag_name = DefTagNames.DEFINITION_KEY
         self._label_tag_name = DefTagNames.DEF_KEY
-
+        # this only gathers issues with duplicate definitions
+        self._issues = []
         if def_dicts:
             self.add_definitions(def_dicts)
 
+    @property
+    def issues(self):
+        return self._issues
+
     def get_def_entry(self, def_name):
         """ Get the definition entry for the definition name.
 
@@ -96,10 +101,10 @@ def _add_definitions_from_dict(self, def_dict, add_as_temp=False):
         """
         for def_tag, def_value in def_dict:
             if def_tag in self._gathered_defs:
-                # todo: add detection here possibly.  Right now the first definition found is used.
-                # This is a warning.  Errors from a duplicate definition in a single source will be reported
-                # by DefinitionDict
-                print(f"WARNING: Duplicate definition found for '{def_tag}'.")
+                error_context = self._gathered_defs[def_tag].source_context
+                self._issues += ErrorHandler.format_error_from_context(DefinitionErrors.DUPLICATE_DEFINITION,
+                                                                       error_context=error_context,
+                                                                       def_name=def_tag)
                 continue
             self._gathered_defs[def_tag] = def_value
             if add_as_temp:
@@ -186,35 +191,35 @@ def _get_definition_contents(self, def_tag, def_expand_group, def_issues):
             The contents to replace the previous def-tag with.
         """
         # todo: This check could be removed for optimizing
-        if def_tag.short_base_tag.lower() == DefTagNames.DEF_EXPAND_KEY or \
-                def_tag.short_base_tag.lower() == DefTagNames.DEF_KEY:
-            is_label_tag = def_tag.extension_or_value_portion
-            placeholder = None
-            found_slash = is_label_tag.find("/")
-            if found_slash != -1:
-                placeholder = is_label_tag[found_slash + 1:]
-                is_label_tag = is_label_tag[:found_slash]
-
-            label_tag_lower = is_label_tag.lower()
-            def_entry = self._gathered_defs.get(label_tag_lower)
-            if def_entry is None:
-                def_issues += ErrorHandler.format_error(ValidationErrors.HED_DEF_UNMATCHED, tag=def_tag)
-            else:
-                def_tag_name, def_contents = def_entry.get_definition(def_tag, placeholder_value=placeholder)
-                if def_tag_name:
-                    if def_expand_group is not def_tag and def_expand_group != def_contents:
-                        def_issues += ErrorHandler.format_error(ValidationErrors.HED_DEF_EXPAND_INVALID,
-                                                                tag=def_tag, actual_def=def_contents,
-                                                                found_def=def_expand_group)
-                        return None
-                    return def_contents
-                elif def_entry.takes_value:
-                    def_issues += ErrorHandler.format_error(ValidationErrors.HED_DEF_VALUE_MISSING, tag=def_tag)
-                else:
-                    def_issues += ErrorHandler.format_error(ValidationErrors.HED_DEF_VALUE_EXTRA, tag=def_tag)
-            pass
-        else:
+        if def_tag.short_base_tag.lower() != DefTagNames.DEF_EXPAND_KEY and \
+                def_tag.short_base_tag.lower() != DefTagNames.DEF_KEY:
             raise ValueError("Internal error in DefMapper")
+
+        is_label_tag = def_tag.extension_or_value_portion
+        placeholder = None
+        found_slash = is_label_tag.find("/")
+        if found_slash != -1:
+            placeholder = is_label_tag[found_slash + 1:]
+            is_label_tag = is_label_tag[:found_slash]
+
+        label_tag_lower = is_label_tag.lower()
+        def_entry = self._gathered_defs.get(label_tag_lower)
+        if def_entry is None:
+            def_issues += ErrorHandler.format_error(ValidationErrors.HED_DEF_UNMATCHED, tag=def_tag)
+        else:
+            def_tag_name, def_contents = def_entry.get_definition(def_tag, placeholder_value=placeholder)
+            if def_tag_name:
+                if def_expand_group is not def_tag and def_expand_group != def_contents:
+                    def_issues += ErrorHandler.format_error(ValidationErrors.HED_DEF_EXPAND_INVALID,
+                                                            tag=def_tag, actual_def=def_contents,
+                                                            found_def=def_expand_group)
+                    return None
+                return def_contents
+            elif def_entry.takes_value:
+                def_issues += ErrorHandler.format_error(ValidationErrors.HED_DEF_VALUE_MISSING, tag=def_tag)
+            else:
+                def_issues += ErrorHandler.format_error(ValidationErrors.HED_DEF_VALUE_EXTRA, tag=def_tag)
+
         return None
 
     def __get_string_funcs__(self, **kwargs):
diff --git a/hed/models/definition_dict.py b/hed/models/definition_dict.py
index 38ba5f4e5..06840ab8d 100644
--- a/hed/models/definition_dict.py
+++ b/hed/models/definition_dict.py
@@ -22,9 +22,9 @@ def __init__(self, name, contents, takes_value, source_context):
             source_context (dict): Info about where this definition was declared.
         """
         self.name = name
-        self.contents = contents
         if contents:
-            contents.cascade_mutable(False)
+            contents = contents.copy()
+        self.contents = contents
         self.takes_value = takes_value
         self.source_context = source_context
         self.tag_dict = {}
@@ -56,12 +56,10 @@ def get_definition(self, replace_tag, placeholder_value=None):
         if self.contents:
             output_group = self.contents
             if placeholder_value:
-                # todo: This part could be optimized more
+                output_group = copy.deepcopy(self.contents)
                 placeholder_tag = output_group.find_placeholder_tag()
                 if not placeholder_tag:
                     raise ValueError("Internal error related to placeholders in definition mapping")
-                output_group = copy.copy(self.contents)
-                placeholder_tag = output_group.make_tag_mutable(placeholder_tag)
                 name = f"{name}/{placeholder_value}"
                 placeholder_tag.replace_placeholder(placeholder_value)
 
@@ -75,14 +73,13 @@ def get_definition(self, replace_tag, placeholder_value=None):
 class DefinitionDict(HedOps):
     """Class responsible for gathering and storing a group of definitions to be considered a single source.
 
-        A bids_old style file might have many of these(one for each json dict, and another for the actual file).
-
         This class extends HedOps because it has string_funcs to check for definitions. It has no tag_funcs.
 
     """
 
     def __init__(self):
-        """ Initialize the class. gathering and storing a group of definitions to be considered a single source. """
+        """ Initialize the class, gathering and storing a group of definitions to be considered a single source. """
+
         super().__init__()
         self._defs = {}
 
@@ -94,16 +91,16 @@ def get_definition_issues(self):
 
         Returns:
             list: List of DefinitionErrors issues found. Each issue is a dictionary.
+
         """
         return self._extract_def_issues
 
     @property
     def defs(self):
-        """
-            Provides direct access to internal dictionary.  Alter at your own risk.
-        Returns
-        -------
-        def_dict: {str: DefinitionEntry}
+        """ Provides direct access to internal dictionary.  Alter at your own risk.
+
+        Returns:
+            dict: {str: DefinitionEntry}
         """
         return self._defs
 
@@ -118,7 +115,7 @@ def __get_tag_funcs__(self, **kwargs):
         return []
 
     def check_for_definitions(self, hed_string_obj, error_handler=None):
-        """ Check a given HedString for definition tags, and add them to the dictionary if so.
+        """ Check a given HedString for definition tags and add them to the dictionary if so.
 
         Args:
             hed_string_obj (HedString): A single hed string to gather definitions from.
diff --git a/hed/models/expression_parser.py b/hed/models/expression_parser.py
index 5f00bf47f..2ded4749a 100644
--- a/hed/models/expression_parser.py
+++ b/hed/models/expression_parser.py
@@ -227,7 +227,7 @@ def _parse(self, expression_string):
 
     def _tokenize(self, expression_string):
         grouping_re = r"\[\[|\[|,|\]\]|\]"
-        paren_re = "\)|\(|~"
+        paren_re = r"\)|\(|~"
         word_re = r"\band\b|\bor\b|[_\-a-zA-Z0-9/.^#]+"
         re_string = fr"({grouping_re}|{paren_re}|{word_re})"
         token_re = re.compile(re_string)
diff --git a/hed/models/hed_group.py b/hed/models/hed_group.py
index 487089228..f8242d725 100644
--- a/hed/models/hed_group.py
+++ b/hed/models/hed_group.py
@@ -1,6 +1,6 @@
 from hed.models.hed_tag import HedTag
-import copy
 from hed.models.hed_group_base import HedGroupBase
+import copy
 
 
 class HedGroup(HedGroupBase):
@@ -23,34 +23,11 @@ def __init__(self, hed_string="", startpos=None, endpos=None, contents=None):
         super().__init__(hed_string=hed_string, startpos=startpos, endpos=endpos)
         if contents:
             self._children = contents
+            for child in self._children:
+                child._parent = self
         else:
             self._children = []
         self._original_children = self._children
-        self.mutable = True  # If False, this group is potentially referenced in other places and should not be altered
-
-    def __eq__(self, other):
-        """ Test if the same object or a HedGroup with same contents.
-
-            Args:
-                other (object):  Any object.
-
-            Returns:
-                bool:  True if other is the same as this HedGroup or is a HedGroup with the same contents.
-
-        TODO: check to see if can be moved to HedGroupBase.
-
-        """
-
-        if self is other:
-            return True
-        if not isinstance(other, HedGroup):
-            return False
-
-        if self.children != other.children:
-            return False
-        if self.is_group != other.is_group:
-            return False
-        return True
 
     def append(self, tag_or_group):
         """ Add a tag or group to this group.
@@ -62,9 +39,7 @@ def append(self, tag_or_group):
             ValueError: If a HedGroupFrozen.
 
         """
-        if not self.mutable:
-            raise ValueError("Trying to alter immutable group")
-
+        tag_or_group._parent = self
         self._children.append(tag_or_group)
 
     def check_if_in_original(self, tag_or_group):
@@ -96,16 +71,9 @@ def replace(self, item_to_replace, new_contents):
         """ Replace an existing tag or group in the group with a new tag, list, or group.
 
         Args:
-            item_to_replace (HedTag or HedGroup): The item to  replace must exist or this will raise an error.
+            item_to_replace (HedTag or HedGroup): The item to replace must exist or this will raise an error.
             new_contents (HedTag, HedGroup or list of HedTag and/or HedGroup): Replacement contents.
-
-        Raises:
-            ValueError:  If this HedGroup is not mutable.
-
         """
-        if not self.mutable:
-            raise ValueError("Trying to alter immutable group")
-
         if self._original_children is self._children:
             self._original_children = self._children.copy()
 
@@ -115,43 +83,10 @@ def replace(self, item_to_replace, new_contents):
                 replace_index = i
                 break
         self._children[replace_index] = new_contents
-
-    def __copy__(self):
-        """ Return a shallow mutable copy with a new _children list, rather than the same list.
-
-        Returns:
-            HedGroup:  A mutable shallow copy of this HedGroup.
-
-        Notes:
-            The child HedTags and HedGroups are the same objects (similar to list copy).
-            This operation is mainly used for handling definitions.
-
-        TODO: Check to see if can be moved to HedGroupBase.
-
-        """
-        cls = self.__class__
-        result = cls.__new__(cls)
-        result.__dict__.update(self.__dict__)
-        result._children = self._children.copy()
-        result.mutable = True
-        return result
-
-    def cascade_mutable(self, value):
-        """ Set the mutable property for all child tags and groups.
-
-        Args:
-            value (bool):  True if all should be mutable, False if not.
-
-        """
-        self.mutable = value
-        for child in self._children:
-            if isinstance(child, HedGroup):
-                child.cascade_mutable(value)
-            else:
-                child.mutable = value
+        new_contents._parent = self
 
     def remove(self, items_to_remove):
-        """Remove any tags/groups in items_to_remoe found in this HedGroup.
+        """ Remove any tags/groups in items_to_remove found in this HedGroup.
 
         Args:
             items_to_remove (list):  List of HedGroups and/or HedTags to remove.
@@ -168,22 +103,14 @@ def _remove(self, items_to_remove, all_groups):
         """ Needs to be documented.
 
         Args:
-            items_to_remove ( ):
-            all_groups ( ):
-
-        Returns:
-
-        Raise:
-            ValueError: ?
-
+            items_to_remove (list):  List of HedGroups and/or HedTags to remove.
+            all_groups (list):   List of HedGroups.
         """
         empty_groups = []
         for remove_child in items_to_remove:
             for group in all_groups:
                 # only proceed if we have an EXACT match for this child
                 if any(remove_child is child for child in group._children):
-                    if not group.mutable:
-                        raise ValueError("Trying to alter immutable group")
                     if group._original_children is group._children:
                         group._original_children = group._children.copy()
 
@@ -194,33 +121,7 @@ def _remove(self, items_to_remove, all_groups):
                     break
 
         if empty_groups:
-            return self.remove(empty_groups)
-
-    def make_tag_mutable(self, tag):
-        """ Replace the given tag in this group with a tag that can be altered and return new version.
-
-        Args:
-            tag (HedTag): The tag to make mutable.
-
-        Returns:
-            HedTag: A mutable new copy of the tag, or the old mutable one.
-
-        Raises:
-            ValueError:  If this HedGroup is not already mutable, the operation fails.
-
-        Notes:
-            This method is more or less exclusively for definition expansion.
-
-        """
-        if not self.mutable:
-            raise ValueError("Trying to alter immutable group")
-
-        if not tag.mutable:
-            tag_copy = copy.copy(tag)
-            tag_copy.mutable = True
-            self.replace(tag, tag_copy)
-            return tag_copy
-        return tag
+            self.remove(empty_groups)
 
     def get_frozen(self):
         """ Returns a frozen (non-mutable) copy of this HedGroup.
@@ -252,6 +153,8 @@ def __init__(self, contents, hed_string=None):
                 If a list, may be a mixture of HedTags and HedGroups. Use the hed_string parameter as the source.
             hed_string (str or None): If contents is a list, this is the raw string the contents came from.
 
+        Notes
+            This makes a complete copy of the HedString.
         """
         if isinstance(contents, HedGroupBase):
             span = contents.span
@@ -268,9 +171,9 @@ def __init__(self, contents, hed_string=None):
 
         super().__init__(hed_string=hed_string, startpos=span[0], endpos=span[1])
         if contents is not None:
-            self._children = frozenset(child if not isinstance(child, HedGroupBase)
+            self._children = frozenset(copy.copy(child) if not isinstance(child, HedGroupBase)
                                        else HedGroupFrozen(child) for child in contents)
-            for child in self.groups():
+            for child in self._children:
                 child._parent = self
         else:
             self._children = None
@@ -280,24 +183,6 @@ def __hash__(self):
         """ Get a hash of this HedFrozenGroup."""
         return hash((self._children, self.is_group))
 
-    def __eq__(self, other):
-        """
-        TODO:  Put in the HedGroupBase
-
-        """
-        if self is other:
-            return True
-
-        if not isinstance(other, HedGroupFrozen):
-            return False
-
-        if self._children != other._children:
-            return False
-
-        if self.is_group != other.is_group:
-            return False
-        return True
-
     def get_all_tags(self):
         """ Return all the HedTags in this group, including descendants.
 
diff --git a/hed/models/hed_group_base.py b/hed/models/hed_group_base.py
index e23068194..5a9eaa26e 100644
--- a/hed/models/hed_group_base.py
+++ b/hed/models/hed_group_base.py
@@ -1,3 +1,5 @@
+import copy
+
 from hed.models.hed_tag import HedTag
 
 
@@ -16,11 +18,27 @@ def __init__(self, hed_string="", startpos=None, endpos=None):
         self._startpos = startpos
         self._endpos = endpos
         self._hed_string = hed_string
-        self.mutable = True  # If False, this group is potentially referenced in other places and should not be altered
+        self._parent = None
 
         # placeholder just to make IDE not complain
         self._children = None
 
+    def __copy__(self):
+        raise ValueError("Cannot make shallow copies of HedGroups")
+
+    def copy(self):
+        """
+            Return a deep copy of this group, with the parent pointer removed.
+
+        Returns:
+            copied_group (HedGroupBase): The copied group
+        """
+        save_parent = self._parent
+        self._parent = None
+        return_copy = copy.deepcopy(self)
+        self._parent = save_parent
+        return return_copy
+
     @property
     def children(self):
         """ Return a list of the direct children of this string. """
@@ -204,6 +222,23 @@ def find_placeholder_tag(self):
     def __bool__(self):
         return bool(self._children)
 
+    def __eq__(self, other):
+        """
+
+        """
+        if self is other:
+            return True
+
+        if not isinstance(other, HedGroupBase):
+            return False
+
+        if self.children != other.children:
+            return False
+
+        if self.is_group != other.is_group:
+            return False
+        return True
+
     def find_tags(self, search_tags, recursive=False, include_groups=2):
         """ Find the search tags and their containing groups that are in this group.
 
diff --git a/hed/models/hed_string.py b/hed/models/hed_string.py
index 1f1a051d1..5f40a049b 100644
--- a/hed/models/hed_string.py
+++ b/hed/models/hed_string.py
@@ -32,7 +32,6 @@ def __init__(self, hed_string, hed_schema=None):
             contents = self.split_into_groups(hed_string, hed_schema)
         except ValueError:
             contents = []
-
         super().__init__(hed_string, contents=contents, startpos=0, endpos=len(hed_string))
 
     @property
diff --git a/hed/models/hed_string_comb.py b/hed/models/hed_string_comb.py
index 1c0998716..3f22ead67 100644
--- a/hed/models/hed_string_comb.py
+++ b/hed/models/hed_string_comb.py
@@ -5,21 +5,22 @@
 
 
 class HedStringComb(HedString):
-    """Represents a hed string made from other hed strings(generally multiple columns)."""
+    """A hed string object made from other hed string objects(generally multiple columns)."""
 
     def __init__(self, hed_string_obj_list):
-        """Constructor for the HedStringComb class.
+        """ Constructor for the HedStringComb class.
 
-        Parameters
-        ----------
-        hed_string_obj_list: [HedString]
-            A list of component HedStrings for this combined
+        Args:
+            hed_string_obj_list ([HedString]): A list of component HedStrings for this combined string.
 
-        Returns
-        -------
         """
         super().__init__("")
         self._children = list(hed_string for hed_string in hed_string_obj_list if hed_string is not None)
+        # Update the direct children to point to this combined string, rather than their original string
+        for child in self._children:
+            for sub_child in child.children:
+                sub_child._parent = self
+
         self._original_children = self._children
 
     def get_original_hed_string(self):
@@ -75,9 +76,6 @@ def replace(self, item_to_replace, new_contents):
         new_contents : HedTag or HedGroup or [HedTag or HedGroup]
             What to replace the tag with.
         """
-        if not self.mutable:
-            raise ValueError("Trying to alter immutable group")
-
         # this needs to pass the tag off to the appropriate group
         replace_sub_string = None
         for sub_string in self._children:
@@ -89,11 +87,10 @@ def replace(self, item_to_replace, new_contents):
         replace_sub_string.replace(item_to_replace, new_contents)
 
     def __copy__(self):
-        """
-            Returns a copy of this combined string converted to HedString
+        """ Return a copy of this combined string converted to HedString.
 
-        Returns
-        -------
+        Returns:
+            HedString: The combined string converted.
 
         """
         # When we copy a combined string, just turn it into a normal string.
@@ -102,7 +99,6 @@ def __copy__(self):
         result._startpos, result._endpos = self.span
         result._hed_string = self.get_original_hed_string()
         result._children = self.children.copy()
-        result.mutable = True
         return result
 
     def _get_org_span(self, tag_or_group):
diff --git a/hed/models/hed_tag.py b/hed/models/hed_tag.py
index 1a6460853..f28ffbc72 100644
--- a/hed/models/hed_tag.py
+++ b/hed/models/hed_tag.py
@@ -39,10 +39,7 @@ def __init__(self, hed_string, span=None, hed_schema=None):
         self._schema_entry = None
 
         self._extension_value = ""
-
-        # Note this only keeps track of tags also used in definitions.  You should also not
-        # alter any tags used in a HedGroupFrozen
-        self.mutable = True
+        self._parent = None
 
         if hed_schema:
             self.convert_to_canonical_forms(hed_schema)
@@ -120,8 +117,6 @@ def short_base_tag(self, new_tag_val):
         -------
 
         """
-        if not self.mutable:
-            raise ValueError("Trying to alter immutable tag")
         if self._schema_entry:
             if self._schema:
                 tag_entry = self._schema.get_tag_entry(new_tag_val, library_prefix=self.library_prefix)
@@ -201,8 +196,6 @@ def tag(self, new_tag_val):
         new_tag_val : str
             New (implicitly long form) of tag to set
         """
-        if not self.mutable:
-            raise ValueError("Trying to alter immutable tag")
 
         if self._schema_entry:
             raise ValueError("Can only edit tags before calculating canonical forms. " +
@@ -312,9 +305,6 @@ def add_prefix_if_not_present(self, required_prefix):
 
         """
 
-        # if not self.mutable:
-        #     raise ValueError("Trying to alter immutable tag")
-
         checking_prefix = required_prefix
         while checking_prefix:
             if self.lower().startswith(checking_prefix.lower()):
@@ -664,9 +654,6 @@ def replace_placeholder(self, placeholder_value):
         placeholder_value : str
             Value to replace placeholder with.
         """
-        # if not self.mutable:
-        #     raise ValueError("Trying to alter immutable tag")
-
         if "#" in self.org_tag:
             if self._schema_entry:
                 self._extension_value = self._extension_value.replace("#", placeholder_value)
diff --git a/hed/models/sidecar.py b/hed/models/sidecar.py
index 2d9ac2e1a..3844f2c43 100644
--- a/hed/models/sidecar.py
+++ b/hed/models/sidecar.py
@@ -9,40 +9,36 @@
 
 
 class Sidecar:
-    """ Contents of a single JSON file with definition dictionaries."""
+    """ Contents of a single JSON file with definition dictionaries. """
 
     def __init__(self, file, name=None):
-        """
+        """ Constructs a Sidecar object representing a JSON file.
+
+        Args:
+            file (str or FileLike): A string or file-like object representing a JSON file.
+            name (str or None): Optional name identifying this sidecar, generally a filename.
 
-        Parameters
-        ----------
-        file: str or FileLike
-            If a string, this is a filename.
-            Otherwise, it will be parsed as a file-like.
-        name: str or None
-            Optional name to identify this group.  Generally a filename.
         """
         self._column_data = {}
         self.name = name
         if file:
-            self.add_sidecar_file(file)
+            self.load_sidecar_file(file)
 
     def __iter__(self):
-        """
-            Creates an iterator to go over the individual column metadata
-        Returns
-        -------
-        column_data: iterator
+        """ An iterator to go over the individual column metadata.
+
+        Returns:
+            iterator: An iterator over the column metadata values.
+
         """
         return iter(self._column_data.values())
 
     def save_as_json(self, save_filename):
-        """
-            Saves out a column definition group to a json file
-        Parameters
-        ----------
-        save_filename : str
-            Path to save file
+        """ Save column metadata to a JSON file.
+
+        Args:
+            save_filename (str): Path to save file
+
         """
         output_dict = {}
         for entry in self._column_data.values():
@@ -51,10 +47,10 @@ def save_as_json(self, save_filename):
             json.dump(output_dict, fp, indent=4)
 
     def get_as_json_string(self):
-        """ Return this entire column definition group as a json string.
+        """ Return this sidecar's column metadata as a string.
 
         Returns:
-            str: The json string representing this column definition group.
+            str: The json string representing this sidecar.
 
         """
         output_dict = {}
@@ -62,72 +58,68 @@ def get_as_json_string(self):
             output_dict[entry.column_name] = entry.hed_dict
         return json.dumps(output_dict, indent=4)
 
-    def add_sidecar_file(self, file):
-        """
-            Loads column definitions from a given json file.
-            You can load multiple files into one Sidecar, but it is discouraged.
-
-        Parameters
-        ----------
-        file: str or FileLike
-            If a string, this is a filename.
-            Otherwise, it will be parsed as a file-like.
+    def load_sidecar_file(self, file):
+        """ Load column metadata from a given json file.
+
+        Args:
+            file (str or FileLike): If a string, this is a filename. Otherwise, it will be parsed as a file-like.
+
+        Raises:
+            HedFileError: If the file was not found or could not be parsed into JSON.
+
+        Notes:
+             You can load multiple files into one Sidecar, but it is discouraged.
+
         """
         if isinstance(file, str):
             try:
                 with open(file, "r") as fp:
                     if not self.name:
                         self.name = file
-                    self._add_json_file_defs(fp)
+                    self._load_json_columns(fp)
             except FileNotFoundError as e:
                 raise HedFileError(HedExceptions.FILE_NOT_FOUND, e.strerror, file)
             except TypeError as e:
                 raise HedFileError(HedExceptions.FILE_NOT_FOUND, str(e), file)
         else:
-            self._add_json_file_defs(file)
+            self._load_json_columns(file)
 
-    def _add_json_file_defs(self, fp):
-        """
-            Takes a file like and parses it as json.
+    def _load_json_columns(self, fp):
+        """ Parse a JSON file into columns and load in the column entry dictionary.
 
-        Parameters
-        ----------
-        fp : file like
-            The source for the json.
+        Args:
+            fp (File-like): The JSON source stream.
 
-        Returns
-        -------
+        Raises:
+            HedFileError:  If the file cannot be parsed.
 
         """
         try:
             loaded_defs = json.load(fp)
-            for col_def, col_dict in loaded_defs.items():
-                self._add_single_col_type(col_def, col_dict)
+            for col_name, col_dict in loaded_defs.items():
+                self._add_single_column(col_name, col_dict)
         except json.decoder.JSONDecodeError as e:
             raise HedFileError(HedExceptions.CANNOT_PARSE_JSON, str(e), self.name)
 
     @staticmethod
-    def load_multiple_sidecars(json_file_input_list):
-        """
-            Utility function for easily loading multiple json files at once
+    def load_multiple_sidecars(input_list):
+        """ Utility function for easily loading multiple json files at once
             This takes a list of filenames or ColumnDefinitionGroups and returns a list of ColumnDefinitionGroups.
 
             Note: it will completely fail and raise a HedFileError if any of the files are not found.
 
-        Parameters
-        ----------
-        json_file_input_list : [str or ColumnDefinitionGroup]
-            A list of filenames or loaded files in any mix
-        Returns
-        -------
-        sidecars: [Sidecar]
-            A list of all input files, loaded into column definition groups if needed.
+        Args:
+            input_list (list): A list of filenames or Sidecar files in any mix. [
+
+        Returns:
+            list: A list sidecars.
+
         """
-        if not isinstance(json_file_input_list, list):
-            json_file_input_list = [json_file_input_list]
+        if not isinstance(input_list, list):
+            input_list = [input_list]
 
         loaded_files = []
-        for json_file in json_file_input_list:
+        for json_file in input_list:
             if isinstance(json_file, str):
                 json_file = Sidecar(json_file)
             loaded_files.append(json_file)
@@ -135,46 +127,32 @@ def load_multiple_sidecars(json_file_input_list):
 
     def hed_string_iter(self, hed_ops=None, error_handler=None, expand_defs=False, remove_definitions=False,
                         allow_placeholders=True, extra_def_dicts=None, **kwargs):
-        """
-        Return iterator to loop over all hed strings in all column definitions
+        """ Get an iterator to loop over all hed strings in all column definitions.
 
         Returns a tuple of (string, position)
         Pass position to set_hed_string to change one.
 
-        Parameters
-        ----------
-        hed_ops : [func or HedOps] or func or HedOps
-            A list of HedOps of funcs to apply to the hed strings before returning
-        error_handler : ErrorHandler
-            The error handler to use for context, uses a default one if none.
-        expand_defs: bool
-            If True, expand all def tags located in the strings.
-        remove_definitions: bool
-            If True, remove all definitions found in the string.
-        allow_placeholders: bool
-            If False, placeholders will be marked as validation warnings.
-        extra_def_dicts: [DefinitionDict] or DefinitionDict or None
-            Extra dicts to add to the list
-        kwargs:
-            See models.hed_ops.translate_ops or the specific hed_ops for additional options
-
-        Yields
-        ------
-        hed_string : HedString
-            HedString at a given column and key position
-        position: tuple
-            Indicates where hed_string was loaded from so it can be later set by the user
-        issues: []
-            A list of issues found performing ops.
+        Args:
+            hed_ops (func, HedOps, list): A HedOps, funcs or list of these to apply to the hed strings
+                before returning
+            error_handler (ErrorHandler): The error handler to use for context, uses a default one if none.
+            expand_defs (bool): If True, expand all def tags located in the strings.
+            remove_definitions (bool): If True, remove all definitions found in the string.
+            allow_placeholders (bool): If False, placeholders will be marked as validation warnings.
+            extra_def_dicts (DefinitionDict, list, None): Extra dicts to add to the list.
+            kwargs: See models.hed_ops.translate_ops or the specific hed_ops for additional options.
+
+        Yields:
+            HedString: A HedString at a given column and key position.
+            (tuple): Indicates where hed_string was loaded from so it can be later set by the user
+            list: A list of issues found performing ops. Each issue is a dictionary.
+
         """
         if error_handler is None:
             error_handler = ErrorHandler()
+        hed_ops = self._standardize_ops(hed_ops)
         if expand_defs or remove_definitions:
-            hed_ops = self._add_definition_mapper(hed_ops, extra_def_dicts)
-        else:
-            if not isinstance(hed_ops, list):
-                hed_ops = [hed_ops]
-            hed_ops = hed_ops.copy()
+            self._add_definition_mapper(hed_ops, extra_def_dicts)
         for column_name, entry in self._column_data.items():
             error_handler.push_error_context(ErrorContext.SIDECAR_COLUMN_NAME, column_name)
             for (hed_string_obj, position, issues) in entry.hed_string_iter(hed_ops=hed_ops,
@@ -188,57 +166,63 @@ def hed_string_iter(self, hed_ops=None, error_handler=None, expand_defs=False, r
             error_handler.pop_error_context()
 
     def set_hed_string(self, new_hed_string, position):
-        """Set a hed string in a provided column/category key/etc
-
-        Parameters
-        ----------
-        new_hed_string : str or HedString
-            The new hed_string to replace the value at position.
-        position : tuple
-            This should only be a value returned from hed_string_iter
+        """ Set a hed string in a provided column/category key/etc.
+
+        Args:
+            new_hed_string (str or HedString): The new hed_string to replace the value at position.
+            position (tuple):   The (HedString, str, list) tuple returned from hed_string_iter.
+
         """
         column_name, position = position
         entry = self._column_data[column_name]
         entry.set_hed_string(new_hed_string, position)
 
     def _add_definition_mapper(self, hed_ops, extra_def_dicts=None):
-        if not isinstance(hed_ops, list):
-            hed_ops = [hed_ops]
-        hed_ops = hed_ops.copy()
+        """ Add a DefMapper if the hed_ops list doesn't have one.
+
+        Args:
+            hed_ops (list):  A list of HedOps
+            extra_def_dicts (list):  DefDicts from outside.
+
+        Returns:
+            DefMapper:  A shallow copy of the hed_ops list with a DefMapper added if there wasn't one.
+
+        """
         if not any(isinstance(hed_op, DefMapper) for hed_op in hed_ops):
             def_dicts = self.get_def_dicts(extra_def_dicts)
             def_mapper = DefMapper(def_dicts)
             hed_ops.append(def_mapper)
-        return hed_ops
+            return def_mapper
+        return None
+
+    @staticmethod
+    def _standardize_ops(hed_ops):
+        if not isinstance(hed_ops, list):
+            hed_ops = [hed_ops]
+        return hed_ops.copy()
+
+    def _add_single_column(self, column_name, dict_for_entry, column_type=None):
+        """ Create a single column metadata entry and add to this sidecar.
+
+        Args:
+            column_name (str or int): The column name or number
+            dict_for_entry (dict): The loaded dictionary for a given column entry (needs the "HED" key if nothing else).
+            column_type (ColumnType): Optional indicator of how to treat the column.
+                This overrides auto-detection from the dict_for_entry.
 
-    def _add_single_col_type(self, column_name, dict_for_entry, column_type=None):
-        """
-        Creates a single column definition with the given parameters.
-
-        Parameters
-        ----------
-        column_name : str or int
-            The column name or number
-        dict_for_entry : dict
-            The loaded dictionary for a given column entry.  Generally needs the "HED" key if nothing else.
-        column_type : ColumnType, optional
-            How it should treat this column.  This overrides auto-detection from the dict_for_entry.
         """
         column_entry = ColumnMetadata(column_type, column_name, dict_for_entry)
         self._column_data[column_name] = column_entry
 
     def get_def_dicts(self, extra_def_dicts=None):
-        """
-            Returns a list of def dicts for each column in this sidecar.
-
-        Parameters
-        ----------
-        extra_def_dicts: [DefinitionDict] or DefinitionDict or None
-            Extra dicts to add to the list
-        Returns
-        -------
-        def_dicts: [DefinitionDict]
-            A list of def dicts for each column plus any found in extra_def_dicts.
+        """ Return a list of DefinitionDict for the columns in this sidecar.
+
+        Args:
+            extra_def_dicts (list, DefinitionDict, or None): Extra dicts to add to the list.
+
+        Returns:
+            list: A list of definition dicts for each column plus any found in extra_def_dicts.
+
         """
         def_dicts = [column_entry.def_dict for column_entry in self]
         if extra_def_dicts:
@@ -249,26 +233,19 @@ def get_def_dicts(self, extra_def_dicts=None):
 
     def validate_entries(self, hed_ops=None, name=None, extra_def_dicts=None,
                          error_handler=None, **kwargs):
-        """Run the given hed_ops on all columns in this sidecar
-
-        Parameters
-        ----------
-
-        hed_ops : [func or HedOps] or func or HedOps
-            A list of HedOps of funcs to apply to the hed strings in this sidecar.
-        name: str
-            If present, will use this as the filename for context, rather than using the actual filename
-            Useful for temp filenames.
-        extra_def_dicts: [DefinitionDict] or DefinitionDict or None
-            If present, also use these in addition to the sidecars def dicts.
-        error_handler : ErrorHandler or None
-            Used to report errors.  Uses a default one if none passed in.
-        kwargs:
-            See models.hed_ops.translate_ops or the specific hed_ops for additional options
-        Returns
-        -------
-        validation_issues: [{}]
-            The list of validation issues found
+        """ Run the given hed_ops on all columns in this sidecar.
+
+        Args:
+            hed_ops (list, func, or HedOps): A HedOps, func or list of these to apply to hed strings in this sidecar.
+            name (str): If present, will use this as the filename for context, rather than using the actual filename
+                Useful for temp filenames.
+            extra_def_dicts: (DefinitionDict, list, or None): If present use these in addition to sidecar's def dicts.
+            error_handler (ErrorHandler or None): Used to report errors.  Uses a default one if none passed in.
+            kwargs: See models.hed_ops.translate_ops or the specific hed_ops for additional options.
+
+        Returns:
+            list: The list of validation issues found. Individual issues are in the form of a dict.
+
         """
         if error_handler is None:
             error_handler = error_reporter.ErrorHandler()
@@ -277,13 +254,33 @@ def validate_entries(self, hed_ops=None, name=None, extra_def_dicts=None,
         if name:
             error_handler.push_error_context(ErrorContext.FILE_NAME, name, False)
 
-        hed_ops = self._add_definition_mapper(hed_ops, extra_def_dicts)
+        hed_ops = self._standardize_ops(hed_ops)
+        def_mapper = self._add_definition_mapper(hed_ops, extra_def_dicts)
 
         all_validation_issues = []
+        all_validation_issues += def_mapper.issues
         for column_data in self:
-            all_validation_issues += column_data.validate_column(hed_ops,
-                                                                 error_handler=error_handler,
-                                                                 **kwargs)
+            all_validation_issues += column_data.validate_column(hed_ops, error_handler=error_handler, **kwargs)
         if name:
             error_handler.pop_error_context()
         return all_validation_issues
+
+
+if __name__ == '__main__':
+    import os
+    from hed.validator.hed_validator import HedValidator
+    from hed.schema.hed_schema_io import load_schema, load_schema_version
+    base_dir= 'D:/Research/HED/hed-examples/datasets/eeg_ds003654s_hed_inheritance'
+    root_path1 = os.path.realpath(os.path.join(base_dir, 'task-FacePerception_events.json'))
+    root_path2 = os.path.realpath(os.path.join(base_dir, 'sub-002/sub-002_task-FacePerception_events.json'))
+
+    sidecar1 = Sidecar(root_path1, name="sidecar1")
+    sidecar2 = Sidecar(root_path2, name="sidecar2")
+    hed_schema = load_schema_version(xml_version="8.0.0")
+    validator = HedValidator(hed_schema=hed_schema)
+    issues1 = sidecar1.validate_entries([validator], check_for_warnings=True)
+    issues2 = sidecar2.validate_entries([validator], check_for_warnings=True)
+
+    sidecar2.add_inherited_columns([sidecar1])
+    issues3 = sidecar2.validate_entries([validator], check_for_warnings=True)
+    print("to here")
\ No newline at end of file
diff --git a/hed/models/hed_input.py b/hed/models/spreadsheet_input.py
similarity index 76%
rename from hed/models/hed_input.py
rename to hed/models/spreadsheet_input.py
index e2914bc91..998aeeefc 100644
--- a/hed/models/hed_input.py
+++ b/hed/models/spreadsheet_input.py
@@ -3,13 +3,13 @@
 from hed.models.def_mapper import DefMapper
 
 
-class HedInput(BaseInput):
+class SpreadsheetInput(BaseInput):
     """ Class representing a spreadsheet of HED tags."""
 
     def __init__(self, file=None, file_type=None, worksheet_name=None, tag_columns=None,
                  has_column_names=True, column_prefix_dictionary=None,
                  def_dicts=None, name=None):
-        """Constructor for the HedInput class.
+        """Constructor for the SpreadsheetInput class.
 
         Args:
             file (str or file like): An xlsx/tsv file to open or a File object.
@@ -22,7 +22,7 @@ def __init__(self, file=None, file_type=None, worksheet_name=None, tag_columns=N
                 first line of the file if the spreadsheet as column names.
             column_prefix_dictionary (dict): A dictionary with column number keys and prefix values.
             def_dicts (DefinitionDict or list):  A DefinitionDict or list of DefDicts containing definitions for this
-                object other than the ones extracted from the HedInput object itself.
+                object other than the ones extracted from the SpreadsheetInput object itself.
 
         Examples:
             A prefix dictionary {3: 'Label/', 5: 'Description/'} indicates that column 3 and 5 have HED tags
@@ -31,7 +31,7 @@ def __init__(self, file=None, file_type=None, worksheet_name=None, tag_columns=N
 
         """
         if tag_columns is None:
-            tag_columns = [2]
+            tag_columns = [1]
         if column_prefix_dictionary is None:
             column_prefix_dictionary = {}
 
@@ -41,3 +41,13 @@ def __init__(self, file=None, file_type=None, worksheet_name=None, tag_columns=N
 
         super().__init__(file, file_type, worksheet_name, has_column_names, new_mapper, def_mapper=def_mapper,
                          name=name)
+
+
+if __name__ == '__main__':
+    import os
+    from hed.schema import load_schema_version
+    path1 = os.path.realpath('H:/HEDPython/hed-web/tests/data/ExcelMultipleSheets.xlsx')
+    base1 = SpreadsheetInput(path1, tag_columns=[4], worksheet_name='LKT 8HED3')
+    hed_schema = load_schema_version(xml_version='8.0.0')
+    issues = base1.convert_to_long(hed_schema=hed_schema)
+    base1.to_excel('D:/temp.xlsx', output_processed_file=True)
\ No newline at end of file
diff --git a/hed/models/events_input.py b/hed/models/tabular_input.py
similarity index 54%
rename from hed/models/events_input.py
rename to hed/models/tabular_input.py
index 16ec5e04b..ea287cce5 100644
--- a/hed/models/events_input.py
+++ b/hed/models/tabular_input.py
@@ -4,40 +4,36 @@
 from hed.models.def_mapper import DefMapper
 
 
-class EventsInput(BaseInput):
-    """ Represents a BIDS tsv file."""
+class TabularInput(BaseInput):
+    """ Represents a BIDS tabular tsv file."""
 
     HED_COLUMN_NAME = "HED"
 
-    def __init__(self, file=None, sidecars=None, attribute_columns=None, extra_def_dicts=None,
+    def __init__(self, file=None, sidecar=None, attribute_columns=None, extra_def_dicts=None,
                  also_gather_defs=True, name=None):
-        """Constructor for the EventsInput class.
+        """ Constructor for the TabularInput class.
+
+        Args:
+            file (str or file like): A tsv file to open.
+            sidecar (str or Sidecar): A Sidecar filename or Sidecar
+            attribute_columns (str or int or [str] or [int]): A list of column names or numbers to treat as attributes.
+                Default: ["duration", "onset"]
+            extra_def_dicts ([DefinitionDict], DefinitionDict, or None): DefinitionDict objects containing all
+                the definitions this file should use other than the ones coming from the file
+                itself and from the sidecar.  These are added as the last entries, so names will override
+                earlier ones.
+            also_gather_defs (bool): If False, do NOT extract any definitions from column groups,
+                assume they are already in the def_dict list.
+            name (str): The name to display for this file for error purposes.
 
-        Parameters
-        ----------
-         file: str or file like
-             An xlsx/tsv file to open.
-        sidecars : str or [str] or Sidecar or [Sidecar]
-            A list of json files to pull column metadata from
-        attribute_columns: str or int or [str] or [int]
-            A list of column names or numbers to treat as attributes.
-            Default: ["duration", "onset"]
-        extra_def_dicts: [DefinitionDict] or DefinitionDict or None
-            DefinitionDict objects containing all the definitions this file should use other than the ones coming from the file
-            itself and from the column def groups.  These are added as the last entries, so names will override
-            earlier ones.
-        also_gather_defs: bool
-            Default to true.  If False, do NOT extract any definitions from column groups, assume they are already
-            in the def_dict list.
-        name: str
-            The name to display for this file for error purposes.
         """
         if attribute_columns is None:
             attribute_columns = ["duration", "onset"]
-        if sidecars:
-            sidecars = Sidecar.load_multiple_sidecars(sidecars)
-
-        new_mapper = ColumnMapper(sidecars=sidecars, optional_tag_columns=[self.HED_COLUMN_NAME],
+        if sidecar:
+            sidecar_list = Sidecar.load_multiple_sidecars(sidecar)
+        else:
+            sidecar_list = None
+        new_mapper = ColumnMapper(sidecars=sidecar_list, optional_tag_columns=[self.HED_COLUMN_NAME],
                                   attribute_columns=attribute_columns)
 
         self._also_gather_defs = also_gather_defs
@@ -52,19 +48,18 @@ def __init__(self, file=None, sidecars=None, attribute_columns=None, extra_def_d
                              "This is probably not intended.")
 
     def create_def_mapper(self, column_mapper, extra_def_dicts=None):
-        """
-            Creates the definition mapper to parse definitions in this file.
+        """ Create the definition mapper to parse definitions in this file.
+
+        Args:
+            column_mapper (ColumnMapper): The column mapper to gather definitions from.
+            extra_def_dicts (DefinitionDict or [DefinitionDict]): Additional definitions to add to mapper.
+
+        Returns:
+            def mapper (DefMapper): A class to validate or expand definitions with the given def dicts.
+
+        Notes:
+            The extra_def_dicts are definitions not included in the column mapper.
 
-        Parameters
-        ----------
-        column_mapper : ColumnMapper
-            The column mapper to gather definitions from
-        extra_def_dicts : DefinitionDict or [DefinitionDict]
-            Optional. Adds any definitions in these to the def mapper as well, in addition to any found in the columns.
-        Returns
-        -------
-        def mapper: DefMapper
-            A class to validate or expand definitions with the given def dicts.
         """
         def_dicts = []
         if self._also_gather_defs:
@@ -79,18 +74,12 @@ def mapper: DefMapper
         return def_mapper
 
     def reset_column_mapper(self, sidecars=None, attribute_columns=None):
-        """
-            Change the sidecars and settings in use for parsing this file.
+        """ Change the sidecars and settings in use for parsing this file.
 
-        Parameters
-        ----------
-        sidecars : str or [str] or Sidecar or [Sidecar]
-            A list of json filenames to pull events from
-        attribute_columns: str or int or [str] or [int]
-            A list of column names or numbers to treat as attributes.
-            Default: ["duration", "onset"]
-        Returns
-        -------
+        Args:
+            sidecars (str or [str] or Sidecar or [Sidecar]): A list of json filenames to pull sidecar info from.
+            attribute_columns (str or int or [str] or [int]): Column names or numbers to treat as attributes.
+                    Default: ["duration", "onset"]
 
         """
         new_mapper = ColumnMapper(sidecars=sidecars, optional_tag_columns=[self.HED_COLUMN_NAME],
@@ -104,7 +93,7 @@ def validate_file_sidecars(self, hed_ops=None, error_handler=None, **kwargs):
         Validates all column definitions and column definition hed strings.
 
         This is not an encouraged way to do this.  You should instead validate the sidecars BEFORE creating the
-        EventsInput object.
+        TabularInput object.
 
         Parameters
         ----------
diff --git a/hed/models/timeseries_input.py b/hed/models/timeseries_input.py
new file mode 100644
index 000000000..65ff80ee6
--- /dev/null
+++ b/hed/models/timeseries_input.py
@@ -0,0 +1,26 @@
+from hed.models.column_mapper import ColumnMapper
+from hed.models.base_input import BaseInput
+from hed.models.sidecar import Sidecar
+from hed.models.def_mapper import DefMapper
+
+
+class TimeseriesInput(BaseInput):
+    """ Represents a BIDS time series tsv file."""
+
+    HED_COLUMN_NAME = "HED"
+
+    def __init__(self, file=None, sidecar=None, extra_def_dicts=None, name=None):
+        """Constructor for the TimeseriesInput class.
+
+        Args:
+            file (str or file like): A tsv file to open.
+            sidecar (str or Sidecar): A json sidecar to pull metadata from.
+            extra_def_dicts ([DefinitionDict] or DefinitionDict or None)
+                DefinitionDict objects containing all the definitions this file should use other than the ones coming
+                from the file itself and from the column def groups. These are added as the last entries,
+                so names will override earlier ones.
+            name (str): The name to display for this file for error purposes.
+        """
+
+        super().__init__(file, file_type=".tsv", worksheet_name=None, has_column_names=False, mapper=None,
+                         def_mapper=None, name=name)
diff --git a/hed/schema/hed_cache.py b/hed/schema/hed_cache.py
index af928db90..d4afb4526 100644
--- a/hed/schema/hed_cache.py
+++ b/hed/schema/hed_cache.py
@@ -24,9 +24,11 @@
 
 HED_XML_PREFIX = 'HED'
 HED_XML_EXTENSION = '.xml'
+hedxml_suffix = "/hedxml" # The suffix for schemas and library schemas at the given urls
+
 DEFAULT_HED_LIST_VERSIONS_URL = """https://api.github.com/repos/hed-standard/hed-specification/contents/hedxml"""
-LIBRARY_HED_URL = "https://api.github.com/repos/hed-standard/hed-schema-library/contents/hedxml"
-DEFAULT_URL_LIST = (DEFAULT_HED_LIST_VERSIONS_URL, LIBRARY_HED_URL)
+LIBRARY_HED_URL = 'https://api.github.com/repos/hed-standard/hed-schema-library/contents/library_schemas'
+DEFAULT_URL_LIST = (DEFAULT_HED_LIST_VERSIONS_URL, LIBRARY_HED_URL, )
 
 DEFAULT_SKIP_FOLDERS = ('deprecated', )
 
@@ -321,9 +323,11 @@ def _get_hed_xml_versions_from_url(hed_base_url, library_name=None,
     all_hed_versions = {}
     for file_entry in loaded_json:
         if file_entry['type'] == "dir":
+            if hed_base_url.endswith(hedxml_suffix):
+                continue
             if file_entry['name'] in skip_folders:
                 continue
-            sub_folder_versions = _get_hed_xml_versions_from_url(hed_base_url + "/" + file_entry['name'],
+            sub_folder_versions = _get_hed_xml_versions_from_url(hed_base_url + "/" + file_entry['name'] + hedxml_suffix,
                                                                  skip_folders=skip_folders, get_all_libraries=True)
             _merge_in_versions(all_hed_versions, sub_folder_versions)
         expression_match = version_pattern.match(file_entry["name"])
diff --git a/hed/schema/hed_schema_io.py b/hed/schema/hed_schema_io.py
index b2c26f85c..9ed3711b2 100644
--- a/hed/schema/hed_schema_io.py
+++ b/hed/schema/hed_schema_io.py
@@ -65,7 +65,7 @@ def load_schema(hed_path=None, library_prefix=None):
 
     if is_url:
         file_as_string = file_util.url_to_string(hed_path)
-        return from_string(file_as_string, file_type=os.path.splitext(hed_path.lower())[1])
+        hed_schema = from_string(file_as_string, file_type=os.path.splitext(hed_path.lower())[1])
     elif hed_path.lower().endswith(".xml"):
         hed_schema = HedSchemaXMLParser.load_xml(hed_path)
     elif hed_path.lower().endswith(".mediawiki"):
diff --git a/hed/tools/__init__.py b/hed/tools/__init__.py
index 33b6b606f..88097d71f 100644
--- a/hed/tools/__init__.py
+++ b/hed/tools/__init__.py
@@ -7,18 +7,17 @@
 from .analysis.tag_summary import TagSummary
 from .analysis.annotation_util import \
     check_df_columns, extract_tags, generate_sidecar_entry, hed_to_df, df_to_hed, merge_hed_dict
-from .analysis.analysis_util import assemble_hed, search_events
+from .analysis.analysis_util import assemble_hed, search_tabular, get_assembled_strings
+from .bids.bids_timeseries_file import BidsTimeseriesFile
 from .bids.bids_dataset import BidsDataset
 from .bids.bids_dataset_summary import BidsDatasetSummary
-from .bids.bids_event_file import BidsEventFile
-from .bids.bids_event_files import BidsEventFiles
+from .bids.bids_file_group import BidsFileGroup
 from .bids.bids_file import BidsFile
 from .bids.bids_file_dictionary import BidsFileDictionary
-from .bids.bids_json_file import BidsJsonFile
 from .bids.bids_sidecar_file import BidsSidecarFile
-from .bids.bids_tsv_dictionary import BidsTsvDictionary
-from .bids.bids_tsv_file import BidsTsvFile
-from .bids.bids_tsv_summary import BidsTsvSummary
+from .bids.bids_tabular_dictionary import BidsTabularDictionary
+from .bids.bids_tabular_file import BidsTabularFile
+from .bids.bids_tabular_summary import BidsTabularSummary
 
 from hed.tools.analysis.tsv_reports import report_tsv_diffs
 
diff --git a/hed/tools/analysis/analysis_util.py b/hed/tools/analysis/analysis_util.py
index b15185cc2..fb6505e24 100644
--- a/hed/tools/analysis/analysis_util.py
+++ b/hed/tools/analysis/analysis_util.py
@@ -1,13 +1,13 @@
 import pandas as pd
-from hed.models import EventsInput, HedStringFrozen, TagExpressionParser
+from hed.models import TabularInput, HedStringFrozen, TagExpressionParser
 
 
-def assemble_hed(events, additional_columns=None, expand_defs=False):
+def assemble_hed(data_input, columns_included=None, expand_defs=False):
     """ Return a dataframe representing the assembled HED annotations for an events file.
 
     Args:
-        events (EventsInput): The input events file to be searched.
-        additional_columns (list or None):  A list of additional column names to include. If None, only onset is used.
+        data_input (TabularInput): The input events file to be searched.
+        columns_included (list or None):  A list of additional column names to include. If None, only onset is used.
         expand_defs (bool): If True, definitions are expanded when the events are assembled.
 
     Returns:
@@ -15,61 +15,109 @@ def assemble_hed(events, additional_columns=None, expand_defs=False):
 
     """
 
-    eligible_columns = list(events.dataframe.columns)
-    frame_dict = {}
-    if additional_columns:
-        for column in additional_columns:
-            if column in eligible_columns:
-                frame_dict[column] = []
+    if columns_included:
+        columns = frozenset(list(data_input.dataframe.columns))
+        eligible_columns = [x for x in columns_included if x in columns]
     else:
-        frame_dict['onset'] = []
+        eligible_columns = None
 
-    hed_tags = []
-    onsets = []
-    for row_number, row_dict in events.iter_dataframe(return_row_dict=True, expand_defs=expand_defs,
-                                                      remove_definitions=True):
-        hed_tags.append(str(row_dict.get("HED", "")))
-        onsets.append(row_dict.get("onset", "n/a"))
-        for column in frame_dict:
-            frame_dict[column].append(events.dataframe.loc[row_number - 2, column])
-    df = pd.DataFrame({'onset': onsets})
-    for column, values in frame_dict.items():
-        df[column] = values
-    df['HED'] = hed_tags
+    hed_obj_list = get_assembled_strings(data_input, expand_defs=expand_defs)
+    hed_string_list = [str(hed) for hed in hed_obj_list]
+    if not eligible_columns:
+        return pd.DataFrame({"HED_assembled": hed_string_list})
+    df = data_input.dataframe[eligible_columns].copy(deep=True)
+    df['HED_assembled'] = hed_string_list
     return df
 
 
-def search_events(events, query, hed_schema):
+def get_assembled_strings(events, hed_schema=None, expand_defs=False):
+    """ Return a list of the HED string objects corresponding to the rows of events.
+
+    Args:
+        events (TabularInput): The input events file to be searched.
+        hed_schema (HedSchema or HedschemaGroup): If provided the HedStrings are converted to canonical form.
+        expand_defs (bool): If True, definitions are expanded when the events are assembled.
+
+    Returns:
+        List: A list ov HedString or HedStringComb objects.
+
+    """
+
+    hed_list = []
+    for row, row_dict in events.iter_dataframe(return_row_dict=True, expand_defs=expand_defs, remove_definitions=True):
+        this_group = row_dict.get("HED", None)
+        if hed_schema and this_group:
+            this_group.convert_to_canonical_forms(hed_schema)
+            hed_list.append(this_group)
+        else:
+            hed_list.append(row_dict.get("HED", None))
+    return hed_list
+
+
+def search_tabular(data_input, hed_schema, query, columns_included=None):
     """ Return a dataframe with results of query.
 
     Args:
-        events (EventsInput): The input events file to be searched.
-        query (str):     The str query to make.
+        data_input (TabularInput): The tabular input file (e.g., events) to be searched.
         hed_schema (HedSchema or HedSchemaGroup):  The schema(s) under which to make the query.
+        query (str):     The str query to make.
+        columns_included (list):  List of names of columns to include
 
     Returns:
         DataFrame or None: A DataFrame with the results of the query or None if no events satisfied the query.
 
     """
-    matched_tags = []
+    if columns_included:
+        columns = frozenset(list(data_input.dataframe.columns))
+        eligible_columns = [x for x in columns_included if x in columns]
+    else:
+        eligible_columns = None
+
+    hed_list = get_assembled_strings(data_input, hed_schema=hed_schema, expand_defs=True)
+    expression = TagExpressionParser(query)
     hed_tags = []
     row_numbers = []
-    expression = TagExpressionParser(query)
-    for row_number, row_dict in events.iter_dataframe(return_row_dict=True, expand_defs=True, remove_definitions=True):
-        expanded_string = str(row_dict.get("HED", ""))
-        hed_string = HedStringFrozen(expanded_string, hed_schema=hed_schema)
-        match = expression.search_hed_string(hed_string)
+    for index, next_item in enumerate(hed_list):
+        match = expression.search_hed_string(next_item)
         if not match:
             continue
-        match_str = ""
-        for m in match:
-            match_str = match_str + f" [{str(m)}] "
+        hed_tags.append(next_item)
+        row_numbers.append(index)
 
-        hed_tags.append(expanded_string)
-        matched_tags.append(match_str)
-        row_numbers.append(row_number - 2)
-
-    if row_numbers:
-        return pd.DataFrame({'row_number': row_numbers, 'matched_tags': matched_tags, 'HED': hed_tags})
+    if not row_numbers:
+        df = None
+    elif not eligible_columns:
+        df = pd.DataFrame({'row_number': row_numbers, 'HED_assembled': hed_tags})
     else:
-        return None
+        df = data_input.dataframe.iloc[row_numbers][eligible_columns].reset_index()  #df[eligible_columns] = data_input.dataframe.iloc[row_numbers][eligible_columns]
+        df['HED_assembled'] = hed_tags
+        df.rename(columns={'index': 'row_number'})
+    return df
+
+
+def filter_events(events, hed_schema, filter_hed):
+    """ Return a dataframe with results of query. """
+    return None
+
+
+if __name__ == '__main__':
+    import os
+    from hed.models import Sidecar
+    from hed.schema import load_schema_version
+    root_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), '../../../tests/data/bids/eeg_ds003654s_hed')
+    events_path = os.path.realpath(os.path.join(root_path, 'sub-002/eeg/sub-002_task-FacePerception_run-1_events.tsv'))
+    json_path = os.path.realpath(os.path.join(root_path, 'task-FacePerception_events.json'))
+    sidecar = Sidecar(json_path, name='face_sub1_json')
+    input_data = TabularInput(events_path, sidecar=sidecar, name="face_sub1_events")
+    hed_schema = load_schema_version(xml_version="8.0.0")
+    print("to here")
+
+    # df = assemble_hed(input_data, columns_included=["onset", "duration", "event_type"], expand_defs=False)
+    # df1 = assemble_hed(input_data, columns_included=["onset", "duration", "event_type"], expand_defs=True)
+    # df2 = assemble_hed(input_data, columns_included=["onset", "baloney", "duration", "event_type"], expand_defs=False)
+    # print("to there")
+
+    query = "Sensory-event"
+    df3 = search_tabular(input_data, hed_schema, query, columns_included=['onset', 'event_type'])
+
+    print(f"{len(df3)} events match")
\ No newline at end of file
diff --git a/hed/tools/analysis/tsv_reports.py b/hed/tools/analysis/tsv_reports.py
index c89019cc5..d828ad84c 100644
--- a/hed/tools/analysis/tsv_reports.py
+++ b/hed/tools/analysis/tsv_reports.py
@@ -1,11 +1,11 @@
 
 
 def report_tsv_diffs(tsv_dict1, tsv_dict2, logger):
-    """ Reports and logs the contents and differences of two equivalent BidsTsvDictionary objects
+    """ Reports and logs the contents and differences of two equivalent BidsTabularDictionary objects
 
     Args:
-        tsv_dict1 (BidsTsvDictionary): A dictionary representing BIDS-keyed tsv files
-        tsv_dict2 (BidsTsvDictionary): A dictionary representing BIDS-keyed tsv files
+        tsv_dict1 (BidsTabularDictionary): A dictionary representing BIDS-keyed tsv files
+        tsv_dict2 (BidsTabularDictionary): A dictionary representing BIDS-keyed tsv files
         logger (HedLogger):            A HedLogger object for reporting the values by key
 
     Returns: (str)                     A string with the differences
diff --git a/hed/tools/bids/bids_dataset.py b/hed/tools/bids/bids_dataset.py
index 15fa9e1c2..09adfd8da 100644
--- a/hed/tools/bids/bids_dataset.py
+++ b/hed/tools/bids/bids_dataset.py
@@ -4,28 +4,41 @@
 from hed.schema.hed_schema_io import load_schema, load_schema_version
 from hed.schema.hed_schema_group import HedSchemaGroup
 from hed.util.data_util import get_new_dataframe
-from hed.tools.bids.bids_event_files import BidsEventFiles
+from hed.tools.bids.bids_file_group import BidsFileGroup
+from hed.tools.bids.bids_sidecar_file import BidsSidecarFile
 from hed.validator import HedValidator
 
 
-LIBRARY_URL_BASE = "https://raw.githubusercontent.com/hed-standard/hed-schema-library/main/hedxml/"
+LIBRARY_URL_BASE = "https://raw.githubusercontent.com/hed-standard/hed-schema-library/main/library_schemas/"
 
 
 class BidsDataset:
-    """Represents the metadata for a BIDS dataset."""
+    """Represents the metadata for a BIDS dataset primarily focused on HED evaluation."""
 
-    def __init__(self, root_path):
+    def __init__(self, root_path, schema_group=None):
+        """ Constructor for a BIDS dataset.
+
+        Args:
+            root_path (str):  Root path of the BIDS dataset.
+            schema_group (HedSchemaGroup):  An optional HedSchemaGroup that overrides the one specified in dataset.
+
+        """
         self.root_path = os.path.realpath(root_path)
         with open(os.path.join(self.root_path, "dataset_description.json"), "r") as fp:
             self.dataset_description = json.load(fp)
         self.participants = get_new_dataframe(os.path.join(self.root_path, "participants.tsv"))
-        self.schemas = HedSchemaGroup(self._schema_from_description())
-        self.event_files = BidsEventFiles(root_path)
+        if schema_group:
+            self.schemas = schema_group
+        else:
+            self.schemas = HedSchemaGroup(self._schema_from_description())
+        self.tabular_files = {"events": BidsFileGroup(root_path, suffix="_events", type="tabular")}
 
     def validate(self, check_for_warnings=True):
         validator = HedValidator(hed_schema=self.schemas)
-        issues = self.event_files.validate(hed_ops=[validator], check_for_warnings=check_for_warnings)
-        return issues
+        event_files = self.tabular_files["events"]
+        issues1 = event_files.validate_sidecars(hed_ops=[validator], check_for_warnings=check_for_warnings)
+        issues2 = event_files.validate_datafiles(hed_ops=[validator], check_for_warnings=check_for_warnings)
+        return issues1 + issues2
 
     def _schema_from_description(self):
         hed = self.dataset_description.get("HEDVersion", None)
@@ -40,10 +53,8 @@ def _schema_from_description(self):
         if 'libraries' in hed:
             for key, library in hed['libraries'].items():
                 library_pieces = library.split('_')
-                url = LIBRARY_URL_BASE + library_pieces[0] + '/HED_' + library + '.xml'
-                x = load_schema(url, library_prefix=key)
-                x.set_library_prefix(key)  # TODO: temporary work around
-                hed_list.append(x)
+                url = LIBRARY_URL_BASE + library_pieces[0] + '/hedxml/HED_' + library + '.xml'
+                hed_list.append(load_schema(url, library_prefix=key))
         return hed_list
 
     def get_summary(self):
@@ -63,14 +74,17 @@ def get_schema_versions(self):
 
 
 if __name__ == '__main__':
+    # path = os.path.join(os.path.dirname(os.path.realpath(__file__)),
+    #                     '../../../tests/data/bids/eeg_ds003654s_hed_library')
     path = os.path.join(os.path.dirname(os.path.realpath(__file__)),
-                        '../../../tests/data/bids/eeg_ds003654s_hed_library')
+                        '../../../tests/data/bids/eeg_ds003654s_hed_inheritance')
+
     bids = BidsDataset(path)
-    issue_list = bids.validate()
+    issue_list = bids.validate(check_for_warnings=False)
     if issue_list:
         issue_str = get_printable_issue_string(issue_list, "HED validation errors:")
     else:
         issue_str = "No issues"
     print(issue_str)
-    summary1 = bids.get_summary()
-    print(json.dumps(summary1, indent=4))
+    # summary1 = bids.get_summary()
+    # print(json.dumps(summary1, indent=4))
diff --git a/hed/tools/bids/bids_dataset_summary.py b/hed/tools/bids/bids_dataset_summary.py
index 2f5c07bec..c6d308b5e 100644
--- a/hed/tools/bids/bids_dataset_summary.py
+++ b/hed/tools/bids/bids_dataset_summary.py
@@ -5,11 +5,10 @@ class BidsDatasetSummary:
     """ Summarizes a BIDS dataset. """
 
     def __init__(self, dataset):
-        """
+        """ Constructor for producing a BIDS dataset JSON summary
 
-        Parameters:
-            dataset: BidsDataset or str
-            The dataset to be summarized.
+        Args:
+            dataset (BidsDataset or str): The dataset to be summarized.
 
         """
         if isinstance(dataset, str):
diff --git a/hed/tools/bids/bids_event_file.py b/hed/tools/bids/bids_event_file.py
deleted file mode 100644
index eb51aaa60..000000000
--- a/hed/tools/bids/bids_event_file.py
+++ /dev/null
@@ -1,36 +0,0 @@
-import os
-from hed.models.events_input import EventsInput
-from hed.tools.bids.bids_tsv_file import BidsTsvFile
-
-
-class BidsEventFile(BidsTsvFile):
-    """ Represents a BIDS event file including its associated sidecars.
-
-
-    """
-
-    def __init__(self, file_path):
-        super().__init__(file_path, set_contents=False)
-        self.contents = None
-        self.sidecars = None
-
-    def clear_contents(self):
-        self.contents = None
-
-    def set_contents(self):
-        self.contents = EventsInput(file=self.file_path, sidecars=self.sidecars,
-                                    name=os.path.realpath(self.file_path))
-
-    def set_sidecars(self, sidecars):
-        if sidecars is None:
-            self.sidecars = []
-        elif not isinstance(sidecars, list):
-            self.sidecars = [sidecars]
-        else:
-            self.sidecars = sidecars
-
-    def __str__(self):
-        my_str = super().__str__()
-        if self.sidecars:
-            my_str = my_str + "\n\tsidecars=" + str(self.sidecars)
-        return my_str
diff --git a/hed/tools/bids/bids_event_files.py b/hed/tools/bids/bids_event_files.py
deleted file mode 100644
index b958c3d4b..000000000
--- a/hed/tools/bids/bids_event_files.py
+++ /dev/null
@@ -1,122 +0,0 @@
-import os
-from hed.schema.hed_schema_io import load_schema_version
-from hed.tools.bids.bids_event_file import BidsEventFile
-from hed.tools.bids.bids_sidecar_file import BidsSidecarFile
-from hed.tools.bids.bids_tsv_summary import BidsTsvSummary
-from hed.models.events_input import EventsInput
-from hed.util.io_util import get_dir_dictionary, get_file_list, get_path_components
-
-
-class BidsEventFiles:
-    """ Container for the event files and sidecars in a BIDS dataset."""
-
-    def __init__(self, root_path):
-        self.root_path = os.path.realpath(root_path)
-        self.sidecar_dict = {}  # Dict uses os.path.abspath(file) as key
-        self.events_dict = {}
-        self.sidecar_dir_dict = {}
-        self._set_sidecar_dict()  # Dict uses os.path.abspath(file) as key
-        self._set_event_file_dict()
-        self._set_sidecar_dir_dict()
-
-        for bids_obj in self.events_dict.values():
-            bids_obj.set_sidecars(self.get_sidecars_from_path(bids_obj))
-
-    def _set_event_file_dict(self):
-        """ Get a dictionary of BidsEventFile objects with underlying EventInput objects not set"""
-        files = get_file_list(self.root_path, name_suffix='_events', extensions=['.tsv'])
-        file_dict = {}
-        for file in files:
-            file_dict[os.path.realpath(file)] = BidsEventFile(file)
-        self.events_dict = file_dict
-
-    def _set_sidecar_dict(self):
-        """ Get a dictionary of BidsSidecarFile objects with underlying Sidecar objects set"""
-        files = get_file_list(self.root_path, name_suffix='_events', extensions=['.json'])
-        file_dict = {}
-        for file in files:
-            file_dict[os.path.realpath(file)] = BidsSidecarFile(os.path.realpath(file), set_contents=True)
-        self.sidecar_dict = file_dict
-
-    def _set_sidecar_dir_dict(self):
-        """ Set the dictionary with direct pointers to sidecars rather than paths"""
-        dir_dict = get_dir_dictionary(self.root_path, name_suffix='events', extensions=['.json'])
-        sidecar_dir_dict = {}
-        for this_dir, dir_list in dir_dict.items():
-            new_dir_list = []
-            for s_file in dir_list:
-                new_dir_list.append(self.sidecar_dict[os.path.realpath(s_file)])
-            sidecar_dir_dict[os.path.realpath(this_dir)] = new_dir_list
-        self.sidecar_dir_dict = sidecar_dir_dict
-
-    def get_sidecars_from_path(self, obj):
-        sidecar_list = []
-        current_path = ''
-        for comp in get_path_components(obj.file_path, self.root_path):
-            current_path = os.path.realpath(os.path.join(current_path, comp))
-            sidecars = self.sidecar_dir_dict.get(current_path, None)
-            sidecar = BidsSidecarFile.get_sidecar(obj, sidecars)
-            if sidecar:
-                sidecar_list.append(sidecar)
-        return sidecar_list
-
-    def summarize(self, value_cols=None, skip_cols=None):
-        """
-
-        Args:
-            value_cols (list):  Column names designated as value columns.
-            skip_cols (list):   Column names designated as columns to skip.
-
-        Returns:
-            BidsTsvSummary:  A summary of the number of values in different columns.
-
-        Notes: The columns that are not value_cols or skip_col are summarized by counting
-        the number of times each unique value appears in that column.
-
-        """
-        info = BidsTsvSummary(value_cols=value_cols, skip_cols=skip_cols)
-        for event_obj in self.events_dict.values():
-            info.update(event_obj.file_path)
-        return info
-
-    def validate(self, hed_ops, check_for_warnings=True, keep_events=False):
-        """ Validate the events files and return an error list.
-
-        Args:
-            hed_ops ([func or HedOps], func, HedOps):  Validation functions to apply.
-            check_for_warnings (bool):  If True, include warnings in the check.
-            keep_events (bool):         If True, the underlying event files are read and their contents retained.
-
-        Returns:
-            (list):    A list of validation issues found.
-
-        """
-        issues = []
-        for json_obj in self.sidecar_dict.values():
-            issues += json_obj.contents.validate_entries(hed_ops=hed_ops, check_for_warnings=check_for_warnings)
-        if issues:
-            return issues
-        for event_obj in self.events_dict.values():
-            contents = event_obj.contents
-            if not contents:
-                contents = EventsInput(file=event_obj.file_path, sidecars=event_obj.sidecars)
-                if keep_events:
-                    event_obj.my_contents = contents
-            issues += contents.validate_file(hed_ops=hed_ops, check_for_warnings=check_for_warnings)
-        return issues
-
-
-if __name__ == '__main__':
-    path = os.path.join(os.path.dirname(os.path.realpath(__file__)),
-                        '../../../tests/data/bids/eeg_ds003654s_hed')
-    bids = BidsEventFiles(path)
-
-    for file_obj in bids.sidecar_dict.values():
-        print(file_obj)
-
-    for file_obj in bids.events_dict.values():
-        print(file_obj)
-
-    hed_schema = load_schema_version(xml_version="8.0.0")
-
-    col_info = bids.summarize()
diff --git a/hed/tools/bids/bids_file.py b/hed/tools/bids/bids_file.py
index 31c0795c1..1dda1185b 100644
--- a/hed/tools/bids/bids_file.py
+++ b/hed/tools/bids/bids_file.py
@@ -22,8 +22,24 @@ def __init__(self, file_path):
         self.suffix = suffix
         self.ext = ext
         self.entity_dict = entity_dict
+        self.sidecar = None    # list of sidecars starting at the root (including itself if sidecar)
+        self.contents = None
+
+    @property
+    def get_contents(self):
+        return self.contents
+
+    def clear_contents(self):
+        self.contents = None
+
+    def set_contents(self, content_info=None, no_overwrite=True):
+        if self.contents and no_overwrite:
+            return
+        self.contents = content_info
 
     def __str__(self):
-        return self.file_path + ":\n\tname_suffix=" + self.suffix + " ext=" + self.ext + \
+        my_str = self.file_path + ":\n\tname_suffix=" + self.suffix + " ext=" + self.ext + \
                " entity_dict=" + str(self.entity_dict)
-
+        if self.sidecar:
+            my_str = my_str + "\n\tmerged sidecar=" + str(self.sidecar.file_path)
+        return my_str
diff --git a/hed/tools/bids/bids_file_group.py b/hed/tools/bids/bids_file_group.py
new file mode 100644
index 000000000..f50ac561b
--- /dev/null
+++ b/hed/tools/bids/bids_file_group.py
@@ -0,0 +1,179 @@
+import os
+from hed.schema.hed_schema_io import load_schema_version
+from hed.tools.bids.bids_timeseries_file import BidsTimeseriesFile
+from hed.tools.bids.bids_tabular_file import BidsTabularFile
+from hed.tools.bids.bids_sidecar_file import BidsSidecarFile
+from hed.tools.bids.bids_tabular_summary import BidsTabularSummary
+from hed.models.tabular_input import TabularInput
+from hed.util.io_util import get_dir_dictionary, get_file_list, get_path_components
+
+
+class BidsFileGroup:
+    """ Container for a group of files with a specified suffix and their supporting sidecars in a BIDS dataset."""
+
+    def __init__(self, root_path, suffix="_events", type="tabular"):
+        self.root_path = os.path.realpath(root_path)
+        self.suffix = suffix
+        self.type = type
+        self.sidecar_dict = self._make_sidecar_dict()
+        self.datafile_dict = self._make_datafile_dict()
+        self.sidecar_dir_dict = self._make_sidecar_dir_dict()
+
+        for bids_obj in self.sidecar_dict.values():
+            x = self.get_sidecars_from_path(bids_obj)
+            bids_obj.set_contents(content_info=x)
+
+        for bids_obj in self.datafile_dict.values():
+            sidecar_list = self.get_sidecars_from_path(bids_obj)
+            if sidecar_list:
+                bids_obj.sidecar = sidecar_list[-1]
+
+    def get_sidecars_from_path(self, obj):
+        """ Creates a list of the applicable sidecars for the indicated object.
+
+        Args:
+            obj (BidsTabularFile or BidsSidecarFile):  The BIDS event file to get the sidecars for
+
+        Returns:
+            list:  A list of the applicable sidecars for obj starting at the root.
+
+        """
+        sidecar_list = []
+        current_path = ''
+        for comp in get_path_components(obj.file_path, self.root_path):
+            current_path = os.path.realpath(os.path.join(current_path, comp))
+            next_sidecar = self._get_sidecar_for_obj(obj, current_path)
+            if next_sidecar:
+                sidecar_list.append(next_sidecar)
+        return sidecar_list
+
+    def _get_sidecar_for_obj(self, obj, current_path):
+        """ Return a single BidsSidecarFile relevant to obj from the sidecars in the current path """
+        sidecars = self.sidecar_dir_dict.get(current_path, None)
+        if not sidecars:
+            return None
+        for sidecar in sidecars:
+            if sidecar.is_sidecar_for(obj):
+                return sidecar
+        return None
+
+    def summarize(self, value_cols=None, skip_cols=None):
+        """
+
+        Args:
+            value_cols (list):  Column names designated as value columns.
+            skip_cols (list):   Column names designated as columns to skip.
+
+        Returns:
+            BidsTabularSummary:  A summary of the number of values in different columns.
+
+        Notes: The columns that are not value_cols or skip_col are summarized by counting
+        the number of times each unique value appears in that column.
+
+        """
+        info = BidsTabularSummary(value_cols=value_cols, skip_cols=skip_cols)
+        for event_obj in self.datafile_dict.values():
+            info.update(event_obj.file_path)
+        return info
+
+    def validate_sidecars(self, hed_ops, check_for_warnings=True):
+        """ Validate merged sidecars.
+
+        Args:
+            hed_ops ([func or HedOps], func, HedOps):  Validation functions to apply.
+            check_for_warnings (bool):  If True, include warnings in the check.
+
+        Returns:
+            (list):    A list of validation issues found. Each issue is a dictionary.
+
+        """
+        issues = []
+        for sidecar in self.sidecar_dict.values():
+            issues += sidecar.contents.validate_entries(hed_ops=hed_ops, check_for_warnings=check_for_warnings)
+        return issues
+
+    def validate_datafiles(self, hed_ops, check_for_warnings=True, keep_contents=False):
+        """ Validate the datafiles and return an error list.
+
+        Args:
+            hed_ops ([func or HedOps], func, HedOps):  Validation functions to apply.
+            check_for_warnings (bool):  If True, include warnings in the check.
+            keep_contents (bool):       If True, the underlying data files are read and their contents retained.
+
+        Returns:
+            (list):    A list of validation issues found. Each issue is a dictionary.
+
+        """
+        issues = []
+        for data_obj in self.datafile_dict.values():
+            data_obj.set_contents(no_overwrite=True)
+            data = data_obj.contents
+            issues += data.validate_file(hed_ops=hed_ops, check_for_warnings=check_for_warnings)
+            if not keep_contents:
+                data_obj.clear_contents()
+        return issues
+
+    def _make_datafile_dict(self):
+        """ Get a dictionary of BidsTabularFile objects with underlying EventInput objects not set.
+
+        Returns:
+            dict:   A dictionary of BidsTabularFile objects keyed by real path.
+
+        """
+        files = get_file_list(self.root_path, name_suffix=self.suffix, extensions=['.tsv'])
+        file_dict = {}
+        if self.type == "tabular":
+            for file in files:
+                file_dict[os.path.realpath(file)] = BidsTabularFile(file)
+        else:
+            for file in files:
+                file_dict[os.path.realpath(file)] = BidsTimeseriesFile(file)
+        return file_dict
+
+    def _make_sidecar_dict(self):
+        """ Create a dictionary of BidsSidecarFile objects for the specified entity type.
+
+        Returns:
+            dict:   a dictionary of BidsSidecarFile objects keyed by real path for the specified suffix type
+
+        Notes:
+            This function creates the sidecars and but does not set their contents.
+
+        """
+        files = get_file_list(self.root_path, name_suffix=self.suffix, extensions=['.json'])
+        file_dict = {}
+        for file in files:
+            file_dict[os.path.realpath(file)] = BidsSidecarFile(os.path.realpath(file))
+        return file_dict
+
+    def _make_sidecar_dir_dict(self):
+        """ Create a the dictionary with real paths of directories as keys and a list of sidecar file paths as values.
+
+        Returns:
+            dict: A dictionary of lists of sidecar BidsSidecarFiles
+
+        """
+        dir_dict = get_dir_dictionary(self.root_path, name_suffix=self.suffix, extensions=['.json'])
+        sidecar_dir_dict = {}
+        for this_dir, dir_list in dir_dict.items():
+            new_dir_list = []
+            for s_file in dir_list:
+                new_dir_list.append(self.sidecar_dict[os.path.realpath(s_file)])
+            sidecar_dir_dict[os.path.realpath(this_dir)] = new_dir_list
+        return sidecar_dir_dict
+
+
+if __name__ == '__main__':
+    path = os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                        '../../../tests/data/bids/eeg_ds003654s_hed')
+    bids = BidsFileGroup(path)
+
+    for file_obj in bids.sidecar_dict.values():
+        print(file_obj)
+
+    for file_obj in bids.datafile_dict.values():
+        print(file_obj)
+
+    hed_schema = load_schema_version(xml_version="8.0.0")
+
+    col_info = bids.summarize()
diff --git a/hed/tools/bids/bids_json_file.py b/hed/tools/bids/bids_json_file.py
deleted file mode 100644
index fba91e19e..000000000
--- a/hed/tools/bids/bids_json_file.py
+++ /dev/null
@@ -1,27 +0,0 @@
-import os
-import json
-from hed.tools.bids.bids_file import BidsFile
-
-
-class BidsJsonFile(BidsFile):
-    """Represents a BIDS JSON file."""
-
-    def __init__(self, file_path, set_contents=False):
-        super().__init__(file_path)
-        self.contents = None
-        if set_contents:
-            self.set_contents()
-
-    def clear_contents(self):
-        self.contents = None
-
-    def set_contents(self):
-        with open(self.file_path, 'r') as f:
-            self.contents = json.load(f)
-
-    def __str__(self):
-        """ Returns a string summary of the tsv file."""
-        if isinstance(self.contents, dict):
-            return super().__str__() + f"\n\tKeys: [{str(list(self.contents.keys()))}\n\t"
-        else:
-            return super().__str__()
diff --git a/hed/tools/bids/bids_sidecar_file.py b/hed/tools/bids/bids_sidecar_file.py
index 99aafcc7b..e9f98c9e2 100644
--- a/hed/tools/bids/bids_sidecar_file.py
+++ b/hed/tools/bids/bids_sidecar_file.py
@@ -1,16 +1,15 @@
 import os
+import io
+import json
 from hed.models.sidecar import Sidecar
 from hed.tools.bids.bids_file import BidsFile
-from hed.tools.bids.bids_json_file import BidsJsonFile
 
 
-class BidsSidecarFile(BidsJsonFile):
+class BidsSidecarFile(BidsFile):
     """ Represents a BIDS JSON sidecar file."""
 
-    def __init__(self, file_path, set_contents=False):
-        super().__init__(file_path, set_contents=False)
-        if set_contents:
-            self.set_contents()
+    def __init__(self, file_path):
+        super().__init__(file_path)
 
     def is_sidecar_for(self, obj):
         """ Returns true if this is a sidecar for obj.
@@ -20,28 +19,34 @@ def is_sidecar_for(self, obj):
 
          Returns:
              bool:   True if this is a BIDS parent of obj and False otherwise
+
+         Notes:
+             A sidecar is a sidecar for itself.
+
          """
 
-        if obj.suffix != self.suffix:
+        if obj.file_path == self.file_path:
+            return True
+        elif obj.suffix != self.suffix:
             return False
-
-        common_path = os.path.commonpath([obj.file_path, self.file_path])
-        if common_path != os.path.dirname(self.file_path):
+        elif os.path.dirname(self.file_path) != os.path.commonpath([obj.file_path, self.file_path]):
             return False
+
         for key, item in self.entity_dict.items():
             if key not in obj.entity_dict or obj.entity_dict[key] != item:
                 return False
         return True
 
-    def set_contents(self):
-        self.contents = Sidecar(self.file_path, name=os.path.realpath(self.file_path))
-
-    @staticmethod
-    def get_sidecar(obj, sidecars):
-        """ Return a single SideCar relevant to obj from list of sidecars """
-        if not sidecars:
-            return None
-        for sidecar in sidecars:
-            if sidecar.is_sidecar_for(obj):
-                return sidecar.contents
-        return None
+    def set_contents(self, content_info=None):
+        if not content_info:
+            self.contents = Sidecar(self.file_path, name=os.path.realpath(os.path.basename(self.file_path)))
+        else:
+            merged_sidecar = {}
+            for s in content_info:
+                with open(s.file_path, 'r') as fp:
+                    next_sidecar = json.load(fp)
+                    for key, item in next_sidecar.items():
+                        merged_sidecar[key] = item
+
+            self.contents = Sidecar(file=io.StringIO(json.dumps(merged_sidecar)),
+                                    name=os.path.realpath(os.path.basename(self.file_path)))
diff --git a/hed/tools/bids/bids_tsv_dictionary.py b/hed/tools/bids/bids_tabular_dictionary.py
similarity index 87%
rename from hed/tools/bids/bids_tsv_dictionary.py
rename to hed/tools/bids/bids_tabular_dictionary.py
index 86b4c48fb..5c1aa4c33 100644
--- a/hed/tools/bids/bids_tsv_dictionary.py
+++ b/hed/tools/bids/bids_tabular_dictionary.py
@@ -2,8 +2,8 @@
 from hed.tools.bids.bids_file_dictionary import BidsFileDictionary
 
 
-class BidsTsvDictionary(BidsFileDictionary):
-    """ Holds a key-file dictionary, but also reads each tsv file and keeps track of number of rows and column names."""
+class BidsTabularDictionary(BidsFileDictionary):
+    """ Holds a key-file dictionary, but also reads each tabular file and keeps track of number of rows and column names."""
 
     def __init__(self, collection_name, file_list=None, entities=('sub', 'ses', 'task', 'run')):
         """ Create a dictionary with keys that are simplified file names and values that are full paths
@@ -62,7 +62,7 @@ def count_diffs(self, other_dict):
         return diff_list
 
     def _create_dict_obj(self, collection_name, file_dict):
-        dict_obj = BidsTsvDictionary(collection_name, file_list=None, entities=self.entities)
+        dict_obj = BidsTabularDictionary(collection_name, file_list=None, entities=self.entities)
         dict_obj.file_dict = file_dict
         dict_obj._set_tsv_info()
         return dict_obj
diff --git a/hed/tools/bids/bids_tabular_file.py b/hed/tools/bids/bids_tabular_file.py
new file mode 100644
index 000000000..ef7df2f67
--- /dev/null
+++ b/hed/tools/bids/bids_tabular_file.py
@@ -0,0 +1,22 @@
+import os
+from hed.models.tabular_input import TabularInput
+from hed.tools.bids.bids_file import BidsFile
+
+
+class BidsTabularFile(BidsFile):
+    """ Represents a BIDS tabular file including its associated sidecar.
+
+    """
+
+    def __init__(self, file_path):
+        """ Constructor for a BIDS events file. """
+        super().__init__(file_path)
+
+    def set_contents(self, content_info=None, no_overwrite=True):
+        if self.contents and no_overwrite:
+            return
+        if self.sidecar:
+            x = TabularInput(file=self.file_path, sidecar=self.sidecar.contents, name=os.path.realpath(self.file_path))
+        else:
+            x = TabularInput(file=self.file_path, name=os.path.realpath(self.file_path))
+        BidsFile.set_contents(self, content_info=x)
diff --git a/hed/tools/bids/bids_tsv_summary.py b/hed/tools/bids/bids_tabular_summary.py
similarity index 92%
rename from hed/tools/bids/bids_tsv_summary.py
rename to hed/tools/bids/bids_tabular_summary.py
index 102cffb95..ef5f292e2 100644
--- a/hed/tools/bids/bids_tsv_summary.py
+++ b/hed/tools/bids/bids_tabular_summary.py
@@ -4,8 +4,8 @@
 from hed.tools.analysis.annotation_util import generate_sidecar_entry
 
 
-class BidsTsvSummary:
-    """Summarizes the contents of tsv files. """
+class BidsTabularSummary:
+    """Summarizes the contents of BIDS tabular files. """
 
     def __init__(self, value_cols=None, skip_cols=None, name=''):
         """ .
@@ -106,7 +106,7 @@ def update_summary(self, col_sum):
         cannot be in the
 
           Args:
-              col_sum (BidsTsvSummary):    A ColumnSummary to be combined with
+              col_sum (BidsTabularSummary):    A ColumnSummary to be combined with
 
           """
         self._update_dict_skip(col_sum)
@@ -205,13 +205,14 @@ def make_combined_dicts(file_dictionary, skip_cols=None):
             skip_cols (list):  Name of the column
 
         Returns:
-            (BidsTsvSummary, dict)  A BidsTsvSummary of the file_dict, plus dict of individual BidsTsvSummary objects
+            BidsTabularSummary:  A summary of the file dictionary
+            dict: A dict of individual BidsTabularSummary objects
         """
 
-        summary_all = BidsTsvSummary(skip_cols=skip_cols)
+        summary_all = BidsTabularSummary(skip_cols=skip_cols)
         summary_dict = {}
         for key, file in file_dictionary.file_dict.items():
-            orig_dict = BidsTsvSummary(skip_cols=skip_cols)
+            orig_dict = BidsTabularSummary(skip_cols=skip_cols)
             df = get_new_dataframe(file.file_path)
             orig_dict.update(df)
             summary_dict[key] = orig_dict
diff --git a/hed/tools/bids/bids_timeseries_file.py b/hed/tools/bids/bids_timeseries_file.py
new file mode 100644
index 000000000..197ce1ad7
--- /dev/null
+++ b/hed/tools/bids/bids_timeseries_file.py
@@ -0,0 +1,17 @@
+import os
+from hed.models.tabular_input import TabularInput
+from hed.tools.bids.bids_file import BidsFile
+
+
+class BidsTimeseriesFile(BidsFile):
+    """ Represents a BIDS continuous file including its associated sidecar. """
+
+    def __init__(self, file_path):
+        """ Constructor for a BIDS events file. """
+        super().__init__(file_path)
+
+    def set_contents(self, content_info=None, no_overwrite=True):
+        if self.contents and no_overwrite:
+            return
+        BidsFile.set_contents(self, content_info=TabularInput(file=self.file_path, sidecar=self.sidecar,
+                                                              name=os.path.realpath(self.file_path)))
diff --git a/hed/tools/bids/bids_tsv_file.py b/hed/tools/bids/bids_tsv_file.py
deleted file mode 100644
index d4f608018..000000000
--- a/hed/tools/bids/bids_tsv_file.py
+++ /dev/null
@@ -1,38 +0,0 @@
-"""BidsTsvFile class."""
-import os
-from pandas import DataFrame
-from hed.tools.bids.bids_file import BidsFile
-from hed.util.data_util import get_new_dataframe
-
-
-class BidsTsvFile(BidsFile):
-    """ Represents a BIDS TSV file, possibly without its contents. """
-
-    def __init__(self, file_path, set_contents=False):
-        """
-            Parameters
-            ----------
-            file_path: str
-                The full path to the file
-            set_contents: bool
-                If true then the file contents are read into a dataframe.
-
-        """
-        super().__init__(file_path)
-        self.contents = None
-        if set_contents:
-            self.set_contents()
-
-    def clear_contents(self):
-        self.contents = None
-
-    def set_contents(self):
-        self.contents = get_new_dataframe(self.file_path)
-
-    def __str__(self):
-        """ Returns a string summary of the tsv file."""
-        if isinstance(self.contents, DataFrame):
-            return super().__str__() + f"\n\tColumns: [{str(self.contents.columns.values.tolist())}\n\t" + \
-                              f"Rows: {len(self.contents.index)}"
-        else:
-            return super().__str__()
diff --git a/hed/util/__init__.py b/hed/util/__init__.py
index 4a1c47d21..6ec1bac01 100644
--- a/hed/util/__init__.py
+++ b/hed/util/__init__.py
@@ -1,5 +1,5 @@
 """ Data and file handling utilities."""
 
-from .data_util import get_new_dataframe, get_value_dict, replace_values
+from .data_util import get_new_dataframe, get_value_dict, replace_values, reorder_columns
 from .file_util import get_file_extension
 from .io_util import check_filename, generate_filename, extract_suffix_path, get_file_list, make_path
diff --git a/hed/util/file_util.py b/hed/util/file_util.py
index 700f48e48..db9a05d73 100644
--- a/hed/util/file_util.py
+++ b/hed/util/file_util.py
@@ -1,4 +1,4 @@
-"""Utilities for writing content to files and for other file manipulation."""
+""" Utilities for writing content to files and for other file manipulation."""
 
 import tempfile
 import os
diff --git a/hed/util/io_util.py b/hed/util/io_util.py
index fb41e562c..cc8b4f8d3 100644
--- a/hed/util/io_util.py
+++ b/hed/util/io_util.py
@@ -156,8 +156,6 @@ def get_file_list(root_path, name_prefix=None, name_suffix=None, extensions=None
 def get_path_components(this_path, root_path):
     """ Get a list with root_path and remaining components.
 
-
-
     Args:
         this_path (str):      The path of a file or directory descendant of root_path
         root_path (str):      A path (no trailing separator)
diff --git a/hed/validator/hed_validator.py b/hed/validator/hed_validator.py
index 13b72ef44..9f2e5135c 100644
--- a/hed/validator/hed_validator.py
+++ b/hed/validator/hed_validator.py
@@ -17,19 +17,14 @@
 
 class HedValidator(HedOps):
     """ Top level validation of HED strings. """
+
     def __init__(self, hed_schema=None, run_semantic_validation=True):
-        """Constructor for the HedValidator class.
-
-        Parameters
-        ----------
-        hed_schema: HedSchema
-            HedSchema object to use for validation
-        run_semantic_validation: bool
-            True if the validator should check the HED data against a schema. False for syntax-only validation.
-        Returns
-        -------
-        HedValidator object
-            A HedValidator object.
+        """ Constructor for the HedValidator class.
+
+        Args:
+            hed_schema (HedSchema or HedSchemaGroup): HedSchema object to use for validation.
+            run_semantic_validation (bool): True if the validator should check the HED data against a schema.
+            False for syntax-only validation.
 
         """
         super().__init__()


Test patch:
diff --git a/tests/data/bids/eeg_ds003654s_hed_library/dataset_description.json b/tests/data/bids/eeg_ds003654s_hed_library/dataset_description.json
index 1ff4032c5..0c485dfb5 100644
--- a/tests/data/bids/eeg_ds003654s_hed_library/dataset_description.json
+++ b/tests/data/bids/eeg_ds003654s_hed_library/dataset_description.json
@@ -5,7 +5,7 @@
         "base": "8.0.0",
         "libraries": {
             "sc": "score_0.0.1",
-            "test": "test_1.0.2"
+            "test": "testlib_1.0.2"
         }
     },
     "License": "CC0",
diff --git a/tests/data/validator_tests/bids_events_no_index.tsv b/tests/data/validator_tests/bids_events_no_index.tsv
new file mode 100644
index 000000000..23e0e10bb
--- /dev/null
+++ b/tests/data/validator_tests/bids_events_no_index.tsv
@@ -0,0 +1,22 @@
+onset	duration	event-type	sample	stage	trial	bci_prediction	bci_prediction_valid	n_repeated	latency
+15.03893333	3	cue	75195	1	1	right	1	2	-4.2
+18.03893333	0	go	90195	1	1	right	1	2	-4.2
+18.1556	0	right-raised	90778	1	1	right	1	2	-4.2
+18.2236	0	right-raised-nomatch	91118	1	1	right	1	2	-4.2
+19.8922	3	cue	99461	1	2	left	1	2	-2.4
+22.8922	0	go	114461	1	2	left	1	2	-2.4
+22.9922	0	right-raised	114961	1	2	left	1	2	-2.4
+23.0602	0	right-raised-match	115301	1	2	left	1	2	-2.4
+24.53046667	3	cue	122652	1	3	left	1	2	-17.2
+27.53046667	0	go	137652	1	3	left	1	2	-17.2
+27.5638	0	left-raised	137819	1	3	left	1	2	-17.2
+27.6318	0	left-raised-nomatch	138159	1	3	left	1	2	-17.2
+32.63533333	3	cue	163177	1	4	left	1	3	9.8
+35.63533333	0	go	178177	1	4	left	1	3	9.8
+35.652	0	right-raised	178260	1	4	left	1	3	9.8
+35.72	0	right-raised-match	178600	1	4	left	1	3	9.8
+37.19273333	3	cue	185964	1	5	right	1	3	-15.2
+40.19273333	0	go	200964	1	5	right	1	3	-15.2
+40.2594	0	right-raised	201297	1	5	right	1	3	-15.2
+40.3274	0	right-raised-nomatch	201637	1	5	right	1	3	-15.2
+45.9162	3	cue	229581	1	6	right	1	5	-16.8
diff --git a/tests/data/validator_tests/bids_events.tsv b/tests/data/validator_tests/bids_events_with_index.tsv
similarity index 100%
rename from tests/data/validator_tests/bids_events.tsv
rename to tests/data/validator_tests/bids_events_with_index.tsv
diff --git a/tests/data/validator_tests/tag_error_span_test.tsv b/tests/data/validator_tests/tag_error_span_test.tsv
index da37e7878..f4eb263d1 100644
--- a/tests/data/validator_tests/tag_error_span_test.tsv
+++ b/tests/data/validator_tests/tag_error_span_test.tsv
@@ -1,3 +1,3 @@
 	hed	hed2
 0	Event	Event
-0	Event	()
\ No newline at end of file
+1	Event	()
\ No newline at end of file
diff --git a/tests/models/test_base_file_input.py b/tests/models/test_base_file_input.py
index 63a874125..57a8bdbe9 100644
--- a/tests/models/test_base_file_input.py
+++ b/tests/models/test_base_file_input.py
@@ -2,7 +2,7 @@
 import os
 import shutil
 
-from hed.models import Sidecar, EventsInput
+from hed.models import Sidecar, TabularInput
 from hed import schema
 from hed.validator import HedValidator
 
@@ -31,7 +31,7 @@ def tearDownClass(cls):
     #     sidecar = Sidecar(json_path)
     #     issues = sidecar.validate_entries(validator)
     #     self.assertEqual(len(issues), 0)
-    #     input_file = EventsInput(events_path, sidecars=sidecar)
+    #     input_file = TabularInput(events_path, sidecars=sidecar)
     #
     #     validation_issues = input_file.validate_file_sidecars(validator)
     #     self.assertEqual(len(validation_issues), 0)
@@ -50,7 +50,7 @@ def tearDownClass(cls):
     #     sidecar = Sidecar(json_path)
     #     issues = sidecar.validate_entries(validator)
     #     self.assertEqual(len(issues), 0)
-    #     input_file = EventsInput(events_path, sidecars=sidecar)
+    #     input_file = TabularInput(events_path, sidecars=sidecar)
     #
     #     validation_issues = input_file.validate_file_sidecars(validator)
     #     self.assertEqual(len(validation_issues), 0)
diff --git a/tests/models/test_column_mapper.py b/tests/models/test_column_mapper.py
index 1e1506f4c..0c084aa22 100644
--- a/tests/models/test_column_mapper.py
+++ b/tests/models/test_column_mapper.py
@@ -3,7 +3,7 @@
 
 from hed.models import ColumnMapper, ColumnType, ColumnMetadata, HedString, model_constants
 from hed.schema import load_schema
-
+from hed import HedFileError
 
 class Test(unittest.TestCase):
     schema_file = '../data/schema_test_data/HED8.0.0t.xml'
@@ -11,10 +11,10 @@ class Test(unittest.TestCase):
     @classmethod
     def setUpClass(cls):
         cls.event_mapper = ColumnMapper()
-        cls.integer_key_dictionary = {1: 'one', 2: 'two', 3: 'three'}
-        cls.one_based_tag_columns = [1, 2, 3]
+        cls.integer_key_dictionary = {0: 'one', 1: 'two', 2: 'three'}
+        cls.zero_based_tag_columns = [0, 1, 2]
         cls.zero_based_row_column_count = 3
-        cls.column_prefix_dictionary = {3: 'Event/Description/', 4: 'Event/Label/', 5: 'Event/Category/'}
+        cls.column_prefix_dictionary = {2: 'Event/Description/', 3: 'Event/Label/', 4: 'Event/Category/'}
         cls.category_key = 'Event/Category/'
         cls.category_participant_and_stimulus_tags = \
             HedString('Event/Category/Participant response, Event/Category/Stimulus')
@@ -51,7 +51,7 @@ def test_set_column_prefix_dict(self):
 
     def test_set_tag_columns(self):
         mapper = ColumnMapper()
-        mapper.set_tag_columns(self.one_based_tag_columns, finalize_mapping=True)
+        mapper.set_tag_columns(self.zero_based_tag_columns, finalize_mapping=True)
         self.assertTrue(len(mapper._final_column_map) >= 2)
 
     def test_optional_column(self):
@@ -138,7 +138,7 @@ def test_expansion_issues(self):
         mapper.add_columns(self.basic_attribute_column)
         mapper.set_column_map(self.basic_column_map)
         expanded_row = mapper.expand_row_tags(self.basic_event_row_invalid)
-        column_issues = expanded_row[model_constants.COLUMN_ISSUES][3]
+        column_issues = expanded_row[model_constants.COLUMN_ISSUES][2]
         self.assertEqual(len(column_issues), 1)
         self.assertTrue(self.basic_attribute_column in expanded_row)
 
@@ -150,24 +150,6 @@ def test_remove_prefix_if_needed(self):
         no_prefix_string = test_string_obj.get_as_form("org_tag", remove_prefix_func)
         self.assertEqual(str(no_prefix_string), str(self.complex_hed_tag_no_prefix))
 
-    def test_subtract_1_from_dictionary_keys(self):
-        one_subtracted_key_dictionary = self.event_mapper._subtract_1_from_dictionary_keys(self.integer_key_dictionary)
-        self.assertIsInstance(one_subtracted_key_dictionary, dict)
-        self.assertTrue(one_subtracted_key_dictionary)
-        original_dictionary_key_sum = sum(self.integer_key_dictionary.keys())
-        new_dictionary_key_sum = sum(one_subtracted_key_dictionary.keys())
-        original_dictionary_key_length = len(self.integer_key_dictionary.keys())
-        self.assertEqual(original_dictionary_key_sum - new_dictionary_key_sum, original_dictionary_key_length)
-
-    def test_subtract_1_from_list_elements(self):
-        one_subtracted_list = self.event_mapper._subtract_1_from_list_elements(self.one_based_tag_columns)
-        self.assertIsInstance(one_subtracted_list, list)
-        self.assertTrue(one_subtracted_list)
-        original_list_sum = sum(self.one_based_tag_columns)
-        new_list_sum = sum(one_subtracted_list)
-        original_list_length = len(self.one_based_tag_columns)
-        self.assertEqual(original_list_sum - new_list_sum, original_list_length)
-
     def test__prepend_prefix_to_required_tag_column_if_needed(self):
         prepended_hed_string = ColumnMetadata._prepend_prefix_to_required_tag_column_if_needed(
             self.category_tags, self.category_key)
@@ -185,7 +167,7 @@ def test_add_prefix_verify_short_tag_conversion(self):
             self.assertEqual("Character/D", tag.short_tag)
 
     def test_add_prefix_verify_short_tag_read(self):
-        column_mapper = ColumnMapper(column_prefix_dictionary={1: self.short_tag_key})
+        column_mapper = ColumnMapper(column_prefix_dictionary={0: self.short_tag_key})
         test_strings = {
             'test_no_prefix': self.short_tag_with_missing_prefix,
             'test_full_prefix': self.short_tag_key + self.short_tag_with_missing_prefix,
@@ -204,9 +186,8 @@ def test_add_prefix_verify_short_tag_read(self):
             expected_result = expected_results[test_key]
 
             expanded_row = column_mapper.expand_row_tags([test_string])
-            prepended_hed_string = expanded_row[model_constants.COLUMN_TO_HED_TAGS][1]
+            prepended_hed_string = expanded_row[model_constants.COLUMN_TO_HED_TAGS][0]
             self.assertEqual(expected_result, str(prepended_hed_string))
 
-
 if __name__ == '__main__':
     unittest.main()
diff --git a/tests/models/test_def_mapper.py b/tests/models/test_def_mapper.py
index 8dbd0c564..dde896ff0 100644
--- a/tests/models/test_def_mapper.py
+++ b/tests/models/test_def_mapper.py
@@ -4,6 +4,7 @@
 from hed import schema
 from hed.models import DefinitionDict, DefMapper, HedString
 from hed.validator import HedValidator
+from hed.errors import ErrorHandler, ErrorContext
 
 
 class Test(unittest.TestCase):
@@ -255,34 +256,23 @@ def test_def_no_content(self):
         def_issues = valid_empty.validate(def_mapper, expand_defs=False)
         self.assertFalse(def_issues)
 
-    def test_mutable(self):
-        basic_definition_string = "(Definition/TestDef, (Keypad-key/TestDef1,Keyboard-key/TestDef2))"
+    def test_duplicate_def(self):
         def_dict = DefinitionDict()
-        def_string = HedString(basic_definition_string)
-        def_string.convert_to_canonical_forms(self.hed_schema)
-        def_dict.check_for_definitions(def_string)
-
-        def_mapper = DefMapper(def_dict)
-        hed_ops = []
-        hed_ops.append(self.hed_schema)
-        hed_ops.append(def_mapper)
-
-        def_contents = def_dict.defs['testdef'].contents
-        def_contents.cascade_mutable(True)
-        # todo: clean this up some.  I think it's mostly good.
-        label_def_string = "Def/TestDef"
-        test_string = HedString(label_def_string)
-        test_string2 = HedString(label_def_string)
-        def_issues = test_string.validate(hed_ops, expand_defs=True)
-        def_issues = test_string2.validate(hed_ops, expand_defs=True)
-        tags_to_remove = test_string.find_tags(search_tags=["keypad-key"], recursive=True)
-
-        for tag, group in tags_to_remove:
-            test_string.remove([tag])
-
-
-        self.assertEqual(test_string, test_string2)
-
+        def_string = HedString(self.placeholder_definition_string)
+        def_string.convert_to_canonical_forms(None)
+        error_handler = ErrorHandler()
+        error_handler.push_error_context(ErrorContext.ROW, 5)
+        def_dict.check_for_definitions(def_string, error_handler=error_handler)
+        def_mapper = DefMapper([])
+        self.assertEqual(len(def_mapper.issues), 0)
+
+        def_mapper = DefMapper([def_dict, def_dict])
+        self.assertEqual(len(def_mapper.issues), 1)
+        self.assertTrue('ec_row' in def_mapper.issues[0])
+
+        def_mapper = DefMapper([def_dict, def_dict, def_dict])
+        self.assertEqual(len(def_mapper.issues), 2)
+        self.assertTrue('ec_row' in def_mapper.issues[0])
 
 
 if __name__ == '__main__':
diff --git a/tests/models/tests_expression_parser.py b/tests/models/test_expression_parser.py
similarity index 88%
rename from tests/models/tests_expression_parser.py
rename to tests/models/test_expression_parser.py
index 06c400dc7..52a0e83e3 100644
--- a/tests/models/tests_expression_parser.py
+++ b/tests/models/test_expression_parser.py
@@ -1,5 +1,6 @@
 import unittest
-from hed.models.hed_string import HedStringFrozen
+from hed.models.hed_string import HedStringFrozen, HedString
+from hed.models.hed_string_comb import HedStringComb
 from hed.models.expression_parser import TagExpressionParser
 import os
 from hed import schema
@@ -17,12 +18,38 @@ def base_test(self, parse_expr, search_strings):
 
         # print(f"Search Pattern: {expression._org_string}")
         for string, expected_result in search_strings.items():
-            hed_string = HedStringFrozen(string, self.hed_schema)
-            result = expression.search_hed_string(hed_string)
+            hed_string_frozen = HedStringFrozen(string, self.hed_schema)
+            result1 = expression.search_hed_string(hed_string_frozen)
             # print(f"\tSearching string '{str(hed_string)}'")
             # if result:
             #    print(f"\t\tFound as group(s) {str([str(r) for r in result])}")
-            self.assertEqual(bool(result), expected_result)
+            self.assertEqual(bool(result1), expected_result)
+
+            # Same test with HedString
+            hed_string = HedString(string, self.hed_schema)
+            result2 = expression.search_hed_string(hed_string)
+            # print(f"\tSearching string '{str(hed_string)}'")
+            # if result:
+            #    print(f"\t\tFound as group(s) {str([str(r) for r in result])}")
+            self.assertEqual(bool(result2), expected_result)
+
+            # Same test with HedStringCombined
+            hed_string_comb = HedStringComb([hed_string])
+            result3 = expression.search_hed_string(hed_string_comb)
+            # print(f"\tSearching string '{str(hed_string)}'")
+            # if result:
+            #    print(f"\t\tFound as group(s) {str([str(r) for r in result])}")
+            self.assertEqual(bool(result3), expected_result)
+
+            for r1, r2, r3 in zip(result1, result2, result3):
+                # Ensure r2 is only a HedString if r3 is.
+                if isinstance(r3, HedStringComb):
+                    self.assertIsInstance(r2, HedString)
+                else:
+                    self.assertNotIsInstance(r2, HedString)
+                self.assertEqual(r2, r3)
+            if len(hed_string_frozen.get_all_tags()) == len(hed_string_comb.get_all_tags()):
+                self.assertEqual(len(result1), len(result2))
 
     def test_broken_search_strings(self):
         test_search_strings = [
diff --git a/tests/models/test_sidecar.py b/tests/models/test_sidecar.py
index 51e729ff9..a1652d695 100644
--- a/tests/models/test_sidecar.py
+++ b/tests/models/test_sidecar.py
@@ -2,7 +2,7 @@
 import os
 import io
 
-from hed.errors import HedFileError
+from hed.errors import HedFileError, ValidationErrors
 from hed.models import ColumnMetadata, HedString, Sidecar
 from hed.validator import HedValidator
 from hed import schema
@@ -82,7 +82,7 @@ def test__iter__(self):
     # def set_hed_string(self, new_hed_string, position):
     #
     #
-    # def _add_single_col_type(self, column_name, dict_for_entry, column_type=None):
+    # def _add_single_column(self, column_name, dict_for_entry, column_type=None):
     #
     # def get_validation_issues(self):
     #
@@ -111,6 +111,14 @@ def test_validate_column_group(self):
                                                                                    extra_def_dicts=extra_def_dict)
         self.assertEqual(len(validation_issues), 0)
 
+    def test_duplicate_def(self):
+        sidecar = self.json_def_sidecar
+        def_dicts = sidecar.get_def_dicts()
+
+        issues = sidecar.validate_entries(extra_def_dicts=def_dicts)
+        self.assertEqual(len(issues), 5)
+        self.assertTrue(issues[0]['code'], ValidationErrors.HED_DEFINITION_INVALID)
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/tests/models/test_hed_file_input.py b/tests/models/test_spreadsheet_input.py
similarity index 81%
rename from tests/models/test_hed_file_input.py
rename to tests/models/test_spreadsheet_input.py
index b7b4caaa3..ea79826eb 100644
--- a/tests/models/test_hed_file_input.py
+++ b/tests/models/test_spreadsheet_input.py
@@ -3,7 +3,7 @@
 import io
 
 from hed.errors import HedFileError
-from hed.models import EventsInput, HedInput, model_constants, Sidecar
+from hed.models import TabularInput, SpreadsheetInput, model_constants, Sidecar
 import shutil
 from hed import schema
 
@@ -19,7 +19,7 @@ def setUpClass(cls):
         cls.hed_schema = schema.load_schema(hed_xml_file)
         cls.default_test_file_name = os.path.join(os.path.dirname(os.path.realpath(__file__)),
                                                   "../data/validator_tests/ExcelMultipleSheets.xlsx")
-        cls.generic_file_input = HedInput(cls.default_test_file_name)
+        cls.generic_file_input = SpreadsheetInput(cls.default_test_file_name)
         cls.integer_key_dictionary = {1: 'one', 2: 'two', 3: 'three'}
         cls.one_based_tag_columns = [1, 2, 3]
         cls.zero_based_tag_columns = [0, 1, 2, 3, 4]
@@ -45,8 +45,8 @@ def test_all(self):
         tag_columns = [4]
         worksheet_name = 'LKT Events'
 
-        file_input = HedInput(hed_input, has_column_names=has_column_names, worksheet_name=worksheet_name,
-                              tag_columns=tag_columns, column_prefix_dictionary=column_prefix_dictionary)
+        file_input = SpreadsheetInput(hed_input, has_column_names=has_column_names, worksheet_name=worksheet_name,
+                                      tag_columns=tag_columns, column_prefix_dictionary=column_prefix_dictionary)
 
         for row_number, column_to_hed_tags in file_input:
             break_here = 3
@@ -64,31 +64,31 @@ def test_get_row_hed_tags(self):
 
     def test_file_as_string(self):
         events_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),
-                                   '../data/validator_tests/bids_events.tsv')
+                                   '../data/validator_tests/bids_events_with_index.tsv')
 
         json_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),
                                  "../data/validator_tests/bids_events.json")
         sidecar = Sidecar(json_path)
         self.assertEqual(len(sidecar.validate_entries(expand_defs=True)), 0)
-        input_file = EventsInput(events_path, sidecars=sidecar)
+        input_file = TabularInput(events_path, sidecar=sidecar)
 
         with open(events_path) as file:
             events_file_as_string = io.StringIO(file.read())
-        input_file_from_string = EventsInput(file=events_file_as_string, sidecars=sidecar)
+        input_file_from_string = TabularInput(file=events_file_as_string, sidecar=sidecar)
 
         for (row_number, column_dict), (row_number2, column_dict) in zip(input_file, input_file_from_string):
             self.assertEqual(row_number, row_number2)
             self.assertEqual(column_dict, column_dict)
 
     def test_bad_file_inputs(self):
-        self.assertRaises(HedFileError, EventsInput, None)
+        self.assertRaises(HedFileError, TabularInput, None)
 
     def test_loading_binary(self):
         with open(self.default_test_file_name, "rb") as f:
-            self.assertRaises(HedFileError, HedInput, f)
+            self.assertRaises(HedFileError, SpreadsheetInput, f)
 
         with open(self.default_test_file_name, "rb") as f:
-            opened_binary_file = HedInput(f, file_type=".xlsx")
+            opened_binary_file = SpreadsheetInput(f, file_type=".xlsx")
             self.assertTrue(True)
 
     def test_to_excel(self):
@@ -121,26 +121,26 @@ def test_loading_and_reloading(self):
 
         test_input_file.to_excel(test_output_name)
 
-        reloaded_input = HedInput(test_output_name)
+        reloaded_input = SpreadsheetInput(test_output_name)
 
         self.assertTrue(test_input_file._dataframe.equals(reloaded_input._dataframe))
 
     def test_loading_and_reset_mapper(self):
         events_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),
-                                   '../data/validator_tests/bids_events.tsv')
+                                   '../data/validator_tests/bids_events_with_index.tsv')
         json_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),
                                  "../data/validator_tests/bids_events.json")
         sidecar = Sidecar(json_path)
         self.assertEqual(len(sidecar.validate_entries()), 0)
-        input_file_1 = EventsInput(events_path, sidecars=sidecar)
-        input_file_2 = EventsInput(events_path, sidecars=sidecar)
+        input_file_1 = TabularInput(events_path, sidecar=sidecar)
+        input_file_2 = TabularInput(events_path, sidecar=sidecar)
 
         input_file_2.reset_column_mapper()
 
         for (row_number, column_dict), (row_number2, column_dict2) in zip(input_file_1.iter_dataframe(),
                                                                           input_file_2.iter_dataframe()):
             self.assertEqual(row_number, row_number2,
-                             f"EventsInput should have row {row_number} equal to {row_number2} after reset")
+                             f"TabularInput should have row {row_number} equal to {row_number2} after reset")
             self.assertTrue(len(column_dict) == 5,
                             f"The column dictionary for row {row_number} should have the right length")
             self.assertTrue(len(column_dict2) == 11,
@@ -149,21 +149,21 @@ def test_loading_and_reset_mapper(self):
     def test_no_column_header_and_convert(self):
         events_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),
                                    '../data/model_tests/no_column_header.tsv')
-        hed_input = HedInput(events_path, has_column_names=False, tag_columns=[1, 2])
+        hed_input = SpreadsheetInput(events_path, has_column_names=False, tag_columns=[1, 2])
         hed_input.convert_to_long(self.hed_schema)
 
         events_path_long = os.path.join(os.path.dirname(os.path.realpath(__file__)),
                                         '../data/model_tests/no_column_header_long.tsv')
-        hed_input_long = HedInput(events_path_long, has_column_names=False, tag_columns=[1, 2])
+        hed_input_long = SpreadsheetInput(events_path_long, has_column_names=False, tag_columns=[1, 2])
         for column1, column2 in zip(hed_input, hed_input_long):
             self.assertEqual(column1, column2)
 
         events_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),
                                    '../data/model_tests/no_column_header.tsv')
-        hed_input = HedInput(events_path, has_column_names=False, tag_columns=[1, 2])
+        hed_input = SpreadsheetInput(events_path, has_column_names=False, tag_columns=[1, 2])
         events_path_long = os.path.join(os.path.dirname(os.path.realpath(__file__)),
                                         '../data/model_tests/no_column_header_long.tsv')
-        hed_input_long = HedInput(events_path_long, has_column_names=False, tag_columns=[1, 2])
+        hed_input_long = SpreadsheetInput(events_path_long, has_column_names=False, tag_columns=[1, 2])
         hed_input_long.convert_to_short(self.hed_schema)
         for column1, column2 in zip(hed_input, hed_input_long):
             self.assertEqual(column1, column2)
@@ -172,12 +172,12 @@ def test_convert_short_long_with_definitions(self):
         # Verify behavior works as expected even if definitions are present
         events_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),
                                    '../data/model_tests/no_column_header_definition.tsv')
-        hed_input = HedInput(events_path, has_column_names=False, tag_columns=[1, 2])
+        hed_input = SpreadsheetInput(events_path, has_column_names=False, tag_columns=[1, 2])
         hed_input.convert_to_long(self.hed_schema)
 
         events_path_long = os.path.join(os.path.dirname(os.path.realpath(__file__)),
                                         '../data/model_tests/no_column_header_definition_long.tsv')
-        hed_input_long = HedInput(events_path_long, has_column_names=False, tag_columns=[1, 2])
+        hed_input_long = SpreadsheetInput(events_path_long, has_column_names=False, tag_columns=[1, 2])
         for column1, column2 in zip(hed_input, hed_input_long):
             self.assertEqual(column1, column2)
 
diff --git a/tests/models/test_events_file_input.py b/tests/models/test_tabular_input.py
similarity index 93%
rename from tests/models/test_events_file_input.py
rename to tests/models/test_tabular_input.py
index 7217e52f6..c1ac15abd 100644
--- a/tests/models/test_events_file_input.py
+++ b/tests/models/test_tabular_input.py
@@ -2,7 +2,7 @@
 import os
 import shutil
 
-from hed.models import Sidecar, EventsInput
+from hed.models import Sidecar, TabularInput
 from hed import schema
 from hed.validator import HedValidator
 
@@ -31,13 +31,14 @@ def test_missing_column_name_issue(self):
         sidecar = Sidecar(json_path)
         issues = sidecar.validate_entries(validator)
         self.assertEqual(len(issues), 0)
-        input_file = EventsInput(events_path, sidecars=sidecar)
+        input_file = TabularInput(events_path, sidecar=sidecar)
 
         validation_issues = input_file.validate_file_sidecars(validator)
         self.assertEqual(len(validation_issues), 0)
         validation_issues = input_file.validate_file(validator, check_for_warnings=True)
         self.assertEqual(len(validation_issues), 1)
 
+
     def test_expand_column_issues(self):
         schema_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),
                                    '../data/validator_tests/bids_schema.mediawiki')
@@ -50,7 +51,7 @@ def test_expand_column_issues(self):
         sidecar = Sidecar(json_path)
         issues = sidecar.validate_entries(validator)
         self.assertEqual(len(issues), 0)
-        input_file = EventsInput(events_path, sidecars=sidecar)
+        input_file = TabularInput(events_path, sidecar=sidecar)
 
         #Fix whatever is wrong with onset tag here.  It's thinking Description/Onset continues is an invalid tag???'
         validation_issues = input_file.validate_file_sidecars(validator)
diff --git a/tests/models/test_timeseries_input.py b/tests/models/test_timeseries_input.py
new file mode 100644
index 000000000..4c740210f
--- /dev/null
+++ b/tests/models/test_timeseries_input.py
@@ -0,0 +1,32 @@
+import unittest
+import os
+import shutil
+
+from hed.models import Sidecar, TimeseriesInput
+from hed import schema
+from hed.validator import HedValidator
+
+# TODO: Add tests about correct handling of 'n/a'
+
+
+class Test(unittest.TestCase):
+    @classmethod
+    def setUpClass(cls):
+        cls.base_output_folder = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../data/tests_output/")
+        os.makedirs(cls.base_output_folder, exist_ok=True)
+
+    @classmethod
+    def tearDownClass(cls):
+        shutil.rmtree(cls.base_output_folder)
+
+    def test_constructor(self):
+        schema_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                   '../data/validator_tests/bids_schema.mediawiki')
+        events_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                   '../data/model_tests/no_column_header.tsv')
+        input_file = TimeseriesInput(events_path)
+        self.assertIsInstance(input_file, TimeseriesInput, "TimeseriesInput constructor creates a timeseries object")
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/tools/annotation/__init__.py b/tests/tools/analysis/__init__.py
similarity index 100%
rename from tests/tools/annotation/__init__.py
rename to tests/tools/analysis/__init__.py
diff --git a/tests/tools/analysis/test_analysis_util.py b/tests/tools/analysis/test_analysis_util.py
new file mode 100644
index 000000000..45bd2c3d7
--- /dev/null
+++ b/tests/tools/analysis/test_analysis_util.py
@@ -0,0 +1,117 @@
+import os
+import io
+import json
+import unittest
+from pandas import DataFrame
+from hed import schema as hedschema
+from hed.models import Sidecar, TabularInput, HedStringComb
+from hed.tools import assemble_hed, get_assembled_strings, search_tabular
+
+
+# noinspection PyBroadException
+class Test(unittest.TestCase):
+
+    @classmethod
+    def setUpClass(cls):
+        bids_root_path = os.path.realpath(os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                          '../../data/bids/eeg_ds003654s_hed'))
+        schema_path = os.path.realpath(os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                       '../../data/schema_test_data/HED8.0.0.xml'))
+        cls.bids_root_path = bids_root_path
+        json_path = os.path.realpath(os.path.join(bids_root_path, 'task-FacePerception_events.json'))
+        events_path = os.path.realpath(os.path.join(bids_root_path,
+                                                    'sub-002/eeg/sub-002_task-FacePerception_run-1_events.tsv'))
+
+        cls.hed_schema = hedschema.load_schema(schema_path)
+        cls.sidecar = Sidecar(json_path, name='face_sub1_json')
+        cls.input_data = TabularInput(events_path, sidecar=cls.sidecar, name="face_sub1_events")
+
+    def test_get_assembled_strings_no_schema(self):
+        hed_list1 = get_assembled_strings(self.input_data, expand_defs=False)
+        self.assertIsInstance(hed_list1, list, "get_assembled_groups should return a list when expand defs is False")
+        self.assertIsInstance(hed_list1[0], HedStringComb)
+        hed_strings1 = [str(hed) for hed in hed_list1]
+        self.assertIsInstance(hed_strings1[0], str, "get_assembled_strings can be converted.")
+        self.assertIsInstance(hed_strings1, list)
+        hed_strings_joined1 = (",").join(hed_strings1)
+        self.assertEqual(hed_strings_joined1.find("Def-expand/"), -1,
+                          "get_assembled_strings should not have Def-expand when expand_defs is False" )
+        self.assertNotEqual(hed_strings_joined1.find("Def/"), -1,
+                          "get_assembled_strings should have Def/ when expand_defs is False")
+        hed_list2 = get_assembled_strings(self.input_data, expand_defs=True)
+        self.assertIsInstance(hed_list2, list, "get_assembled_groups should return a list")
+        self.assertIsInstance(hed_list2[0], HedStringComb)
+        hed_strings2 = [str(hed) for hed in hed_list2]
+        self.assertIsInstance(hed_strings2[0], str, "get_assembled_strings can be converted.")
+        self.assertIsInstance(hed_strings2, list, "get_assembled")
+        hed_strings_joined2 = (",").join(hed_strings2)
+        self.assertNotEqual(hed_strings_joined2.find("Def-expand/"), -1,
+                          "get_assembled_strings should have Def-expand when expand_defs is True" )
+        self.assertEqual(hed_strings_joined2.find("Def/"), -1,
+                          "get_assembled_strings should not have Def/ when expand_defs is True")
+
+    def test_get_assembled_strings_with_schema(self):
+        hed_list1 = get_assembled_strings(self.input_data, hed_schema=self.hed_schema, expand_defs=False)
+        self.assertIsInstance(hed_list1, list,
+                              "get_assembled_groups should return a list when expand defs is False")
+        self.assertIsInstance(hed_list1[0], HedStringComb)
+        hed_strings1 = [str(hed) for hed in hed_list1]
+        self.assertIsInstance(hed_strings1[0], str, "get_assembled_strings can be converted.")
+        self.assertIsInstance(hed_strings1, list)
+        hed_strings_joined1 = (",").join(hed_strings1)
+        self.assertEqual(hed_strings_joined1.find("Def-expand/"), -1,
+                         "get_assembled_strings should not have Def-expand when expand_defs is False")
+        self.assertNotEqual(hed_strings_joined1.find("Def/"), -1,
+                            "get_assembled_strings should have Def/ when expand_defs is False")
+        hed_list2 = get_assembled_strings(self.input_data, hed_schema=self.hed_schema, expand_defs=True)
+        self.assertIsInstance(hed_list2, list, "get_assembled_groups should return a list")
+        self.assertIsInstance(hed_list2[0], HedStringComb)
+        hed_strings2 = [str(hed) for hed in hed_list2]
+        self.assertIsInstance(hed_strings2[0], str, "get_assembled_strings can be converted.")
+        self.assertIsInstance(hed_strings2, list, "get_assembled")
+        hed_strings_joined2 = (",").join(hed_strings2)
+        self.assertNotEqual(hed_strings_joined2.find("Def-expand/"), -1,
+                            "get_assembled_strings should have Def-expand when expand_defs is True")
+        self.assertEqual(hed_strings_joined2.find("Def/"), -1,
+                         "get_assembled_strings should not have Def/ when expand_defs is True")
+
+    def test_assemble_hed(self):
+        df1 = assemble_hed(self.input_data, columns_included=["onset", "duration", "event_type"], expand_defs=False)
+        self.assertIsInstance(df1, DataFrame, "hed_assemble should return a dataframe when columns are included")
+        columns1 = list(df1.columns)
+        self.assertEqual(len(columns1), 4,
+                         "assemble_hed should return the correct number of columns when columns are included ")
+        first_str1 = df1.iloc[0]['HED_assembled']
+        self.assertNotEqual(first_str1.find('Def/'), -1, "assemble_hed with no def expand has Def tags")
+        self.assertEqual(first_str1.find('Def-expand'), -1,
+                         "assemble_hed with no def expand does not have Def-expand tags")
+        df2 = assemble_hed(self.input_data, columns_included=["onset", "duration", "event_type"], expand_defs=True)
+        first_str2 = df2.iloc[0]['HED_assembled']
+        self.assertEqual(first_str2.find('Def/'), -1, "assemble_hed with def expand has no Def tag")
+        self.assertNotEqual(first_str2.find('Def-expand/'), -1, "assemble_hed with def expand has Def-expand tags")
+
+        df3 = assemble_hed(self.input_data,
+                           columns_included=["onset", "baloney", "duration", "event_type"], expand_defs=False)
+        columns3 = list(df3.columns)
+        self.assertEqual(len(columns3), 4,
+                         "assemble_hed should return the correct number of columns when bad columns are included ")
+
+    def test_search_tabular(self):
+        query1 = "sensory-event"
+        df1 = search_tabular(self.input_data, self.hed_schema, query1, columns_included=None)
+        self.assertIsInstance(df1, DataFrame, "search_tabular returns a dataframe when the query is satisfied.")
+        self.assertEqual(len(df1.columns), 2, "search_tabular has the right number of columns when query okay")
+        self.assertEqual(len(df1.index), 438, "search_tabular has right number of rows when query okay")
+        query2 = 'data-feature'
+        df2 = search_tabular(self.input_data, self.hed_schema, query2, columns_included=None)
+        self.assertFalse(df2, "search_tabular returns None when query is not satisfied.")
+
+        query3 = "sensory-event"
+        df3 = search_tabular(self.input_data, self.hed_schema, query3, columns_included=['event_type', 'rep_status'])
+        self.assertIsInstance(df3, DataFrame, "search_tabular returns a DataFrame when extra columns")
+        self.assertEqual(len(df3.columns), 4, "search_tabular returns right number of columns when extra columns")
+        self.assertEqual(len(df3.index), 438, "search_tabular has right number of rows when query okay")
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/tools/annotation/test_annotation_util.py b/tests/tools/analysis/test_annotation_util.py
similarity index 97%
rename from tests/tools/annotation/test_annotation_util.py
rename to tests/tools/analysis/test_annotation_util.py
index c3e9407b6..bc06ce899 100644
--- a/tests/tools/annotation/test_annotation_util.py
+++ b/tests/tools/analysis/test_annotation_util.py
@@ -5,7 +5,7 @@
 from pandas import DataFrame
 from hed import schema as hedschema
 from hed.models import Sidecar
-from hed.tools import BidsTsvSummary, check_df_columns, df_to_hed, extract_tags, hed_to_df, merge_hed_dict
+from hed.tools import BidsTabularSummary, check_df_columns, df_to_hed, extract_tags, hed_to_df, merge_hed_dict
 from hed.tools.analysis.annotation_util import _find_last_pos, _find_first_pos, \
     _flatten_cat_col, _flatten_val_col, _get_value_entry, trim_back, trim_front, _tag_list_to_str, _update_cat_dict, \
     generate_sidecar_entry
@@ -256,7 +256,7 @@ def test_merge_hed_dict_full(self):
         value_columns = ["rep_lag", "stim_file", "value"]
         event_files = get_file_list(self.bids_root_path, extensions=[".tsv"], name_suffix="_events",
                                     exclude_dirs=exclude_dirs)
-        value_sum = BidsTsvSummary(value_cols=value_columns, skip_cols=skip_columns)
+        value_sum = BidsTabularSummary(value_cols=value_columns, skip_cols=skip_columns)
         value_sum.update(event_files)
         sidecar_template = value_sum.extract_sidecar_template()
         example_spreadsheet = hed_to_df(sidecar_template)
diff --git a/tests/tools/annotation/test_definition_summary.py b/tests/tools/analysis/test_definition_summary.py
similarity index 63%
rename from tests/tools/annotation/test_definition_summary.py
rename to tests/tools/analysis/test_definition_summary.py
index dc1c9c09a..8ea692d0a 100644
--- a/tests/tools/annotation/test_definition_summary.py
+++ b/tests/tools/analysis/test_definition_summary.py
@@ -17,17 +17,13 @@ def test_constructor(self):
                      f"(Definition/Famous-face-cond, (Condition-variable/Face-type, (Image, (Face, Famous)),"
                      f"Description/A face that should be recognized by the participants))"]
 
-        sidecar1 = BidsSidecarFile(self.test_json, set_contents=False)
+        sidecar1 = BidsSidecarFile(self.test_json)
         self.assertEqual(sidecar1.suffix, 'events', "BidsSidecarFile should have correct name_suffix")
         self.assertEqual(sidecar1.ext, '.json', "BidsSidecarFile should have correct ext")
         self.assertEqual(len(sidecar1.entity_dict), 1, "BidsSidecarFile should have right number of entity_dict")
-        self.assertFalse(sidecar1.contents)
-
-        sidecar2 = BidsSidecarFile(self.test_json, set_contents=True)
-        self.assertEqual(sidecar2.suffix, 'events', "BidsSidecarFile should have correct name_suffix")
-        self.assertEqual(sidecar2.ext, '.json', "BidsSidecarFile should have correct ext")
-        self.assertEqual(len(sidecar2.entity_dict), 1, "BidsSidecarFile should have right number of entity_dict")
-        self.assertIsInstance(sidecar2.contents, Sidecar, "BidsSidecarFile should contain a Sidecar")
+        self.assertFalse(sidecar1.contents, "BidsSidecarFile should not have contents on construction.")
+        sidecar1.set_contents()
+        self.assertIsInstance(sidecar1.contents, Sidecar, "BidsSidecarFile should contain a Sidecar after set_contents")
 
 
 if __name__ == '__main__':
diff --git a/tests/tools/annotation/test_file_dictionary.py b/tests/tools/analysis/test_file_dictionary.py
similarity index 100%
rename from tests/tools/annotation/test_file_dictionary.py
rename to tests/tools/analysis/test_file_dictionary.py
diff --git a/tests/tools/annotation/test_key_map.py b/tests/tools/analysis/test_key_map.py
similarity index 100%
rename from tests/tools/annotation/test_key_map.py
rename to tests/tools/analysis/test_key_map.py
diff --git a/tests/tools/annotation/test_key_template.py b/tests/tools/analysis/test_key_template.py
similarity index 100%
rename from tests/tools/annotation/test_key_template.py
rename to tests/tools/analysis/test_key_template.py
diff --git a/tests/tools/annotation/test_sidecar_summary.py b/tests/tools/analysis/test_sidecar_summary.py
similarity index 82%
rename from tests/tools/annotation/test_sidecar_summary.py
rename to tests/tools/analysis/test_sidecar_summary.py
index a1afe969c..74669ecd4 100644
--- a/tests/tools/annotation/test_sidecar_summary.py
+++ b/tests/tools/analysis/test_sidecar_summary.py
@@ -12,16 +12,18 @@ def setUpClass(cls):
                                          '../../data/bids/eeg_ds003654s_hed/task-FacePerception_events.json')
 
     def test_constructor(self):
-        sidecar1 = BidsSidecarFile(self.sidecar_path1, set_contents=False)
+        sidecar1 = BidsSidecarFile(self.sidecar_path1)
         self.assertEqual(sidecar1.suffix, 'events', "BidsSidecarFile should have correct name_suffix")
         self.assertEqual(sidecar1.ext, '.json', "BidsSidecarFile should have correct ext")
         self.assertEqual(len(sidecar1.entity_dict), 1, "BidsSidecarFile should have right number of entity_dict")
         self.assertFalse(sidecar1.contents)
 
-        sidecar2 = BidsSidecarFile(self.sidecar_path1, set_contents=True)
+        sidecar2 = BidsSidecarFile(self.sidecar_path1)
         self.assertEqual(sidecar2.suffix, 'events', "BidsSidecarFile should have correct name_suffix")
         self.assertEqual(sidecar2.ext, '.json', "BidsSidecarFile should have correct ext")
         self.assertEqual(len(sidecar2.entity_dict), 1, "BidsSidecarFile should have right number of entity_dict")
+        self.assertFalse(sidecar2.contents, "BidsSidecarFile has no contents unless set")
+        sidecar2.set_contents()
         self.assertIsInstance(sidecar2.contents, Sidecar, "BidsSidecarFile should contain a Sidecar")
 
 
diff --git a/tests/tools/annotation/test_summary_util.py b/tests/tools/analysis/test_summary_util.py
similarity index 100%
rename from tests/tools/annotation/test_summary_util.py
rename to tests/tools/analysis/test_summary_util.py
diff --git a/tests/tools/bids/test_bids_dataset.py b/tests/tools/bids/test_bids_dataset.py
index 3b2eddada..154f7d38d 100644
--- a/tests/tools/bids/test_bids_dataset.py
+++ b/tests/tools/bids/test_bids_dataset.py
@@ -1,9 +1,10 @@
 import os
 import unittest
 import pandas as pd
+from hed.schema.hed_schema_io import load_schema, load_schema_version
 from hed.schema.hed_schema_group import HedSchemaGroup
 from hed.tools.bids.bids_dataset import BidsDataset
-from hed.tools.bids.bids_event_files import BidsEventFiles
+from hed.tools.bids.bids_file_group import BidsFileGroup
 
 
 class Test(unittest.TestCase):
@@ -11,15 +12,17 @@ class Test(unittest.TestCase):
     @classmethod
     def setUpClass(cls):
         cls.root_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), '../../data/bids/eeg_ds003654s_hed')
-        cls.library_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),
-                                        '../../data/bids/eeg_ds003654s_hed_library')
+        cls.library_path = os.path.realpath(os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                        '../../data/bids/eeg_ds003654s_hed_library'))
 
-    def test_bids_constructor(self):
+    def test_constructor(self):
         bids1 = BidsDataset(Test.root_path)
         self.assertIsInstance(bids1, BidsDataset, "BidsDataset should create a valid object from valid dataset")
         self.assertIsInstance(bids1.participants, pd.DataFrame, "BidsDataset participants should be a DataFrame")
         self.assertIsInstance(bids1.dataset_description, dict, "BidsDataset dataset_description should be a dict")
-        self.assertIsInstance(bids1.event_files, BidsEventFiles, "BidsDataset event_files should be  BidsEventFiles")
+        for group in bids1.tabular_files.values():
+            self.assertIsInstance(group, BidsFileGroup, "BidsDataset event files should be in a BidsFileGroup")
+
         self.assertIsInstance(bids1.schemas, HedSchemaGroup, "BidsDataset schemas should be HedSchemaGroup")
 
         bids2 = BidsDataset(self.library_path)
@@ -29,12 +32,14 @@ def test_bids_constructor(self):
                               "BidsDataset with libraries should have a participants that is a DataFrame")
         self.assertIsInstance(bids2.dataset_description, dict,
                               "BidsDataset with libraries dataset_description should be a dict")
-        self.assertIsInstance(bids2.event_files, BidsEventFiles,
-                              "BidsDataset with libraries event_files should be  BidsEventFiles")
+        for group in bids2.tabular_files.values():
+            self.assertIsInstance(group, BidsFileGroup,
+                                  "BidsDataset event files with libraries should be in a BidsFileGroup")
+
         self.assertIsInstance(bids2.schemas, HedSchemaGroup,
                               "BidsDataset with libraries should have schemas that is a HedSchemaGroup")
 
-    def test_bids_validator(self):
+    def test_validator(self):
         bids1 = BidsDataset(self.root_path)
         self.assertIsInstance(bids1, BidsDataset, "BidsDataset should create a valid object from valid dataset")
         issues = bids1.validate()
@@ -43,8 +48,7 @@ def test_bids_validator(self):
         self.assertTrue(issues,
                         "BidsDataset validate should return issues when check_for_warnings is True")
         issues = bids1.validate(check_for_warnings=False)
-        self.assertFalse(issues,
-                         "BidsDataset validate should return no issues when check_for_warnings is False")
+        self.assertFalse(issues, "BidsDataset validate should return no issues when check_for_warnings is False")
 
         bids2 = BidsDataset(self.library_path)
         self.assertIsInstance(bids2, BidsDataset,
@@ -59,6 +63,35 @@ def test_bids_validator(self):
         self.assertFalse(issues,
                          "BidsDataset with libraries should return no issues when check_for_warnings is False")
 
+    def test_with_schema_group(self):
+
+        base_version = '8.0.0'
+        library1_url = "https://raw.githubusercontent.com/hed-standard/hed-schema-library/main/" + \
+                       "library_schemas/score/hedxml/HED_score_0.0.1.xml"
+        library2_url = "https://raw.githubusercontent.com/hed-standard/hed-schema-library/main/" + \
+                       "library_schemas/testlib/hedxml/HED_testlib_1.0.2.xml"
+        schema_list = [load_schema_version(xml_version=base_version)]
+        schema_list.append(load_schema(library1_url, library_prefix="sc"))
+        schema_list.append(load_schema(library2_url, library_prefix="test"))
+        x = HedSchemaGroup(schema_list)
+        bids1 = BidsDataset(self.library_path, schema_group=x)
+        self.assertIsInstance(bids1, BidsDataset,
+                              "BidsDataset with libraries should create a valid object from valid dataset")
+        self.assertIsInstance(bids1.participants, pd.DataFrame,
+                              "BidsDataset with libraries should have a participants that is a DataFrame")
+        self.assertIsInstance(bids1.dataset_description, dict,
+                              "BidsDataset with libraries dataset_description should be a dict")
+        for group in bids1.tabular_files.values():
+            self.assertIsInstance(group, BidsFileGroup,
+                                  "BidsDataset with libraries event_files should be  BidsFileGroup")
+        self.assertIsInstance(bids1.schemas, HedSchemaGroup,
+                              "BidsDataset with libraries should have schemas that is a HedSchemaGroup")
+        issues = bids1.validate()
+        self.assertTrue(issues, "BidsDataset validate should return issues when the default check_for_warnings is used")
+        issues = bids1.validate(check_for_warnings=True)
+        self.assertTrue(issues, "BidsDataset validate should return issues when check_for_warnings is True")
+        issues = bids1.validate(check_for_warnings=False)
+
     def test_get_summary(self):
         bids1 = BidsDataset(self.root_path)
         summary1 = bids1.get_summary()
diff --git a/tests/tools/bids/test_bids_event_file.py b/tests/tools/bids/test_bids_event_file.py
deleted file mode 100644
index bc5e4f82f..000000000
--- a/tests/tools/bids/test_bids_event_file.py
+++ /dev/null
@@ -1,59 +0,0 @@
-import os
-import unittest
-from hed.models import EventsInput
-from hed.tools.bids.bids_event_file import BidsEventFile
-from hed.tools.bids.bids_sidecar_file import BidsSidecarFile
-
-
-class Test(unittest.TestCase):
-
-    @classmethod
-    def setUpClass(cls):
-        cls.event_path = \
-            os.path.join(os.path.dirname(os.path.realpath(__file__)),
-                         '../../data/bids/eeg_ds003654s_hed/sub-002/eeg/sub-002_task-FacePerception_run-1_events.tsv')
-        cls.sidecar_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),
-                                        '../../data/bids/eeg_ds003654s_hed/task-FacePerception_events.json')
-
-    def test_constructor(self):
-        events = BidsEventFile(Test.event_path)
-        self.assertEqual(events.suffix, 'events', "BidsEventFile should have correct events suffix")
-        self.assertEqual(events.ext, '.tsv', "BidsEventFile should have a .tsv extension")
-        self.assertEqual(len(events.entity_dict), 3, "BidsEventFile should have right number of entity_dict")
-        events_str = str(events)
-        self.assertTrue(events_str, "BidsEventFile should have a string representation")
-
-    def test_set_contents(self):
-        events = BidsEventFile(Test.event_path)
-        self.assertFalse(events.contents, "BidsEventFile should have no contents until set")
-        events.set_contents()
-        self.assertIsInstance(events.contents, EventsInput,
-                              "BidsEventFile should have EventsInput contents after setting")
-        events.clear_contents()
-        self.assertFalse(events.contents, "BidsEventFile should have no contents after clearing")
-
-    def test_set_sidecars(self):
-        events = BidsEventFile(Test.event_path)
-        self.assertFalse(events.sidecars, "BidsEventFile should have no sidecars on construction")
-        sidecar = BidsSidecarFile(Test.sidecar_path)
-        events.set_sidecars(sidecar)
-        self.assertIsInstance(events.sidecars, list, "BidsEventFile converts a single sidecar to a list")
-        events.set_sidecars([sidecar])
-        self.assertIsInstance(events.sidecars, list, "BidsEventFile is a list")
-        self.assertEqual(len(events.sidecars), 1, "BidsEventFile has the correct number of sidecars")
-        self.assertIsInstance(events.sidecars[0], BidsSidecarFile,
-                              "BidsEventFile has sidecars which are of type BidsSidecarFile")
-
-    def test_set_sidecars_empty(self):
-        events = BidsEventFile(Test.event_path)
-        events.set_sidecars([])
-        self.assertIsInstance(events.sidecars, list, "BidsEventFile is a list event when empty")
-        self.assertFalse(events.sidecars, "BidsEventFile can have an empty list of sidecars")
-
-        events.set_sidecars(None)
-        self.assertIsInstance(events.sidecars, list, "BidsEventFile is a list event when empty")
-        self.assertFalse(events.sidecars, "BidsEventFile can have an empty list of sidecars")
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/tests/tools/bids/test_bids_event_files.py b/tests/tools/bids/test_bids_event_files.py
deleted file mode 100644
index aaf8f090c..000000000
--- a/tests/tools/bids/test_bids_event_files.py
+++ /dev/null
@@ -1,43 +0,0 @@
-import os
-import unittest
-from hed.schema.hed_schema_io import load_schema
-from hed.tools.bids.bids_event_files import BidsEventFiles
-from hed.validator.hed_validator import HedValidator
-
-
-class Test(unittest.TestCase):
-
-    @classmethod
-    def setUpClass(cls):
-        cls.root_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), '../../data/bids/eeg_ds003654s_hed')
-        cls.event_path = \
-            os.path.join(os.path.dirname(os.path.realpath(__file__)),
-                         '../../data/bids/eeg_ds003654s_hed/sub-002/eeg/sub-002_task-FacePerception_run-1_events.tsv')
-        cls.sidecar_path = \
-            os.path.join(os.path.dirname(os.path.realpath(__file__)),
-                         '../../data/bids/eeg_ds003654s_hed/task-FacePerception_events.tsv')
-
-    def test_constructor(self):
-        events = BidsEventFiles(Test.root_path)
-        self.assertIsInstance(events, BidsEventFiles, "BidsEventFiles should create an BidsEventFiles instance")
-        self.assertIsInstance(events.events_dict, dict, "BidsEventFiles should have an event files dictionary")
-        self.assertEqual(len(events.events_dict), 6, "BidsEventFiles event files dictionary should have 2 entries")
-        self.assertIsInstance(events.sidecar_dict, dict, "BidsEventFiles should have sidecar files dictionary")
-        self.assertEqual(len(events.sidecar_dict), 1, "BidsEventFiles event files dictionary should have 1 entry")
-        self.assertIsInstance(events.sidecar_dir_dict, dict, "BidsEventFiles should have sidecar directory dictionary")
-
-    def test_validator(self):
-        events = BidsEventFiles(Test.root_path)
-        hed_schema = \
-            load_schema('https://raw.githubusercontent.com/hed-standard/hed-specification/master/hedxml/HED8.0.0.xml')
-        validator = HedValidator(hed_schema)
-        validation_issues = events.validate(hed_ops=[validator], check_for_warnings=False)
-        self.assertFalse(validation_issues, "BidsEventFiles should have no validation errors")
-        validation_issues = events.validate(hed_ops=[validator], check_for_warnings=True)
-        self.assertTrue(validation_issues, "BidsEventFiles should have validation warnings")
-        self.assertEqual(len(validation_issues), 6,
-                         "BidsEventFiles should have 2 validation warnings for missing columns")
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/tests/tools/bids/test_bids_file_group.py b/tests/tools/bids/test_bids_file_group.py
new file mode 100644
index 000000000..9412ed578
--- /dev/null
+++ b/tests/tools/bids/test_bids_file_group.py
@@ -0,0 +1,43 @@
+import os
+import unittest
+from hed.schema.hed_schema_io import load_schema
+from hed.tools.bids.bids_file_group import BidsFileGroup
+from hed.validator.hed_validator import HedValidator
+
+
+class Test(unittest.TestCase):
+
+    @classmethod
+    def setUpClass(cls):
+        cls.root_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), '../../data/bids/eeg_ds003654s_hed')
+        cls.event_path = \
+            os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                         '../../data/bids/eeg_ds003654s_hed/sub-002/eeg/sub-002_task-FacePerception_run-1_events.tsv')
+        cls.sidecar_path = \
+            os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                         '../../data/bids/eeg_ds003654s_hed/task-FacePerception_events.tsv')
+
+    def test_constructor(self):
+        events = BidsFileGroup(Test.root_path)
+        self.assertIsInstance(events, BidsFileGroup, "BidsFileGroup should create an BidsFileGroup instance")
+        self.assertIsInstance(events.datafile_dict, dict, "BidsFileGroup should have an event files dictionary")
+        self.assertEqual(len(events.datafile_dict), 6, "BidsFileGroup event files dictionary should have 2 entries")
+        self.assertIsInstance(events.sidecar_dict, dict, "BidsFileGroup should have sidecar files dictionary")
+        self.assertEqual(len(events.sidecar_dict), 1, "BidsFileGroup event files dictionary should have 1 entry")
+        self.assertIsInstance(events.sidecar_dir_dict, dict, "BidsFileGroup should have sidecar directory dictionary")
+
+    def test_validator(self):
+        events = BidsFileGroup(Test.root_path)
+        hed_schema = \
+            load_schema('https://raw.githubusercontent.com/hed-standard/hed-specification/master/hedxml/HED8.0.0.xml')
+        validator = HedValidator(hed_schema)
+        validation_issues = events.validate_datafiles(hed_ops=[validator], check_for_warnings=False)
+        self.assertFalse(validation_issues, "BidsFileGroup should have no validation errors")
+        validation_issues = events.validate_datafiles(hed_ops=[validator], check_for_warnings=True)
+        self.assertTrue(validation_issues, "BidsFileGroup should have validation warnings")
+        self.assertEqual(len(validation_issues), 6,
+                         "BidsFileGroup should have 2 validation warnings for missing columns")
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/tools/bids/test_bids_json_file.py b/tests/tools/bids/test_bids_json_file.py
deleted file mode 100644
index 57f1ac260..000000000
--- a/tests/tools/bids/test_bids_json_file.py
+++ /dev/null
@@ -1,58 +0,0 @@
-import os
-import unittest
-from hed.errors import HedFileError
-from hed.tools import BidsJsonFile
-
-
-class Test(unittest.TestCase):
-
-    @classmethod
-    def setUpClass(cls):
-        cls.description_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),
-                                            '../../data/bids/eeg_ds003654s_hed/dataset_description.json')
-        cls.json_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),
-                                     '../../data/bids/eeg_ds003654s_hed/task-FacePerception_events.json')
-
-    def test_constructor(self):
-        json1 = BidsJsonFile(Test.json_path, set_contents=False)
-        self.assertEqual(json1.suffix, 'events', "BidsJsonFile should have correct suffix")
-        self.assertEqual(json1.ext, '.json', "BidsJsonFile should have correct ext")
-        self.assertEqual(len(json1.entity_dict), 1, "BidsJsonFile should have right size entity_dict")
-        self.assertFalse(json1.contents)
-
-        sidecar2 = BidsJsonFile(Test.json_path, set_contents=True)
-        self.assertEqual(sidecar2.suffix, 'events', "BidsJsonFile should have correct name_suffix")
-        self.assertEqual(sidecar2.ext, '.json', "BidsJsonFile should have correct ext")
-        self.assertEqual(len(sidecar2.entity_dict), 1, "BidsJsonFile should have right number of entity_dict")
-        self.assertIsInstance(sidecar2.contents, dict, "BidsJsonFile should contain a dictionary")
-
-    def test_bad_constructor(self):
-
-        try:
-            json1 = BidsJsonFile(Test.description_path, set_contents=True)
-        except HedFileError:
-            pass
-        except Exception:
-            self.fail("BidsJsonFile threw the wrong exception when filename invalid")
-        else:
-            self.fail("BidsJsonFile should have thrown a HedFileError when duplicate key")
-
-    def test_bids_json_file_str(self):
-        json_file1 = BidsJsonFile(Test.json_path)
-        self.assertTrue(str(json_file1), "BidsJsonFile should have a string representation")
-        json_file2 = BidsJsonFile(Test.json_path, set_contents=True)
-        self.assertTrue(str(json_file2), "BidsJsonFile should have a string representation")
-        self.assertGreater(len(str(json_file2)), len(str(json_file1)),
-                           "BidsJsonFile with contents should have a longer string representation than without")
-
-    def test_bids_json_file_set_contents(self):
-        json_file = BidsJsonFile(Test.json_path)
-        self.assertFalse(json_file.contents, "BidsJsonFile should have no contents until set")
-        json_file.set_contents()
-        self.assertIsInstance(json_file.contents, dict, "BidsJsonFile should have dict contents after setting")
-        json_file.clear_contents()
-        self.assertFalse(json_file.contents, "BidsJsonFile should have no contents after clearing")
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/tests/tools/bids/test_bids_sidecar_file.py b/tests/tools/bids/test_bids_sidecar_file.py
index 5cb40bca5..8b73e293e 100644
--- a/tests/tools/bids/test_bids_sidecar_file.py
+++ b/tests/tools/bids/test_bids_sidecar_file.py
@@ -1,7 +1,8 @@
 import os
 import unittest
+from hed.errors import HedFileError
 from hed.models.sidecar import Sidecar
-from hed.tools.bids.bids_event_file import BidsEventFile
+from hed.tools.bids.bids_tabular_file import BidsTabularFile
 from hed.tools.bids.bids_file import BidsFile
 from hed.tools.bids.bids_sidecar_file import BidsSidecarFile
 
@@ -10,6 +11,8 @@ class Test(unittest.TestCase):
 
     @classmethod
     def setUpClass(cls):
+        cls.description_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                            '../../data/bids/eeg_ds003654s_hed/dataset_description.json')
         cls.event_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),
                                       '../../data/bids/eeg_ds003654s_hed/sub-002/'
                                       'eeg/sub-002_task-FacePerception_run-1_events.tsv')
@@ -17,21 +20,40 @@ def setUpClass(cls):
                                         '../../data/bids/eeg_ds003654s_hed/task-FacePerception_events.json')
 
     def test_constructor(self):
-        sidecar1 = BidsSidecarFile(Test.sidecar_path, set_contents=False)
+        sidecar1 = BidsSidecarFile(Test.sidecar_path)
         self.assertEqual(sidecar1.suffix, 'events', "BidsSidecarFile should have correct name_suffix")
         self.assertEqual(sidecar1.ext, '.json', "BidsSidecarFile should have correct ext")
         self.assertEqual(len(sidecar1.entity_dict), 1, "BidsSidecarFile should have right number of entity_dict")
         self.assertFalse(sidecar1.contents)
 
-        sidecar2 = BidsSidecarFile(Test.sidecar_path, set_contents=True)
-        self.assertEqual(sidecar2.suffix, 'events', "BidsSidecarFile should have correct name_suffix")
-        self.assertEqual(sidecar2.ext, '.json', "BidsSidecarFile should have correct ext")
-        self.assertEqual(len(sidecar2.entity_dict), 1, "BidsSidecarFile should have right number of entity_dict")
-        self.assertIsInstance(sidecar2.contents, Sidecar, "BidsSidecarFile should contain a Sidecar")
+    def test_bad_constructor(self):
+
+        try:
+            json1 = BidsSidecarFile(Test.description_path)
+        except HedFileError:
+            pass
+        except Exception:
+            self.fail("BidsSidecarFile threw the wrong exception when filename invalid")
+        else:
+            self.fail("BidsSidecarFile should have thrown a HedFileError when duplicate key")
+
+    def test_bids_sidecar_file_str(self):
+        sidecar1 = BidsSidecarFile(Test.sidecar_path)
+        self.assertTrue(str(sidecar1), "BidsSidecarFile should have a string representation")
+        sidecar2 = BidsSidecarFile(Test.sidecar_path)
+        self.assertTrue(str(sidecar2), "BidsSidecarFile should have a string representation")
+
+    def test_bids_sidecar_file_set_contents(self):
+        sidecar1 = BidsSidecarFile(Test.sidecar_path)
+        self.assertFalse(sidecar1.contents, "BidsSidecarFile should have no contents until set")
+        sidecar1.set_contents()
+        self.assertIsInstance(sidecar1.contents, Sidecar, "BidsSidecarFile should have dict contents after setting")
+        sidecar1.clear_contents()
+        self.assertFalse(sidecar1.contents, "BidsSidecarFile should have no contents after clearing")
 
     def test_is_sidecar_for(self):
         sidecar1 = BidsSidecarFile(Test.sidecar_path)
-        events1 = BidsEventFile(Test.event_path)
+        events1 = BidsTabularFile(Test.event_path)
         self.assertTrue(sidecar1.is_sidecar_for(events1))
 
         the_path = '/d/base/sub-01/ses-test/func/sub-01_ses-test_task-overt_run-2_bold.nfti'
diff --git a/tests/tools/bids/test_bids_tsv_file_dictionary.py b/tests/tools/bids/test_bids_tabular_dictionary.py
similarity index 60%
rename from tests/tools/bids/test_bids_tsv_file_dictionary.py
rename to tests/tools/bids/test_bids_tabular_dictionary.py
index 7c61630d5..54cc29ae5 100644
--- a/tests/tools/bids/test_bids_tsv_file_dictionary.py
+++ b/tests/tools/bids/test_bids_tabular_dictionary.py
@@ -2,7 +2,7 @@
 import os
 
 from hed.errors.exceptions import HedFileError
-from hed.tools import BidsTsvDictionary
+from hed.tools import BidsTabularDictionary
 from hed.util import get_file_list
 
 
@@ -14,28 +14,29 @@ def setUpClass(cls):
         cls.file_list = get_file_list(cls.bids_base_dir, name_suffix="_events",
                                       extensions=['.tsv'], exclude_dirs=['stimuli'])
 
-    def test_bids_tsv_dictionary_constructor_valid(self):
-        dict1 = BidsTsvDictionary("Tsv Name", self.file_list, entities=('sub', 'run'))
+    def test_bids_constructor_valid(self):
+        dict1 = BidsTabularDictionary("Tsv Name", self.file_list, entities=('sub', 'run'))
         self.assertEqual(6, len(dict1.key_list),
-                         "BidsTsvDictionary should have correct number of entries when key okay")
+                         "BidsTabularDictionary should have correct number of entries when key okay")
 
-    def test_bids_tsv_dictionary_constructor_invalid(self):
+    def test_constructor_invalid(self):
         try:
-            dict1 = BidsTsvDictionary("Tsv name", self.file_list, entities='sub')
+            dict1 = BidsTabularDictionary("Tsv name", self.file_list, entities=('sub'))
         except HedFileError:
             pass
         except Exception:
-            self.fail("BidsTsvDictionary threw the wrong exception when duplicate key")
+            self.fail("BidsTabularDictionary threw the wrong exception when duplicate key")
         else:
             self.fail("FileDictionary should have thrown a HedFileError when duplicate key")
 
     def test_create_split_dict(self):
-        dict1 = BidsTsvDictionary("My name", self.file_list, entities=('sub', 'run'))
+        dict1 = BidsTabularDictionary("My name", self.file_list, entities=('sub', 'run'))
         dist1_split, leftovers = dict1.create_split_dict('run')
         self.assertIsInstance(dist1_split, dict, "create_split_dict returns a dictionary")
         self.assertEqual(3, len(dist1_split), 'create_split_dict should return the correct number of items')
         for value in dist1_split.values():
-            self.assertIsInstance(value, BidsTsvDictionary, "create_split_dict dict has BidsTsvDictionary objects")
+            self.assertIsInstance(value, BidsTabularDictionary,
+                                  "create_split_dict dict has BidsTabularDictionary objects")
         self.assertFalse(leftovers, "create_split_dict leftovers should be empty")
 
 
diff --git a/tests/tools/bids/test_bids_tabular_file.py b/tests/tools/bids/test_bids_tabular_file.py
new file mode 100644
index 000000000..4c61f08e0
--- /dev/null
+++ b/tests/tools/bids/test_bids_tabular_file.py
@@ -0,0 +1,37 @@
+import os
+import unittest
+from hed.models import TabularInput
+from hed.tools.bids.bids_tabular_file import BidsTabularFile
+from hed.tools.bids.bids_sidecar_file import BidsSidecarFile
+
+
+class Test(unittest.TestCase):
+
+    @classmethod
+    def setUpClass(cls):
+        cls.event_path = \
+            os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                         '../../data/bids/eeg_ds003654s_hed/sub-002/eeg/sub-002_task-FacePerception_run-1_events.tsv')
+        cls.sidecar_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                        '../../data/bids/eeg_ds003654s_hed/task-FacePerception_events.json')
+
+    def test_constructor(self):
+        events = BidsTabularFile(Test.event_path)
+        self.assertEqual(events.suffix, 'events', "BidsTabularFile should have correct events suffix")
+        self.assertEqual(events.ext, '.tsv', "BidsTabularFile should have a .tsv extension")
+        self.assertEqual(len(events.entity_dict), 3, "BidsTabularFile should have right number of entity_dict")
+        events_str = str(events)
+        self.assertTrue(events_str, "BidsTabularFile should have a string representation")
+
+    def test_set_contents(self):
+        events = BidsTabularFile(Test.event_path)
+        self.assertFalse(events.contents, "BidsTabularFile should have no contents until set")
+        events.set_contents()
+        self.assertIsInstance(events.contents, TabularInput,
+                              "BidsTabularFile should have TabularInput contents after setting")
+        events.clear_contents()
+        self.assertFalse(events.contents, "BidsTabularFile should have no contents after clearing")
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/tools/bids/test_bids_tsv_summary.py b/tests/tools/bids/test_bids_tabular_summary.py
similarity index 70%
rename from tests/tools/bids/test_bids_tsv_summary.py
rename to tests/tools/bids/test_bids_tabular_summary.py
index ca5e82f36..af57e287b 100644
--- a/tests/tools/bids/test_bids_tsv_summary.py
+++ b/tests/tools/bids/test_bids_tabular_summary.py
@@ -1,8 +1,7 @@
 import unittest
 import os
-from unittest import mock
 from hed.errors.exceptions import HedFileError
-from hed.tools import BidsTsvSummary, BidsFileDictionary
+from hed.tools import BidsTabularSummary, BidsFileDictionary
 from hed.util import get_file_list, get_new_dataframe
 
 
@@ -23,64 +22,64 @@ def setUpClass(cls):
                                                            'sub-002/eeg/sub-002_task-FacePerception_run-1_events.tsv'))
 
     def test_constructor(self):
-        dict1 = BidsTsvSummary()
-        self.assertIsInstance(dict1, BidsTsvSummary, "BidsTsvSummary constructor is allowed to have no arguments")
+        dict1 = BidsTabularSummary()
+        self.assertIsInstance(dict1, BidsTabularSummary,
+                              "BidsTabularSummary constructor is allowed to have no arguments")
         self.assertFalse(dict1.value_info)
 
-        dict2 = BidsTsvSummary(value_cols=['a', 'b', 'c'], name='baloney')
-        self.assertIsInstance(dict2, BidsTsvSummary, "BidsTsvSummary: multiple values are okay in constructor")
-        self.assertEqual(len(dict2.value_info.keys()), 3, "BidsTsvSummary should have keys for each value column")
+        dict2 = BidsTabularSummary(value_cols=['a', 'b', 'c'], name='baloney')
+        self.assertIsInstance(dict2, BidsTabularSummary, "BidsTabularSummary: multiple values are okay in constructor")
+        self.assertEqual(len(dict2.value_info.keys()), 3, "BidsTabularSummary should have keys for each value column")
 
     def test_get_number_unique_values(self):
-        dict1 = BidsTsvSummary()
+        dict1 = BidsTabularSummary()
         wh_df = get_new_dataframe(self.wh_events_path)
         dict1.update(wh_df)
         self.assertEqual(len(dict1.value_info.keys()), 0,
-                         "BidsTsvSummary value_info should be empty if no value columns")
+                         "BidsTabularSummary value_info should be empty if no value columns")
         self.assertEqual(len(dict1.categorical_info.keys()), len(wh_df.columns),
-                         "BidsTsvSummary categorical_info should have all the columns if no restrictions")
+                         "BidsTabularSummary categorical_info should have all the columns if no restrictions")
         count_dict = dict1.get_number_unique_values()
         self.assertEqual(len(count_dict), 10, "get_number_unique_values should have the correct number of entries")
         self.assertEqual(count_dict['onset'], 551, "get_number_unique_values should have the right number of unique")
 
     def test__str__(self):
-        from io import StringIO
-        t_map = BidsTsvSummary(name="My output")
+        t_map = BidsTabularSummary(name="My output")
         t_map.update(self.stern_map_path)
         df = get_new_dataframe(self.stern_map_path)
         t_map.update(self.stern_map_path)
         self.assertEqual(len(t_map.categorical_info.keys()), len(df.columns),
-                         "BidsTsvSummary should have all columns as categorical if no value or skip are given")
+                         "BidsTabularSummary should have all columns as categorical if no value or skip are given")
         t_map_str = str(t_map)
         self.assertTrue(t_map_str, "__str__ returns a non-empty string when the map has content.")
 
     def test_update(self):
-        dict1 = BidsTsvSummary()
+        dict1 = BidsTabularSummary()
         stern_df = get_new_dataframe(self.stern_map_path)
         dict1.update(stern_df)
         self.assertEqual(len(dict1.value_info.keys()), 0,
-                         "BidsTsvSummary value_info should be empty if no value columns")
+                         "BidsTabularSummary value_info should be empty if no value columns")
         self.assertEqual(len(dict1.categorical_info.keys()), len(stern_df.columns),
-                         "BidsTsvSummary categorical_info should have all the columns if no restrictions")
+                         "BidsTabularSummary categorical_info should have all the columns if no restrictions")
 
-        dict2 = BidsTsvSummary(value_cols=['letter'], skip_cols=['event_type'])
+        dict2 = BidsTabularSummary(value_cols=['letter'], skip_cols=['event_type'])
         dict2.update(stern_df)
         self.assertEqual(len(dict2.value_info.keys()), 1,
-                         "BidsTsvSummary value_info should have letter value column")
+                         "BidsTabularSummary value_info should have letter value column")
         self.assertEqual(dict2.value_info['letter'], len(stern_df),
-                         "BidsTsvSummary value counts should length of column")
-        self.assertEqual(len(dict2.skip_cols), 1, "BidsTsvSummary should have one skip column")
+                         "BidsTabularSummary value counts should length of column")
+        self.assertEqual(len(dict2.skip_cols), 1, "BidsTabularSummary should have one skip column")
         self.assertEqual(len(dict2.categorical_info.keys()), len(stern_df.columns) - 2,
-                         "BidsTsvSummary categorical_info should have all the columns except value and skip columns")
+                         "BidsTabularSummary categorical_info should have all columns except value and skip columns")
         dict2.update(stern_df)
         self.assertEqual(len(dict2.value_info.keys()), 1,
-                         "BidsTsvSummary value_info should have letter value column")
+                         "BidsTabularSummary value_info should have letter value column")
         self.assertEqual(dict2.value_info['letter'], 2*len(stern_df),
-                         "BidsTsvSummary value counts should update by column length each time update is called")
+                         "BidsTabularSummary value counts should update by column length each time update is called")
 
     def test_update_dict(self):
-        dict1 = BidsTsvSummary()
-        dict2 = BidsTsvSummary()
+        dict1 = BidsTabularSummary()
+        dict2 = BidsTabularSummary()
         stern_df_map = get_new_dataframe(self.stern_map_path)
         dict1.update(stern_df_map)
         dict2.update_summary(dict1)
@@ -97,9 +96,9 @@ def test_update_dict(self):
     def test_update_dict_with_value_cols(self):
         stern_df_test1 = get_new_dataframe(self.stern_test1_path)
         stern_df_test3 = get_new_dataframe(self.stern_test3_path)
-        dict1 = BidsTsvSummary(value_cols=['latency'])
+        dict1 = BidsTabularSummary(value_cols=['latency'])
         dict1.update(stern_df_test3)
-        dict2 = BidsTsvSummary(value_cols=['latency'])
+        dict2 = BidsTabularSummary(value_cols=['latency'])
         dict2.update(stern_df_test1)
         dict2.update_summary(dict1)
         dict1.update(stern_df_test1)
@@ -111,9 +110,9 @@ def test_update_dict_with_value_cols(self):
     def test_update_dict_with_bad_value_cols(self):
         stern_df_test1 = get_new_dataframe(self.stern_test1_path)
         stern_df_test3 = get_new_dataframe(self.stern_test3_path)
-        dict1 = BidsTsvSummary(value_cols=['latency'])
+        dict1 = BidsTabularSummary(value_cols=['latency'])
         dict1.update(stern_df_test3)
-        dict3 = BidsTsvSummary()
+        dict3 = BidsTabularSummary()
         dict3.update(stern_df_test1)
         try:
             dict1.update_summary(dict3)
@@ -126,9 +125,9 @@ def test_update_dict_with_bad_value_cols(self):
 
     def test_update_dict_bad_skip_col(self):
         stern_test3 = get_new_dataframe(self.stern_test3_path)
-        dict1 = BidsTsvSummary(skip_cols=['latency'])
+        dict1 = BidsTabularSummary(skip_cols=['latency'])
         dict1.update(stern_test3)
-        dict2 = BidsTsvSummary(value_cols=['latency'])
+        dict2 = BidsTabularSummary(value_cols=['latency'])
         dict2.update(stern_test3)
         try:
             dict2.update_summary(dict1)
@@ -141,34 +140,35 @@ def test_update_dict_bad_skip_col(self):
 
     def test_get_columns_info(self):
         df = get_new_dataframe(self.stern_test2_path)
-        col_info = BidsTsvSummary.get_columns_info(df)
+        col_info = BidsTabularSummary.get_columns_info(df)
         self.assertIsInstance(col_info, dict, "get_columns_info should return a dictionary")
         self.assertEqual(len(col_info.keys()), len(df.columns),
                          "get_columns_info should return a dictionary with a key for each column")
 
     def test_get_columns_info_skip_columns(self):
         df = get_new_dataframe(self.stern_test2_path)
-        col_info = BidsTsvSummary.get_columns_info(df, ['latency'])
+        col_info = BidsTabularSummary.get_columns_info(df, ['latency'])
         self.assertIsInstance(col_info, dict, "get_columns_info should return a dictionary")
         self.assertEqual(len(col_info.keys()), len(df.columns) - 1,
                          "get_columns_info should return a dictionary with a key for each column included")
-        col_info = BidsTsvSummary.get_columns_info(df, list(df.columns.values))
+        col_info = BidsTabularSummary.get_columns_info(df, list(df.columns.values))
         self.assertIsInstance(col_info, dict, "get_columns_info should return a dictionary")
         self.assertFalse(col_info, "get_columns_info should return a dictionary with a key for each column included")
 
     def test_make_combined_dicts(self):
         files_bids = get_file_list(self.bids_base_dir, extensions=[".tsv"], name_suffix="_events")
         file_dict = BidsFileDictionary("my name", files_bids)
-        dicts_all1, dicts1 = BidsTsvSummary.make_combined_dicts(file_dict)
-        self.assertTrue(isinstance(dicts_all1, BidsTsvSummary),
-                        "make_combined_dicts should return a BidsTsvSummary")
+        dicts_all1, dicts1 = BidsTabularSummary.make_combined_dicts(file_dict)
+        self.assertTrue(isinstance(dicts_all1, BidsTabularSummary),
+                        "make_combined_dicts should return a BidsTabularSummary")
         self.assertTrue(isinstance(dicts1, dict), "make_combined_dicts should also return a dictionary of file names")
         self.assertEqual(6, len(dicts1), "make_combined_dicts should return correct number of file names")
         self.assertEqual(10, len(dicts_all1.categorical_info),
                          "make_combined_dicts should return right number of entries")
-        dicts_all2, dicts2 = BidsTsvSummary.make_combined_dicts(file_dict, skip_cols=["onset", "duration", "sample"])
-        self.assertTrue(isinstance(dicts_all2, BidsTsvSummary),
-                        "make_combined_dicts should return a BidsTsvSummary")
+        dicts_all2, dicts2 = BidsTabularSummary.make_combined_dicts(file_dict,
+                                                                    skip_cols=["onset", "duration", "sample"])
+        self.assertTrue(isinstance(dicts_all2, BidsTabularSummary),
+                        "make_combined_dicts should return a BidsTabularSummary")
         self.assertTrue(isinstance(dicts2, dict), "make_combined_dicts should also return a dictionary of file names")
         self.assertEqual(6, len(dicts2), "make_combined_dicts should return correct number of file names")
         self.assertEqual(len(dicts_all2.categorical_info), 7,
diff --git a/tests/tools/bids/test_bids_timeseries_file.py b/tests/tools/bids/test_bids_timeseries_file.py
new file mode 100644
index 000000000..03bc1276d
--- /dev/null
+++ b/tests/tools/bids/test_bids_timeseries_file.py
@@ -0,0 +1,37 @@
+import os
+import unittest
+from hed.models import TabularInput
+from hed.tools.bids.bids_timeseries_file import BidsTimeseriesFile
+from hed.tools.bids.bids_sidecar_file import BidsSidecarFile
+
+
+class Test(unittest.TestCase):
+
+    @classmethod
+    def setUpClass(cls):
+        cls.event_path = \
+            os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                         '../../data/bids/eeg_ds003654s_hed/sub-002/eeg/sub-002_task-FacePerception_run-1_events.tsv')
+        cls.sidecar_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                        '../../data/bids/eeg_ds003654s_hed/task-FacePerception_events.json')
+
+    def test_constructor(self):
+        events = BidsTimeseriesFile(Test.event_path)
+        self.assertEqual(events.suffix, 'events', "BidsTabularFile should have correct events suffix")
+        self.assertEqual(events.ext, '.tsv', "BidsTabularFile should have a .tsv extension")
+        self.assertEqual(len(events.entity_dict), 3, "BidsTabularFile should have right number of entity_dict")
+        events_str = str(events)
+        self.assertTrue(events_str, "BidsTabularFile should have a string representation")
+
+    def test_set_contents(self):
+        events = BidsTimeseriesFile(Test.event_path)
+        self.assertFalse(events.contents, "BidsTabularFile should have no contents until set")
+        events.set_contents()
+        self.assertIsInstance(events.contents, TabularInput,
+                              "BidsTabularFile should have TabularInput contents after setting")
+        events.clear_contents()
+        self.assertFalse(events.contents, "BidsTabularFile should have no contents after clearing")
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/tools/bids/test_bids_tsv_file.py b/tests/tools/bids/test_bids_tsv_file.py
deleted file mode 100644
index 88f298361..000000000
--- a/tests/tools/bids/test_bids_tsv_file.py
+++ /dev/null
@@ -1,40 +0,0 @@
-import os
-from pandas import DataFrame
-import unittest
-from hed.tools.bids.bids_tsv_file import BidsTsvFile
-
-
-class Test(unittest.TestCase):
-
-    @classmethod
-    def setUpClass(cls):
-        cls.tsv_path = \
-            os.path.join(os.path.dirname(os.path.realpath(__file__)),
-                         '../../data/bids/eeg_ds003654s_hed/sub-002/eeg/sub-002_task-FacePerception_run-1_events.tsv')
-
-    def test_bids_tsv_file_constructor(self):
-        tsv_file = BidsTsvFile(Test.tsv_path)
-        self.assertEqual(tsv_file.suffix, 'events', "BidsTsvFile should have correct events suffix")
-        self.assertEqual(tsv_file.ext, '.tsv', "BidsTsvFile should have a .tsv extension")
-        self.assertEqual(len(tsv_file.entity_dict), 3, "BidsTsvFile should have right number of entity_dict")
-
-    def test_bids_tsv_file_str(self):
-        tsv_file1 = BidsTsvFile(Test.tsv_path)
-        self.assertTrue(str(tsv_file1), "BidsTsvFile should have a string representation")
-        tsv_file2 = BidsTsvFile(Test.tsv_path, set_contents=True)
-        self.assertTrue(str(tsv_file2), "BidsTsvFile should have a string representation")
-        self.assertGreater(len(str(tsv_file2)), len(str(tsv_file1)),
-                           "BidsTsvFile with contents should have a longer string representation than without")
-
-    def test_bids_tsv_file_set_contents(self):
-        tsv_file = BidsTsvFile(Test.tsv_path)
-        self.assertFalse(tsv_file.contents, "BidsTsvFile should have no contents until set")
-        tsv_file.set_contents()
-        self.assertIsInstance(tsv_file.contents, DataFrame,
-                              "BidsTsvFile should have DataFrame contents after setting")
-        tsv_file.clear_contents()
-        self.assertFalse(tsv_file.contents, "BidsTsvFile should have no contents after clearing")
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/tests/tools/curation/test_tsv_reports.py b/tests/tools/curation/test_tsv_reports.py
index a2e2860fb..f40fa7e88 100644
--- a/tests/tools/curation/test_tsv_reports.py
+++ b/tests/tools/curation/test_tsv_reports.py
@@ -4,7 +4,7 @@
 from unittest import mock
 
 from hed.errors.exceptions import HedFileError
-from hed.tools import BidsTsvDictionary, HedLogger, report_tsv_diffs
+from hed.tools import BidsTabularDictionary, HedLogger, report_tsv_diffs
 from hed.util import get_file_list
 
 
@@ -17,11 +17,11 @@ def setUpClass(cls):
                                       extensions=['.tsv'], exclude_dirs=['stimuli'])
 
     def test_report_tsv_diffs(self):
-        dict1 = BidsTsvDictionary("Bids1", self.file_list, entities=('sub', 'run'))
-        dict2 = BidsTsvDictionary("Bids2", self.file_list, entities=('sub', 'run'))
+        dict1 = BidsTabularDictionary("Bids1", self.file_list, entities=('sub', 'run'))
+        dict2 = BidsTabularDictionary("Bids2", self.file_list, entities=('sub', 'run'))
         logger = HedLogger()
         self.assertEqual(6, len(dict1.key_list),
-                         "BidsTsvDictionary should have correct number of entries when key okay")
+                         "BidsTabularDictionary should have correct number of entries when key okay")
         self.assertFalse(logger.log, "report_tsv_diffs the logger is empty before report is called")
         with mock.patch('sys.stdout', new=StringIO()) as fake_out1:
             self.assertIsInstance(fake_out1, StringIO, "Mock creates a StringIO")
diff --git a/tests/validator/test_hed_validator.py b/tests/validator/test_hed_validator.py
index 46c1e4725..d5b6b0137 100644
--- a/tests/validator/test_hed_validator.py
+++ b/tests/validator/test_hed_validator.py
@@ -4,7 +4,7 @@
 #from hed import
 from hed.errors import ErrorContext
 from hed import schema
-from hed.models import DefMapper, HedString, HedInput, EventsInput, Sidecar
+from hed.models import DefMapper, HedString, SpreadsheetInput, TabularInput, Sidecar
 from hed.validator import HedValidator
 
 
@@ -20,14 +20,14 @@ def setUpClass(cls):
         cls.validation_issues = []
         cls.hed_base_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), '../data/validator_tests/')
         cls.hed_filepath_with_errors = os.path.join(cls.hed_base_dir, "ExcelMultipleSheets.xlsx")
-        cls.hed_file_with_errors = HedInput(cls.hed_filepath_with_errors)
+        cls.hed_file_with_errors = SpreadsheetInput(cls.hed_filepath_with_errors)
 
         cls.hed_filepath_major_errors = os.path.join(cls.hed_base_dir, "bids_events_invalid.tsv")
-        cls.hed_file_with_major_errors = HedInput(cls.hed_filepath_major_errors, tag_columns=[3])
+        cls.hed_file_with_major_errors = SpreadsheetInput(cls.hed_filepath_major_errors, tag_columns=[2])
 
         cls.hed_filepath_major_errors_multi_column = os.path.join(cls.hed_base_dir, "bids_events_invalid_columns.tsv")
         cls.hed_file_with_major_errors_multi_column = \
-            HedInput(cls.hed_filepath_major_errors_multi_column, tag_columns=[3, 4])
+            SpreadsheetInput(cls.hed_filepath_major_errors_multi_column, tag_columns=[2, 3])
 
     def test__validate_input(self):
         test_string_obj = HedString(self.base_hed_input)
@@ -59,11 +59,11 @@ def test__validate_input_major_errors_multi_column(self):
         self.assertIsInstance(validation_issues, list)
         self.assertEqual(len(validation_issues), 2)
 
-    def test_complex_file_validation(self):
+    def test_complex_file_validation_no_index(self):
         schema_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),
                                    '../data/validator_tests/bids_schema.mediawiki')
         events_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),
-                                   '../data/validator_tests/bids_events.tsv')
+                                   '../data/validator_tests/bids_events_no_index.tsv')
 
         hed_schema = schema.load_schema(schema_path)
         json_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../data/validator_tests/bids_events.json")
@@ -71,7 +71,26 @@ def test_complex_file_validation(self):
         sidecar = Sidecar(json_path)
         issues = sidecar.validate_entries(validator)
         self.assertEqual(len(issues), 0)
-        input_file = EventsInput(events_path, sidecars=sidecar)
+        input_file = TabularInput(events_path, sidecar=sidecar)
+
+        validation_issues = input_file.validate_file_sidecars(validator)
+        self.assertEqual(len(validation_issues), 0)
+        validation_issues = input_file.validate_file(validator)
+        self.assertEqual(len(validation_issues), 0)
+
+    def test_complex_file_validation_with_index(self):
+        schema_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                   '../data/validator_tests/bids_schema.mediawiki')
+        events_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                   '../data/validator_tests/bids_events_with_index.tsv')
+
+        hed_schema = schema.load_schema(schema_path)
+        json_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../data/validator_tests/bids_events.json")
+        validator = HedValidator(hed_schema=hed_schema)
+        sidecar = Sidecar(json_path)
+        issues = sidecar.validate_entries(validator)
+        self.assertEqual(len(issues), 0)
+        input_file = TabularInput(events_path, sidecar=sidecar)
 
         validation_issues = input_file.validate_file_sidecars(validator)
         self.assertEqual(len(validation_issues), 0)
@@ -82,7 +101,7 @@ def test_complex_file_validation_invalid(self):
         schema_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),
                                    '../data/validator_tests/bids_schema.mediawiki')
         events_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),
-                                   '../data/validator_tests/bids_events.tsv')
+                                   '../data/validator_tests/bids_events_with_index.tsv')
 
         hed_schema = schema.load_schema(schema_path)
         json_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),
@@ -91,7 +110,7 @@ def test_complex_file_validation_invalid(self):
         sidecar = Sidecar(json_path)
         # issues = sidecar.validate_entries(hed_ops=validator, check_for_warnings=True)
         # self.assertEqual(len(issues), 4)
-        input_file = EventsInput(events_path, sidecars=sidecar)
+        input_file = TabularInput(events_path, sidecar=sidecar)
         #
         # validation_issues = input_file.validate_file_sidecars(validator, check_for_warnings=True)
         # self.assertEqual(len(validation_issues), 4)
@@ -105,13 +124,13 @@ def test_complex_file_validation_invalid_definitions_removed(self):
         schema_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),
                                    '../data/validator_tests/bids_schema.mediawiki')
         events_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),
-                                   '../data/validator_tests/bids_events.tsv')
+                                   '../data/validator_tests/bids_events_with_index.tsv')
 
         hed_schema = schema.load_schema(schema_path)
         json_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),
                                  "../data/validator_tests/bids_events_bad_defs2.json")
         sidecar = Sidecar(json_path)
-        input_file = EventsInput(events_path, sidecars=sidecar)
+        input_file = TabularInput(events_path, sidecar=sidecar)
         validator = HedValidator(hed_schema=hed_schema)
 
         validation_issues1 = input_file.validate_file_sidecars(validator)
@@ -127,11 +146,11 @@ def test_file_bad_defs_in_spreadsheet(self):
                                    '../data/validator_tests/hed3_tags_single_sheet_bad_defs.xlsx')
         hed_schema = schema.load_schema(schema_path)
 
-        prefixed_needed_tag_columns = {2: 'Property/Informational-property/Label/',
-                                       3: 'Property/Informational-property/Description/'}
-        loaded_file = HedInput(events_path, tag_columns=[4],
-                               column_prefix_dictionary=prefixed_needed_tag_columns,
-                               worksheet_name='LKT Events')
+        prefixed_needed_tag_columns = {1: 'Property/Informational-property/Label/',
+                                       2: 'Property/Informational-property/Description/'}
+        loaded_file = SpreadsheetInput(events_path, tag_columns=[3],
+                                       column_prefix_dictionary=prefixed_needed_tag_columns,
+                                       worksheet_name='LKT Events')
 
         validator = HedValidator(hed_schema=hed_schema)
         validation_issues = loaded_file.validate_file(validator, check_for_warnings=True)
@@ -145,7 +164,7 @@ def test_error_spans_from_file_and_missing_required_column(self):
 
         hed_schema = schema.load_schema(schema_path)
 
-        input_file = HedInput(events_path, tag_columns=[2, 3, "error"])
+        input_file = SpreadsheetInput(events_path, tag_columns=[1, 2, "error"])
         validator = HedValidator(hed_schema=hed_schema)
         validation_issues = input_file.validate_file(validator)
         self.assertEqual(validation_issues[1]['char_index'], 6)