docs: def read(filepath):
      """
      Read a single InkML file
  
      Parameters
      ----------
      filepath : string
          path to the (readable) InkML file
  
      Returns
      -------
      HandwrittenData :
          The parsed InkML file as a HandwrittenData object
      """
      import xml.etree.ElementTree
      root = xml.etree.ElementTree.parse(filepath).getroot()
  
      # Get the raw data
      recording = []
      strokes = sorted(root.findall('{http://www.w3.org/2003/InkML}trace'),
                       key=lambda child: int(child.attrib['id']))
      time = 0
      for stroke in strokes:
          stroke = stroke.text.strip().split(',')
          stroke = [point.strip().split(' ') for point in stroke]
          if len(stroke[0]) == 3:
              stroke = [{'x': float(x), 'y': float(y), 'time': float(t)}
                        for x, y, t in stroke]
          else:
              stroke = [{'x': float(x), 'y': float(y)} for x, y in stroke]
              new_stroke = []
              for p in stroke:
                  new_stroke.append({'x': p['x'], 'y': p['y'], 'time': time})
                  time += 20
              stroke = new_stroke
              time += 200
          recording.append(stroke)
  
      # Get LaTeX
      formula_in_latex = None
      annotations = root.findall('{http://www.w3.org/2003/InkML}annotation')
      for annotation in annotations:
          if annotation.attrib['type'] == 'truth':
              formula_in_latex = annotation.text
      hw = handwritten_data.HandwrittenData(json.dumps(recording),
                                            formula_in_latex=formula_in_latex)
      for annotation in annotations:
          if annotation.attrib['type'] == 'writer':
              hw.writer = annotation.text
          elif annotation.attrib['type'] == 'category':
              hw.category = annotation.text
          elif annotation.attrib['type'] == 'expression':
              hw.expression = annotation.text
  
      # Get segmentation
      segmentation = []
      trace_groups = root.findall('{http://www.w3.org/2003/InkML}traceGroup')
      if len(trace_groups) != 1:
          raise Exception('Malformed InkML',
                          ('Exactly 1 top level traceGroup expected, found %i. '
                           '(%s) - probably no ground truth?') %
                          (len(trace_groups), filepath))
      trace_group = trace_groups[0]
      symbol_stream = []  # has to be consistent with segmentation
      for tg in trace_group.findall('{http://www.w3.org/2003/InkML}traceGroup'):
          annotations = tg.findall('{http://www.w3.org/2003/InkML}annotation')
          if len(annotations) != 1:
              raise ValueError("%i annotations found for '%s'." %
                               (len(annotations), filepath))
          db_id = formula_to_dbid(normalize_symbol_name(annotations[0].text))
          symbol_stream.append(db_id)
          trace_views = tg.findall('{http://www.w3.org/2003/InkML}traceView')
          symbol = []
          for traceView in trace_views:
              symbol.append(int(traceView.attrib['traceDataRef']))
          segmentation.append(symbol)
      hw.symbol_stream = symbol_stream
      hw.segmentation = segmentation
      _flat_seg = [stroke2 for symbol2 in segmentation for stroke2 in symbol2]
      assert len(_flat_seg) == len(recording), \
          ("Segmentation had length %i, but recording has %i strokes (%s)" %
           (len(_flat_seg), len(recording), filepath))
      assert set(_flat_seg) == set(range(len(_flat_seg)))
      hw.inkml = beautify_xml(filepath)
      hw.filepath = filepath
      return hw