Пример #1
0
    def create_from_metadata_info(
            cls,
            model_buffer: bytearray,
            general_md: Optional[metadata_info.GeneralMd] = None,
            input_md: Optional[List[Type[metadata_info.TensorMd]]] = None,
            output_md: Optional[List[Type[metadata_info.TensorMd]]] = None,
            associated_files: Optional[List[str]] = None):
        """Creates MetadataWriter based on the metadata information.

    Args:
      model_buffer: valid buffer of the model file.
      general_md: general information about the model.
      input_md: metadata information of the input tensors.
      output_md: metadata information of the output tensors.
      associated_files: path to the associated files to be populated.

    Returns:
      A MetadataWriter Object.
    """

        if general_md is None:
            general_md = metadata_info.GeneralMd()
        if input_md is None:
            input_md = []
        if output_md is None:
            output_md = []

        model_metadata = general_md.create_metadata()
        input_metadata = [m.create_metadata() for m in input_md]
        output_metadata = [m.create_metadata() for m in output_md]
        return cls.create_from_metadata(model_buffer, model_metadata,
                                        input_metadata, output_metadata,
                                        associated_files)
Пример #2
0
 def __init__(self, model_buffer: bytearray, model_name: str,
              model_description: str):
     self._model_buffer = model_buffer
     self._general_md = metadata_info.GeneralMd(
         name=model_name, description=model_description)
     self._input_mds = []
     self._output_mds = []
     self._associate_files = []
Пример #3
0
    def test_populate_create_from_metadata_info_should_succeed(self):
        model_buffer = test_utils.load_file(_MODEL)
        general_md = metadata_info.GeneralMd(name=_MODEL_NAME)
        input_md = metadata_info.TensorMd(name=_INPUT_NAME)
        output_md = metadata_info.TensorMd(name=_OUTPUT_NAME)

        writer = metadata_writer.MetadataWriter.create_from_metadata_info(
            model_buffer, general_md, [input_md], [output_md], [_LABEL_FILE])
        model_with_metadata = writer.populate()

        self._assert_correct_metadata(model_with_metadata,
                                      _EXPECTED_META_INFO_JSON)
Пример #4
0
    def create_from_metadata_info(
            cls,
            model_buffer: bytearray,
            general_md: Optional[metadata_info.GeneralMd] = None,
            input_md: Optional[metadata_info.InputTextTensorMd] = None,
            output_md: Optional[metadata_info.ClassificationTensorMd] = None):
        """Creates MetadataWriter based on general/input/output information.

    Args:
      model_buffer: valid buffer of the model file.
      general_md: general information about the model. If not specified, default
        general metadata will be generated.
      input_md: input text tensor information, if not specified, default input
        metadata will be generated.
      output_md: output classification tensor information, if not specified,
        default output metadata will be generated.

    Returns:
      A MetadataWriter object.
    """

        if general_md is None:
            general_md = metadata_info.GeneralMd(
                name=_MODEL_NAME, description=_MODEL_DESCRIPTION)

        if input_md is None:
            input_md = metadata_info.InputTextTensorMd(
                name=_INPUT_NAME, description=_INPUT_DESCRIPTION)

        if output_md is None:
            output_md = metadata_info.ClassificationTensorMd(
                name=_OUTPUT_NAME, description=_OUTPUT_DESCRIPTION)

        if output_md.associated_files is None:
            output_md.associated_files = []

        tokenizer_files = []
        if input_md.tokenizer_md:
            tokenizer_files = writer_utils.get_tokenizer_associated_files(
                input_md.tokenizer_md.create_metadata().options)

        return super().create_from_metadata_info(
            model_buffer=model_buffer,
            general_md=general_md,
            input_md=[input_md],
            output_md=[output_md],
            associated_files=[
                file.file_path for file in output_md.associated_files
            ] + tokenizer_files)
    def create_from_metadata_info(
            cls,
            model_buffer: bytearray,
            general_md: Optional[metadata_info.GeneralMd] = None,
            input_md: Optional[metadata_info.InputImageTensorMd] = None,
            output_md: Optional[metadata_info.TensorMd] = None):
        """Creates MetadataWriter based on general/input/outputs information.

    Args:
      model_buffer: valid buffer of the model file.
      general_md: general infromation about the model.
      input_md: input image tensor informaton.
      output_md: output segmentation mask tensor informaton. This tensor is a
        multidimensional array of [1 x mask_height x mask_width x num_classes],
        where mask_width and mask_height are the dimensions of the segmentation
        masks produced by the model, and num_classes is the number of classes
        supported by the model.

    Returns:
      A MetadataWriter object.
    """

        if general_md is None:
            general_md = metadata_info.GeneralMd(
                name=_MODEL_NAME, description=_MODEL_DESCRIPTION)

        if input_md is None:
            input_md = metadata_info.InputImageTensorMd(
                name=_INPUT_NAME,
                description=_INPUT_DESCRIPTION,
                color_space_type=_metadata_fb.ColorSpaceType.RGB)

        if output_md is None:
            output_md = metadata_info.TensorMd(name=_OUTPUT_NAME,
                                               description=_OUTPUT_DESCRIPTION)

        if output_md.associated_files is None:
            output_md.associated_files = []

        return super().create_from_metadata(
            model_buffer,
            model_metadata=general_md.create_metadata(),
            input_metadata=[input_md.create_metadata()],
            output_metadata=[_create_segmentation_masks_metadata(output_md)],
            associated_files=[
                file.file_path for file in output_md.associated_files
            ])
    def create_from_metadata_info_for_multihead(
        cls,
        model_buffer: bytearray,
        general_md: Optional[metadata_info.GeneralMd] = None,
        input_md: Optional[metadata_info.InputAudioTensorMd] = None,
        output_md_list: Optional[List[
            metadata_info.ClassificationTensorMd]] = None):
        """Creates a MetadataWriter instance for multihead models.

    Args:
      model_buffer: valid buffer of the model file.
      general_md: general infromation about the model. If not specified, default
        general metadata will be generated.
      input_md: input audio tensor informaton. If not specified, default input
        metadata will be generated.
      output_md_list: information of each output tensor head. If not specified,
        default metadata will be generated for each output tensor. If
        `tensor_name` in each `ClassificationTensorMd` instance is not
        specified, elements in `output_md_list` need to have one-to-one mapping
        with the output tensors [1] in the TFLite model.
      [1]:
        https://github.com/tensorflow/tflite-support/blob/b2a509716a2d71dfff706468680a729cc1604cff/tensorflow_lite_support/metadata/metadata_schema.fbs#L605-L612

    Returns:
      A MetadataWriter object.
    """

        if general_md is None:
            general_md = metadata_info.GeneralMd(
                name=_MODEL_NAME, description=_MODEL_DESCRIPTION)

        if input_md is None:
            input_md = metadata_info.InputAudioTensorMd(
                name=_INPUT_NAME, description=_INPUT_DESCRIPTION)

        associated_files = []
        for md in output_md_list or []:
            associated_files.extend(
                [file.file_path for file in md.associated_files or []])

        return super().create_from_metadata_info(
            model_buffer=model_buffer,
            general_md=general_md,
            input_md=[input_md],
            output_md=output_md_list,
            associated_files=associated_files)
    def test_create_metadata_should_succeed(self):
        general_md = metadata_info.GeneralMd(name="model",
                                             version="v1",
                                             description="A ML model.",
                                             author="TensorFlow",
                                             licenses="Apache")
        general_metadata = general_md.create_metadata()

        # Create the Flatbuffers object and convert it to the json format.
        builder = flatbuffers.Builder(0)
        builder.Finish(general_metadata.Pack(builder),
                       _metadata.MetadataPopulator.METADATA_FILE_IDENTIFIER)
        metadata_json = _metadata.convert_to_json(bytes(builder.Output()))

        expected_json = test_utils.load_file(self._EXPECTED_GENERAL_META_JSON,
                                             "r")
        self.assertEqual(metadata_json, expected_json)
Пример #8
0
  def create_from_metadata_info(
      cls,
      model_buffer: bytearray,
      general_md: Optional[metadata_info.GeneralMd] = None,
      input_md: Optional[metadata_info.InputImageTensorMd] = None,
      output_md: Optional[metadata_info.ClassificationTensorMd] = None):
    """Creates MetadataWriter based on general/input/output information.

    Args:
      model_buffer: valid buffer of the model file.
      general_md: general infromation about the model. If not specified, default
        general metadata will be generated.
      input_md: input image tensor informaton, if not specified, default input
        metadata will be generated.
      output_md: output classification tensor informaton, if not specified,
        default output metadata will be generated.

    Returns:
      A MetadataWriter object.
    """

    if general_md is None:
      general_md = metadata_info.GeneralMd(
          name=_MODEL_NAME, description=_MODEL_DESCRIPTION)

    if input_md is None:
      input_md = metadata_info.InputImageTensorMd(
          name=_INPUT_NAME,
          description=_INPUT_DESCRIPTION,
          color_space_type=_metadata_fb.ColorSpaceType.RGB)

    if output_md is None:
      output_md = metadata_info.ClassificationTensorMd(
          name=_OUTPUT_NAME, description=_OUTPUT_DESCRIPTION)

    if output_md.associated_files is None:
      output_md.associated_files = []

    return super().create_from_metadata_info(
        model_buffer=model_buffer,
        general_md=general_md,
        input_md=[input_md],
        output_md=[output_md],
        associated_files=[
            file.file_path for file in output_md.associated_files
        ])
Пример #9
0
  def create_from_metadata_info(
      cls,
      model_buffer: bytearray,
      general_md: Optional[metadata_info.GeneralMd] = None,
      input_md: Optional[List[Type[metadata_info.TensorMd]]] = None,
      output_md: Optional[List[Type[metadata_info.TensorMd]]] = None,
      associated_files: Optional[List[str]] = None):
    """Creates MetadataWriter based on the metadata information.

    Args:
      model_buffer: valid buffer of the model file.
      general_md: general information about the model.
      input_md: metadata information of the input tensors.
      output_md: metadata information of the output tensors.
      associated_files: path to the associated files to be populated.

    Returns:
      A MetadataWriter Object.

    Raises:
      ValueError: if the tensor names from `input_md` and `output_md` do not
      match the tensor names read from the model.
    """

    if general_md is None:
      general_md = metadata_info.GeneralMd()
    if input_md is None:
      input_md = []
    if output_md is None:
      output_md = []

    # Order the input/output metadata according to tensor orders from the model.
    input_md = _order_tensor_metadata(
        input_md, writer_utils.get_input_tensor_names(model_buffer))
    output_md = _order_tensor_metadata(
        output_md, writer_utils.get_output_tensor_names(model_buffer))

    model_metadata = general_md.create_metadata()
    input_metadata = [m.create_metadata() for m in input_md]
    output_metadata = [m.create_metadata() for m in output_md]
    return cls.create_from_metadata(model_buffer, model_metadata,
                                    input_metadata, output_metadata,
                                    associated_files)
  def test_create_from_metadata_info_succeeds_for_multihead(self):
    calibration_file1 = test_utils.create_calibration_file(
        self.get_temp_dir(), "score_cali_1.txt")
    calibration_file2 = test_utils.create_calibration_file(
        self.get_temp_dir(), "score_cali_2.txt")

    general_md = metadata_info.GeneralMd(name="AudioClassifier")
    input_md = metadata_info.InputAudioTensorMd(
        name="audio_clip", sample_rate=_SAMPLE_RATE, channels=_CHANNELS)
    # The output tensors in the model are: Identity, Identity_1
    # Create metadata in a different order to test if MetadataWriter can correct
    # it.
    output_head_md_1 = metadata_info.ClassificationTensorMd(
        name="head1",
        label_files=[
            metadata_info.LabelFileMd("labels_en_1.txt"),
            metadata_info.LabelFileMd("labels_cn_1.txt")
        ],
        score_calibration_md=metadata_info.ScoreCalibrationMd(
            _metadata_fb.ScoreTransformationType.LOG,
            _DEFAULT_SCORE_CALIBRATION_VALUE, calibration_file1),
        tensor_name="Identity_1")
    output_head_md_2 = metadata_info.ClassificationTensorMd(
        name="head2",
        label_files=[
            metadata_info.LabelFileMd("labels_en_2.txt"),
            metadata_info.LabelFileMd("labels_cn_2.txt")
        ],
        score_calibration_md=metadata_info.ScoreCalibrationMd(
            _metadata_fb.ScoreTransformationType.LOG,
            _DEFAULT_SCORE_CALIBRATION_VALUE, calibration_file2),
        tensor_name="Identity")

    writer = (
        audio_classifier.MetadataWriter.create_from_metadata_info_for_multihead(
            test_utils.load_file(_MULTIHEAD_MODEL), general_md, input_md,
            [output_head_md_1, output_head_md_2]))

    metadata_json = writer.get_metadata_json()
    expected_json = test_utils.load_file(_JSON_MULTIHEAD, "r")
    self.assertEqual(metadata_json, expected_json)
Пример #11
0
    def create_from_metadata_info(
            cls,
            model_buffer: bytearray,
            general_md: Optional[metadata_info.GeneralMd] = None,
            input_md: Optional[metadata_info.InputImageTensorMd] = None,
            output_location_md: Optional[metadata_info.TensorMd] = None,
            output_category_md: Optional[
                metadata_info.CategoryTensorMd] = None,
            output_score_md: Union[
                None, metadata_info.TensorMd,
                metadata_info.ClassificationTensorMd] = None,
            output_number_md: Optional[metadata_info.TensorMd] = None):
        """Creates MetadataWriter based on general/input/outputs information.

    Args:
      model_buffer: valid buffer of the model file.
      general_md: general infromation about the model.
      input_md: input image tensor informaton.
      output_location_md: output location tensor informaton. The location tensor
        is a multidimensional array of [N][4] floating point values between 0
        and 1, the inner arrays representing bounding boxes in the form [top,
        left, bottom, right].
      output_category_md: output category tensor information. The category
        tensor is an array of N integers (output as floating point values) each
        indicating the index of a class label from the labels file.
      output_score_md: output score tensor information. The score tensor is an
        array of N floating point values between 0 and 1 representing
        probability that a class was detected. Use ClassificationTensorMd to
        calibrate score.
      output_number_md: output number of detections tensor information. This
        tensor is an integer value of N.

    Returns:
      A MetadataWriter object.
    """
        if general_md is None:
            general_md = metadata_info.GeneralMd(
                name=_MODEL_NAME, description=_MODEL_DESCRIPTION)

        if input_md is None:
            input_md = metadata_info.InputImageTensorMd(
                name=_INPUT_NAME,
                description=_INPUT_DESCRIPTION,
                color_space_type=_metadata_fb.ColorSpaceType.RGB)

        warn_message_format = (
            "The output name isn't the default string \"%s\". This may cause the "
            "model not work in the TFLite Task Library since the tensor name will "
            "be used to handle the output order in the TFLite Task Library.")
        if output_location_md is None:
            output_location_md = metadata_info.TensorMd(
                name=_OUTPUT_LOCATION_NAME,
                description=_OUTPUT_LOCATION_DESCRIPTION)
        elif output_location_md.name != _OUTPUT_LOCATION_NAME:
            logging.warning(warn_message_format, _OUTPUT_LOCATION_NAME)

        if output_category_md is None:
            output_category_md = metadata_info.CategoryTensorMd(
                name=_OUTPUT_CATRGORY_NAME,
                description=_OUTPUT_CATEGORY_DESCRIPTION)
        elif output_category_md.name != _OUTPUT_CATRGORY_NAME:
            logging.warning(warn_message_format, _OUTPUT_CATRGORY_NAME)

        if output_score_md is None:
            output_score_md = metadata_info.ClassificationTensorMd(
                name=_OUTPUT_SCORE_NAME,
                description=_OUTPUT_SCORE_DESCRIPTION,
            )
        elif output_score_md.name != _OUTPUT_SCORE_NAME:
            logging.warning(warn_message_format, _OUTPUT_SCORE_NAME)

        if output_number_md is None:
            output_number_md = metadata_info.TensorMd(
                name=_OUTPUT_NUMBER_NAME,
                description=_OUTPUT_NUMBER_DESCRIPTION)
        elif output_number_md.name != _OUTPUT_NUMBER_NAME:
            logging.warning(warn_message_format, _OUTPUT_NUMBER_NAME)

        # Create output tensor group info.
        group = _metadata_fb.TensorGroupT()
        group.name = _GROUP_NAME
        group.tensorNames = [
            output_location_md.name, output_category_md.name,
            output_score_md.name
        ]

        # Gets the tensor inidces of tflite outputs and then gets the order of the
        # output metadata by the value of tensor indices. For instance, if the
        # output indices are [601, 599, 598, 600], tensor names and indices aligned
        # are:
        #   - location: 598
        #   - category: 599
        #   - score: 600
        #   - number of detections: 601
        # because of the op's ports of TFLITE_DETECTION_POST_PROCESS
        # (https://github.com/tensorflow/tensorflow/blob/a4fe268ea084e7d323133ed7b986e0ae259a2bc7/tensorflow/lite/kernels/detection_postprocess.cc#L47-L50).
        # Thus, the metadata of tensors are sorted in this way, according to
        # output_tensor_indicies correctly.
        output_tensor_indices = _get_tflite_outputs(model_buffer)
        metadata_list = [
            _create_location_metadata(output_location_md),
            _create_metadata_with_value_range(output_category_md),
            _create_metadata_with_value_range(output_score_md),
            output_number_md.create_metadata()
        ]

        # Align indices with tensors.
        sorted_indices = sorted(output_tensor_indices)
        indices_to_tensors = dict(zip(sorted_indices, metadata_list))

        # Output metadata according to output_tensor_indices.
        output_metadata = [
            indices_to_tensors[i] for i in output_tensor_indices
        ]

        # Create subgraph info.
        subgraph_metadata = _metadata_fb.SubGraphMetadataT()
        subgraph_metadata.inputTensorMetadata = [input_md.create_metadata()]
        subgraph_metadata.outputTensorMetadata = output_metadata
        subgraph_metadata.outputTensorGroups = [group]

        # Create model metadata
        model_metadata = general_md.create_metadata()
        model_metadata.subgraphMetadata = [subgraph_metadata]

        b = flatbuffers.Builder(0)
        b.Finish(model_metadata.Pack(b),
                 _metadata.MetadataPopulator.METADATA_FILE_IDENTIFIER)

        associated_files = []
        _extend_new_files(associated_files,
                          output_category_md.associated_files)
        _extend_new_files(associated_files, output_score_md.associated_files)
        return cls(model_buffer, b.Output(), associated_files=associated_files)
Пример #12
0
  def create_from_metadata_info(
      cls,
      model_buffer: bytearray,
      general_md: Optional[metadata_info.GeneralMd] = None,
      input_md: Optional[metadata_info.InputImageTensorMd] = None,
      output_location_md: Optional[metadata_info.TensorMd] = None,
      output_category_md: Optional[metadata_info.CategoryTensorMd] = None,
      output_score_md: Optional[metadata_info.TensorMd] = None,
      output_number_md: Optional[metadata_info.TensorMd] = None):
    """Creates MetadataWriter based on general/input/outputs information.

    Args:
      model_buffer: valid buffer of the model file.
      general_md: general infromation about the model.
      input_md: input image tensor informaton.
      output_location_md: output location tensor informaton. The location tensor
        is a multidimensional array of [N][4] floating point values between 0
        and 1, the inner arrays representing bounding boxes in the form [top,
        left, bottom, right].
      output_category_md: output category tensor information. The category
        tensor is an array of N integers (output as floating point values) each
        indicating the index of a class label from the labels file.
      output_score_md: output score tensor information. The score tensor is an
        array of N floating point values between 0 and 1 representing
        probability that a class was detected.
      output_number_md: output number of dections tensor information. This
        tensor is an integer value of N.

    Returns:
      A MetadataWriter object.
    """

    if general_md is None:
      general_md = metadata_info.GeneralMd(
          name=_MODEL_NAME, description=_MODEL_DESCRIPTION)

    if input_md is None:
      input_md = metadata_info.InputImageTensorMd(
          name=_INPUT_NAME,
          description=_INPUT_DESCRIPTION,
          color_space_type=_metadata_fb.ColorSpaceType.RGB)

    if output_location_md is None:
      output_location_md = metadata_info.TensorMd(
          name=_OUTPUT_LOCATION_NAME, description=_OUTPUT_LOCATION_DESCRIPTION)

    if output_category_md is None:
      output_category_md = metadata_info.CategoryTensorMd(
          name=_OUTPUT_CATRGORY_NAME, description=_OUTPUT_CATEGORY_DESCRIPTION)

    if output_score_md is None:
      output_score_md = metadata_info.TensorMd(
          name=_OUTPUT_SCORE_NAME, description=_OUTPUT_SCORE_DESCRIPTION)

    if output_number_md is None:
      output_number_md = metadata_info.TensorMd(
          name=_OUTPUT_NUMBER_NAME, description=_OUTPUT_NUMBER_DESCRIPTION)

    if output_category_md.associated_files is None:
      output_category_md.associated_files = []

    return super().create_from_metadata(
        model_buffer,
        model_metadata=general_md.create_metadata(),
        input_metadata=[input_md.create_metadata()],
        output_metadata=[
            _create_location_metadata(output_location_md),
            _create_metadata_with_value_range(output_category_md),
            _create_metadata_with_value_range(output_score_md),
            output_number_md.create_metadata()
        ],
        associated_files=[
            file.file_path for file in output_category_md.associated_files
        ])
Пример #13
0
  def create_from_metadata_info(
      cls,
      model_buffer: bytearray,
      general_md: Optional[metadata_info.GeneralMd] = None,
      input_md: Optional[metadata_info.InputImageTensorMd] = None,
      output_location_md: Optional[metadata_info.TensorMd] = None,
      output_category_md: Optional[metadata_info.CategoryTensorMd] = None,
      output_score_md: Optional[metadata_info.TensorMd] = None,
      output_number_md: Optional[metadata_info.TensorMd] = None):
    """Creates MetadataWriter based on general/input/outputs information.

    Args:
      model_buffer: valid buffer of the model file.
      general_md: general infromation about the model.
      input_md: input image tensor informaton.
      output_location_md: output location tensor informaton. The location tensor
        is a multidimensional array of [N][4] floating point values between 0
        and 1, the inner arrays representing bounding boxes in the form [top,
        left, bottom, right].
      output_category_md: output category tensor information. The category
        tensor is an array of N integers (output as floating point values) each
        indicating the index of a class label from the labels file.
      output_score_md: output score tensor information. The score tensor is an
        array of N floating point values between 0 and 1 representing
        probability that a class was detected.
      output_number_md: output number of dections tensor information. This
        tensor is an integer value of N.

    Returns:
      A MetadataWriter object.
    """

    if general_md is None:
      general_md = metadata_info.GeneralMd(
          name=_MODEL_NAME, description=_MODEL_DESCRIPTION)

    if input_md is None:
      input_md = metadata_info.InputImageTensorMd(
          name=_INPUT_NAME,
          description=_INPUT_DESCRIPTION,
          color_space_type=_metadata_fb.ColorSpaceType.RGB)

    if output_location_md is None:
      output_location_md = metadata_info.TensorMd(
          name=_OUTPUT_LOCATION_NAME, description=_OUTPUT_LOCATION_DESCRIPTION)

    if output_category_md is None:
      output_category_md = metadata_info.CategoryTensorMd(
          name=_OUTPUT_CATRGORY_NAME, description=_OUTPUT_CATEGORY_DESCRIPTION)

    if output_score_md is None:
      output_score_md = metadata_info.TensorMd(
          name=_OUTPUT_SCORE_NAME, description=_OUTPUT_SCORE_DESCRIPTION)

    if output_number_md is None:
      output_number_md = metadata_info.TensorMd(
          name=_OUTPUT_NUMBER_NAME, description=_OUTPUT_NUMBER_DESCRIPTION)

    if output_category_md.associated_files is None:
      output_category_md.associated_files = []

    # Create output tensor group info.
    group = _metadata_fb.TensorGroupT()
    group.name = _GROUP_NAME
    group.tensorNames = [
        output_location_md.name, output_category_md.name, output_score_md.name
    ]

    # Create subgraph info.
    subgraph_metadata = _metadata_fb.SubGraphMetadataT()
    subgraph_metadata.inputTensorMetadata = [input_md.create_metadata()]
    subgraph_metadata.outputTensorMetadata = [
        _create_location_metadata(output_location_md),
        _create_metadata_with_value_range(output_category_md),
        _create_metadata_with_value_range(output_score_md),
        output_number_md.create_metadata()
    ]
    subgraph_metadata.outputTensorGroups = [group]

    # Create model metadata
    model_metadata = general_md.create_metadata()
    model_metadata.subgraphMetadata = [subgraph_metadata]

    b = flatbuffers.Builder(0)
    b.Finish(
        model_metadata.Pack(b),
        _metadata.MetadataPopulator.METADATA_FILE_IDENTIFIER)

    return cls(
        model_buffer,
        b.Output(),
        associated_files=[
            file.file_path for file in output_category_md.associated_files
        ])