code: def read(csa_str):
      """Read CSA header from string `csa_str`
  
      Parameters
      ----------
      csa_str : str
         byte string containing CSA header information
  
      Returns
      -------
      header : dict
         header information as dict, where `header` has fields (at least)
         ``type, n_tags, tags``.  ``header['tags']`` is also a dictionary
         with one key, value pair for each tag in the header.
      """
      csa_len = len(csa_str)
      csa_dict = {"tags": {}}
      hdr_id = csa_str[:4]
      up_str = Unpacker(csa_str, endian="<")
      if hdr_id == b"SV10":  # CSA2
          hdr_type = 2
          up_str.ptr = 4  # omit the SV10
          csa_dict["unused0"] = up_str.read(4)
      else:  # CSA1
          hdr_type = 1
      csa_dict["type"] = hdr_type
      csa_dict["n_tags"], csa_dict["check"] = up_str.unpack("2I")
      if not 0 < csa_dict["n_tags"] <= MAX_CSA_ITEMS:
          raise CSAReadError(
              f"Number of tags `t` should be 0 < t <= {MAX_CSA_ITEMS}. "
              f'Instead found {csa_dict["n_tags"]} tags.'
          )
      for tag_no in range(csa_dict["n_tags"]):
          name, vm, vr, syngodt, n_items, last3 = up_str.unpack(
              "64si4s3i"
          )
          vr = nt_str(vr)
          name = nt_str(name)
          tag = {
              "n_items": n_items,
              "vm": vm,  # value multiplicity
              "vr": vr,  # value representation
              "syngodt": syngodt,
              "last3": last3,
              "tag_no": tag_no,
          }
          if vm == 0:
              n_values = n_items
          else:
              n_values = vm
          # data converter
          converter = _CONVERTERS.get(vr)
          # CSA1 specific length modifier
          if tag_no == 1:
              tag0_n_items = n_items
          if n_items > MAX_CSA_ITEMS:
              raise CSAReadError(
                  f"Expected <= {MAX_CSA_ITEMS} tags, got {n_items}"
              )
          items = []
          for item_no in range(n_items):
              x0, x1, x2, x3 = up_str.unpack("4i")
              ptr = up_str.ptr
              if hdr_type == 1:  # CSA1 - odd length calculation
                  item_len = x0 - tag0_n_items
                  if item_len < 0 or (ptr + item_len) > csa_len:
                      if item_no < vm:
                          items.append("")
                      break
              else:  # CSA2
                  item_len = x1
                  if (ptr + item_len) > csa_len:
                      raise CSAReadError(
                          "Item is too long, aborting read"
                      )
              if item_no >= n_values:
                  assert item_len == 0
                  continue
              item = nt_str(up_str.read(item_len))
              if converter:
                  # we may have fewer real items than are given in
                  # n_items, but we don't know how many - assume that
                  # we've reached the end when we hit an empty item
                  if item_len == 0:
                      n_values = item_no
                      continue
                  item = converter(item)
              items.append(item)
              # go to 4 byte boundary
              plus4 = item_len % 4
              if plus4 != 0:
                  up_str.ptr += 4 - plus4
          tag["items"] = items
          csa_dict["tags"][name] = tag
      return csa_dict
  
file_path: nibabel/nibabel/nicom/csareader.py
imports: import numpy
  from .structreader import Unpacker
  from .utils import find_private_section
label_desc: Use image node_id 2 for calling a global function with example usage: read(csa_str) and returns: csa_dict