processing_donut.py 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. # coding=utf-8
  2. # Copyright 2022 The HuggingFace Inc. team.
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. """
  16. Processor class for Donut.
  17. """
  18. import re
  19. import warnings
  20. from contextlib import contextmanager
  21. from typing import Optional, Union
  22. from ...image_utils import ImageInput
  23. from ...processing_utils import ProcessingKwargs, ProcessorMixin, Unpack
  24. from ...tokenization_utils_base import PreTokenizedInput, TextInput
  25. from ...utils import logging
  26. class DonutProcessorKwargs(ProcessingKwargs, total=False):
  27. _defaults = {}
  28. logger = logging.get_logger(__name__)
  29. class DonutProcessor(ProcessorMixin):
  30. r"""
  31. Constructs a Donut processor which wraps a Donut image processor and an XLMRoBERTa tokenizer into a single
  32. processor.
  33. [`DonutProcessor`] offers all the functionalities of [`DonutImageProcessor`] and
  34. [`XLMRobertaTokenizer`/`XLMRobertaTokenizerFast`]. See the [`~DonutProcessor.__call__`] and
  35. [`~DonutProcessor.decode`] for more information.
  36. Args:
  37. image_processor ([`DonutImageProcessor`], *optional*):
  38. An instance of [`DonutImageProcessor`]. The image processor is a required input.
  39. tokenizer ([`XLMRobertaTokenizer`/`XLMRobertaTokenizerFast`], *optional*):
  40. An instance of [`XLMRobertaTokenizer`/`XLMRobertaTokenizerFast`]. The tokenizer is a required input.
  41. """
  42. attributes = ["image_processor", "tokenizer"]
  43. image_processor_class = "AutoImageProcessor"
  44. tokenizer_class = "AutoTokenizer"
  45. def __init__(self, image_processor=None, tokenizer=None, **kwargs):
  46. feature_extractor = None
  47. if "feature_extractor" in kwargs:
  48. warnings.warn(
  49. "The `feature_extractor` argument is deprecated and will be removed in v5, use `image_processor`"
  50. " instead.",
  51. FutureWarning,
  52. )
  53. feature_extractor = kwargs.pop("feature_extractor")
  54. image_processor = image_processor if image_processor is not None else feature_extractor
  55. super().__init__(image_processor, tokenizer)
  56. self.current_processor = self.image_processor
  57. self._in_target_context_manager = False
  58. def __call__(
  59. self,
  60. images: Optional[ImageInput] = None,
  61. text: Optional[Union[str, list[str], TextInput, PreTokenizedInput]] = None,
  62. audio=None,
  63. videos=None,
  64. **kwargs: Unpack[DonutProcessorKwargs],
  65. ):
  66. """
  67. When used in normal mode, this method forwards all its arguments to AutoImageProcessor's
  68. [`~AutoImageProcessor.__call__`] and returns its output. If used in the context
  69. [`~DonutProcessor.as_target_processor`] this method forwards all its arguments to DonutTokenizer's
  70. [`~DonutTokenizer.__call__`]. Please refer to the docstring of the above two methods for more information.
  71. """
  72. if self._in_target_context_manager:
  73. return self.current_processor(images, text, **kwargs)
  74. if images is None and text is None:
  75. raise ValueError("You need to specify either an `images` or `text` input to process.")
  76. output_kwargs = self._merge_kwargs(
  77. DonutProcessorKwargs,
  78. tokenizer_init_kwargs=self.tokenizer.init_kwargs,
  79. **kwargs,
  80. )
  81. if images is not None:
  82. inputs = self.image_processor(images, **output_kwargs["images_kwargs"])
  83. if text is not None:
  84. if images is not None:
  85. output_kwargs["text_kwargs"].setdefault("add_special_tokens", False)
  86. encodings = self.tokenizer(text, **output_kwargs["text_kwargs"])
  87. if text is None:
  88. return inputs
  89. elif images is None:
  90. return encodings
  91. else:
  92. inputs["labels"] = encodings["input_ids"] # for BC
  93. inputs["input_ids"] = encodings["input_ids"]
  94. return inputs
  95. @property
  96. def model_input_names(self):
  97. image_processor_input_names = self.image_processor.model_input_names
  98. return list(image_processor_input_names + ["input_ids", "labels"])
  99. @contextmanager
  100. def as_target_processor(self):
  101. """
  102. Temporarily sets the tokenizer for processing the input. Useful for encoding the labels when fine-tuning TrOCR.
  103. """
  104. warnings.warn(
  105. "`as_target_processor` is deprecated and will be removed in v5 of Transformers. You can process your "
  106. "labels by using the argument `text` of the regular `__call__` method (either in the same call as "
  107. "your images inputs, or in a separate call."
  108. )
  109. self._in_target_context_manager = True
  110. self.current_processor = self.tokenizer
  111. yield
  112. self.current_processor = self.image_processor
  113. self._in_target_context_manager = False
  114. def token2json(self, tokens, is_inner_value=False, added_vocab=None):
  115. """
  116. Convert a (generated) token sequence into an ordered JSON format.
  117. """
  118. if added_vocab is None:
  119. added_vocab = self.tokenizer.get_added_vocab()
  120. output = {}
  121. while tokens:
  122. # We want r"<s_(.*?)>" but without ReDOS risk, so do it manually in two parts
  123. potential_start = re.search(r"<s_", tokens, re.IGNORECASE)
  124. if potential_start is None:
  125. break
  126. start_token = tokens[potential_start.start() :]
  127. if ">" not in start_token:
  128. break
  129. start_token = start_token[: start_token.index(">") + 1]
  130. key = start_token[len("<s_") : -len(">")]
  131. key_escaped = re.escape(key)
  132. end_token = re.search(rf"</s_{key_escaped}>", tokens, re.IGNORECASE)
  133. if end_token is None:
  134. tokens = tokens.replace(start_token, "")
  135. else:
  136. end_token = end_token.group()
  137. start_token_escaped = re.escape(start_token)
  138. end_token_escaped = re.escape(end_token)
  139. content = re.search(
  140. f"{start_token_escaped}(.*?){end_token_escaped}", tokens, re.IGNORECASE | re.DOTALL
  141. )
  142. if content is not None:
  143. content = content.group(1).strip()
  144. if r"<s_" in content and r"</s_" in content: # non-leaf node
  145. value = self.token2json(content, is_inner_value=True, added_vocab=added_vocab)
  146. if value:
  147. if len(value) == 1:
  148. value = value[0]
  149. output[key] = value
  150. else: # leaf nodes
  151. output[key] = []
  152. for leaf in content.split(r"<sep/>"):
  153. leaf = leaf.strip()
  154. if leaf in added_vocab and leaf[0] == "<" and leaf[-2:] == "/>":
  155. leaf = leaf[1:-2] # for categorical special tokens
  156. output[key].append(leaf)
  157. if len(output[key]) == 1:
  158. output[key] = output[key][0]
  159. tokens = tokens[tokens.find(end_token) + len(end_token) :].strip()
  160. if tokens[:6] == r"<sep/>": # non-leaf nodes
  161. return [output] + self.token2json(tokens[6:], is_inner_value=True, added_vocab=added_vocab)
  162. if output:
  163. return [output] if is_inner_value else output
  164. else:
  165. return [] if is_inner_value else {"text_sequence": tokens}
  166. @property
  167. def feature_extractor_class(self):
  168. warnings.warn(
  169. "`feature_extractor_class` is deprecated and will be removed in v5. Use `image_processor_class` instead.",
  170. FutureWarning,
  171. )
  172. return self.image_processor_class
  173. @property
  174. def feature_extractor(self):
  175. warnings.warn(
  176. "`feature_extractor` is deprecated and will be removed in v5. Use `image_processor` instead.",
  177. FutureWarning,
  178. )
  179. return self.image_processor
  180. __all__ = ["DonutProcessor"]