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

Issue:
Updated the webtools sidecar iterator



Discussion / hints:

Fix patch:
diff --git a/hedtools/.coveragerc b/hedtools/.coveragerc
index 60df76488..50fbc0cfb 100644
--- a/hedtools/.coveragerc
+++ b/hedtools/.coveragerc
@@ -7,7 +7,7 @@ omit =
         **/tests/
         **/test/
         */test*
-        **/*__init__.py
+        **/__init__.py
 
 
 [report]
diff --git a/hedtools/hed/schema/hed_schema_group.py b/hedtools/hed/schema/hed_schema_group.py
index 7df48a07e..b1ca9b8e7 100644
--- a/hedtools/hed/schema/hed_schema_group.py
+++ b/hedtools/hed/schema/hed_schema_group.py
@@ -10,7 +10,7 @@
 
 class HedSchemaGroup:
     """
-        This class handles combining multiple HedSchema objects for validation.
+        This class handles combining multiple HedSchema objects for validation and analysis.
 
         You cannot save/load/etc the combined schema object directly.
     """
diff --git a/hedtools/hed/tools/bids/bids_dataset.py b/hedtools/hed/tools/bids/bids_dataset.py
index 130c04339..c01f88c76 100644
--- a/hedtools/hed/tools/bids/bids_dataset.py
+++ b/hedtools/hed/tools/bids/bids_dataset.py
@@ -3,10 +3,11 @@
 from hed.errors.error_reporter import get_printable_issue_string
 from hed.schema.hed_schema_io import load_schema, load_schema_version
 from hed.schema.hed_schema_group import HedSchemaGroup
-from hed.tools.data_util import get_new_dataframe
+from hed.util.data_util import get_new_dataframe
 from hed.tools.bids.bids_event_files import BidsEventFiles
 from hed.validator import HedValidator
 
+
 LIBRARY_URL_BASE = "https://raw.githubusercontent.com/hed-standard/hed-schema-library/main/hedxml/HED_"
 
 
@@ -18,11 +19,11 @@ def __init__(self, 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.hed_schema = HedSchemaGroup(self._schema_from_description())
+        self.schemas = HedSchemaGroup(self._schema_from_description())
         self.event_files = BidsEventFiles(root_path)
 
     def validate(self, check_for_warnings=True):
-        validator = HedValidator(hed_schema=self.hed_schema)
+        validator = HedValidator(hed_schema=self.schemas)
         issues = self.event_files.validate(validators=[validator], check_for_warnings=check_for_warnings)
         return issues
 
@@ -44,6 +45,21 @@ def _schema_from_description(self):
                 hed_list.append(x)
         return hed_list
 
+    def get_summary(self):
+        summary = {"hed_schema_versions": self.get_schema_versions()}
+        return summary
+
+    def get_schema_versions(self):
+        version_list = []
+        x = self.schemas
+        for prefix, schema in self.schemas._schemas.items():
+            name = schema.version
+            if schema.library:
+                name = schema.library + '_' + name
+            name = prefix + name
+            version_list.append(name)
+        return version_list
+
 
 if __name__ == '__main__':
     path = os.path.join(os.path.dirname(os.path.abspath(__file__)),
@@ -55,3 +71,5 @@ def _schema_from_description(self):
     else:
         issue_str = "No issues"
     print(issue_str)
+    summary1 = bids.get_summary()
+    print(json.dumps(summary1, indent=4))
diff --git a/hedtools/hed/tools/bids/bids_dataset_summary.py b/hedtools/hed/tools/bids/bids_dataset_summary.py
index 7ac9266bc..7c14e9957 100644
--- a/hedtools/hed/tools/bids/bids_dataset_summary.py
+++ b/hedtools/hed/tools/bids/bids_dataset_summary.py
@@ -1,8 +1,16 @@
-from hed.tools.bids.bids_event_files import BidsEventFiles
+""" BidsDatasetSummary: Holds a summary of BIDS dataset """
+
+from hed.tools.bids.bids_dataset import BidsDataset
 
 
 class BidsDatasetSummary:
 
-    def __init__(self, root_path, hed_schema):
-        self.event_files = BidsEventFiles(root_path)
-        self.hed_schema = hed_schema
+    def __init__(self, dataset):
+        """ Construct a summary of a BidsDataset
+
+        Parameters:
+            dataset: BidsDataset
+            The dataset to be summarized.
+
+        """
+        self.dataset = dataset
diff --git a/hedtools/hed/tools/bids/bids_event_file.py b/hedtools/hed/tools/bids/bids_event_file.py
index e851e1c9e..fc0a48df7 100644
--- a/hedtools/hed/tools/bids/bids_event_file.py
+++ b/hedtools/hed/tools/bids/bids_event_file.py
@@ -1,13 +1,13 @@
 import os
 from hed.models.events_input import EventsInput
-from hed.tools.bids.bids_file import BidsFile
+from hed.tools.bids.bids_tsv_file import BidsTsvFile
 
 
-class BidsEventFile(BidsFile):
+class BidsEventFile(BidsTsvFile):
     """Represents a bids_old file."""
 
     def __init__(self, file_path):
-        super().__init__(os.path.abspath(file_path))
+        super().__init__(os.path.abspath(file_path), set_contents=False)
         self.contents = None
         self.sidecars = None
 
diff --git a/hedtools/hed/tools/bids/bids_event_files.py b/hedtools/hed/tools/bids/bids_event_files.py
index 80863e293..79ed44b74 100644
--- a/hedtools/hed/tools/bids/bids_event_files.py
+++ b/hedtools/hed/tools/bids/bids_event_files.py
@@ -4,7 +4,7 @@
 from hed.tools.bids.bids_event_file import BidsEventFile
 from hed.tools.bids.bids_sidecar_file import BidsSidecarFile
 from hed.models.events_input import EventsInput
-from hed.tools.io_util import get_dir_dictionary, get_file_list, get_path_components
+from hed.util.io_util import get_dir_dictionary, get_file_list, get_path_components
 from hed.validator.hed_validator import HedValidator
 
 
diff --git a/hedtools/hed/tools/bids/bids_file.py b/hedtools/hed/tools/bids/bids_file.py
index 0aa69bc60..91c0c3793 100644
--- a/hedtools/hed/tools/bids/bids_file.py
+++ b/hedtools/hed/tools/bids/bids_file.py
@@ -1,10 +1,9 @@
 import os
-from hed.tools.bids.bids_util import parse_bids_filename
-from hed.tools.io_util import get_file_list
+from hed.util.io_util import parse_bids_filename
 
 
 class BidsFile:
-    """Represents a bids_old file."""
+    """Represents a bids file name but not contents."""
 
     def __init__(self, file_path):
         self.file_path = os.path.abspath(file_path)
@@ -17,10 +16,3 @@ def __str__(self):
         return self.file_path + ":\n\tname_suffix=" + self.suffix + " ext=" + self.ext + \
                " entities=" + str(self.entities)
 
-
-if __name__ == '__main__':
-    path = 'D:\\Research\\HED\\hed-examples\\datasets\\eeg_ds003654s'
-    files = get_file_list(path, name_prefix=None, name_suffix="events", extensions=None)
-    for file in files:
-        bids = BidsFile(file)
-        print(str(bids))
diff --git a/hedtools/hed/tools/bids/bids_json_file.py b/hedtools/hed/tools/bids/bids_json_file.py
index 42f43cb67..403bcb17a 100644
--- a/hedtools/hed/tools/bids/bids_json_file.py
+++ b/hedtools/hed/tools/bids/bids_json_file.py
@@ -8,9 +8,20 @@ class BidsJsonFile(BidsFile):
 
     def __init__(self, file_path, set_contents=False):
         super().__init__(os.path.abspath(file_path))
+        self.contents = None
         if set_contents:
+            self.set_contents()
 
-            with open(self.file_path, 'r') as f:
-                self.contents = json.load(f)
+    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:
-            self.contents = None
+            return super().__str__()
diff --git a/hedtools/hed/tools/bids/bids_sidecar_file.py b/hedtools/hed/tools/bids/bids_sidecar_file.py
index c16162f91..3f7133b3f 100644
--- a/hedtools/hed/tools/bids/bids_sidecar_file.py
+++ b/hedtools/hed/tools/bids/bids_sidecar_file.py
@@ -1,17 +1,16 @@
 import os
 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(BidsFile):
+class BidsSidecarFile(BidsJsonFile):
     """Represents a bids_old file."""
 
     def __init__(self, file_path, set_contents=False):
-        super().__init__(os.path.abspath(file_path))
+        super().__init__(os.path.abspath(file_path), set_contents=False)
         if set_contents:
-            self.contents = Sidecar(self.file_path, name=os.path.abspath(self.file_path))
-        else:
-            self.contents = None
+            self.set_contents()
 
     def is_sidecar_for(self, obj):
         """ Returns true if this is a sidecar for obj.
@@ -34,6 +33,9 @@ def is_sidecar_for(self, obj):
                 return False
         return True
 
+    def set_contents(self):
+        self.contents = Sidecar(self.file_path, name=os.path.abspath(self.file_path))
+
     @staticmethod
     def get_sidecar(obj, sidecars):
         """ Return a single SideCar relevant to obj from list of sidecars """
diff --git a/hedtools/hed/tools/bids/bids_sidecar_summary.py b/hedtools/hed/tools/bids/bids_sidecar_summary.py
new file mode 100644
index 000000000..a33b0d512
--- /dev/null
+++ b/hedtools/hed/tools/bids/bids_sidecar_summary.py
@@ -0,0 +1,16 @@
+""" BidsSidecarSummary: Holds a summary of BIDS Json Sidecar """
+
+from hed.tools.bids.bids_sidecar_file import BidsSidecarFile
+
+
+class BidsSidecarSummary:
+
+    def __init__(self, dataset):
+        """ Construct a summary of a BidsDataset 
+
+        Parameters:
+            dataset: BidsDataset
+            The dataset to be summarized.
+
+        """
+        self.dataset = dataset
\ No newline at end of file
diff --git a/hedtools/hed/tools/bids/bids_tsv_file.py b/hedtools/hed/tools/bids/bids_tsv_file.py
new file mode 100644
index 000000000..3f6e04318
--- /dev/null
+++ b/hedtools/hed/tools/bids/bids_tsv_file.py
@@ -0,0 +1,37 @@
+"""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 with contents that are a dataframe.
+
+    Parameters
+    ----------
+    file_path: str
+        The full path to the file
+    set_contents: bool
+        If true then the file contents are read into a dataframe.
+
+  """
+    def __init__(self, file_path, set_contents=False):
+        super().__init__(os.path.abspath(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/hedtools/hed/tools/bids/bids_util.py b/hedtools/hed/tools/bids/bids_util.py
index 171ea39bc..58bece465 100644
--- a/hedtools/hed/tools/bids/bids_util.py
+++ b/hedtools/hed/tools/bids/bids_util.py
@@ -1,36 +1,3 @@
-import os
 
 
-def parse_bids_filename(file_path):
-    """ Split a filename into its BIDS suffix, extension, and entities
 
-        Args:
-            file_path (str)     Path to be parsed
-
-        Returns:
-            suffix (str)        BIDS suffix name
-            ext (str)           File extension (including the .)
-            entities (dict)     Dictionary with key-value pair being (entity type, entity value)
-            unmatched (list)    List of unmatched pieces of the filename
-
-    """
-
-    filename = os.path.splitext(os.path.basename(file_path))
-    ext = filename[1].lower()
-    basename = filename[0]
-    if '-' not in basename:
-        return basename, ext, {}, []
-    entity_pieces = basename.split('_')
-    if len(entity_pieces) < 2:
-        return '', ext, {}, [basename]
-    suffix = entity_pieces[-1]
-    entities = {}
-    unmatched = []
-    entity_pieces = entity_pieces[:-1]
-    for entity in reversed(list(enumerate(entity_pieces))):
-        pieces = entity[1].split('-')
-        if len(pieces) != 2:
-            unmatched.append(entity[1])
-        else:
-            entities[pieces[0]] = pieces[1]
-    return suffix, ext, entities, unmatched
diff --git a/hedtools/hed/tools/hed_logger.py b/hedtools/hed/tools/hed_logger.py
index b1ffe9aa5..a60e9541b 100644
--- a/hedtools/hed/tools/hed_logger.py
+++ b/hedtools/hed/tools/hed_logger.py
@@ -1,6 +1,11 @@
 
+""" HedLogger class. """
+
+
 class HedLogger:
+    """ Class to log status messages organized by key
 
+    """
     def __init__(self):
         self.log = {}
 
@@ -20,7 +25,7 @@ def get_log(self, key):
         else:
             return []
 
-    def print_log(self, filename=None):
+    def print_log(self):
         for key, item in self.log.items():
             print(f"{key}")
             if item:
diff --git a/hedtools/hed/tools/summaries/__init__.py b/hedtools/hed/tools/summaries/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/hedtools/hed/tools/col_dict.py b/hedtools/hed/tools/summaries/col_dict.py
similarity index 96%
rename from hedtools/hed/tools/col_dict.py
rename to hedtools/hed/tools/summaries/col_dict.py
index 60e79b440..ad084056d 100644
--- a/hedtools/hed/tools/col_dict.py
+++ b/hedtools/hed/tools/summaries/col_dict.py
@@ -1,6 +1,6 @@
 import pandas as pd
 from hed.errors.exceptions import HedFileError
-from hed.tools.data_util import get_new_dataframe
+from hed.util.data_util import get_new_dataframe
 
 
 class ColumnDict:
diff --git a/hedtools/hed/tools/hed_def_summary.py b/hedtools/hed/tools/summaries/hed_def_summary.py
similarity index 93%
rename from hedtools/hed/tools/hed_def_summary.py
rename to hedtools/hed/tools/summaries/hed_def_summary.py
index 61ab0647b..53fd7d0c7 100644
--- a/hedtools/hed/tools/hed_def_summary.py
+++ b/hedtools/hed/tools/summaries/hed_def_summary.py
@@ -1,4 +1,4 @@
-from hed.tools.hed_group_summary import HedGroupSummary
+from hed.tools.summaries.hed_group_summary import HedGroupSummary
 from hed.models.sidecar import Sidecar
 from hed.models.def_dict import DefDict
 from hed.schema.hed_schema_file import load_schema
diff --git a/hedtools/hed/tools/hed_group_summary.py b/hedtools/hed/tools/summaries/hed_group_summary.py
similarity index 89%
rename from hedtools/hed/tools/hed_group_summary.py
rename to hedtools/hed/tools/summaries/hed_group_summary.py
index 1f8208500..61b1d0889 100644
--- a/hedtools/hed/tools/hed_group_summary.py
+++ b/hedtools/hed/tools/summaries/hed_group_summary.py
@@ -3,7 +3,8 @@
 from hed.schema import load_schema
 
 
-class HedGroupSummary():
+class HedGroupSummary:
+
     def __init__(self, hed_group, hed_schema, name=None, keep_all_values=False):
         self.name = name
         self.hed_group = hed_group
@@ -66,9 +67,10 @@ def __str__(self):
 
 
 if __name__ == '__main__':
-    the_path = '../../tests/data/bids/eeg_ds003654s_hed/task-FacePerception_events.json'
+    the_path = '../../../tests/data/bids/eeg_ds003654s_hed/task-FacePerception_events.json'
     sidecar = Sidecar(the_path)
-    hed_schema = load_schema('https://raw.githubusercontent.com/hed-standard/hed-specification/master/hedxml/HED8.0.0.xml')
+    hed_schema = \
+        load_schema('https://raw.githubusercontent.com/hed-standard/hed-specification/master/hedxml/HED8.0.0.xml')
     def_dicts = [column_entry.def_dict for column_entry in sidecar]
     group_list = []
     for def_dict in def_dicts:
diff --git a/hedtools/hed/tools/key_map.py b/hedtools/hed/tools/summaries/key_map.py
similarity index 95%
rename from hedtools/hed/tools/key_map.py
rename to hedtools/hed/tools/summaries/key_map.py
index 1c82b954f..fde105ecf 100644
--- a/hedtools/hed/tools/key_map.py
+++ b/hedtools/hed/tools/summaries/key_map.py
@@ -1,7 +1,7 @@
 import pandas as pd
 from hed.errors.exceptions import HedFileError
-from hed.tools.data_util import get_row_hash
-from hed.tools.data_util import get_new_dataframe, remove_quotes, separate_columns
+from hed.util.data_util import get_row_hash
+from hed.util.data_util import get_new_dataframe, remove_quotes, separate_columns
 
 
 class KeyMap:
diff --git a/hedtools/hed/tools/key_template.py b/hedtools/hed/tools/summaries/key_template.py
similarity index 94%
rename from hedtools/hed/tools/key_template.py
rename to hedtools/hed/tools/summaries/key_template.py
index 50c14dbfc..e0c2658d1 100644
--- a/hedtools/hed/tools/key_template.py
+++ b/hedtools/hed/tools/summaries/key_template.py
@@ -1,6 +1,6 @@
 import pandas as pd
 from hed.errors.exceptions import HedFileError
-from hed.tools.data_util import get_key_hash, get_new_dataframe, get_row_hash, remove_quotes, separate_columns
+from hed.util.data_util import get_key_hash, get_new_dataframe, get_row_hash, remove_quotes, separate_columns
 
 
 class KeyTemplate:
diff --git a/hedtools/hed/tools/map_util.py b/hedtools/hed/tools/summaries/map_summary.py
similarity index 91%
rename from hedtools/hed/tools/map_util.py
rename to hedtools/hed/tools/summaries/map_summary.py
index d5caaa27a..c39b4c2c5 100644
--- a/hedtools/hed/tools/map_util.py
+++ b/hedtools/hed/tools/summaries/map_summary.py
@@ -1,7 +1,7 @@
 
-from hed.tools.col_dict import ColumnDict
-from hed.tools.io_util import get_file_list
-from hed.tools.data_util import get_new_dataframe
+from hed.tools.summaries.col_dict import ColumnDict
+from hed.util.io_util import get_file_list
+from hed.util.data_util import get_new_dataframe
 
 
 def get_columns_info(dataframe, skip_cols=None):
diff --git a/hedtools/hed/tools/tag_util.py b/hedtools/hed/tools/tag_util.py
index 9dbcf1ef4..ea600e28c 100644
--- a/hedtools/hed/tools/tag_util.py
+++ b/hedtools/hed/tools/tag_util.py
@@ -1,6 +1,6 @@
-from hed.schema.hed_schema_file import load_schema_version
+from hed.schema.hed_schema_io import load_schema_version
 from hed.tools.bids.bids_event_files import BidsEventFiles
-from hed.tools.hed_group_summary import HedGroupSummary
+from hed.tools.summaries.hed_group_summary import HedGroupSummary
 
 
 def extract_definitions(bids):
@@ -11,9 +11,9 @@ def extract_definitions(bids):
 #     hed_version = "8.0.0"
 #     hed_str = "Sensory-event, Visual-presentation, Condition-variable/Blah, ((Square, Red)," + \
 #               "(Center-of, Computer-screen))"
-#     hed_schema = load_schema_version(xml_version_number=hed_version)
+#     schemas = load_schema_version(xml_version_number=hed_version)
 #     obj = HedString(hed_str)
-#     obj.convert_to_canonical_forms(hed_schema)
+#     obj.convert_to_canonical_forms(schemas)
 #     a = obj.get_all_groups()
 #     print("Tag groups:")
 #     for group in a:
@@ -48,7 +48,7 @@ def extract_definitions(bids):
 
         #         y = x.get_all_tags()
         #         for tag in y:
-        #             tag.convert_to_canonical_forms(hed_schema)
+        #             tag.convert_to_canonical_forms(schemas)
         #             print(f"{tag.short_base_tag}: {tag.extension_or_value_portion}")
         #         print("to here")
         # print(f"{sidecar}")
diff --git a/hedtools/hed/tools/data_util.py b/hedtools/hed/util/data_util.py
similarity index 100%
rename from hedtools/hed/tools/data_util.py
rename to hedtools/hed/util/data_util.py
diff --git a/hedtools/hed/tools/io_util.py b/hedtools/hed/util/io_util.py
similarity index 79%
rename from hedtools/hed/tools/io_util.py
rename to hedtools/hed/util/io_util.py
index 66012fbb3..febaa17aa 100644
--- a/hedtools/hed/tools/io_util.py
+++ b/hedtools/hed/util/io_util.py
@@ -2,6 +2,32 @@
 from werkzeug.utils import secure_filename
 
 
+def check_filename(test_file, name_prefix=None, name_suffix=None, extensions=None):
+    """ Determines whether test_file has correct extension, name_suffix, and name_prefix.
+
+    Everything is converted to lower case prior to testing so this test should be case insensitive.
+
+     Args:
+         test_file (str) :           Path of filename to test
+         name_prefix (str):          An optional name_prefix for the base filename
+         name_suffix (str):          An optional name_suffix for the base file name
+         extensions (list):     An optional list of file extensions
+
+     Returns:
+         tuple (list, list):            Returns two lists one with
+     """
+
+    file_split = os.path.splitext(test_file.lower())
+    is_name = True
+    if extensions and file_split[1] not in [x.lower() for x in extensions]:
+        is_name = False
+    elif name_suffix and not file_split[0].endswith(name_suffix.lower()):
+        is_name = False
+    elif name_prefix and not file_split[0].startswith(name_prefix.lower()):
+        is_name = False
+    return is_name
+
+
 def generate_filename(base_name, name_prefix=None, name_suffix=None, extension=None):
     """Generates a filename for the attachment of the form prefix_basename_suffix + extension.
 
@@ -54,7 +80,7 @@ def get_dir_dictionary(dir_path, name_prefix=None, name_suffix=None, extensions=
     for r, d, f in os.walk(dir_path):
         file_list = []
         for r_file in f:
-            if test_filename(r_file, name_prefix, name_suffix, extensions):
+            if check_filename(r_file, name_prefix, name_suffix, extensions):
                 file_list.append(os.path.abspath(os.path.join(r, r_file)))
         if skip_empty and not file_list:
             continue
@@ -77,7 +103,7 @@ def get_file_list(dir_path, name_prefix=None, name_suffix=None, extensions=None)
     file_list = []
     for r, d, f in os.walk(dir_path):
         for r_file in f:
-            if test_filename(r_file, name_prefix, name_suffix, extensions):
+            if check_filename(r_file, name_prefix, name_suffix, extensions):
                 file_list.append(os.path.join(r, r_file))
     return file_list
 
@@ -138,34 +164,43 @@ def make_key(key_string, indices=(0, -2), separator='_'):
     return key_value[:-1]
 
 
-def test_filename(test_file, name_prefix=None, name_suffix=None, extensions=None):
-    """ Determines whether test_file has correct extension, name_suffix, and name_prefix.
+if __name__ == '__main__':
+    path = 'D:\\Research\\HED\\hed-examples\\datasets\\eeg_ds003654s'
+    files = get_file_list(path, name_prefix=None, name_suffix="events", extensions=None)
+    for file in files:
+        get_path_components(file, path)
 
-    Everything is converted to lower case prior to testing so this test should be case insensitive.
 
-     Args:
-         test_file (str) :           Path of filename to test
-         name_prefix (str):          An optional name_prefix for the base filename
-         name_suffix (str):          An optional name_suffix for the base file name
-         extensions (list):     An optional list of file extensions
+def parse_bids_filename(file_path):
+    """ Split a filename into its BIDS suffix, extension, and entities
 
-     Returns:
-         tuple (list, list):            Returns two lists one with
-     """
+        Args:
+            file_path (str)     Path to be parsed
 
-    file_split = os.path.splitext(test_file.lower())
-    is_name = True
-    if extensions and file_split[1] not in [x.lower() for x in extensions]:
-        is_name = False
-    elif name_suffix and not file_split[0].endswith(name_suffix.lower()):
-        is_name = False
-    elif name_prefix and not file_split[0].startswith(name_prefix.lower()):
-        is_name = False
-    return is_name
+        Returns:
+            suffix (str)        BIDS suffix name
+            ext (str)           File extension (including the .)
+            entities (dict)     Dictionary with key-value pair being (entity type, entity value)
+            unmatched (list)    List of unmatched pieces of the filename
 
+    """
 
-if __name__ == '__main__':
-    path = 'D:\\Research\\HED\\hed-examples\\datasets\\eeg_ds003654s'
-    files = get_file_list(path, name_prefix=None, name_suffix="events", extensions=None)
-    for file in files:
-        get_path_components(file, path)
+    filename = os.path.splitext(os.path.basename(file_path))
+    ext = filename[1].lower()
+    basename = filename[0]
+    if '-' not in basename:
+        return basename, ext, {}, []
+    entity_pieces = basename.split('_')
+    if len(entity_pieces) < 2:
+        return '', ext, {}, [basename]
+    suffix = entity_pieces[-1]
+    entities = {}
+    unmatched = []
+    entity_pieces = entity_pieces[:-1]
+    for entity in reversed(list(enumerate(entity_pieces))):
+        pieces = entity[1].split('-')
+        if len(pieces) != 2:
+            unmatched.append(entity[1])
+        else:
+            entities[pieces[0]] = pieces[1]
+    return suffix, ext, entities, unmatched
\ No newline at end of file
diff --git a/webtools/.coveragerc b/webtools/.coveragerc
index 85771449f..b3e955c03 100644
--- a/webtools/.coveragerc
+++ b/webtools/.coveragerc
@@ -2,12 +2,17 @@
 branch = True
 omit =
         */__init__.py
+        **/__init__.py
         ./deploy_hed/*
-        ./examples/*
         ./constants/*
         */app_factory.py
         ./venv/*
         *tests/*
+        */venv/*
+        */tests/*
+        **/tests/
+        **/test/
+        */test*
 
 
 [report]
diff --git a/webtools/hedweb/events.py b/webtools/hedweb/events.py
index 3cf31946b..f9fa34a57 100644
--- a/webtools/hedweb/events.py
+++ b/webtools/hedweb/events.py
@@ -10,8 +10,8 @@
 from hed.validator.hed_validator import HedValidator
 from hedweb.constants import base_constants
 from hedweb.columns import create_column_selections
-from hed.tools.io_util import generate_filename
-from hed.tools.map_util import get_columns_info
+from hed.util.io_util import generate_filename
+from hed.tools.summaries.map_summary import get_columns_info
 from hed.tools.sidecar_map import SidecarMap
 from hedweb.web_util import form_has_option, get_hed_schema_from_pull_down
 
diff --git a/webtools/hedweb/schema.py b/webtools/hedweb/schema.py
index e6efec0ec..aa00cf174 100644
--- a/webtools/hedweb/schema.py
+++ b/webtools/hedweb/schema.py
@@ -7,7 +7,7 @@
 from hed.util.file_util import get_file_extension
 from hed.errors.error_reporter import get_printable_issue_string
 from hed.errors.exceptions import HedFileError
-from hed.tools.io_util import generate_filename
+from hed.util.io_util import generate_filename
 from hedweb.web_util import form_has_file, form_has_option, form_has_url
 from hedweb.constants import base_constants, file_constants
 
diff --git a/webtools/hedweb/services.py b/webtools/hedweb/services.py
index f5347d394..7022c6c57 100644
--- a/webtools/hedweb/services.py
+++ b/webtools/hedweb/services.py
@@ -165,7 +165,8 @@ def services_list():
         description = info['Description']
         parm_string = json.dumps(info['Parameters'])
         return_string = info['Returns']
-        next_string = f'{service}:\n\tDescription: {description}\n\tParameters: {parm_string}\n\tReturns: {return_string}\n'
+        next_string = \
+            f'{service}:\n\tDescription: {description}\n\tParameters: {parm_string}\n\tReturns: {return_string}\n'
         services_string += next_string
 
     meanings_string = '\nParameter meanings:\n'
diff --git a/webtools/hedweb/sidecar.py b/webtools/hedweb/sidecar.py
index 8b3766fae..04646ef3f 100644
--- a/webtools/hedweb/sidecar.py
+++ b/webtools/hedweb/sidecar.py
@@ -9,7 +9,7 @@
 from hed.errors.error_reporter import get_printable_issue_string
 from hed.errors.exceptions import HedFileError
 from hedweb.constants import base_constants, file_constants
-from hed.tools.io_util import generate_filename
+from hed.util.io_util import generate_filename
 from hed.tools.sidecar_map import SidecarMap
 from hedweb.web_util import form_has_option, get_hed_schema_from_pull_down
 
@@ -104,10 +104,8 @@ def sidecar_convert(hed_schema, json_sidecar, command=base_constants.COMMAND_TO_
     else:
         tag_form = 'short_tag'
     issues = []
-    validator = HedValidator(hed_schema)
-    for hed_string_obj, position_info, issue_items in json_sidecar.hed_string_iter(validators=validator,
-                                                                                   expand_defs=expand_defs,
-                                                                                   allow_placeholder=True):
+    for hed_string_obj, position_info, issue_items in json_sidecar.hed_string_iter(validators=hed_schema,
+                                                                                   expand_defs=expand_defs):
         converted_string = hed_string_obj.get_as_form(tag_form)
         issues = issues + issue_items
         json_sidecar.set_hed_string(converted_string, position_info)
diff --git a/webtools/hedweb/spreadsheet.py b/webtools/hedweb/spreadsheet.py
index f2f37afd0..c64c45591 100644
--- a/webtools/hedweb/spreadsheet.py
+++ b/webtools/hedweb/spreadsheet.py
@@ -5,7 +5,7 @@
 from hed import schema as hedschema
 from hed.errors.error_reporter import get_printable_issue_string
 from hed.errors.exceptions import HedFileError
-from hed.tools.io_util import generate_filename
+from hed.util.io_util import generate_filename
 from hed.validator.hed_validator import HedValidator
 from hed.util.file_util import get_file_extension
 


Test patch:
diff --git a/hedtools/tests/models/test_sidecar.py b/hedtools/tests/models/test_sidecar.py
index 77fce958a..563451fdf 100644
--- a/hedtools/tests/models/test_sidecar.py
+++ b/hedtools/tests/models/test_sidecar.py
@@ -85,7 +85,7 @@ def test__iter__(self):
     #
     # def get_validation_issues(self):
     #
-    # def validate_entries(self, hed_schema=None, name=None):
+    # def validate_entries(self, schemas=None, name=None):
     #
 
     def test_validate_column_group(self):
diff --git a/hedtools/tests/schema/test_hed_tag.py b/hedtools/tests/schema/test_hed_tag.py
index f38b93a1e..9be08717b 100644
--- a/hedtools/tests/schema/test_hed_tag.py
+++ b/hedtools/tests/schema/test_hed_tag.py
@@ -58,7 +58,7 @@ def test_correctly_determine_tag_takes_value(self):
     def test_should_determine_default_unit(self):
         unit_class_tag1 = HedTag('duration/35 ms', hed_schema=self.hed_schema)
         # unit_class_tag2 = HedTag('participant/effect/cognitive/reward/11 dollars',
-        #                          hed_schema=self.hed_schema)
+        #                          schemas=self.schemas)
         no_unit_class_tag = HedTag('RGB-red/0.5', hed_schema=self.hed_schema)
         no_value_tag = HedTag('Black', hed_schema=self.hed_schema)
         unit_class_tag1_result = unit_class_tag1.get_unit_class_default_unit()
@@ -73,7 +73,7 @@ def test_should_determine_default_unit(self):
     def test_correctly_determine_tag_unit_classes(self):
         unit_class_tag1 = HedTag('distance/35 px', hed_schema=self.hed_schema)
         # Todo: Make a schema with a currency unit to test this
-        # unit_class_tag2 = HedTag('reward/$10.55', hed_schema=self.hed_schema)
+        # unit_class_tag2 = HedTag('reward/$10.55', schemas=self.schemas)
         unit_class_tag3 = HedTag('duration/#', hed_schema=self.hed_schema)
         no_unit_class_tag = HedTag('RGB-red/0.5', hed_schema=self.hed_schema)
         unit_class_tag1_result = list(unit_class_tag1.unit_classes.keys())
@@ -88,7 +88,7 @@ def test_correctly_determine_tag_unit_classes(self):
     def test_determine_tags_legal_units(self):
         unit_class_tag1 = HedTag('distance/35 px', hed_schema=self.hed_schema)
         # todo: add this back in when we have a currency unit or make a test for one.
-        # unit_class_tag2 = HedTag('reward/$10.55', hed_schema=self.hed_schema)
+        # unit_class_tag2 = HedTag('reward/$10.55', schemas=self.schemas)
         no_unit_class_tag = HedTag('RGB-red/0.5', hed_schema=self.hed_schema)
         unit_class_tag1_result = unit_class_tag1.get_tag_unit_class_units()
         # unit_class_tag2_result = unit_class_tag2.get_tag_unit_class_units()
@@ -110,15 +110,15 @@ def test_determine_tags_legal_units(self):
 
     def test_strip_off_units_from_value(self):
         # todo: add this back in when we have a currency unit or make a test for one.
-        # dollars_string_no_space = HedTag('Participant/Effect/Cognitive/Reward/$25.99', hed_schema=self.hed_schema)
-        # dollars_string = HedTag('Participant/Effect/Cognitive/Reward/$ 25.99', hed_schema=self.hed_schema)
-        # dollars_string_invalid = HedTag('Participant/Effect/Cognitive/Reward/25.99$', hed_schema=self.hed_schema)
+        # dollars_string_no_space = HedTag('Participant/Effect/Cognitive/Reward/$25.99', schemas=self.schemas)
+        # dollars_string = HedTag('Participant/Effect/Cognitive/Reward/$ 25.99', schemas=self.schemas)
+        # dollars_string_invalid = HedTag('Participant/Effect/Cognitive/Reward/25.99$', schemas=self.schemas)
         volume_string_no_space = HedTag('Volume/100m^3', hed_schema=self.hed_schema)
         volume_string = HedTag('Volume/100 m^3', hed_schema=self.hed_schema)
         prefixed_volume_string = HedTag('Volume/100 cm^3', hed_schema=self.hed_schema)
         invalid_volume_string = HedTag('Volume/200 cm', hed_schema=self.hed_schema)
         # currency_units = {
-        #     'currency':self.hed_schema.unit_classes['currency']
+        #     'currency':self.schemas.unit_classes['currency']
         # }
         volume_units = {
             'volume': self.hed_schema.unit_classes['volumeUnits']
diff --git a/hedtools/tests/schema_cache_test/last_update.txt b/hedtools/tests/schema_cache_test/last_update.txt
index a1f46e262..f0bc262f5 100644
--- a/hedtools/tests/schema_cache_test/last_update.txt
+++ b/hedtools/tests/schema_cache_test/last_update.txt
@@ -1,1 +1,1 @@
-1642546633.415931
\ No newline at end of file
+1642695314.9813824
\ No newline at end of file
diff --git a/hedtools/tests/tools/bids/test_bids_dataset.py b/hedtools/tests/tools/bids/test_bids_dataset.py
index 5ebc15780..991d8926a 100644
--- a/hedtools/tests/tools/bids/test_bids_dataset.py
+++ b/hedtools/tests/tools/bids/test_bids_dataset.py
@@ -1,7 +1,10 @@
 import os
+import json
 import unittest
 import pandas as pd
-from hed.tools.bids.bids_dataset import BidsDataset, BidsEventFiles
+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
 
 
 class Test(unittest.TestCase):
@@ -9,23 +12,72 @@ class Test(unittest.TestCase):
     @classmethod
     def setUpClass(cls):
         cls.root_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../../data/bids/eeg_ds003654s_hed')
+        cls.library_path = os.path.join(os.path.dirname(os.path.abspath(__file__)),
+                                        '../../data/bids/eeg_ds003654s_hed_library')
 
     def test_bids_constructor(self):
-        bids = BidsDataset(Test.root_path)
-        self.assertIsInstance(bids, BidsDataset, "BidsDataset should create a valid object from valid dataset")
-        self.assertIsInstance(bids.participants, pd.DataFrame, "BidsDataset participants should be a DataFrame")
-        self.assertIsInstance(bids.dataset_description, dict, "BidsDataset dataset_description should be a dict")
-        self.assertIsInstance(bids.event_files, BidsEventFiles, "BidsDataset event_files should be  BidsEventFiles")
+        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")
+        self.assertIsInstance(bids1.schemas, HedSchemaGroup, "BidsDataset schemas should be HedSchemaGroup")
+
+        bids2 = BidsDataset(Test.library_path)
+        self.assertIsInstance(bids2, BidsDataset,
+                              "BidsDataset with libraries should create a valid object from valid dataset")
+        self.assertIsInstance(bids2.participants, pd.DataFrame,
+                              "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")
+        self.assertIsInstance(bids2.schemas, HedSchemaGroup,
+                              "BidsDataset with libraries should have schemas that is a HedSchemaGroup")
 
     def test_bids_validator(self):
-        bids = BidsDataset(Test.root_path)
-        self.assertIsInstance(bids, BidsDataset, "BidsDataset should create a valid object from valid dataset")
-        issues = bids.validate()
-        self.assertTrue(issues, "It should return issues when the default check_for_warnings is used")
-        issues = bids.validate(check_for_warnings=True)
-        self.assertTrue(issues, "It should return issues when check_for_warnings is explicitly set to True")
-        issues = bids.validate(check_for_warnings=False)
-        self.assertFalse(issues, "It should return no issues when check_for_warnings is explicitly set to False")
+        bids1 = BidsDataset(Test.root_path)
+        self.assertIsInstance(bids1, BidsDataset, "BidsDataset should create a valid object from valid dataset")
+        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)
+        self.assertFalse(issues,
+                         "BidsDataset validate should return no issues when check_for_warnings is False")
+
+        bids2 = BidsDataset(Test.library_path)
+        self.assertIsInstance(bids2, BidsDataset,
+                              "BidsDataset with libraries should create a valid object from valid dataset")
+        issues = bids2.validate()
+        self.assertTrue(issues,
+                        "BidsDataset with libraries should return issues when the default check_for_warnings is used")
+        issues = bids2.validate(check_for_warnings=True)
+        self.assertTrue(issues,
+                        "BidsDataset with libraries should return issues when check_for_warnings is True")
+        issues = bids2.validate(check_for_warnings=False)
+        self.assertFalse(issues,
+                         "BidsDataset with libraries should return no issues when check_for_warnings is False")
+
+    def test_get_summary(self):
+        bids1 = BidsDataset(Test.root_path)
+        summary1 = bids1.get_summary()
+        self.assertIsInstance(summary1, dict, "BidsDataset summary is a dictionary")
+        self.assertTrue("hed_schema_versions" in summary1, "BidsDataset summary has a hed_schema_versions key")
+        self.assertIsInstance(summary1["hed_schema_versions"], list,
+                              "BidsDataset summary hed_schema_versions is a list")
+        self.assertEqual(len(summary1["hed_schema_versions"]), 1,
+                         "BidsDataset summary hed_schema_versions entry has one schema")
+        bids2 = BidsDataset(Test.library_path)
+        summary2 = bids2.get_summary()
+        self.assertIsInstance(summary2, dict, "BidsDataset with libraries has a summary that is a dictionary")
+        self.assertTrue("hed_schema_versions" in summary2,
+                        "BidsDataset with libraries has a summary with a hed_schema_versions key")
+        self.assertIsInstance(summary2["hed_schema_versions"], list,
+                              "BidsDataset with libraries hed_schema_versions in summary is a list")
+        self.assertEqual(len(summary2["hed_schema_versions"]), 3,
+                         "BidsDataset with libraries summary hed_schema_versions list has 3 schemas")
 
 
 if __name__ == '__main__':
diff --git a/hedtools/tests/tools/bids/test_bids_file.py b/hedtools/tests/tools/bids/test_bids_file.py
index e44d86e98..f7daad355 100644
--- a/hedtools/tests/tools/bids/test_bids_file.py
+++ b/hedtools/tests/tools/bids/test_bids_file.py
@@ -25,9 +25,12 @@ def test_bids_file_constructor(self):
 
     def test_bids_file_str(self):
         bids = BidsFile(Test.sidecar_path)
-        my_str = str(bids)
-        self.assertIsInstance(my_str, str, "BidsFile __str__ method should return a string")
-        self.assertGreater(len(my_str), 0, "BidsFile __str__ method returns a non-empty string")
+        my_str1 = str(bids)
+        self.assertIsInstance(my_str1, str, "BidsFile convert to string should return a string")
+        self.assertGreater(len(my_str1), 0, "BidsFile convert to string should return a non-empty string")
+        my_str2 = bids.__str__()
+        self.assertIsInstance(my_str2, str, "BidsFile __str__ method should return a string")
+        self.assertGreater(len(my_str2), 0, "BidsFile __str__ method should return a non-empty string")
 
 
 if __name__ == '__main__':
diff --git a/hedtools/tests/tools/bids/test_bids_json_file.py b/hedtools/tests/tools/bids/test_bids_json_file.py
index 3c0de3210..4bc0fdb30 100644
--- a/hedtools/tests/tools/bids/test_bids_json_file.py
+++ b/hedtools/tests/tools/bids/test_bids_json_file.py
@@ -1,6 +1,5 @@
 import os
 import unittest
-from hed.models.sidecar import Sidecar
 from hed.tools.bids.bids_json_file import BidsJsonFile
 
 
@@ -10,21 +9,37 @@ class Test(unittest.TestCase):
     def setUpClass(cls):
         cls.description_path = os.path.join(os.path.dirname(os.path.abspath(__file__)),
                                             '../../data/bids/eeg_ds003654s_hed/dataset_description.json')
-        cls.sidecar_path = os.path.join(os.path.dirname(os.path.abspath(__file__)),
+        cls.json_path = os.path.join(os.path.dirname(os.path.abspath(__file__)),
                                         '../../data/bids/eeg_ds003654s_hed/task-FacePerception_events.json')
 
     def test_constructor(self):
         json1 = BidsJsonFile(Test.description_path, set_contents=False)
         self.assertEqual(json1.suffix, 'dataset_description', "BidsJsonFile should have correct name_suffix")
-        self.assertEqual(json1.ext, '.json', "BidsSidecarFile should have correct ext")
-        self.assertEqual(len(json1.entities), 0, "BidsSidecarFile should have right number of entities")
+        self.assertEqual(json1.ext, '.json', "BidsJsonFile should have correct ext")
+        self.assertEqual(len(json1.entities), 0, "BidssonFile should have right number of entities")
         self.assertFalse(json1.contents)
 
-        sidecar2 = BidsJsonFile(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.entities), 1, "BidsSidecarFile should have right number of entities")
-        self.assertIsInstance(sidecar2.contents, dict, "BidsSidecarFile should contain a dictionary")
+        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.entities), 1, "BidsJsonFile should have right number of entities")
+        self.assertIsInstance(sidecar2.contents, dict, "BidsJsonFile should contain a dictionary")
+
+    def test_bids_json_file_str(self):
+        json_file1 = BidsJsonFile(Test.description_path)
+        self.assertTrue(str(json_file1), "BidsJsonFile should have a string representation")
+        json_file2 = BidsJsonFile(Test.description_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__':
diff --git a/hedtools/tests/tools/bids/test_bids_sidecar_file.py b/hedtools/tests/tools/bids/test_bids_sidecar_file.py
index 71b5f896b..ba5c6c90c 100644
--- a/hedtools/tests/tools/bids/test_bids_sidecar_file.py
+++ b/hedtools/tests/tools/bids/test_bids_sidecar_file.py
@@ -11,7 +11,8 @@ class Test(unittest.TestCase):
     @classmethod
     def setUpClass(cls):
         cls.event_path = os.path.join(os.path.dirname(os.path.abspath(__file__)),
-                                      '../../data/bids/eeg_ds003654s_hed/sub-002/eeg/sub-002_task-FacePerception_run-1_events.tsv')
+                                      '../../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.abspath(__file__)),
                                         '../../data/bids/eeg_ds003654s_hed/task-FacePerception_events.json')
 
diff --git a/hedtools/tests/tools/bids/test_bids_tsv_file.py b/hedtools/tests/tools/bids/test_bids_tsv_file.py
new file mode 100644
index 000000000..6802556d0
--- /dev/null
+++ b/hedtools/tests/tools/bids/test_bids_tsv_file.py
@@ -0,0 +1,40 @@
+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.abspath(__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.entities), 3, "BidsTsvFile should have right number of entities")
+
+    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/hedtools/tests/tools/bids/test_bids_util.py b/hedtools/tests/tools/bids/test_bids_util.py
index bab948c54..db5371a21 100644
--- a/hedtools/tests/tools/bids/test_bids_util.py
+++ b/hedtools/tests/tools/bids/test_bids_util.py
@@ -1,6 +1,6 @@
 import os
 import unittest
-from hed.tools.bids.bids_util import parse_bids_filename
+from hed.util.io_util import parse_bids_filename
 
 
 class Test(unittest.TestCase):
diff --git a/hedtools/tests/tools/summaries/__init__.py b/hedtools/tests/tools/summaries/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/hedtools/tests/tools/test_col_dict.py b/hedtools/tests/tools/summaries/test_col_dict.py
similarity index 95%
rename from hedtools/tests/tools/test_col_dict.py
rename to hedtools/tests/tools/summaries/test_col_dict.py
index f151667f2..b8937e424 100644
--- a/hedtools/tests/tools/test_col_dict.py
+++ b/hedtools/tests/tools/summaries/test_col_dict.py
@@ -3,15 +3,15 @@
 from unittest import mock
 import pandas as pd
 from hed.errors.exceptions import HedFileError
-from hed.tools.col_dict import ColumnDict
-from hed.tools.data_util import get_new_dataframe
+from hed.tools.summaries.col_dict import ColumnDict
+from hed.util.data_util import get_new_dataframe
 
 
 class Test(unittest.TestCase):
     @classmethod
     def setUpClass(cls):
-        stern_base_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../data/sternberg')
-        att_base_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../data/attention_shift')
+        stern_base_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../../data/sternberg')
+        att_base_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../../data/attention_shift')
         cls.stern_map_path = os.path.join(stern_base_dir, "sternberg_map.tsv")
         cls.stern_test1_path = os.path.join(stern_base_dir, "sternberg_test_events.tsv")
         cls.stern_test2_path = os.path.join(stern_base_dir, "sternberg_with_quotes_events.tsv")
diff --git a/hedtools/tests/tools/test_hed_group_summary.py b/hedtools/tests/tools/summaries/test_hed_group_summary.py
similarity index 87%
rename from hedtools/tests/tools/test_hed_group_summary.py
rename to hedtools/tests/tools/summaries/test_hed_group_summary.py
index 1aeae3ba9..335c99a59 100644
--- a/hedtools/tests/tools/test_hed_group_summary.py
+++ b/hedtools/tests/tools/summaries/test_hed_group_summary.py
@@ -3,7 +3,7 @@
 from hed.models.sidecar import Sidecar
 from hed.schema import load_schema
 
-from hed.tools.hed_group_summary import HedGroupSummary
+from hed.tools.summaries.hed_group_summary import HedGroupSummary
 
 
 class Test(unittest.TestCase):
@@ -11,7 +11,7 @@ class Test(unittest.TestCase):
     @classmethod
     def setUpClass(cls):
         cls.bids_path = os.path.join(os.path.dirname(os.path.abspath(__file__)),
-                                     '../data/bids/eeg_ds003654s_hed/task-FacePerception_events.json')
+                                     '../../data/bids/eeg_ds003654s_hed/task-FacePerception_events.json')
 
     def test_hed_group_summary_constructor(self):
         hed_url_path = 'https://raw.githubusercontent.com/hed-standard/hed-specification/master/hedxml/HED8.0.0.xml'
diff --git a/hedtools/tests/tools/test_key_dict.py b/hedtools/tests/tools/summaries/test_key_dict.py
similarity index 95%
rename from hedtools/tests/tools/test_key_dict.py
rename to hedtools/tests/tools/summaries/test_key_dict.py
index 9955ded90..5a94c352c 100644
--- a/hedtools/tests/tools/test_key_dict.py
+++ b/hedtools/tests/tools/summaries/test_key_dict.py
@@ -3,15 +3,15 @@
 from unittest import mock
 import pandas as pd
 from hed.errors.exceptions import HedFileError
-from hed.tools.key_template import KeyTemplate
-from hed.tools.data_util import get_row_hash
+from hed.tools.summaries.key_template import KeyTemplate
+from hed.util.data_util import get_row_hash
 
 
 class Test(unittest.TestCase):
     @classmethod
     def setUpClass(cls):
-        stern_base_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../data/sternberg')
-        att_base_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../data/attention_shift')
+        stern_base_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../../data/sternberg')
+        att_base_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../../data/attention_shift')
         cls.stern_map_path = os.path.join(stern_base_dir, "sternberg_map.tsv")
         cls.stern_test1_path = os.path.join(stern_base_dir, "sternberg_test_events.tsv")
         cls.stern_test2_path = os.path.join(stern_base_dir, "sternberg_with_quotes_events.tsv")
diff --git a/hedtools/tests/tools/test_key_map.py b/hedtools/tests/tools/summaries/test_key_map.py
similarity index 94%
rename from hedtools/tests/tools/test_key_map.py
rename to hedtools/tests/tools/summaries/test_key_map.py
index 5cec8e014..10e274eec 100644
--- a/hedtools/tests/tools/test_key_map.py
+++ b/hedtools/tests/tools/summaries/test_key_map.py
@@ -2,17 +2,17 @@
 import os
 import pandas as pd
 from hed.errors.exceptions import HedFileError
-from hed.tools.key_map import KeyMap
-from hed.tools.io_util import get_file_list
-from hed.tools.data_util import get_row_hash
+from hed.tools.summaries.key_map import KeyMap
+from hed.util.io_util import get_file_list
+from hed.util.data_util import get_row_hash
 
 
 class Test(unittest.TestCase):
     @classmethod
     def setUpClass(cls):
-        cls.data_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../data/')
-        stern_base_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../data/sternberg')
-        att_base_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../data/attention_shift')
+        cls.data_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../../data/')
+        stern_base_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../../data/sternberg')
+        att_base_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../../data/attention_shift')
         cls.stern_map_path = os.path.join(stern_base_dir, "sternberg_map.tsv")
         cls.stern_test1_path = os.path.join(stern_base_dir, "sternberg_test_events.tsv")
         cls.stern_test2_path = os.path.join(stern_base_dir, "sternberg_with_quotes_events.tsv")
diff --git a/hedtools/tests/tools/test_map_util.py b/hedtools/tests/tools/summaries/test_map_summary.py
similarity index 91%
rename from hedtools/tests/tools/test_map_util.py
rename to hedtools/tests/tools/summaries/test_map_summary.py
index d2badf30e..462c996f6 100644
--- a/hedtools/tests/tools/test_map_util.py
+++ b/hedtools/tests/tools/summaries/test_map_summary.py
@@ -1,18 +1,18 @@
 import os
 import unittest
-from hed.tools.col_dict import ColumnDict
-from hed.tools.io_util import get_file_list, make_file_dict
-from hed.tools.map_util import get_columns_info, get_key_counts, make_combined_dicts, update_dict_counts
-from hed.tools.data_util import get_new_dataframe
+from hed.tools.summaries.col_dict import ColumnDict
+from hed.util.io_util import get_file_list, make_file_dict
+from hed.tools.summaries.map_summary import get_columns_info, get_key_counts, make_combined_dicts, update_dict_counts
+from hed.util.data_util import get_new_dataframe
 
 
 class Test(unittest.TestCase):
 
     @classmethod
     def setUpClass(cls):
-        cls.bids_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../data/bids/eeg_ds003654s_hed')
-        stern_base_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../data/sternberg')
-        att_base_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../data/attention_shift')
+        cls.bids_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../../data/bids/eeg_ds003654s_hed')
+        stern_base_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../../data/sternberg')
+        att_base_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../../data/attention_shift')
         cls.stern_map_path = os.path.join(stern_base_dir, "sternberg_map.tsv")
         cls.stern_test1_path = os.path.join(stern_base_dir, "sternberg_test_events.tsv")
         cls.stern_test2_path = os.path.join(stern_base_dir, "sternberg_with_quotes_events.tsv")
diff --git a/hedtools/tests/tools/test_hed_logger.py b/hedtools/tests/tools/test_hed_logger.py
index a3328924f..b1f328aff 100644
--- a/hedtools/tests/tools/test_hed_logger.py
+++ b/hedtools/tests/tools/test_hed_logger.py
@@ -34,10 +34,22 @@ def test_get_keys(self):
 
     def test_print_log(self):
         status = HedLogger()
-        with mock.patch('sys.stdout', new=StringIO()) as fake_out:
-            self.assertIsInstance(fake_out, StringIO, "Mock creates a StringIO")
+        with mock.patch('sys.stdout', new=StringIO()) as fake_out1:
+            self.assertIsInstance(fake_out1, StringIO, "Mock creates a StringIO")
             status.add("baloney", "Test message 1", also_print=True)
             status.add("baloney", "Test message 2", also_print=True)
+            stuff1 = fake_out1.getvalue()
+            self.assertGreater(len(stuff1), 0, "HedLogger should print messages if also_print is True")
+            self.assertEqual(stuff1.count('\n'), 2,
+                             "HedLogger should output right number of lines if also_print is True")
+
+        with mock.patch('sys.stdout', new=StringIO()) as fake_out2:
+            self.assertIsInstance(fake_out2, StringIO, "Mock creates a StringIO")
+            status.print_log()
+            stuff2 = fake_out2.getvalue()
+            self.assertGreater(len(stuff2), 0, "HedLogger should print messages if also_print is True")
+            self.assertEqual(stuff2.count('\n'), 3,
+                             "HedLogger should output right number of lines if also_print is True")
 
 
 if __name__ == '__main__':
diff --git a/hedtools/tests/tools/test_sidecar_map.py b/hedtools/tests/tools/test_sidecar_map.py
index baaadcb33..5c1e05e45 100644
--- a/hedtools/tests/tools/test_sidecar_map.py
+++ b/hedtools/tests/tools/test_sidecar_map.py
@@ -1,7 +1,7 @@
 import os
 import unittest
 from hed.tools.sidecar_map import SidecarMap
-from hed.tools.data_util import get_new_dataframe
+from hed.util.data_util import get_new_dataframe
 from hed.errors.exceptions import HedFileError
 
 
diff --git a/hedtools/tests/tools/test_data_util.py b/hedtools/tests/util/test_data_util.py
similarity index 97%
rename from hedtools/tests/tools/test_data_util.py
rename to hedtools/tests/util/test_data_util.py
index 2c93cc408..721d2aa23 100644
--- a/hedtools/tests/tools/test_data_util.py
+++ b/hedtools/tests/util/test_data_util.py
@@ -3,7 +3,7 @@
 import numpy as np
 from pandas import DataFrame, read_csv
 from hed.errors.exceptions import HedFileError
-from hed.tools.data_util import add_columns, check_match, delete_columns, delete_rows_by_column, \
+from hed.util.data_util import add_columns, check_match, delete_columns, delete_rows_by_column, \
     get_key_hash, get_new_dataframe, get_row_hash, \
     make_info_dataframe, remove_quotes, reorder_columns, replace_values, separate_columns
 
diff --git a/hedtools/tests/tools/test_io_util.py b/hedtools/tests/util/test_io_util.py
similarity index 50%
rename from hedtools/tests/tools/test_io_util.py
rename to hedtools/tests/util/test_io_util.py
index 2f5888c34..1b14e65f5 100644
--- a/hedtools/tests/tools/test_io_util.py
+++ b/hedtools/tests/util/test_io_util.py
@@ -1,14 +1,15 @@
 import os
 import unittest
 from hed.errors.exceptions import HedFileError
-from hed.tools.io_util import generate_filename, get_dir_dictionary, get_file_list, get_path_components
+from hed.util.io_util import generate_filename, get_dir_dictionary, get_file_list, get_path_components
+from hed.util.io_util import parse_bids_filename
 
 
 class Test(unittest.TestCase):
 
     @classmethod
     def setUpClass(cls):
-        cls.bids_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../data/bids_old')
+        cls.bids_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../data/bids/eeg_ds003654s_hed')
         stern_base_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../data/sternberg')
         att_base_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../data/attention_shift')
         cls.stern_map_path = os.path.join(stern_base_dir, "sternberg_map.tsv")
@@ -52,11 +53,10 @@ def test_generate_file_name(self):
         filename = generate_filename('HED7.2.0.xml', name_suffix='_blech', extension='.txt')
         self.assertEqual('HED7.2.0_blech.txt', filename, "Returns correct string when base_name has periods")
 
-
-def test_get_dir_dictionary(self):
-    dir_dict = get_dir_dictionary(self.bids_dir, name_suffix="_events")
-    self.assertTrue(isinstance(dir_dict, dict), "get_dir_dictionary returns a dictionary")
-    self.assertEqual(len(dir_dict), 3, "get_dir_dictionary returns a dictionary of the correct length")
+    def test_get_dir_dictionary(self):
+        dir_dict = get_dir_dictionary(self.bids_dir, name_suffix="_events")
+        self.assertTrue(isinstance(dir_dict, dict), "get_dir_dictionary returns a dictionary")
+        self.assertEqual(len(dir_dict), 3, "get_dir_dictionary returns a dictionary of the correct length")
 
     def test_get_file_list_files(self):
         dir_pairs = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../data/hed_pairs/prologue_tests')
@@ -109,6 +109,81 @@ def test_get_path_components(self):
         self.assertEqual(comps3[0], os.path.abspath(base_path), "get_path_components base_path is its own base_path")
         self.assertEqual(len(comps2), 1, "get_path_components file in base_path has no additional components")
 
+    def test_parse_bids_filename_full(self):
+        the_path1 = '/d/base/sub-01/ses-test/func/sub-01_ses-test_task-overt_run-2_bold.json'
+        suffix1, ext1, entity_dict1, unmatched1 = parse_bids_filename(the_path1)
+        self.assertEqual(suffix1, 'bold', "parse_bids_filename should correctly parse name_suffix for full path")
+        self.assertEqual(ext1, '.json', "parse_bids_filename should correctly parse ext for full path")
+        self.assertIsInstance(entity_dict1, dict, "parse_bids_filename should return entities as a dictionary")
+        self.assertEqual(entity_dict1['sub'], '01', "parse_bids_filename should have a sub entity")
+        self.assertEqual(entity_dict1['ses'], 'test', "parse_bids_filename should have a ses entity")
+        self.assertEqual(entity_dict1['task'], 'overt', "parse_bids_filename should have a task entity")
+        self.assertEqual(entity_dict1['run'], '2', "parse_bids_filename should have a run entity")
+        self.assertEqual(len(entity_dict1), 4, "parse_bids_filename should 4 entities in the dictionary")
+        self.assertEqual(len(unmatched1), 0, "parse_bids_filename should not have unmatched items")
+
+    def test_parse_bids_filename_partial(self):
+        path1 = 'task-overt_bold.json'
+        suffix1, ext1, entity_dict1, unmatched1 = parse_bids_filename(path1)
+        self.assertEqual(suffix1, 'bold', "parse_bids_filename should correctly parse name_suffix for name")
+        self.assertEqual(ext1, '.json', "parse_bids_filename should correctly parse ext for name")
+        self.assertIsInstance(entity_dict1, dict, "parse_bids_filename should return entities as a dictionary")
+        self.assertEqual(entity_dict1['task'], 'overt', "parse_bids_filename should have a task entity")
+        self.assertEqual(len(entity_dict1), 1, "parse_bids_filename should 1 entities in the dictionary")
+        path2 = 'task-overt_bold'
+        suffix2, ext2, entity_dict2, unmatched2 = parse_bids_filename(path2)
+        self.assertEqual(suffix2, 'bold', "parse_bids_filename should correctly parse name_suffix for name")
+        self.assertEqual(ext2, '', "parse_bids_filename should return empty extension when only name")
+        self.assertIsInstance(entity_dict2, dict, "parse_bids_filename should return entities as a dictionary")
+        self.assertEqual(entity_dict2['task'], 'overt', "parse_bids_filename should have a task entity")
+        path3 = 'bold'
+        suffix3, ext3, entity_dict3, unmatched3 = parse_bids_filename(path3)
+        self.assertEqual(suffix3, 'bold', "parse_bids_filename should correctly parse name_suffix for name")
+        self.assertEqual(ext3, '', "parse_bids_filename should return empty extension when only name")
+        self.assertIsInstance(entity_dict3, dict, "parse_bids_filename should return entities as a dictionary")
+        self.assertEqual(len(entity_dict3), 0, "parse_bids_filename should return empty dictionary when no entities")
+
+    def test_parse_bids_filename_unmatched(self):
+        path1 = 'dataset_description.json'
+        suffix1, ext1, entity_dict1, unmatched1 = parse_bids_filename(path1)
+        self.assertEqual(suffix1, 'dataset_description',
+                         "parse_bids_filename should correctly parse name_suffix for name")
+        self.assertEqual(ext1, '.json', "parse_bids_filename should return empty extension when only name")
+        self.assertIsInstance(entity_dict1, dict, "parse_bids_filename should return entities as a dictionary")
+        self.assertEqual(len(entity_dict1), 0, "parse_bids_filename should handle names with no entities")
+        self.assertEqual(unmatched1, [],
+                         "parse_bids_filename a name with no entities should be unmatched")
+
+        path2 = 'participants.json'
+        suffix2, ext2, entity_dict2, unmatched2 = parse_bids_filename(path2)
+        self.assertEqual(suffix2, 'participants', "parse_bids_filename should correctly parse name_suffix for name")
+        self.assertEqual(ext2, '.json', "parse_bids_filename should return correct extension when only suffix")
+        self.assertIsInstance(entity_dict2, dict, "parse_bids_filename should return entities as a dictionary")
+        self.assertEqual(len(entity_dict2), 0, "parse_bids_filename should handle names with no entities")
+        self.assertFalse(unmatched2, "parse_bids_filename a name with only suffix should have no unmatched")
+
+    def test_parse_bids_filename_invalid(self):
+        path1 = 'task_sub-01_description.json'
+        suffix1, ext1, entity_dict1, unmatched1 = parse_bids_filename(path1)
+        self.assertEqual(suffix1, 'description', "parse_bids_filename should correctly parse name_suffix for name")
+        self.assertEqual(ext1, '.json', "parse_bids_filename correctly return extension when unmatched entity")
+        self.assertIsInstance(entity_dict1, dict, "parse_bids_filename should return entities as a dictionary")
+        self.assertEqual(len(entity_dict1), 1, "parse_bids_filename should handle names with no entities")
+        self.assertEqual(len(unmatched1), 1,
+                         "parse_bids_filename unmatched list should have correct length when unmatched entity")
+        self.assertEqual(unmatched1[0], 'task',
+                         "parse_bids_filename unmatched list should have correct values when unmatched entity")
+        path2 = 'sub-01.json'
+        suffix2, ext2, entity_dict2, unmatched2 = parse_bids_filename(path2)
+        self.assertEqual(suffix2, '', "parse_bids_filename should correctly parse name_suffix for name")
+        self.assertEqual(ext2, '.json', "parse_bids_filename correctly return extension when unmatched entity")
+        self.assertIsInstance(entity_dict2, dict, "parse_bids_filename should return entities as a dictionary")
+        self.assertEqual(len(entity_dict2), 0, "parse_bids_filename should handle names with no suffix")
+        self.assertEqual(len(unmatched2), 1,
+                         "parse_bids_filename unmatched list should have correct length when no suffix")
+        self.assertEqual(unmatched2[0], 'sub-01',
+                         "parse_bids_filename unmatched list should have correct values when unmatched entity")
+
 
 if __name__ == '__main__':
     unittest.main()