iink SDK on Device

Answered

How to reference custom lexicon directory in iOS?

Hi,


I am trying to build on device lexicon using IINKRecognitionAssetsBuilder but I am having problem with referencing it inside .conf file. Below is the code I use for initialising INKEngine:

   

 // Configure the iink runtime environment
let configurationPath = Bundle.main.bundlePath.appending("/recognition-assets/conf")
do {
    // Tells the engine where to load the recognition assets from.
    try engine.configuration.set(stringArray: [configurationPath],
                                    forKey:"configuration-manager.search-path")
} catch {
    print("Should not happen, please check your resources assets : " + error.localizedDescription)
    return nil
}

// Set the temporary directory
do {
    try engine.configuration.set(string: NSTemporaryDirectory(),
                                    forKey: "content-package.temp-folder")
} catch {
    print("Failed to set temporary folder: " + error.localizedDescription)
    return nil
}

do {
    let assetsBuilder = engine.createRecognitionAssetsBuilder()
    try assetsBuilder?.compile("Text Lexicon", data: "tvoja")

    
    let myDirectoryPath = FileManager.default.documentDirectoryPath().appending("/myDirectory") // This folder is inside /Documents folder
    if FileManager.default.fileExists(atPath: myDirectoryPath) == false {
        // Create folder if needed.
        try FileManager.default.createDirectory(atPath: myDirectoryPath, withIntermediateDirectories: false)
    }
    let lexPath = FileManager.default.documentDirectoryPath().appending("/myDirectory/custom-grammar.res")
    
    try assetsBuilder?.store(lexPath)
    try engine.configuration.set(string: "text", forKey: "text.configuration.name")

} catch {
    print(error)
}

And now I have a problem how to reference it inside .conf file.


Bundle-Version: 1.0

Bundle-Name: en_US

Configuration-Script:

 AddResDir ../resources/

 AddResDir myResources/ --> This doesn't work because I am getting errors when I try to use INKEditor.


Name: text

Type: Text

Configuration-Script:

 AddResource en_US/en_US-ak-cur.res

 AddResource en_US/en_US-lk-text.res

 AddResource en_US/lexicon.res

 AddResource custom-grammar.res

 EnableAlienCharacters

 SetTextListSize 1

 SetWordListSize 5

 SetCharListSize 1

 

I see that in Android you are using AddResDir /data/user/0/com.sample/app_sample but this isn't available in iOS. Can you help me obtaining correct path for AddResDir?


Thanks,

Josip


Best Answer

Hi Gwenaëlle,


Thanks! When I separate words with newline character it works.


I want to say how my previous implementation in the end didn't work but after using your approach to copy everything from bundle folder to my own it started working. In case that somebody needs it here is how it looks like.

This is my en_US.conf file

 

Bundle-Version: 1.0
Bundle-Name: en_US
Configuration-Script:
 AddResDir ../resources/

Name: text
Type: Text
Configuration-Script:
 AddResource en_US/en_US-ak-cur.res
 AddResource en_US/en_US-lk-text.res
 AddResource en_US/lexicon.res
 AddResource custom-grammar.res
 EnableAlienCharacters
 SetTextListSize 1
 SetWordListSize 5
 SetCharListSize 1

 1. I copy everything from recognition-assets to /Documents/RecognitionAssets

 

let bundleAssetsPath = Bundle.main.bundlePath.appending("/recognition-assets")
let localAssetsPath = FileManager.default.documentDirectoryPath().appending("/\(Self.recognitionAssetsFolderName)")

do {

    if FileManager.default.fileExists(atPath: localAssetsPath) == false {
        try FileManager.default.createDirectory(atPath: localAssetsPath, withIntermediateDirectories: false)
    }

    let contents = try FileManager.default.contentsOfDirectory(atPath: bundleAssetsPath)
    for item in contents {

        let atURL = URL(fileURLWithPath: "\(bundleAssetsPath)/\(item)")
        let toPath = localAssetsPath.appending("/\(atURL.lastPathComponent)")

        if FileManager.default.fileExists(atPath: toPath) {
            try FileManager.default.removeItem(atPath: toPath)
        }

        try FileManager.default.copyItem(atPath: atURL.path, toPath: toPath)
    }
} catch {
    print("An error occurred while copying recognition assets: \(error)")
}

2. Configure configuration-manager.search-path

 

do {
    // Tells the engine where to load the recognition assets from.
    let confPath = localAssetsPath.appending("/conf")
    try engine.configuration.set(stringArray: [confPath],
                                    forKey:"configuration-manager.search-path")
} catch {
    print("Should not happen, please check your resources assets : " + error.localizedDescription)
    return nil
}

 3. Load custom dictionary 

 

let words = storedCustomDictionary() ?? ""

do {
                
    let assetsBuilder = engine.createRecognitionAssetsBuilder()
    try assetsBuilder?.compile("Text Lexicon", data: words)

    let lexPath = FileManager.default
        .documentDirectoryPath()
        .appending("/\(Self.recognitionAssetsFolderName)/resources/custom-grammar.res")

    try assetsBuilder?.store(lexPath)
    try engine.configuration.set(string: "text", forKey: "text.configuration.name")

} catch {
    print(error)
}

 And that's it. Thanks again! 


Hi Josip,


We are glad you succeeded in building your lexicon.


Thank you for sharing with all users your working code for building your own lexicon!


Best regards,


Gwenaëlle

Answer

Hi Gwenaëlle,


Thanks! When I separate words with newline character it works.


I want to say how my previous implementation in the end didn't work but after using your approach to copy everything from bundle folder to my own it started working. In case that somebody needs it here is how it looks like.

This is my en_US.conf file

 

Bundle-Version: 1.0
Bundle-Name: en_US
Configuration-Script:
 AddResDir ../resources/

Name: text
Type: Text
Configuration-Script:
 AddResource en_US/en_US-ak-cur.res
 AddResource en_US/en_US-lk-text.res
 AddResource en_US/lexicon.res
 AddResource custom-grammar.res
 EnableAlienCharacters
 SetTextListSize 1
 SetWordListSize 5
 SetCharListSize 1

 1. I copy everything from recognition-assets to /Documents/RecognitionAssets

 

let bundleAssetsPath = Bundle.main.bundlePath.appending("/recognition-assets")
let localAssetsPath = FileManager.default.documentDirectoryPath().appending("/\(Self.recognitionAssetsFolderName)")

do {

    if FileManager.default.fileExists(atPath: localAssetsPath) == false {
        try FileManager.default.createDirectory(atPath: localAssetsPath, withIntermediateDirectories: false)
    }

    let contents = try FileManager.default.contentsOfDirectory(atPath: bundleAssetsPath)
    for item in contents {

        let atURL = URL(fileURLWithPath: "\(bundleAssetsPath)/\(item)")
        let toPath = localAssetsPath.appending("/\(atURL.lastPathComponent)")

        if FileManager.default.fileExists(atPath: toPath) {
            try FileManager.default.removeItem(atPath: toPath)
        }

        try FileManager.default.copyItem(atPath: atURL.path, toPath: toPath)
    }
} catch {
    print("An error occurred while copying recognition assets: \(error)")
}

2. Configure configuration-manager.search-path

 

do {
    // Tells the engine where to load the recognition assets from.
    let confPath = localAssetsPath.appending("/conf")
    try engine.configuration.set(stringArray: [confPath],
                                    forKey:"configuration-manager.search-path")
} catch {
    print("Should not happen, please check your resources assets : " + error.localizedDescription)
    return nil
}

 3. Load custom dictionary 

 

let words = storedCustomDictionary() ?? ""

do {
                
    let assetsBuilder = engine.createRecognitionAssetsBuilder()
    try assetsBuilder?.compile("Text Lexicon", data: words)

    let lexPath = FileManager.default
        .documentDirectoryPath()
        .appending("/\(Self.recognitionAssetsFolderName)/resources/custom-grammar.res")

    try assetsBuilder?.store(lexPath)
    try engine.configuration.set(string: "text", forKey: "text.configuration.name")

} catch {
    print(error)
}

 And that's it. Thanks again! 

Hi Josip,


Thank you for your update.


In the lexicon, each word must be added in a separate line. See some examples

So you should indeed separate them with a newline character.


Best regards,


Gwenaëlle

Hi Gwenaelle,


Using a a secondary language won't work because some clients have business specific phrases or abbreviations and I was hoping that custom lexicon could help me with that.

I was able to make it work by using following: 

Bundle-Version: 1.0
Bundle-Name: en_US
Configuration-Script:
 AddResDir ../resources/
 AddResDir ../myResources --> it is important that you don't add / at the end.

Name: text
Type: Text
Configuration-Script:
 AddResource en_US/en_US-ak-cur.res
 AddResource en_US/en_US-lk-text.res
 AddResource en_US/lexicon.res
 #AddResource custom-grammar.res
 EnableAlienCharacters
 SetTextListSize 1
 SetWordListSize 5
 SetCharListSize 1

 


And then save your custom lexicon into documents directory:

if FileManager.default.fileExists(atPath: myDirectoryPath) == false {
    try FileManager.default.createDirectory(atPath: myDirectoryPath, withIntermediateDirectories: false)
}
let lexPath = myDirectoryPath.appending("/custom-grammar.res")

try assetsBuilder?.store(lexPath)
try engine.configuration.set(string: "text", forKey: "text.configuration.name")

 Interestingly I didn't have to update configuration-manager.search-path.


 At the moment I have only a single word inside my custom lexicon. If I want to add multiple words how should I write them? I.e. should I separate them with commas or with newline character?

Hi Josip,


Thank you for your question.


When using AddResDir  it is recommended to use relative path rather than absolute ones, in order to avoid any deployment issue 

So to fix your path issue, we advise you to extract the content of your "recognition-assets" folder at runtime under your /myDirectory in your documentDirectoryPath 

and to only work  within documentDirectoryPath: thus all your files .res and .conf will be located within the documentDirectoryPath making it possible to use relative paths.

You should update your configuration-manager.search-path accordingly.


We have another remark, though, as you seem to want to mix foreign words within english ones. As the ak (alphabet knowledge) is based on the en_US one, this might not be the best option if you are adding word containing characters that are specific to this language.

Have you considered the other option that is configuring the recognition engine with the other language? Indeed default configurations for all languages but English variants also  attach a “secondary English” LK that allows the engine to recognize a  mix of the target language and English. Except for this particular case, it is not expected to mix languages together.


Best regards,


Gwenaëlle