dwalton76 / rubiks-cube-tracker

Given an image of a rubiks cube, find the RGB value for each square.
MIT License
129 stars 37 forks source link

AttributeError: 'RubiksImage' object has no attribute 'get_black_border_width' #10

Closed CptDangerous closed 1 year ago

CptDangerous commented 1 year ago

I'm having a nightmare trying to get my EV3/BrickPi3 Rubik's cube solver robot to work after having to re-install the software from scratch (SD card threw in the towel and no back-up image!)

Running BricKuber.py the message I was getting yesterday was as follows:

Taking pictures of each face, and determining cube configuration.
START: Read Face Colors: top
Picture taken
Failed in processing image: 2022-10-04 20:11:36,396            __init__.py     INFO: Analyze /tmp/BricKuber_top_face.jpg
Traceback (most recent call last):
  File "/usr/local/bin/rubiks-cube-tracker.py", line 56, in <module>
    rimg.analyze_file(args.filename)
  File "/usr/local/lib/python3.7/dist-packages/rubikscubetracker/__init__.py", line 1685, in analyze_file
    return self.analyze(webcam=False, cube_size=cube_size)
  File "/usr/local/lib/python3.7/dist-packages/rubikscubetracker/__init__.py", line 1531, in analyze
    self.get_black_border_width(webcam)
File "/usr/local/lib/python3.7/dist-packages/rubikscubetracker/__init__.py", line 1134, in 
get_black_border_width
         ), f"black_border_width  {self.black_border_width},  median_square_width  {self.median_square_width}"
 AssertionError: black_border_width 94,  median_square_width 39

which to my untutored eye seemed to be saying that the black border was wider than the detected square.

I left it at that for the rest of the day and came back to the problem this evening. I printed out the whole of 'init.py' in an attempt to trace the flow through the code. Much to my surprise, when I ran BricKuber.py again I got a different error:

Taking pictures of each face, and determining cube configuration.
START: Read Face Colors: top
Picture taken
Failed in processing image: 2022-10-04 20:11:36,396            __init__.py     INFO: Analyze /tmp/BricKuber_top_face.jpg
Traceback (most recent call last):
  File "/usr/local/bin/rubiks-cube-tracker.py", line 56, in <module>
    rimg.analyze_file(args.filename)
  File "/usr/local/lib/python3.7/dist-packages/rubikscubetracker/__init__.py", line 1685, in analyze_file
    return self.analyze(webcam=False, cube_size=cube_size)
  File "/usr/local/lib/python3.7/dist-packages/rubikscubetracker/__init__.py", line 1531, in analyze
    self.get_black_border_width(webcam)
AttributeError: 'RubiksImage' object has no attribute 'get_black_border_width'

As far as I'm aware I haven't altered the code in any way nor reinstalled any packages but the Class RubiksImage has no attribute 'get_black_border_width'

Please can you throw any light on this puzzle?

dwalton76 commented 1 year ago

In this chunk of code

        self.release()
        self.CameraReadFaceColors("top")

        self.flip(True)
        self.CameraReadFaceColors("front")

        self.flip(True)
        self.CameraReadFaceColors("bottom")

        self.spin(90)
        self.flip(True)
        self.CameraReadFaceColors("right")

        self.spin(-90)
        self.flip(True)
        self.CameraReadFaceColors("back")

        self.flip(True)
        self.CameraReadFaceColors("left")

If they aren't rotated correctly but the color resolver got the colors correct anyway it was just lucky...it won't work consistently.

CptDangerous commented 1 year ago

This way lies madness!

I labeled two faces so I knew where L & R were, then using the opposite spin to the issued code I watched the faces being presented to the camera. With the s/w in that configuration the correct faces were photographed according to the self.CameraReadFaceColors("?"). However the cube layout as presented is incorrect. The R face is rotated thru 180 & the L face is rotated thru -90. It's as if the software is numbering the squares incorrectly?

The weird thing is that the cube layout shown at the end of the resolver pass is not the same as the .html

rubiks-color-resolver.html.gz

but the cube layout is:

Cube

           Bu Ye Wh
           Wh Wh Wh
           Wh OR Gr
 OR Gr OR  Gr Gr Ye  Rd Rd Rd  Bu Gr Ye
 Wh OR OR  Bu Gr Ye  Rd Rd Bu  Wh Bu OR
 Wh Ye Wh  Rd Ye Gr  Ye Rd Ye  Rd Rd Bu
           Gr OR OR
           Bu Ye Bu
           OR Gr Bu
BDUUUUULFRRRRRBDRDFFDBFDRDFFLLBDBLFBLFLULLUDUBFDUBLRRB

For completeness here is the input to rubiks-color-resolver.py

['rubiks-color-resolver.py', '--rgb', '{"1": [225, 216, 233], "2": [240, 225, 120], "3": [24, 77, 187], "4": [226, 209, 105], "5": [244, 233, 241], "6": [253, 250, 253], "7": [230, 220, 237], "8": [243, 224, 123], "9": [252, 152, 112], "19": [49, 128, 33], "20": [65, 160, 48], "21": [253, 254, 126], "22": [17, 64, 167], "23": [73, 170, 56], "24": [253, 185, 116], "25": [209, 35, 45], "26": [254, 254, 252], "27": [102, 193, 62], "46": [53, 137, 33], "47": [253, 168, 103], "48": [253, 254, 132], "49": [20, 68, 168], "50": [253, 251, 105], "51": [49, 117, 214], "52": [212, 36, 47], "53": [82, 169, 48], "54": [44, 108, 207], "28": [223, 1, 35], "29": [246, 1, 55], "30": [253, 1, 72], "31": [253, 119, 67], "32": [250, 3, 60], "33": [23, 105, 200], "34": [253, 246, 60], "35": [250, 2, 60], "36": [160, 219, 47], "37": [249, 237, 244], "38": [67, 167, 52], "39": [253, 189, 125], "40": [253, 243, 250], "41": [42, 97, 198], "42": [246, 75, 91], "43": [16, 61, 160], "44": [227, 52, 63], "45": [40, 106, 207], "10": [227, 201, 106], "11": [45, 133, 49], "12": [253, 159, 116], "13": [239, 227, 241], "14": [248, 141, 95], "15": [253, 167, 114], "16": [238, 226, 238], "17": [251, 230, 123], "18": [253, 162, 115]}']

All colours were correctly identified according to the .html.

I'll leave the cube in this configuration in case you want more info.

CptDangerous commented 1 year ago

I need my beauty sleep so I'm going to bed...

dwalton76 commented 1 year ago

All colours were correctly identified according to the .html na they are way off...scroll down to the bottom of the html file and you will see the final colors it picked image

But that is way off when you compare it to the RGB values extracted from the pics: image

I think the image of the Right side needs to be rotated another 180 degrees. Look at that 9, 21, 28 corner...that has an orange and a red square on the same corner but that is not possible since red/orange are on opposite sides of the cube from each other.

Add a self.spin(180) just before your self.CameraReadFaceColors("right") call and then I think at least the right side would be rotated correctly.

CptDangerous commented 1 year ago

Hold the front page! It solved a random cube!!!!!

Just a few remarks which may no longer be relevant:

I had no idea what the "Final Cube" was meant to represent. I was simply going by the "Initial RGB Values" which looked about right except for rotations.

After doing some 3-D gymnastics in my mind, I realised that, in order to present the faces as they would be in the flattened cube presentation, some extra spins were required. I added these to the code as follows:

        self.CameraReadFaceColors("top")
        self.flip(True)
        self.CameraReadFaceColors("front")
        self.flip(True)
        self.CameraReadFaceColors("bottom")
        self.spin(-90)
        self.flip(True)
        self.spin(180)
        self.CameraReadFaceColors("right")
        self.spin(-90)
        self.flip(True)
        self.spin(90)
        self.CameraReadFaceColors("back")
        self.spin(-90)
        self.flip(True)
        self.spin(90)
        self.CameraReadFaceColors("left")
        self.CCO = [5, 1, 0]

Note the extra spins after some flips.

BTW flip in this context means rotate the cube clockwise with respect to the current L-R axis wrt the R face. It's awkward to use L & R here as that should refer to the initial cube but I couldn't think of a better way to express it.

With this arrangement the code runs through to starting the kociemba solution. So I've lost the configuration I have been working with. :-(

However, I think the self. CCO = [5, 1, 0] is no longer correct because of the extra spins but I haven't yet worked out what that should be. Later: I think that should now be [1, 5, 2] as the cube finishes up with the original L face uppermost. Later still: No that CCO doesn't work so maybe I don't understand the meaning. Though I see in the code it represents [U, F, R] but from what perspective? Later yet: Got it! CCO = [5, 3, 1] works AND the robot solves the cube!!!!!!

Incidentally I think there is probably a more efficient order to take the photos, in terms of spins, now that there are extras required to satisfy the flattened layout.

In the original Dexter/BricKuber supplied brickuber_lib.py there was a bunch of code to reorientate the faces - I think. :-)

The initial & final now look the same or thereabouts:

rubiks-color-resolver.html.gz

dwalton76 commented 1 year ago

looks like everything is rotated correctly now, awesome. The Initial RGB values are the RGB values we extracted for each square from the pictures but at this point the color resolver doesn't know which of the six cube colors each square is. The Final Cube picture is where the color resolver has worked out what color each square is (hopefully correctly). So I often compare the two to quickly tell if it got everything right. image

Incidentally I think there is probably a more efficient order to take the photos, in terms of spins agreed...one that jumps out to me is to do top, front, bottom, back and then left/right or right/left. That would be something like

        self.CameraReadFaceColors("top")

        self.flip(True)
        self.CameraReadFaceColors("front")

        self.flip(True)
        self.CameraReadFaceColors("bottom")

        self.flip(True)
        self.CameraReadFaceColors("back")

        # this spin might need to be a -90 instead of +90
        self.spin(90)
        self.flip(True)
        # not sure if left or right would be exposed here but it would be one of the two
        self.CameraReadFaceColors("right or left?")

        self.flip(True)
        self.flip(True)
        # might need a self.spin(180) here
        self.CameraReadFaceColors("right or left?")

ok for the self.CCO value I put together some notes on how this works

    # 0 is side U
    # 1 is side F
    # 2 is side R
    # 3 is side D
    # 4 is side B
    # 5 is side L
    #
    # In self.CCO:
    # - the first entry in self.CCO is the side that is facing up
    # - the second entry in self.CCO is the side that is facing the front
    # - the third entry in self.CCO is the side that is facing the right

If I had a cube in my hand (I'm at work so no rubiks cubes here) I could probably walk through your move sequence and figure this out but trying to do that in my head is making my head spin. The easiest way might be to stick a solved cube in the robot, let it scan all of the faces and use that to determine what self.CCO should be at the end of the scan. Or heck just put your scrambled cube in so that at the start the white center is up, green center is front and red center is to the right image

and then at the end of the scan look at the center squares to tell what CCO should be.

CptDangerous commented 1 year ago

Just to prove I'm not BS'ing: IMG_0070 IMG_0071

That was a bit of a marathon but I do at least understand the logic of brickuber_lib.py better now.

Very many thanks for your help. I doubt I'd have cracked it by myself.

I'd be interested to know what your job is. I'm a retired software engineer. I started working with computers back in the age of steam i.e. vacuum tubes in 1960. I'll be 80 next July but, believe it or not, I'm still retained by an employer on a consultancy basis as I have knowledge of an almost defunct OS (OpenVMS) which some of my employer's clients still use. When I pop my clogs I don't know where they'll find that expertise elsewhere. :-)

Thanks again.

CptDangerous commented 1 year ago

Incidentally while I was reading through some of the code comments I found a remark concerning self.SPIN_DIRECTION. The comment said that the NXT1 & EV3 robots required opposite spins but the code had self.SPIN_DIRECTION = 1 for both robot styles. I changed the code for EV3 to -1 and reverted all the spin 90s to their original signs and everything started to make sense. This parameter is used by the solver code too.

I've incorporated your notes on CCO into the code so I don't have the same headache when I next visit the code.

Now I must take an image copy of the SSD so I have a working copy to fall back on in case of a disaster.

Cheers & beers,

Chris

dwalton76 commented 1 year ago

awesome, can you post your final Projects/BricKuber/brickuber_lib.py here? I will submit a pull request to https://github.com/DexterInd/BrickPi3 to get it updated with all of the fixes we made...will save someone else a lot of headache in the future :)

I am also a software engineer, I work in the networking industry. I worked for Cisco Systems for ages where I wrote mostly C but then started doing the startup thing about 10 years ago and picked up python...I've been writing python fulltime since then. The rubiks cube software is just for fun...I built this robot https://www.youtube.com/watch?v=3HjKxTlLDLw

and then had to write a ton of software to be able to figure out the state of the cube...that is where all of the rubiks-cube-tracker and rubiks-color-resolver software is used. Then I had to write a solver for 5x5x5 and larger cubes because there were not any open source solvers out there for anything larger than a 4x4x4. The solver was much much harder than I anticipated...had I known what I was getting into I probably would not have done it. I worked on the solver off/on for several years...basically trying to get it to find shorter and shorter solutions. Short of some massive breakthrough on path searching algorithms I think it is done at this point...I am out of ideas on things to find shorter solutions.

That is cool that you are still working. I am 46, hopefully I am still tinkering with python when I am 80 :)

dwalton76 commented 1 year ago

The tape you have on the white center square....I have one of those same cubes and I am pretty sure the logo is a sticker you can pull off.

CptDangerous commented 1 year ago

Ah, thanks for that. I'll have to see if I can peel that off.

I did most of my later work in C & C++ with the odd (really odd) excursion into Pascal. I tried to ignore Python but it is everywhere now. I can see the attraction of the language but it is still so fluid it seems to change almost by the day! One thing that baffles me is where do I find library definitions? It's all very well to say 'import x from y' but where are the specs for y let alone x!

I used to work in the aircraft industry many decades ago and really enjoyed writing real-time data acquisition code. That is where you learn to squeeze a quart into a pint pot and make every line of code work as efficiently as possible. Happy days!

Can you talk me through uploading the current state of brickuber_lib.py please?

dwalton76 commented 1 year ago

If you gzip brickuber_lib.py you should be able to drag-n-drop the brickuber_lib.py.gz to the issue here

CptDangerous commented 1 year ago

brickuber_lib.py.gz

dwalton76 commented 1 year ago

I opened two PRs against the BrickPi3 repo

Feel free to add some comments to that first one about the motor constant changes...they may ask for some more details on that.