Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(predictions): TABLE, CELL & KEY_VALUE_SET blocks are not properly processed #660

Merged
merged 17 commits into from
Aug 10, 2020
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,96 @@ class IdentifyResultTransformers {

}

static func processChild(ids: [String],
blockMap: [String: AWSTextractBlock]) -> String {
var keyText = ""
for keyId in ids {
let keyBlock = blockMap[keyId]
guard let keyBlockType = keyBlock?.blockType else {
continue
}
switch keyBlockType {
case .word:
if let text = keyBlock?.text {
keyText += text + " "
}
default: break
ruiguoamz marked this conversation as resolved.
Show resolved Hide resolved
}
}
return keyText
}

static func processValue(ids: [String],
blockMap: [String: AWSTextractBlock]) -> (String, Bool) {
var valueText = ""
var valueSelected = false

// VALUE block has a CHILD list of IDs for the WORD block
for valueId in ids {
let valueBlock = blockMap[valueId]
guard let valueRelatioins = valueBlock?.relationships else {
ruiguoamz marked this conversation as resolved.
Show resolved Hide resolved
continue
}
for valueRelation in valueRelatioins {
guard let ids = valueRelation.ids else {
ruiguoamz marked this conversation as resolved.
Show resolved Hide resolved
break
}
for id in ids {
let wordBlock = blockMap[id]
guard let wordValueBlockType = wordBlock?.blockType else {
continue
}
switch wordValueBlockType {
case .word:
if let text = wordBlock?.text {
valueText += text + " "
}
case .selectionElement:
valueSelected = wordBlock?.selectionStatus == .selected ? true : false
ruiguoamz marked this conversation as resolved.
Show resolved Hide resolved
default: break
}
}
}

}
return (valueText, valueSelected)
ruiguoamz marked this conversation as resolved.
Show resolved Hide resolved
}

static func parseLineBlock(block: AWSTextractBlock) -> IdentifiedLine? {
guard let text = block.text,
let boundingBox = processBoundingBox(block.geometry?.boundingBox),
let polygon = processPolygon(block.geometry?.polygon) else {
return nil
}

return IdentifiedLine(text: text,
boundingBox: boundingBox,
polygon: polygon,
page: Int(truncating: block.page ?? 0))
}

static func parseWordBlock(block: AWSTextractBlock) -> IdentifiedWord? {
guard let text = block.text,
let boundingBox = processBoundingBox(block.geometry?.boundingBox),
let polygon = processPolygon(block.geometry?.polygon) else {
return nil
}

return IdentifiedWord(text: text,
boundingBox: boundingBox,
polygon: polygon,
page: Int(truncating: block.page ?? 0))
}

static func parseSelectionElementBlock(block: AWSTextractBlock) -> Selection? {
guard let boundingBox = processBoundingBox(block.geometry?.boundingBox),
let polygon = processPolygon(block.geometry?.polygon) else {
return nil
}
let selectionStatus = block.selectionStatus == .selected ? true : false
ruiguoamz marked this conversation as resolved.
Show resolved Hide resolved
return Selection(boundingBox: boundingBox, polygon: polygon, isSelected: selectionStatus)
}

ruiguoamz marked this conversation as resolved.
Show resolved Hide resolved
// swiftlint:disable cyclomatic_complexity
static func processLandmarks(_ rekognitionLandmarks: [AWSRekognitionLandmark]?) -> [Landmark] {
var landmarks = [Landmark]()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,79 +53,52 @@ class IdentifyTextResultTransformers: IdentifyResultTransformers {
}

static func processText(_ textractTextBlocks: [AWSTextractBlock]) -> IdentifyDocumentTextResult {

var fullText = ""
ruiguoamz marked this conversation as resolved.
Show resolved Hide resolved
var words = [IdentifiedWord]()
var lines = [String]()
var linesDetailed = [IdentifiedLine]()
var selections = [Selection]()
var fullText = ""
var tables = [Table]()
var keyValues = [BoundedKeyValue]()
var blockMap = [String: AWSTextractBlock]()
var tableBlocks = [AWSTextractBlock]()
var keyValueBlocks = [AWSTextractBlock]()
var blockMap = [String: AWSTextractBlock]()

for block in textractTextBlocks {
guard let text = block.text, let identifier = block.identifier else {
guard let identifier = block.identifier else {
continue
}

guard let boundingBox = processBoundingBox(block.geometry?.boundingBox) else {
continue
}

guard let polygon = processPolygon(block.geometry?.polygon) else {
continue
}
let word = IdentifiedWord(text: text,
boundingBox: boundingBox,
polygon: polygon,
page: Int(truncating: block.page ?? 0))

let line = IdentifiedLine(text: text,
boundingBox: boundingBox,
polygon: polygon,
page: Int(truncating: block.page ?? 0))
blockMap[identifier] = block
ruiguoamz marked this conversation as resolved.
Show resolved Hide resolved

switch block.blockType {
case .line:
lines.append(text)
linesDetailed.append(line)
if let line = parseLineBlock(block: block) {
lines.append(line.text)
linesDetailed.append(line)
}
case .word:
fullText += text + " "
words.append(word)
blockMap[identifier] = block
if let word = parseWordBlock(block: block) {
fullText += word.text + " "
words.append(word)
blockMap[identifier] = block
}
case .selectionElement:
let selectionStatus = block.selectionStatus == .selected ? true : false
let selection = Selection(boundingBox: boundingBox, polygon: polygon, isSelected: selectionStatus)
selections.append(selection)
blockMap[identifier] = block
if let selection = parseSelectionElementBlock(block: block) {
selections.append(selection)
blockMap[identifier] = block
}
case .table:
tableBlocks.append(block)
case .keyValueSet:
keyValueBlocks.append(block)
blockMap[identifier] = block
default:
blockMap[identifier] = block

}
}

if !tableBlocks.isEmpty {
for tableBlock in tableBlocks {
if let table = processTable(tableBlock, blockMap: blockMap) {
tables.append(table)
}
continue
}
}

if !keyValueBlocks.isEmpty {
for keyValueBlock in keyValueBlocks where keyValueBlock.entityTypes?.contains("KEY") ?? false {
if let keyValue = processKeyValue(keyValueBlock, blockMap: blockMap) {
keyValues.append(keyValue)
}
}
}
tables = processTables(tableBlocks: tableBlocks, blockMap: blockMap)
keyValues = processKeyValues(keyValueBlocks: keyValueBlocks, blockMap: blockMap)

return IdentifyDocumentTextResult(
fullText: fullText,
Expand All @@ -137,15 +110,47 @@ class IdentifyTextResultTransformers: IdentifyResultTransformers {
keyValues: keyValues)
}

static func processTable(_ block: AWSTextractBlock,
static func processTables(tableBlocks: [AWSTextractBlock],
blockMap: [String: AWSTextractBlock]) -> [Table] {
var tables = [Table]()
for tableBlock in tableBlocks {
if let table = processTable(tableBlock, blockMap: blockMap) {
tables.append(table)
}
}
return tables
}

static func processKeyValues(keyValueBlocks: [AWSTextractBlock],
blockMap: [String: AWSTextractBlock]) -> [BoundedKeyValue] {
var keyValues = [BoundedKeyValue]()
for keyValueBlock in keyValueBlocks where keyValueBlock.entityTypes?.contains("KEY") ?? false {
ruiguoamz marked this conversation as resolved.
Show resolved Hide resolved
if let keyValue = processKeyValue(keyValueBlock, blockMap: blockMap) {
keyValues.append(keyValue)
}
}
return keyValues
}

// https://docs.aws.amazon.com/textract/latest/dg/how-it-works-tables.html
/**
* Converts a given Amazon Textract block into Amplify-compatible
* table object.
* @param block Textract text block
* @param blockMap map of Textract blocks by their IDs
* @return Amplify Table instance
*/
static func processTable(_ tableBlock: AWSTextractBlock,
blockMap: [String: AWSTextractBlock]) -> Table? {

guard let relationships = block.relationships else {
guard let relationships = tableBlock.relationships else {
ruiguoamz marked this conversation as resolved.
Show resolved Hide resolved
return nil
}
var table = Table()
var rows = Set<Int>()
var cols = Set<Int>()

// Each TABLE block contains CELL blocks
for tableRelation in relationships {
guard let ids = tableRelation.ids else {
ruiguoamz marked this conversation as resolved.
Show resolved Hide resolved
continue
Expand All @@ -162,13 +167,13 @@ class IdentifyTextResultTransformers: IdentifyResultTransformers {
let row = Int(truncating: rowIndex) - 1
let col = Int(truncating: colIndex) - 1
if !rows.contains(row) {
rows.insert(row)
rows.insert(row)
}
if !cols.contains(col) {
cols.insert(col)
cols.insert(col)
}
if let cell = constructTableCell(cellBlock) {
table.cells.append(cell)
if let cell = constructTableCell(cellBlock, blockMap) {
table.cells.append(cell)
ruiguoamz marked this conversation as resolved.
Show resolved Hide resolved
}
}

Expand All @@ -178,27 +183,42 @@ class IdentifyTextResultTransformers: IdentifyResultTransformers {
return table
}

static func constructTableCell(_ block: AWSTextractBlock?) -> Table.Cell? {
guard let blockType = block?.blockType,
let selectionStatus = block?.selectionStatus,
let text = block?.text,
static func constructTableCell(_ block: AWSTextractBlock?, _ blockMap: [String: AWSTextractBlock]) -> Table.Cell? {
guard let selectionStatus = block?.selectionStatus,
ruiguoamz marked this conversation as resolved.
Show resolved Hide resolved
let rowSpan = block?.rowSpan,
let columnSpan = block?.columnSpan,
let geometry = block?.geometry,
let textractBoundingBox = geometry.boundingBox,
let texttractPolygon = geometry.polygon
let texttractPolygon = geometry.polygon,
let relationships = block?.relationships
else {
return nil
}

var words = ""
var isSelected = false

switch blockType {
case .word:
words += text + " "
case .selectionElement:
isSelected = selectionStatus == .selected ? true : false
default: break
// Each CELL block consists of WORD and/or SELECTION_ELEMENT blocks
for cellRelation in relationships {
guard let ids = cellRelation.ids else {
continue
}
ruiguoamz marked this conversation as resolved.
Show resolved Hide resolved

for wordId in ids {
let wordBlock = blockMap[wordId]

switch wordBlock?.blockType {
case .word:
guard let text = wordBlock?.text else {
return nil
}
words += text + " "
case .selectionElement:
isSelected = selectionStatus == .selected ? true : false
ruiguoamz marked this conversation as resolved.
Show resolved Hide resolved
default:
break
}
}
}

guard let boundingBox = processBoundingBox(textractBoundingBox) else {
Expand All @@ -218,6 +238,14 @@ class IdentifyTextResultTransformers: IdentifyResultTransformers {
return cell
}

// https://docs.aws.amazon.com/textract/latest/dg/how-it-works-kvp.html
/**
* Converts a given Amazon Textract block into Amplify-compatible
* key-value pair feature. Returns null if not a valid table.
* @param block Textract text block
* @param blockMap map of Textract blocks by their IDs
* @return Amplify KeyValue instance
*/
ruiguoamz marked this conversation as resolved.
Show resolved Hide resolved
static func processKeyValue(_ keyBlock: AWSTextractBlock?,
blockMap: [String: AWSTextractBlock]) -> BoundedKeyValue? {
var keyText = ""
Expand All @@ -229,32 +257,20 @@ class IdentifyTextResultTransformers: IdentifyResultTransformers {
return nil
}

// KEY_VALUE_SET block contains CHILD and VALUE entity type blocks
for keyBlockRelationship in relationships {
guard let text = keyBlock.text,
let ids = keyBlockRelationship.ids else {
continue

guard let ids = keyBlockRelationship.ids else {
continue
}

switch keyBlockRelationship.types {
case .child where keyBlock.blockType == .word:
keyText += text + " "
case .child:
keyText = processChild(ids: ids, blockMap: blockMap)
ruiguoamz marked this conversation as resolved.
Show resolved Hide resolved
case .value:
for valueId in ids {
let valueBlock = blockMap[valueId]
guard let valueBlockType = valueBlock?.blockType else {
continue
}
switch valueBlockType {
case .word:

if let text = valueBlock?.text {
valueText += text + " "
}
case .selectionElement:
valueSelected = keyBlock.selectionStatus == .selected ? true : false
default: break
}
}

let valueResult = processValue(ids: ids, blockMap: blockMap)
valueText = valueResult.0
valueSelected = valueResult.1
ruiguoamz marked this conversation as resolved.
Show resolved Hide resolved
default:
break
}
Expand All @@ -269,9 +285,9 @@ class IdentifyTextResultTransformers: IdentifyResultTransformers {
}

return BoundedKeyValue(key: keyText,
value: valueText,
isSelected: valueSelected,
boundingBox: boundingBox,
polygon: polygon)
value: valueText,
isSelected: valueSelected,
boundingBox: boundingBox,
polygon: polygon)
}
}
Loading