From 7318ad83025f1ec9259afb87688507e8f42b4b9e Mon Sep 17 00:00:00 2001 From: Danilo Burbano Date: Tue, 30 Sep 2025 10:36:38 -0500 Subject: [PATCH 1/7] [SPARKNLP-1291] Adding support fort input string column on readers --- .../partition/partition_properties.py | 37 ++++-- python/test/reader/reader2doc_test.py | 20 ++++ python/test/reader/reader2table_test.py | 34 +++++- .../partition/HasReaderProperties.scala | 10 +- .../reader/HasReaderContent.scala | 39 ++++++- .../com/johnsnowlabs/reader/Reader2Doc.scala | 19 +-- .../johnsnowlabs/reader/Reader2Table.scala | 12 +- .../johnsnowlabs/reader/util/HTMLParser.scala | 18 +-- .../johnsnowlabs/reader/Reader2DocTest.scala | 108 ++++++++++++++++++ .../reader/Reader2TableTest.scala | 75 ++++++++++++ 10 files changed, 335 insertions(+), 37 deletions(-) diff --git a/python/sparknlp/partition/partition_properties.py b/python/sparknlp/partition/partition_properties.py index 7089e4fc4c4bf8..a7ffbaeafa81b9 100644 --- a/python/sparknlp/partition/partition_properties.py +++ b/python/sparknlp/partition/partition_properties.py @@ -18,6 +18,23 @@ class HasReaderProperties(Params): + inputCol = Param( + Params._dummy(), + "inputCol", + "input column name", + typeConverter=TypeConverters.toString + ) + + def setInputCol(self, value): + """Sets input column name. + + Parameters + ---------- + value : str + Name of the Input Column + """ + return self._set(inputCol=value) + outputCol = Param( Params._dummy(), "outputCol", @@ -25,6 +42,16 @@ class HasReaderProperties(Params): typeConverter=TypeConverters.toString ) + def setOutputCol(self, value): + """Sets output column name. + + Parameters + ---------- + value : str + Name of the Output Column + """ + return self._set(outputCol=value) + contentPath = Param( Params._dummy(), "contentPath", @@ -683,13 +710,3 @@ def setReadAsImage(self, value: bool): True to read as images, False otherwise. """ return self._set(readAsImage=value) - - def setOutputCol(self, value): - """Sets output column name. - - Parameters - ---------- - value : str - Name of the Output Column - """ - return self._set(outputCol=value) \ No newline at end of file diff --git a/python/test/reader/reader2doc_test.py b/python/test/reader/reader2doc_test.py index f6960d35004a6b..aace3ce9b35d36 100644 --- a/python/test/reader/reader2doc_test.py +++ b/python/test/reader/reader2doc_test.py @@ -111,4 +111,24 @@ def runTest(self): result_df = model.transform(self.empty_df) + self.assertTrue(result_df.select("document").count() > 0) + +@pytest.mark.fast +class Reader2DocTestInputColumn(unittest.TestCase): + + def setUp(self): + spark = SparkContextForTest.spark + content = "Test<title><body><p>Unclosed tag" + self.html_df = spark.createDataFrame([(1, content)], ["id", "html"]) + + def runTest(self): + reader2doc = Reader2Doc() \ + .setInputCol("html") \ + .setOutputCol("document") + + pipeline = Pipeline(stages=[reader2doc]) + model = pipeline.fit(self.html_df) + + result_df = model.transform(self.html_df) + self.assertTrue(result_df.select("document").count() > 0) \ No newline at end of file diff --git a/python/test/reader/reader2table_test.py b/python/test/reader/reader2table_test.py index 2a86e1e891ba9a..6aabb488564e3c 100644 --- a/python/test/reader/reader2table_test.py +++ b/python/test/reader/reader2table_test.py @@ -40,7 +40,6 @@ def runTest(self): model = pipeline.fit(self.empty_df) result_df = model.transform(self.empty_df) - result_df.show(truncate=False) self.assertTrue(result_df.select("document").count() > 0) @@ -60,4 +59,35 @@ def runTest(self): result_df = model.transform(self.empty_df) - self.assertTrue(result_df.select("document").count() > 1) \ No newline at end of file + self.assertTrue(result_df.select("document").count() > 1) + +@pytest.mark.fast +class Reader2TableInputColTest(unittest.TestCase): + + def setUp(self): + content = """ + <html> + <body> + <table> + <tr> + <td>Hello World</td> + </tr> + </table> + </body> + </html> + """ + spark = SparkContextForTest.spark + self.html_df = spark.createDataFrame([(1, content)], ["id", "html"]) + + def runTest(self): + reader2table = Reader2Table() \ + .setInputCol("html") \ + .setContentType("text/html") \ + .setOutputCol("document") + + pipeline = Pipeline(stages=[reader2table]) + model = pipeline.fit(self.html_df) + + result_df = model.transform(self.html_df) + + self.assertTrue(result_df.select("document").count() > 0) diff --git a/src/main/scala/com/johnsnowlabs/partition/HasReaderProperties.scala b/src/main/scala/com/johnsnowlabs/partition/HasReaderProperties.scala index df612ca1e208d5..f8bae5570187af 100644 --- a/src/main/scala/com/johnsnowlabs/partition/HasReaderProperties.scala +++ b/src/main/scala/com/johnsnowlabs/partition/HasReaderProperties.scala @@ -19,6 +19,13 @@ import org.apache.spark.ml.param.{BooleanParam, Param} trait HasReaderProperties extends HasHTMLReaderProperties { + protected final val inputCol: Param[String] = + new Param(this, "inputCol", "input column to process") + + final def setInputCol(value: String): this.type = set(inputCol, value) + + final def getInputCol: String = $(inputCol) + val contentPath = new Param[String](this, "contentPath", "Path to the content source") def setContentPath(value: String): this.type = set(contentPath, value) @@ -75,6 +82,7 @@ trait HasReaderProperties extends HasHTMLReaderProperties { titleFontSize -> 9, inferTableStructure -> false, includePageBreaks -> false, - ignoreExceptions -> true) + ignoreExceptions -> true, + inputCol -> "") } diff --git a/src/main/scala/com/johnsnowlabs/reader/HasReaderContent.scala b/src/main/scala/com/johnsnowlabs/reader/HasReaderContent.scala index e047a16d4c792d..203c1ea9881b89 100644 --- a/src/main/scala/com/johnsnowlabs/reader/HasReaderContent.scala +++ b/src/main/scala/com/johnsnowlabs/reader/HasReaderContent.scala @@ -104,12 +104,11 @@ trait HasReaderContent extends HasReaderProperties { } } - def partitionContent( + private def partitionContentFromPath( partition: Partition, contentPath: String, isText: Boolean, dataset: Dataset[_]): DataFrame = { - val ext = contentPath.split("\\.").lastOption.getOrElse("").toLowerCase if (! $(ignoreExceptions) && !supportedTypes.contains(ext)) { return buildErrorDataFrame(dataset, contentPath, ext) @@ -148,6 +147,38 @@ trait HasReaderContent extends HasReaderProperties { } else partitionDf } + def partitionContent( + partition: Partition, + contentPath: String, + isText: Boolean, + dataset: Dataset[_]): DataFrame = { + + val partitionDf = + if (getInputCol != null && getInputCol.nonEmpty) { + partitionContentFromDataFrame(partition, dataset, getInputCol) + } else { + partitionContentFromPath(partition, contentPath, isText, dataset) + } + + if ($(ignoreExceptions)) { + partitionDf.filter(col("exception").isNull) + } else partitionDf + } + + /** Partition content when it is already present in a dataset column. */ + private def partitionContentFromDataFrame( + partition: Partition, + dataset: Dataset[_], + inputCol: String): DataFrame = { + val partitionUDF = + udf((text: String) => partition.partitionStringContent(text, $(this.headers).asJava)) + + dataset + .withColumn(partition.getOutputColumn, partitionUDF(col(inputCol))) + .withColumn("fileName", lit(null: String)) + .withColumn("exception", lit(null: String)) + } + val getFileName: UserDefinedFunction = udf { path: String => if (path != null) path.split("/").last else "" } @@ -166,4 +197,8 @@ trait HasReaderContent extends HasReaderProperties { dataset.sparkSession.createDataFrame(emptyRDD, schema) } + def getContentType: String = { + if ($(contentType).trim.isEmpty && getInputCol.nonEmpty) "text/plain" else $(contentType) + } + } diff --git a/src/main/scala/com/johnsnowlabs/reader/Reader2Doc.scala b/src/main/scala/com/johnsnowlabs/reader/Reader2Doc.scala index a29411903b3e8e..eb147fd5c7e44e 100644 --- a/src/main/scala/com/johnsnowlabs/reader/Reader2Doc.scala +++ b/src/main/scala/com/johnsnowlabs/reader/Reader2Doc.scala @@ -127,13 +127,13 @@ class Reader2Doc(override val uid: String) override def transform(dataset: Dataset[_]): DataFrame = { validateRequiredParameters() - val structuredDf = if ($(contentType).trim.isEmpty) { + val structuredDf = if ($(contentType).trim.isEmpty && getInputCol.trim.isEmpty) { val partitionParams = Map( "inferTableStructure" -> $(inferTableStructure).toString, "outputFormat" -> $(outputFormat)) partitionMixedContent(dataset, $(contentPath), partitionParams) } else { - partitionContent(partitionBuilder, $(contentPath), isStringContent($(contentType)), dataset) + partitionContent(partitionBuilder, $(contentPath), isStringContent(getContentType), dataset) } if (!structuredDf.isEmpty) { val annotatedDf = structuredDf @@ -149,7 +149,7 @@ class Reader2Doc(override val uid: String) protected def partitionBuilder: Partition = { val params = Map( - "contentType" -> $(contentType), + "contentType" -> getContentType, "storeContent" -> $(storeContent).toString, "titleFontSize" -> $(titleFontSize).toString, "inferTableStructure" -> $(inferTableStructure).toString, @@ -186,15 +186,16 @@ class Reader2Doc(override val uid: String) } protected def validateRequiredParameters(): Unit = { - require( - $(contentPath) != null && $(contentPath).trim.nonEmpty, - "contentPath must be set and not empty") + val hasContentPath = $(contentPath) != null && $(contentPath).trim.nonEmpty + if (hasContentPath) { + require( + ResourceHelper.validFile($(contentPath)), + "contentPath must point to a valid file or directory") + } + require( $(outputFormat) == "plain-text", "Only 'plain-text' outputFormat is supported for this operation.") - require( - ResourceHelper.validFile($(contentPath)), - "contentPath must point to a valid file or directory") } protected def partitionToAnnotation: UserDefinedFunction = udf { diff --git a/src/main/scala/com/johnsnowlabs/reader/Reader2Table.scala b/src/main/scala/com/johnsnowlabs/reader/Reader2Table.scala index 849a82a154eee1..59fd92dc6568e2 100644 --- a/src/main/scala/com/johnsnowlabs/reader/Reader2Table.scala +++ b/src/main/scala/com/johnsnowlabs/reader/Reader2Table.scala @@ -16,6 +16,7 @@ package com.johnsnowlabs.reader import com.johnsnowlabs.nlp.Annotation +import com.johnsnowlabs.nlp.util.io.ResourceHelper import org.apache.spark.ml.util.{DefaultParamsReadable, Identifiable} import org.apache.spark.sql.expressions.UserDefinedFunction import org.apache.spark.sql.functions.udf @@ -84,7 +85,7 @@ class Reader2Table(override val uid: String) extends Reader2Doc { } private def getAcceptedTypes(fileName: String): Set[String] = { - if (fileName.isEmpty) { + if (fileName == null || fileName.isEmpty) { val officeDocTypes = Set( "application/msword", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", @@ -171,9 +172,12 @@ class Reader2Table(override val uid: String) extends Reader2Doc { } override def validateRequiredParameters(): Unit = { - require( - $(contentPath) != null && $(contentPath).trim.nonEmpty, - "contentPath must be set and not empty") + val hasContentPath = $(contentPath) != null && $(contentPath).trim.nonEmpty + if (hasContentPath) { + require( + ResourceHelper.validFile($(contentPath)), + "contentPath must point to a valid file or directory") + } require( Set("html-table", "json-table").contains($(outputFormat)), "outputFormat must be either 'html-table' or 'json-table'.") diff --git a/src/main/scala/com/johnsnowlabs/reader/util/HTMLParser.scala b/src/main/scala/com/johnsnowlabs/reader/util/HTMLParser.scala index 08bdb4df1c167f..b56238b39d11d8 100644 --- a/src/main/scala/com/johnsnowlabs/reader/util/HTMLParser.scala +++ b/src/main/scala/com/johnsnowlabs/reader/util/HTMLParser.scala @@ -87,23 +87,23 @@ object HTMLParser { def tableElementToJson(tableElem: Element): String = { implicit val formats = Serialization.formats(NoTypeHints) - val caption = Option(tableElem.selectFirst("caption")).map(_.text.trim).getOrElse("") + val caption = Option(tableElem.selectFirst("caption")) + .map(_.text.trim) + .getOrElse("") - // Headers: first row with th or td as header - val headerRowOpt = tableElem - .select("tr") - .asScala - .find(tr => tr.select("th,td").asScala.nonEmpty && tr.select("th").asScala.nonEmpty) + val allRows = tableElem.select("tr").asScala.toList + + val headerRowOpt = allRows.find(tr => tr.select("th").asScala.nonEmpty) val headers: List[String] = headerRowOpt .map(_.select("th,td").asScala.map(_.text.trim).toList) .getOrElse(List.empty) - val allRows = tableElem.select("tr").asScala.toList - val headerIndex = headerRowOpt.map(allRows.indexOf).getOrElse(0) + val headerIndexOpt = headerRowOpt.map(allRows.indexOf) + val dataRows = allRows.zipWithIndex - .filter { case (_, idx) => idx != headerIndex } // skip header row + .filter { case (_, idx) => !headerIndexOpt.contains(idx) } .map(_._1) .map(row => row.select("td").asScala.map(_.text.trim).toList) .filter(_.nonEmpty) diff --git a/src/test/scala/com/johnsnowlabs/reader/Reader2DocTest.scala b/src/test/scala/com/johnsnowlabs/reader/Reader2DocTest.scala index 866177c52aaba1..388a766df3864c 100644 --- a/src/test/scala/com/johnsnowlabs/reader/Reader2DocTest.scala +++ b/src/test/scala/com/johnsnowlabs/reader/Reader2DocTest.scala @@ -349,4 +349,112 @@ class Reader2DocTest extends AnyFlatSpec with SparkSessionTest { assert(resultDf.filter(col("exception").isNotNull).count() >= 1) } + it should "proces from a spark dataframe" taggedAs FastTest in { + + val content = + """ + |The big brown fox + |was walking down the lane. + | + |At the end of the lane, + |the fox met a bear. + |""".stripMargin + + val txtDf = spark.createDataFrame(Seq((1, content))).toDF("id", "txt") + + txtDf.show(truncate = false) + + val reader2Doc = new Reader2Doc() + .setInputCol("txt") + .setOutputCol("document") + + val pipeline = new Pipeline().setStages(Array(reader2Doc)) + val resultDf = pipeline.fit(txtDf).transform(txtDf) + + resultDf.show(truncate = false) + } + + it should "proces HTML style from a spark dataframe" taggedAs FastTest in { + + val content = + """ + |<!DOCTYPE html> + |<html lang="en"> + |<head> + | <meta charset="UTF-8"> + | <title>Title Font Size Demo + | + | + |

This SHOULD be a title

+ |

This is a normal paragraph.

+ |

This MIGHT be a title

+ |

Another regular paragraph.

+ | + | + |""".stripMargin + + val txtDf = spark.createDataFrame(Seq((1, content))).toDF("id", "html") + + txtDf.show(truncate = false) + + val reader2Doc = new Reader2Doc() + .setInputCol("html") + .setOutputCol("raw-html") + .setContentType("text/plain") + + val pipeline = new Pipeline().setStages(Array(reader2Doc)) + val resultDf = pipeline.fit(txtDf).transform(txtDf) + + resultDf.show(truncate = false) + } + + it should "be fault-tolerant for HTML content" taggedAs FastTest in { + + val content = + "Test<title><body><p>Unclosed tag" + + val htmlDf = spark.createDataFrame(Seq((1, content))).toDF("id", "html") + + htmlDf.show(truncate = false) + + val reader2Doc = new Reader2Doc() + .setInputCol("html") + .setOutputCol("document") + .setContentType("text/html") + + val pipeline = new Pipeline().setStages(Array(reader2Doc)) + val resultDf = pipeline.fit(htmlDf).transform(htmlDf) + + resultDf.show(truncate = false) + } + + it should "read XML as HTML" taggedAs FastTest in { + + val reader2Doc = new Reader2Doc() + .setContentType("text/html") + .setContentPath(s"$xmlDirectory/malformed.xml") + .setOutputCol("document") + + val pipeline = new Pipeline().setStages(Array(reader2Doc)) + + val pipelineModel = pipeline.fit(emptyDataSet) + val resultDf = pipelineModel.transform(emptyDataSet) + + resultDf.show(truncate = false) + } + + it should "read malformed XML as HTML" taggedAs FastTest in { + val reader2Doc = new Reader2Doc() + .setContentType("application/xml") + .setContentPath(s"$xmlDirectory/malformed.xml") + .setOutputCol("document") + + val pipeline = new Pipeline().setStages(Array(reader2Doc)) + + val pipelineModel = pipeline.fit(emptyDataSet) + val resultDf = pipelineModel.transform(emptyDataSet) + + resultDf.show(truncate = false) + } + } diff --git a/src/test/scala/com/johnsnowlabs/reader/Reader2TableTest.scala b/src/test/scala/com/johnsnowlabs/reader/Reader2TableTest.scala index 7e7423fea300d6..647ec7185e4b77 100644 --- a/src/test/scala/com/johnsnowlabs/reader/Reader2TableTest.scala +++ b/src/test/scala/com/johnsnowlabs/reader/Reader2TableTest.scala @@ -40,6 +40,25 @@ class Reader2TableTest extends AnyFlatSpec with SparkSessionTest { assert(resultJson.has("caption"), "JSON missing 'caption'") assert(resultJson.has("header"), "JSON missing 'header'") assert(resultJson.has("rows"), "JSON missing 'rows'") + + val caption = resultJson.get("caption").asText() + assert(caption.nonEmpty, "Caption should not be empty") + + val header = resultJson.get("header") + assert(header.isArray, "Header should be an array") + assert(header.size() > 0, "Header array should not be empty") + + val rows = resultJson.get("rows") + assert(rows.isArray, "Rows should be an array") + assert(rows.size() > 0, "Rows array should not be empty") + + rows.forEach { row => + assert(row.isArray, "Each row should be an array") + assert(row.size() > 0, "Row should contain values") + row.forEach { cell => + assert(cell.asText().nonEmpty, "Table cell should not be empty") + } + } } } @@ -389,4 +408,60 @@ class Reader2TableTest extends AnyFlatSpec with SparkSessionTest { assert(resultDf.filter(col("exception").isNotNull).count() >= 1) } + + it should "read from spark dataframe" taggedAs FastTest in { + val content: String = + """<html> + | <body> + | <table> + | <tr> + | <td>Hello World</td> + | </tr> + | </table> + | </body> + |</html>""".stripMargin + + val htmlDf = spark.createDataFrame(Seq((1, content))).toDF("id", "html") + + htmlDf.show(truncate = false) + + val reader2Table = new Reader2Table() + .setInputCol("html") + .setContentType("text/html") + .setOutputCol("document") + + val pipeline = new Pipeline().setStages(Array(reader2Table)) + val resultDf = pipeline.fit(htmlDf).transform(htmlDf) + + val annotationsResult = AssertAnnotations.getActualResult(resultDf, "document") + val objectMapper = new ObjectMapper() + annotationsResult.foreach { annotation => + val jsonStringOutput = annotation.head.result + val resultJson = objectMapper.readTree(jsonStringOutput) + + assert(resultJson.has("caption"), "JSON missing 'caption'") + assert(resultJson.has("header"), "JSON missing 'header'") + assert(resultJson.has("rows"), "JSON missing 'rows'") + + val caption = resultJson.get("caption").asText() + assert(caption.isEmpty, "Caption should be empty") + + val header = resultJson.get("header") + assert(header.isArray, "Header should be an array") + assert(header.size() == 0, "Header array should be empty") + + val rows = resultJson.get("rows") + assert(rows.isArray, "Rows should be an array") + assert(rows.size() > 0, "Rows array should not be empty") + + rows.forEach { row => + assert(row.isArray, "Each row should be an array") + assert(row.size() > 0, "Row should contain values") + row.forEach { cell => + assert(cell.asText().nonEmpty, "Table cell should not be empty") + } + } + } + } + } From fec5e08ec16437c68265dc86cf3b90f55847eb61 Mon Sep 17 00:00:00 2001 From: Danilo Burbano <danilo@johnsnowlabs.com> Date: Tue, 30 Sep 2025 11:33:31 -0500 Subject: [PATCH 2/7] [SPARKNLP-1291] Removing unnecessary tests --- .../johnsnowlabs/reader/Reader2DocTest.scala | 34 ++----------------- 1 file changed, 3 insertions(+), 31 deletions(-) diff --git a/src/test/scala/com/johnsnowlabs/reader/Reader2DocTest.scala b/src/test/scala/com/johnsnowlabs/reader/Reader2DocTest.scala index 388a766df3864c..e33c9c5717127e 100644 --- a/src/test/scala/com/johnsnowlabs/reader/Reader2DocTest.scala +++ b/src/test/scala/com/johnsnowlabs/reader/Reader2DocTest.scala @@ -18,6 +18,7 @@ package com.johnsnowlabs.reader import com.johnsnowlabs.nlp.annotators.SparkSessionTest import com.johnsnowlabs.nlp.{Annotation, AssertAnnotations} import com.johnsnowlabs.tags.{FastTest, SlowTest} +import org.apache.hadoop.mapreduce.lib.input.InvalidInputException import org.apache.spark.sql.functions.col import org.apache.spark.ml.Pipeline import org.scalatest.flatspec.AnyFlatSpec @@ -212,10 +213,10 @@ class Reader2DocTest extends AnyFlatSpec with SparkSessionTest { val pipeline = new Pipeline().setStages(Array(reader2Doc)) val pipelineModel = pipeline.fit(emptyDataSet) - val ex = intercept[IllegalArgumentException] { + val ex = intercept[InvalidInputException ] { pipelineModel.transform(emptyDataSet) } - ex.getMessage.contains("contentPath must be set") + ex.getMessage.contains("contentPath must point to a valid file or directory") } it should "return all sentences joined into a single document" in { @@ -428,33 +429,4 @@ class Reader2DocTest extends AnyFlatSpec with SparkSessionTest { resultDf.show(truncate = false) } - it should "read XML as HTML" taggedAs FastTest in { - - val reader2Doc = new Reader2Doc() - .setContentType("text/html") - .setContentPath(s"$xmlDirectory/malformed.xml") - .setOutputCol("document") - - val pipeline = new Pipeline().setStages(Array(reader2Doc)) - - val pipelineModel = pipeline.fit(emptyDataSet) - val resultDf = pipelineModel.transform(emptyDataSet) - - resultDf.show(truncate = false) - } - - it should "read malformed XML as HTML" taggedAs FastTest in { - val reader2Doc = new Reader2Doc() - .setContentType("application/xml") - .setContentPath(s"$xmlDirectory/malformed.xml") - .setOutputCol("document") - - val pipeline = new Pipeline().setStages(Array(reader2Doc)) - - val pipelineModel = pipeline.fit(emptyDataSet) - val resultDf = pipelineModel.transform(emptyDataSet) - - resultDf.show(truncate = false) - } - } From 308cf299362083f968b527ce53f2dd8274c69e45 Mon Sep 17 00:00:00 2001 From: Danilo Burbano <37355249+danilojsl@users.noreply.github.com> Date: Wed, 8 Oct 2025 08:12:40 -0500 Subject: [PATCH 3/7] [SPARKNLP-1292] Adding fault-tolerance support when reading malformed XML files (#14666) Co-authored-by: Devin Ha <devin@trungducha.de> --- build.sbt | 4 +- project/Dependencies.scala | 3 ++ .../com/johnsnowlabs/reader/XMLReader.scala | 19 ++++++++- .../johnsnowlabs/reader/Reader2DocTest.scala | 20 +++++++++- .../johnsnowlabs/reader/XMLReaderTest.scala | 39 +++++++++++++++++++ 5 files changed, 81 insertions(+), 4 deletions(-) diff --git a/build.sbt b/build.sbt index c1762b95c70e8d..8ddde39b15a3fd 100644 --- a/build.sbt +++ b/build.sbt @@ -73,7 +73,9 @@ lazy val utilDependencies = Seq( scratchpad exclude ("org.apache.logging.log4j", "log4j-api"), pdfBox, - flexmark) + flexmark, + tagSoup +) lazy val typedDependencyParserDependencies = Seq(junit) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 524225b7e1074b..da0039c6d52f03 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -153,5 +153,8 @@ object Dependencies { val flexmarkVersion = "0.61.34" val flexmark = "com.vladsch.flexmark" % "flexmark-all" % flexmarkVersion + + val tagSoupVersion = "1.2.1" + val tagSoup = "org.ccil.cowan.tagsoup" % "tagsoup" % tagSoupVersion /** ------- Dependencies end ------- */ } diff --git a/src/main/scala/com/johnsnowlabs/reader/XMLReader.scala b/src/main/scala/com/johnsnowlabs/reader/XMLReader.scala index 3665cdec2b6651..86a474cd6f9a48 100644 --- a/src/main/scala/com/johnsnowlabs/reader/XMLReader.scala +++ b/src/main/scala/com/johnsnowlabs/reader/XMLReader.scala @@ -20,9 +20,13 @@ import com.johnsnowlabs.nlp.util.io.ResourceHelper.validFile import com.johnsnowlabs.partition.util.PartitionHelper.datasetWithTextFile import org.apache.spark.sql.DataFrame import org.apache.spark.sql.functions.{col, udf} +import org.ccil.cowan.tagsoup.jaxp.SAXFactoryImpl +import org.xml.sax.InputSource +import java.io.StringReader import scala.collection.mutable import scala.collection.mutable.ListBuffer +import scala.xml.parsing.NoBindingFactoryAdapter import scala.xml.{Elem, Node, XML} /** Class to parse and read XML files. @@ -102,7 +106,9 @@ class XMLReader( }) def parseXml(xmlString: String): List[HTMLElement] = { - val xml = XML.loadString(xmlString) + val parser = new SAXFactoryImpl().newSAXParser() + val adapter = new NoBindingFactoryAdapter + val xml = adapter.loadXML(new InputSource(new StringReader(xmlString)), parser) val elements = ListBuffer[HTMLElement]() def traverse(node: Node, parentId: Option[String]): Unit = { @@ -128,10 +134,19 @@ class XMLReader( elements += HTMLElement(elementType, content, metadata) } + elem.attributes.asAttrMap.foreach { case (attrName, attrValue) => + val attrId = hash(tagName + attrName + attrValue) + val metadata = + mutable.Map("elementId" -> attrId, "parentId" -> elementId, "attribute" -> attrName) + if (xmlKeepTags) metadata += ("tag" -> tagName) + + elements += HTMLElement(ElementType.NARRATIVE_TEXT, attrValue, metadata) + } + // Traverse children elem.child.foreach(traverse(_, Some(elementId))) - case _ => // Ignore other types + case _ => // ignore } } diff --git a/src/test/scala/com/johnsnowlabs/reader/Reader2DocTest.scala b/src/test/scala/com/johnsnowlabs/reader/Reader2DocTest.scala index e33c9c5717127e..63d6acea333434 100644 --- a/src/test/scala/com/johnsnowlabs/reader/Reader2DocTest.scala +++ b/src/test/scala/com/johnsnowlabs/reader/Reader2DocTest.scala @@ -319,7 +319,6 @@ class Reader2DocTest extends AnyFlatSpec with SparkSessionTest { assert(annotations.head.metadata("elementType") != ElementType.IMAGE) } } - it should "validate invalid paths" taggedAs SlowTest in { val reader2Doc = new Reader2Doc() @@ -429,4 +428,23 @@ class Reader2DocTest extends AnyFlatSpec with SparkSessionTest { resultDf.show(truncate = false) } + it should "parse attributes inside XML files" taggedAs FastTest in { + val reader2Doc = new Reader2Doc() + .setContentType("application/xml") + .setContentPath(s"$xmlDirectory/test.xml") + .setOutputCol("document") + + val pipeline = new Pipeline().setStages(Array(reader2Doc)) + + val pipelineModel = pipeline.fit(emptyDataSet) + val resultDf = pipelineModel.transform(emptyDataSet) + + val annotationsResult = AssertAnnotations.getActualResult(resultDf, "document") + val attributeElements = annotationsResult.flatMap { annotations => + annotations.filter(ann => ann.metadata.contains("attribute")) + } + + assert(attributeElements.length > 0, "Expected to find attribute elements in the XML content") + } + } diff --git a/src/test/scala/com/johnsnowlabs/reader/XMLReaderTest.scala b/src/test/scala/com/johnsnowlabs/reader/XMLReaderTest.scala index a75537803e61de..1a0bd3ed3688e1 100644 --- a/src/test/scala/com/johnsnowlabs/reader/XMLReaderTest.scala +++ b/src/test/scala/com/johnsnowlabs/reader/XMLReaderTest.scala @@ -40,4 +40,43 @@ class XMLReaderTest extends AnyFlatSpec { assert(noParentIdCount.count() > 0) } + it should "extract attributes as NARRATIVE_TEXT elements" taggedAs FastTest in { + val xml = + """<root> + | <observation code="ASSERTION" statusCode="completed"/> + |</root>""".stripMargin + + val reader = new XMLReader(xmlKeepTags = true, onlyLeafNodes = true) + val elements = reader.parseXml(xml) + + val attrElements = elements.filter(_.elementType == ElementType.NARRATIVE_TEXT) + + assert(attrElements.nonEmpty, "Attributes should be extracted as NARRATIVE_TEXT") + + val codeAttrOpt = + attrElements.find(_.metadata.get("attribute").exists(_.equalsIgnoreCase("code"))) + assert(codeAttrOpt.isDefined, "Expected attribute 'code' was not found") + assert(codeAttrOpt.get.content == "ASSERTION") + + val statusAttrOpt = + attrElements.find(_.metadata.get("attribute").exists(_.equalsIgnoreCase("statusCode"))) + assert(statusAttrOpt.isDefined, "Expected attribute 'statusCode' was not found") + assert(statusAttrOpt.get.content == "completed") + } + + it should "link attribute elements to their parentId" taggedAs FastTest in { + val xml = + """<root> + | <item id="123" class="test">Content</item> + |</root>""".stripMargin + + val reader = new XMLReader(xmlKeepTags = true, onlyLeafNodes = true) + val elements = reader.parseXml(xml) + + val itemElem = elements.find(e => e.metadata.get("tag").contains("item")).get + val attrElems = elements.filter(_.metadata.contains("attribute")) + + assert(attrElems.forall(_.metadata("parentId") == itemElem.metadata("elementId"))) + } + } From b540f5ccaca4f1fc1d301db6aa4acdd52f0aba41 Mon Sep 17 00:00:00 2001 From: Danilo Burbano <37355249+danilojsl@users.noreply.github.com> Date: Wed, 8 Oct 2025 08:33:42 -0500 Subject: [PATCH 4/7] [SPARKNLP-1290] Introducing ReaderAssembler Annotator (#14668) * [SPARKNLP-1291] Adding support fort input string column on readers * [SPARKNLP-1291] Removing unnecessary tests * [SPARKNLP-129o] Adding ReaderAssembler annotator * [SPARKNLP-1290] Adding ReaderAssembler python wrapper * [SPARKNLP-1291] Adding ReaderAssembler documentation --------- Co-authored-by: Devin Ha <devin@trungducha.de> --- .../SparkNLP_ReaderAssembler_Demo.ipynb | 498 +++++++++++++ .../partition/partition_properties.py | 50 ++ python/sparknlp/reader/reader2doc.py | 77 +- python/sparknlp/reader/reader2table.py | 34 - python/sparknlp/reader/reader_assembler.py | 159 ++++ .../matcher/multi_date_matcher_test.py | 2 +- python/test/reader/readerassembler_test.py | 43 ++ .../partition/HasBinaryReaderProperties.scala | 7 + .../partition/HasPdfReaderProperties.scala | 22 + .../partition/HasReaderProperties.scala | 12 +- .../partition/HasTagsReaderProperties.scala | 3 + .../partition/PartitionTransformer.scala | 4 +- .../com/johnsnowlabs/reader/CSVReader.scala | 18 - .../reader/HasReaderContent.scala | 22 +- .../com/johnsnowlabs/reader/PdfToText.scala | 4 +- .../com/johnsnowlabs/reader/Reader2Doc.scala | 31 +- .../johnsnowlabs/reader/Reader2Image.scala | 16 +- .../johnsnowlabs/reader/ReaderAssembler.scala | 686 ++++++++++++++++++ ...ies.scala => HasPdfToTextProperties.scala} | 7 +- .../resources/reader/doc/doc-img-table.docx | Bin 0 -> 114020 bytes .../resources/reader/html/table-image.html | 38 + .../resources/reader/pdf/pdf-with-2images.pdf | Bin 0 -> 130182 bytes .../johnsnowlabs/reader/Reader2DocTest.scala | 18 +- .../reader/Reader2ImageTest.scala | 54 +- .../reader/Reader2TableTest.scala | 16 + .../reader/ReaderAssemblerTest.scala | 246 +++++++ 26 files changed, 1880 insertions(+), 187 deletions(-) create mode 100644 examples/python/data-preprocessing/SparkNLP_ReaderAssembler_Demo.ipynb create mode 100644 python/sparknlp/reader/reader_assembler.py create mode 100644 python/test/reader/readerassembler_test.py create mode 100644 src/main/scala/com/johnsnowlabs/partition/HasBinaryReaderProperties.scala create mode 100644 src/main/scala/com/johnsnowlabs/partition/HasPdfReaderProperties.scala create mode 100644 src/main/scala/com/johnsnowlabs/partition/HasTagsReaderProperties.scala create mode 100644 src/main/scala/com/johnsnowlabs/reader/ReaderAssembler.scala rename src/main/scala/com/johnsnowlabs/reader/util/{HasPdfProperties.scala => HasPdfToTextProperties.scala} (94%) create mode 100644 src/test/resources/reader/doc/doc-img-table.docx create mode 100644 src/test/resources/reader/html/table-image.html create mode 100644 src/test/resources/reader/pdf/pdf-with-2images.pdf create mode 100644 src/test/scala/com/johnsnowlabs/reader/ReaderAssemblerTest.scala diff --git a/examples/python/data-preprocessing/SparkNLP_ReaderAssembler_Demo.ipynb b/examples/python/data-preprocessing/SparkNLP_ReaderAssembler_Demo.ipynb new file mode 100644 index 00000000000000..3ac257f27e62e4 --- /dev/null +++ b/examples/python/data-preprocessing/SparkNLP_ReaderAssembler_Demo.ipynb @@ -0,0 +1,498 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![JohnSnowLabs](https://sparknlp.org/assets/images/logo.png)\n", + "\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/JohnSnowLabs/spark-nlp/blob/master/examples/python/transformers/SparkNLP_ReaderAssembler_Demo.ipynb)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "quSlGrh2X0Ar" + }, + "source": [ + "# Introducing ReaderAssembler in SparkNLP\n", + "\n", + "This notebook showcases the newly added `ReaderAssembler` annotator in Spark NLP. It provides a unified interface for combining multiple Spark NLP\n", + "readers (such as Reader2Doc, Reader2Table, and Reader2Image) into a single, configurable component." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "5ema2j9sODvP", + "outputId": "1f11421f-6d44-45bd-decb-f895139736ce" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Mounted at /content/drive\n" + ] + } + ], + "source": [ + "from google.colab import drive\n", + "drive.mount('/content/drive')" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "id": "XLyy6krEOFLd" + }, + "outputs": [], + "source": [ + "!cp drive/MyDrive/JSL/sparknlp/sparknlp.jar .\n", + "!cp drive/MyDrive/JSL/sparknlp/spark_nlp-6.1.4-py2.py3-none-any.whl ." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "y0yAjzokOajt", + "outputId": "721e8c02-bfec-44e5-9d39-92188cc3637f" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Processing ./spark_nlp-6.1.4-py2.py3-none-any.whl\n", + "Installing collected packages: spark-nlp\n", + "Successfully installed spark-nlp-6.1.4\n" + ] + } + ], + "source": [ + "!pip install spark_nlp-6.1.4-py2.py3-none-any.whl" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "xvycj4qAObCw", + "outputId": "b67eee0d-4be7-4ce9-ba5c-058f2557a613" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Apache Spark version: 3.5.1\n" + ] + } + ], + "source": [ + "import sparknlp\n", + "\n", + "# # let's start Spark with Spark NLP with GPU enabled. If you don't have GPUs available remove this parameter.\n", + "spark = sparknlp.start()\n", + "print(sparknlp.version())\n", + "\n", + "print(\"Apache Spark version: {}\".format(spark.version))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "iXtXmyJFYfGG" + }, + "source": [ + "To illustrate the use of this reader, let’s define an HTML document containing image data and display a preview." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 178 + }, + "id": "6ZUkBA7rZ1lp", + "outputId": "755d7fa9-f2d8-40d9-8d42-29751eb1a3c6" + }, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "<!DOCTYPE html>\n", + "<html>\n", + "<head>\n", + " <title>Image Parsing Test\n", + "\n", + "\n", + "

This is a normal paragraph.

\n", + "

Test Images

\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "
Hello World
\n", + "\n", + "\"Base64\n", + "\n", + "\n", + "\"React\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from IPython.core.display import display, HTML\n", + "\n", + "html_code = \"\"\"\n", + "\n", + "\n", + "\n", + " Image Parsing Test\n", + "\n", + "\n", + "

This is a normal paragraph.

\n", + "

Test Images

\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "
Hello World
\n", + "\n", + "\"Base64\n", + "\n", + "\n", + "\"React\n", + "\"\"\"\n", + "\n", + "display(HTML(html_code))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "_KhznNBIYx0m" + }, + "source": [ + "As you can see in the image above, we have two files: a small red dot and an atom. We expect a VLM model to generate descriptions of these images for us." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "id": "MTnevAlxaXB5" + }, + "outputs": [], + "source": [ + "with open(\"example-images.html\", \"w\") as f:\n", + " f.write(html_code)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "id": "4JOsiklDVTgd" + }, + "outputs": [], + "source": [ + "empty_df = spark.createDataFrame([], \"string\").toDF(\"text\")" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "id": "pZwclDzKVVX_" + }, + "outputs": [], + "source": [ + "from pyspark.ml import Pipeline\n", + "from sparknlp.reader.reader_assembler import ReaderAssembler\n", + "\n", + "reader = ReaderAssembler() \\\n", + " .setContentType(\"text/html\") \\\n", + " .setContentPath(\"./example-images.html\") \\\n", + " .setOutputCol(\"document\")\n", + "\n", + "pipeline = Pipeline(stages=[reader])\n", + "model = pipeline.fit(empty_df)\n", + "\n", + "reader_df = model.transform(empty_df)" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "V59P4av5s4dR", + "outputId": "491267e0-d489-4ca8-d7cb-1480c9da7042" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "root\n", + " |-- fileName: string (nullable = true)\n", + " |-- document_text: array (nullable = true)\n", + " | |-- element: struct (containsNull = true)\n", + " | | |-- annotatorType: string (nullable = true)\n", + " | | |-- begin: integer (nullable = false)\n", + " | | |-- end: integer (nullable = false)\n", + " | | |-- result: string (nullable = true)\n", + " | | |-- metadata: map (nullable = true)\n", + " | | | |-- key: string\n", + " | | | |-- value: string (valueContainsNull = true)\n", + " | | |-- embeddings: array (nullable = true)\n", + " | | | |-- element: float (containsNull = false)\n", + " |-- document_table: array (nullable = true)\n", + " | |-- element: struct (containsNull = true)\n", + " | | |-- annotatorType: string (nullable = true)\n", + " | | |-- begin: integer (nullable = false)\n", + " | | |-- end: integer (nullable = false)\n", + " | | |-- result: string (nullable = true)\n", + " | | |-- metadata: map (nullable = true)\n", + " | | | |-- key: string\n", + " | | | |-- value: string (valueContainsNull = true)\n", + " | | |-- embeddings: array (nullable = true)\n", + " | | | |-- element: float (containsNull = false)\n", + " |-- document_image: array (nullable = true)\n", + " | |-- element: struct (containsNull = true)\n", + " | | |-- annotatorType: string (nullable = true)\n", + " | | |-- origin: string (nullable = true)\n", + " | | |-- height: integer (nullable = false)\n", + " | | |-- width: integer (nullable = false)\n", + " | | |-- nChannels: integer (nullable = false)\n", + " | | |-- mode: integer (nullable = false)\n", + " | | |-- result: binary (nullable = true)\n", + " | | |-- metadata: map (nullable = true)\n", + " | | | |-- key: string\n", + " | | | |-- value: string (valueContainsNull = true)\n", + " | | |-- text: string (nullable = true)\n", + " |-- exception: void (nullable = true)\n", + "\n" + ] + } + ], + "source": [ + "reader_df.printSchema()" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "S68AuJHus87y", + "outputId": "75cc6139-5bb3-4b0d-99e1-4d4f61b9d144" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+\n", + "|document_text |\n", + "+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+\n", + "|[{document, 0, 26, This is a normal paragraph., {pageNumber -> 1, sentence -> 0, elementType -> Title}, []}, {document, 27, 37, Test Images, {pageNumber -> 1, sentence -> 1, elementType -> Title}, []}]|\n", + "+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+\n", + "\n" + ] + } + ], + "source": [ + "reader_df.select(\"document_text\").show(truncate=False)" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "z0mpdspftFU7", + "outputId": "a384416d-2022-42d1-9f8c-070d8058f29a" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "+------------------------------------------------------------------------------------------------------------------------------------+\n", + "|document_table |\n", + "+------------------------------------------------------------------------------------------------------------------------------------+\n", + "|[{document, 0, 50, {\"caption\":\"\",\"header\":[],\"rows\":[[\"Hello World\"]]}, {pageNumber -> 1, sentence -> 2, elementType -> Table}, []}]|\n", + "+------------------------------------------------------------------------------------------------------------------------------------+\n", + "\n" + ] + } + ], + "source": [ + "reader_df.select(\"document_table\").show(truncate=False)" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "Pewp7QFvtHzL", + "outputId": "05b650f9-88da-4c40-ed01-a9bc1d2aba64" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "+--------------------+\n", + "| document_image|\n", + "+--------------------+\n", + "|[{image, example-...|\n", + "+--------------------+\n", + "\n" + ] + } + ], + "source": [ + "reader_df.select(\"document_image\").show()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "QwfZCxXsu014" + }, + "source": [ + "## Integration with SparkNLP VLM models" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "p3hDYMnyuSG6", + "outputId": "e0d12ba7-26cd-44c2-a741-a3262a6f0adf" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "qwen2_vl_2b_instruct_int4 download started this may take some time.\n", + "Approximate size to download 1.4 GB\n", + "[OK!]\n" + ] + } + ], + "source": [ + "from sparknlp.annotator import Qwen2VLTransformer\n", + "\n", + "visualQAClassifier = (\n", + " Qwen2VLTransformer.pretrained()\n", + " .setInputCols(\"document_image\")\n", + " .setOutputCol(\"answer\")\n", + ")\n", + "\n", + "pipeline = Pipeline().setStages([visualQAClassifier])\n", + "result_df = pipeline.fit(reader_df).transform(reader_df)" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "bmFcgB2Sud9t", + "outputId": "806894f9-3a2e-4915-eadc-f48ba5df810f" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "+------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+\n", + "|origin |result |\n", + "+------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+\n", + "|[example-images.html, example-images.html]|[The image is a simple, solid-colored background with a gradient effect. The colors blend smoothly from a lighter yellow at the top to a darker yellow at the bottom. There are no patterns, textures, or any other visual elements present. The image appears to be a plain, unadorned background.]|\n", + "+------------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+\n", + "\n" + ] + } + ], + "source": [ + "result_df.select(\"document_image.origin\", \"answer.result\").show(truncate=False)" + ] + } + ], + "metadata": { + "colab": { + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.3" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/python/sparknlp/partition/partition_properties.py b/python/sparknlp/partition/partition_properties.py index a7ffbaeafa81b9..7af8d91b2d0f69 100644 --- a/python/sparknlp/partition/partition_properties.py +++ b/python/sparknlp/partition/partition_properties.py @@ -194,6 +194,56 @@ def setExplodeDocs(self, value: bool): """ return self._set(explodeDocs=value) + flattenOutput = Param( + Params._dummy(), + "flattenOutput", + "If true, output is flattened to plain text with minimal metadata", + typeConverter=TypeConverters.toBoolean + ) + + def setFlattenOutput(self, value): + """Sets whether to flatten the output to plain text with minimal metadata. + + ParametersF + ---------- + value : bool + If true, output is flattened to plain text with minimal metadata + """ + return self._set(flattenOutput=value) + + titleThreshold = Param( + Params._dummy(), + "titleThreshold", + "Minimum font size threshold for title detection in PDF docs", + typeConverter=TypeConverters.toFloat + ) + + def setTitleThreshold(self, value): + """Sets the minimum font size threshold for title detection in PDF documents. + + Parameters + ---------- + value : float + Minimum font size threshold for title detection in PDF docs + """ + return self._set(titleThreshold=value) + + outputAsDocument = Param( + Params._dummy(), + "outputAsDocument", + "Whether to return all sentences joined into a single document", + typeConverter=TypeConverters.toBoolean + ) + + def setOutputAsDocument(self, value): + """Sets whether to return all sentences joined into a single document. + + Parameters + ---------- + value : bool + Whether to return all sentences joined into a single document + """ + return self._set(outputAsDocument=value) class HasEmailReaderProperties(Params): diff --git a/python/sparknlp/reader/reader2doc.py b/python/sparknlp/reader/reader2doc.py index aca1dc93403339..9352eda3c257f5 100644 --- a/python/sparknlp/reader/reader2doc.py +++ b/python/sparknlp/reader/reader2doc.py @@ -12,7 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. from pyspark import keyword_only -from pyspark.ml.param import TypeConverters, Params, Param from sparknlp.common import AnnotatorType from sparknlp.internal import AnnotatorTransformer @@ -69,32 +68,11 @@ class Reader2Doc( |[{'document', 15, 38, 'This is a narrative text', {'pageNumber': 1, 'elementType': 'NarrativeText', 'fileName': 'pdf-title.pdf'}, []}]| |[{'document', 39, 68, 'This is another narrative text', {'pageNumber': 1, 'elementType': 'NarrativeText', 'fileName': 'pdf-title.pdf'}, []}]| +------------------------------------------------------------------------------------------------------------------------------------+ -""" + """ name = "Reader2Doc" - outputAnnotatorType = AnnotatorType.DOCUMENT - - - flattenOutput = Param( - Params._dummy(), - "flattenOutput", - "If true, output is flattened to plain text with minimal metadata", - typeConverter=TypeConverters.toBoolean - ) - titleThreshold = Param( - Params._dummy(), - "titleThreshold", - "Minimum font size threshold for title detection in PDF docs", - typeConverter=TypeConverters.toFloat - ) - - outputAsDocument = Param( - Params._dummy(), - "outputAsDocument", - "Whether to return all sentences joined into a single document", - typeConverter=TypeConverters.toBoolean - ) + outputAnnotatorType = AnnotatorType.DOCUMENT excludeNonText = Param( Params._dummy(), @@ -103,6 +81,16 @@ class Reader2Doc( typeConverter=TypeConverters.toBoolean ) + def setExcludeNonText(self, value): + """Sets whether to exclude non-text content from the output. + + Parameters + ---------- + value : bool + Whether to exclude non-text content from the output. Default is False. + """ + return self._set(excludeNonText=value) + @keyword_only def __init__(self): super(Reader2Doc, self).__init__(classname="com.johnsnowlabs.reader.Reader2Doc") @@ -117,44 +105,3 @@ def __init__(self): def setParams(self): kwargs = self._input_kwargs return self._set(**kwargs) - - - def setFlattenOutput(self, value): - """Sets whether to flatten the output to plain text with minimal metadata. - - ParametersF - ---------- - value : bool - If true, output is flattened to plain text with minimal metadata - """ - return self._set(flattenOutput=value) - - def setTitleThreshold(self, value): - """Sets the minimum font size threshold for title detection in PDF documents. - - Parameters - ---------- - value : float - Minimum font size threshold for title detection in PDF docs - """ - return self._set(titleThreshold=value) - - def setOutputAsDocument(self, value): - """Sets whether to return all sentences joined into a single document. - - Parameters - ---------- - value : bool - Whether to return all sentences joined into a single document - """ - return self._set(outputAsDocument=value) - - def setExcludeNonText(self, value): - """Sets whether to exclude non-text content from the output. - - Parameters - ---------- - value : bool - Whether to exclude non-text content from the output. Default is False. - """ - return self._set(excludeNonText=value) \ No newline at end of file diff --git a/python/sparknlp/reader/reader2table.py b/python/sparknlp/reader/reader2table.py index 2b353c3098b82b..f39089fbda1fea 100644 --- a/python/sparknlp/reader/reader2table.py +++ b/python/sparknlp/reader/reader2table.py @@ -32,20 +32,6 @@ class Reader2Table( outputAnnotatorType = AnnotatorType.DOCUMENT - flattenOutput = Param( - Params._dummy(), - "flattenOutput", - "If true, output is flattened to plain text with minimal metadata", - typeConverter=TypeConverters.toBoolean - ) - - titleThreshold = Param( - Params._dummy(), - "titleThreshold", - "Minimum font size threshold for title detection in PDF docs", - typeConverter=TypeConverters.toFloat - ) - @keyword_only def __init__(self): super(Reader2Table, self).__init__(classname="com.johnsnowlabs.reader.Reader2Table") @@ -55,23 +41,3 @@ def __init__(self): def setParams(self): kwargs = self._input_kwargs return self._set(**kwargs) - - def setFlattenOutput(self, value): - """Sets whether to flatten the output to plain text with minimal metadata. - - Parameters - ---------- - value : bool - If true, output is flattened to plain text with minimal metadata - """ - return self._set(flattenOutput=value) - - def setTitleThreshold(self, value): - """Sets the minimum font size threshold for title detection in PDF documents. - - Parameters - ---------- - value : float - Minimum font size threshold for title detection in PDF docs - """ - return self._set(titleThreshold=value) diff --git a/python/sparknlp/reader/reader_assembler.py b/python/sparknlp/reader/reader_assembler.py new file mode 100644 index 00000000000000..84d0e0d21c453b --- /dev/null +++ b/python/sparknlp/reader/reader_assembler.py @@ -0,0 +1,159 @@ +# Copyright 2017-2025 John Snow Labs +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from pyspark import keyword_only + +from sparknlp.common import AnnotatorType +from sparknlp.internal import AnnotatorTransformer +from sparknlp.partition.partition_properties import * + +class ReaderAssembler( + AnnotatorTransformer, + HasReaderProperties, + HasHTMLReaderProperties, + HasEmailReaderProperties, + HasExcelReaderProperties, + HasPowerPointProperties, + HasTextReaderProperties, + HasPdfProperties +): + """ + The ReaderAssembler annotator provides a unified interface for combining multiple Spark NLP + readers (such as Reader2Doc, Reader2Table, and Reader2Image) into a single, configurable + component. It automatically orchestrates the execution of different readers based on input type, + configured priorities, and fallback strategies allowing you to handle diverse content formats + without manually chaining multiple readers in your pipeline. + + ReaderAssembler simplifies the process of building flexible pipelines capable of ingesting and + processing documents, tables, and images in a consistent way. It handles reader selection, + ordering, and fault-tolerance internally, ensuring that pipelines remain concise, robust, and + easy to maintain. + + Examples + -------- + >>> from johnsnowlabs.reader import ReaderAssembler + >>> from pyspark.ml import Pipeline + >>> + >>> reader_assembler = ReaderAssembler() \\ + ... .setContentType("text/html") \\ + ... .setContentPath("/table-image.html") \\ + ... .setOutputCol("document") + >>> + >>> pipeline = Pipeline(stages=[reader_assembler]) + >>> pipeline_model = pipeline.fit(empty_data_set) + >>> result_df = pipeline_model.transform(empty_data_set) + >>> + >>> result_df.show() + +--------+--------------------+--------------------+--------------------+---------+ + |fileName| document_text| document_table| document_image|exception| + +--------+--------------------+--------------------+--------------------+---------+ + | null|[{'document', 0, 26...|[{'document', 0, 50...|[{'image', , 5, 5, ...| null| + +--------+--------------------+--------------------+--------------------+---------+ + + This annotator is especially useful when working with heterogeneous input data — for example, + when a dataset includes PDFs, spreadsheets, and images — allowing Spark NLP to automatically + invoke the appropriate reader for each file type while preserving a unified schema in the output. +""" + + + name = 'ReaderAssembler' + + outputAnnotatorType = AnnotatorType.DOCUMENT + + excludeNonText = Param( + Params._dummy(), + "excludeNonText", + "Whether to exclude non-text content from the output. Default is False.", + typeConverter=TypeConverters.toBoolean + ) + + userMessage = Param( + Params._dummy(), + "userMessage", + "Custom user message.", + typeConverter=TypeConverters.toString + ) + + promptTemplate = Param( + Params._dummy(), + "promptTemplate", + "Format of the output prompt.", + typeConverter=TypeConverters.toString + ) + + customPromptTemplate = Param( + Params._dummy(), + "customPromptTemplate", + "Custom prompt template for image models.", + typeConverter=TypeConverters.toString + ) + + @keyword_only + def __init__(self): + super(ReaderAssembler, self).__init__(classname="com.johnsnowlabs.reader.ReaderAssembler") + self._setDefault(contentType="", + explodeDocs=False, + userMessage="Describe this image", + promptTemplate="qwen2vl-chat", + readAsImage=True, + customPromptTemplate="", + ignoreExceptions=True, + flattenOutput=False, + titleThreshold=18) + + + @keyword_only + def setParams(self): + kwargs = self._input_kwargs + return self._set(**kwargs) + + def setExcludeNonText(self, value): + """Sets whether to exclude non-text content from the output. + + Parameters + ---------- + value : bool + Whether to exclude non-text content from the output. Default is False. + """ + return self._set(excludeNonText=value) + + def setUserMessage(self, value: str): + """Sets custom user message. + + Parameters + ---------- + value : str + Custom user message to include. + """ + return self._set(userMessage=value) + + def setPromptTemplate(self, value: str): + """Sets format of the output prompt. + + Parameters + ---------- + value : str + Prompt template format. + """ + return self._set(promptTemplate=value) + + def setCustomPromptTemplate(self, value: str): + """Sets custom prompt template for image models. + + Parameters + ---------- + value : str + Custom prompt template string. + """ + return self._set(customPromptTemplate=value) \ No newline at end of file diff --git a/python/test/annotator/matcher/multi_date_matcher_test.py b/python/test/annotator/matcher/multi_date_matcher_test.py index a7fe4636fa30dc..549946d106ebc6 100644 --- a/python/test/annotator/matcher/multi_date_matcher_test.py +++ b/python/test/annotator/matcher/multi_date_matcher_test.py @@ -47,4 +47,4 @@ def runTest(self): result = model.transform(self.data) actual_dates = result.select(size("date.result")).collect()[0][0] - self.assertEquals(actual_dates, 4) + self.assertEqual(actual_dates, 4) diff --git a/python/test/reader/readerassembler_test.py b/python/test/reader/readerassembler_test.py new file mode 100644 index 00000000000000..0a4532e0547721 --- /dev/null +++ b/python/test/reader/readerassembler_test.py @@ -0,0 +1,43 @@ + +# Copyright 2017-2024 John Snow Labs +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import unittest + +import pytest +from pyspark.ml import Pipeline + +from sparknlp.reader.reader_assembler import ReaderAssembler +from test.util import SparkContextForTest + +@pytest.mark.fast +class ReaderAssemblerTest(unittest.TestCase): + + def setUp(self): + spark = SparkContextForTest.spark + self.empty_df = spark.createDataFrame([], "string").toDF("text") + + def runTest(self): + reader_assembler = ReaderAssembler() \ + .setContentType("text/html") \ + .setContentPath(f"file:///{os.getcwd()}/../src/test/resources/reader/html/table-image.html") \ + .setOutputCol("document") + + pipeline = Pipeline(stages=[reader_assembler]) + model = pipeline.fit(self.empty_df) + + result_df = model.transform(self.empty_df) + + self.assertTrue(result_df.count() > 0) \ No newline at end of file diff --git a/src/main/scala/com/johnsnowlabs/partition/HasBinaryReaderProperties.scala b/src/main/scala/com/johnsnowlabs/partition/HasBinaryReaderProperties.scala new file mode 100644 index 00000000000000..bba0e5c974ef34 --- /dev/null +++ b/src/main/scala/com/johnsnowlabs/partition/HasBinaryReaderProperties.scala @@ -0,0 +1,7 @@ +package com.johnsnowlabs.partition + +trait HasBinaryReaderProperties + extends HasEmailReaderProperties + with HasExcelReaderProperties + with HasPdfReaderProperties + with HasPowerPointProperties {} diff --git a/src/main/scala/com/johnsnowlabs/partition/HasPdfReaderProperties.scala b/src/main/scala/com/johnsnowlabs/partition/HasPdfReaderProperties.scala new file mode 100644 index 00000000000000..5dd4427da23463 --- /dev/null +++ b/src/main/scala/com/johnsnowlabs/partition/HasPdfReaderProperties.scala @@ -0,0 +1,22 @@ +package com.johnsnowlabs.partition + +import com.johnsnowlabs.nlp.ParamsAndFeaturesWritable +import org.apache.spark.ml.param.{BooleanParam, Param} + +trait HasPdfReaderProperties extends ParamsAndFeaturesWritable { + + val titleThreshold: Param[Double] = + new Param[Double]( + this, + "titleThreshold", + "Minimum font size threshold for title detection in PDF docs") + + def setTitleThreshold(value: Double): this.type = { + set(titleThreshold, value) + } + + final val readAsImage = new BooleanParam(this, "readAsImage", "Read PDF pages as images.") + + setDefault(titleThreshold -> 18, readAsImage -> false) + +} diff --git a/src/main/scala/com/johnsnowlabs/partition/HasReaderProperties.scala b/src/main/scala/com/johnsnowlabs/partition/HasReaderProperties.scala index f8bae5570187af..5d4df7b7952996 100644 --- a/src/main/scala/com/johnsnowlabs/partition/HasReaderProperties.scala +++ b/src/main/scala/com/johnsnowlabs/partition/HasReaderProperties.scala @@ -15,10 +15,10 @@ */ package com.johnsnowlabs.partition +import com.johnsnowlabs.nlp.ParamsAndFeaturesWritable import org.apache.spark.ml.param.{BooleanParam, Param} -trait HasReaderProperties extends HasHTMLReaderProperties { - +trait HasReaderProperties extends ParamsAndFeaturesWritable { protected final val inputCol: Param[String] = new Param(this, "inputCol", "input column to process") @@ -75,6 +75,14 @@ trait HasReaderProperties extends HasHTMLReaderProperties { def setExplodeDocs(value: Boolean): this.type = set(explodeDocs, value) + val flattenOutput: BooleanParam = + new BooleanParam( + this, + "flattenOutput", + "If true, output is flattened to plain text with minimal metadata") + + def setFlattenOutput(value: Boolean): this.type = set(flattenOutput, value) + setDefault( contentPath -> "", contentType -> "text/plain", diff --git a/src/main/scala/com/johnsnowlabs/partition/HasTagsReaderProperties.scala b/src/main/scala/com/johnsnowlabs/partition/HasTagsReaderProperties.scala new file mode 100644 index 00000000000000..0bdfa7dbf5fea7 --- /dev/null +++ b/src/main/scala/com/johnsnowlabs/partition/HasTagsReaderProperties.scala @@ -0,0 +1,3 @@ +package com.johnsnowlabs.partition + +trait HasTagsReaderProperties extends HasHTMLReaderProperties with HasXmlReaderProperties {} diff --git a/src/main/scala/com/johnsnowlabs/partition/PartitionTransformer.scala b/src/main/scala/com/johnsnowlabs/partition/PartitionTransformer.scala index 463170920181d1..4c685b0d5f9e71 100644 --- a/src/main/scala/com/johnsnowlabs/partition/PartitionTransformer.scala +++ b/src/main/scala/com/johnsnowlabs/partition/PartitionTransformer.scala @@ -22,7 +22,7 @@ import com.johnsnowlabs.partition.util.PartitionHelper.{ datasetWithTextFile, isStringContent } -import com.johnsnowlabs.reader.util.HasPdfProperties +import com.johnsnowlabs.reader.util.HasPdfToTextProperties import com.johnsnowlabs.reader.{HTMLElement, PdfToText} import org.apache.spark.ml.PipelineModel import org.apache.spark.ml.util.Identifiable @@ -85,7 +85,7 @@ class PartitionTransformer(override val uid: String) with HasHTMLReaderProperties with HasPowerPointProperties with HasTextReaderProperties - with HasPdfProperties + with HasPdfToTextProperties with HasXmlReaderProperties with HasChunkerProperties { diff --git a/src/main/scala/com/johnsnowlabs/reader/CSVReader.scala b/src/main/scala/com/johnsnowlabs/reader/CSVReader.scala index 2a078486913bd3..92eda1ba07f184 100644 --- a/src/main/scala/com/johnsnowlabs/reader/CSVReader.scala +++ b/src/main/scala/com/johnsnowlabs/reader/CSVReader.scala @@ -179,22 +179,4 @@ class CSVReader( } } -// def buildErrorDataFrame(dataset: Dataset[_], contentPath: String, ext: String): DataFrame = { -// val fileName = if (contentPath != null) contentPath.split("/").last else "" -// val errorMessage = s"File type .$ext not supported" -// -// val errorPartition = HTMLElement( -// elementType = ElementType.UNCATEGORIZED_TEXT, -// content = errorMessage, -// metadata = scala.collection.mutable.Map[String, String](), -// binaryContent = None) -// -// val spark = dataset.sparkSession -// import spark.implicits._ -// -// val errorArray = Seq((contentPath, Seq(errorPartition), fileName, errorMessage)) -// errorArray -// .toDF("path", "partition", "fileName", "exception") -// } - } diff --git a/src/main/scala/com/johnsnowlabs/reader/HasReaderContent.scala b/src/main/scala/com/johnsnowlabs/reader/HasReaderContent.scala index 203c1ea9881b89..ed5804d14bcb68 100644 --- a/src/main/scala/com/johnsnowlabs/reader/HasReaderContent.scala +++ b/src/main/scala/com/johnsnowlabs/reader/HasReaderContent.scala @@ -4,7 +4,7 @@ import com.johnsnowlabs.partition.util.PartitionHelper.{ datasetWithBinaryFile, datasetWithTextFile } -import com.johnsnowlabs.partition.{HasReaderProperties, Partition} +import com.johnsnowlabs.partition.{HasReaderProperties, HasTagsReaderProperties, Partition} import org.apache.spark.sql.expressions.UserDefinedFunction import org.apache.spark.sql.functions.{col, lit, udf} import org.apache.spark.sql.types.{StringType, StructField, StructType} @@ -13,7 +13,7 @@ import org.apache.spark.sql.{DataFrame, Dataset, Row} import java.io.File import scala.jdk.CollectionConverters.mapAsJavaMapConverter -trait HasReaderContent extends HasReaderProperties { +trait HasReaderContent extends HasReaderProperties with HasTagsReaderProperties { val supportedTypes: Map[String, (String, Boolean)] = Map( "txt" -> ("text/plain", true), @@ -104,7 +104,7 @@ trait HasReaderContent extends HasReaderProperties { } } - private def partitionContentFromPath( + def partitionContentFromPath( partition: Partition, contentPath: String, isText: Boolean, @@ -116,10 +116,7 @@ trait HasReaderContent extends HasReaderProperties { val partitionDf = if (isText) { val stringContentDF = if ($(contentType) == "text/csv" || ext == "csv") { - partition.setOutputColumn("csv") - partition - .partition(contentPath) - .withColumnRenamed(partition.getOutputColumn, "partition") + partitionCSVContent(partition, contentPath) } else { val partitionUDF = udf((text: String) => partition.partitionStringContent(text, $(this.headers).asJava)) @@ -183,12 +180,12 @@ trait HasReaderContent extends HasReaderProperties { if (path != null) path.split("/").last else "" } - private def listAllFilesRecursively(dir: File): Seq[File] = { + def listAllFilesRecursively(dir: File): Seq[File] = { val these = Option(dir.listFiles).getOrElse(Array.empty) these.filter(_.isFile) ++ these.filter(_.isDirectory).flatMap(listAllFilesRecursively) } - private def buildEmptyDataFrame(dataset: Dataset[_]): DataFrame = { + def buildEmptyDataFrame(dataset: Dataset[_]): DataFrame = { val schema = StructType( Seq( StructField("partition", StringType, nullable = true), @@ -201,4 +198,11 @@ trait HasReaderContent extends HasReaderProperties { if ($(contentType).trim.isEmpty && getInputCol.nonEmpty) "text/plain" else $(contentType) } + private def partitionCSVContent(partition: Partition, contentPath: String): DataFrame = { + partition.setOutputColumn("csv") + partition + .partition(contentPath) + .withColumnRenamed(partition.getOutputColumn, "partition") + } + } diff --git a/src/main/scala/com/johnsnowlabs/reader/PdfToText.scala b/src/main/scala/com/johnsnowlabs/reader/PdfToText.scala index e7ea2d217981c2..dfe93fb19cbe73 100644 --- a/src/main/scala/com/johnsnowlabs/reader/PdfToText.scala +++ b/src/main/scala/com/johnsnowlabs/reader/PdfToText.scala @@ -16,7 +16,7 @@ package com.johnsnowlabs.reader import com.johnsnowlabs.nlp.IAnnotation -import com.johnsnowlabs.reader.util.HasPdfProperties +import com.johnsnowlabs.reader.util.HasPdfToTextProperties import com.johnsnowlabs.reader.util.pdf._ import com.johnsnowlabs.reader.util.pdf.schema.{MappingMatrix, PageMatrix} import org.apache.pdfbox.pdmodel.PDDocument @@ -97,7 +97,7 @@ class PdfToText(override val uid: String) with HasOutputCol with HasLocalProcess with PdfToTextTrait - with HasPdfProperties { + with HasPdfToTextProperties { def this() = this(Identifiable.randomUID("PDF_TO_TEXT_TRANSFORMER")) diff --git a/src/main/scala/com/johnsnowlabs/reader/Reader2Doc.scala b/src/main/scala/com/johnsnowlabs/reader/Reader2Doc.scala index eb147fd5c7e44e..bd418b34116e74 100644 --- a/src/main/scala/com/johnsnowlabs/reader/Reader2Doc.scala +++ b/src/main/scala/com/johnsnowlabs/reader/Reader2Doc.scala @@ -21,7 +21,7 @@ import com.johnsnowlabs.nlp.{Annotation, HasOutputAnnotationCol, HasOutputAnnota import com.johnsnowlabs.partition._ import com.johnsnowlabs.partition.util.PartitionHelper.isStringContent import org.apache.spark.ml.Transformer -import org.apache.spark.ml.param.{BooleanParam, Param, ParamMap} +import org.apache.spark.ml.param.{BooleanParam, ParamMap} import org.apache.spark.ml.util.{DefaultParamsReadable, DefaultParamsWritable, Identifiable} import org.apache.spark.sql._ import org.apache.spark.sql.expressions.UserDefinedFunction @@ -70,34 +70,12 @@ class Reader2Doc(override val uid: String) with DefaultParamsWritable with HasOutputAnnotatorType with HasOutputAnnotationCol - with HasEmailReaderProperties - with HasExcelReaderProperties - with HasHTMLReaderProperties - with HasPowerPointProperties + with HasBinaryReaderProperties with HasTextReaderProperties - with HasXmlReaderProperties with HasReaderContent { def this() = this(Identifiable.randomUID("Reader2Doc")) - val flattenOutput: BooleanParam = - new BooleanParam( - this, - "flattenOutput", - "If true, output is flattened to plain text with minimal metadata") - - def setFlattenOutput(value: Boolean): this.type = set(flattenOutput, value) - - val titleThreshold: Param[Float] = - new Param[Float]( - this, - "titleThreshold", - "Minimum font size threshold for title detection in PDF docs") - - def setTitleThreshold(value: Float): this.type = { - set(titleThreshold, value) - } - /** Whether to return all sentences joined into a single document * * @group param @@ -120,7 +98,6 @@ class Reader2Doc(override val uid: String) this.explodeDocs -> false, contentType -> "", flattenOutput -> false, - titleThreshold -> 18, outputAsDocument -> false, outputFormat -> "plain-text", excludeNonText -> false) @@ -172,7 +149,7 @@ class Reader2Doc(override val uid: String) new Partition(params.asJava) } - private def afterAnnotate(dataset: DataFrame): DataFrame = { + def afterAnnotate(dataset: DataFrame): DataFrame = { if ($(explodeDocs)) { dataset .select(dataset.columns.filterNot(_ == getOutputCol).map(col) :+ explode( @@ -198,7 +175,7 @@ class Reader2Doc(override val uid: String) "Only 'plain-text' outputFormat is supported for this operation.") } - protected def partitionToAnnotation: UserDefinedFunction = udf { + def partitionToAnnotation: UserDefinedFunction = udf { (partitions: Seq[Row], fileName: String) => if (partitions == null) Nil else if ($(outputAsDocument)) { diff --git a/src/main/scala/com/johnsnowlabs/reader/Reader2Image.scala b/src/main/scala/com/johnsnowlabs/reader/Reader2Image.scala index ce9c05bfeb4a3a..1cf431abbdbac9 100644 --- a/src/main/scala/com/johnsnowlabs/reader/Reader2Image.scala +++ b/src/main/scala/com/johnsnowlabs/reader/Reader2Image.scala @@ -24,10 +24,10 @@ import com.johnsnowlabs.partition.util.PartitionHelper.{ datasetWithTextFile, isStringContent } -import com.johnsnowlabs.partition.{HasHTMLReaderProperties, HasReaderProperties, Partition} -import com.johnsnowlabs.reader.util.{HasPdfProperties, ImageParser, ImagePromptTemplate} +import com.johnsnowlabs.partition.{HasBinaryReaderProperties, Partition} +import com.johnsnowlabs.reader.util.{ImageParser, ImagePromptTemplate} import org.apache.spark.ml.Transformer -import org.apache.spark.ml.param.{BooleanParam, Param, ParamMap} +import org.apache.spark.ml.param.{Param, ParamMap} import org.apache.spark.ml.util.{DefaultParamsWritable, Identifiable} import org.apache.spark.sql.expressions.UserDefinedFunction import org.apache.spark.sql.functions._ @@ -94,9 +94,7 @@ class Reader2Image(override val uid: String) with DefaultParamsWritable with HasOutputAnnotatorType with HasOutputAnnotationCol - with HasReaderProperties - with HasHTMLReaderProperties - with HasPdfProperties + with HasBinaryReaderProperties with HasReaderContent { def this() = this(Identifiable.randomUID("Reader2Image")) @@ -139,7 +137,7 @@ class Reader2Image(override val uid: String) val annotatedDf = structuredDf .withColumn( getOutputCol, - wrapColumnMetadata(partitionAnnotation(col(partition.getOutputColumn), col("path")))) + wrapColumnMetadata(partitionToAnnotation(col(partition.getOutputColumn), col("path")))) afterAnnotate(annotatedDf).select("fileName", getOutputCol, "exception") } else { @@ -231,7 +229,7 @@ class Reader2Image(override val uid: String) "gif" -> ("image/raw", false), "pdf" -> ("application/pdf", false)) - private def partitionAnnotation: UserDefinedFunction = { + def partitionToAnnotation: UserDefinedFunction = { udf((partitions: Seq[Row], path: String) => elementsAsIndividualAnnotations(partitions, path: String)) } @@ -334,7 +332,7 @@ class Reader2Image(override val uid: String) } } - private def afterAnnotate(dataset: DataFrame): DataFrame = { + def afterAnnotate(dataset: DataFrame): DataFrame = { if ($(explodeDocs)) { dataset .select(dataset.columns.filterNot(_ == getOutputCol).map(col) :+ explode( diff --git a/src/main/scala/com/johnsnowlabs/reader/ReaderAssembler.scala b/src/main/scala/com/johnsnowlabs/reader/ReaderAssembler.scala new file mode 100644 index 00000000000000..af3b407d314239 --- /dev/null +++ b/src/main/scala/com/johnsnowlabs/reader/ReaderAssembler.scala @@ -0,0 +1,686 @@ +/* + * Copyright 2017-2025 John Snow Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.johnsnowlabs.reader + +import com.johnsnowlabs.nlp.AnnotatorType.{DOCUMENT, IMAGE} +import com.johnsnowlabs.nlp.util.io.ResourceHelper +import com.johnsnowlabs.nlp.{ + Annotation, + AnnotationImage, + HasOutputAnnotationCol, + HasOutputAnnotatorType +} +import com.johnsnowlabs.partition.util.PartitionHelper.{ + datasetWithBinaryFile, + datasetWithTextFile, + isStringContent +} +import com.johnsnowlabs.partition.{HasBinaryReaderProperties, HasTextReaderProperties, Partition} +import org.apache.spark.ml.Transformer +import org.apache.spark.ml.param.{BooleanParam, Param, ParamMap} +import org.apache.spark.ml.util.{DefaultParamsWritable, Identifiable} +import org.apache.spark.sql.functions._ +import org.apache.spark.sql.types._ +import org.apache.spark.sql.{Column, DataFrame, Dataset} + +import java.io.File +import scala.collection.mutable +import scala.jdk.CollectionConverters.mapAsJavaMapConverter + +/** The ReaderAssembler annotator provides a unified interface for combining multiple Spark NLP + * readers (such as Reader2Doc, Reader2Table, and Reader2Image) into a single, configurable + * component. It automatically orchestrates the execution of different readers based on input + * type, configured priorities, and fallback strategies allowing you to handle diverse content + * formats without manually chaining multiple readers in your pipeline. + * + * ReaderAssembler simplifies the process of building flexible pipelines capable of ingesting and + * processing documents, tables, and images in a consistent way. It handles reader selection, + * ordering, and fault-tolerance internally, ensuring that pipelines remain concise, robust, and + * easy to maintain. + * + * ==Example== + * {{{ + * import com.johnsnowlabs.reader.ReaderAssembler + * import com.johnsnowlabs.nlp.base.DocumentAssembler + * import org.apache.spark.ml.Pipeline + * + * val readerAssembler = new ReaderAssembler() + * .setContentType("text/html") + * .setContentPath(s"$htmlFilesDirectory/table-image.html") + * .setOutputCol("document") + * + * val pipeline = new Pipeline() + * .setStages(Array(readerAssembler)) + * + * val pipelineModel = pipeline.fit(emptyDataSet) + * val resultDf = pipelineModel.transform(emptyDataSet) + * + * resultDf.show() + * +--------+--------------------+--------------------+--------------------+---------+ + * |fileName| document_text| document_table| document_image|exception| + * +--------+--------------------+--------------------+--------------------+---------+ + * | null|[{document, 0, 26...|[{document, 0, 50...|[{image, , 5, 5, ...| null| + * +--------+--------------------+--------------------+--------------------+---------+ + * }}} + * + * This annotator is especially useful when working with heterogeneous input data — for example, + * when a dataset includes PDFs, spreadsheets, and images — allowing Spark NLP to automatically + * invoke the appropriate reader for each file type while preserving a unified schema in the + * output. + */ + +class ReaderAssembler(override val uid: String) + extends Transformer + with DefaultParamsWritable + with HasOutputAnnotatorType + with HasOutputAnnotationCol + with HasBinaryReaderProperties + with HasTextReaderProperties + with HasReaderContent { + + def this() = this(Identifiable.randomUID("ReaderAssembler")) + + val excludeNonText: BooleanParam = + new BooleanParam(this, "excludeNonText", "Excludes rows that are not text data. e.g. tables") + + /** Excludes rows that are not text data. e.g. tables */ + def setExcludeNonText(value: Boolean): this.type = set(excludeNonText, value) + + val userMessage: Param[String] = new Param[String](this, "userMessage", "custom user message") + + def setUserMessage(value: String): this.type = set(userMessage, value) + + val promptTemplate: Param[String] = + new Param[String](this, "promptTemplate", "format of the output prompt") + + def setPromptTemplate(value: String): this.type = set(promptTemplate, value) + + val customPromptTemplate: Param[String] = + new Param[String](this, "customPromptTemplate", "custom prompt template for image models") + + def setCustomPromptTemplate(value: String): this.type = set(promptTemplate, value) + + setDefault( + this.explodeDocs -> false, + contentType -> "", + outputFormat -> "json-table", + inferTableStructure -> true, + flattenOutput -> false, + excludeNonText -> false) + + private lazy val reader2DocOutputCol: String = s"${getOutputCol}_text" + private lazy val reader2TableOutputCol: String = s"${getOutputCol}_table" + private lazy val reader2ImageOutputCol: String = s"${getOutputCol}_image" + + override val supportedTypes: Map[String, (String, Boolean)] = Map( + "txt" -> ("text/plain", true), + "html" -> ("text/html", true), + "htm" -> ("text/html", true), + "md" -> ("text/markdown", true), + "xml" -> ("application/xml", true), + "csv" -> ("text/csv", true), + "pdf" -> ("application/pdf", false), + "doc" -> ("application/msword", false), + "docx" -> ("application/msword", false), + "xls" -> ("application/vnd.ms-excel", false), + "xlsx" -> ("application/vnd.ms-excel", false), + "ppt" -> ("application/vnd.ms-powerpoint", false), + "pptx" -> ("application/vnd.ms-powerpoint", false), + "eml" -> ("message/rfc822", false), + "msg" -> ("message/rfc822", false), + "png" -> ("image/raw", false), + "jpg" -> ("image/raw", false), + "jpeg" -> ("image/raw", false), + "bmp" -> ("image/raw", false), + "gif" -> ("image/raw", false), + "pdf" -> ("application/pdf", false)) + + override def transform(dataset: Dataset[_]): DataFrame = { + + val structureDf = + if (getInputCol != null && getInputCol.nonEmpty && isStringContent($(contentType))) { + processStringInputFromDataset(dataset) + } else { + $(contentType) match { + // Plain-text-like formats (txt, html, csv, etc.) + case ct if isStringContent(ct) => + partitionStringContent(dataset) + + // Known binary formats (pdf, doc, docx, pptx, etc.) + case ct if supportedTypes.exists { case (_, (mime, isText)) => + mime == ct && !isText + } => + partitionBinaryContent(dataset) + + // Default fallback: mixed directory (or unknown but supported) + case _ => + partitionMixedContentDir(dataset) + } + + } + + if (structureDf.isEmpty) { + structureDf + } else { + + val annotatedDf = structureDf + .withColumn( + reader2DocOutputCol, + wrapDocColumn( + reader2Doc.partitionToAnnotation(col("partition_text"), col("fileName")), + reader2DocOutputCol)) + .withColumn( + reader2TableOutputCol, + wrapTableColumn( + reader2Table.partitionToAnnotation(col("partition_table"), col("fileName")), + reader2TableOutputCol)) + .withColumn( + reader2ImageOutputCol, + wrapImageColumn( + reader2Image.partitionToAnnotation(col("partition_image"), col("fileName")), + reader2ImageOutputCol)) + + afterAnnotate(annotatedDf) + .select( + "fileName", + reader2DocOutputCol, + reader2TableOutputCol, + reader2ImageOutputCol, + "exception") + } + + } + + private def partitionStringContent(dataset: Dataset[_]): DataFrame = { + if ($(contentType) == "text/csv") { + val imageCol = typedLit(Seq()) + return partitionContentFromPath( + partitionTableBuilder, + $(contentPath), + isText = true, + dataset) + .withColumnRenamed("partition", "partition_text") + .withColumn("partition_table", col("partition_text")) + .withColumn("partition_image", imageCol) + } + + val contentDf = datasetWithTextFile(dataset.sparkSession, $(contentPath)) + + val partitionTextUDF = + udf((text: String) => + partitionTextBuilder.partitionStringContent(text, $(this.headers).asJava)) + val partitionTableUDF = + udf((text: String) => + partitionTableBuilder.partitionStringContent(text, $(this.headers).asJava)) + val partitionImageUDF = + udf((text: String) => + partitionImageBuilder.partitionStringContent(text, $(this.headers).asJava)) + + contentDf + .withColumn("partition_text", partitionTextUDF(col("content"))) + .withColumn("partition_table", partitionTableUDF(col("content"))) + .withColumn("partition_image", partitionImageUDF(col("content"))) + .withColumn("fileName", getFileName(col("path"))) + .withColumn("exception", lit(null: String)) + .drop("content") + } + + private def partitionBinaryContent(dataset: Dataset[_]): DataFrame = { + val contentDf = datasetWithBinaryFile(dataset.sparkSession, $(contentPath)) + + val partitionTextUDF = + udf((input: Array[Byte]) => partitionTextBuilder.partitionBytesContent(input)) + val partitionTableUDF = + udf((input: Array[Byte]) => partitionTableBuilder.partitionBytesContent(input)) + val partitionImageUDF = + udf((input: Array[Byte]) => partitionImageBuilder.partitionBytesContent(input)) + + contentDf + .withColumn("partition_text", partitionTextUDF(col("content"))) + .withColumn("partition_table", partitionTableUDF(col("content"))) + .withColumn("partition_image", partitionImageUDF(col("content"))) + .withColumn("fileName", getFileName(col("path"))) + .withColumn("exception", lit(null: String)) + .drop("content") + } + + def afterAnnotate(dataset: DataFrame): DataFrame = { + val reader2DocOutputCol = s"${getOutputCol}_text" + val reader2TableOutputCol = s"${getOutputCol}_table" + val reader2ImageOutputCol = s"${getOutputCol}_image" + + if ($(explodeDocs)) { + // helper function to explode and safely re-wrap one column + def explodeColumn(df: DataFrame, colName: String): DataFrame = { + if (df.columns.contains(colName)) { + val fieldMeta = df.schema.fields + .find(_.name == colName) + .map(_.metadata) + .getOrElse(new MetadataBuilder().build()) + df + .select( + df.columns.filterNot(_ == colName).map(col) :+ explode_outer(col(colName)).as( + "_tmp"): _*) + .withColumn(colName, array(col("_tmp")).as(colName, fieldMeta)) + .drop("_tmp") + } else { + df + } + } + + // Apply explode logic to all three columns if present + val explodedDf = Seq(reader2DocOutputCol, reader2TableOutputCol, reader2ImageOutputCol) + .foldLeft(dataset)((df, colName) => explodeColumn(df, colName)) + + explodedDf + } else { + dataset + } + } + + private def partitionTextBuilder: Partition = + buildPartition(Map("inferTableStructure" -> "false"), "partition") + + private def partitionTableBuilder: Partition = + buildPartition(Map("inferTableStructure" -> "true"), "partition_table") + + private def partitionImageBuilder: Partition = + buildPartition( + Map("inferTableStructure" -> "false", "readAsImage" -> "true"), + "partition_image") + + private def buildPartition( + overrides: Map[String, String], + outputCol: String, + contentType: Option[String] = None): Partition = { + val baseParams = Map( + "contentType" -> (if (contentType.isDefined) contentType.get else getContentType), + "storeContent" -> $(storeContent).toString, + "titleFontSize" -> $(titleFontSize).toString, + "includePageBreaks" -> $(includePageBreaks).toString, + "addAttachmentContent" -> $(addAttachmentContent).toString, + "cellSeparator" -> $(cellSeparator), + "appendCells" -> $(appendCells).toString, + "timeout" -> $(timeout).toString, + "includeSlideNotes" -> $(includeSlideNotes).toString, + "titleLengthSize" -> $(titleLengthSize).toString, + "groupBrokenParagraphs" -> $(groupBrokenParagraphs).toString, + "paragraphSplit" -> $(paragraphSplit), + "shortLineWordThreshold" -> $(shortLineWordThreshold).toString, + "maxLineCount" -> $(maxLineCount).toString, + "threshold" -> $(threshold).toString, + "xmlKeepTags" -> $(xmlKeepTags).toString, + "onlyLeafNodes" -> $(onlyLeafNodes).toString, + "titleThreshold" -> $(titleThreshold).toString, + "outputFormat" -> $(outputFormat)) + + val finalParams = baseParams ++ overrides + new Partition(finalParams.asJava).setOutputColumn(outputCol) + } + + override def copy(extra: ParamMap): Transformer = defaultCopy(extra) + + override val outputAnnotatorType: AnnotatorType = DOCUMENT + private val outputImageAnnotatorType: AnnotatorType = IMAGE + + private lazy val docColumnMetadata: Metadata = { + new MetadataBuilder().putString("annotatorType", outputAnnotatorType).build() + } + + private lazy val tableColumnMetadata: Metadata = { + new MetadataBuilder().putString("annotatorType", outputAnnotatorType).build() + } + + private lazy val imageColumnMetadata: Metadata = { + new MetadataBuilder().putString("annotatorType", outputImageAnnotatorType).build() + } + + private def wrapDocColumn(col: Column, outputCol: String): Column = { + col.as(outputCol, docColumnMetadata) + } + + private def wrapTableColumn(col: Column, outputCol: String): Column = { + col.as(outputCol, tableColumnMetadata) + } + + private def wrapImageColumn(col: Column, outputCol: String): Column = { + col.as(outputCol, imageColumnMetadata) + } + + protected def validateRequiredParameters(): Unit = { + val hasContentPath = $(contentPath) != null && $(contentPath).trim.nonEmpty + if (hasContentPath) { + require( + ResourceHelper.validFile($(contentPath)), + "contentPath must point to a valid file or directory") + } + } + + override def transformSchema(schema: StructType): StructType = { + val reader2DocOutputCol = s"${getOutputCol}_text" + val reader2TableOutputCol = s"${getOutputCol}_table" + val reader2ImageOutputCol = s"${getOutputCol}_image" + + val outputFields = Seq( + StructField( + reader2DocOutputCol, + ArrayType(Annotation.dataType), + nullable = false, + docColumnMetadata), + StructField( + reader2TableOutputCol, + ArrayType(Annotation.dataType), + nullable = false, + tableColumnMetadata), + StructField( + reader2ImageOutputCol, + ArrayType(AnnotationImage.dataType), + nullable = false, + imageColumnMetadata)) + + StructType(schema.fields ++ outputFields) + } + + private def partitionMixedContentDir(dataset: Dataset[_]): DataFrame = { + + val allFiles = listAllFilesRecursively(new File($(contentPath))).map(_.toString) + if (allFiles.isEmpty) { + return buildEmptyDataFrame(dataset) + } + + val grouped = allFiles + .groupBy { path => + val ext = path.substring(path.lastIndexOf('.') + 1).toLowerCase + if (supportedTypes.contains(ext)) Some(ext) + else if (! $(ignoreExceptions)) Some(s"__unsupported__$ext") + else None + } + .collect { case (Some(ext), files) => ext -> files } + + if (grouped.isEmpty) { + return buildEmptyDataFrame(dataset) + } + + val dfs = grouped.flatMap { + // Unsupported types → build error DataFrames + case (ext, files) if ext.startsWith("__unsupported__") => + val badExt = ext.stripPrefix("__unsupported__") + val dfs = files.map(path => buildErrorDataFrame(dataset, path, badExt)) + Some(dfs.reduce(_.unionByName(_, allowMissingColumns = true))) + + case (ext, files) if supportedTypes(ext)._1 == "text/csv" => + Some(processCsvFiles(dataset, files, ext)) + + case (ext, files) if Seq("png", "jpg", "jpeg", "bmp", "gif").contains(ext) => + Some(processImageFiles(dataset, files, ext)) + + // Default case → text or binary partitioning + case (ext, files) => + val (mimeType, isText) = supportedTypes(ext) + Some(processGenericFiles(dataset, files, mimeType, isText)) + }.toSeq + + if (dfs.isEmpty) buildEmptyDataFrame(dataset) + else dfs.reduce(_.unionByName(_, allowMissingColumns = true)) + } + + private def processGenericFiles( + dataset: Dataset[_], + files: Seq[String], + mimeType: String, + isText: Boolean): DataFrame = { + + val spark = dataset.sparkSession + val pathsStr = files.mkString(",") + + val partitionTextBuilder = + buildPartition( + Map("inferTableStructure" -> "false", "contentType" -> mimeType), + "partition_text") + val partitionTableBuilder = + buildPartition( + Map("inferTableStructure" -> "true", "contentType" -> mimeType), + "partition_table") + val partitionImageBuilder = + buildPartition( + Map("inferTableStructure" -> "false", "readAsImage" -> "true", "contentType" -> mimeType), + "partition_image") + + val baseDf = + if (isText) datasetWithTextFile(spark, pathsStr) + else datasetWithBinaryFile(spark, pathsStr) + + val textUdf = + if (isText) + udf((content: String) => + partitionTextBuilder.partitionStringContent(content, $(this.headers).asJava)) + else udf((bytes: Array[Byte]) => partitionTextBuilder.partitionBytesContent(bytes)) + + val tableUdf = + if (isText) + udf((content: String) => + partitionTableBuilder.partitionStringContent(content, $(this.headers).asJava)) + else udf((bytes: Array[Byte]) => partitionTableBuilder.partitionBytesContent(bytes)) + + val imageUdf = + if (isText) + udf((content: String) => + partitionImageBuilder.partitionStringContent(content, $(this.headers).asJava)) + else udf((bytes: Array[Byte]) => partitionImageBuilder.partitionBytesContent(bytes)) + + val df = baseDf + .withColumn("partition_text", textUdf(col("content"))) + .withColumn("partition_table", tableUdf(col("content"))) + .withColumn("partition_image", imageUdf(col("content"))) + .withColumn("fileName", getFileName(col("path"))) + .withColumn("exception", lit(null: String)) + .drop("content") + + if ($(ignoreExceptions)) df.filter(col("exception").isNull) else df + } + + private def processCsvFiles(dataset: Dataset[_], files: Seq[String], ext: String): DataFrame = { + val pathsStr = files.mkString(",") + val mimeType = supportedTypes(ext)._1 + val partitionTableBuilder = + buildPartition( + Map("inferTableStructure" -> "true", "contentType" -> mimeType), + "partition_table") + + val imageElement = Seq( + HTMLElement(ElementType.ERROR, s"Could not parse image", mutable.Map())) + val imageCol = typedLit(imageElement) + + val csvDf = partitionContentFromPath(partitionTableBuilder, pathsStr, isText = true, dataset) + .withColumnRenamed("partition", "partition_text") + .withColumn("partition_table", col("partition_text")) + .withColumn("partition_image", imageCol) + .withColumn("fileName", getFileName(col("path"))) + .withColumn("exception", lit(null: String)) + + if ($(ignoreExceptions)) csvDf.filter(col("exception").isNull) else csvDf + } + + private def processImageFiles( + dataset: Dataset[_], + files: Seq[String], + ext: String): DataFrame = { + val spark = dataset.sparkSession + val pathsStr = files.mkString(",") + val binaryDf = datasetWithBinaryFile(spark, pathsStr) + val imageUDF = udf((bytes: Array[Byte]) => { + val metadata = Map("format" -> ext) + Seq( + HTMLElement( + elementType = ElementType.IMAGE, + content = "", + metadata = scala.collection.mutable.Map(metadata.toSeq: _*), + binaryContent = Some(bytes))) + }) + + binaryDf + .withColumn("partition_image", imageUDF(col("content"))) + .withColumn("partition_text", typedLit(Seq.empty[HTMLElement])) + .withColumn("partition_table", typedLit(Seq.empty[HTMLElement])) + .withColumn("fileName", getFileName(col("path"))) + .withColumn("exception", lit(null: String)) + .drop("content") + } + + /** Process in-memory string inputs from a dataset column instead of file paths. This is similar + * to partitionContentFromDataFrame but handles text/table/image partitions. + */ + private def processStringInputFromDatasetV2(dataset: Dataset[_]): DataFrame = { + val mimeType = getContentType + val partitionTextBuilder = + buildPartition( + Map("inferTableStructure" -> "false", "contentType" -> mimeType), + "partition_text") + val partitionTableBuilder = + buildPartition( + Map("inferTableStructure" -> "true", "contentType" -> mimeType), + "partition_table") + + val textUdf = + udf((text: String) => + partitionTextBuilder.partitionStringContent(text, $(this.headers).asJava)) + val tableUdf = + udf((text: String) => + partitionTableBuilder.partitionStringContent(text, $(this.headers).asJava)) + + val emptyImageArray = typedLit(Seq.empty[HTMLElement]) + + dataset + .withColumn("partition_text", textUdf(col(getInputCol))) + .withColumn("partition_table", tableUdf(col(getInputCol))) + .withColumn("partition_image", emptyImageArray) + .withColumn("fileName", lit(null: String)) + .withColumn("exception", lit(null: String)) + } + + /** Handles cases where input is already in a column (string-based only). Supports text, tables, + * and embedded base64 images. + */ + private def processStringInputFromDataset(dataset: Dataset[_]): DataFrame = { + + val mimeType = getContentType + + val partitionTextBuilder = + buildPartition( + Map("inferTableStructure" -> "false", "contentType" -> mimeType), + "partition_text") + val partitionTableBuilder = + buildPartition( + Map("inferTableStructure" -> "true", "contentType" -> mimeType), + "partition_table") + val partitionImageBuilder = + buildPartition( + Map("inferTableStructure" -> "false", "readAsImage" -> "true", "contentType" -> mimeType), + "partition_image") + + val textUdf = + udf((text: String) => + partitionTextBuilder.partitionStringContent(text, $(this.headers).asJava)) + + val tableUdf = + udf((text: String) => + partitionTableBuilder.partitionStringContent(text, $(this.headers).asJava)) + + val imageUdf = + udf((text: String) => + partitionImageBuilder.partitionStringContent(text, $(this.headers).asJava)) + + dataset + .withColumn("partition_text", textUdf(col(getInputCol))) + .withColumn("partition_table", tableUdf(col(getInputCol))) + .withColumn("partition_image", imageUdf(col(getInputCol))) + .withColumn("fileName", lit(null: String)) + .withColumn("exception", lit(null: String)) + } + + override def buildErrorDataFrame( + dataset: Dataset[_], + filePath: String, + badExt: String): DataFrame = { + val spark = dataset.sparkSession + import spark.implicits._ + + val errorElement = Seq( + HTMLElement(ElementType.ERROR, s"Unsupported file type: .$badExt", mutable.Map())) + val errorCol = typedLit(errorElement) + + Seq(filePath) + .toDF("path") + .withColumn("fileName", getFileName(col("path"))) + .withColumn("partition_text", errorCol) + .withColumn("partition_table", errorCol) + .withColumn("partition_image", errorCol) + .withColumn("exception", lit(s"Unsupported file type: .$badExt")) + .select( + "path", + "partition_text", + "partition_table", + "partition_image", + "fileName", + "exception") + } + + private lazy val reader2Doc: Reader2Doc = new Reader2Doc() + .setContentType($(contentType)) + .setContentPath($(contentPath)) + .setExplodeDocs($(explodeDocs)) + .setExcludeNonText(true) + .setAddAttachmentContent($(addAttachmentContent)) + .setCellSeparator($(cellSeparator)) + .setAppendCells($(appendCells)) + .setTitleThreshold($(titleThreshold)) + .setIncludeSlideNotes($(includeSlideNotes)) + .setTitleLengthSize($(titleLengthSize)) + .setGroupBrokenParagraphs($(groupBrokenParagraphs)) + .setParagraphSplit($(paragraphSplit)) + .setShortLineWordThreshold($(shortLineWordThreshold)) + .setMaxLineCount($(maxLineCount)) + .setThreshold($(threshold)) + .setOutputCol(reader2DocOutputCol) + + private lazy val reader2Table: Reader2Table = new Reader2Table() + .setContentType($(contentType)) + .setContentPath($(contentPath)) + .setExplodeDocs($(explodeDocs)) + .setAddAttachmentContent($(addAttachmentContent)) + .setCellSeparator($(cellSeparator)) + .setAppendCells($(appendCells)) + .setTitleThreshold($(titleThreshold)) + .setIncludeSlideNotes($(includeSlideNotes)) + .setTitleLengthSize($(titleLengthSize)) + .setGroupBrokenParagraphs($(groupBrokenParagraphs)) + .setParagraphSplit($(paragraphSplit)) + .setShortLineWordThreshold($(shortLineWordThreshold)) + .setMaxLineCount($(maxLineCount)) + .setThreshold($(threshold)) + .setOutputCol(reader2TableOutputCol) + + private lazy val reader2Image: Reader2Image = new Reader2Image() + .setContentType($(contentType)) + .setContentPath($(contentPath)) + .setExplodeDocs($(explodeDocs)) + .setAddAttachmentContent($(addAttachmentContent)) + .setCellSeparator($(cellSeparator)) + .setAppendCells($(appendCells)) + .setTitleThreshold($(titleThreshold)) + .setOutputCol(reader2ImageOutputCol) + +} diff --git a/src/main/scala/com/johnsnowlabs/reader/util/HasPdfProperties.scala b/src/main/scala/com/johnsnowlabs/reader/util/HasPdfToTextProperties.scala similarity index 94% rename from src/main/scala/com/johnsnowlabs/reader/util/HasPdfProperties.scala rename to src/main/scala/com/johnsnowlabs/reader/util/HasPdfToTextProperties.scala index df51dde332e4de..97b4578709f288 100644 --- a/src/main/scala/com/johnsnowlabs/reader/util/HasPdfProperties.scala +++ b/src/main/scala/com/johnsnowlabs/reader/util/HasPdfToTextProperties.scala @@ -19,7 +19,7 @@ import com.johnsnowlabs.nlp.ParamsAndFeaturesWritable import com.johnsnowlabs.reader.util.pdf.TextStripperType import org.apache.spark.ml.param.{BooleanParam, IntParam, Param} -trait HasPdfProperties extends ParamsAndFeaturesWritable { +trait HasPdfToTextProperties extends ParamsAndFeaturesWritable { final val pageNumCol = new Param[String](this, "pageNumCol", "Page number output column name.") final val originCol = @@ -74,8 +74,6 @@ trait HasPdfProperties extends ParamsAndFeaturesWritable { /** @group setParam */ def setNormalizeLigatures(value: Boolean): this.type = set(normalizeLigatures, value) - final val readAsImage = new BooleanParam(this, "readAsImage", "Read PDF pages as images.") - setDefault( pageNumCol -> "pagenum", originCol -> "path", @@ -86,7 +84,6 @@ trait HasPdfProperties extends ParamsAndFeaturesWritable { sort -> false, textStripper -> TextStripperType.PDF_TEXT_STRIPPER, extractCoordinates -> false, - normalizeLigatures -> true, - readAsImage -> false) + normalizeLigatures -> true) } diff --git a/src/test/resources/reader/doc/doc-img-table.docx b/src/test/resources/reader/doc/doc-img-table.docx new file mode 100644 index 0000000000000000000000000000000000000000..2686cfe36a07cfc3716cf712c91301d9f037ad5e GIT binary patch literal 114020 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 + + + Image Parsing Test + + +

Test Images

+ + + + + + + + + + + + + + + + + + +
Student Grades
NameSubjectGrade
AliceMathA
BobScienceB+
+

+ A base64 image after the table is below +

+ + +Base64 Red Dot + + + diff --git a/src/test/resources/reader/pdf/pdf-with-2images.pdf b/src/test/resources/reader/pdf/pdf-with-2images.pdf new file mode 100644 index 0000000000000000000000000000000000000000..d03d5b91eacecf6bd7b35187d463532e3f8a372c GIT binary patch literal 130182 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 FN~s<(m+~^O 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 literal 0 HcmV?d00001 diff --git a/src/test/scala/com/johnsnowlabs/reader/Reader2DocTest.scala b/src/test/scala/com/johnsnowlabs/reader/Reader2DocTest.scala index 63d6acea333434..5dbbc9085827fe 100644 --- a/src/test/scala/com/johnsnowlabs/reader/Reader2DocTest.scala +++ b/src/test/scala/com/johnsnowlabs/reader/Reader2DocTest.scala @@ -145,7 +145,7 @@ class Reader2DocTest extends AnyFlatSpec with SparkSessionTest { val pipelineModel = pipeline.fit(emptyDataSet) val resultDf = pipelineModel.transform(emptyDataSet) - resultDf.show() + assert(resultDf.count() == 1) } @@ -213,7 +213,7 @@ class Reader2DocTest extends AnyFlatSpec with SparkSessionTest { val pipeline = new Pipeline().setStages(Array(reader2Doc)) val pipelineModel = pipeline.fit(emptyDataSet) - val ex = intercept[InvalidInputException ] { + val ex = intercept[InvalidInputException] { pipelineModel.transform(emptyDataSet) } ex.getMessage.contains("contentPath must point to a valid file or directory") @@ -319,6 +319,7 @@ class Reader2DocTest extends AnyFlatSpec with SparkSessionTest { assert(annotations.head.metadata("elementType") != ElementType.IMAGE) } } + it should "validate invalid paths" taggedAs SlowTest in { val reader2Doc = new Reader2Doc() @@ -362,8 +363,6 @@ class Reader2DocTest extends AnyFlatSpec with SparkSessionTest { val txtDf = spark.createDataFrame(Seq((1, content))).toDF("id", "txt") - txtDf.show(truncate = false) - val reader2Doc = new Reader2Doc() .setInputCol("txt") .setOutputCol("document") @@ -371,7 +370,7 @@ class Reader2DocTest extends AnyFlatSpec with SparkSessionTest { val pipeline = new Pipeline().setStages(Array(reader2Doc)) val resultDf = pipeline.fit(txtDf).transform(txtDf) - resultDf.show(truncate = false) + assert(resultDf.count() > 0) } it should "proces HTML style from a spark dataframe" taggedAs FastTest in { @@ -395,8 +394,6 @@ class Reader2DocTest extends AnyFlatSpec with SparkSessionTest { val txtDf = spark.createDataFrame(Seq((1, content))).toDF("id", "html") - txtDf.show(truncate = false) - val reader2Doc = new Reader2Doc() .setInputCol("html") .setOutputCol("raw-html") @@ -405,7 +402,7 @@ class Reader2DocTest extends AnyFlatSpec with SparkSessionTest { val pipeline = new Pipeline().setStages(Array(reader2Doc)) val resultDf = pipeline.fit(txtDf).transform(txtDf) - resultDf.show(truncate = false) + assert(resultDf.count() > 0) } it should "be fault-tolerant for HTML content" taggedAs FastTest in { @@ -415,8 +412,6 @@ class Reader2DocTest extends AnyFlatSpec with SparkSessionTest { val htmlDf = spark.createDataFrame(Seq((1, content))).toDF("id", "html") - htmlDf.show(truncate = false) - val reader2Doc = new Reader2Doc() .setInputCol("html") .setOutputCol("document") @@ -425,7 +420,7 @@ class Reader2DocTest extends AnyFlatSpec with SparkSessionTest { val pipeline = new Pipeline().setStages(Array(reader2Doc)) val resultDf = pipeline.fit(htmlDf).transform(htmlDf) - resultDf.show(truncate = false) + assert(resultDf.count() > 0) } it should "parse attributes inside XML files" taggedAs FastTest in { @@ -446,5 +441,4 @@ class Reader2DocTest extends AnyFlatSpec with SparkSessionTest { assert(attributeElements.length > 0, "Expected to find attribute elements in the XML content") } - } diff --git a/src/test/scala/com/johnsnowlabs/reader/Reader2ImageTest.scala b/src/test/scala/com/johnsnowlabs/reader/Reader2ImageTest.scala index 39bc86e45319cc..efee327acffdc1 100644 --- a/src/test/scala/com/johnsnowlabs/reader/Reader2ImageTest.scala +++ b/src/test/scala/com/johnsnowlabs/reader/Reader2ImageTest.scala @@ -48,7 +48,7 @@ class Reader2ImageTest extends AnyFlatSpec with SparkSessionTest { val pipelineModel = pipeline.fit(emptyDataSet) val resultDf = pipelineModel.transform(emptyDataSet) - resultDf.show() + val annotationsResult = AssertAnnotations.getActualImageResult(resultDf, "image") assert(annotationsResult.length == 2) @@ -494,6 +494,58 @@ class Reader2ImageTest extends AnyFlatSpec with SparkSessionTest { assert(imagesDf.isEmpty) } + it should "extract images from PDF" taggedAs FastTest in { + val sourceFile = "pdf-with-2images.pdf" + val reader2Image = new Reader2Image() + .setContentPath(s"$pdfDirectory/$sourceFile") + .setContentType("application/pdf") + .setOutputCol("image") + + val pipeline = new Pipeline().setStages(Array(reader2Image)) + + val pipelineModel = pipeline.fit(emptyDataSet) + val resultDf = pipelineModel.transform(emptyDataSet) + resultDf.show() + val annotationsResult = AssertAnnotations.getActualImageResult(resultDf, "image") + + assert(annotationsResult.length == 2) + annotationsResult.foreach { annotations => + assert(annotations.head.annotatorType == AnnotatorType.IMAGE) + assert(annotations.head.origin == sourceFile) + assert(annotations.head.result.nonEmpty) + assert(annotations.head.height > 0) + assert(annotations.head.width > 0) + assert(annotations.head.nChannels > 0) + assert(annotations.head.mode > 0) + assert(annotations.head.metadata.nonEmpty) + } + } + + it should "integrate PDF images output with VLM models" taggedAs SlowTest in { + val sourceFile = "pdf-with-2images.pdf" + val reader2Image = new Reader2Image() + .setContentPath(s"$pdfDirectory/$sourceFile") + .setContentType("application/pdf") + .setOutputCol("image") + + val pipeline = new Pipeline().setStages(Array(reader2Image)) + val pipelineModel = pipeline.fit(emptyDataSet) + val imagesDf = pipelineModel.transform(emptyDataSet) + imagesDf.show() + + val visualQAClassifier = Qwen2VLTransformer + .pretrained() + .setInputCols("image") + .setOutputCol("answer") + + val vlmPipeline = new Pipeline().setStages(Array(visualQAClassifier)) + val resultDf = vlmPipeline.fit(imagesDf).transform(imagesDf) + + resultDf.select("image.origin", "answer.result").show(truncate = false) + + assert(!resultDf.isEmpty) + } + def getSupportedFiles(dirPath: String): Seq[String] = { val supportedExtensions = Seq(".html", ".htm", ".md", "doc", "docx") diff --git a/src/test/scala/com/johnsnowlabs/reader/Reader2TableTest.scala b/src/test/scala/com/johnsnowlabs/reader/Reader2TableTest.scala index 647ec7185e4b77..5d0d40dc681a2a 100644 --- a/src/test/scala/com/johnsnowlabs/reader/Reader2TableTest.scala +++ b/src/test/scala/com/johnsnowlabs/reader/Reader2TableTest.scala @@ -464,4 +464,20 @@ class Reader2TableTest extends AnyFlatSpec with SparkSessionTest { } } + it should "work for csv files" taggedAs FastTest in { + val csvDirectory = "src/test/resources/reader/csv" + val reader2Table = new Reader2Table() + .setContentType("text/csv") + .setContentPath(s"$csvDirectory/stanley-cups.csv") + .setOutputCol("document") + .setExplodeDocs(true) + + val pipeline = new Pipeline().setStages(Array(reader2Table)) + + val pipelineModel = pipeline.fit(emptyDataSet) + val resultDf = pipelineModel.transform(emptyDataSet) + resultDf.show(truncate = false) + // assert(resultDf.count() == 1) + } + } diff --git a/src/test/scala/com/johnsnowlabs/reader/ReaderAssemblerTest.scala b/src/test/scala/com/johnsnowlabs/reader/ReaderAssemblerTest.scala new file mode 100644 index 00000000000000..e7fc7f2dfa318c --- /dev/null +++ b/src/test/scala/com/johnsnowlabs/reader/ReaderAssemblerTest.scala @@ -0,0 +1,246 @@ +/* + * Copyright 2017-2025 John Snow Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.johnsnowlabs.reader + +import com.johnsnowlabs.nlp.AssertAnnotations +import com.johnsnowlabs.nlp.annotators.SparkSessionTest +import com.johnsnowlabs.nlp.annotators.cv.Qwen2VLTransformer +import com.johnsnowlabs.tags.{FastTest, SlowTest} +import org.apache.spark.ml.Pipeline +import org.scalatest.flatspec.AnyFlatSpec + +class ReaderAssemblerTest extends AnyFlatSpec with SparkSessionTest { + + val filesDirectory = "src/test/resources/reader/" + val htmlFilesDirectory = "src/test/resources/reader/html" + val docDirectory = "src/test/resources/reader/doc" + val csvDirectory = "src/test/resources/reader/csv" + val pdfDirectory = "src/test/resources/reader/pdf/" + + "ReaderAssembler" should "read HTML files" taggedAs FastTest in { + val reader = new ReaderAssembler() + .setContentType("text/html") + .setContentPath(s"$htmlFilesDirectory/table-image.html") + .setOutputCol("document") + + val pipeline = new Pipeline().setStages(Array(reader)) + val resultDf = pipeline.fit(emptyDataSet).transform(emptyDataSet) + + val textResult = AssertAnnotations.getActualResult(resultDf, "document_text") + val tableResult = AssertAnnotations.getActualResult(resultDf, "document_table") + val imageResult = AssertAnnotations.getActualImageResult(resultDf, "document_image") + val actualText = textResult.filter(annotation => annotation.nonEmpty) + val actualTable = tableResult.filter(annotation => annotation.nonEmpty) + val actualImages = imageResult.filter(annotation => annotation.nonEmpty) + + assert(actualText.nonEmpty) + assert(actualTable.nonEmpty) + assert(actualImages.nonEmpty) + } + + it should "work for word documents" taggedAs FastTest in { + val reader = new ReaderAssembler() + .setContentType("application/msword") + .setContentPath(s"$docDirectory/doc-img-table.docx") + .setOutputCol("document") + + val pipeline = new Pipeline().setStages(Array(reader)) + val resultDf = pipeline.fit(emptyDataSet).transform(emptyDataSet) + + val textResult = AssertAnnotations.getActualResult(resultDf, "document_text") + val tableResult = AssertAnnotations.getActualResult(resultDf, "document_table") + val imageResult = AssertAnnotations.getActualImageResult(resultDf, "document_image") + val actualText = textResult.filter(annotation => annotation.nonEmpty) + val actualTable = tableResult.filter(annotation => annotation.nonEmpty) + val actualImages = imageResult.filter(annotation => annotation.nonEmpty) + + assert(actualText.nonEmpty) + assert(actualTable.nonEmpty) + assert(actualImages.nonEmpty) + } + + it should "work for csv files" taggedAs FastTest in { + val reader = new ReaderAssembler() + .setContentType("text/csv") + .setContentPath(s"$csvDirectory/stanley-cups.csv") + .setOutputCol("document") + + val pipeline = new Pipeline().setStages(Array(reader)) + val resultDf = pipeline.fit(emptyDataSet).transform(emptyDataSet) + + val textResult = AssertAnnotations.getActualResult(resultDf, "document_text") + val tableResult = AssertAnnotations.getActualResult(resultDf, "document_table") + val imageResult = AssertAnnotations.getActualImageResult(resultDf, "document_image") + val actualText = textResult.filter(annotation => annotation.nonEmpty) + val actualTable = tableResult.filter(annotation => annotation.nonEmpty) + val actualImages = imageResult.filter(annotation => annotation.nonEmpty) + + assert(actualText.nonEmpty) + assert(actualTable.nonEmpty) + assert(actualImages.isEmpty) + } + + it should "integrate HTML files with VLM models" taggedAs SlowTest in { + val reader = new ReaderAssembler() + .setContentType("text/html") + .setContentPath(s"$htmlFilesDirectory/table-image.html") + .setOutputCol("document") + + val pipeline = new Pipeline().setStages(Array(reader)) + val readerDf = pipeline.fit(emptyDataSet).transform(emptyDataSet) + readerDf.show() + readerDf.printSchema() + + val visualQAClassifier = Qwen2VLTransformer + .pretrained() + .setInputCols("document_image") + .setOutputCol("answer") + + val vlmPipeline = new Pipeline().setStages(Array(visualQAClassifier)) + val resultDf = vlmPipeline.fit(readerDf).transform(readerDf) + + resultDf.select("document_image.origin", "answer.result").show(truncate = false) + + } + + it should "integrate Word files with VLM models" taggedAs SlowTest in { + val reader = new ReaderAssembler() + .setContentType("application/msword") + .setContentPath(s"$docDirectory/doc-img-table.docx") + .setOutputCol("document") + + val pipeline = new Pipeline().setStages(Array(reader)) + val readerDf = pipeline.fit(emptyDataSet).transform(emptyDataSet) + readerDf.show() + readerDf.printSchema() + + val visualQAClassifier = Qwen2VLTransformer + .pretrained() + .setInputCols("document_image") + .setOutputCol("answer") + + val vlmPipeline = new Pipeline().setStages(Array(visualQAClassifier)) + val resultDf = vlmPipeline.fit(readerDf).transform(readerDf) + + resultDf.select("document_image.origin", "answer.result").show(truncate = false) + } + + it should "read PDF files" taggedAs FastTest in { + val reader = new ReaderAssembler() + .setContentType("application/pdf") + .setContentPath(s"$pdfDirectory/pdf-with-2images.pdf") + .setOutputCol("document") + + val pipeline = new Pipeline().setStages(Array(reader)) + val resultDf = pipeline.fit(emptyDataSet).transform(emptyDataSet) + + val textResult = AssertAnnotations.getActualResult(resultDf, "document_text") + val tableResult = AssertAnnotations.getActualResult(resultDf, "document_table") + val imageResult = AssertAnnotations.getActualImageResult(resultDf, "document_image") + val actualText = textResult.filter(annotation => annotation.nonEmpty) + val actualTable = tableResult.filter(annotation => annotation.nonEmpty) + val actualImages = imageResult.filter(annotation => annotation.nonEmpty) + + assert(actualText.nonEmpty) + assert(actualTable.isEmpty) + assert(actualImages.nonEmpty) + } + + it should "integrate PDF files with VLM models" taggedAs SlowTest in { + val reader = new ReaderAssembler() + .setContentType("application/pdf") + .setContentPath(s"$pdfDirectory/pdf-with-2images.pdf") + .setOutputCol("document") + + val pipeline = new Pipeline().setStages(Array(reader)) + val readerDf = pipeline.fit(emptyDataSet).transform(emptyDataSet) + readerDf.show() + readerDf.printSchema() + + val visualQAClassifier = Qwen2VLTransformer + .pretrained() + .setInputCols("document_image") + .setOutputCol("answer") + + val vlmPipeline = new Pipeline().setStages(Array(visualQAClassifier)) + val resultDf = vlmPipeline.fit(readerDf).transform(readerDf) + + resultDf.select("document_image.origin", "answer.result").show(truncate = false) + } + + it should "read from a directory" taggedAs FastTest in { + val reader = new ReaderAssembler() + .setContentPath(s"$filesDirectory") + .setOutputCol("document") + + val pipeline = new Pipeline().setStages(Array(reader)) + val resultDf = pipeline.fit(emptyDataSet).transform(emptyDataSet) + + val textResult = AssertAnnotations.getActualResult(resultDf, "document_text") + val tableResult = AssertAnnotations.getActualResult(resultDf, "document_table") + val imageResult = AssertAnnotations.getActualImageResult(resultDf, "document_image") + val actualText = textResult.filter(annotation => annotation.nonEmpty) + val actualTable = tableResult.filter(annotation => annotation.nonEmpty) + val actualImages = imageResult.filter(annotation => annotation.nonEmpty) + + assert(actualText.nonEmpty) + assert(actualTable.nonEmpty) + assert(actualImages.nonEmpty) + } + + it should "work for inputCol" taggedAs FastTest in { + + val content: String = + """ + | + |

This is a normal paragraph.

+ | + | + | + | + |
Hello World
+ | Red dot + | + |""".stripMargin + + val htmlDf = spark.createDataFrame(Seq((1, content))).toDF("id", "html") + + val reader = new ReaderAssembler() + .setInputCol("html") + .setContentType("text/html") + .setOutputCol("document") + + val pipeline = new Pipeline().setStages(Array(reader)) + val resultDf = pipeline.fit(htmlDf).transform(htmlDf) + + resultDf.show() + + val textResult = AssertAnnotations.getActualResult(resultDf, "document_text") + val tableResult = AssertAnnotations.getActualResult(resultDf, "document_table") + val imageResult = AssertAnnotations.getActualImageResult(resultDf, "document_image") + val actualText = textResult.filter(annotation => annotation.nonEmpty) + val actualTable = tableResult.filter(annotation => annotation.nonEmpty) + val actualImages = imageResult.filter(annotation => annotation.nonEmpty) + + assert(actualText.nonEmpty) + assert(actualTable.nonEmpty) + assert(actualImages.nonEmpty) + } + +} From 6e888bd1ebb1e4f0004036f7e2860bd2529df6f1 Mon Sep 17 00:00:00 2001 From: Christian Kasim Loan Date: Wed, 8 Oct 2025 15:39:58 +0200 Subject: [PATCH 5/7] [SPARKNLP-1296] Improve AutoGGUF FallbackReader (#14667) * fix duplicate loading in FeaturesFallbackReader * Improve fallback loader - clearer exception case handling - fixes issues when loading in Databricks --------- Co-authored-by: Devin Ha --- .../nlp/ParamsAndFeaturesReadable.scala | 25 ++++++++----------- .../annotators/seq2seq/AutoGGUFModel.scala | 8 +++--- .../annotators/seq2seq/AutoGGUFReranker.scala | 11 ++++---- .../seq2seq/AutoGGUFVisionModel.scala | 4 ++- .../nlp/embeddings/AutoGGUFEmbeddings.scala | 3 ++- .../nlp/util/io/ResourceHelper.scala | 20 +++++++++++++++ .../seq2seq/AutoGGUFRerankerTest.scala | 9 +++++-- .../seq2seq/AutoGGUFVisionModelTestSpec.scala | 13 +++++++--- .../AutoGGUFEmbeddingsTestSpec.scala | 10 +++++--- 9 files changed, 68 insertions(+), 35 deletions(-) diff --git a/src/main/scala/com/johnsnowlabs/nlp/ParamsAndFeaturesReadable.scala b/src/main/scala/com/johnsnowlabs/nlp/ParamsAndFeaturesReadable.scala index fa0ee9596b206e..2cb3d505da3dd5 100644 --- a/src/main/scala/com/johnsnowlabs/nlp/ParamsAndFeaturesReadable.scala +++ b/src/main/scala/com/johnsnowlabs/nlp/ParamsAndFeaturesReadable.scala @@ -88,29 +88,24 @@ class FeaturesFallbackReader[T <: HasFeatures]( override def load(path: String): T = { Try { - // Read params, features and model - val instance = baseReader.load(path) - loadFeatures(path, instance) - onRead(instance, path, sparkSession) - - instance + // Read params, features and model via FeaturesReader.load + baseReader.load(path) } match { - case Failure(_) => - // TODO: Logger warn instead? + case Success(value) => value + case Failure(_: java.util.NoSuchElementException) => println( s"Failed to load all parameters from $path, attempting fallback loader. " + s"Parameters will be set to default values.") fallbackLoad(path, sparkSession) - case Success(value) => value + case Failure(_: java.lang.ClassCastException) => + println( + s"Failed to cast to class of $path, attempting fallback loader. " + + s"Parameters will be set to default values.") + fallbackLoad(path, sparkSession) + case Failure(exception) => throw exception } } - private def loadFeatures(path: String, instance: T): Unit = { - for (feature <- instance.features) { - val value = feature.deserialize(sparkSession, path, feature.name) - feature.setValue(value) - } - } } /** Enables loading models with params and features with a fallback mechanism. The `fallbackLoad` diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFModel.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFModel.scala index e72f2afa27c43e..62001f5acedf0d 100644 --- a/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFModel.scala +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFModel.scala @@ -16,6 +16,7 @@ package com.johnsnowlabs.nlp.annotators.seq2seq import com.johnsnowlabs.ml.gguf.GGUFWrapper +import com.johnsnowlabs.ml.gguf.GGUFWrapper.findGGUFModelInFolder import com.johnsnowlabs.ml.util.LlamaCPP import com.johnsnowlabs.nlp._ import com.johnsnowlabs.nlp.llama.LlamaExtensions @@ -240,9 +241,10 @@ trait ReadAutoGGUFModel { this: ParamsAndFeaturesFallbackReadable[AutoGGUFModel] => override def fallbackLoad(folder: String, spark: SparkSession): AutoGGUFModel = { - val localFolder: String = ResourceHelper.copyToLocal(folder) - val ggufFile = GGUFWrapper.findGGUFModelInFolder(localFolder) - loadSavedModel(ggufFile, spark) + val actualFolderPath: String = ResourceHelper.resolvePath(folder) + val localFolder = ResourceHelper.copyToLocal(actualFolderPath) + val modelFile = findGGUFModelInFolder(localFolder) + loadSavedModel(modelFile, spark) } def readModel(instance: AutoGGUFModel, path: String, spark: SparkSession): Unit = { diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFReranker.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFReranker.scala index f916ad82a071d2..457d5e81d32737 100644 --- a/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFReranker.scala +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFReranker.scala @@ -20,15 +20,13 @@ import com.johnsnowlabs.ml.util.LlamaCPP import com.johnsnowlabs.nlp._ import com.johnsnowlabs.nlp.llama.LlamaExtensions import com.johnsnowlabs.nlp.util.io.ResourceHelper -import de.kherud.llama.{InferenceParameters, LlamaException, LlamaModel, Pair} +import de.kherud.llama.{LlamaException, LlamaModel, Pair} import org.apache.spark.broadcast.Broadcast +import org.apache.spark.ml.param.Param import org.apache.spark.ml.util.Identifiable import org.apache.spark.sql.SparkSession -import org.apache.spark.ml.param.Param -import scala.jdk.CollectionConverters._ -import java.util -import java.util.{ArrayList, List} +import scala.jdk.CollectionConverters._ /** Annotator that uses the llama.cpp library to rerank text documents based on their relevance to * a given query using GGUF-format reranking models. @@ -271,7 +269,8 @@ trait ReadAutoGGUFReranker { this: ParamsAndFeaturesFallbackReadable[AutoGGUFReranker] => override def fallbackLoad(folder: String, spark: SparkSession): AutoGGUFReranker = { - val localFolder: String = ResourceHelper.copyToLocal(folder) + val actualFolderPath: String = ResourceHelper.resolvePath(folder) + val localFolder = ResourceHelper.copyToLocal(actualFolderPath) val ggufFile = GGUFWrapper.findGGUFModelInFolder(localFolder) loadSavedModel(ggufFile, spark) } diff --git a/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFVisionModel.scala b/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFVisionModel.scala index f284eab38930e2..ec2e9989c5b3b6 100644 --- a/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFVisionModel.scala +++ b/src/main/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFVisionModel.scala @@ -289,7 +289,9 @@ trait ReadAutoGGUFVisionModel { this: ParamsAndFeaturesFallbackReadable[AutoGGUFVisionModel] => override def fallbackLoad(folder: String, spark: SparkSession): AutoGGUFVisionModel = { - val localFolder: String = ResourceHelper.copyToLocal(folder) + val actualFolderPath: String = ResourceHelper.resolvePath(folder) + + val localFolder = ResourceHelper.copyToLocal(actualFolderPath) val (ggufFile, mmprojFile) = GGUFWrapperMultiModal.findGGUFModelsInFolder(localFolder) loadSavedModel(ggufFile, mmprojFile, spark) } diff --git a/src/main/scala/com/johnsnowlabs/nlp/embeddings/AutoGGUFEmbeddings.scala b/src/main/scala/com/johnsnowlabs/nlp/embeddings/AutoGGUFEmbeddings.scala index 75d1ded2484eab..093f2f338bb440 100644 --- a/src/main/scala/com/johnsnowlabs/nlp/embeddings/AutoGGUFEmbeddings.scala +++ b/src/main/scala/com/johnsnowlabs/nlp/embeddings/AutoGGUFEmbeddings.scala @@ -284,7 +284,8 @@ trait ReadAutoGGUFEmbeddings { this: ParamsAndFeaturesFallbackReadable[AutoGGUFEmbeddings] => override def fallbackLoad(folder: String, spark: SparkSession): AutoGGUFEmbeddings = { - val localFolder: String = ResourceHelper.copyToLocal(folder) + val actualFolderPath: String = ResourceHelper.resolvePath(folder) + val localFolder = ResourceHelper.copyToLocal(actualFolderPath) val ggufFile = GGUFWrapper.findGGUFModelInFolder(localFolder) loadSavedModel(ggufFile, spark) } diff --git a/src/main/scala/com/johnsnowlabs/nlp/util/io/ResourceHelper.scala b/src/main/scala/com/johnsnowlabs/nlp/util/io/ResourceHelper.scala index d628cb0d9c3060..153d0604c4f62d 100644 --- a/src/main/scala/com/johnsnowlabs/nlp/util/io/ResourceHelper.scala +++ b/src/main/scala/com/johnsnowlabs/nlp/util/io/ResourceHelper.scala @@ -747,10 +747,30 @@ object ResourceHelper { } } + /** Get the Hadoop FileSystem from a given path + * + * @param path + * Path to the resource + * @return + * Hadoop FileSystem + */ def fileSystemFromPath(path: String): FileSystem = { val uri = new URI(path.replaceAllLiterally("\\", "/")) FileSystem.get(uri, spark.sparkContext.hadoopConfiguration) } + + /** Resolves the given path to its absolute form, handling different file systems. + * + * @param folder + * The input path to resolve. + * @return + * The resolved absolute path as a string. + */ + def resolvePath(folder: String): String = { + val fileSystem: FileSystem = ResourceHelper.fileSystemFromPath(folder) + fileSystem.resolvePath(new Path(folder)).toString + } + def isHTTPProtocol(urlStr: String): Boolean = { try { val url = new URL(urlStr) diff --git a/src/test/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFRerankerTest.scala b/src/test/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFRerankerTest.scala index 532d9a921967c8..a1bef38ed9767a 100644 --- a/src/test/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFRerankerTest.scala +++ b/src/test/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFRerankerTest.scala @@ -1,10 +1,10 @@ package com.johnsnowlabs.nlp.annotators.seq2seq import com.johnsnowlabs.nlp.Annotation -import com.johnsnowlabs.nlp.finisher.GGUFRankingFinisher import com.johnsnowlabs.nlp.base.DocumentAssembler +import com.johnsnowlabs.nlp.finisher.GGUFRankingFinisher import com.johnsnowlabs.nlp.util.io.ResourceHelper -import com.johnsnowlabs.tags.{SlowTest, FastTest} +import com.johnsnowlabs.tags.SlowTest import org.apache.spark.ml.Pipeline import org.apache.spark.sql.{DataFrame, Dataset, Row} import org.scalatest.flatspec.AnyFlatSpec @@ -135,4 +135,9 @@ class AutoGGUFRerankerTest extends AnyFlatSpec { // assertAnnotationsNonEmpty(result) result.select("ranked_documents").show(truncate = false) } + + it should "load models with deprecated parameters" taggedAs SlowTest in { + // testing only, should be able to load + AutoGGUFReranker.pretrained("Nomic_Embed_Text_v1.5.Q8_0.gguf") + } } diff --git a/src/test/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFVisionModelTestSpec.scala b/src/test/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFVisionModelTestSpec.scala index f5318f9785d985..24d0146cde9a57 100644 --- a/src/test/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFVisionModelTestSpec.scala +++ b/src/test/scala/com/johnsnowlabs/nlp/annotators/seq2seq/AutoGGUFVisionModelTestSpec.scala @@ -15,11 +15,11 @@ class AutoGGUFVisionModelTestSpec extends AnyFlatSpec { behavior of "AutoGGUFVisionModel" - lazy val documentAssembler = new DocumentAssembler() + lazy val documentAssembler: DocumentAssembler = new DocumentAssembler() .setInputCol("caption") .setOutputCol("caption_document") - lazy val imageAssembler = new ImageAssembler() + lazy val imageAssembler: ImageAssembler = new ImageAssembler() .setInputCol("image") .setOutputCol("image_assembler") @@ -44,7 +44,7 @@ class AutoGGUFVisionModelTestSpec extends AnyFlatSpec { "tractor.JPEG" -> "tractor") lazy val nPredict = 40 - lazy val model = AutoGGUFVisionModel + lazy val model: AutoGGUFVisionModel = AutoGGUFVisionModel // .loadSavedModel( // "models/Qwen2.5-VL-3B-Instruct-Q4_K_M.gguf", // "models/mmproj-Qwen2.5-VL-3B-Instruct-Q8_0.gguf", @@ -64,7 +64,8 @@ class AutoGGUFVisionModelTestSpec extends AnyFlatSpec { .setTopK(40) .setTopP(0.95f) - lazy val pipeline = new Pipeline().setStages(Array(documentAssembler, imageAssembler, model)) + lazy val pipeline: Pipeline = + new Pipeline().setStages(Array(documentAssembler, imageAssembler, model)) def checkBinaryContents(): Unit = { val imageData = data.select("image.data").limit(1).collect()(0).getAs[Array[Byte]](0) @@ -135,4 +136,8 @@ class AutoGGUFVisionModelTestSpec extends AnyFlatSpec { AutoGGUFVisionModel.load(savePath) } + + it should "load models with deprecated parameters" taggedAs SlowTest in { + AutoGGUFVisionModel.pretrained("llava_v1.5_7b_Q4_0_gguf") + } } diff --git a/src/test/scala/com/johnsnowlabs/nlp/embeddings/AutoGGUFEmbeddingsTestSpec.scala b/src/test/scala/com/johnsnowlabs/nlp/embeddings/AutoGGUFEmbeddingsTestSpec.scala index e73519c4798bf4..9331fb23ba9bc6 100644 --- a/src/test/scala/com/johnsnowlabs/nlp/embeddings/AutoGGUFEmbeddingsTestSpec.scala +++ b/src/test/scala/com/johnsnowlabs/nlp/embeddings/AutoGGUFEmbeddingsTestSpec.scala @@ -5,6 +5,7 @@ import com.johnsnowlabs.nlp.base.DocumentAssembler import com.johnsnowlabs.nlp.util.io.ResourceHelper import com.johnsnowlabs.tags.SlowTest import org.apache.spark.ml.Pipeline +import org.apache.spark.sql.{Dataset, Row} import org.scalatest.flatspec.AnyFlatSpec class AutoGGUFEmbeddingsTestSpec extends AnyFlatSpec { @@ -12,11 +13,11 @@ class AutoGGUFEmbeddingsTestSpec extends AnyFlatSpec { behavior of "AutoGGUFEmbeddings" - lazy val documentAssembler = new DocumentAssembler() + lazy val documentAssembler: DocumentAssembler = new DocumentAssembler() .setInputCol("text") .setOutputCol("document") - lazy val data = Seq( + lazy val data: Dataset[Row] = Seq( "The moons of Jupiter are ", // "The moons of Jupiter are 77 in total, with 79 confirmed natural satellites and 2 man-made ones. The four" "Earth is ", // "Earth is 4.5 billion years old. It has been home to countless species, some of which have gone extinct, while others have evolved into" "The moon is ", // "The moon is 1/400th the size of the sun. The sun is 1.39 million kilometers in diameter, while" @@ -24,7 +25,7 @@ class AutoGGUFEmbeddingsTestSpec extends AnyFlatSpec { ).toDF("text").repartition(1) lazy val longDataCopies = 16 - lazy val longData = { + lazy val longData: Dataset[Row] = { val text = "All work and no play makes Jack a dull boy" * 100 Seq.fill(longDataCopies)(text).toDF("text").repartition(4) } @@ -137,4 +138,7 @@ class AutoGGUFEmbeddingsTestSpec extends AnyFlatSpec { AutoGGUFEmbeddings.load(savePath) } + it should "load models with deprecated parameters" taggedAs SlowTest in { + AutoGGUFEmbeddings.pretrained("Nomic_Embed_Text_v1.5.Q8_0.gguf") + } } From 014aa790b40fb9c0f2eb93609a333d9d7c11ae90 Mon Sep 17 00:00:00 2001 From: Devin Ha Date: Thu, 9 Oct 2025 09:25:55 +0200 Subject: [PATCH 6/7] Bump Version [run doc] --- CHANGELOG | 16 ++++++++++++++++ README.md | 8 ++++---- build.sbt | 2 +- conda/meta.yaml | 4 ++-- docs/_config.yml | 2 +- docs/_config_local.yml | 2 +- python/README.md | 8 ++++---- python/setup.py | 2 +- python/sparknlp/__init__.py | 2 +- scripts/colab_setup.sh | 2 +- scripts/kaggle_setup.sh | 2 +- scripts/sagemaker_setup.sh | 2 +- src/main/scala/com/johnsnowlabs/util/Build.scala | 2 +- 13 files changed, 35 insertions(+), 19 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 8dc2db3ef5741c..e8d6be0ba7ccbb 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,19 @@ +======= +6.1.5 +======= +--------------------------- +New Features & Enhancements +--------------------------- + +* [SPARKNLP-1291] Adding support fort input string column on readers #14665 +* [SPARKNLP-1292] Adding fault-tolerance support for malformed XML #14666 +* [SPARKNLP-1290] Introducing ReaderAssembler Annotator #14668 + +--------- +Bug Fixes +--------- +* fix duplicate loading in FeaturesFallbackReader #14667 + ======= 6.1.4 ======= diff --git a/README.md b/README.md index 95d32e60f60d0e..22ef0f4f0d4844 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ $ java -version $ conda create -n sparknlp python=3.7 -y $ conda activate sparknlp # spark-nlp by default is based on pyspark 3.x -$ pip install spark-nlp==6.1.4 pyspark==3.3.1 +$ pip install spark-nlp==6.1.5 pyspark==3.3.1 ``` In Python console or Jupyter `Python3` kernel: @@ -129,7 +129,7 @@ For a quick example of using pipelines and models take a look at our official [d ### Apache Spark Support -Spark NLP *6.1.4* has been built on top of Apache Spark 3.4 while fully supports Apache Spark 3.0.x, 3.1.x, 3.2.x, 3.3.x, 3.4.x, and 3.5.x +Spark NLP *6.1.5* has been built on top of Apache Spark 3.4 while fully supports Apache Spark 3.0.x, 3.1.x, 3.2.x, 3.3.x, 3.4.x, and 3.5.x | Spark NLP | Apache Spark 3.5.x | Apache Spark 3.4.x | Apache Spark 3.3.x | Apache Spark 3.2.x | Apache Spark 3.1.x | Apache Spark 3.0.x | Apache Spark 2.4.x | Apache Spark 2.3.x | |-----------|--------------------|--------------------|--------------------|--------------------|--------------------|--------------------|--------------------|--------------------| @@ -159,7 +159,7 @@ Find out more about 4.x `SparkNLP` versions in our official [documentation](http ### Databricks Support -Spark NLP 6.1.4 has been tested and is compatible with the following runtimes: +Spark NLP 6.1.5 has been tested and is compatible with the following runtimes: | **CPU** | **GPU** | |--------------------|--------------------| @@ -177,7 +177,7 @@ We are compatible with older runtimes. For a full list check databricks support ### EMR Support -Spark NLP 6.1.4 has been tested and is compatible with the following EMR releases: +Spark NLP 6.1.5 has been tested and is compatible with the following EMR releases: | **EMR Release** | |--------------------| diff --git a/build.sbt b/build.sbt index 8ddde39b15a3fd..9d52b973822792 100644 --- a/build.sbt +++ b/build.sbt @@ -6,7 +6,7 @@ name := getPackageName(is_silicon, is_gpu, is_aarch64) organization := "com.johnsnowlabs.nlp" -version := "6.1.4" +version := "6.1.5" (ThisBuild / scalaVersion) := scalaVer diff --git a/conda/meta.yaml b/conda/meta.yaml index 594d2b66e4e88a..1d922001a8fb0b 100644 --- a/conda/meta.yaml +++ b/conda/meta.yaml @@ -1,5 +1,5 @@ {% set name = "spark-nlp" %} -{% set version = "6.1.3" %} +{% set version = "6.1.5" %} package: name: {{ name|lower }} @@ -7,7 +7,7 @@ package: source: url: https://pypi.io/packages/source/{{ name[0] }}/{{ name }}/spark_nlp-{{ version }}.tar.gz - sha256: fc1d8a2981a427a7ce6161a7cb2dc1eb20e79c90ed3063cd3afd08c8113f3060 + sha256: 834e5b785d6f1c6deb48195d88d11ae45433bb398dc632a63106e08fbe6f9273 build: noarch: python diff --git a/docs/_config.yml b/docs/_config.yml index ddf3b6ac9b8327..f0bd2aed2d53f8 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -24,7 +24,7 @@ baseurl : # does not include hostname title : Spark NLP description: > # this means to ignore newlines until "Language & timezone" High Performance NLP with Apache Spark -sparknlp_version: 6.1.4 # Version to be substituted in the documentation +sparknlp_version: 6.1.5 # Version to be substituted in the documentation ## => Language and Timezone diff --git a/docs/_config_local.yml b/docs/_config_local.yml index c5e2975af326c7..2f6470c0a5435e 100644 --- a/docs/_config_local.yml +++ b/docs/_config_local.yml @@ -27,7 +27,7 @@ baseurl : # does not include hostname title : Spark NLP description: > # this means to ignore newlines until "Language & timezone" High Performance NLP with Apache Spark -sparknlp_version: 6.1.4 # Version to be substituted in the documentation +sparknlp_version: 6.1.5 # Version to be substituted in the documentation ## => Language and Timezone diff --git a/python/README.md b/python/README.md index 95d32e60f60d0e..22ef0f4f0d4844 100644 --- a/python/README.md +++ b/python/README.md @@ -63,7 +63,7 @@ $ java -version $ conda create -n sparknlp python=3.7 -y $ conda activate sparknlp # spark-nlp by default is based on pyspark 3.x -$ pip install spark-nlp==6.1.4 pyspark==3.3.1 +$ pip install spark-nlp==6.1.5 pyspark==3.3.1 ``` In Python console or Jupyter `Python3` kernel: @@ -129,7 +129,7 @@ For a quick example of using pipelines and models take a look at our official [d ### Apache Spark Support -Spark NLP *6.1.4* has been built on top of Apache Spark 3.4 while fully supports Apache Spark 3.0.x, 3.1.x, 3.2.x, 3.3.x, 3.4.x, and 3.5.x +Spark NLP *6.1.5* has been built on top of Apache Spark 3.4 while fully supports Apache Spark 3.0.x, 3.1.x, 3.2.x, 3.3.x, 3.4.x, and 3.5.x | Spark NLP | Apache Spark 3.5.x | Apache Spark 3.4.x | Apache Spark 3.3.x | Apache Spark 3.2.x | Apache Spark 3.1.x | Apache Spark 3.0.x | Apache Spark 2.4.x | Apache Spark 2.3.x | |-----------|--------------------|--------------------|--------------------|--------------------|--------------------|--------------------|--------------------|--------------------| @@ -159,7 +159,7 @@ Find out more about 4.x `SparkNLP` versions in our official [documentation](http ### Databricks Support -Spark NLP 6.1.4 has been tested and is compatible with the following runtimes: +Spark NLP 6.1.5 has been tested and is compatible with the following runtimes: | **CPU** | **GPU** | |--------------------|--------------------| @@ -177,7 +177,7 @@ We are compatible with older runtimes. For a full list check databricks support ### EMR Support -Spark NLP 6.1.4 has been tested and is compatible with the following EMR releases: +Spark NLP 6.1.5 has been tested and is compatible with the following EMR releases: | **EMR Release** | |--------------------| diff --git a/python/setup.py b/python/setup.py index 768bcbdaaba4b4..1bede4d6ce92d4 100644 --- a/python/setup.py +++ b/python/setup.py @@ -41,7 +41,7 @@ # project code, see # https://packaging.python.org/en/latest/single_source_version.html - version='6.1.4', # Required + version='6.1.5', # Required # This is a one-line description or tagline of what your project does. This # corresponds to the 'Summary' metadata field: diff --git a/python/sparknlp/__init__.py b/python/sparknlp/__init__.py index c022833a7618c1..b3bc25c079b9b5 100644 --- a/python/sparknlp/__init__.py +++ b/python/sparknlp/__init__.py @@ -66,7 +66,7 @@ annotators = annotator embeddings = annotator -__version__ = "6.1.4" +__version__ = "6.1.5" def start(gpu=False, diff --git a/scripts/colab_setup.sh b/scripts/colab_setup.sh index 66bed9bce2a4ab..ddd39d6d73c097 100644 --- a/scripts/colab_setup.sh +++ b/scripts/colab_setup.sh @@ -1,7 +1,7 @@ #!/bin/bash #default values for pyspark, spark-nlp, and SPARK_HOME -SPARKNLP="6.1.4" +SPARKNLP="6.1.5" PYSPARK="3.4.4" while getopts s:p:g option; do diff --git a/scripts/kaggle_setup.sh b/scripts/kaggle_setup.sh index de10fea9567f69..1eaab5a8f6e7ff 100644 --- a/scripts/kaggle_setup.sh +++ b/scripts/kaggle_setup.sh @@ -1,7 +1,7 @@ #!/bin/bash #default values for pyspark, spark-nlp, and SPARK_HOME -SPARKNLP="6.1.4" +SPARKNLP="6.1.5" PYSPARK="3.2.3" while getopts s:p:g option diff --git a/scripts/sagemaker_setup.sh b/scripts/sagemaker_setup.sh index 781d8538b4d3fe..518b1b0cdefa63 100644 --- a/scripts/sagemaker_setup.sh +++ b/scripts/sagemaker_setup.sh @@ -1,7 +1,7 @@ #!/bin/bash # Default values for pyspark, spark-nlp, and SPARK_HOME -SPARKNLP="6.1.4" +SPARKNLP="6.1.5" PYSPARK="3.2.3" echo "Setup SageMaker for PySpark $PYSPARK and Spark NLP $SPARKNLP" diff --git a/src/main/scala/com/johnsnowlabs/util/Build.scala b/src/main/scala/com/johnsnowlabs/util/Build.scala index e0bcc4ca90fe65..03ea3565ebd9c8 100644 --- a/src/main/scala/com/johnsnowlabs/util/Build.scala +++ b/src/main/scala/com/johnsnowlabs/util/Build.scala @@ -17,5 +17,5 @@ package com.johnsnowlabs.util object Build { - val version: String = "6.1.4" + val version: String = "6.1.5" } From 53aeeec28077ab59df5fcac4ee38054dc7ca5235 Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 9 Oct 2025 07:41:03 +0000 Subject: [PATCH 7/7] Update Scala and Python APIs --- docs/api/com/index.html | 8 +- .../com/johnsnowlabs/client/CloudClient.html | 8 +- .../com/johnsnowlabs/client/CloudManager.html | 8 +- .../johnsnowlabs/client/CloudResources$.html | 8 +- .../com/johnsnowlabs/client/CloudStorage.html | 8 +- .../client/aws/AWSAnonymousCredentials.html | 8 +- .../client/aws/AWSBasicCredentials.html | 8 +- .../johnsnowlabs/client/aws/AWSClient.html | 8 +- .../client/aws/AWSCredentialsProvider.html | 8 +- .../johnsnowlabs/client/aws/AWSGateway.html | 8 +- .../client/aws/AWSProfileCredentials.html | 8 +- .../client/aws/AWSTokenCredentials.html | 8 +- .../client/aws/CredentialParams.html | 8 +- .../johnsnowlabs/client/aws/Credentials.html | 8 +- .../com/johnsnowlabs/client/aws/index.html | 8 +- .../client/azure/AzureClient.html | 8 +- .../client/azure/AzureGateway.html | 8 +- .../com/johnsnowlabs/client/azure/index.html | 8 +- .../johnsnowlabs/client/gcp/GCPClient.html | 8 +- .../johnsnowlabs/client/gcp/GCPGateway.html | 8 +- .../com/johnsnowlabs/client/gcp/index.html | 8 +- docs/api/com/johnsnowlabs/client/index.html | 8 +- .../client/util/CloudHelper$.html | 8 +- .../com/johnsnowlabs/client/util/index.html | 8 +- .../johnsnowlabs/collections/SearchTrie$.html | 8 +- .../johnsnowlabs/collections/SearchTrie.html | 8 +- .../collections/StorageSearchTrie$.html | 8 +- .../collections/StorageSearchTrie.html | 8 +- .../com/johnsnowlabs/collections/index.html | 8 +- docs/api/com/johnsnowlabs/index.html | 8 +- docs/api/com/johnsnowlabs/ml/ai/DeBerta.html | 8 +- .../ml/ai/MergeTokenStrategy$.html | 8 +- .../johnsnowlabs/ml/ai/OpenAICompletion.html | 8 +- .../johnsnowlabs/ml/ai/OpenAIEmbeddings$.html | 8 +- .../johnsnowlabs/ml/ai/OpenAIEmbeddings.html | 8 +- .../com/johnsnowlabs/ml/ai/SmolVLMConfig.html | 8 +- docs/api/com/johnsnowlabs/ml/ai/index.html | 8 +- .../com/johnsnowlabs/ml/ai/model/Choice.html | 8 +- .../ml/ai/model/CompletionResponse.html | 8 +- .../ml/ai/model/EmbeddingData.html | 8 +- .../ml/ai/model/TextEmbeddingResponse.html | 8 +- .../com/johnsnowlabs/ml/ai/model/Usage.html | 8 +- .../johnsnowlabs/ml/ai/model/UsageData.html | 8 +- .../com/johnsnowlabs/ml/ai/model/index.html | 8 +- .../ml/ai/seq2seq/DecoderProcessor.html | 8 +- .../ml/ai/seq2seq/OnnxT5EncoderDecoder.html | 8 +- .../ai/seq2seq/OpenvinoT5EncoderDecoder.html | 8 +- .../ml/ai/seq2seq/T5EncoderDecoder.html | 8 +- .../com/johnsnowlabs/ml/ai/seq2seq/index.html | 8 +- .../ml/ai/t5/OnnxT5EncoderDecoder.html | 8 +- .../t5/T5EncoderDecoder$DecoderProcessor.html | 8 +- .../ml/ai/t5/T5EncoderDecoder.html | 8 +- docs/api/com/johnsnowlabs/ml/ai/t5/index.html | 8 +- .../ml/ai/util/Florence2Utils$$BBox.html | 8 +- .../ai/util/Florence2Utils$$BBoxesResult.html | 8 +- .../util/Florence2Utils$$Florence2Result.html | 8 +- .../ai/util/Florence2Utils$$MixedResult.html | 8 +- .../ai/util/Florence2Utils$$OCRInstance.html | 8 +- .../ml/ai/util/Florence2Utils$$OCRResult.html | 8 +- ...orence2Utils$$PhraseGroundingInstance.html | 8 +- ...Florence2Utils$$PhraseGroundingResult.html | 8 +- .../util/Florence2Utils$$PolygonInstance.html | 8 +- .../util/Florence2Utils$$PolygonsResult.html | 8 +- .../util/Florence2Utils$$PureTextResult.html | 8 +- .../ml/ai/util/Florence2Utils$.html | 8 +- .../ml/ai/util/Generation/Generate.html | 8 +- .../ai/util/Generation/GenerationConfig.html | 8 +- .../ml/ai/util/Generation/Logit/Logit.html | 8 +- .../ForcedTokenLogitProcessor.html | 8 +- .../Logit/LogitProcess/LogitProcessor.html | 8 +- .../LogitProcess/MinLengthLogitProcessor.html | 8 +- .../NoRepeatNgramsLogitProcessor.html | 8 +- .../RepetitionPenaltyLogitProcessor.html | 8 +- .../LogitProcess/SuppressLogitProcessor.html | 8 +- .../Generation/Logit/LogitProcess/index.html | 8 +- .../Generation/Logit/LogitProcessorList.html | 8 +- .../Logit/LogitWarper/LogitWarper.html | 8 +- .../LogitWarper/TemperatureLogitWarper.html | 8 +- .../Logit/LogitWarper/TopKLogitWarper.html | 8 +- .../Logit/LogitWarper/TopPLogitWarper.html | 8 +- .../Generation/Logit/LogitWarper/index.html | 8 +- .../ml/ai/util/Generation/Logit/index.html | 8 +- .../Generation/Search/BeamHypotheses.html | 8 +- .../ai/util/Generation/Search/BeamScorer.html | 8 +- .../Generation/Search/BeamSearchScorer.html | 8 +- .../ml/ai/util/Generation/Search/index.html | 8 +- .../ml/ai/util/Generation/index.html | 8 +- .../com/johnsnowlabs/ml/ai/util/index.html | 8 +- .../ai/util/transform/ChannelDimension$.html | 8 +- .../ml/ai/util/transform/E5VUtils$.html | 8 +- .../ml/ai/util/transform/index.html | 8 +- docs/api/com/johnsnowlabs/ml/crf/Attr.html | 8 +- .../com/johnsnowlabs/ml/crf/AttrFeature.html | 8 +- .../api/com/johnsnowlabs/ml/crf/AttrStat.html | 8 +- .../com/johnsnowlabs/ml/crf/CrfDataset.html | 8 +- .../com/johnsnowlabs/ml/crf/CrfParams.html | 8 +- .../johnsnowlabs/ml/crf/DatasetEncoder.html | 8 +- .../johnsnowlabs/ml/crf/DatasetMetadata.html | 8 +- .../johnsnowlabs/ml/crf/DatasetReader$.html | 8 +- .../johnsnowlabs/ml/crf/EdgeCalculator$.html | 8 +- .../com/johnsnowlabs/ml/crf/FbCalculator.html | 8 +- .../api/com/johnsnowlabs/ml/crf/Instance.html | 8 +- .../johnsnowlabs/ml/crf/InstanceLabels.html | 8 +- .../johnsnowlabs/ml/crf/L2DecayStrategy.html | 8 +- .../johnsnowlabs/ml/crf/LinearChainCrf.html | 8 +- .../ml/crf/LinearChainCrfModel.html | 8 +- .../ml/crf/SerializedDatasetMetadata.html | 8 +- .../ml/crf/SerializedLinearChainCrfModel.html | 8 +- .../ml/crf/SparseArray$$SeqWrapper.html | 8 +- .../com/johnsnowlabs/ml/crf/SparseArray$.html | 8 +- .../com/johnsnowlabs/ml/crf/SparseArray.html | 8 +- .../ml/crf/TextSentenceAttrs.html | 8 +- .../ml/crf/TextSentenceLabels.html | 8 +- .../com/johnsnowlabs/ml/crf/Transition.html | 8 +- .../com/johnsnowlabs/ml/crf/VectorMath$.html | 8 +- .../com/johnsnowlabs/ml/crf/WordAttrs.html | 8 +- docs/api/com/johnsnowlabs/ml/crf/index.html | 8 +- .../johnsnowlabs/ml/gguf/GGUFWrapper$.html | 8 +- .../com/johnsnowlabs/ml/gguf/GGUFWrapper.html | 8 +- .../ml/gguf/GGUFWrapperMultiModal$.html | 8 +- .../ml/gguf/GGUFWrapperMultiModal.html | 8 +- docs/api/com/johnsnowlabs/ml/gguf/index.html | 8 +- docs/api/com/johnsnowlabs/ml/index.html | 8 +- .../com/johnsnowlabs/ml/onnx/OnnxSession.html | 8 +- .../ml/onnx/OnnxWrapper$$DecoderWrappers.html | 8 +- ...er$$EncoderDecoderWithoutPastWrappers.html | 8 +- .../OnnxWrapper$$EncoderDecoderWrappers.html | 8 +- .../johnsnowlabs/ml/onnx/OnnxWrapper$.html | 8 +- .../com/johnsnowlabs/ml/onnx/OnnxWrapper.html | 8 +- .../johnsnowlabs/ml/onnx/ReadOnnxModel.html | 8 +- ...sources$$implicits$$OnnxSessionResult.html | 8 +- .../ml/onnx/TensorResources$$implicits$.html | 8 +- .../ml/onnx/TensorResources$.html | 8 +- .../johnsnowlabs/ml/onnx/TensorResources.html | 8 +- .../johnsnowlabs/ml/onnx/WriteOnnxModel.html | 8 +- docs/api/com/johnsnowlabs/ml/onnx/index.html | 8 +- .../OpenvinoWrapper$$DecoderWrappers.html | 8 +- .../OpenvinoWrapper$$E5VWrappers.html | 8 +- ...er$$EncoderDecoderWithoutPastWrappers.html | 8 +- ...envinoWrapper$$EncoderDecoderWrappers.html | 8 +- .../OpenvinoWrapper$$Florence2Wrappers.html | 8 +- .../OpenvinoWrapper$$Gemma3Wrappers.html | 8 +- .../OpenvinoWrapper$$InternVLWrappers.html | 8 +- .../OpenvinoWrapper$$JanusWrappers.html | 8 +- .../OpenvinoWrapper$$LLAVAWrappers.html | 8 +- .../OpenvinoWrapper$$MLLamaWrappers.html | 8 +- .../OpenvinoWrapper$$PaliGemmaWrappers.html | 8 +- .../OpenvinoWrapper$$Phi3VWrappers.html | 8 +- .../OpenvinoWrapper$$Qwen2VLWrappers.html | 8 +- .../OpenvinoWrapper$$SmolVLMWrappers.html | 8 +- .../ml/openvino/OpenvinoWrapper$.html | 8 +- .../ml/openvino/OpenvinoWrapper.html | 8 +- .../ml/openvino/ReadOpenvinoModel.html | 8 +- .../ml/openvino/WriteOpenvinoModel.html | 8 +- .../com/johnsnowlabs/ml/openvino/index.html | 8 +- .../tensorflow/ClassifierDatasetEncoder.html | 8 +- .../ClassifierDatasetEncoderParams.html | 8 +- .../ml/tensorflow/DatasetEncoderParams.html | 8 +- .../johnsnowlabs/ml/tensorflow/Logging.html | 8 +- .../ml/tensorflow/ModelSignature.html | 8 +- .../johnsnowlabs/ml/tensorflow/NerBatch$.html | 8 +- .../johnsnowlabs/ml/tensorflow/NerBatch.html | 8 +- .../ml/tensorflow/NerDatasetEncoder.html | 8 +- .../ml/tensorflow/ReadTensorflowModel.html | 8 +- .../ml/tensorflow/SentenceGrouper.html | 8 +- .../ml/tensorflow/TensorResources$.html | 8 +- .../ml/tensorflow/TensorResources.html | 8 +- .../ml/tensorflow/TensorflowClassifier.html | 8 +- .../ml/tensorflow/TensorflowWrapper$.html | 8 +- .../ml/tensorflow/TensorflowWrapper.html | 8 +- .../johnsnowlabs/ml/tensorflow/Variables.html | 8 +- .../ml/tensorflow/WriteTensorflowModel.html | 8 +- .../com/johnsnowlabs/ml/tensorflow/index.html | 8 +- .../sentencepiece/ReadSentencePieceModel.html | 8 +- .../sentencepiece/SentencePieceException.html | 8 +- .../sentencepiece/SentencePieceProcessor.html | 8 +- .../sentencepiece/SentencePieceWrapper$.html | 8 +- .../WriteSentencePieceModel.html | 8 +- .../ml/tensorflow/sentencepiece/index.html | 8 +- ...delSignatureConstants$$AttentionMask$.html | 8 +- ...lSignatureConstants$$AttentionMaskV1$.html | 8 +- ...SignatureConstants$$AudioValuesInput$.html | 8 +- ...s$$CachedDecoderEncoderAttentionMask$.html | 8 +- ...stants$$CachedDecoderEncoderInputIds$.html | 8 +- ...eConstants$$CachedDecoderInputCache1$.html | 8 +- ...eConstants$$CachedDecoderInputCache2$.html | 8 +- ...tureConstants$$CachedDecoderInputIds$.html | 8 +- ...natureConstants$$CachedEncoderOutput$.html | 8 +- ...gnatureConstants$$CachedLogitsOutput$.html | 8 +- ...delSignatureConstants$$CachedOutPut2$.html | 8 +- ...delSignatureConstants$$CachedOutput1$.html | 8 +- .../sign/ModelSignatureConstants$$DType$.html | 8 +- ...atureConstants$$DecoderAttentionMask$.html | 8 +- ...ureConstants$$DecoderCachedCache1Key$.html | 8 +- ...ureConstants$$DecoderCachedCache2Key$.html | 8 +- ...ts$$DecoderCachedEncoderAttentionKey$.html | 8 +- ...stants$$DecoderCachedEncoderStateKey$.html | 8 +- ...eConstants$$DecoderCachedInputIdsKey$.html | 8 +- ...natureConstants$$DecoderCachedOutput$.html | 8 +- ...stants$$DecoderCachedOutputCache1Key$.html | 8 +- ...stants$$DecoderCachedOutputCache2Key$.html | 8 +- ...ureConstants$$DecoderCachedOutputKey$.html | 8 +- ...nstants$$DecoderEncoderAttentionMask$.html | 8 +- ...ureConstants$$DecoderEncoderInputIds$.html | 8 +- ...onstants$$DecoderInitOutputCache1Key$.html | 8 +- ...onstants$$DecoderInitOutputCache2Key$.html | 8 +- ...lSignatureConstants$$DecoderInputIds$.html | 8 +- ...delSignatureConstants$$DecoderOutput$.html | 8 +- .../ModelSignatureConstants$$DimCount$.html | 8 +- ...atureConstants$$EncoderAttentionMask$.html | 8 +- ...gnatureConstants$$EncoderContextMask$.html | 8 +- ...lSignatureConstants$$EncoderInputIds$.html | 8 +- ...delSignatureConstants$$EncoderOutput$.html | 8 +- ...lSignatureConstants$$EndLogitsOutput$.html | 8 +- ...ignatureConstants$$InitCachedOutPut2$.html | 8 +- ...ignatureConstants$$InitCachedOutput1$.html | 8 +- ...nts$$InitDecoderEncoderAttentionMask$.html | 8 +- ...onstants$$InitDecoderEncoderInputIds$.html | 8 +- ...natureConstants$$InitDecoderInputIds$.html | 8 +- ...SignatureConstants$$InitLogitsOutput$.html | 8 +- .../ModelSignatureConstants$$InputIds$.html | 8 +- .../ModelSignatureConstants$$InputIdsV1$.html | 8 +- ...lSignatureConstants$$LastHiddenState$.html | 8 +- ...ignatureConstants$$LastHiddenStateV1$.html | 8 +- ...odelSignatureConstants$$LogitsOutput$.html | 8 +- .../sign/ModelSignatureConstants$$Name$.html | 8 +- ...SignatureConstants$$PixelValuesInput$.html | 8 +- ...odelSignatureConstants$$PoolerOutput$.html | 8 +- ...elSignatureConstants$$PoolerOutputV1$.html | 8 +- ...elSignatureConstants$$SerializedSize$.html | 8 +- ...odelSignatureConstants$$ShapeDimList$.html | 8 +- ...ignatureConstants$$StartLogitsOutput$.html | 8 +- ...lSignatureConstants$$TFInfoDescriptor.html | 8 +- ...lSignatureConstants$$TFInfoNameMapper.html | 8 +- ...stants$$TapasLogitsAggregationOutput$.html | 8 +- ...ignatureConstants$$TapasLogitsOutput$.html | 8 +- ...odelSignatureConstants$$TokenTypeIds$.html | 8 +- ...elSignatureConstants$$TokenTypeIdsV1$.html | 8 +- .../sign/ModelSignatureConstants$.html | 8 +- .../sign/ModelSignatureManager$.html | 8 +- .../ml/tensorflow/sign/index.html | 8 +- ...inAlg$$implicits$$ExtendedDenseMatrix.html | 8 +- .../ml/util/LinAlg$$implicits$.html | 8 +- .../api/com/johnsnowlabs/ml/util/LinAlg$.html | 8 +- .../com/johnsnowlabs/ml/util/LlamaCPP$.html | 8 +- .../ml/util/LoadExternalModel$.html | 8 +- .../com/johnsnowlabs/ml/util/ModelArch$.html | 8 +- .../com/johnsnowlabs/ml/util/ModelEngine.html | 8 +- docs/api/com/johnsnowlabs/ml/util/ONNX$.html | 8 +- .../com/johnsnowlabs/ml/util/Openvino$.html | 8 +- .../com/johnsnowlabs/ml/util/PyTorch$.html | 8 +- .../com/johnsnowlabs/ml/util/TensorFlow$.html | 8 +- .../com/johnsnowlabs/ml/util/Unknown$.html | 8 +- docs/api/com/johnsnowlabs/ml/util/index.html | 8 +- .../johnsnowlabs/nlp/ActivationFunction$.html | 8 +- .../nlp/Annotation$$AnnotationContainer.html | 8 +- ...nnotation$$extractors$$AnnotationData.html | 8 +- .../nlp/Annotation$$extractors$.html | 8 +- .../api/com/johnsnowlabs/nlp/Annotation$.html | 8 +- docs/api/com/johnsnowlabs/nlp/Annotation.html | 8 +- .../AnnotationAudio$$AnnotationContainer.html | 8 +- .../nlp/AnnotationAudio$$AudioFields.html | 8 +- .../johnsnowlabs/nlp/AnnotationAudio$.html | 8 +- .../com/johnsnowlabs/nlp/AnnotationAudio.html | 8 +- .../AnnotationImage$$AnnotationContainer.html | 8 +- .../nlp/AnnotationImage$$ImageFields.html | 8 +- .../johnsnowlabs/nlp/AnnotationImage$.html | 8 +- .../com/johnsnowlabs/nlp/AnnotationImage.html | 8 +- .../johnsnowlabs/nlp/AnnotatorApproach.html | 8 +- .../com/johnsnowlabs/nlp/AnnotatorModel.html | 8 +- .../com/johnsnowlabs/nlp/AnnotatorType$.html | 8 +- .../com/johnsnowlabs/nlp/AudioAssembler$.html | 8 +- .../com/johnsnowlabs/nlp/AudioAssembler.html | 8 +- docs/api/com/johnsnowlabs/nlp/CanBeLazy.html | 8 +- docs/api/com/johnsnowlabs/nlp/Doc2Chunk$.html | 8 +- docs/api/com/johnsnowlabs/nlp/Doc2Chunk.html | 8 +- .../johnsnowlabs/nlp/DocumentAssembler$.html | 8 +- .../johnsnowlabs/nlp/DocumentAssembler.html | 8 +- .../johnsnowlabs/nlp/EmbeddingsFinisher$.html | 8 +- .../johnsnowlabs/nlp/EmbeddingsFinisher.html | 8 +- .../nlp/FeaturesFallbackReader.html | 8 +- .../com/johnsnowlabs/nlp/FeaturesReader.html | 8 +- .../com/johnsnowlabs/nlp/FeaturesWriter.html | 8 +- docs/api/com/johnsnowlabs/nlp/Finisher$.html | 8 +- docs/api/com/johnsnowlabs/nlp/Finisher.html | 8 +- .../com/johnsnowlabs/nlp/GraphFinisher.html | 8 +- .../nlp/HasAudioFeatureProperties.html | 8 +- .../johnsnowlabs/nlp/HasBatchedAnnotate.html | 8 +- .../nlp/HasBatchedAnnotateAudio.html | 8 +- .../nlp/HasBatchedAnnotateImage.html | 8 +- .../nlp/HasBatchedAnnotateTextImage.html | 8 +- .../nlp/HasCandidateLabelsProperties.html | 8 +- .../nlp/HasCaseSensitiveProperties.html | 8 +- .../HasClassifierActivationProperties.html | 8 +- .../nlp/HasClsTokenProperties.html | 8 +- .../nlp/HasEnableCachingProperties.html | 8 +- docs/api/com/johnsnowlabs/nlp/HasEngine.html | 8 +- .../api/com/johnsnowlabs/nlp/HasFeatures.html | 10 +- .../nlp/HasGeneratorProperties.html | 8 +- .../nlp/HasImageFeatureProperties.html | 8 +- .../nlp/HasInputAnnotationCols.html | 8 +- .../nlp/HasLlamaCppInferenceProperties.html | 8 +- .../nlp/HasLlamaCppModelProperties.html | 8 +- .../nlp/HasMultipleInputAnnotationCols.html | 8 +- .../nlp/HasOutputAnnotationCol.html | 10 +- .../nlp/HasOutputAnnotatorType.html | 10 +- .../com/johnsnowlabs/nlp/HasPretrained.html | 8 +- .../HasProtectedParams$ProtectedParam.html | 8 +- .../johnsnowlabs/nlp/HasProtectedParams.html | 8 +- .../com/johnsnowlabs/nlp/HasRecursiveFit.html | 8 +- .../nlp/HasRecursiveTransform.html | 8 +- .../johnsnowlabs/nlp/HasSimpleAnnotate.html | 8 +- .../api/com/johnsnowlabs/nlp/IAnnotation.html | 8 +- .../com/johnsnowlabs/nlp/ImageAssembler$.html | 8 +- .../com/johnsnowlabs/nlp/ImageAssembler.html | 8 +- .../com/johnsnowlabs/nlp/JavaAnnotation.html | 8 +- .../com/johnsnowlabs/nlp/LightPipeline.html | 8 +- .../nlp/MultiDocumentAssembler$.html | 8 +- .../nlp/MultiDocumentAssembler.html | 8 +- .../ParamsAndFeaturesFallbackReadable.html | 8 +- .../nlp/ParamsAndFeaturesReadable.html | 8 +- .../nlp/ParamsAndFeaturesWritable.html | 10 +- .../johnsnowlabs/nlp/PromptAssembler$.html | 8 +- .../com/johnsnowlabs/nlp/PromptAssembler.html | 8 +- .../com/johnsnowlabs/nlp/RawAnnotator.html | 8 +- .../johnsnowlabs/nlp/RecursivePipeline.html | 8 +- .../nlp/RecursivePipelineModel.html | 8 +- docs/api/com/johnsnowlabs/nlp/SparkNLP$.html | 8 +- .../com/johnsnowlabs/nlp/TableAssembler$.html | 8 +- .../com/johnsnowlabs/nlp/TableAssembler.html | 8 +- .../com/johnsnowlabs/nlp/TokenAssembler$.html | 8 +- .../com/johnsnowlabs/nlp/TokenAssembler.html | 8 +- .../nlp/annotators/Chunk2Doc$.html | 8 +- .../nlp/annotators/Chunk2Doc.html | 8 +- .../nlp/annotators/ChunkTokenizer$.html | 8 +- .../nlp/annotators/ChunkTokenizer.html | 8 +- .../nlp/annotators/ChunkTokenizerModel$.html | 8 +- .../nlp/annotators/ChunkTokenizerModel.html | 8 +- .../johnsnowlabs/nlp/annotators/Chunker$.html | 8 +- .../johnsnowlabs/nlp/annotators/Chunker.html | 8 +- .../nlp/annotators/DataFrameOptimizer.html | 8 +- .../nlp/annotators/Date2Chunk$.html | 8 +- .../nlp/annotators/Date2Chunk.html | 8 +- .../nlp/annotators/DateMatcher$.html | 8 +- .../nlp/annotators/DateMatcher.html | 8 +- .../nlp/annotators/DateMatcherTranslator.html | 8 +- .../DateMatcherTranslatorPolicy.html | 8 +- .../nlp/annotators/DateMatcherUtils.html | 8 +- .../DocumentCharacterTextSplitter$.html | 8 +- .../DocumentCharacterTextSplitter.html | 8 +- .../nlp/annotators/DocumentNormalizer$.html | 8 +- .../nlp/annotators/DocumentNormalizer.html | 8 +- .../annotators/DocumentTokenSplitter$.html | 8 +- .../nlp/annotators/DocumentTokenSplitter.html | 8 +- .../nlp/annotators/EnglishStemmer$.html | 8 +- .../nlp/annotators/GraphExtraction.html | 8 +- .../nlp/annotators/Lemmatizer$.html | 8 +- .../nlp/annotators/Lemmatizer.html | 8 +- .../nlp/annotators/LemmatizerModel$.html | 8 +- .../nlp/annotators/LemmatizerModel.html | 8 +- .../nlp/annotators/LookAroundManager$.html | 8 +- .../nlp/annotators/MultiDateMatcher$.html | 8 +- .../nlp/annotators/MultiDateMatcher.html | 8 +- .../nlp/annotators/MultiDatePolicy$.html | 8 +- .../nlp/annotators/NGramGenerator$.html | 8 +- .../nlp/annotators/NGramGenerator.html | 8 +- .../nlp/annotators/Normalizer$.html | 8 +- .../nlp/annotators/Normalizer.html | 8 +- .../nlp/annotators/NormalizerModel$.html | 8 +- ...alizerModel$TokenizerAndNormalizerMap.html | 8 +- .../nlp/annotators/NormalizerModel.html | 8 +- .../annotators/PretrainedAnnotations$.html | 8 +- .../ReadablePretrainedLemmatizer.html | 8 +- ...adablePretrainedStopWordsCleanerModel.html | 8 +- .../ReadablePretrainedTextMatcher.html | 8 +- .../ReadablePretrainedTokenizer.html | 8 +- .../nlp/annotators/RecursiveTokenizer.html | 8 +- .../annotators/RecursiveTokenizerModel$.html | 8 +- .../annotators/RecursiveTokenizerModel.html | 8 +- .../nlp/annotators/RegexMatcher$.html | 8 +- .../nlp/annotators/RegexMatcher.html | 8 +- .../nlp/annotators/RegexMatcherModel$.html | 8 +- .../nlp/annotators/RegexMatcherModel.html | 8 +- .../nlp/annotators/RegexTokenizer$.html | 8 +- .../nlp/annotators/RegexTokenizer.html | 8 +- .../nlp/annotators/SingleDatePolicy$.html | 8 +- .../johnsnowlabs/nlp/annotators/Stemmer$.html | 8 +- .../johnsnowlabs/nlp/annotators/Stemmer.html | 8 +- .../nlp/annotators/StopWordsCleaner$.html | 8 +- .../nlp/annotators/StopWordsCleaner.html | 8 +- .../nlp/annotators/TextMatcher$.html | 8 +- .../nlp/annotators/TextMatcher.html | 8 +- .../nlp/annotators/TextMatcherModel$.html | 8 +- .../nlp/annotators/TextMatcherModel.html | 8 +- .../nlp/annotators/TextSplitter.html | 8 +- .../nlp/annotators/Token2Chunk$.html | 8 +- .../nlp/annotators/Token2Chunk.html | 8 +- .../nlp/annotators/Tokenizer$.html | 8 +- .../nlp/annotators/Tokenizer.html | 8 +- .../nlp/annotators/TokenizerModel$.html | 8 +- .../nlp/annotators/TokenizerModel.html | 8 +- .../nlp/annotators/audio/HubertForCTC$.html | 8 +- .../nlp/annotators/audio/HubertForCTC.html | 8 +- .../audio/ReadHubertForAudioDLModel.html | 8 +- .../audio/ReadWav2Vec2ForAudioDLModel.html | 8 +- .../audio/ReadWhisperForCTCDLModel.html | 8 +- ...ReadablePretrainedHubertForAudioModel.html | 8 +- ...adablePretrainedWav2Vec2ForAudioModel.html | 8 +- .../ReadablePretrainedWhisperForCTCModel.html | 8 +- .../nlp/annotators/audio/Wav2Vec2ForCTC$.html | 8 +- .../nlp/annotators/audio/Wav2Vec2ForCTC.html | 8 +- .../nlp/annotators/audio/WhisperForCTC$.html | 8 +- .../nlp/annotators/audio/WhisperForCTC.html | 8 +- .../audio/feature_extractor/AudioUtils$.html | 8 +- .../PreprocessorAttributes$.html | 8 +- .../WhisperPreprocessor.html | 8 +- .../audio/feature_extractor/index.html | 8 +- .../nlp/annotators/audio/index.html | 8 +- .../nlp/annotators/btm/BigTextMatcher$.html | 8 +- .../nlp/annotators/btm/BigTextMatcher.html | 8 +- .../annotators/btm/BigTextMatcherModel$.html | 8 +- .../annotators/btm/BigTextMatcherModel.html | 8 +- .../btm/ReadablePretrainedBigTextMatcher.html | 8 +- .../nlp/annotators/btm/TMEdgesReadWriter.html | 8 +- .../nlp/annotators/btm/TMEdgesReader.html | 8 +- .../nlp/annotators/btm/TMNodesReader.html | 8 +- .../nlp/annotators/btm/TMNodesWriter.html | 8 +- .../nlp/annotators/btm/TMVocabReadWriter.html | 8 +- .../nlp/annotators/btm/TMVocabReader.html | 8 +- .../nlp/annotators/btm/TrieNode.html | 8 +- .../nlp/annotators/btm/index.html | 8 +- .../dl/AlbertForMultipleChoice$.html | 8 +- .../dl/AlbertForMultipleChoice.html | 8 +- .../dl/AlbertForQuestionAnswering$.html | 8 +- .../dl/AlbertForQuestionAnswering.html | 8 +- .../dl/AlbertForSequenceClassification$.html | 8 +- .../dl/AlbertForSequenceClassification.html | 8 +- .../dl/AlbertForTokenClassification$.html | 8 +- .../dl/AlbertForTokenClassification.html | 8 +- .../dl/AlbertForZeroShotClassification$.html | 8 +- .../dl/AlbertForZeroShotClassification.html | 8 +- .../dl/BartForZeroShotClassification$.html | 8 +- .../dl/BartForZeroShotClassification.html | 8 +- .../classifier/dl/BertForMultipleChoice$.html | 8 +- .../classifier/dl/BertForMultipleChoice.html | 8 +- .../dl/BertForQuestionAnswering$.html | 8 +- .../dl/BertForQuestionAnswering.html | 8 +- .../dl/BertForSequenceClassification$.html | 8 +- .../dl/BertForSequenceClassification.html | 8 +- .../dl/BertForTokenClassification$.html | 8 +- .../dl/BertForTokenClassification.html | 8 +- .../dl/BertForZeroShotClassification$.html | 8 +- .../dl/BertForZeroShotClassification.html | 8 +- .../dl/CamemBertForQuestionAnswering$.html | 8 +- .../dl/CamemBertForQuestionAnswering.html | 8 +- .../CamemBertForSequenceClassification$.html | 8 +- .../CamemBertForSequenceClassification.html | 8 +- .../dl/CamemBertForTokenClassification$.html | 8 +- .../dl/CamemBertForTokenClassification.html | 8 +- .../CamemBertForZeroShotClassification$.html | 8 +- .../CamemBertForZeroShotClassification.html | 8 +- .../classifier/dl/ClassifierDLApproach$.html | 8 +- .../classifier/dl/ClassifierDLApproach.html | 8 +- .../classifier/dl/ClassifierDLModel$.html | 8 +- .../classifier/dl/ClassifierDLModel.html | 8 +- .../classifier/dl/ClassifierEncoder.html | 8 +- .../classifier/dl/ClassifierMetrics.html | 8 +- .../dl/DeBertaForQuestionAnswering$.html | 8 +- .../dl/DeBertaForQuestionAnswering.html | 8 +- .../dl/DeBertaForSequenceClassification$.html | 8 +- .../dl/DeBertaForSequenceClassification.html | 8 +- .../dl/DeBertaForTokenClassification$.html | 8 +- .../dl/DeBertaForTokenClassification.html | 8 +- .../dl/DeBertaForZeroShotClassification$.html | 8 +- .../dl/DeBertaForZeroShotClassification.html | 8 +- .../dl/DistilBertForMultipleChoice$.html | 8 +- .../dl/DistilBertForMultipleChoice.html | 8 +- .../dl/DistilBertForQuestionAnswering$.html | 8 +- .../dl/DistilBertForQuestionAnswering.html | 8 +- .../DistilBertForSequenceClassification$.html | 8 +- .../DistilBertForSequenceClassification.html | 8 +- .../dl/DistilBertForTokenClassification$.html | 8 +- .../dl/DistilBertForTokenClassification.html | 8 +- .../DistilBertForZeroShotClassification$.html | 8 +- .../DistilBertForZeroShotClassification.html | 8 +- .../dl/LongformerForQuestionAnswering$.html | 8 +- .../dl/LongformerForQuestionAnswering.html | 8 +- .../LongformerForSequenceClassification$.html | 8 +- .../LongformerForSequenceClassification.html | 8 +- .../dl/LongformerForTokenClassification$.html | 8 +- .../dl/LongformerForTokenClassification.html | 8 +- .../dl/MPNetForQuestionAnswering$.html | 8 +- .../dl/MPNetForQuestionAnswering.html | 8 +- .../dl/MPNetForSequenceClassification$.html | 8 +- .../dl/MPNetForSequenceClassification.html | 8 +- .../dl/MPNetForTokenClassification$.html | 8 +- .../dl/MPNetForTokenClassification.html | 8 +- .../dl/MultiClassifierDLApproach.html | 8 +- .../dl/MultiClassifierDLModel$.html | 8 +- .../classifier/dl/MultiClassifierDLModel.html | 8 +- .../dl/ReadAlbertForMultipleChoiceModel.html | 8 +- ...ReadAlbertForQuestionAnsweringDLModel.html | 8 +- .../dl/ReadAlbertForSequenceDLModel.html | 8 +- .../dl/ReadAlbertForTokenDLModel.html | 8 +- .../dl/ReadAlbertForZeroShotDLModel.html | 8 +- .../dl/ReadBartForZeroShotDLModel.html | 8 +- .../dl/ReadBertForMultipleChoiceModel.html | 8 +- .../ReadBertForQuestionAnsweringDLModel.html | 8 +- .../dl/ReadBertForSequenceDLModel.html | 8 +- .../dl/ReadBertForTokenDLModel.html | 8 +- .../dl/ReadBertForZeroShotDLModel.html | 8 +- .../dl/ReadCamemBertForQADLModel.html | 8 +- .../dl/ReadCamemBertForSequenceDLModel.html | 8 +- .../dl/ReadCamemBertForTokenDLModel.html | 8 +- ...eadCamemBertForZeroShotClassification.html | 8 +- .../dl/ReadClassifierDLTensorflowModel.html | 8 +- ...eadDeBertaForQuestionAnsweringDLModel.html | 8 +- .../dl/ReadDeBertaForSequenceDLModel.html | 8 +- .../dl/ReadDeBertaForTokenDLModel.html | 8 +- .../dl/ReadDeBertaForZeroShotDLModel.html | 8 +- .../ReadDistilBertForMultipleChoiceModel.html | 8 +- ...DistilBertForQuestionAnsweringDLModel.html | 8 +- .../dl/ReadDistilBertForSequenceDLModel.html | 8 +- .../dl/ReadDistilBertForTokenDLModel.html | 8 +- .../dl/ReadDistilBertForZeroShotDLModel.html | 8 +- ...LongformerForQuestionAnsweringDLModel.html | 8 +- .../dl/ReadLongformerForSequenceDLModel.html | 8 +- .../dl/ReadLongformerForTokenDLModel.html | 8 +- .../ReadMPNetForQuestionAnsweringDLModel.html | 8 +- .../dl/ReadMPNetForSequenceDLModel.html | 8 +- .../dl/ReadMPNetForTokenDLModel.html | 8 +- .../ReadMultiClassifierDLTensorflowModel.html | 8 +- ...nedCamemBertForZeroShotClassification.html | 8 +- .../ReadRoBertaForMultipleChoiceDLModel.html | 8 +- ...dRoBertaForMultipleChoiceModelDLModel.html | 8 +- ...eadRoBertaForQuestionAnsweringDLModel.html | 8 +- .../dl/ReadRoBertaForSequenceDLModel.html | 8 +- .../dl/ReadRoBertaForTokenDLModel.html | 8 +- .../dl/ReadRoBertaForZeroShotDLModel.html | 8 +- .../dl/ReadSentimentDLTensorflowModel.html | 8 +- .../ReadTapasForQuestionAnsweringDLModel.html | 8 +- ...XlmRoBertaForQuestionAnsweringDLModel.html | 8 +- .../dl/ReadXlmRoBertaForSequenceDLModel.html | 8 +- .../dl/ReadXlmRoBertaForTokenDLModel.html | 8 +- .../dl/ReadXlmRoBertaForZeroShotDLModel.html | 8 +- .../dl/ReadXlnetForSequenceDLModel.html | 8 +- .../dl/ReadXlnetForTokenDLModel.html | 8 +- ...retrainedAlbertForMultipleChoiceModel.html | 8 +- .../ReadablePretrainedAlbertForQAModel.html | 8 +- ...dablePretrainedAlbertForSequenceModel.html | 8 +- ...ReadablePretrainedAlbertForTokenModel.html | 8 +- ...dablePretrainedAlbertForZeroShotModel.html | 8 +- ...eadablePretrainedBartForZeroShotModel.html | 8 +- ...ePretrainedBertForMultipleChoiceModel.html | 8 +- .../dl/ReadablePretrainedBertForQAModel.html | 8 +- ...eadablePretrainedBertForSequenceModel.html | 8 +- .../ReadablePretrainedBertForTokenModel.html | 8 +- ...eadablePretrainedBertForZeroShotModel.html | 8 +- ...ReadablePretrainedCamemBertForQAModel.html | 8 +- ...lePretrainedCamemBertForSequenceModel.html | 8 +- ...dablePretrainedCamemBertForTokenModel.html | 8 +- .../dl/ReadablePretrainedClassifierDL.html | 8 +- .../ReadablePretrainedDeBertaForQAModel.html | 8 +- ...ablePretrainedDeBertaForSequenceModel.html | 8 +- ...eadablePretrainedDeBertaForTokenModel.html | 8 +- ...ablePretrainedDeBertaForZeroShotModel.html | 8 +- ...ainedDistilBertForMultipleChoiceModel.html | 8 +- ...eadablePretrainedDistilBertForQAModel.html | 8 +- ...ePretrainedDistilBertForSequenceModel.html | 8 +- ...ablePretrainedDistilBertForTokenModel.html | 8 +- ...ePretrainedDistilBertForZeroShotModel.html | 8 +- ...eadablePretrainedLongformerForQAModel.html | 8 +- ...ePretrainedLongformerForSequenceModel.html | 8 +- ...ablePretrainedLongformerForTokenModel.html | 8 +- .../dl/ReadablePretrainedMPNetForQAModel.html | 8 +- ...adablePretrainedMPNetForSequenceModel.html | 8 +- ...eadablePretrainedMPNetForTokenDLModel.html | 8 +- .../ReadablePretrainedMultiClassifierDL.html | 8 +- .../ReadablePretrainedRoBertaForMCModel.html | 8 +- .../ReadablePretrainedRoBertaForQAModel.html | 8 +- ...ablePretrainedRoBertaForSequenceModel.html | 8 +- ...eadablePretrainedRoBertaForTokenModel.html | 8 +- ...ablePretrainedRoBertaForZeroShotModel.html | 8 +- .../dl/ReadablePretrainedSentimentDL.html | 8 +- .../dl/ReadablePretrainedTapasForQAModel.html | 8 +- ...eadablePretrainedXlmRoBertaForQAModel.html | 8 +- ...ePretrainedXlmRoBertaForSequenceModel.html | 8 +- ...ablePretrainedXlmRoBertaForTokenModel.html | 8 +- ...ePretrainedXlmRoBertaForZeroShotModel.html | 8 +- ...adablePretrainedXlnetForSequenceModel.html | 8 +- .../ReadablePretrainedXlnetForTokenModel.html | 8 +- ...ainedXmlRoBertaForMultipleChoiceModel.html | 8 +- .../dl/RoBertaForMultipleChoice$.html | 8 +- .../dl/RoBertaForMultipleChoice.html | 8 +- .../dl/RoBertaForQuestionAnswering$.html | 8 +- .../dl/RoBertaForQuestionAnswering.html | 8 +- .../dl/RoBertaForSequenceClassification$.html | 8 +- .../dl/RoBertaForSequenceClassification.html | 8 +- .../dl/RoBertaForTokenClassification$.html | 8 +- .../dl/RoBertaForTokenClassification.html | 8 +- .../dl/RoBertaForZeroShotClassification$.html | 8 +- .../dl/RoBertaForZeroShotClassification.html | 8 +- .../classifier/dl/SentimentApproach$.html | 8 +- .../classifier/dl/SentimentDLApproach.html | 8 +- .../classifier/dl/SentimentDLModel$.html | 8 +- .../classifier/dl/SentimentDLModel.html | 8 +- .../dl/TapasForQuestionAnswering$.html | 8 +- .../dl/TapasForQuestionAnswering.html | 8 +- .../dl/XlmRoBertaForMultipleChoice$.html | 8 +- .../dl/XlmRoBertaForMultipleChoice.html | 8 +- .../dl/XlmRoBertaForQuestionAnswering$.html | 8 +- .../dl/XlmRoBertaForQuestionAnswering.html | 8 +- .../XlmRoBertaForSequenceClassification$.html | 8 +- .../XlmRoBertaForSequenceClassification.html | 8 +- .../dl/XlmRoBertaForTokenClassification$.html | 8 +- .../dl/XlmRoBertaForTokenClassification.html | 8 +- .../XlmRoBertaForZeroShotClassification$.html | 8 +- .../XlmRoBertaForZeroShotClassification.html | 8 +- .../dl/XlnetForSequenceClassification$.html | 8 +- .../dl/XlnetForSequenceClassification.html | 8 +- .../dl/XlnetForTokenClassification$.html | 8 +- .../dl/XlnetForTokenClassification.html | 8 +- .../nlp/annotators/classifier/dl/index.html | 8 +- .../nlp/annotators/classifier/index.html | 8 +- .../nlp/annotators/cleaners/Cleaner$.html | 8 +- .../nlp/annotators/cleaners/Cleaner.html | 8 +- .../nlp/annotators/cleaners/Extractor.html | 8 +- .../nlp/annotators/cleaners/index.html | 8 +- .../cleaners/util/CleanerHelper$.html | 8 +- .../nlp/annotators/cleaners/util/index.html | 8 +- .../nlp/annotators/common/Annotated$.html | 8 +- .../nlp/annotators/common/Annotated.html | 8 +- .../nlp/annotators/common/ChunkSplit$.html | 8 +- .../nlp/annotators/common/ConllSentence.html | 8 +- .../DatasetHelpers$$DataFrameHelper.html | 8 +- .../annotators/common/DatasetHelpers$.html | 8 +- .../annotators/common/DependencyParsed$.html | 8 +- .../common/DependencyParsedSentence.html | 8 +- .../common/EmbeddingsWithSentence$.html | 8 +- .../annotators/common/IndexedTaggedWord.html | 8 +- .../nlp/annotators/common/IndexedToken.html | 8 +- .../nlp/annotators/common/InfixToken$.html | 8 +- .../nlp/annotators/common/InfixToken.html | 8 +- .../LabeledDependency$$DependencyInfo.html | 8 +- .../annotators/common/LabeledDependency$.html | 8 +- .../nlp/annotators/common/NerTagged$.html | 8 +- .../nlp/annotators/common/PosTagged$.html | 8 +- .../nlp/annotators/common/PrefixedToken$.html | 8 +- .../nlp/annotators/common/PrefixedToken.html | 8 +- .../common/PreprocessingParser.html | 8 +- .../nlp/annotators/common/Sentence$.html | 8 +- .../nlp/annotators/common/Sentence.html | 8 +- .../nlp/annotators/common/SentenceSplit$.html | 8 +- .../nlp/annotators/common/SuffixedToken$.html | 8 +- .../nlp/annotators/common/SuffixedToken.html | 8 +- .../nlp/annotators/common/TableData$.html | 8 +- .../nlp/annotators/common/TableData.html | 8 +- .../nlp/annotators/common/Tagged.html | 8 +- .../annotators/common/TaggedSentence$.html | 8 +- .../nlp/annotators/common/TaggedSentence.html | 8 +- .../nlp/annotators/common/TaggedWord.html | 8 +- .../nlp/annotators/common/TokenPiece.html | 8 +- .../common/TokenPieceEmbeddings$.html | 8 +- .../common/TokenPieceEmbeddings.html | 8 +- .../annotators/common/TokenizedSentence.html | 8 +- .../common/TokenizedWithSentence$.html | 8 +- .../annotators/common/WordWithDependency.html | 8 +- .../common/WordpieceEmbeddingsSentence$.html | 8 +- .../common/WordpieceEmbeddingsSentence.html | 8 +- .../common/WordpieceTokenized$.html | 8 +- .../common/WordpieceTokenizedSentence.html | 8 +- .../nlp/annotators/common/index.html | 8 +- .../ReadSpanBertCorefTensorflowModel.html | 8 +- .../ReadablePretrainedSpanBertCorefModel.html | 8 +- .../annotators/coref/SpanBertCorefModel$.html | 8 +- .../annotators/coref/SpanBertCorefModel.html | 8 +- .../nlp/annotators/coref/index.html | 8 +- .../cv/BLIPForQuestionAnswering$.html | 8 +- .../cv/BLIPForQuestionAnswering.html | 8 +- .../cv/CLIPForZeroShotClassification$.html | 8 +- .../cv/CLIPForZeroShotClassification.html | 8 +- .../cv/ConvNextForImageClassification$.html | 8 +- .../cv/ConvNextForImageClassification.html | 8 +- .../annotators/cv/Florence2Transformer$.html | 8 +- .../annotators/cv/Florence2Transformer.html | 8 +- .../annotators/cv/Gemma3ForMultiModal$.html | 8 +- .../annotators/cv/Gemma3ForMultiModal.html | 8 +- .../nlp/annotators/cv/HasRescaleFactor.html | 8 +- .../annotators/cv/InternVLForMultiModal$.html | 8 +- .../annotators/cv/InternVLForMultiModal.html | 8 +- .../annotators/cv/JanusForMultiModal$.html | 8 +- .../nlp/annotators/cv/JanusForMultiModal.html | 8 +- .../annotators/cv/LLAVAForMultiModal$.html | 8 +- .../nlp/annotators/cv/LLAVAForMultiModal.html | 8 +- .../annotators/cv/MLLamaForMultimodal$.html | 8 +- .../annotators/cv/MLLamaForMultimodal.html | 8 +- .../cv/PaliGemmaForMultiModal$.html | 8 +- .../annotators/cv/PaliGemmaForMultiModal.html | 8 +- .../nlp/annotators/cv/Phi3Vision$.html | 8 +- .../nlp/annotators/cv/Phi3Vision.html | 8 +- .../annotators/cv/Qwen2VLTransformer$.html | 8 +- .../nlp/annotators/cv/Qwen2VLTransformer.html | 8 +- .../ReadBLIPForQuestionAnsweringDLModel.html | 8 +- ...eadCLIPForZeroShotClassificationModel.html | 8 +- .../cv/ReadConvNextForImageDLModel.html | 8 +- .../cv/ReadFlorence2TransformerDLModel.html | 8 +- .../cv/ReadGemma3ForMultiModalDLModel.html | 8 +- .../cv/ReadInternVLForMultiModalDLModel.html | 8 +- .../cv/ReadJanusForMultiModalDLModel.html | 8 +- .../cv/ReadLLAVAForMultiModalDLModel.html | 8 +- .../cv/ReadMLLamaForMultimodalDLModel.html | 8 +- .../cv/ReadPaliGemmaForMultiModalDLModel.html | 8 +- .../annotators/cv/ReadPhi3VisionDLModel.html | 8 +- .../cv/ReadQwen2VLTransformerDLModel.html | 8 +- .../cv/ReadSmolVLMTransformerDLModel.html | 8 +- .../cv/ReadSwinForImageDLModel.html | 8 +- .../annotators/cv/ReadViTForImageDLModel.html | 8 +- .../cv/ReadVisionEncoderDecoderDLModel.html | 8 +- ...blePretrainedBLIPForQuestionAnswering.html | 8 +- ...nedCLIPForZeroShotClassificationModel.html | 8 +- ...adablePretrainedConvNextForImageModel.html | 8 +- ...lePretrainedFlorence2TransformerModel.html | 8 +- ...ReadablePretrainedGemma3ForMultiModal.html | 8 +- ...adablePretrainedInternVLForMultiModal.html | 8 +- .../ReadablePretrainedJanusForMultiModal.html | 8 +- .../ReadablePretrainedLLAVAForMultiModal.html | 8 +- ...ReadablePretrainedMLLamaForMultimodal.html | 8 +- ...dablePretrainedPaliGemmaForMultiModal.html | 8 +- .../cv/ReadablePretrainedPhi3Vision.html | 8 +- .../ReadablePretrainedQwen2VLTransformer.html | 8 +- .../ReadablePretrainedSmolVLMTransformer.html | 8 +- .../ReadablePretrainedSwinForImageModel.html | 8 +- .../ReadablePretrainedViTForImageModel.html | 8 +- ...lePretrainedVisionEncoderDecoderModel.html | 8 +- .../annotators/cv/SmolVLMTransformer$.html | 8 +- .../nlp/annotators/cv/SmolVLMTransformer.html | 8 +- .../cv/SwinForImageClassification$.html | 8 +- .../cv/SwinForImageClassification.html | 8 +- .../cv/ViTForImageClassification$.html | 8 +- .../cv/ViTForImageClassification.html | 8 +- ...sionEncoderDecoderForImageCaptioning$.html | 8 +- ...isionEncoderDecoderForImageCaptioning.html | 8 +- .../johnsnowlabs/nlp/annotators/cv/index.html | 8 +- .../nlp/annotators/cv/util/index.html | 8 +- .../cv/util/transform/InternVLUtils$.html | 8 +- .../cv/util/transform/MllamaUtils$.html | 8 +- .../annotators/cv/util/transform/index.html | 8 +- .../er/AhoCorasickAutomaton$Node.html | 8 +- .../annotators/er/AhoCorasickAutomaton.html | 8 +- .../nlp/annotators/er/EntityPattern.html | 8 +- .../annotators/er/EntityRulerApproach.html | 8 +- .../annotators/er/EntityRulerFeatures.html | 8 +- .../nlp/annotators/er/EntityRulerModel$.html | 8 +- .../nlp/annotators/er/EntityRulerModel.html | 8 +- .../nlp/annotators/er/EntityRulerUtil$.html | 8 +- .../annotators/er/FlattenEntityPattern.html | 8 +- .../nlp/annotators/er/PatternsReadWriter.html | 8 +- .../nlp/annotators/er/PatternsReader.html | 8 +- .../er/ReadablePretrainedEntityRuler.html | 8 +- .../er/RegexPatternsReadWriter.html | 8 +- .../annotators/er/RegexPatternsReader.html | 8 +- .../johnsnowlabs/nlp/annotators/er/index.html | 8 +- .../johnsnowlabs/nlp/annotators/index.html | 8 +- .../nlp/annotators/keyword/index.html | 8 +- .../keyword/yake/YakeKeywordExtraction$.html | 8 +- .../keyword/yake/YakeKeywordExtraction.html | 8 +- .../annotators/keyword/yake/YakeParams.html | 8 +- .../nlp/annotators/keyword/yake/index.html | 8 +- .../annotators/keyword/yake/util/Token.html | 8 +- .../keyword/yake/util/Utilities$.html | 8 +- .../annotators/keyword/yake/util/index.html | 8 +- .../annotators/ld/dl/LanguageDetectorDL$.html | 8 +- .../annotators/ld/dl/LanguageDetectorDL.html | 8 +- ...ReadLanguageDetectorDLTensorflowModel.html | 8 +- ...ablePretrainedLanguageDetectorDLModel.html | 8 +- .../nlp/annotators/ld/dl/index.html | 8 +- .../johnsnowlabs/nlp/annotators/ld/index.html | 8 +- .../nlp/annotators/ner/ModelMetrics$.html | 8 +- .../nlp/annotators/ner/NamedEntity.html | 8 +- .../nlp/annotators/ner/NerApproach.html | 8 +- .../nlp/annotators/ner/NerConverter$.html | 8 +- .../nlp/annotators/ner/NerConverter.html | 8 +- .../nlp/annotators/ner/NerOverwriter$.html | 8 +- .../nlp/annotators/ner/NerOverwriter.html | 8 +- .../nlp/annotators/ner/NerTagsEncoding$.html | 8 +- .../nlp/annotators/ner/Verbose$.html | 8 +- .../ner/crf/DictionaryFeatures$.html | 8 +- .../ner/crf/DictionaryFeatures.html | 8 +- .../ner/crf/FeatureGenerator$TokenType$.html | 8 +- .../annotators/ner/crf/FeatureGenerator.html | 8 +- .../annotators/ner/crf/NerCrfApproach$.html | 8 +- .../annotators/ner/crf/NerCrfApproach.html | 8 +- .../nlp/annotators/ner/crf/NerCrfModel$.html | 8 +- .../nlp/annotators/ner/crf/NerCrfModel.html | 8 +- .../ner/crf/ReadablePretrainedNerCrf.html | 8 +- .../nlp/annotators/ner/crf/index.html | 8 +- .../nlp/annotators/ner/dl/LoadsContrib$.html | 8 +- .../nlp/annotators/ner/dl/NerDLApproach$.html | 8 +- .../nlp/annotators/ner/dl/NerDLApproach.html | 8 +- .../annotators/ner/dl/NerDLGraphChecker$.html | 8 +- .../annotators/ner/dl/NerDLGraphChecker.html | 8 +- .../ner/dl/NerDLGraphCheckerModel$.html | 8 +- .../ner/dl/NerDLGraphCheckerModel.html | 8 +- .../nlp/annotators/ner/dl/NerDLModel$.html | 8 +- .../nlp/annotators/ner/dl/NerDLModel.html | 8 +- .../ner/dl/NerDLModelPythonReader$.html | 8 +- .../ner/dl/ReadZeroShotNerDLModel.html | 8 +- .../ner/dl/ReadablePretrainedNerDL.html | 8 +- .../ner/dl/ReadablePretrainedZeroShotNer.html | 8 +- .../nlp/annotators/ner/dl/ReadsNERGraph.html | 8 +- .../annotators/ner/dl/WithGraphResolver.html | 8 +- .../annotators/ner/dl/ZeroShotNerModel$.html | 8 +- .../annotators/ner/dl/ZeroShotNerModel.html | 8 +- .../nlp/annotators/ner/dl/index.html | 8 +- .../nlp/annotators/ner/index.html | 8 +- ...lizableFormat$$SerializableDateFormat.html | 8 +- .../AnnotatorParam$SerializableFormat$.html | 8 +- .../nlp/annotators/param/AnnotatorParam.html | 8 +- .../annotators/param/EvaluationDLParams.html | 8 +- .../param/ExternalResourceParam.html | 8 +- .../param/SerializedAnnotatorComponent.html | 8 +- .../param/WritableAnnotatorComponent.html | 8 +- .../nlp/annotators/param/index.html | 8 +- .../parser/dep/DependencyParserApproach$.html | 8 +- .../parser/dep/DependencyParserApproach.html | 8 +- .../parser/dep/DependencyParserModel$.html | 8 +- .../parser/dep/DependencyParserModel.html | 8 +- .../GreedyTransition/DependencyMaker$.html | 8 +- .../DependencyMaker$CurrentState.html | 8 +- .../DependencyMaker$ParseState.html | 8 +- .../dep/GreedyTransition/DependencyMaker.html | 8 +- .../GreedyTransitionApproach$.html | 8 +- .../parser/dep/GreedyTransition/index.html | 8 +- .../GreedyTransition/package$$Feature.html | 8 +- .../GreedyTransition/package$$WordData.html | 8 +- .../parser/dep/Perceptron$WeightLearner.html | 8 +- .../nlp/annotators/parser/dep/Perceptron.html | 8 +- .../dep/ReadablePretrainedDependency.html | 8 +- .../annotators/parser/dep/TagDictionary$.html | 8 +- .../nlp/annotators/parser/dep/Tagger$.html | 8 +- .../nlp/annotators/parser/dep/Tagger.html | 8 +- .../nlp/annotators/parser/dep/index.html | 8 +- .../nlp/annotators/parser/index.html | 8 +- .../annotators/parser/typdep/ConllData.html | 8 +- .../parser/typdep/DependencyArcList.html | 8 +- .../parser/typdep/DependencyInstance.html | 8 +- .../parser/typdep/DependencyPipe.html | 8 +- .../parser/typdep/LocalFeatureData.html | 8 +- .../parser/typdep/LowRankTensor.html | 8 +- .../nlp/annotators/parser/typdep/Options.html | 8 +- .../annotators/parser/typdep/Parameters.html | 8 +- .../parser/typdep/PredictionParameters.html | 8 +- .../ReadablePretrainedTypedDependency.html | 8 +- .../parser/typdep/TrainDependencies.html | 8 +- .../annotators/parser/typdep/TrainFile.html | 8 +- .../parser/typdep/TypedDependencyParser.html | 8 +- .../TypedDependencyParserApproach$.html | 8 +- .../typdep/TypedDependencyParserApproach.html | 8 +- .../typdep/TypedDependencyParserModel$.html | 8 +- .../typdep/TypedDependencyParserModel.html | 8 +- .../typdep/feature/FeatureTemplate.html | 8 +- .../feature/SyntacticFeatureFactory.html | 8 +- .../parser/typdep/feature/index.html | 8 +- .../nlp/annotators/parser/typdep/index.html | 8 +- .../parser/typdep/io/Conll09Reader.html | 8 +- .../parser/typdep/io/ConllUReader.html | 8 +- .../parser/typdep/io/ConllWriter.html | 8 +- .../parser/typdep/io/DependencyReader.html | 8 +- .../annotators/parser/typdep/io/index.html | 8 +- .../parser/typdep/util/Alphabet.html | 8 +- .../parser/typdep/util/Collector.html | 8 +- .../parser/typdep/util/DependencyLabel.html | 8 +- .../parser/typdep/util/Dictionary.html | 8 +- .../parser/typdep/util/DictionarySet.html | 8 +- .../parser/typdep/util/FeatureVector.html | 8 +- .../parser/typdep/util/ScoreCollector.html | 8 +- .../annotators/parser/typdep/util/Utils.html | 8 +- .../annotators/parser/typdep/util/index.html | 8 +- .../nlp/annotators/pos/index.html | 8 +- .../pos/perceptron/AveragedPerceptron.html | 8 +- .../pos/perceptron/PerceptronApproach$.html | 8 +- .../pos/perceptron/PerceptronApproach.html | 8 +- .../PerceptronApproachDistributed$.html | 8 +- .../PerceptronApproachDistributed.html | 8 +- .../pos/perceptron/PerceptronModel$.html | 8 +- .../pos/perceptron/PerceptronModel.html | 8 +- .../perceptron/PerceptronPredictionUtils.html | 8 +- .../perceptron/PerceptronTrainingUtils.html | 8 +- .../pos/perceptron/PerceptronUtils.html | 8 +- .../ReadablePretrainedPerceptron.html | 8 +- .../StringMapStringDoubleAccumulator.html | 8 +- .../perceptron/TrainingPerceptronLegacy.html | 8 +- .../TupleKeyLongDoubleMapAccumulator.html | 8 +- .../nlp/annotators/pos/perceptron/index.html | 8 +- .../sbd/SentenceDetectorParams.html | 8 +- .../nlp/annotators/sbd/index.html | 8 +- .../sbd/pragmatic/CustomPragmaticMethod.html | 8 +- .../sbd/pragmatic/DefaultPragmaticMethod.html | 8 +- .../sbd/pragmatic/MixedPragmaticMethod.html | 8 +- .../pragmatic/PragmaticContentFormatter$.html | 8 +- .../pragmatic/PragmaticContentFormatter.html | 8 +- .../sbd/pragmatic/PragmaticDictionaries$.html | 8 +- .../sbd/pragmatic/PragmaticMethod.html | 8 +- .../pragmatic/PragmaticSentenceExtractor.html | 8 +- .../sbd/pragmatic/PragmaticSymbols$.html | 8 +- .../annotators/sbd/pragmatic/RuleSymbols.html | 8 +- .../sbd/pragmatic/SentenceDetector$.html | 8 +- .../sbd/pragmatic/SentenceDetector.html | 8 +- .../nlp/annotators/sbd/pragmatic/index.html | 8 +- .../nlp/annotators/sda/index.html | 8 +- .../sda/pragmatic/PragmaticScorer.html | 8 +- .../sda/pragmatic/SentimentDetector$.html | 8 +- .../sda/pragmatic/SentimentDetector.html | 8 +- .../pragmatic/SentimentDetectorModel$.html | 8 +- .../sda/pragmatic/SentimentDetectorModel.html | 8 +- .../nlp/annotators/sda/pragmatic/index.html | 8 +- .../sda/vivekn/ReadablePretrainedVivekn.html | 8 +- .../sda/vivekn/ViveknSentimentApproach.html | 8 +- .../sda/vivekn/ViveknSentimentModel$.html | 8 +- .../sda/vivekn/ViveknSentimentModel.html | 8 +- .../sda/vivekn/ViveknSentimentUtils.html | 8 +- .../nlp/annotators/sda/vivekn/index.html | 8 +- .../sentence_detector_dl/Metrics.html | 8 +- .../ReadablePretrainedSentenceDetectorDL.html | 8 +- .../ReadsSentenceDetectorDLGraph.html | 8 +- .../SentenceDetectorDLApproach.html | 8 +- .../SentenceDetectorDLEncoder$.html | 8 +- .../SentenceDetectorDLEncoder.html | 8 +- .../SentenceDetectorDLEncoderParam.html | 8 +- .../SentenceDetectorDLModel$.html | 8 +- .../SentenceDetectorDLModel.html | 8 +- .../sentence_detector_dl/index.html | 8 +- .../annotators/seq2seq/AutoGGUFModel$.html | 8 +- .../nlp/annotators/seq2seq/AutoGGUFModel.html | 8 +- .../annotators/seq2seq/AutoGGUFReranker$.html | 8 +- .../annotators/seq2seq/AutoGGUFReranker.html | 8 +- .../seq2seq/AutoGGUFVisionModel$.html | 8 +- .../seq2seq/AutoGGUFVisionModel.html | 8 +- .../annotators/seq2seq/BartTransformer$.html | 8 +- .../annotators/seq2seq/BartTransformer.html | 8 +- .../annotators/seq2seq/CPMTransformer$.html | 8 +- .../annotators/seq2seq/CPMTransformer.html | 8 +- .../seq2seq/CoHereTransformer$.html | 8 +- .../annotators/seq2seq/CoHereTransformer.html | 8 +- .../annotators/seq2seq/GPT2Transformer$.html | 8 +- .../annotators/seq2seq/GPT2Transformer.html | 8 +- .../seq2seq/LLAMA2Transformer$.html | 8 +- .../annotators/seq2seq/LLAMA2Transformer.html | 8 +- .../seq2seq/LLAMA3Transformer$.html | 8 +- .../annotators/seq2seq/LLAMA3Transformer.html | 8 +- .../seq2seq/M2M100Transformer$.html | 8 +- .../annotators/seq2seq/M2M100Transformer.html | 8 +- .../seq2seq/MarianTransformer$.html | 8 +- .../annotators/seq2seq/MarianTransformer.html | 8 +- .../seq2seq/MistralTransformer$.html | 8 +- .../seq2seq/MistralTransformer.html | 8 +- .../annotators/seq2seq/NLLBTransformer$.html | 8 +- .../annotators/seq2seq/NLLBTransformer.html | 8 +- .../annotators/seq2seq/OLMoTransformer$.html | 8 +- .../annotators/seq2seq/OLMoTransformer.html | 8 +- .../annotators/seq2seq/Phi2Transformer$.html | 8 +- .../annotators/seq2seq/Phi2Transformer.html | 8 +- .../annotators/seq2seq/Phi3Transformer$.html | 8 +- .../annotators/seq2seq/Phi3Transformer.html | 8 +- .../annotators/seq2seq/Phi4Transformer$.html | 8 +- .../annotators/seq2seq/Phi4Transformer.html | 8 +- .../annotators/seq2seq/QwenTransformer$.html | 8 +- .../annotators/seq2seq/QwenTransformer.html | 8 +- .../annotators/seq2seq/ReadAutoGGUFModel.html | 8 +- .../seq2seq/ReadAutoGGUFReranker.html | 8 +- .../seq2seq/ReadAutoGGUFVisionModel.html | 8 +- .../seq2seq/ReadBartTransformerDLModel.html | 8 +- .../seq2seq/ReadCPMTransformerDLModel.html | 8 +- .../seq2seq/ReadCoHereTransformerDLModel.html | 8 +- .../seq2seq/ReadGPT2TransformerDLModel.html | 8 +- .../seq2seq/ReadLLAMA2TransformerDLModel.html | 8 +- .../seq2seq/ReadLLAMA3TransformerDLModel.html | 8 +- .../seq2seq/ReadM2M100TransformerDLModel.html | 8 +- .../seq2seq/ReadMarianMTDLModel.html | 8 +- .../ReadMistralTransformerDLModel.html | 8 +- .../seq2seq/ReadNLLBTransformerDLModel.html | 8 +- .../seq2seq/ReadOLMoTransformerDLModel.html | 8 +- .../seq2seq/ReadPhi2TransformerDLModel.html | 8 +- .../seq2seq/ReadPhi3TransformerDLModel.html | 8 +- .../seq2seq/ReadPhi4TransformerDLModel.html | 8 +- .../seq2seq/ReadQwenTransformerDLModel.html | 8 +- .../ReadStarCoderTransformerDLModel.html | 8 +- .../seq2seq/ReadT5TransformerDLModel.html | 8 +- .../ReadablePretrainedAutoGGUFModel.html | 8 +- .../ReadablePretrainedAutoGGUFReranker.html | 8 +- ...ReadablePretrainedAutoGGUFVisionModel.html | 8 +- ...eadablePretrainedBartTransformerModel.html | 8 +- ...ReadablePretrainedCPMTransformerModel.html | 8 +- ...dablePretrainedCoHereTransformerModel.html | 8 +- ...eadablePretrainedGPT2TransformerModel.html | 8 +- ...dablePretrainedLLAMA2TransformerModel.html | 8 +- ...dablePretrainedLLAMA3TransformerModel.html | 8 +- ...dablePretrainedM2M100TransformerModel.html | 8 +- .../ReadablePretrainedMarianMTModel.html | 8 +- ...ablePretrainedMistralTransformerModel.html | 8 +- ...eadablePretrainedNLLBTransformerModel.html | 8 +- ...eadablePretrainedOLMoTransformerModel.html | 8 +- ...eadablePretrainedPhi2TransformerModel.html | 8 +- ...eadablePretrainedPhi3TransformerModel.html | 8 +- ...eadablePretrainedPhi4TransformerModel.html | 8 +- ...eadablePretrainedQwenTransformerModel.html | 8 +- ...lePretrainedStarCoderTransformerModel.html | 8 +- .../ReadablePretrainedT5TransformerModel.html | 8 +- .../seq2seq/StarCoderTransformer$.html | 8 +- .../seq2seq/StarCoderTransformer.html | 8 +- .../annotators/seq2seq/T5Transformer$.html | 8 +- .../nlp/annotators/seq2seq/T5Transformer.html | 8 +- .../nlp/annotators/seq2seq/index.html | 8 +- .../DocumentSimilarityRankerApproach$.html | 8 +- .../DocumentSimilarityRankerApproach.html | 8 +- .../DocumentSimilarityRankerModel$.html | 8 +- .../DocumentSimilarityRankerModel.html | 8 +- .../similarity/DocumentSimilarityUtil$.html | 8 +- .../similarity/IndexedNeighbors.html | 8 +- .../IndexedNeighborsWithDistance.html | 8 +- .../similarity/NeighborAnnotation.html | 8 +- .../similarity/NeighborsResultSet.html | 8 +- .../ReadableDocumentSimilarityRanker.html | 8 +- .../nlp/annotators/similarity/index.html | 8 +- .../spell/context/CandidateStrategy$.html | 8 +- ...ntextSpellCheckerApproach$ArrayHelper.html | 8 +- .../context/ContextSpellCheckerApproach.html | 8 +- .../context/ContextSpellCheckerModel$.html | 8 +- .../ContextSpellCheckerModel$StringTools.html | 8 +- .../context/ContextSpellCheckerModel.html | 8 +- .../spell/context/HasTransducerFeatures.html | 8 +- .../spell/context/LangModelSentence.html | 8 +- .../ReadablePretrainedContextSpell.html | 8 +- .../context/ReadsLanguageModelGraph.html | 8 +- .../spell/context/WeightedLevenshtein.html | 8 +- .../nlp/annotators/spell/context/index.html | 8 +- .../spell/context/parser/AgeToken.html | 8 +- .../spell/context/parser/DateToken.html | 8 +- .../context/parser/GenericRegexParser.html | 8 +- .../context/parser/GenericVocabParser.html | 8 +- .../spell/context/parser/LocationClass.html | 8 +- .../spell/context/parser/MainVocab.html | 8 +- .../spell/context/parser/MedicationClass.html | 8 +- .../spell/context/parser/NamesClass.html | 8 +- .../spell/context/parser/NumberToken.html | 8 +- .../spell/context/parser/RegexParser.html | 8 +- .../context/parser/SerializableClass.html | 8 +- .../context/parser/SpecialClassParser.html | 8 +- .../context/parser/TransducerSeqFeature.html | 8 +- .../spell/context/parser/UnitToken.html | 8 +- .../spell/context/parser/VocabParser.html | 8 +- .../spell/context/parser/index.html | 8 +- .../nlp/annotators/spell/index.html | 8 +- .../spell/norvig/NorvigSweetingApproach$.html | 8 +- .../spell/norvig/NorvigSweetingApproach.html | 8 +- .../spell/norvig/NorvigSweetingModel$.html | 8 +- .../spell/norvig/NorvigSweetingModel.html | 8 +- .../spell/norvig/NorvigSweetingParams.html | 8 +- .../norvig/ReadablePretrainedNorvig.html | 8 +- .../nlp/annotators/spell/norvig/index.html | 8 +- .../ReadablePretrainedSymmetric.html | 8 +- .../symmetric/SymmetricDeleteApproach$.html | 8 +- .../symmetric/SymmetricDeleteApproach.html | 8 +- .../symmetric/SymmetricDeleteModel$.html | 8 +- .../SymmetricDeleteModel$SuggestedWord.html | 8 +- .../spell/symmetric/SymmetricDeleteModel.html | 8 +- .../symmetric/SymmetricDeleteParams.html | 8 +- .../nlp/annotators/spell/symmetric/index.html | 8 +- .../nlp/annotators/spell/util/Utilities$.html | 8 +- .../nlp/annotators/spell/util/index.html | 8 +- .../nlp/annotators/tapas/TapasCellDate$.html | 8 +- .../nlp/annotators/tapas/TapasCellDate.html | 8 +- .../nlp/annotators/tapas/TapasCellValue$.html | 8 +- .../nlp/annotators/tapas/TapasCellValue.html | 8 +- .../nlp/annotators/tapas/TapasEncoder.html | 8 +- .../nlp/annotators/tapas/TapasInputData.html | 8 +- .../tapas/TapasNumericRelation$.html | 8 +- .../tapas/TapasNumericValueSpan$.html | 8 +- .../tapas/TapasNumericValueSpan.html | 8 +- .../nlp/annotators/tapas/index.html | 8 +- .../tokenizer/bpe/BartTokenizer.html | 8 +- .../tokenizer/bpe/BertTokenizer.html | 8 +- .../tokenizer/bpe/BpeTokenizer$.html | 8 +- .../tokenizer/bpe/CLIPTokenizer.html | 8 +- .../tokenizer/bpe/Florence2Tokenizer.html | 8 +- .../tokenizer/bpe/Gemma3Tokenizer.html | 8 +- .../tokenizer/bpe/Gpt2Tokenizer.html | 8 +- .../tokenizer/bpe/InternVLTokenizer.html | 8 +- .../tokenizer/bpe/JanusTokenizer.html | 8 +- .../tokenizer/bpe/LLAMA3Tokenizer.html | 8 +- .../tokenizer/bpe/LLAVATokenizer.html | 8 +- .../tokenizer/bpe/MLLamaTokenizer.html | 8 +- .../tokenizer/bpe/OLMoTokenizer.html | 8 +- .../tokenizer/bpe/PaliGemmaTokenizer.html | 8 +- .../tokenizer/bpe/Phi2Tokenizer.html | 8 +- .../tokenizer/bpe/Phi3VisionTokenizer.html | 8 +- .../tokenizer/bpe/Phi4Tokenizer.html | 8 +- .../tokenizer/bpe/Qwen2VLTokenizer.html | 8 +- .../tokenizer/bpe/QwenTokenizer.html | 8 +- .../tokenizer/bpe/RobertaTokenizer.html | 8 +- .../tokenizer/bpe/SmolVLMTokenizer.html | 8 +- .../tokenizer/bpe/SpecialToken.html | 8 +- .../tokenizer/bpe/StarCoderTokenizer.html | 8 +- .../tokenizer/bpe/WhisperTokenDecoder.html | 8 +- .../nlp/annotators/tokenizer/bpe/index.html | 8 +- .../nlp/annotators/tokenizer/index.html | 8 +- .../ws/ReadablePretrainedWordSegmenter.html | 8 +- .../nlp/annotators/ws/TagsType$.html | 8 +- .../annotators/ws/WordSegmenterApproach$.html | 8 +- .../annotators/ws/WordSegmenterApproach.html | 8 +- .../annotators/ws/WordSegmenterModel$.html | 8 +- .../nlp/annotators/ws/WordSegmenterModel.html | 8 +- .../johnsnowlabs/nlp/annotators/ws/index.html | 8 +- .../nlp/embeddings/AlbertEmbeddings$.html | 8 +- .../nlp/embeddings/AlbertEmbeddings.html | 8 +- .../nlp/embeddings/AutoGGUFEmbeddings$.html | 8 +- .../nlp/embeddings/AutoGGUFEmbeddings.html | 8 +- .../nlp/embeddings/BGEEmbeddings$.html | 8 +- .../nlp/embeddings/BGEEmbeddings.html | 8 +- .../nlp/embeddings/BertEmbeddings$.html | 8 +- .../nlp/embeddings/BertEmbeddings.html | 8 +- .../embeddings/BertSentenceEmbeddings$.html | 8 +- .../embeddings/BertSentenceEmbeddings.html | 8 +- .../nlp/embeddings/CamemBertEmbeddings$.html | 8 +- .../nlp/embeddings/CamemBertEmbeddings.html | 8 +- .../nlp/embeddings/ChunkEmbeddings$.html | 8 +- .../nlp/embeddings/ChunkEmbeddings.html | 8 +- .../nlp/embeddings/DeBertaEmbeddings$.html | 8 +- .../nlp/embeddings/DeBertaEmbeddings.html | 8 +- .../nlp/embeddings/DistilBertEmbeddings$.html | 8 +- .../nlp/embeddings/DistilBertEmbeddings.html | 8 +- .../nlp/embeddings/Doc2VecApproach$.html | 8 +- .../nlp/embeddings/Doc2VecApproach.html | 8 +- .../nlp/embeddings/Doc2VecModel$.html | 8 +- .../nlp/embeddings/Doc2VecModel.html | 8 +- .../nlp/embeddings/E5Embeddings$.html | 8 +- .../nlp/embeddings/E5Embeddings.html | 8 +- .../nlp/embeddings/E5VEmbeddings$.html | 8 +- .../nlp/embeddings/E5VEmbeddings.html | 8 +- .../nlp/embeddings/ElmoEmbeddings$.html | 8 +- .../nlp/embeddings/ElmoEmbeddings.html | 8 +- .../EmbeddingsCoverage$CoverageResult.html | 8 +- .../nlp/embeddings/EmbeddingsCoverage.html | 8 +- .../embeddings/HasEmbeddingsProperties.html | 8 +- .../nlp/embeddings/InstructorEmbeddings$.html | 8 +- .../nlp/embeddings/InstructorEmbeddings.html | 8 +- .../nlp/embeddings/LongformerEmbeddings$.html | 8 +- .../nlp/embeddings/LongformerEmbeddings.html | 8 +- .../nlp/embeddings/MPNetEmbeddings$.html | 8 +- .../nlp/embeddings/MPNetEmbeddings.html | 8 +- .../nlp/embeddings/MiniLMEmbeddings$.html | 8 +- .../nlp/embeddings/MiniLMEmbeddings.html | 8 +- .../nlp/embeddings/MxbaiEmbeddings$.html | 8 +- .../nlp/embeddings/MxbaiEmbeddings.html | 8 +- .../nlp/embeddings/NomicEmbeddings$.html | 8 +- .../nlp/embeddings/NomicEmbeddings.html | 8 +- .../PoolingStrategy$$AnnotatorType$.html | 8 +- .../nlp/embeddings/PoolingStrategy$.html | 8 +- .../nlp/embeddings/ReadAlbertDLModel.html | 8 +- .../embeddings/ReadAutoGGUFEmbeddings.html | 8 +- .../nlp/embeddings/ReadBGEDLModel.html | 8 +- .../nlp/embeddings/ReadBertDLModel.html | 8 +- .../embeddings/ReadBertSentenceDLModel.html | 8 +- .../nlp/embeddings/ReadCamemBertDLModel.html | 8 +- .../nlp/embeddings/ReadDeBertaDLModel.html | 8 +- .../nlp/embeddings/ReadDistilBertDLModel.html | 8 +- .../nlp/embeddings/ReadE5DLModel.html | 8 +- .../embeddings/ReadE5VEmbeddingsDLModel.html | 8 +- .../nlp/embeddings/ReadElmoDLModel.html | 8 +- .../nlp/embeddings/ReadInstructorDLModel.html | 8 +- .../nlp/embeddings/ReadLongformerDLModel.html | 8 +- .../nlp/embeddings/ReadMPNetDLModel.html | 8 +- .../nlp/embeddings/ReadMiniLMDLModel.html | 8 +- .../nlp/embeddings/ReadMxbaiDLModel.html | 8 +- .../ReadNomicEmbeddingsDLModel.html | 8 +- .../nlp/embeddings/ReadRobertaDLModel.html | 8 +- .../ReadRobertaSentenceDLModel.html | 8 +- .../nlp/embeddings/ReadSnowFlakeDLModel.html | 8 +- .../nlp/embeddings/ReadUAEDLModel.html | 8 +- .../nlp/embeddings/ReadUSEDLModel.html | 8 +- .../nlp/embeddings/ReadXlmRobertaDLModel.html | 8 +- .../ReadXlmRobertaSentenceDLModel.html | 8 +- .../nlp/embeddings/ReadXlnetDLModel.html | 8 +- .../ReadablePretrainedAlbertModel.html | 8 +- .../ReadablePretrainedAutoGGUFEmbeddings.html | 8 +- .../ReadablePretrainedBGEModel.html | 8 +- .../ReadablePretrainedBertModel.html | 8 +- .../ReadablePretrainedBertSentenceModel.html | 8 +- .../ReadablePretrainedCamemBertModel.html | 8 +- .../ReadablePretrainedDeBertaModel.html | 8 +- .../ReadablePretrainedDistilBertModel.html | 8 +- .../embeddings/ReadablePretrainedDoc2Vec.html | 8 +- .../embeddings/ReadablePretrainedE5Model.html | 8 +- .../ReadablePretrainedE5VEmbeddings.html | 8 +- .../ReadablePretrainedElmoModel.html | 8 +- .../ReadablePretrainedInstructorModel.html | 8 +- .../ReadablePretrainedLongformerModel.html | 8 +- .../ReadablePretrainedMPNetModel.html | 8 +- .../ReadablePretrainedMiniLMModel.html | 8 +- .../ReadablePretrainedMxbaiModel.html | 8 +- ...eadablePretrainedNomicEmbeddingsModel.html | 8 +- .../ReadablePretrainedRobertaModel.html | 8 +- ...eadablePretrainedRobertaSentenceModel.html | 8 +- .../ReadablePretrainedSnowFlakeModel.html | 8 +- .../ReadablePretrainedUAEModel.html | 8 +- .../ReadablePretrainedUSEModel.html | 8 +- .../ReadablePretrainedWord2Vec.html | 8 +- .../ReadablePretrainedWordEmbeddings.html | 8 +- .../ReadablePretrainedXlmRobertaModel.html | 8 +- ...ablePretrainedXlmRobertaSentenceModel.html | 8 +- .../ReadablePretrainedXlnetModel.html | 8 +- .../nlp/embeddings/ReadsFromBytes.html | 8 +- .../nlp/embeddings/RoBertaEmbeddings$.html | 8 +- .../nlp/embeddings/RoBertaEmbeddings.html | 8 +- .../RoBertaSentenceEmbeddings$.html | 8 +- .../embeddings/RoBertaSentenceEmbeddings.html | 8 +- .../nlp/embeddings/SentenceEmbeddings$.html | 8 +- .../nlp/embeddings/SentenceEmbeddings.html | 8 +- .../nlp/embeddings/SnowFlakeEmbeddings$.html | 8 +- .../nlp/embeddings/SnowFlakeEmbeddings.html | 8 +- .../nlp/embeddings/UAEEmbeddings$.html | 8 +- .../nlp/embeddings/UAEEmbeddings.html | 8 +- .../embeddings/UniversalSentenceEncoder$.html | 8 +- .../embeddings/UniversalSentenceEncoder.html | 8 +- .../nlp/embeddings/Word2VecApproach$.html | 8 +- .../nlp/embeddings/Word2VecApproach.html | 8 +- .../nlp/embeddings/Word2VecModel$.html | 8 +- .../nlp/embeddings/Word2VecModel.html | 8 +- .../nlp/embeddings/WordEmbeddings$.html | 8 +- .../nlp/embeddings/WordEmbeddings.html | 8 +- .../WordEmbeddingsBinaryIndexer$.html | 8 +- .../nlp/embeddings/WordEmbeddingsModel$.html | 8 +- .../nlp/embeddings/WordEmbeddingsModel.html | 8 +- .../nlp/embeddings/WordEmbeddingsReader.html | 8 +- .../WordEmbeddingsTextIndexer$.html | 8 +- .../nlp/embeddings/WordEmbeddingsWriter.html | 8 +- .../nlp/embeddings/XlmRoBertaEmbeddings$.html | 8 +- .../nlp/embeddings/XlmRoBertaEmbeddings.html | 8 +- .../XlmRoBertaSentenceEmbeddings$.html | 8 +- .../XlmRoBertaSentenceEmbeddings.html | 8 +- .../nlp/embeddings/XlnetEmbeddings$.html | 8 +- .../nlp/embeddings/XlnetEmbeddings.html | 8 +- .../johnsnowlabs/nlp/embeddings/index.html | 8 +- .../DocumentSimilarityRankerFinisher$.html | 8 +- .../DocumentSimilarityRankerFinisher.html | 8 +- .../nlp/finisher/GGUFRankingFinisher$.html | 8 +- .../nlp/finisher/GGUFRankingFinisher.html | 8 +- .../com/johnsnowlabs/nlp/finisher/index.html | 8 +- .../nlp/functions$$EachAnnotations.html | 8 +- .../nlp/functions$$ExplodeAnnotations.html | 8 +- .../nlp/functions$$FilterAnnotations.html | 8 +- .../nlp/functions$$MapAnnotations.html | 8 +- docs/api/com/johnsnowlabs/nlp/functions$.html | 8 +- docs/api/com/johnsnowlabs/nlp/index.html | 8 +- .../nlp/pretrained/PretrainedPipeline$.html | 8 +- .../nlp/pretrained/PretrainedPipeline.html | 8 +- .../pretrained/PythonResourceDownloader$.html | 8 +- .../nlp/pretrained/RepositoryMetadata.html | 8 +- .../nlp/pretrained/ResourceDownloader$.html | 8 +- .../nlp/pretrained/ResourceDownloader.html | 8 +- .../nlp/pretrained/ResourceMetadata$.html | 8 +- .../nlp/pretrained/ResourceMetadata.html | 8 +- .../nlp/pretrained/ResourceRequest.html | 8 +- .../nlp/pretrained/ResourceType$.html | 8 +- .../nlp/pretrained/S3ResourceDownloader.html | 8 +- .../johnsnowlabs/nlp/pretrained/index.html | 8 +- .../com/johnsnowlabs/nlp/recursive/index.html | 8 +- .../nlp/recursive/package$$Recursive.html | 8 +- .../recursive/package$$RecursiveModel.html | 8 +- .../nlp/serialization/ArrayFeature.html | 8 +- .../nlp/serialization/Feature.html | 8 +- .../nlp/serialization/MapFeature.html | 8 +- .../SerializedExternalResource.html | 8 +- .../nlp/serialization/SetFeature.html | 8 +- .../nlp/serialization/StructFeature.html | 8 +- .../nlp/serialization/TransducerFeature.html | 8 +- .../johnsnowlabs/nlp/serialization/index.html | 8 +- .../com/johnsnowlabs/nlp/training/CoNLL.html | 8 +- .../nlp/training/CoNLL2003NerReader.html | 8 +- .../nlp/training/CoNLLDocument.html | 8 +- .../CoNLLHelper$$CoNLLSentenceCols.html | 8 +- .../training/CoNLLHelper$$CoNLLTokenCols.html | 8 +- .../nlp/training/CoNLLHelper$.html | 8 +- .../com/johnsnowlabs/nlp/training/CoNLLU.html | 8 +- .../nlp/training/CoNLLUCols$.html | 8 +- .../nlp/training/CoNLLUDocument.html | 8 +- .../com/johnsnowlabs/nlp/training/POS.html | 8 +- .../johnsnowlabs/nlp/training/PubTator.html | 8 +- .../nlp/training/SpacyToAnnotation.html | 8 +- .../com/johnsnowlabs/nlp/training/index.html | 8 +- .../nlp/util/EmbeddingsDataFrameUtils$.html | 8 +- .../johnsnowlabs/nlp/util/FinisherUtil$.html | 8 +- .../johnsnowlabs/nlp/util/GraphBuilder.html | 8 +- .../nlp/util/LfuCache$CachedItem.html | 8 +- .../nlp/util/LfuCache$DoubleLinked.html | 8 +- .../nlp/util/LfuCache$FrequencyList.html | 8 +- .../com/johnsnowlabs/nlp/util/LfuCache.html | 8 +- .../nlp/util/LruMap$KeyPriority.html | 8 +- .../nlp/util/LruMap$KeyPriorityOrdering$.html | 8 +- .../api/com/johnsnowlabs/nlp/util/LruMap.html | 8 +- .../nlp/util/SparkNlpConfig$.html | 8 +- docs/api/com/johnsnowlabs/nlp/util/index.html | 8 +- .../nlp/util/io/CloudStorageType$.html | 8 +- .../nlp/util/io/ExternalResource$.html | 8 +- .../nlp/util/io/ExternalResource.html | 8 +- .../nlp/util/io/MatchStrategy$.html | 8 +- .../nlp/util/io/OutputHelper$.html | 8 +- .../com/johnsnowlabs/nlp/util/io/ReadAs$.html | 8 +- .../util/io/ResourceHelper$$SourceStream.html | 8 +- .../nlp/util/io/ResourceHelper$.html | 51 +- .../com/johnsnowlabs/nlp/util/io/index.html | 8 +- .../nlp/util/regex/RegexRule.html | 8 +- .../util/regex/RuleFactory$$RuleMatch.html | 8 +- .../nlp/util/regex/RuleFactory$.html | 8 +- .../nlp/util/regex/RuleFactory.html | 8 +- .../nlp/util/regex/TransformStrategy$.html | 8 +- .../johnsnowlabs/nlp/util/regex/index.html | 8 +- .../johnsnowlabs/partition/BasicChunker$.html | 20 +- .../api/com/johnsnowlabs/partition/Chunk.html | 20 +- .../partition/HasBinaryReaderProperties.html | 1597 +++++++++ .../partition/HasChunkerProperties.html | 20 +- .../partition/HasEmailReaderProperties.html | 22 +- .../partition/HasExcelReaderProperties.html | 22 +- .../partition/HasHTMLReaderProperties.html | 22 +- .../partition/HasPdfReaderProperties.html | 1461 ++++++++ .../partition/HasPowerPointProperties.html | 22 +- .../partition/HasReaderProperties.html | 166 +- .../partition/HasTagsReaderProperties.html | 1625 +++++++++ .../partition/HasTextReaderProperties.html | 22 +- .../partition/HasXmlReaderProperties.html | 22 +- .../johnsnowlabs/partition/Partition$.html | 20 +- .../com/johnsnowlabs/partition/Partition.html | 20 +- .../partition/PartitionChunker.html | 20 +- .../partition/PartitionTransformer.html | 294 +- .../johnsnowlabs/partition/TitleChunker$.html | 20 +- .../api/com/johnsnowlabs/partition/index.html | 72 +- .../partition/util/PartitionHelper$.html | 8 +- .../johnsnowlabs/partition/util/index.html | 8 +- .../com/johnsnowlabs/reader/CSVReader.html | 36 +- .../com/johnsnowlabs/reader/ElementType$.html | 52 +- .../com/johnsnowlabs/reader/EmailReader.html | 20 +- .../com/johnsnowlabs/reader/ExcelReader.html | 20 +- .../com/johnsnowlabs/reader/HTMLElement.html | 44 +- .../com/johnsnowlabs/reader/HTMLReader.html | 20 +- .../johnsnowlabs/reader/HasReaderContent.html | 2141 +++++++++++ .../johnsnowlabs/reader/MarkdownReader.html | 20 +- .../com/johnsnowlabs/reader/MimeType$.html | 20 +- .../com/johnsnowlabs/reader/PdfReader.html | 26 +- .../com/johnsnowlabs/reader/PdfToText$.html | 20 +- .../com/johnsnowlabs/reader/PdfToText.html | 150 +- .../johnsnowlabs/reader/PdfToTextTrait.html | 20 +- .../johnsnowlabs/reader/PowerPointReader.html | 20 +- .../com/johnsnowlabs/reader/Reader2Doc$.html | 20 +- .../com/johnsnowlabs/reader/Reader2Doc.html | 364 +- .../com/johnsnowlabs/reader/Reader2Image.html | 2989 ++++++++++++++++ .../johnsnowlabs/reader/Reader2Table$.html | 20 +- .../com/johnsnowlabs/reader/Reader2Table.html | 358 +- .../johnsnowlabs/reader/ReaderAssembler.html | 3171 +++++++++++++++++ .../johnsnowlabs/reader/SparkNLPReader.html | 20 +- .../com/johnsnowlabs/reader/TextReader.html | 20 +- .../com/johnsnowlabs/reader/WordReader.html | 20 +- .../com/johnsnowlabs/reader/XMLReader.html | 20 +- docs/api/com/johnsnowlabs/reader/index.html | 157 +- .../reader/util/DocParser$$RichParagraph.html | 8 +- .../johnsnowlabs/reader/util/DocParser$.html | 24 +- .../util/DocxParser$$RichXWPFDocument.html | 8 +- .../util/DocxParser$$RichXWPFParagraph.html | 8 +- .../util/DocxParser$$RichXWPFTable.html | 8 +- .../johnsnowlabs/reader/util/DocxParser$.html | 24 +- .../reader/util/EmailParser$.html | 714 ++++ .../johnsnowlabs/reader/util/HTMLParser$.html | 24 +- ...rties.html => HasPdfToTextProperties.html} | 332 +- .../reader/util/ImageParser$.html | 748 ++++ .../reader/util/ImagePromptTemplate$.html | 730 ++++ .../reader/util/PartitionOptions$.html | 24 +- .../reader/util/PptParser$$RichHSLFSlide.html | 8 +- .../reader/util/PptParser$$RichXSLFSlide.html | 8 +- .../johnsnowlabs/reader/util/PptParser$.html | 24 +- .../johnsnowlabs/reader/util/TextParser$.html | 24 +- .../reader/util/XlsxParser$$RichCell.html | 8 +- .../reader/util/XlsxParser$$RichRow.html | 8 +- .../reader/util/XlsxParser$$RichSheet.html | 8 +- .../johnsnowlabs/reader/util/XlsxParser$.html | 24 +- .../com/johnsnowlabs/reader/util/index.html | 80 +- .../reader/util/pdf/BinaryFile.html | 8 +- .../reader/util/pdf/CustomStripper.html | 8 +- .../reader/util/pdf/CustomTextLine.html | 8 +- .../reader/util/pdf/HasInputValidator.html | 8 +- .../reader/util/pdf/HasLocalProcess.html | 8 +- .../johnsnowlabs/reader/util/pdf/OcrText.html | 8 +- .../util/pdf/PDFLayoutTextStripper.html | 8 +- .../johnsnowlabs/reader/util/pdf/PageNum.html | 8 +- .../reader/util/pdf/PdfPipelineException.html | 8 +- .../util/pdf/PdfUtils$ChainException.html | 8 +- .../util/pdf/PdfUtils$exceptionUtils.html | 8 +- .../reader/util/pdf/PdfUtils.html | 8 +- .../reader/util/pdf/PositionsOutput.html | 8 +- .../reader/util/pdf/TextStripperType$.html | 8 +- .../reader/util/pdf/UnicodeUtils.html | 8 +- .../johnsnowlabs/reader/util/pdf/index.html | 8 +- .../util/pdf/schema/MappingMatrix$.html | 8 +- .../reader/util/pdf/schema/MappingMatrix.html | 8 +- .../reader/util/pdf/schema/PageMatrix$.html | 8 +- .../reader/util/pdf/schema/PageMatrix.html | 8 +- .../reader/util/pdf/schema/index.html | 8 +- .../com/johnsnowlabs/storage/BytesKey.html | 8 +- .../com/johnsnowlabs/storage/Database$.html | 8 +- .../com/johnsnowlabs/storage/Database.html | 8 +- .../johnsnowlabs/storage/HasConnection.html | 8 +- .../com/johnsnowlabs/storage/HasStorage.html | 8 +- .../johnsnowlabs/storage/HasStorageModel.html | 8 +- .../storage/HasStorageOptions.html | 8 +- .../storage/HasStorageReader.html | 8 +- .../johnsnowlabs/storage/HasStorageRef$.html | 8 +- .../johnsnowlabs/storage/HasStorageRef.html | 8 +- .../storage/RocksDBConnection$.html | 8 +- .../storage/RocksDBConnection.html | 8 +- .../storage/StorageBatchWriter.html | 8 +- .../johnsnowlabs/storage/StorageFormat.html | 8 +- .../johnsnowlabs/storage/StorageHelper$.html | 8 +- .../johnsnowlabs/storage/StorageLocator$.html | 8 +- .../johnsnowlabs/storage/StorageLocator.html | 8 +- .../storage/StorageReadWriter.html | 8 +- .../johnsnowlabs/storage/StorageReadable.html | 8 +- .../johnsnowlabs/storage/StorageReader.html | 8 +- .../johnsnowlabs/storage/StorageWriter.html | 8 +- docs/api/com/johnsnowlabs/storage/index.html | 8 +- .../api/com/johnsnowlabs/util/Benchmark$.html | 8 +- docs/api/com/johnsnowlabs/util/Build$.html | 8 +- .../johnsnowlabs/util/CoNLLGenerator$.html | 8 +- .../com/johnsnowlabs/util/ConfigHelper$.html | 8 +- .../com/johnsnowlabs/util/ConfigLoader$.html | 8 +- .../com/johnsnowlabs/util/FileHelper$.html | 8 +- .../com/johnsnowlabs/util/JsonBuilder$.html | 8 +- .../com/johnsnowlabs/util/JsonParser$.html | 8 +- .../johnsnowlabs/util/PipelineModels$.html | 8 +- .../johnsnowlabs/util/TrainingHelper$.html | 8 +- docs/api/com/johnsnowlabs/util/Version$.html | 8 +- docs/api/com/johnsnowlabs/util/Version.html | 8 +- .../johnsnowlabs/util/ZipArchiveUtil$.html | 8 +- docs/api/com/johnsnowlabs/util/index.html | 8 +- .../util/spark/LongMapAccumulator.html | 8 +- .../util/spark/MapAccumulator.html | 8 +- .../johnsnowlabs/util/spark/SparkUtil$.html | 8 +- .../com/johnsnowlabs/util/spark/index.html | 8 +- docs/api/index.html | 8 +- docs/api/index.js | 2 +- docs/api/python/.buildinfo | 2 +- docs/api/python/_api/modules.html | 8 +- docs/api/python/_api/sparknlp.annotation.html | 10 +- .../_api/sparknlp.annotation_audio.html | 10 +- .../_api/sparknlp.annotation_image.html | 10 +- .../python/_api/sparknlp.annotator.audio.html | 10 +- ...arknlp.annotator.audio.hubert_for_ctc.html | 10 +- ...knlp.annotator.audio.wav2vec2_for_ctc.html | 10 +- ...rknlp.annotator.audio.whisper_for_ctc.html | 10 +- .../_api/sparknlp.annotator.chunk2_doc.html | 10 +- .../_api/sparknlp.annotator.chunker.html | 10 +- ...ssifier_dl.albert_for_multiple_choice.html | 10 +- ...fier_dl.albert_for_question_answering.html | 10 +- ...dl.albert_for_sequence_classification.html | 10 +- ...er_dl.albert_for_token_classification.html | 10 +- ...l.albert_for_zero_shot_classification.html | 10 +- ..._dl.bart_for_zero_shot_classification.html | 10 +- ...lassifier_dl.bert_for_multiple_choice.html | 10 +- ...sifier_dl.bert_for_question_answering.html | 10 +- ...r_dl.bert_for_sequence_classification.html | 10 +- ...fier_dl.bert_for_token_classification.html | 10 +- ..._dl.bert_for_zero_shot_classification.html | 10 +- ...r_dl.camembert_for_question_answering.html | 10 +- ...camembert_for_sequence_classification.html | 10 +- ...dl.camembert_for_token_classification.html | 10 +- ...amembert_for_zero_shot_classification.html | 10 +- ...annotator.classifier_dl.classifier_dl.html | 10 +- ...ier_dl.deberta_for_question_answering.html | 10 +- ...l.deberta_for_sequence_classification.html | 10 +- ...r_dl.deberta_for_token_classification.html | 10 +- ....deberta_for_zero_shot_classification.html | 10 +- ...dl.distil_bert_for_question_answering.html | 10 +- ...stil_bert_for_sequence_classification.html | 10 +- ....distil_bert_for_token_classification.html | 10 +- ...til_bert_for_zero_shot_classification.html | 10 +- ...ier_dl.distilbert_for_multiple_choice.html | 10 +- .../sparknlp.annotator.classifier_dl.html | 10 +- ..._dl.longformer_for_question_answering.html | 10 +- ...ongformer_for_sequence_classification.html | 10 +- ...l.longformer_for_token_classification.html | 10 +- ...ifier_dl.mpnet_for_question_answering.html | 10 +- ..._dl.mpnet_for_sequence_classification.html | 10 +- ...ier_dl.mpnet_for_token_classification.html | 10 +- ...tor.classifier_dl.multi_classifier_dl.html | 10 +- ...sifier_dl.roberta_for_multiple_choice.html | 10 +- ...ier_dl.roberta_for_question_answering.html | 10 +- ...l.roberta_for_sequence_classification.html | 10 +- ...r_dl.roberta_for_token_classification.html | 10 +- ....roberta_for_zero_shot_classification.html | 10 +- ....annotator.classifier_dl.sentiment_dl.html | 10 +- ...ifier_dl.tapas_for_question_answering.html | 10 +- ...er_dl.xlm_roberta_for_multiple_choice.html | 10 +- ...dl.xlm_roberta_for_question_answering.html | 10 +- ...m_roberta_for_sequence_classification.html | 10 +- ....xlm_roberta_for_token_classification.html | 10 +- ..._roberta_for_zero_shot_classification.html | 10 +- ..._dl.xlnet_for_sequence_classification.html | 10 +- ...ier_dl.xlnet_for_token_classification.html | 10 +- .../sparknlp.annotator.cleaners.cleaner.html | 10 +- ...sparknlp.annotator.cleaners.extractor.html | 10 +- .../_api/sparknlp.annotator.cleaners.html | 10 +- .../python/_api/sparknlp.annotator.coref.html | 10 +- ...arknlp.annotator.coref.spanbert_coref.html | 10 +- ...otator.cv.blip_for_question_answering.html | 10 +- ....cv.clip_for_zero_shot_classification.html | 10 +- ....cv.convnext_for_image_classification.html | 10 +- ...lp.annotator.cv.florence2_transformer.html | 10 +- ...lp.annotator.cv.gemma3_for_multimodal.html | 10 +- .../python/_api/sparknlp.annotator.cv.html | 10 +- ....annotator.cv.internvl_for_multimodal.html | 10 +- ...nlp.annotator.cv.janus_for_multimodal.html | 10 +- ...nlp.annotator.cv.llava_for_multimodal.html | 10 +- ...lp.annotator.cv.mllama_for_multimodal.html | 10 +- ...annotator.cv.paligemma_for_multimodal.html | 10 +- ...notator.cv.phi3_vision_for_multimodal.html | 10 +- ...knlp.annotator.cv.qwen2vl_transformer.html | 10 +- ...knlp.annotator.cv.smolvlm_transformer.html | 10 +- ...ator.cv.swin_for_image_classification.html | 10 +- ..._encoder_decoder_for_image_captioning.html | 10 +- ...tator.cv.vit_for_image_classification.html | 10 +- ...parknlp.annotator.dataframe_optimizer.html | 10 +- .../_api/sparknlp.annotator.date2_chunk.html | 10 +- ...nnotator.dependency.dependency_parser.html | 10 +- .../_api/sparknlp.annotator.dependency.html | 10 +- ...or.dependency.typed_dependency_parser.html | 10 +- ...ator.document_character_text_splitter.html | 10 +- ...parknlp.annotator.document_normalizer.html | 10 +- ...nlp.annotator.document_token_splitter.html | 10 +- ...nnotator.document_token_splitter_test.html | 10 +- ...nnotator.embeddings.albert_embeddings.html | 10 +- ...tator.embeddings.auto_gguf_embeddings.html | 10 +- ....annotator.embeddings.bert_embeddings.html | 10 +- ...r.embeddings.bert_sentence_embeddings.html | 10 +- ...p.annotator.embeddings.bge_embeddings.html | 10 +- ...tator.embeddings.camembert_embeddings.html | 10 +- ...annotator.embeddings.chunk_embeddings.html | 10 +- ...notator.embeddings.deberta_embeddings.html | 10 +- ...tor.embeddings.distil_bert_embeddings.html | 10 +- ...sparknlp.annotator.embeddings.doc2vec.html | 10 +- ...lp.annotator.embeddings.e5_embeddings.html | 10 +- ...p.annotator.embeddings.e5v_embeddings.html | 10 +- ....annotator.embeddings.elmo_embeddings.html | 10 +- .../_api/sparknlp.annotator.embeddings.html | 10 +- ...ator.embeddings.instructor_embeddings.html | 10 +- ...ator.embeddings.longformer_embeddings.html | 10 +- ...nnotator.embeddings.minilm_embeddings.html | 10 +- ...annotator.embeddings.mpnet_embeddings.html | 10 +- ...annotator.embeddings.mxbai_embeddings.html | 10 +- ...annotator.embeddings.nomic_embeddings.html | 10 +- ...notator.embeddings.roberta_embeddings.html | 10 +- ...mbeddings.roberta_sentence_embeddings.html | 10 +- ...otator.embeddings.sentence_embeddings.html | 10 +- ...tator.embeddings.snowflake_embeddings.html | 10 +- ...p.annotator.embeddings.uae_embeddings.html | 10 +- ...embeddings.universal_sentence_encoder.html | 10 +- ...parknlp.annotator.embeddings.word2vec.html | 10 +- ....annotator.embeddings.word_embeddings.html | 10 +- ...tor.embeddings.xlm_roberta_embeddings.html | 10 +- ...dings.xlm_roberta_sentence_embeddings.html | 10 +- ...annotator.embeddings.xlnet_embeddings.html | 10 +- .../sparknlp.annotator.er.entity_ruler.html | 10 +- .../python/_api/sparknlp.annotator.er.html | 10 +- .../sparknlp.annotator.graph_extraction.html | 10 +- docs/api/python/_api/sparknlp.annotator.html | 10 +- ...sparknlp.annotator.keyword_extraction.html | 10 +- ...rd_extraction.yake_keyword_extraction.html | 10 +- .../python/_api/sparknlp.annotator.ld_dl.html | 10 +- ....annotator.ld_dl.language_detector_dl.html | 10 +- .../_api/sparknlp.annotator.lemmatizer.html | 10 +- ...lp.annotator.matcher.big_text_matcher.html | 10 +- ...arknlp.annotator.matcher.date_matcher.html | 10 +- .../_api/sparknlp.annotator.matcher.html | 10 +- ....annotator.matcher.multi_date_matcher.html | 10 +- ...rknlp.annotator.matcher.regex_matcher.html | 10 +- ...arknlp.annotator.matcher.text_matcher.html | 10 +- .../sparknlp.annotator.n_gram_generator.html | 10 +- .../python/_api/sparknlp.annotator.ner.html | 10 +- .../sparknlp.annotator.ner.ner_approach.html | 10 +- .../sparknlp.annotator.ner.ner_converter.html | 10 +- .../_api/sparknlp.annotator.ner.ner_crf.html | 10 +- .../_api/sparknlp.annotator.ner.ner_dl.html | 10 +- ...lp.annotator.ner.ner_dl_graph_checker.html | 10 +- ...sparknlp.annotator.ner.ner_overwriter.html | 10 +- ...nlp.annotator.ner.zero_shot_ner_model.html | 10 +- .../_api/sparknlp.annotator.normalizer.html | 10 +- .../_api/sparknlp.annotator.openai.html | 10 +- ...lp.annotator.openai.openai_completion.html | 10 +- ...lp.annotator.openai.openai_embeddings.html | 10 +- ...lp.annotator.param.classifier_encoder.html | 10 +- ....annotator.param.evaluation_dl_params.html | 10 +- .../python/_api/sparknlp.annotator.param.html | 10 +- .../python/_api/sparknlp.annotator.pos.html | 10 +- .../sparknlp.annotator.pos.perceptron.html | 10 +- .../_api/sparknlp.annotator.sentence.html | 10 +- ....annotator.sentence.sentence_detector.html | 10 +- ...notator.sentence.sentence_detector_dl.html | 10 +- .../_api/sparknlp.annotator.sentiment.html | 10 +- ...nnotator.sentiment.sentiment_detector.html | 10 +- ....annotator.sentiment.vivekn_sentiment.html | 10 +- ...nlp.annotator.seq2seq.auto_gguf_model.html | 10 +- ....annotator.seq2seq.auto_gguf_reranker.html | 10 +- ...otator.seq2seq.auto_gguf_vision_model.html | 10 +- ...lp.annotator.seq2seq.bart_transformer.html | 10 +- ....annotator.seq2seq.cohere_transformer.html | 10 +- ...nlp.annotator.seq2seq.cpm_transformer.html | 10 +- ...lp.annotator.seq2seq.gpt2_transformer.html | 10 +- .../_api/sparknlp.annotator.seq2seq.html | 10 +- ....annotator.seq2seq.llama2_transformer.html | 10 +- ....annotator.seq2seq.llama3_transformer.html | 10 +- ....annotator.seq2seq.m2m100_transformer.html | 10 +- ....annotator.seq2seq.marian_transformer.html | 10 +- ...annotator.seq2seq.mistral_transformer.html | 10 +- ...lp.annotator.seq2seq.nllb_transformer.html | 10 +- ...lp.annotator.seq2seq.olmo_transformer.html | 10 +- ...lp.annotator.seq2seq.phi2_transformer.html | 10 +- ...lp.annotator.seq2seq.phi3_transformer.html | 10 +- ...lp.annotator.seq2seq.phi4_transformer.html | 10 +- ...lp.annotator.seq2seq.qwen_transformer.html | 10 +- ...notator.seq2seq.starcoder_transformer.html | 10 +- ...knlp.annotator.seq2seq.t5_transformer.html | 10 +- ...similarity.document_similarity_ranker.html | 10 +- .../_api/sparknlp.annotator.similarity.html | 10 +- ...tor.spell_check.context_spell_checker.html | 10 +- .../_api/sparknlp.annotator.spell_check.html | 10 +- ...annotator.spell_check.norvig_sweeting.html | 10 +- ...nnotator.spell_check.symmetric_delete.html | 10 +- .../_api/sparknlp.annotator.stemmer.html | 10 +- ...sparknlp.annotator.stop_words_cleaner.html | 10 +- ...nlp.annotator.tf_ner_dl_graph_builder.html | 10 +- ...rknlp.annotator.token.chunk_tokenizer.html | 10 +- .../python/_api/sparknlp.annotator.token.html | 10 +- ...p.annotator.token.recursive_tokenizer.html | 10 +- ...rknlp.annotator.token.regex_tokenizer.html | 10 +- .../sparknlp.annotator.token.tokenizer.html | 10 +- .../_api/sparknlp.annotator.token2_chunk.html | 10 +- .../python/_api/sparknlp.annotator.ws.html | 10 +- .../sparknlp.annotator.ws.word_segmenter.html | 10 +- .../_api/sparknlp.base.audio_assembler.html | 10 +- .../python/_api/sparknlp.base.doc2_chunk.html | 10 +- .../sparknlp.base.document_assembler.html | 10 +- .../sparknlp.base.embeddings_finisher.html | 10 +- .../python/_api/sparknlp.base.finisher.html | 10 +- .../sparknlp.base.gguf_ranking_finisher.html | 10 +- .../_api/sparknlp.base.graph_finisher.html | 10 +- .../_api/sparknlp.base.has_recursive_fit.html | 10 +- ...sparknlp.base.has_recursive_transform.html | 10 +- docs/api/python/_api/sparknlp.base.html | 10 +- .../_api/sparknlp.base.image_assembler.html | 10 +- .../_api/sparknlp.base.light_pipeline.html | 10 +- ...parknlp.base.multi_document_assembler.html | 10 +- .../_api/sparknlp.base.prompt_assembler.html | 10 +- .../sparknlp.base.recursive_pipeline.html | 10 +- .../_api/sparknlp.base.table_assembler.html | 10 +- .../_api/sparknlp.base.token_assembler.html | 10 +- .../sparknlp.common.annotator_approach.html | 10 +- .../_api/sparknlp.common.annotator_model.html | 10 +- .../sparknlp.common.annotator_properties.html | 10 +- .../_api/sparknlp.common.annotator_type.html | 10 +- .../_api/sparknlp.common.coverage_result.html | 10 +- docs/api/python/_api/sparknlp.common.html | 10 +- .../_api/sparknlp.common.match_strategy.html | 10 +- .../_api/sparknlp.common.properties.html | 10 +- .../python/_api/sparknlp.common.read_as.html | 10 +- ...p.common.recursive_annotator_approach.html | 10 +- .../python/_api/sparknlp.common.storage.html | 10 +- .../python/_api/sparknlp.common.utils.html | 10 +- docs/api/python/_api/sparknlp.functions.html | 10 +- docs/api/python/_api/sparknlp.html | 20 +- .../sparknlp.internal.annotator_java_ml.html | 10 +- ...arknlp.internal.annotator_transformer.html | 10 +- ...arknlp.internal.extended_java_wrapper.html | 10 +- docs/api/python/_api/sparknlp.internal.html | 10 +- ...rknlp.internal.params_getters_setters.html | 10 +- .../_api/sparknlp.internal.recursive.html | 10 +- .../python/_api/sparknlp.logging.comet.html | 10 +- docs/api/python/_api/sparknlp.logging.html | 10 +- docs/api/python/_api/sparknlp.partition.html | 66 +- .../_api/sparknlp.partition.partition.html | 10 +- ...arknlp.partition.partition_properties.html | 552 ++- ...rknlp.partition.partition_transformer.html | 10 +- docs/api/python/_api/sparknlp.pretrained.html | 10 +- ...arknlp.pretrained.pretrained_pipeline.html | 10 +- ...arknlp.pretrained.resource_downloader.html | 10 +- .../_api/sparknlp.pretrained.utils.html | 10 +- .../python/_api/sparknlp.reader.enums.html | 10 +- docs/api/python/_api/sparknlp.reader.html | 72 +- .../_api/sparknlp.reader.pdf_to_text.html | 10 +- .../_api/sparknlp.reader.reader2doc.html | 179 +- .../_api/sparknlp.reader.reader2image.html | 914 +++++ .../_api/sparknlp.reader.reader2table.html | 159 +- .../sparknlp.reader.reader_assembler.html | 935 +++++ .../_api/sparknlp.reader.sparknlp_reader.html | 10 +- .../python/_api/sparknlp.training.conll.html | 10 +- .../python/_api/sparknlp.training.conllu.html | 10 +- docs/api/python/_api/sparknlp.training.html | 10 +- .../python/_api/sparknlp.training.pos.html | 10 +- .../_api/sparknlp.training.pub_tator.html | 10 +- ...sparknlp.training.spacy_to_annotation.html | 10 +- .../_api/sparknlp.training.tfgraphs.html | 10 +- .../python/_api/sparknlp.upload_to_hub.html | 10 +- docs/api/python/_api/sparknlp.util.html | 10 +- docs/api/python/genindex.html | 358 +- docs/api/python/getting_started/index.html | 26 +- docs/api/python/index.html | 8 +- docs/api/python/modules/index.html | 10 +- docs/api/python/modules/sparknlp.html | 10 +- .../python/modules/sparknlp/annotation.html | 8 +- .../modules/sparknlp/annotation_audio.html | 8 +- .../modules/sparknlp/annotation_image.html | 8 +- .../python/modules/sparknlp/annotator.html | 8 +- .../annotator/audio/hubert_for_ctc.html | 8 +- .../annotator/audio/wav2vec2_for_ctc.html | 8 +- .../annotator/audio/whisper_for_ctc.html | 8 +- .../sparknlp/annotator/chunk2_doc.html | 8 +- .../modules/sparknlp/annotator/chunker.html | 8 +- .../albert_for_multiple_choice.html | 8 +- .../albert_for_question_answering.html | 8 +- .../albert_for_sequence_classification.html | 8 +- .../albert_for_token_classification.html | 8 +- .../albert_for_zero_shot_classification.html | 8 +- .../bart_for_zero_shot_classification.html | 8 +- .../bert_for_multiple_choice.html | 8 +- .../bert_for_question_answering.html | 8 +- .../bert_for_sequence_classification.html | 8 +- .../bert_for_token_classification.html | 8 +- .../bert_for_zero_shot_classification.html | 8 +- .../camembert_for_question_answering.html | 8 +- ...camembert_for_sequence_classification.html | 8 +- .../camembert_for_token_classification.html | 8 +- ...amembert_for_zero_shot_classification.html | 8 +- .../classifier_dl/classifier_dl.html | 8 +- .../deberta_for_question_answering.html | 8 +- .../deberta_for_sequence_classification.html | 8 +- .../deberta_for_token_classification.html | 8 +- .../deberta_for_zero_shot_classification.html | 8 +- .../distil_bert_for_question_answering.html | 8 +- ...stil_bert_for_sequence_classification.html | 8 +- .../distil_bert_for_token_classification.html | 8 +- ...til_bert_for_zero_shot_classification.html | 8 +- .../distilbert_for_multiple_choice.html | 8 +- .../longformer_for_question_answering.html | 8 +- ...ongformer_for_sequence_classification.html | 8 +- .../longformer_for_token_classification.html | 8 +- .../mpnet_for_question_answering.html | 8 +- .../mpnet_for_sequence_classification.html | 8 +- .../mpnet_for_token_classification.html | 8 +- .../classifier_dl/multi_classifier_dl.html | 8 +- .../roberta_for_multiple_choice.html | 8 +- .../roberta_for_question_answering.html | 8 +- .../roberta_for_sequence_classification.html | 8 +- .../roberta_for_token_classification.html | 8 +- .../roberta_for_zero_shot_classification.html | 8 +- .../annotator/classifier_dl/sentiment_dl.html | 8 +- .../tapas_for_question_answering.html | 8 +- .../xlm_roberta_for_multiple_choice.html | 8 +- .../xlm_roberta_for_question_answering.html | 8 +- ...m_roberta_for_sequence_classification.html | 8 +- .../xlm_roberta_for_token_classification.html | 8 +- ..._roberta_for_zero_shot_classification.html | 8 +- .../xlnet_for_sequence_classification.html | 8 +- .../xlnet_for_token_classification.html | 8 +- .../sparknlp/annotator/cleaners/cleaner.html | 8 +- .../annotator/cleaners/extractor.html | 8 +- .../annotator/coref/spanbert_coref.html | 8 +- .../cv/blip_for_question_answering.html | 8 +- .../cv/clip_for_zero_shot_classification.html | 8 +- .../cv/convnext_for_image_classification.html | 8 +- .../annotator/cv/florence2_transformer.html | 8 +- .../annotator/cv/gemma3_for_multimodal.html | 8 +- .../annotator/cv/internvl_for_multimodal.html | 8 +- .../annotator/cv/janus_for_multimodal.html | 8 +- .../annotator/cv/llava_for_multimodal.html | 8 +- .../annotator/cv/mllama_for_multimodal.html | 8 +- .../cv/paligemma_for_multimodal.html | 8 +- .../cv/phi3_vision_for_multimodal.html | 8 +- .../annotator/cv/qwen2vl_transformer.html | 8 +- .../annotator/cv/smolvlm_transformer.html | 8 +- .../cv/swin_for_image_classification.html | 8 +- ..._encoder_decoder_for_image_captioning.html | 8 +- .../cv/vit_for_image_classification.html | 8 +- .../annotator/dataframe_optimizer.html | 8 +- .../sparknlp/annotator/date2_chunk.html | 8 +- .../dependency/dependency_parser.html | 8 +- .../dependency/typed_dependency_parser.html | 8 +- .../document_character_text_splitter.html | 8 +- .../annotator/document_normalizer.html | 8 +- .../annotator/document_token_splitter.html | 8 +- .../document_token_splitter_test.html | 8 +- .../embeddings/albert_embeddings.html | 8 +- .../embeddings/auto_gguf_embeddings.html | 8 +- .../annotator/embeddings/bert_embeddings.html | 8 +- .../embeddings/bert_sentence_embeddings.html | 8 +- .../annotator/embeddings/bge_embeddings.html | 8 +- .../embeddings/camembert_embeddings.html | 8 +- .../embeddings/chunk_embeddings.html | 8 +- .../embeddings/deberta_embeddings.html | 8 +- .../embeddings/distil_bert_embeddings.html | 8 +- .../annotator/embeddings/doc2vec.html | 8 +- .../annotator/embeddings/e5_embeddings.html | 8 +- .../annotator/embeddings/e5v_embeddings.html | 8 +- .../annotator/embeddings/elmo_embeddings.html | 8 +- .../embeddings/instructor_embeddings.html | 8 +- .../embeddings/longformer_embeddings.html | 8 +- .../embeddings/minilm_embeddings.html | 8 +- .../embeddings/mpnet_embeddings.html | 8 +- .../embeddings/mxbai_embeddings.html | 8 +- .../embeddings/nomic_embeddings.html | 8 +- .../embeddings/roberta_embeddings.html | 8 +- .../roberta_sentence_embeddings.html | 8 +- .../embeddings/sentence_embeddings.html | 8 +- .../embeddings/snowflake_embeddings.html | 8 +- .../annotator/embeddings/uae_embeddings.html | 8 +- .../universal_sentence_encoder.html | 8 +- .../annotator/embeddings/word2vec.html | 8 +- .../annotator/embeddings/word_embeddings.html | 8 +- .../embeddings/xlm_roberta_embeddings.html | 8 +- .../xlm_roberta_sentence_embeddings.html | 8 +- .../embeddings/xlnet_embeddings.html | 8 +- .../sparknlp/annotator/er/entity_ruler.html | 8 +- .../sparknlp/annotator/graph_extraction.html | 8 +- .../yake_keyword_extraction.html | 8 +- .../annotator/ld_dl/language_detector_dl.html | 8 +- .../sparknlp/annotator/lemmatizer.html | 8 +- .../annotator/matcher/big_text_matcher.html | 8 +- .../annotator/matcher/date_matcher.html | 8 +- .../annotator/matcher/multi_date_matcher.html | 8 +- .../annotator/matcher/regex_matcher.html | 8 +- .../annotator/matcher/text_matcher.html | 8 +- .../sparknlp/annotator/n_gram_generator.html | 8 +- .../sparknlp/annotator/ner/ner_approach.html | 8 +- .../sparknlp/annotator/ner/ner_converter.html | 8 +- .../sparknlp/annotator/ner/ner_crf.html | 8 +- .../sparknlp/annotator/ner/ner_dl.html | 8 +- .../annotator/ner/ner_dl_graph_checker.html | 8 +- .../annotator/ner/ner_overwriter.html | 8 +- .../annotator/ner/zero_shot_ner_model.html | 8 +- .../sparknlp/annotator/normalizer.html | 8 +- .../annotator/openai/openai_completion.html | 8 +- .../annotator/openai/openai_embeddings.html | 8 +- .../annotator/param/classifier_encoder.html | 8 +- .../annotator/param/evaluation_dl_params.html | 8 +- .../sparknlp/annotator/pos/perceptron.html | 8 +- .../annotator/sentence/sentence_detector.html | 8 +- .../sentence/sentence_detector_dl.html | 8 +- .../sentiment/sentiment_detector.html | 8 +- .../annotator/sentiment/vivekn_sentiment.html | 8 +- .../annotator/seq2seq/auto_gguf_model.html | 8 +- .../annotator/seq2seq/auto_gguf_reranker.html | 8 +- .../seq2seq/auto_gguf_vision_model.html | 8 +- .../annotator/seq2seq/bart_transformer.html | 8 +- .../annotator/seq2seq/cohere_transformer.html | 8 +- .../annotator/seq2seq/cpm_transformer.html | 8 +- .../annotator/seq2seq/gpt2_transformer.html | 8 +- .../annotator/seq2seq/llama2_transformer.html | 8 +- .../annotator/seq2seq/llama3_transformer.html | 8 +- .../annotator/seq2seq/m2m100_transformer.html | 8 +- .../annotator/seq2seq/marian_transformer.html | 8 +- .../seq2seq/mistral_transformer.html | 8 +- .../annotator/seq2seq/nllb_transformer.html | 8 +- .../annotator/seq2seq/olmo_transformer.html | 8 +- .../annotator/seq2seq/phi2_transformer.html | 8 +- .../annotator/seq2seq/phi3_transformer.html | 8 +- .../annotator/seq2seq/phi4_transformer.html | 8 +- .../annotator/seq2seq/qwen_transformer.html | 8 +- .../seq2seq/starcoder_transformer.html | 8 +- .../annotator/seq2seq/t5_transformer.html | 8 +- .../document_similarity_ranker.html | 8 +- .../spell_check/context_spell_checker.html | 8 +- .../spell_check/norvig_sweeting.html | 8 +- .../spell_check/symmetric_delete.html | 8 +- .../modules/sparknlp/annotator/stemmer.html | 8 +- .../annotator/stop_words_cleaner.html | 8 +- .../annotator/tf_ner_dl_graph_builder.html | 8 +- .../annotator/token/chunk_tokenizer.html | 8 +- .../annotator/token/recursive_tokenizer.html | 8 +- .../annotator/token/regex_tokenizer.html | 8 +- .../sparknlp/annotator/token/tokenizer.html | 8 +- .../sparknlp/annotator/token2_chunk.html | 8 +- .../sparknlp/annotator/ws/word_segmenter.html | 8 +- .../sparknlp/base/audio_assembler.html | 8 +- .../modules/sparknlp/base/doc2_chunk.html | 8 +- .../sparknlp/base/document_assembler.html | 8 +- .../sparknlp/base/embeddings_finisher.html | 8 +- .../modules/sparknlp/base/finisher.html | 8 +- .../sparknlp/base/gguf_ranking_finisher.html | 8 +- .../modules/sparknlp/base/graph_finisher.html | 8 +- .../sparknlp/base/has_recursive_fit.html | 8 +- .../base/has_recursive_transform.html | 8 +- .../sparknlp/base/image_assembler.html | 8 +- .../modules/sparknlp/base/light_pipeline.html | 8 +- .../base/multi_document_assembler.html | 8 +- .../sparknlp/base/prompt_assembler.html | 8 +- .../sparknlp/base/recursive_pipeline.html | 8 +- .../sparknlp/base/table_assembler.html | 8 +- .../sparknlp/base/token_assembler.html | 8 +- .../sparknlp/common/annotator_approach.html | 8 +- .../sparknlp/common/annotator_model.html | 8 +- .../sparknlp/common/annotator_properties.html | 8 +- .../sparknlp/common/annotator_type.html | 8 +- .../sparknlp/common/coverage_result.html | 8 +- .../sparknlp/common/match_strategy.html | 8 +- .../modules/sparknlp/common/properties.html | 8 +- .../modules/sparknlp/common/read_as.html | 8 +- .../common/recursive_annotator_approach.html | 8 +- .../modules/sparknlp/common/storage.html | 8 +- .../python/modules/sparknlp/common/utils.html | 8 +- .../python/modules/sparknlp/functions.html | 8 +- .../sparknlp/internal/annotator_java_ml.html | 8 +- .../internal/annotator_transformer.html | 8 +- .../internal/extended_java_wrapper.html | 8 +- .../internal/params_getters_setters.html | 8 +- .../modules/sparknlp/internal/recursive.html | 8 +- .../modules/sparknlp/logging/comet.html | 8 +- .../modules/sparknlp/partition/partition.html | 8 +- .../partition/partition_properties.html | 455 ++- .../partition/partition_transformer.html | 8 +- .../pretrained/pretrained_pipeline.html | 8 +- .../pretrained/resource_downloader.html | 8 +- .../modules/sparknlp/pretrained/utils.html | 8 +- .../python/modules/sparknlp/reader/enums.html | 8 +- .../modules/sparknlp/reader/pdf_to_text.html | 8 +- .../modules/sparknlp/reader/reader2doc.html | 175 +- .../modules/sparknlp/reader/reader2image.html | 570 +++ .../modules/sparknlp/reader/reader2table.html | 134 +- .../sparknlp/reader/reader_assembler.html | 593 +++ .../sparknlp/reader/sparknlp_reader.html | 8 +- .../modules/sparknlp/training/conll.html | 8 +- .../modules/sparknlp/training/conllu.html | 8 +- .../python/modules/sparknlp/training/pos.html | 8 +- .../modules/sparknlp/training/pub_tator.html | 8 +- .../training/spacy_to_annotation.html | 8 +- .../modules/sparknlp/training/tfgraphs.html | 8 +- .../modules/sparknlp/upload_to_hub.html | 8 +- docs/api/python/modules/sparknlp/util.html | 8 +- docs/api/python/objects.inv | Bin 34493 -> 34846 bytes docs/api/python/py-modindex.html | 18 +- .../sparknlp/annotation/index.html | 8 +- .../sparknlp/annotation_audio/index.html | 8 +- .../sparknlp/annotation_image/index.html | 8 +- .../annotator/audio/hubert_for_ctc/index.html | 8 +- .../sparknlp/annotator/audio/index.html | 8 +- .../audio/wav2vec2_for_ctc/index.html | 8 +- .../audio/whisper_for_ctc/index.html | 8 +- .../sparknlp/annotator/chunk2_doc/index.html | 8 +- .../sparknlp/annotator/chunker/index.html | 8 +- .../albert_for_multiple_choice/index.html | 8 +- .../albert_for_question_answering/index.html | 8 +- .../index.html | 8 +- .../index.html | 8 +- .../index.html | 8 +- .../index.html | 8 +- .../bert_for_multiple_choice/index.html | 8 +- .../bert_for_question_answering/index.html | 8 +- .../index.html | 8 +- .../bert_for_token_classification/index.html | 8 +- .../index.html | 8 +- .../index.html | 8 +- .../index.html | 8 +- .../index.html | 8 +- .../index.html | 8 +- .../classifier_dl/classifier_dl/index.html | 8 +- .../deberta_for_question_answering/index.html | 8 +- .../index.html | 8 +- .../index.html | 8 +- .../index.html | 8 +- .../index.html | 8 +- .../index.html | 8 +- .../index.html | 8 +- .../index.html | 8 +- .../distilbert_for_multiple_choice/index.html | 8 +- .../annotator/classifier_dl/index.html | 8 +- .../index.html | 8 +- .../index.html | 8 +- .../index.html | 8 +- .../mpnet_for_question_answering/index.html | 8 +- .../index.html | 8 +- .../mpnet_for_token_classification/index.html | 8 +- .../multi_classifier_dl/index.html | 8 +- .../roberta_for_multiple_choice/index.html | 8 +- .../roberta_for_question_answering/index.html | 8 +- .../index.html | 8 +- .../index.html | 8 +- .../index.html | 8 +- .../classifier_dl/sentiment_dl/index.html | 8 +- .../tapas_for_question_answering/index.html | 8 +- .../index.html | 8 +- .../index.html | 8 +- .../index.html | 8 +- .../index.html | 8 +- .../index.html | 8 +- .../index.html | 8 +- .../xlnet_for_token_classification/index.html | 8 +- .../annotator/cleaners/cleaner/index.html | 8 +- .../annotator/cleaners/extractor/index.html | 8 +- .../sparknlp/annotator/cleaners/index.html | 8 +- .../sparknlp/annotator/coref/index.html | 8 +- .../annotator/coref/spanbert_coref/index.html | 8 +- .../cv/blip_for_question_answering/index.html | 8 +- .../index.html | 8 +- .../index.html | 8 +- .../cv/florence2_transformer/index.html | 8 +- .../cv/gemma3_for_multimodal/index.html | 8 +- .../sparknlp/annotator/cv/index.html | 8 +- .../cv/internvl_for_multimodal/index.html | 8 +- .../cv/janus_for_multimodal/index.html | 8 +- .../cv/llava_for_multimodal/index.html | 8 +- .../cv/mllama_for_multimodal/index.html | 8 +- .../cv/paligemma_for_multimodal/index.html | 8 +- .../cv/phi3_vision_for_multimodal/index.html | 8 +- .../cv/qwen2vl_transformer/index.html | 8 +- .../cv/smolvlm_transformer/index.html | 8 +- .../swin_for_image_classification/index.html | 8 +- .../index.html | 8 +- .../vit_for_image_classification/index.html | 8 +- .../annotator/dataframe_optimizer/index.html | 8 +- .../sparknlp/annotator/date2_chunk/index.html | 8 +- .../dependency/dependency_parser/index.html | 8 +- .../sparknlp/annotator/dependency/index.html | 8 +- .../typed_dependency_parser/index.html | 8 +- .../index.html | 8 +- .../annotator/document_normalizer/index.html | 8 +- .../document_token_splitter/index.html | 8 +- .../document_token_splitter_test/index.html | 8 +- .../embeddings/albert_embeddings/index.html | 8 +- .../auto_gguf_embeddings/index.html | 8 +- .../embeddings/bert_embeddings/index.html | 8 +- .../bert_sentence_embeddings/index.html | 8 +- .../embeddings/bge_embeddings/index.html | 8 +- .../camembert_embeddings/index.html | 8 +- .../embeddings/chunk_embeddings/index.html | 8 +- .../embeddings/deberta_embeddings/index.html | 8 +- .../distil_bert_embeddings/index.html | 8 +- .../annotator/embeddings/doc2vec/index.html | 8 +- .../embeddings/e5_embeddings/index.html | 8 +- .../embeddings/e5v_embeddings/index.html | 8 +- .../embeddings/elmo_embeddings/index.html | 8 +- .../sparknlp/annotator/embeddings/index.html | 8 +- .../instructor_embeddings/index.html | 8 +- .../longformer_embeddings/index.html | 8 +- .../embeddings/minilm_embeddings/index.html | 8 +- .../embeddings/mpnet_embeddings/index.html | 8 +- .../embeddings/mxbai_embeddings/index.html | 8 +- .../embeddings/nomic_embeddings/index.html | 8 +- .../embeddings/roberta_embeddings/index.html | 8 +- .../roberta_sentence_embeddings/index.html | 8 +- .../embeddings/sentence_embeddings/index.html | 8 +- .../snowflake_embeddings/index.html | 8 +- .../embeddings/uae_embeddings/index.html | 8 +- .../universal_sentence_encoder/index.html | 8 +- .../annotator/embeddings/word2vec/index.html | 8 +- .../embeddings/word_embeddings/index.html | 8 +- .../xlm_roberta_embeddings/index.html | 8 +- .../index.html | 8 +- .../embeddings/xlnet_embeddings/index.html | 8 +- .../annotator/er/entity_ruler/index.html | 8 +- .../sparknlp/annotator/er/index.html | 8 +- .../annotator/graph_extraction/index.html | 8 +- .../autosummary/sparknlp/annotator/index.html | 8 +- .../annotator/keyword_extraction/index.html | 8 +- .../yake_keyword_extraction/index.html | 8 +- .../sparknlp/annotator/ld_dl/index.html | 8 +- .../ld_dl/language_detector_dl/index.html | 8 +- .../sparknlp/annotator/lemmatizer/index.html | 8 +- .../matcher/big_text_matcher/index.html | 8 +- .../annotator/matcher/date_matcher/index.html | 8 +- .../sparknlp/annotator/matcher/index.html | 8 +- .../matcher/multi_date_matcher/index.html | 8 +- .../matcher/regex_matcher/index.html | 8 +- .../annotator/matcher/text_matcher/index.html | 8 +- .../annotator/n_gram_generator/index.html | 8 +- .../sparknlp/annotator/ner/index.html | 8 +- .../annotator/ner/ner_approach/index.html | 8 +- .../annotator/ner/ner_converter/index.html | 8 +- .../sparknlp/annotator/ner/ner_crf/index.html | 8 +- .../sparknlp/annotator/ner/ner_dl/index.html | 8 +- .../ner/ner_dl_graph_checker/index.html | 8 +- .../annotator/ner/ner_overwriter/index.html | 8 +- .../ner/zero_shot_ner_model/index.html | 8 +- .../sparknlp/annotator/normalizer/index.html | 8 +- .../sparknlp/annotator/openai/index.html | 8 +- .../openai/openai_completion/index.html | 8 +- .../openai/openai_embeddings/index.html | 8 +- .../param/classifier_encoder/index.html | 8 +- .../param/evaluation_dl_params/index.html | 8 +- .../sparknlp/annotator/param/index.html | 8 +- .../sparknlp/annotator/pos/index.html | 8 +- .../annotator/pos/perceptron/index.html | 8 +- .../sparknlp/annotator/sentence/index.html | 8 +- .../sentence/sentence_detector/index.html | 8 +- .../sentence/sentence_detector_dl/index.html | 8 +- .../sparknlp/annotator/sentiment/index.html | 8 +- .../sentiment/sentiment_detector/index.html | 8 +- .../sentiment/vivekn_sentiment/index.html | 8 +- .../seq2seq/auto_gguf_model/index.html | 8 +- .../seq2seq/auto_gguf_reranker/index.html | 8 +- .../seq2seq/auto_gguf_vision_model/index.html | 8 +- .../seq2seq/bart_transformer/index.html | 8 +- .../seq2seq/cohere_transformer/index.html | 8 +- .../seq2seq/cpm_transformer/index.html | 8 +- .../seq2seq/gpt2_transformer/index.html | 8 +- .../sparknlp/annotator/seq2seq/index.html | 8 +- .../seq2seq/llama2_transformer/index.html | 8 +- .../seq2seq/llama3_transformer/index.html | 8 +- .../seq2seq/m2m100_transformer/index.html | 8 +- .../seq2seq/marian_transformer/index.html | 8 +- .../seq2seq/mistral_transformer/index.html | 8 +- .../seq2seq/nllb_transformer/index.html | 8 +- .../seq2seq/olmo_transformer/index.html | 8 +- .../seq2seq/phi2_transformer/index.html | 8 +- .../seq2seq/phi3_transformer/index.html | 8 +- .../seq2seq/phi4_transformer/index.html | 8 +- .../seq2seq/qwen_transformer/index.html | 8 +- .../seq2seq/starcoder_transformer/index.html | 8 +- .../seq2seq/t5_transformer/index.html | 8 +- .../document_similarity_ranker/index.html | 8 +- .../sparknlp/annotator/similarity/index.html | 8 +- .../context_spell_checker/index.html | 8 +- .../sparknlp/annotator/spell_check/index.html | 8 +- .../spell_check/norvig_sweeting/index.html | 8 +- .../spell_check/symmetric_delete/index.html | 8 +- .../sparknlp/annotator/stemmer/index.html | 8 +- .../annotator/stop_words_cleaner/index.html | 8 +- .../tf_ner_dl_graph_builder/index.html | 8 +- .../token/chunk_tokenizer/index.html | 8 +- .../sparknlp/annotator/token/index.html | 8 +- .../token/recursive_tokenizer/index.html | 8 +- .../token/regex_tokenizer/index.html | 8 +- .../annotator/token/tokenizer/index.html | 8 +- .../annotator/token2_chunk/index.html | 8 +- .../sparknlp/annotator/ws/index.html | 8 +- .../annotator/ws/word_segmenter/index.html | 8 +- .../sparknlp/base/audio_assembler/index.html | 8 +- .../sparknlp/base/doc2_chunk/index.html | 8 +- .../base/document_assembler/index.html | 8 +- .../base/embeddings_finisher/index.html | 8 +- .../sparknlp/base/finisher/index.html | 8 +- .../base/gguf_ranking_finisher/index.html | 8 +- .../sparknlp/base/graph_finisher/index.html | 8 +- .../base/has_recursive_fit/index.html | 8 +- .../base/has_recursive_transform/index.html | 8 +- .../sparknlp/base/image_assembler/index.html | 8 +- .../autosummary/sparknlp/base/index.html | 8 +- .../sparknlp/base/light_pipeline/index.html | 8 +- .../base/multi_document_assembler/index.html | 8 +- .../sparknlp/base/prompt_assembler/index.html | 8 +- .../base/recursive_pipeline/index.html | 8 +- .../sparknlp/base/table_assembler/index.html | 8 +- .../sparknlp/base/token_assembler/index.html | 8 +- .../common/annotator_approach/index.html | 8 +- .../common/annotator_model/index.html | 8 +- .../common/annotator_properties/index.html | 8 +- .../sparknlp/common/annotator_type/index.html | 8 +- .../common/coverage_result/index.html | 8 +- .../autosummary/sparknlp/common/index.html | 8 +- .../sparknlp/common/match_strategy/index.html | 8 +- .../sparknlp/common/properties/index.html | 8 +- .../sparknlp/common/read_as/index.html | 8 +- .../recursive_annotator_approach/index.html | 8 +- .../sparknlp/common/storage/index.html | 8 +- .../sparknlp/common/utils/index.html | 8 +- .../autosummary/sparknlp/functions/index.html | 8 +- .../reference/autosummary/sparknlp/index.html | 10 +- .../internal/annotator_java_ml/index.html | 8 +- .../internal/annotator_transformer/index.html | 8 +- .../internal/extended_java_wrapper/index.html | 8 +- .../autosummary/sparknlp/internal/index.html | 8 +- .../params_getters_setters/index.html | 8 +- .../sparknlp/internal/recursive/index.html | 8 +- .../sparknlp/logging/comet/index.html | 8 +- .../autosummary/sparknlp/logging/index.html | 8 +- .../autosummary/sparknlp/partition/index.html | 8 +- .../sparknlp/partition/partition/index.html | 8 +- .../partition/partition_properties/index.html | 576 ++- .../partition_transformer/index.html | 8 +- .../sparknlp/pretrained/index.html | 8 +- .../pretrained/pretrained_pipeline/index.html | 8 +- .../pretrained/resource_downloader/index.html | 8 +- .../sparknlp/pretrained/utils/index.html | 8 +- .../sparknlp/reader/enums/index.html | 10 +- .../autosummary/sparknlp/reader/index.html | 12 +- .../sparknlp/reader/pdf_to_text/index.html | 10 +- .../sparknlp/reader/reader2doc/index.html | 189 +- .../sparknlp/reader/reader2image/index.html | 617 ++++ .../sparknlp/reader/reader2table/index.html | 157 +- .../reader/reader_assembler/index.html | 638 ++++ .../reader/sparknlp_reader/index.html | 10 +- .../sparknlp/training/conll/index.html | 8 +- .../sparknlp/training/conllu/index.html | 8 +- .../autosummary/sparknlp/training/index.html | 8 +- .../sparknlp/training/pos/index.html | 8 +- .../sparknlp/training/pub_tator/index.html | 8 +- .../training/spacy_to_annotation/index.html | 8 +- .../sparknlp/training/tfgraphs/index.html | 8 +- .../sparknlp/upload_to_hub/index.html | 8 +- .../autosummary/sparknlp/util/index.html | 8 +- docs/api/python/reference/index.html | 8 +- docs/api/python/search.html | 8 +- docs/api/python/searchindex.js | 2 +- .../python/static/documentation_options.js | 2 +- docs/api/python/third_party/Comet.html | 8 +- docs/api/python/third_party/MLflow.html | 8 +- docs/api/python/third_party/index.html | 8 +- docs/api/python/user_guide/annotation.html | 8 +- docs/api/python/user_guide/annotators.html | 8 +- .../python/user_guide/custom_pipelines.html | 8 +- docs/api/python/user_guide/helpers.html | 8 +- docs/api/python/user_guide/index.html | 8 +- .../python/user_guide/light_pipelines.html | 8 +- .../user_guide/pretrained_pipelines.html | 8 +- docs/api/python/user_guide/training.html | 8 +- docs/api/scala/collection/compat/index.html | 8 +- docs/api/scala/collection/index.html | 8 +- docs/api/scala/index.html | 8 +- .../_api/sparknlp.reader.reader2image.rst | 7 + .../_api/sparknlp.reader.reader_assembler.rst | 7 + python/docs/_api/sparknlp.reader.rst | 2 + 2223 files changed, 32859 insertions(+), 10268 deletions(-) create mode 100644 docs/api/com/johnsnowlabs/partition/HasBinaryReaderProperties.html create mode 100644 docs/api/com/johnsnowlabs/partition/HasPdfReaderProperties.html create mode 100644 docs/api/com/johnsnowlabs/partition/HasTagsReaderProperties.html create mode 100644 docs/api/com/johnsnowlabs/reader/HasReaderContent.html create mode 100644 docs/api/com/johnsnowlabs/reader/Reader2Image.html create mode 100644 docs/api/com/johnsnowlabs/reader/ReaderAssembler.html create mode 100644 docs/api/com/johnsnowlabs/reader/util/EmailParser$.html rename docs/api/com/johnsnowlabs/reader/util/{HasPdfProperties.html => HasPdfToTextProperties.html} (80%) create mode 100644 docs/api/com/johnsnowlabs/reader/util/ImageParser$.html create mode 100644 docs/api/com/johnsnowlabs/reader/util/ImagePromptTemplate$.html create mode 100644 docs/api/python/_api/sparknlp.reader.reader2image.html create mode 100644 docs/api/python/_api/sparknlp.reader.reader_assembler.html create mode 100644 docs/api/python/modules/sparknlp/reader/reader2image.html create mode 100644 docs/api/python/modules/sparknlp/reader/reader_assembler.html create mode 100644 docs/api/python/reference/autosummary/sparknlp/reader/reader2image/index.html create mode 100644 docs/api/python/reference/autosummary/sparknlp/reader/reader_assembler/index.html create mode 100644 python/docs/_api/sparknlp.reader.reader2image.rst create mode 100644 python/docs/_api/sparknlp.reader.reader_assembler.rst diff --git a/docs/api/com/index.html b/docs/api/com/index.html index 80416aff840491..57f19700536fda 100644 --- a/docs/api/com/index.html +++ b/docs/api/com/index.html @@ -3,9 +3,9 @@ - Spark NLP 6.1.3 ScalaDoc - com - - + Spark NLP 6.1.5 ScalaDoc - com + + @@ -28,7 +28,7 @@