Add zero before number in QML clock - qt

I'm learning qml and trying to build a basic clock. Now I'm trying to add zeros before the hours, minutes and seconds when they are less than 10. How to do that most effective way?
Here is the code I'm using:
Item {
id: time
width: parent.width
height: 80
property date now: new Date()
property int hours: time.now.getHours()
property int minutes: time.now.getMinutes()
property int seconds: time.now.getSeconds()
property int day: time.now.getDate()
property var month: ["January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"];
property int month_number: time.now.getMonth() + 1
property int year: time.now.getFullYear()
Text {
text: time.hours + ":" + time.minutes + ":" + time.seconds
font.pixelSize: 48
font.weight: Font.DemiBold
color: "#AEB3C3"
}
Text {
text: time.day + " " + time.month[time.month_number] + " " + time.year + " г."
font.pixelSize: 20
color: "#AEB3C3"
y: 60
}
Timer {
id: updateclock
interval: 1000
running: true
repeat: true
onTriggered: {
time.now = new Date()
}
}
}

Firstly, Javascript had String.prototype.padStart() which can be used to ensure your hh:mm:ss pads your numbers to two digits, e.g.
let hours = 5;
let minutes = 31;
let seconds = 4;
console.log( hours.toString().padStart(2, '0')
+ ':' + minutes.toString().padStart(2, '0')
+ ':' + seconds.toString().padStart(2, '0') );
// Output: 05:31:04
However, in your specific example, technically you should go straight for Qt.formatDateTime which has built in support to display time in both hh:mm:ss and dd MMMM yyyy formats. This has built-in padding zero and language locale options for displaying the month string. Also, it is generally better to use pointSize instead of pixelSize so that your code can work on high DPI devices without the need of applying a DPI scale factor:
import QtQuick
import QtQuick.Controls
Page {
property double now: Date.now()
property date nowDate: new Date(now)
Column {
anchors.centerIn: parent
Text {
text: Qt.formatDateTime(nowDate, "hh:mm:ss")
color: "#AEB3C3"
font.pointSize: 20
}
Text {
text: Qt.formatDateTime(nowDate, "dd MMMM yyyy")
color: "#AEB3C3"
font.pointSize: 20
}
}
Timer {
interval: 1000
repeat: true
running: true
onTriggered: now = Date.now()
}
}
You can Try it Online!

Related

Add 10 days with selected date -on-change method -service now

I have 2 date fields issued date and due date.When I choose issued date,due date should be auto populated by adding 10days with selected date. I have written on-change method for this
function onChange(control, oldValue, newValue, isLoading, isTemplate) {
if (isLoading || newValue === '') {
return;
}
//var issuedDate=new GlideDateTime(g_form.getValue('u_issued_date'))
//var issuedDate=g_form.getValue('u_issued_date')
alert(issuedDate)
var gdt = new GlideDateTime(issuedDate);
gdt.addDays(10)
g_form.setValue('u_due_date',gdt);
}
I am getting an error GlideDateTime is not defined function ().How can I achieve this? Is there any other way?
GlideDateTime is not available on client side. For simple operation like the one you are having you can use javascript Date object. Which is pain to format, but doable, example:
var date = new Date(g_form.getValue('u_issued_date'));
date.setDate(date.getDate() + 10); //add 10 days
g_form.setValue('u_due_date', formatDate(date));
function formatDate (date) {
return date.getFullYear() + '-' +
leadingZero(date.getMonth() + 1) + '-' +
leadingZero(date.getDate()) + ' ' +
date.getHours() + ':' +
date.getMinutes() + ':' +
date.getSeconds();
}
function leadingZero (value) {
return ("0" + value).slice(-2);
}
For more complicated operation you would wish GlideDateTime you will have to use GlideAjax, that will do operations on server side, and provide result.

Programmatically adding a NSTableView to a NSStackView

I was wondering about programmatically adding a NSTableView inside a NSStackView using Swift 3/MacOS Sierra.
The idea would be to have say 2 NSTextFields aligned via the centerY axis in the .leading gravity space, then a tableview in the .center gravity space, then 2 more NSTextFields aligned via the centerY axis in the .trailing gravity space. The stack view would span the width of the NSView -- like a header.
Is this a good idea or should I avoid doing this? It has been very difficult to get it to look correct -- the table always has too large of a width despite adding constraints to try to pin it to a fixed width.
Any insight would be appreciated. I'm new to programming MacOS.
Thanks,
Here is the output in Interface Builder:
output of the headerview
Here is the code of the NSView I'm using:
The view controller is elsewhere but I'm not really having problems with the view controller -- it's displaying the data in the table correctly. It's just the sizing/positioning of the tableview (which I'm trying to do in the NSView via the NSStackView) is always wrong. It should have a width of 650 but instead has a width of 907 and I get the same error all the time in the debug console:
2017-09-12 17:43:36.041062-0500 RaceProgram[795:36958] [Layout] Detected missing constraints for < RacingProgram.RaceImportViewHeader: 0x6000001ccd50 >. It cannot be placed because there are not enough constraints to fully define the size and origin. Add the missing constraints, or set translatesAutoresizingMaskIntoConstraints=YES and constraints will be generated for you. If this view is laid out manually on macOS 10.12 and later, you may choose to not call [super layout] from your override. Set a breakpoint on DETECTED_MISSING_CONSTRAINTS to debug. This error will only be logged once.
import Cocoa
#IBDesignable
class RaceImportViewHeader: NSView {
// MARK: Properties
private var raceQualificationsTableView:NSTableView
private var raceImportHeaderStackView:NSStackView
private var raceNumberTitle: NSTextField
private var raceNumberValue: NSTextField
public var raceQualificationsTableRowHeight: CGFloat
#IBInspectable var borderColor:NSColor = .black
#IBInspectable var backgroundColor:NSColor = .lightGray
enum InitMethod {
case Coder(NSCoder)
case Frame(CGRect)
}
override convenience init(frame: CGRect) {
self.init(.Frame(frame))!
}
required convenience init?(coder: NSCoder) {
self.init(.Coder(coder))
}
private init?(_ initMethod: InitMethod) {
// Group together the initializers for this view class
raceQualificationsTableView = NSTableView()
raceImportHeaderStackView = NSStackView()
raceNumberTitle = NSTextField()
raceNumberValue = NSTextField()
raceQualificationsTableRowHeight = 17.0 // Initialize the row height for raceQualifications
switch initMethod {
case let .Coder(coder): super.init(coder: coder)
case let .Frame(frame): super.init(frame: frame)
}
self.translatesAutoresizingMaskIntoConstraints = false
drawUI()
}
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
let viewSize: NSRect = self.frame
let newRect = NSRect(x: 0, y: 0, width: viewSize.width, height: viewSize.height)
// Outline the Header --> Only for layout debug purposes
let path = NSBezierPath(rect: newRect)
backgroundColor.setFill()
path.fill()
borderColor.setStroke() // Set the stroke color
path.stroke() // Fill the stroke or border of the rectangle
}
// MARK: UI Construction
func drawUI() {
let viewFrame = self.frame // with respect to the super class
let viewBounds = self.bounds // with respect to the view
// MARK: Race Number Setup
func addRaceNumberTitle(startingPositionX: CGFloat) {
// This configures label for race Number
let width:CGFloat = 60.0 //Arbitrary at the moment
let height:CGFloat = 40.0
let leftPadding:CGFloat = 2.5 // The super view (frame)is the NSView in this case
let topPadding:CGFloat = (viewBounds.height - height)/2
let raceNumberTitleNSRect = NSRect(x: leftPadding + startingPositionX, y: viewBounds.height - height - topPadding, width: width, height: height)
//Swift.print("The raceNumberTitleNSRect title NSRect is \(raceNumberTitleNSRect)")
raceNumberTitle = NSTextField(frame: raceNumberTitleNSRect)
raceNumberTitle.stringValue = "Race\nNumber"
raceNumberTitle.maximumNumberOfLines = 2
raceNumberTitle.isEditable = false
raceNumberTitle.isBordered = false
raceNumberTitle.alignment = .center
raceNumberTitle.backgroundColor = .clear
raceNumberTitle.sizeToFit()
let updatedHeight = raceNumberTitle.frame.height
let newUpdatedPadding = (viewBounds.height - updatedHeight) / 2
let oldOriginX = raceNumberTitle.frame.origin.x
let newOriginY = viewBounds.height - updatedHeight - newUpdatedPadding
let newOrigin = NSPoint(x: oldOriginX, y: newOriginY)
raceNumberTitle.setFrameOrigin(newOrigin)
//addSubview(raceNumberTitle) // Add to view
raceImportHeaderStackView.addView(raceNumberTitle, in: .leading)
}
func addRaceNumberValue(startingPositionX: CGFloat) {
// This configures value label for race number
let width:CGFloat = 20.0 //Arbitrary at the moment
let height:CGFloat = 40.0
let leftPadding:CGFloat = 5.0 // The super view (frame)is the NSView in this case
let topPadding:CGFloat = (viewBounds.height - height)/2
let raceNumberInRect = NSRect(x: startingPositionX + leftPadding, y: viewBounds.height - height - topPadding, width: width, height: height)
Swift.print("The raceNumberInRect title NSRect is \(raceNumberInRect)")
raceNumberValue = NSTextField(frame: raceNumberInRect)
raceNumberValue.identifier = "raceNumber"
raceNumberValue.placeholderString = "1"
raceNumberValue.font = NSFont(name: "Impact", size: 20.0)
raceNumberValue.maximumNumberOfLines = 1
raceNumberValue.isEditable = false
raceNumberValue.isBordered = true
raceNumberValue.alignment = .center
raceNumberValue.backgroundColor = .clear
raceNumberValue.sizeToFit()
let updatedHeight = raceNumberValue.frame.height
let oldOriginX = raceNumberValue.frame.origin.x
let newUpdatedPadding = (viewBounds.height - updatedHeight) / 2
let newOriginY = viewBounds.height - updatedHeight - newUpdatedPadding
let newOrigin = NSPoint(x: oldOriginX, y: newOriginY)
raceNumberValue.setFrameOrigin(newOrigin)
//addSubview(raceNumberValue) // Add to view
raceImportHeaderStackView.addView(raceNumberValue, in: .leading)
}
// MARK: Race Qualifications Table Setup
func addRaceQualificationsTable(startingPositionX: CGFloat) {
// Padding variables
let leftPadding:CGFloat = 5.0
let topPadding:CGFloat = 5.0
// Table Properties
let width:CGFloat = 650.0
let height:CGFloat = 40
let tableRect = CGRect(x: startingPositionX + leftPadding, y: viewBounds.height - height - topPadding, width: width, height: height)
//let insetForTableView:CGFloat = 1.0
//let scrollRect = CGRect(x: tableRect.origin.x-insetForTableView, y: tableRect.origin.y-insetForTableView, width: tableRect.width+2*insetForTableView, height: tableRect.height+2*insetForTableView)
let tableNSSize = NSSize(width: tableRect.width, height: tableRect.height)
let scrollNSRect = NSScrollView.frameSize(forContentSize: tableNSSize, horizontalScrollerClass: nil, verticalScrollerClass: nil, borderType: .bezelBorder, controlSize: .regular, scrollerStyle: .legacy)
Swift.print("tableRect \(tableRect)")
Swift.print("scrollNSRect \(scrollNSRect)")
//Swift.print("scrollRect \(scrollRect)")
let scrollViewOrigin:CGPoint = tableRect.origin
let scrollViewNSSize:CGSize = scrollNSRect
let scrollRect = NSRect(origin: scrollViewOrigin, size: scrollViewNSSize)
Swift.print("scrollRect \(scrollRect)")
let tableScrollView = NSScrollView(frame: scrollRect)
raceQualificationsTableView = NSTableView(frame: tableRect)
raceQualificationsTableView.identifier = "raceQualificationsTable" // Setup identifier
raceQualificationsTableView.rowHeight = 20.0
Swift.print("instrinic size \(raceQualificationsTableView.intrinsicContentSize)")
//Swift.print("tableScrollView contentsize \(tableScrollView.contentSize)")
tableScrollView.documentView = raceQualificationsTableView
tableScrollView.autoresizingMask = .viewNotSizable
Swift.print("tableScroll content size \(tableScrollView.contentSize)")
//self.addSubview(tableScrollView)
raceImportHeaderStackView.addView(tableScrollView, in: .center)
}
func configureRaceQualificationsTable(showRaceNumberCol: Bool, showRaceCodeCol: Bool) {
let headerAlignment = NSTextAlignment.center // Easy way to change justification of headers
// MARK: Race Number Column Options
let raceNumberColumn = NSTableColumn(identifier: "raceNumberCol")
raceNumberColumn.title = "Race"
raceNumberColumn.minWidth = 40.0
raceNumberColumn.width = 40.0
raceNumberColumn.headerToolTip = "Race Number from the Imported Card"
raceNumberColumn.headerCell.alignment = headerAlignment
// Note: Word Race is always going to be wider than the race number value
// So size to Fit is appropriate here.
raceNumberColumn.sizeToFit()
if showRaceNumberCol {
// Option of not adding this to the table
raceQualificationsTableView.addTableColumn(raceNumberColumn)
}
// MARK: Driver Column Options
let breedColumn = NSTableColumn(identifier: "driverCol")
driverColumn.title = "Driver"
driverColumn.minWidth = 10
driverColumn.headerToolTip = "Driver information"
driverColumn.headerCell.alignment = headerAlignment
driverColumn.sizeToFit()
raceQualificationsTableView.addTableColumn(driverColumn)
// MARK: Race Code Column Options
let raceTypeCodeColumn = NSTableColumn(identifier: "raceTypeCodeCol")
raceTypeCodeColumn.title = "Race Code"
raceTypeCodeColumn.minWidth = 40
raceTypeCodeColumn.headerToolTip = "Race Classification Code"
raceTypeCodeColumn.headerCell.alignment = headerAlignment
raceTypeCodeColumn.sizeToFit()
if showRaceCodeCol {
// Option of not adding to the table
raceQualificationsTableView.addTableColumn(raceTypeCodeColumn)
}
// MARK: Race Type Code Description Options
let raceTypeCodeDescColumn = NSTableColumn(identifier: "raceTypeCodeDescCol")
raceTypeCodeDescColumn.title = "Race Desc"
raceTypeCodeDescColumn.minWidth = 50
raceTypeCodeDescColumn.width = 100
raceTypeCodeDescColumn.headerToolTip = "Race Classification Full Description"
raceTypeCodeDescColumn.headerCell.alignment = headerAlignment
raceQualificationsTableView.addTableColumn(raceTypeCodeDescColumn)
// MARK: Race Restriction Column Options
let raceRestrictionColumn = NSTableColumn(identifier: "raceRestrictionCol")
raceRestrictionColumn.title = "Restrictions"
raceRestrictionColumn.minWidth = 50
raceRestrictionColumn.width = 80
raceRestrictionColumn.headerToolTip = "Race Restrictions"
raceRestrictionColumn.headerCell.alignment = headerAlignment
raceQualificationsTableView.addTableColumn(raceRestrictionColumn)
// MARK: Sex Restriction Column Options
let sexRestrictionColumn = NSTableColumn(identifier: "sexRestrictionCol")
sexRestrictionColumn.title = "Sex"
sexRestrictionColumn.minWidth = 100
sexRestrictionColumn.width = 100
sexRestrictionColumn.headerToolTip = "Sex Restrictions"
sexRestrictionColumn.headerCell.alignment = headerAlignment
raceQualificationsTableView.addTableColumn(sexRestrictionColumn)
// MARK: Age Restriction Column Options
let ageRestrictionColumn = NSTableColumn(identifier: "ageRestrictionCol")
ageRestrictionColumn.title = "Age"
ageRestrictionColumn.minWidth = 100
ageRestrictionColumn.width = 100
ageRestrictionColumn.headerToolTip = "Age Restrictions"
ageRestrictionColumn.headerCell.alignment = headerAlignment
raceQualificationsTableView.addTableColumn(ageRestrictionColumn)
// MARK: Division Column Options
let divisionColumn = NSTableColumn(identifier: "divisionCol")
divisionColumn.title = "Division"
divisionColumn.minWidth = 50
let minDivisionColumnWidth = raceQualificationsTableView.frame.width - raceNumberColumn.width - driverColumn.width - raceTypeCodeColumn.width - raceTypeCodeDescColumn.width - raceRestrictionColumn.width - sexRestrictionColumn.width - ageRestrictionColumn.width
// Calculate the available room for the division column
if (showRaceCodeCol && showRaceNumberCol) {
// This is the minimum case
// No idea why we need the 25.0 manual adjustment
divisionColumn.width = minDivisionColumnWidth - 25.0
} else if (showRaceCodeCol && !showRaceNumberCol) {
// Add back race type code
// No idea why we need to manually adjust 53.5
divisionColumn.width = minDivisionColumnWidth + raceTypeCodeColumn.width - 53.5
} else if (!showRaceCodeCol && showRaceNumberCol) {
// Add back race number col
divisionColumn.width = minDivisionColumnWidth + raceNumberColumn.width
} else {
// Else it's the maximum space
// This code was making the frame too large -- it was increasing the
// the frame size of the column to 670.0 I put a manual reduction of
// 20 to keep the frame size the same. Not sure where this 20 is coming from.
divisionColumn.width = minDivisionColumnWidth + raceNumberColumn.width + raceTypeCodeColumn.width - 20.0
}
//Swift.print("The division column width is \(divisionColumn.width)")
divisionColumn.headerToolTip = "Division -- Unknown what this means"
divisionColumn.headerCell.alignment = headerAlignment
raceQualificationsTableView.addTableColumn(divisionColumn)
//Swift.print("raceQualificationsTableView.frame.width is \( raceQualificationsTableView.frame.width)")
}
// MARK: Race Distance Surface Course Setup
func addRaceDistanceSurfaceCourseTable(startingPositionX: CGFloat) {
// Table Properties
let width:CGFloat = 250.0
let height:CGFloat = 40.0
// Padding variables
let leftPadding:CGFloat = 5.0
let topPosition:CGFloat = (viewBounds.height - ((viewBounds.height - height)/2) - height)
let tableRect = CGRect(x: leftPadding + startingPositionX, y: topPosition, width: width, height: height)
let tableScrollView = NSScrollView(frame: tableRect)
raceDistanceSurfaceCourseTableView = NSTableView(frame: tableRect)
raceDistanceSurfaceCourseTableView.identifier = "raceDistanceSurfaceCourseTable" // Setup identifier
//raceDistanceSurfaceCourseTableView.rowHeight = 20.0
raceDistanceSurfaceCourseTableView.intercellSpacing = NSSize(width: 1.0, height: 1.0)
raceDistanceSurfaceCourseTableView.headerView = ImportRaceTableHeaders()
tableScrollView.documentView = raceDistanceSurfaceCourseTableView
//tableScrollView.hasVerticalScroller = false
//tableScrollView.verticalScroller = nil // Turn off vertical scrolling
//tableScrollView.verticalScrollElasticity = .none
//raceDistanceSurfaceCourseTableView = NSTableViewHeader
//self.addSubview(tableScrollView)
raceImportHeaderStackView.addView(raceDistanceSurfaceCourseTableView, in: .center)
}
// MARK: Construct the fields:
//configureHeaderView()
configureStackView()
addRaceNumberTitle(startingPositionX: 0.0) // Add the race number title
addRaceNumberValue(startingPositionX: raceNumberTitle.frame.origin.x + raceNumberTitle.frame.width) //Add the Race Number value text field
addRaceQualificationsTable(startingPositionX: raceNumberValue.frame.origin.x + raceNumberValue.frame.width)
configureRaceQualificationsTable(showRaceNumberCol: false, showRaceCodeCol: false)
}
// MARK: TableView Functions
func reloadTableViewData(identifier: String) {
Swift.print("Manual reload of data for identifier \(identifier)")
switch identifier {
case "raceQualificationsTable":
raceQualificationsTableView.reloadData()
case "raceDistanceSurfaceCourseTable":
raceDistanceSurfaceCourseTableView.reloadData()
default:
break
}
}
// MARK: Delegate/DataSources Outlets for TableViews
// Race Qualification Table (the header table)
#IBOutlet weak var raceQualificationsDelegate: NSTableViewDelegate? {
get {
return raceQualificationsTableView.delegate
}
set {
raceQualificationsTableView.delegate = newValue
}
}
#IBOutlet weak var raceQualificationsDataSource: NSTableViewDataSource? {
get {
return raceQualificationsTableView.dataSource
}
set {
raceQualificationsTableView.dataSource = newValue
}
}
// Race Distance Surface Course
#IBOutlet weak var raceDistanceSurfaceCourseDelegate: NSTableViewDelegate? {
get {
return raceDistanceSurfaceCourseTableView.delegate
}
set {
raceDistanceSurfaceCourseTableView.delegate = newValue
}
}
#IBOutlet weak var raceDistanceSurfaceCourseDataSource: NSTableViewDataSource? {
get {
return raceDistanceSurfaceCourseTableView.dataSource
}
set {
raceDistanceSurfaceCourseTableView.dataSource = newValue
}
}
// MARK: Label Outlets
#IBOutlet var raceNumber:String? {
get {
return raceNumberValue.stringValue
}
set {
raceNumberValue.stringValue = newValue!
}
}
}

Text extraction - line-by-line

I am using Google Vision API, primarily to extract texts. I works fine, but for specific cases where I would need the API to scan the enter line, spits out the text before moving to the next line. However, it appears that the API is using some kind of logic that makes it scan top to bottom on the left side and moving to right side and doing a top to bottom scan. I would have liked if the API read left-to-right, move down and so on.
For example, consider the image:
The API returns the text like this:
“ Name DOB Gender: Lives In John Doe 01-Jan-1970 LA ”
Whereas, I would have expected something like this:
“ Name: John Doe DOB: 01-Jan-1970 Gender: M Lives In: LA ”
I suppose there is a way to define the block size or margin setting (?) to read the image/scan line by line?
Thanks for your help.
Alex
This might be a late answer but adding it for future reference.
You can add feature hints to your JSON request to get the desired results.
{
"requests": [
{
"image": {
"source": {
"imageUri": "https://i.stack.imgur.com/TRTXo.png"
}
},
"features": [
{
"type": "DOCUMENT_TEXT_DETECTION"
}
]
}
]
}
For text which are very far apart the DOCUMENT_TEXT_DETECTION also does not provide proper line segmentation.
The following code does simple line segmentation based on the character polygon coordinates.
https://github.com/sshniro/line-segmentation-algorithm-to-gcp-vision
Here a simple code to read line by line. y-axis for lines and x-axis for each word in the line.
items = []
lines = {}
for text in response.text_annotations[1:]:
top_x_axis = text.bounding_poly.vertices[0].x
top_y_axis = text.bounding_poly.vertices[0].y
bottom_y_axis = text.bounding_poly.vertices[3].y
if top_y_axis not in lines:
lines[top_y_axis] = [(top_y_axis, bottom_y_axis), []]
for s_top_y_axis, s_item in lines.items():
if top_y_axis < s_item[0][1]:
lines[s_top_y_axis][1].append((top_x_axis, text.description))
break
for _, item in lines.items():
if item[1]:
words = sorted(item[1], key=lambda t: t[0])
items.append((item[0], ' '.join([word for _, word in words]), words))
print(items)
You can extract the text based on the bounds per line too, you can use boundyPoly and concatenate the text in the same line
"boundingPoly": {
"vertices": [
{
"x": 87,
"y": 148
},
{
"x": 411,
"y": 148
},
{
"x": 411,
"y": 206
},
{
"x": 87,
"y": 206
}
]
for example this 2 words are in the same "line"
"description": "you",
"boundingPoly": {
"vertices": [
{
"x": 362,
"y": 1406
},
{
"x": 433,
"y": 1406
},
{
"x": 433,
"y": 1448
},
{
"x": 362,
"y": 1448
}
]
}
},
{
"description": "start",
"boundingPoly": {
"vertices": [
{
"x": 446,
"y": 1406
},
{
"x": 540,
"y": 1406
},
{
"x": 540,
"y": 1448
},
{
"x": 446,
"y": 1448
}
]
}
}
I get max and min y and iterate over y to get all potential lines, here is the full code
import io
import sys
from os import listdir
from google.cloud import vision
def read_image(image_file):
client = vision.ImageAnnotatorClient()
with io.open(image_file, "rb") as image_file:
content = image_file.read()
image = vision.Image(content=content)
return client.document_text_detection(
image=image,
image_context={"language_hints": ["bg"]}
)
def extract_paragraphs(image_file):
response = read_image(image_file)
min_y = sys.maxsize
max_y = -1
for t in response.text_annotations:
poly_range = get_poly_y_range(t.bounding_poly)
t_min = min(poly_range)
t_max = max(poly_range)
if t_min < min_y:
min_y = t_min
if t_max > max_y:
max_y = t_max
max_size = max_y - min_y
text_boxes = []
for t in response.text_annotations:
poly_range = get_poly_y_range(t.bounding_poly)
t_x = get_poly_x(t.bounding_poly)
t_min = min(poly_range)
t_max = max(poly_range)
poly_size = t_max - t_min
text_boxes.append({
'min_y': t_min,
'max_y': t_max,
'x': t_x,
'size': poly_size,
'description': t.description
})
paragraphs = []
for i in range(min_y, max_y):
para_line = []
for text_box in text_boxes:
t_min = text_box['min_y']
t_max = text_box['max_y']
x = text_box['x']
size = text_box['size']
# size < max_size excludes the biggest rect
if size < max_size * 0.9 and t_min <= i <= t_max:
para_line.append(
{
'text': text_box['description'],
'x': x
}
)
# here I have to sort them by x so the don't get randomly shuffled
para_line = sorted(para_line, key=lambda x: x['x'])
line = " ".join(map(lambda x: x['text'], para_line))
paragraphs.append(line)
# if line not in paragraphs:
# paragraphs.append(line)
return "\n".join(paragraphs)
def get_poly_y_range(poly):
y_list = []
for v in poly.vertices:
if v.y not in y_list:
y_list.append(v.y)
return y_list
def get_poly_x(poly):
return poly.vertices[0].x
def extract_paragraphs_from_image(picName):
print(picName)
pic_path = rootPics + "/" + picName
text = extract_paragraphs(pic_path)
text_path = outputRoot + "/" + picName + ".txt"
write(text_path, text)
This code is WIP.
In the end, I get the same line multiple times and post-processing to determine the exact values. (paragraphs variable). Let me know if I have to clarify anything
Inspired by Borislav's answer, I just wrote something for python that also works for handwriting. It's messy and I am new to python, but I think you can get an idea of how to do this.
A class to hold some extended data for each word, for example, the average y position of a word, which I used to calculate the differences between words:
import re
from operator import attrgetter
import numpy as np
class ExtendedAnnotation:
def __init__(self, annotation):
self.vertex = annotation.bounding_poly.vertices
self.text = annotation.description
self.avg_y = (self.vertex[0].y + self.vertex[1].y + self.vertex[2].y + self.vertex[3].y) / 4
self.height = ((self.vertex[3].y - self.vertex[1].y) + (self.vertex[2].y - self.vertex[0].y)) / 2
self.start_x = (self.vertex[0].x + self.vertex[3].x) / 2
def __repr__(self):
return '{' + self.text + ', ' + str(self.avg_y) + ', ' + str(self.height) + ', ' + str(self.start_x) + '}'
Create objects with that data:
def get_extended_annotations(response):
extended_annotations = []
for annotation in response.text_annotations:
extended_annotations.append(ExtendedAnnotation(annotation))
# delete last item, as it is the whole text I guess.
del extended_annotations[0]
return extended_annotations
Calculate the threshold.
First, all words a sorted by their y position, defined as being the average of all 4 corners of a word. The x position is not relevant at this moment.
Then, the differences between every word and their following word are calculated. For a perfectly straight line of words, you would expect the differences of the y position between every two words to be 0. Even for handwriting, it should be around 1 ~ 10.
However, whenever there is a line break, the difference between the last word of the former row and the first word of the new row is much greater than that, for example, 50 or 60.
So to decide whether there should be a line break between two words, the standard deviation of the differences is used.
def get_threshold_for_y_difference(annotations):
annotations.sort(key=attrgetter('avg_y'))
differences = []
for i in range(0, len(annotations)):
if i == 0:
continue
differences.append(abs(annotations[i].avg_y - annotations[i - 1].avg_y))
return np.std(differences)
Having calculated the threshold, the list of all words gets grouped into rows accordingly.
def group_annotations(annotations, threshold):
annotations.sort(key=attrgetter('avg_y'))
line_index = 0
text = [[]]
for i in range(0, len(annotations)):
if i == 0:
text[line_index].append(annotations[i])
continue
y_difference = abs(annotations[i].avg_y - annotations[i - 1].avg_y)
if y_difference > threshold:
line_index = line_index + 1
text.append([])
text[line_index].append(annotations[i])
return text
Finally, each row is sorted by their x position to get them into the correct order from left to right.
Then a little regex is used to remove whitespace in front of interpunctuation.
def sort_and_combine_grouped_annotations(annotation_lists):
grouped_list = []
for annotation_group in annotation_lists:
annotation_group.sort(key=attrgetter('start_x'))
texts = (o.text for o in annotation_group)
texts = ' '.join(texts)
texts = re.sub(r'\s([-;:?.!](?:\s|$))', r'\1', texts)
grouped_list.append(texts)
return grouped_list
Based on Borislav Stoilov latest answer I wrote the code for c# for anybody that might need it in the future. Find the code bellow:
public static List<TextParagraph> ExtractParagraphs(IReadOnlyList<EntityAnnotation> textAnnotations)
{
var min_y = int.MaxValue;
var max_y = -1;
foreach (var item in textAnnotations)
{
var poly_range = Get_poly_y_range(item.BoundingPoly);
var t_min = poly_range.Min();
var t_max = poly_range.Max();
if (t_min < min_y) min_y = t_min;
if (t_max > max_y) max_y = t_max;
}
var max_size = max_y - min_y;
var text_boxes = new List<TextBox>();
foreach (var item in textAnnotations)
{
var poly_range = Get_poly_y_range(item.BoundingPoly);
var t_x = Get_poly_x(item.BoundingPoly);
var t_min = poly_range.Min();
var t_max = poly_range.Max();
var poly_size = t_max - t_min;
text_boxes.Add(new TextBox
{
Min_y = t_min,
Max_y = t_max,
X = t_x,
Size = poly_size,
Description = item.Description
});
}
var paragraphs = new List<TextParagraph>();
for (int i = min_y; i < max_y; i++)
{
var para_line = new List<TextLine>();
foreach (var text_box in text_boxes)
{
int t_min = text_box.Min_y;
int t_max = text_box.Max_y;
int x = text_box.X;
int size = text_box.Size;
//# size < max_size excludes the biggest rect
if (size < (max_size * 0.9) && t_min <= i && i <= t_max)
para_line.Add(
new TextLine
{
Text = text_box.Description,
X = x
}
);
}
// here I have to sort them by x so the don't get randomly enter code hereshuffled
para_line = para_line.OrderBy(x => x.X).ToList();
var line = string.Join(" ", para_line.Select(x => x.Text));
var paragraph = new TextParagraph
{
Order = i,
Text = line,
WordCount = para_line.Count,
TextBoxes = para_line
};
paragraphs.Add(paragraph);
}
return paragraphs;
//return string.Join("\n", paragraphs);
}
private static List<int> Get_poly_y_range(BoundingPoly poly)
{
var y_list = new List<int>();
foreach (var v in poly.Vertices)
{
if (!y_list.Contains(v.Y))
{
y_list.Add(v.Y);
}
}
return y_list;
}
private static int Get_poly_x(BoundingPoly poly)
{
return poly.Vertices[0].X;
}
Calling ExtractParagraphs() method will return a list of strings which contains doubles from the file. I also wrote some custom code to treat that problem. If you need any help processing the doubles let me know, and I could provide the rest of the code.
Example:
Text in picture: "I want to make this thing work 24/7!"
Code will return:
"I"
"I want"
"I want to "
"I want to make"
"I want to make this"
"I want to make this thing"
"I want to make this thing work"
"I want to make this thing work 24/7!"
"to make this thing work 24/7!"
"this thing work 24/7!"
"thing work 24/7!"
"work 24/7!"
"24/7!"
I also have an implementation of parsing PDFs to PNGs beacause Google Cloud Vision Api won't accept PDFs that are not stored in the Cloud Bucket. If needed I can provide it.
Happy coding!

Summary Grid extjs

I want to get the summary of the column stay time on my grid. But I can't seem to understand or figure out how to use the summary grid in extjs. Could anyone please help me or guide me?
Here's my grid code:
Ext.ns('dlti.view.widget');
Ext.define('dlti.view.widget.PlaylistDetailsGrid' ,{
extend: 'Ext.grid.Panel',
id: 'playlist-details',
alias: 'widget.PlaylistDetailsGrid',
forceFit: true,
stripeRows: true,
selType: 'rowmodel',
autosync: true,
height: 150,
width: 950,
store: new dlti.store.PlaylistDetailsStore(),
columns: [
{
text: 'Filename',
dataIndex: 'filename',
renderer: function renderDescTarget(val, p, record) {
var desc = '';
desc = '<p style="color:#000;font-size:12px;">' + val + '</p>';
return desc;
}
},
{
text: 'Transition',
dataIndex: 'transition',
renderer: function renderDescTarget(val, p, record) {
var desc = '';
desc = '<p style="color:#000;font-size:12px;">' + val + '</p>';
return desc;
}
},
{
text: 'Stay Time',
dataIndex: 'timeframe',
renderer: function renderDescTarget(val, p, record) {
var desc = '';
desc = '<p style="color:#000;font-size:12px;">' + val + '</p>';
return desc;
}
}
]
});
You can specify summaryType and summaryRenderer like this:
summaryType: 'count',
summaryRenderer: function(value, summaryData, dataIndex) {
return ((value === 0 || value > 1) ? '(' + value + ' Tasks)' : '(1 Task)');
}
summaryType can have values like count, max, average, sum etc. and summaryRenderer is similar to the column renderer where you can give any custom logic for formatting the summary.

agendaDay row time format in fullCalendar

I am trying to set the time format in my calendar rows to appear as 1pm - 2pm, 2pm - 3pm, 3pm- 4pm, etc.
I have tried the following:
agenda: 'h:mm{ - h:mm}',
axisFormat: 'h:mm{ - h:mm}',
day: 'h:mm{ - h:mm}',
axisFormat: 'h(:mm)tt',
timeFormat: {
agenda: 'h:mm{ - h:mm}'
},
but none of the above, either alone or in combination seem to work for me.
I initialize my calendar as below:
$('#calendar').fullCalendar({
defaultView: 'agendaDay',
allDaySlot: false,
firstHour: 9,
minTime: 9,
maxTime: 19,
selectable: true,
unselectAuto: true,
slotMinutes: 60,
weekends: false,
year: current_year,
month: current_month,
date: current_day,
columnFormat: '',
FullCalendar does not provide a "to" time for the axisFormat and therefore the { - h:mm} part of you axisFormat is ignored.
And I don't think there's any way to do it without editing the FullCalendar source code.
But if you are feeling adventurous you could do the following changes in fullcalendar.js around line 3207:
d = zeroDate();
maxd = addMinutes(cloneDate(d), maxMinute);
addMinutes(d, minMinute);
// Add two lines
var toD = cloneDate(d);
addMinutes(toD, opt('slotMinutes'));
slotCnt = 0;
for (i=0; d < maxd; i++) {
minutes = d.getMinutes();
s +=
"<tr class='fc-slot" + i + ' ' + (!minutes ? '' : 'fc-minor') + "'>" +
"<th class='fc-agenda-axis " + headerClass + "'>" +
// Changed line from "formatDate(d, opt('axisFormat')...)" to "formatDates(d, toD, opt('axisFormat')...)"
((!slotNormal || !minutes) ? formatDates(d, toD, opt('axisFormat')) : ' ') +
"</th>" +
"<td class='" + contentClass + "'>" +
"<div style='position:relative'> </div>" +
"</td>" +
"</tr>";
addMinutes(d, opt('slotMinutes'));
// Add line
addMinutes(toD, opt('slotMinutes'));
slotCnt++;
}

Resources