Myro2Notes

From IPRE Wiki
Jump to: navigation, search

Myro 2 Notes

The latest version of Myro can be found here. This page contains notes regarding this newest version of Myro.

There are three main categories of new commands in Myro: Image processing, and Web development, other new dongle functions.

Getting Started

With the new camera board (now called the IPRE Fluke), you should:

  1. Double-click on "Start Python.pyw". This is the only tested method of running Myro.
  2. Enter "from myro import *"
  3. Turn on the robot, and connect via Bluetooth.
  4. Before connecting onto the robot, enter "upgrade()". This will force the latest bytecode program to the Scribbler, even if the Scribbler is right out of the box.
  5. Now, you can enter "initialize()" and look for the comport window which may appear the IDLE window.

Some notes for Myro programmers:

  1. You don't need to include anything but "from myro import *" and is considered bad style to "from LIBRARY import *" anything else. Namespace conflicts and issues that will be hard to predict and debug.
  2. There are time and random functions in Myro. Use wait(seconds), flipCoin(), and randomNumber()
  3. Code for sharing should leave out the com port in initialize. initialize() will ask you if you haven't given a port.
  4. You might leave the initialize out of programs completely. You can initialize() again and again without issue though, but it takes a couple of extra seconds.
  5. rotate(p) and forward(p) will blend together. If you don't want to blend them, use rotate(p, t) and forward(p, t) or use stop() between them.
from myro import *
initialize()
while True:
    if getObstacle("right"):
        backward(1, .1)
        turnLeft(0.7, .1)
    elif getObstacle("left"):
        backward(1, .1)
        turnRight(0.7, .1)
    else:
    forward(1)
        wait(.1) 

From the Python Shell:

>>> from myro import *
# open your file in a window, and select "Run" -> "Run module"
>>> brain()

Image processing

The following objects and functions are related to the new camera functions on the Scribbler. There are two different interfaces: the Multimedia interface (largely based on Mark Guzdial's intro book), and the Graphics Object interface (largely based on John Zelle's intro book). These are independent; however, there are methods to move between the two. The first is built on the second.

The single class that is new is the Picture class.

Multimedia Interface

Creating a picture, manually, from a file, or from the robot:

picture = takePicture("color" | "gray" | "blob") # gets image from robot
picture = makePicture(filename)                  # reads in a image file, examples: PNG, JPG, GIF
picture = makePicture(columns, rows)             # creates a blank picture
picture = makePicture(columns, rows, array)      # creates a new picture from an array (a column-major sequence of 0-255 values that represent Red, Green, Blue (in that order))
picture = makePicture(columns, rows, array, mode)# creates a new picture from an array, where mode = "color", or "gray"
pictureCopy = copyPicture(picture)               # creates a copy of picture

Output:

show(picture)
repaint(picture=None)                            # pixel changes don't appear until you repaint()
savePicture(picture, filename)

The show(picture) function has a couple of mouse functions. You can click with the left mouse on a pixel and you can see "(x,y): (r,g,b)" in the window's status bar. If you click and drag a rectangle in the window, you will set the blob-tracking colors to the ranges of colors inside the bounding box.

show() can also take an optional name:

show(pic2, "name2")

so that you can show more than one image at a time.

Image dimensions:

int_value = getWidth(picture)
int_value = getHeight(picture)

Predefined colors:

black     = makeColor(  0,   0,   0)
white     = makeColor(255, 255, 255)
blue      = makeColor(  0,   0, 255)
red       = makeColor(255,   0,   0)
green     = makeColor(  0, 255,   0)
gray      = makeColor(128, 128, 128)
darkGray  = makeColor( 64,  64,  64)
lightGray = makeColor(192, 192, 192)
yellow    = makeColor(255, 255,   0)
pink      = makeColor(255, 175, 175)
magenta   = makeColor(255,   0, 255)
cyan      = makeColor(  0, 255, 255)

Creating and manipulating color objects:

color = makeColor(r, g, b)
color = pickAColor()
color = getColor(pixel)
setColor([pixel | color], color)
makeDarker(color)  # takes a color and makes it slightly "darker" 
makeLighter(color) # takes a color and makes it slightly "lighter" 

You can also determine the distance between two colors using:

distance(color1, color2)

Pixel manipulation:

pixel = getPixel(picture, x, y)
pixels = getPixels(picture)
int_value = getRed(pixel)
int_value = getGreen(pixel)
int_value = getBlue(pixel)
int_value = setRed(pixel, color)
int_value = setGreen(pixel, color)
int_value = setBlue(pixel, color)
int_value = getX(pixel)
int_value = getY(pixel)
r, g, b = getRGB([color | pixel])

Red, green, and blue int_values are between 0 and 255, inclusive.

The getPixels() function is designed to be used with a for-statement, like so:

for pixel in getPixels(picture):
    # do something with each pixel

You can also make a Pixmap object that allows integration into other graphics objects:

pixmap = makePixmap(picture)                # turns a picture into a pixmap

This can be used by the object-oriented graphics system.

win = GraphWin(title)
p = Point(x, y)
image = Image(Point(x, y), pixmap)
image.draw(win)

See John Zelle's book for more details.

All types of images have an RGB value, even if an image type actually has a palette (such as gif files), or grayscale images. Images that have a palette will use the palette entry for getColor(), and will look-up the closest color in the palette when using setColor(). For example, if you try to set a pixel to Color(255, 255, 255) but there is no such color in the palette, then the color will be set to a nearby color. Gray scale images will have identical RGB values for each pixel. For example, getColor(getPixel(p, x, y)) might give Color(125, 125, 125) for one position and Color(65, 65, 65) for another. When setting a color in a grayscale image, the single value is actually taken as the average of the 3 components. For example, if you tried:

graypic = takePicture("gray")
setColor(getPixel(graypic, 0, 0), Color(50, 100, 150))    # Doesn't do what you expect!

The color of that pixel would end up at 100, (50 + 100 + 150)/3 = 100.

If you wanted to actually make part of a grayscale image a color, then you would need to turn the grayscale into a color image, via something like:

# take a grayscale picture:
p = takePicture("gray")
# make a new color picture the same size:
colorPic = makePicture(getWidth(p), getHeight(p))
# go through each pixel and copy it to new color picture:
for p in getPixels(picture):
    setColor(getPixel(colorPic, getX(p), getY(p)), getColor(p))

You can also save any picture to a file:

writePictureTo(picture, filename) # for compatibility with Mark Guzdial's book
savePicture(picture, filename)    # does the same as above, but has intuitive name

copyPicture() is effectively defined as:

def copyPicture(picture):
    newPicture = makePicture(getWidth(picture), getHeight(picture))
    for x in range(getWidth(picture)):
        for y in range(getHeight(picture)):
            setColor(getPixel(newPicture, x, y), getColor(getPixel(picture, x, y)))
    return newPicture

However, it is implemented in a faster manner.

Sound:

sound = makeSound(filename) # opens a file and returns a sound object
play(sound) # plays the sound file through the computer's speakers

Examples

Processing by rows and cols:

from myro import *
picture = makePicture(pickAFile())
show(picture)
for i in range(getWidth(picture)):
    for j in range(getHeight(picture)):
        pixel = getPixel(picture, i, j)
        setGreen(pixel, 255)
    repaint() # not every time, just once per col

Processing by each pixel:

from myro import *
picture = makePicture(pickAFile())
show(picture)
for pixel in getPixels(picture):
    setGreen(pixel, 255)
repaint()

Graphics Objects Interface

picture = Picture(columns, rows)
pixmap  = makePixmap(picture)
image = Image(Point(x, y), pixmap)

Example

win = GraphWin()
image = Image(Point(0, 0), makePixmap(picture))
image.draw(win)
win["width"] = 451
win["height"] = getHeight(picture) + 10
image.move(10,10)
image.refresh(win)

Web development

In order to use the web development you first need to run the register() function. You will need to enter the fields as below:

Your email address    : (your email address)
Your robot's name     : (any name up to 16 characters, no spaces or other punctuation)
Course keyword        : owls OR yjackets OR yellowj
Create a Myro password: (any password, don't use a good one, but it is encrypted)

The keyword is a code that determines school and section number.

For example, here are some valid entries:

Your email address    : dblank@brynmawr.edu
Your robot's name     : Theodore
Course keyword        : owls
Create a Myro password: *********
Your email address    : someone@mailinator.com
Your robot's name     : Robokiller
Course keyword        : yjackets
Create a Myro password: *****
Your email address    : someoneelse@mailinator.com
Your robot's name     : TheDude
Course keyword        : yellowj
Create a Myro password: *****

If you have a robot connected, it will set the robot's name. Otherwise, when you connect your robot, you should name your robot the same name:

setName("Theodore")
setName("Robokiller")

You can now surf to your page and edit your HTML section if you wish:

http://myro.roboteducation.org/myweb/

To login in, use all lowercase for the robot name, even if you registered it with uppercase letters. The password is case sensitive.

To send a picture to your website, use the sendPicture() function:

sendPicture(picture, photoname, password, robotname = None)

If you leave robotname off, sendPicture() will ask the robot what its name is.

There is currently a limit on the size of a picture that can be sent. Only pictures about the size from the camera will work. You'll get a message if it is too large.

Here is a full example:

from myro import *
register() # opens up window
initialize()
picture = takePicture()
sendPicture(picture, "dormroom3", "bash72hg")              # robot's name is "theodore" so that will get sent

Unfortunately, you currently have to enter your password directly here.

If you need to change your password, use the command:

setPassword(robotName, emailAddress, newPassword)

This is new in Myro version 2.2.5.

New Fluke Functions

getBright("left" | "middle" | "center" | "right" | 0 | 1 | 2)
getObstacle("left" | "right" | 0 | 1)
setIRPower(value)
set("irpower", value)
getBattery()
takePicture("color" | "blob" | "gray")
setWhiteBalance("on" | "off" | 0 | 1)
set("whitebalance", value)
setLEDFront(value)
setLEDBack(value)
set("led", "back", value)
set("led", "front", value)

Low-level Fluke functions

You can find the low-level details for dongle control on The IPRE Fluke.

Gamepad

The Gamepad interface requires Myro version 2.3.4.

  1. Get a USB-based Gamepad. A cheap one with buttons and a couple of "axises" will do. You can find these for $4 - $10. You don't need those with force feedback, as there is not (currently) a cross platform API. But you can use Gamepads with joysticks and other extras. Mine has 8 buttons and 2 axises, $9.95.
  2. You'll need pygame installed. http://www.pygame.org/download.shtml
  3. Plug into the USB ports all of the Gamepads that you will want to use for this session. You can use as many USB ports as you have.
  4. Start Python, and import myro
  5. There are two commands: getGamepad() and getGamepadNow()
    1. getGamePad() waits for an event before returning (ie, is blocking),
    2. getGamepadNow() will immediately return the current state of the gamepad.
    3. Both take the same arguments.

Gamepad Examples

>>> getGamepad("count")
2
>>> getGamepad("button") # waits till you press at least one button
[0, 0, 1, 0, 1, 0, 0, 0]
>>> getGamepad(1, "button") # waits till ID 1 presses at least one button
[1, 0, 0, 0, 0, 0, 0, 0]
>>> getGamepad(range(getGamepad("count")), "button")
# waits till someone presses a button
[[0, [1, 0, 0, 0, 0, 0, 0, 0]],
 [1, [0, 0, 0, 0, 0, 0, 0, 0]]]
>>> getGamepad("button", "axis", wait=.01)
{"button": [1, 0, 0, 0, 0, 0, 0, 0],
 "axis": [-0.999969482421875, 0.0]}
(sometimes axis doesn't return exactly 1 or -1).

Here is a short, useful program:

>>> while 1: move(*getGamepad("robot"))

Here is a more functional one:

done = False
while not done:
    results = getGamepad("button", "robot")
    move(*results["robot"])
    if results["button"][1]: beep(.5, 440)
    if results["button"][2]: beep(.5, 880)
    done = (results["button"][0] == 1)

Gamepad Details

  1. getGamepad("count") - returns (immediately) the number of gamepads connected
  2. getGamepad(ID, ITEM, ...) - return the ITEMs for gamepad ID. ID can be left out and will default to 0, the first one. If you request more than one ITEM, then they come back in a dictionary. Just request one ITEM and you'll get the value (as a list, string, or number).
  3. getGamepad([ID1, ID2...], ITEM, ...) - return the ITEMs for gamepad IDs as a list of lists of ID, RESULTS. For example:
    >>> getGamepad([0, 1], "button", "axis")
    [[0, {'button': [0, 0, 0, 0, 0, 0, 0, 0], 'axis': [0.0, 1.0]}], [1, {'button': [1, 1, 0, 0, 0, 0, 0, 0], 'axis': [-1.0, -1.0]}]]
    >>> getGamepad([0, 1], "axis")
    [[0, [0.0, 1.0]], [1, [-1.0, -1.0]]]
  4. getGamepad() has one keyword argument, "wait" for setting a sleep value between gamepad polls. Default is 0.05 seconds. Setting to zero will have small latency, but may eat up your CPU.

ITEM can be:

  1. "count" - returns (immediately) number of gamepads plugged in
  2. "robot" - returns axis values (floats) ready to be used by move() Myro command as [translate, rotate]
  3. empty - if nothing is passed in, it returns all of the following as a dictionary
  4. "init" - has this gamepad been initialized? as boolean
  5. "name" - name of gamepad, as string
  6. "axis" - returns values of axis controller in a list (as floats)
  7. "ball" - returns values of ball controller in a list
  8. "button" - returns values of buttons in a list (as integers)
  9. "hat" - returns values of hat in a list

Gamepad Project Ideas

You can do some very quick interfaces:

  1. create a camera interface. axises move the robot, buttons take picture, brighten, darken, save, or refresh the window.
  2. create a musical instrument. axis can change the octave, buttons can work together to play two-tone frequencies. Or play sound files.
  3. create a "tape recorder" that will remember the moves and timings so that you can "play back" movements with a button
  4. create a game with Zelle's object interface. Break out, tag, and pong come to mind.

Here is a short little demo of how you could write a multi-player "game" where each player has a gamepad controller and appears as a circle on the screen.

But to make it a bit more integrated with the robot (and more personal and fun), the second version takes a picture of you (literally), and "you" become the little circle on the screen!

This will require that the student use more sophisticated concepts: objects, lists, and dictionaries.

from myro import *
def game1():
    win = GraphWin("My Game!", 500, 500)
    numplayers = getGamepad("count")
    colors = ['red', 'blue', 'green', 'yellow', 'orange']
    players = []
    # Create the players:
    for p in range(numplayers):
        circle = Circle(Point(randomNumber() * 500,
                              randomNumber() * 500), 10)
        circle.setFill(colors[p])
        players.append(circle)
        players[-1].draw(win)
    # Let's play!
    speak("Red is it! Don't let red touch you!")
    while True:
        for (id, data) in getGamepadNow(range(numplayers),"axis","button"):
            players[id].move(data["axis"][0] * 20,
                             data["axis"][1] * 20)
            if data["button"][0]: # fire a missile
                computer.beep(.1, 440 * 2 ** id)
        wait(.1)

def game2():
    win = GraphWin("My Game!", 500, 500)
    numplayers = getGamepad("count")
    players = []
    # Create the players:
    for p in range(numplayers):
        speak("Get ready to take picture of player %d." % p)
        wait(1)
        speak("1")
        wait(1)
        speak("2")
        wait(1)
        speak("3")
        pic = takePicture()
        image = Image(Point(randomNumber() * 500,
                            randomNumber() * 500), makePixmap(pic))
        players.append()
        players[-1].draw(win)
    # Let's play!
    speak("Player 1 is it. Don't let them touch you!")
    while True:
        for (id, data) in getGamepadNow(range(numplayers),"axis","button"):
            players[id].move(data["axis"][0] * 20,
                             data["axis"][1] * 20)
            if data["button"][0]: # fire a missile
                computer.beep(.1, 440 * 2 ** id)
        wait(.1)

Other differences and notes

The simulator is now working. Of course, it doesn't have a camera though, so no takePicture(). Use initialize("simulator") to start.

The joyStick() function now creates a window, but still allows you to type and run other programs in the Python Shell. I've added locking mechanisms in 2.2.5 to allow students to do whatever they wish without adverse effects.

The window produced with the show(picture) command allows mouse clicks. The clicks will display the (x,y) and (r,g,b) in a status bar. Drag a rectangle to set YUV ranges for blob-tracking.

The scribbler_server will now remember two settings between reboots: volume and echo. setVolume(0) to turn off all beeps. Echo will automatically be turned off by Myro. MSRS users will want to turn echo mode back on. The default is still on.

The starting pieces of sound processing works:

sound = makeSound(filename)
play(sound)

getPixels() returns a generator object. Therefore, you cannot get a pixel by index.

The Myweb webpages give some notes. The images that a user has will be available when he/she edits their page. Images are placed in /myweb/data/robotname/*.jpg. Therefore one can:

sendPicture(takePicture(), "my-house", "PassWoRDz")          # assumes robot is connected, to take picture and get name
sendPicture(takePicture(), "my-house", "PassWoRDz", "kitty") # you can provide the name explicitly, also

and refer to it picture with:

<img src="/myweb/data/kitty/my-house.jpg">

Both the Myro software on the computer and the dongle code (and therefore the Scribbler server) can now be upgraded after initial installation. Use the upgrade() function, which defaults to "all" but can also be "myro" or "dongle". To force a upgrade on the Scribbler before it even has a version of any IPRE software on it, call upgrade() immediately after "from myro import *".

New Array() class, and makeArray() function:

a = Array() # zero dimensions
a = Array(10) # one dimension, 10 elements long
>>> a[9]
0
>>> a = Array(2, 3, 4) # 3 dimensions
>>> a[0][0][0]
0
>>> a[0][0][0] = 8
>>> a[0][0][0]
8
>>> a
<myro.Array object at 0x0160AC10>

makeArray can also take a Picture:

>>> p = makePicture(10, 10)
>>> array = makeArray(p)
>>> array[0]
<myro.Column object at 0x0161BB90>
>>> array[0][0]
<Pixel instance (r=0, g=0, b=0) at (0, 0)>
>>> array[0][1]
<Pixel instance (r=0, g=0, b=0) at (0, 1)>
>>> array[1][1]
<Pixel instance (r=0, g=0, b=0) at (1, 1)>
>>> setColor(array[1][1], Color(100, 50, 25))
>>> array[1][1]
<Pixel instance (r=100, g=50, b=25) at (1, 1)>

TODO

  1. Text: write example behaviors using all aspects of the dongle
  2. Web server: ability to change robot names (using register(oldname))
  3. Web server: need to move images if change name
  4. Enhancement: ability to scale picture (actually, just zoom)
  5. Source: docstrings
  6. Enhancement: animated gifs?
  7. Enhancement: add images to CVS
  8. Enhancement: do regression tests with images
  9. Dongle: 4 User defined windows
  10. Dongle: Bootloader to upgrade firmware over bluetooth
  11. Dongle: Implement GetInfo()

Bugs

  1. getBright() turns front LED on/off - a bug in the dongle code (fixed in next release)
  2. Some images are returned with U/V swapped resulting in a strangely colored image.
  3. should flush buffers when control+c in the middle of a transfer
  4. confirm on sendPicture receipt? it sometimes takes some time... no confirm?
  5. closing the joystick(1): if you close the window while it is in the middle of getting values/updating windows, it looks like it will crash the tkinter eval loop.