I am building an OCR app for android and i use tesseract ocr engine. Somehow every time i use the engine on a photo it returns an empty text.
This is my code:
public String detectText(Bitmap bitmap) {
TessBaseAPI tessBaseAPI = new TessBaseAPI();
String mDataDir = setTessData();
tessBaseAPI.setDebug(true);
tessBaseAPI.init(mDataDir + File.separator, "eng");
tessBaseAPI.setImage(bitmap);
tessBaseAPI.setPageSegMode(TessBaseAPI.OEM_TESSERACT_ONLY);
String text = tessBaseAPI.getUTF8Text();
tessBaseAPI.end();
return text;
}
private String setTessData(){
String mDataDir = this.getExternalFilesDir("data").getAbsolutePath();
String mTrainedDataPath = mDataDir + File.separator + "tessdata";
String mLang = "eng";
// Checking if language file already exist inside data folder
File dir = new File(mTrainedDataPath);
if (!dir.exists()) {
if (!dir.mkdirs()) {
//showDialogFragment(SD_ERR_DIALOG, "sd_err_dialog");
} else {
}
}
if (!(new File(mTrainedDataPath + File.separator + mLang + ".traineddata")).exists()) {
// If English or Hebrew, we just copy the file from assets
if (mLang.equals("eng") || mLang.equals("heb")){
try {
AssetManager assetManager = context.getAssets();
InputStream in = assetManager.open(mLang + ".traineddata");
OutputStream out = new FileOutputStream(mTrainedDataPath + File.separator + mLang + ".traineddata");
copyFile(in, out);
//Toast.makeText(context, getString(R.string.selected_language) + " " + mLangArray[mLangID], Toast.LENGTH_SHORT).show();
//Log.v(TAG, "Copied " + mLang + " traineddata");
} catch (IOException e) {
//showDialogFragment(SD_ERR_DIALOG, "sd_err_dialog");
}
}
else{
// Checking if Network is available
if (!isNetworkAvailable(this)){
//showDialogFragment(NETWORK_ERR_DIALOG, "network_err_dialog");
}
else {
// Shows a dialog with File dimension. When user click on OK download starts. If he press Cancel revert to english language (like NETWORK ERROR)
//showDialogFragment(CONTINUE_DIALOG, "continue_dialog");
}
}
}
else {
//Toast.makeText(mThis, getString(R.string.selected_language) + " " + mLangArray[mLangID], Toast.LENGTH_SHORT).show();
}
return mDataDir;
}
I have debugged it many times and the bitmap is being transferred correctly to the detectText method. The language data files(tessdata) exists on the phone and the path to them is also correct.
Does anybody knows what the problem here?
You are using the OCR Engine Mode Enum value for setting the page segmentation in your setTessData() method.
setTessData() {
...
tessBaseAPI.setPageSegMode(TessBaseAPI.OEM_TESSERACT_ONLY);
}
Based on the type of image on which you are trying to detect the characters, setting an appropriate Page segmentation mode will help detect the characters.
For example :
tessBaseAPI.setPageSegMode(TessBaseAPI.PageSegMode.PSM_AUTO);
The various other Page segmentation values are present in TessBaseApi.java :
/** Page segmentation mode. */
public static final class PageSegMode {
/** Orientation and script detection only. */
public static final int PSM_OSD_ONLY = 0;
/** Automatic page segmentation with orientation and script detection. (OSD) */
public static final int PSM_AUTO_OSD = 1;
/** Fully automatic page segmentation, but no OSD, or OCR. */
public static final int PSM_AUTO_ONLY = 2;
/** Fully automatic page segmentation, but no OSD. */
public static final int PSM_AUTO = 3;
/** Assume a single column of text of variable sizes. */
public static final int PSM_SINGLE_COLUMN = 4;
/** Assume a single uniform block of vertically aligned text. */
public static final int PSM_SINGLE_BLOCK_VERT_TEXT = 5;
/** Assume a single uniform block of text. (Default.) */
public static final int PSM_SINGLE_BLOCK = 6;
/** Treat the image as a single text line. */
public static final int PSM_SINGLE_LINE = 7;
/** Treat the image as a single word. */
public static final int PSM_SINGLE_WORD = 8;
/** Treat the image as a single word in a circle. */
public static final int PSM_CIRCLE_WORD = 9;
/** Treat the image as a single character. */
public static final int PSM_SINGLE_CHAR = 10;
/** Find as much text as possible in no particular order. */
public static final int PSM_SPARSE_TEXT = 11;
/** Sparse text with orientation and script detection. */
public static final int PSM_SPARSE_TEXT_OSD = 12;
/** Number of enum entries. */
public static final int PSM_COUNT = 13;
}
You can experiment with different page segmentation enum values and see which gives the best result.
Related
The response coming from server is too long. So I am not able to see the complete response in the logcat monitor in android studio.
Is there any way to get the complete logcat of the response ?
For this, you should use debug points. You can get complete response and can see step by step execution of your code. For more you can go into the below website :
https://developer.android.com/studio/debug/index.html
Creating custom class for this. The important method for using it inside your code is splitAndLog by #pctroll.
public class Utils {
/**
* Divides a string into chunks of a given character size.
*
* #param text String text to be sliced
* #param sliceSize int Number of characters
* #return ArrayList<String> Chunks of strings
*/
public static ArrayList<String> splitString(String text, int sliceSize) {
ArrayList<String> textList = new ArrayList<String>();
String aux;
int left = -1, right = 0;
int charsLeft = text.length();
while (charsLeft != 0) {
left = right;
if (charsLeft >= sliceSize) {
right += sliceSize;
charsLeft -= sliceSize;
}
else {
right = text.length();
aux = text.substring(left, right);
charsLeft = 0;
}
aux = text.substring(left, right);
textList.add(aux);
}
return textList;
}
/**
* Divides a string into chunks.
*
* #param text String text to be sliced
* #return ArrayList<String>
*/
public static ArrayList<String> splitString(String text) {
return splitString(text, 80);
}
/**
* Divides the string into chunks for displaying them
* into the Eclipse's LogCat.
*
* #param text The text to be split and shown in LogCat
* #param tag The tag in which it will be shown.
*/
public static void splitAndLog(String tag, String text) {
ArrayList<String> messageList = Utils.splitString(text);
for (String message : messageList) {
Log.d(tag, message);
}
}
}
I would like to use something like log4j to log on files. I can see the logback project but I can't understand how to use a FileAppender programmatically.
Is There a simple way to log on files?
To write Logs on SD Card i am using below class.
You can try or modify as per your need ,
public class Log {
/** you can use application storage just change below line to your like getFile or package name etc.. **/
private static String enable_path=Environment.getExternalStorageDirectory().toString() + "/your foder name";
private static File logFolderPath = new File(enable_path);
private static boolean isLogEnabled(){
return logFolderPath.exists();
}
public static void e(String tag, String msg) {
if(isLogEnabled()){
android.util.Log.e(tag, msg);
put('E', tag, msg, null);
}
}
public static void e(String tag, String msg, Throwable thr) {
if(isLogEnabled()){
android.util.Log.e(tag, msg, thr);
put('E', tag, msg + ": " + thr.getMessage(), thr);
}
}
private static final File LOG_FILE = new File(logFolderPath, "Logs.txt");
private static FileWriter fw;
private static final SimpleDateFormat sdf = new SimpleDateFormat(" yyyy-MM-dd HH:mm:ss.SSS ", Locale.US);
private static long lastMs, lastNs;
private synchronized static void put(char level, String tag, String msg,Throwable thr) {
try {
if(!LOG_FILE.exists())
LOG_FILE.createNewFile();
/* if (fw == null)*/ {
fw = new FileWriter(LOG_FILE, true); // true: append
}
Date d = new Date();
long nowMs = d.getTime();
long nowNs = System.nanoTime();
if (lastMs == 0)
lastMs = nowMs;
if (lastNs == 0)
lastNs = nowNs;
fw.write(level);
fw.write(sdf.format(d));
fw.write(Long.toString(nowMs - lastMs));
fw.write(' ');
fw.write(Long.toString(nowNs));
fw.write(' ');
fw.write(Double.toString((nowNs - lastNs) / 1e6));
fw.write(' ');
fw.write(tag);
fw.write(' ');
fw.write(msg);
fw.write('\n');
if (thr != null)
thr.printStackTrace(new PrintWriter(fw));
fw.flush();
lastMs = nowMs;
lastNs = nowNs;
}
catch (IOException ex) {
android.util.Log.e(tag, "IOException", ex);
}
}
}
To programmatically create a FileAppender in logback-android, use the following code:
//import org.slf4j.LoggerFactory;
//import org.slf4j.Logger;
//import ch.qos.logback.core.FileAppender;
//import ch.qos.logback.core.util.StatusPrinter;
//import ch.qos.logback.classic.LoggerContext;
//import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
//import ch.qos.logback.classic.Level;
//import ch.qos.logback.classic.spi.ILoggingEvent;
private void configureFileAppender() {
// reset the default context (which may already have been initialized)
// since we want to reconfigure it
LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
context.reset();
final String LOG_DIR = getApplicationInfo().dataDir + "/logs";
FileAppender<ILoggingEvent> fileAppender = new FileAppender<ILoggingEvent>();
fileAppender.setAppend(true);
fileAppender.setContext(context);
fileAppender.setFile(LOG_DIR + "/log.txt");
PatternLayoutEncoder encoder = new PatternLayoutEncoder();
encoder.setPattern("%logger{35} - %msg%n");
encoder.setContext(context);
encoder.start();
fileAppender.setEncoder(encoder);
fileAppender.start();
// add the newly created appender to the root logger;
// qualify Logger to disambiguate from org.slf4j.Logger
ch.qos.logback.classic.Logger root = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
root.setLevel(Level.TRACE);
root.addAppender(fileAppender);
// DEBUG: print any status messages (warnings, etc) encountered in logback config
//StatusPrinter.print(context);
}
You might want to consider using a RollingFileAppender instead to avoid filling up your disk. See example from the logback-android Wiki.
I have made Android application that download zip file from server using Android Download Manager class and then uncompress the file and store that into SD card on pictures folder. On some of the phones.
The zip file is not downloading and download manager progress bar never show progress even if I keep it for hours. Whereas on other phones this works perfectly.
The file size is 40 MB. Is there any known limitation of Android Download Manager or in case of .zip files?
I have been using a variation of (using another class for unzipping, but since the issue here is related to downloading, i am suggesting it) this class for purposes of downloading (the file name retains specific implementation, but it is just a matter of renaming accordingly...). The work() method of this class can be called from within the run method of a Runnable object for parallel threading as inferred from the initial comment:
package com.package;
/*
This class is intended to download file filtering purpose and suffix from the server.
IMPORTANT:This is intended to be instantiated within a separate thread (i.e., != UI Thread)
*/
import android.support.v7.app.AppCompatActivity;
import android.widget.Toast;
final class FileDownloader
{
// Declaring a the maximum buffer size
private static final int MAXIMUM_BUFFER_SIZE = 1024;
// Declaring static final byte fields for coding the status
protected static final byte ISDOWNLOADING = 0;
protected static final byte ERROROCCURRED = 1;
protected static final byte DOWNLOADISCOMPLETE = 2;
// Declaring a private URL field for storing the file for downloading
private java.net.URL url = null;
// Declaring a private int field for storing the file size in bytes
private int filesize;
// Declaring a private int field for storing the amount of downloaded bytes
private int bytesDownloaded;
// Declaring a private byte field for storing the current status of the download
private byte currentStatus;
// A private static final string for storing the server contents location
private static final String SERVER = "https://server.com/zipfiles/";
// Declaring a private field for storing the caller context, used for defining
// the path for saving files
private android.content.Context callerContext = null;
// The following rule is going to be applied for distributing purpose and their contents:
// 'purpose.x.zip' zip file to store the folders of the the x purpose_id and its inherent
// structure
private static final String PURPOSE= "purpose";
private String x = null;
private static final String SUFFIX = "zip";
// The remote file to be downloaded is going to be [stringed as]:
// SERVER + PURPOSE + "." + ((String.valueOf(x)).trim()) + "." + suffix
private String remoteFile = null;
// Defining a private static final File field for storing the purposes' contents within it.
// Specifically, this is being designed to be:
// java.io.File seekingRegisteredUserFolder =
// new java.io.File(callerContext.getFilesDir(), "RegisteredUser");
private final java.io.File seekingRegisteredUserFolder;
// The class constructor. The constructor depends on constructing elements for downloading
// the remoteFile respective to the element_ [cf. constructor parameter] under consideration,
// viz.:
protected FileDownloader(final String x_, final android.content.Context callerContext_)
throws
java.net.MalformedURLException,
java.io.FileNotFoundException,
java.lang.SecurityException
{
this.x = x_;
this.remoteFile = SERVER + PURPOSE + "." + ((String.valueOf(this.x)).trim()) + "." + SUFFIX;
int parsedW = 0;
try
{
parsedW = Integer.parseInt(x_);
}
catch (Exception throwableThrownParsingW)
{
throw new java.net.MalformedURLException();
}
// Implementation specific
if (parsedW < 1)
{
throw new java.net.MalformedURLException();
}
this.callerContext = callerContext_;
this.seekingRegisteredUserFolder = new java.io.File((this.callerContext).getFilesDir(), "RegisteredUser");
if (!((this.seekingRegisteredUserFolder).exists()))
{
throw new java.io.FileNotFoundException();
}
this.url = new java.net.URL(this.remoteFile);
this.filesize = -1;
this.bytesDownloaded = 0;
this.currentStatus = ISDOWNLOADING;
}
// Begins the file download. This is to be called under an object of this class instantiation
boolean work()
{
final java.io.RandomAccessFile[] randomAccessFile = {null};
final java.io.InputStream[] inputStream = {null};
final java.io.File[] purpose = {null};
try
{
purpose[0] = new java.io.File(seekingRegisteredUserFolder, (PURPOSE + "." + x + "." + SUFFIX));
// Opens a connection to the URL via ssl
final javax.net.ssl.HttpsURLConnection[] connection = {null};
connection[0] = (javax.net.ssl.HttpsURLConnection) url.openConnection();
// Defines the file part to download
connection[0].setRequestProperty("Range", "bytes=" + bytesDownloaded + "-");
// Connects to the server
connection[0].connect();
// The response code must be within the 200 range
if ((connection[0].getResponseCode() / 100) != 2)
{
currentStatus = ERROROCCURRED;
}
// Inferring the validity of the content size
final int[] contentLength = {0};
contentLength[0] = connection[0].getContentLength();
if (contentLength[0] < 1)
{
currentStatus = ERROROCCURRED;
}
// Configuring the download size, case not yet configured
if (filesize == -1)
{
filesize = contentLength[0];
}
// Opens the file, seeking its final
randomAccessFile[0] = new java.io.RandomAccessFile(purpose[0], "rw");
randomAccessFile[0].seek(bytesDownloaded);
inputStream[0] = connection[0].getInputStream();
while (currentStatus == ISDOWNLOADING)
{
// Defines the buffer according to the left amount of file to complete
byte[] byteBuffer = null;
if ((filesize - bytesDownloaded) > MAXIMUM_BUFFER_SIZE)
{
byteBuffer = new byte[MAXIMUM_BUFFER_SIZE];
}
else
{
byteBuffer = new byte[filesize - bytesDownloaded];
}
// Reads from server to the buffer
int read = inputStream[0].read(byteBuffer);
if (read == -1)
{
break;
}
// Writes from buffer to file
randomAccessFile[0].write(byteBuffer, 0, read);
bytesDownloaded += read;
}
// Changing the status for complete since this point of code has been reached
if (currentStatus == ISDOWNLOADING)
{
currentStatus = DOWNLOADISCOMPLETE;
}
}
catch (java.lang.Exception connectionException)
{
currentStatus = ERROROCCURRED;
}
finally
{
// Closes the [RandomAccessFile] file
if (randomAccessFile[0] != null)
{
try
{
randomAccessFile[0].close();
}
catch (java.lang.Exception closingFileException)
{
currentStatus = ERROROCCURRED;
}
}
if (inputStream[0] != null)
{
try
{
inputStream[0].close();
}
catch (java.lang.Exception closingConnectionException)
{
currentStatus = ERROROCCURRED;
}
}
}
if ((currentStatus == DOWNLOADISCOMPLETE) && (purpose[0] != null) &&
(purpose[0]).isFile() && (purpose[0].length() > 0) && (purpose[0].length() == filesize))
{
((AppCompatActivity) callerContext).runOnUiThread
(
new Runnable()
{
#Override
public final void run()
{
Toast.makeText(callerContext, "Downloaded: " + remoteFile.substring(remoteFile.indexOf(SERVER) + SERVER.length()), Toast.LENGTH_LONG).show();
}
}
);
return true;
}
return false;
}
}
I've been looking for a way to capture the order of element's listed within a tag, but haven't been very successful..
EDIT NEW:
<android>
<leags>
<leag name = "someName">
<headlines>
<pageType>headlines</pageType>
<story>someStoryURL</story>
<fullStory>someFullStoryURL</fullStory>
</headlines>
<scores></scores>
<statistics></statistics>
</leag>
<leags>
</android>
-Want to capture the order of elements in leag as 1)headlines 2)scores 3)statistics. If the xml changes and scores is listed before headlines it would be 1)scores 2)headlines 3)statistics.
I parse only android - Like this:
#Root(name = "android", strict = false)
public class android
{
#ElementList(name = "leags", required = false)
private List<Leag> leags;
public Leag getLeagByName(String name)
{ // go through list of leags and return leag with matching name}
}
So in my "leag" object I'd want to capture the order of elements - Is there a way to do that?
I'm assuming you'd need to set new AnnotionStrategy() like this:
tAndroid android = null;
Serializer serial = new Persister(new AnnotationStrategy());
android = serial.read(android .class, source);
League x= android.getLeagueByName("oeoe");
for(String s: x.getOrder())
{
Log.i("Order", s);
}
BEFORE EDIT:
Supposing the xml above is what's being pased by the following code:
#Element(name="headlines")
public class Headlines
{
#Element(name="pageType", required = false)
private String pageType;
#Element(name="story", required = false)
private String storiesURL;
#Element(name="fullStory", required = false)
private String fullStoryURL;
public String getStoriesURL()
{
return storiesURL;
}
public String getFullStoryURL()
{
return fullStoryURL;
}
#Override
public String toString()
{
return "PageType: " + this.pageType +
"\nStoriesURL: " + this.storiesURL +
"\nFullStoryURL: " + this.fullStoryURL;
}
}
Is there a way to somehow return the order in which the elements get parsed?
Like a method that will return a string of some sort with the correct order like:
pageType
story
fullStory
You can use a Converter to get the order. But you can't return the order from it (or better: you can, but better don't do it).
It's relatively easy to get the order - the trick is getting it out from the Converter. On way is to add a list to your class and store it there.
Implementation:
#Root(name = "headlines")
#Convert(value = Headlines.HeadlinesConverter.class)
public class Headlines
{
#Element(name="pageType", required = false)
private String pageType;
#Element(name="story", required = false)
private String storiesURL;
#Element(name="fullStory", required = false)
private String fullStoryURL;
private List<String> order; // Here we save the order of the elements
public String getPageType()
{
return pageType;
}
public String getStoriesURL()
{
return storiesURL;
}
public String getFullStoryURL()
{
return fullStoryURL;
}
public List<String> getOrder()
{
return order;
}
#Override
public String toString()
{
return "Headlines{" + "pageType=" + pageType
+ ", storiesURL=" + storiesURL
+ ", fullStoryURL=" + fullStoryURL + '}';
}
// You can implement the converter as an extra class too
static class HeadlinesConverter implements Converter<Headlines>
{
#Override
public Headlines read(InputNode node) throws Exception
{
Headlines h = new Headlines();
h.order = new ArrayList<>(3);
InputNode next = node.getNext();
while( next != null )
{
final String value = next.getValue();
/*
* You can use reflection (= slower) instead the switch, or
* remove the switch:
*
* h.order.add(next.getName());
*
* and do this after the while loop:
*
* h.pageType = node.getNext("pageType");
* ...
*/
switch(next.getName())
{
case "pageType":
h.pageType = value;
break;
case "story":
h.storiesURL = value;
break;
case "fullStory":
h.fullStoryURL = value;
break;
default:
/* Maybe some error-handling here?! */
break;
}
h.order.add(next.getName()); // add 'value' if you want the order of the values
next = node.getNext();
}
return h;
}
#Override
public void write(OutputNode node, Headlines value) throws Exception
{
throw new UnsupportedOperationException("Not supported yet.");
}
}
}
Note: I didn't use setter here - but it's better you do so.
Example code:
final File f = new File("test.xml");
Serializer ser = new Persister(new AnnotationStrategy()); /* ! */
Headlines h = ser.read(Headlines.class, f);
int i = 1;
for( String s : h.getOrder() )
{
System.out.println((i++) + ". " + s);
}
and finally the output:
1. pageType
2. story
3. fullStory
You need to use #Order annotation. Here can see an example http://simple.sourceforge.net/download/stream/doc/tutorial/tutorial.php#xpath
I have created an app using SPenSdk libraries. My app simply open an canvasView and I saved that canvas view as an image in the sd card. Now problems arise when the user removes it sd card from the devices. At that time when I run my app it crashes because it is unable to find images for loading. So is there any way in which I can save my images created using canvas view in such a way that it will be my part of the app. so the removing of sd card doesn't affect the app.
Code For Saving Images In SdCard
public class CanvasActivity extends Activity
{
public static final String DEFAULT_APP_IMAGEDATA_DIRECTORY = "/mnt/sdcard/SmemoExample";
private File m_Folder = null;
private String m_FileName;
public static final String SAVED_FILE_EXTENSION = "png";
public static final String EXTRA_IMAGE_PATH = "path";
public static final String EXTRA_IMAGE_NAME = "filename";
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
this.requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.main);
m_Folder = new File(DEFAULT_APP_IMAGEDATA_DIRECTORY);
m_FileName = getIntent().getStringExtra(EXTRA_IMAGE_NAME);
loadCanvas(m_FileName);
}
public boolean saveCanvas()
{
byte[] buffer = m_CanvasView.getData();
if (buffer == null)
return false;
if (!(m_Folder.exists()))
m_Folder.mkdirs();
String savePath = null;
if(m_FileName == null)
{
savePath = m_Folder.getPath() + '/' + UtilitiesActivity.getUniqueFilename(m_Folder, "image", SAVED_FILE_EXTENSION);
}
else
{
savePath = m_Folder.getPath() + '/' + m_FileName;
}
if (UtilitiesActivity.writeBytedata(savePath, buffer))
return true;
else
return false;
}
public boolean loadCanvas(String fileName)
{
String loadPath = m_Folder.getPath() + '/' + fileName;
byte[] buffer = UtilitiesActivity.readBytedata(loadPath);
if (buffer == null)
return false;
m_CanvasView.setData(buffer);
return true;
}
private CanvasView.OnHistoryChangeListener historyChangeListener = new CanvasView.OnHistoryChangeListener()
{
#Override
public void onHistoryChanged(boolean bUndoable, boolean bRedoable)
{
m_UndoButton.setEnabled(bUndoable);
m_RedoButton.setEnabled(bRedoable);
}
};
CanvasView.InitializeFinishListener mInitializeFinishListener = new CanvasView.InitializeFinishListener() {
#Override
public void onFinish() {
Bitmap bg = BitmapFactory.decodeResource(getResources(), R.drawable.canvas_bg);
m_CanvasView.setBackgroundImage(bg);
bg.recycle();
}
};
}
The issue isn't database or non database the issue is WHERE are your images. There's limited space ON the device, but if you don't want to store images on the sdcard you should just store them on the device's onboard memory. Stuffing them into database blobs doesn't help you here (actually, it just makes things more complicated).
Here's how you do it:
http://developer.android.com/guide/topics/data/data-storage.html#filesInternal