Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,6 @@ target/
.venv
images/cards
.DS_Store

**/images/user_images/*/*.*
!**/images/user_images/*/*.keep
Empty file added __init__.py
Empty file.
11 changes: 6 additions & 5 deletions bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,13 @@


class listener_tweeter(StreamListener):
def _onconnect(self):
print 'Connected! Listening...'

# override on_status to pass data from on_data method of tweepy's StreamListener
def on_status(self, status):
print '{} said: "{}"'.format(status.user.screen_name, status.text)

# ignore retweets or tweets from self
if status.retweeted or ( status.user.screen_name == 'ProfessorSet' ):
if status.retweeted or ( status.user.screen_name.lower() == 'professorset' ):
return

img_str = None
Expand All @@ -26,8 +24,8 @@ def on_status(self, status):
try:
tweet_url = re.search(r'https:.*\b', status.text).group(0)
with_sets_outlined = (False if re.search(r'sets or not', status.text) else True)

text, img_str = solve_tweeted_set(tweet_url, with_sets_outlined=with_sets_outlined)
#print text

#ignore tweets with no image
except AttributeError:
Expand All @@ -36,7 +34,7 @@ def on_status(self, status):
text += '!'*n

response_text = '.@{} {}'.format(status.user.screen_name, text)
print response_text
#print response_text

try:
response = api.retweet(id=status.id)
Expand Down Expand Up @@ -100,12 +98,15 @@ def solve_tweeted_set(tweet_url, with_sets_outlined=True):
# scrape tweet HTML string for image url
soup = BeautifulSoup(tweet_content, 'lxml')
img_url = soup.find('meta', attrs={'property': 'og:image'})['content']
#print img_url

# find Sets
kwargs = {'path_is_url': True, 'pop_open': False}
kwargs['draw_contours'] = (True if with_sets_outlined else False)
kwargs['sets_or_no'] = (False if with_sets_outlined else True)
#print "about to play"
num_sets, initial_img_str = t.play_game(img_url, **kwargs)
#print num_sets

# send string with media_data (rather than media) tag because it is base64 encoded
img_str = 'media_data={}'.format(initial_img_str)
Expand Down
1 change: 1 addition & 0 deletions cv.py
Binary file modified images/.DS_Store
Binary file not shown.
Empty file added images/user_images/input/.keep
Empty file.
Empty file added images/user_images/output/.keep
Empty file.
2 changes: 0 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
altgraph==0.10.2
bdist-mpkg==0.5.0
bonjour-py==0.3
macholib==1.5.1
matplotlib==1.3.1
modulegraph==0.10.4
numpy==1.8.0rc1
py2app==0.7.3
Expand Down
81 changes: 59 additions & 22 deletions set_solver.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
import cv2, sys, os
import cv2.cv as cv
import sys
import numpy as np
import util as util
import os
import code
import set_constants as sc

reload(util)

def resize_image(img, new_width=600):
"""Given cv2 image object and maximum dimension, returns resized image such that height or width (whichever is larger) == max dimension"""
h, w, _ = img.shape

if h > w: img = np.rot90(img)

new_height = int((1.0*h/w)*new_width)
Expand All @@ -31,7 +28,7 @@ def get_card_properties(cards, training_set=None):
texture = get_card_texture(img)
p = (num, color, shape, texture)
if None not in p:
properties.append(p)
properties.append(p)
return properties

def pretty_print_properties(properties):
Expand Down Expand Up @@ -97,7 +94,7 @@ def transform_card(card, image):
approximated_poly = get_approx_poly(card, do_rectify=True)

if approximated_poly is None:
# could not find card poly
# could not find card poly
return None

dest = np.array(card_shape, np.float32)
Expand Down Expand Up @@ -414,21 +411,15 @@ def get_color_from_hue(hue):
# get histogram of hue values
hist,_ = np.histogram(hue, 15, (0, 255))

print hist

if hist[3]+hist[4] > 1200:
return sc.PROP_COLOR_GREEN
elif hist[8]+hist[9] > 250:
elif hist[8]+hist[9] > 30:
return sc.PROP_COLOR_PURPLE
else:
return sc.PROP_COLOR_RED


# if sum(hist[130:181]) > 750:
# return sc.PROP_COLOR_PURPLE
# elif sum(hist[40:81]) > 750:
# return sc.PROP_COLOR_GREEN
# else:
# return sc.PROP_COLOR_RED

def get_texture_from_hue(hue, contour_box):
# for convenience
card_w = sc.SIZE_CARD_W
Expand All @@ -439,12 +430,14 @@ def get_texture_from_hue(hue, contour_box):

# get a 20x20 square from each of the corners of the card, average values
hue_bg = np.mean([
hue[0:20, 0:20], hue[card_h-20:card_h, card_w-20:card_w],
hue[0:20, card_w-20:card_w], hue[card_h-20:card_h, 0:20]])
hue[6:26, 6:26], hue[card_h-26:card_h-6, card_w-26:card_w-6],
hue[6:26, card_w-26:card_w-6], hue[card_h-26:card_h-6, 6:26]])

# get a 20x20 square from the center of the shape, average values
hue_center = np.mean(hue[y+h/2-10:y+h/2+10, x+w/2-10:x+w/2+10])

util.show(hue[y+h/2-10:y+h/2+10, x+w/2-10:x+w/2+10])

# guess texture based on ratio of inside to outside hues
hue_ratio = max(hue_bg, hue_center) / min(hue_bg, hue_center)
if hue_ratio < 1.3:
Expand All @@ -454,6 +447,49 @@ def get_texture_from_hue(hue, contour_box):
else:
return sc.PROP_TEXTURE_SOLID

def get_texture_from_hue_val(hue, val, contour_box):
# for convenience
card_w = sc.SIZE_CARD_W
card_h = sc.SIZE_CARD_H

# uppack bounding box of contour
x, y, w, h = contour_box

val_bg_box = np.mean([
val[6:26, 6:26], val[card_h-26:card_h-6, card_w-26:card_w-6],
val[6:26, card_w-26:card_w-6], val[card_h-26:card_h-6, 6:26]],
axis=0).astype('uint8')

val_center_box = val[y+h/2-10:y+h/2+10, x+w/2-10:x+w/2+10]

val_diff = cv2.absdiff(val_bg_box, val_center_box)
val_mean = np.mean(val_diff)
val_std = np.std(val_diff)

# get a 20x20 hue square from each of the corners of the card, average values
hue_bg = np.mean([
hue[6:26, 6:26], hue[card_h-26:card_h-6, card_w-26:card_w-6],
hue[6:26, card_w-26:card_w-6], hue[card_h-26:card_h-6, 6:26]])

# get a 12x12 square from the center of the shape, average values
hue_center = np.mean(hue[y+h/2-10:y+h/2+10, x+w/2-10:x+w/2+10])

# guess texture based on ratio of inside to outside hues
hue_ratio = max(hue_bg, hue_center) / min(hue_bg, hue_center)

print "{}\t{}\t{}".format(val_std, hue_ratio, val_mean)

if val_mean > 35 or hue_ratio > 5:
return sc.PROP_TEXTURE_SOLID
elif val_mean > 10:
return sc.PROP_TEXTURE_STRIPED
elif val_mean < 3:
return sc.PROP_TEXTURE_EMPTY
elif hue_ratio > 2:
return sc.PROP_TEXTURE_STRIPED
else:
return sc.PROP_TEXTURE_EMPTY

def get_shape_from_contour(contour, contour_box):
# uppack bounding box of contour
x, y, w, h = contour_box
Expand Down Expand Up @@ -496,11 +532,12 @@ def get_card_properties_v2(card, debug=False):
x, y, w, h = contour_boxes[0]
hue_crop = hue[y:y+h, x:x+w]

#prop_num = get_number_from_contours(contour_areas, contour_centers)
prop_num = get_dropoff([b[2]*b[3] for b in contour_boxes], maxratio=1.2)
# no more than 3 shapes per card
prop_num_init = get_dropoff([b[2]*b[3] for b in contour_boxes], maxratio=1.2)
prop_num = ( prop_num_init if prop_num_init < 3 else 3 )
prop_col = get_color_from_hue(hue_crop)
prop_shp = get_shape_from_contour(contours[0], contour_boxes[0])
prop_tex = get_texture_from_hue(hue, contour_boxes[0])
prop_tex = get_texture_from_hue_val(hue, val, contour_boxes[0])

if debug:
pretty_print_properties([(prop_num, prop_col, prop_shp, prop_tex)])
Expand All @@ -516,6 +553,6 @@ def get_cards_properties(cards):
for card in cards:
p = get_card_properties_v2(card)
if None not in p:
properties.append(p)
properties.append(p)

return properties
return properties
54 changes: 54 additions & 0 deletions solver_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import tests

import os
from flask import Flask, request, redirect, url_for, send_from_directory
from werkzeug import secure_filename

ALLOWED_EXTENSIONS = set(['png', 'jpg', 'jpeg', 'gif'])
THIS_DIR = os.path.dirname(os.path.realpath(__file__))

app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = os.path.join(THIS_DIR, 'images', 'user_images', 'input')
app.config['OUTPUT_FOLDER'] = os.path.join(THIS_DIR, 'images', 'user_images', 'output')

def allowed_file(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1] in ALLOWED_EXTENSIONS

@app.route('/', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
file = request.files['file']
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
final_image_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
file.save(final_image_path)

solve_uploaded_set(final_image_path)

return redirect(url_for('show_results', filename=filename))
return '''
<!doctype html>
<title>Upload new File</title>
<h1>Upload new File</h1>
<form action="" method=post enctype=multipart/form-data>
<p><input type=file name=file>
<input type=submit value=Upload>
</form>
'''

@app.route('/<filename>')
def show_results(filename):
return send_from_directory(app.config['OUTPUT_FOLDER'], 'OUTPUT_{0}'.format(filename))

def solve_uploaded_set(image_url):
kwargs = {
'pop_open': False,
'save_image': True,
'output_dir': app.config['OUTPUT_FOLDER'],
}

return tests.play_game(image_url, **kwargs)

if __name__ == "__main__":
app.run(host='192.168.1.101')
16 changes: 10 additions & 6 deletions tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@
from set_test import game
from collections import Counter
import numpy as np
import base64, Image, StringIO
import base64, StringIO
from PIL import Image

THIS_DIR = os.path.dirname(os.path.realpath(__file__))
DEFAULT_IMAGE_OUT_DIR = os.path.join(THIS_DIR, 'images', 'user_images', 'output')

def test():
# 3 cards on flat table
Expand Down Expand Up @@ -96,7 +99,8 @@ def main():
def play_game(path_in, path_is_url=False, printall=False, \
draw_contours=True, resize_contours=True, \
draw_rects=False, sets_or_no=False, \
pop_open=True):
pop_open=True, save_image=False, \
output_dir=DEFAULT_IMAGE_OUT_DIR):
"""Takes in an image path (to local file or onlin), finds all sets, and pretty prints them to screen.
if printall - prints the identities of all cards in the image
if draw_contours - outlines the cards belonging to each set
Expand Down Expand Up @@ -197,8 +201,10 @@ def play_game(path_in, path_is_url=False, printall=False, \
mystr = output.getvalue()
output.close()

# don't write image to file, dude....because we are badass Tweepy hackers
#cv2.imwrite('tmp.jpeg', final_img)
if save_image:
out_file_name = 'OUTPUT_{0}'.format(os.path.basename(path_in))
final_out_path = os.path.join(output_dir, out_file_name)
cv2.imwrite(final_out_path, final_img)

# encode image string to base64 and safe-encode it for Twitter upload request
final = requests.utils.quote(base64.b64encode(mystr), safe='')
Expand All @@ -211,5 +217,3 @@ def play_game(path_in, path_is_url=False, printall=False, \
# size is 89867.1532847 bytes

return (num_sets, final)