audrey xie
not a jack of all trades, nor a master of one. i prefer just being a lazy bum.

Sudowoodo reminding you to be careful when using sudo

Remember the lecture that you recieved when you first used sudo?

We trust you have recieved the usual lecture from the local System Administrator.
It usually boils down to these three things:

    #1) Respect the privacy of others.
    #2) Think before you type.
    #3) With great power comes great responsibility.

It’s a pretty nice warning and I think it’s quite unfortunate that it only appears once. Luckily, there’s a way to make it permanent. Simply open the sudoers file and in the Defaults section set the lecture to always.

# open the sudoers file using
sudo visudo

# under defaults add
Defaults    lecture=always

And congrats, the message now appears every time.

But wait, there’s more! You can take it one step further by setting a custom lecture. I thought it would be clever to display an ascii image of sudowoodo, the rock type pokemon that looks like a tree, because it’s SUDOwoodo (haha, get it?).

So make a new file with whatever message you want displayed and set the lecture_file to wherever the file is (my file was located at /etc/sudoers.lecture).

You can find the ascii sudowoodo here.

# open the sudoers file
sudo visudo

# under defaults add (replace the path with your own)
Defaults    lecture_file=/etc/sudoers.lecture

And congrats again, you now have a custom sudo message.

The project comes to an end — EV Moped Conversion pt. 4

On a cycle the frame is gone. You’re completely in contact with it all. You’re in the scene, not just watching it anymore, and the sense of presence is overwhelming. (Robert Pirsig, Zen and the Art of Motorcycle Maintenance)

It’s been quite a while since the last moped update. Partly because of my own tendency to procrastinate, but also because of school obligations and college apps. Anyways, it turns out that the lithium ion trash-can battery is actually able to function after messing with the lock! With the battery, hub motor, and motor controller all sourced and installed, I set about tying up loose ends.

The first thing that I fixed was the kickstand. On the original moped, the kickstand was directly attached to the engine and the engine was bolted onto the frame. Since I don’t have many machines/tools available to use, I couldn’t machine a part that would be bolted into the frame to hold the kickstand. Thus, I decided to gut the engine to reduce its weight and keep it as the kickstand holder. This was quite easy, prying apart two halves of the engine made the internal components readily removeable.

After wiring up the controller, installing the e-brakes, and swapping the old handle grips for the new one with a throttle, I was ready to test ride the moped. To my surprise, everything worked (this rarely happens).

I rode around for a good half an hour before becoming a little too overzealous with the throttle and accelerating way too fast, causing the axle itself to turn and tangle the wire running from the motor to the controller. Soon after that, the moped stopped working.

My guess is that the insulation on one of the wires leading to the hall-effect sensors and a motor phase wire may have become damaged, exposing the metal underneath. And when the wires tangled, they may have touched and shorted. The excess current probably damaged the hall-effect sensor, which would explain why the wheel was unable to turn despite throttling.

I wasn’t about to buy a new hub motor. And after probing with a multimeter I was able to conclude that one of my hall-effect sensors was dead, so I ordered a replacement. Because this specific hub motor was unbranded, I couldn’t find the drawing sheets. However, all sources on the Internet indicated that the sensors would be found inside the hub motor, on the edge of the stator and embedded in the laminated steel sheets. Lacking a better plan, I set about taking apart the hub motor to find and replace the sensors. The gear puller was particularly handy for this endeavour.

Luckily for me, the sensors were exactly where online sources said they would be. Unfortunately, they were also epoxied into each of the cavities. With a mallet in one hand and a flathead screwdriver in the other, I spent around an hour chipping out the sensors. Yes, hall-effect sensors with a plural because in my rush to fix the hub motor, I forgot to mark which sensor I needed to replace. So I had to replace all three. I superglued the them in because I don’t have epoxy, soldered them back onto the board, and screwed the lid on. After that, I rewired the controller and tested the moped. Thanks to my legendary soldering and problem solving skills, the hub motor now works! I’ve learned my lesson, and will try not to accelerate as much.

Here’s the moped in action!

Zip-tie Monstrosity — An Interactive Projected Surface


The general idea is that there’s a projector vertically mounted overhead that projects the game onto the ground, then there’s a webcam also placed over the projected area that is used to track the player’s location on the field, which will translate into the location of the cursor on the laptop. Lastly, the player will be holding a bluetooth mouse, used only for left-clicking. This means that the player has to actually walk and step on game objects that are projected on the ground.


The principle behind translating player position into cursor location is simple. Just look for a red blob, find the center of the red blob, set that value as the location of the screen. I guess here’s the code for the color detection algorithm? (btw I’m using OpenCV)

while True:
    _, frame =
    # The webcam I'm using inverts everything, 
    # I also oriented it the wrong way when attaching it to the ceiling.
    # So I'm compensating for that in code because I'm lazy.
    frame = cv2.flip(frame, -1)
    # Cropping because the projection does not fill up the webcam FOV
    frame = frame[int(r[1]):int(r[1]+r[3]), int(r[0]):int(r[0]+r[2])]
    #HSV works better than thresholding R
    hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
    # lower red range
    lower_red = np.array([0, 120, 70])
    upper_red = np.array([10, 255, 255])
    mask1 = cv2.inRange(hsv, lower_red, upper_red)
    # upper red range
    lower_red = np.array([170, 120, 70])
    upper_red = np.array([180, 255, 255])
    mask = mask1 + cv2.inRange(hsv, lower_red, upper_red)
    kernel = np.ones((5, 5), np.uint8)
    # Get only red blobs
    mask = cv2.erode(mask, kernel)
    _, contours, _ = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
    if contours:
        # Only use the largest red blob
        cnt = max(contours, key = lambda x: cv2.contourArea(x))
        cv2.drawContours(frame, [cnt], 0, (0, 0, 0), 5)            
        # Calculate centroid of red blob
        M = cv2.moments(cnt)  
        # Scale the two values based on screen resolution
        center_x = int((M['m10']/M['m00']) * x_mult)
        center_y = int((M['m01']/M['m00']) * y_mult)
        # Set cursor location using centroid of red blob
        pyautogui.moveTo(center_x, center_y)
    cv2.imshow("Frame", frame)
    cv2.imshow("Mask", mask)
    # Use esc to stop program
    key = cv2.waitKey(1)
    if key == 27:

Since the projection didn’t fill the webcam FOV, I added some code to set ROI.

cap = cv2.VideoCapture(1)
_, frame =
from_center = False
r = cv2.selectROI(frame, from_center)
screen_width = r[2]
screen_height = r[3]
x_mult = 3200 / screen_width
y_mult = 1800 / screen_height

ROI selector

The code can be found here.


So with the code working, it was now time to set up this whole system. The webcam was easily attached to the ceiling using hook and loop tape. The projector was another story. My parents were very against me drilling holes in the ceiling, thus, I had to get creative. Experimentation lead me to create a zip-tie monstrosity that hangs from a clamp fastened to a beam on the ceiling (the clamp was later moved to the skylight).

Hook and loop tape

Zip-tie pt. 1

Zip-tie pt. 2

Then I just connected everything and hoped that it would work. To my surprise, it did. After some cable management, I got to playing. Sadly, the projected image is really small, which means that the cursor position isn’t very accurate. Also, the latency for the cursor position is pretty bad, which is a death sentence in the world of rhythm games. So my dream of playing life sized osu! has to wait until another day.

Projector setup closeup

10/10 cable management

Laptop setup

Projected surface

Final Product

But it can do other functions, such as drawing. Here’s a video.

When it doubt, Dremel it out — EV Moped Conversion pt. 3

The real purpose of the scientific method is to make sure nature hasn’t misled you into thinking you know something you actually don’t know. (Robert Pirsig, Zen and the Art of Motorcycle Maintenance)

Lithium ion batteries are expensive. Fortunately, my brother found one in a trash can at school and gave it to me when he came back to the Bay Area (thanks gregory!). But there were a few problems. First, the battery was not charged and I didn’t have the charger. Second, there was a lock on the battery for turning it on and off, and I didn’t have the key. Third, and most importantly, I didn’t know if the battery actually worked. Maybe it was in the trash can for a reason, right? Either way, I would have to solve the first two problems before being able to solve the third.

The charger had to be ordered online and would take some time to arrive, so I started off with the issue regarding the key. I wanted to either remove the lock and replace the on/off mechanism with a large button or remove the pins from the lock so that it wouldn’t require a key to turn. In order to decide which path I would follow, I first had to take a closer look at the lock. Thus, ignoring the large sticker on the front saying, “WARNING: DO NOT DISASSEMBLE,” I disassembled the battery.

I thought that process would be fairly straightforward, just unscrew the bolts until the battery falls apart. However, today wasn’t my lucky day, and I soon realized that there was a metal rail running along one side of the battery like a spine that held everything together. Worst of all, there was no easy way to take it out. I was getting impatient, so I brought out the trusty Dremel Micro outfitted with a cutting blade and surgically removed the metal piece. Lastly, I very carefully cut the wires in the battery to fully separate the part containing the lock. Since I didn’t want to make the mistake of soldering the wrong wires together when I reassembled the battery as they all looked very similar, I labeled the corresponding wire halves by making different shapes with electrical tape and taping those shapes to the wires. Black rectangle goes to black rectangle, red triangle corresponds to red triangle, so on and so forth. Looking back, I would like to applaud myself for this rare moment of forethought.

Now came the fun part, messing with the lock. A couple years ago, I had a very brief obsession with watching lock picking tutorials on Youtube. I didn’t want to do anything illegal, so I never really got to apply my knowledge. However, this battery gave me the perfect opportunity to see if I actually gained any skills from watching those videos. I decided to go full Hollywood, and use bobby pins as my lock picking tools. After fiddling around for two hours, I finally gave up. The lock was not picked, and I suffered a big blow to my ego and confidence.

When in doubt, use the Dremel Micro. I cut out the pins, and at last, the lock was able to spin freely. It turns out that this lock was more complex than I thought. Instead of only having pins on one side, there were pins on two sides of the lock. Therefore, in order to pick it, you would have needed to use two bobby pins on either side, three hands, and very good timing.

Now that the lock was no longer a lock, it was time to reassemble the battery. I began by soldering the wires. There’s nothing much to talk about, just melt the solder, stick the wires together, and heat the heat-shrink tubing. Well my dad, with his infinite electrical engineering experience, said that I should probably fray the ends of the wires and shove them together before soldering for wires that carry more current. It’s hard to explain, either way, there are pictures below if you want to try this technique.

The charger hasn’t arrived yet, so the next update will probably also be about batteries.

Act First, Think Later — EV Moped Conversion pt. 2

Anxiety, the next gumption trap, is sort of the opposite of ego. You’re so sure you’ll do everything wrong you’re afraid to do anything at all. Often this, rather than “laziness” is the real reason you find it hard to get started. (Robert Pirsig, Zen and the Art of Motorcycle Maintenance)

Disassembling the moped was fun and all. But now came the difficult part, putting it back together. I started off with what I believed was the most simple part, replacing the original wheel with the hub-motor-laced wheel. The hub motor is just your average brushless DC motor (BLDC). The permanent magnets are located on the rotor, while the electromagnets are on the stator. Electricity charges the electromagnets on the stator to rotate the rotor. The only thing slightly different from a typical BLDC motor is that its axle is more reinforced.

The hub motor that I was working with directly drives the back wheel and has a max power of 3kw. The top speed is hard to estimate because I’m not sure what the final weight of the moped will be. Either way, the hub motor was laced using a single-cross pattern, which is the generic lacing pattern for larger hub motors and wheels.

Hub motor laced into wheel

Original wheel

I’ve realized that my way of working follows the “act first, think later” methodology, which often leaves me in bad situations. Case in point, I needed to remove the inner tube and tire from the original wheel and install it on the new wheel. Since I didn’t have the proper tire replacement tools, I decided that a pair of flathead screwdrivers would do the job well enough. Removing the inner tube and tire proceeded smoothly, but when I tried installing them on the new wheel, that’s when everything went haywire. First, the metal flatheads did a good job of scratching the black paint on the wheel. Second, the tire just could not fit onto the tire. It was only later that I realized, you can’t install a moped tire the same way one would install a bicycle tire. A quick internet search would have saved me the three hours spent prying the tire onto the wheel. Third, flathead screwdrivers are sharp and they will easily tear through butyl rubber, the stuff that inner tubes are made of.

The tire doesn’t fit

It turns out that there is a much smarter and much easier way of installing moped tires. Oddly enough, it involves Windex and zip ties. You first deflate the inner tube, then place it inside the tire. Next come the zip ties, compress the tire as much as possible before tying a zip tie. Repeat this step multiple times. Finally, spray an excessive amount of Windex on the wheel and tire. This time around, it was much easier to install the moped tire. Cut the zip ties and inflate the inner tube. The wheel should be good to go now, that is if you didn’t accidentally damage the inner tube. Unfortunately I did, so I had to remove the tire, patch the puncture wounds on the inner tube, and repeat the process.

Zip ties

Zip ties and wheel

Tire installed on wheel

Wheel on moped

Thanks Craig — EV Moped Conversion pt. 1

The real cycle you’re working on is the cycle called yourself. (Robert Pirsig, Zen and the Art of Motorcycle Maintenance)

After reading Zen and the Art of Motorcycle Maintenance, I got caught up in the ideal of owning a two wheeled, motorized vehicle. Somehow, I thought it would be a good idea to take an old gas moped and convert it to electric. As a person who has some very minimal mechanical knowledge, it shouldn’t be too hard right?

I was able to find a red colored ‘76 Puch Maxi who’s frame was in fairly good condition. The original lead-acid battery for the lights were gone and it hadn’t been run in five years, but either way, it was cheap.

The Puch Maxi was produced throughout the 70s/80s and was popular due to its fuel economy. All models were outfitted with a single cylinder, 50cc, two stroke engine. Some earlier versions used a pedal start mechanism, while the later ones have a kick start lever. Moped is a blend of the words “motor” and “pedaler.” So as the name would suggest, when the engine is disengaged, the Puch Maxi moped can be ridden like a bicycle. I think it’s cool in concept, but imagine having a 110 lb bike; it would be a nightmare to pedal.

Original moped

The engine and transmission were attached to the frame by three bolts. They were very rusty, but a bit of WD-40 and an impact driver were all that was needed to loosen them. I realized that the kickstand was connected to the engine, so the moped had to be flipped over in order to remove it. Later on, this move proved beneficial because I discovered that I had forgotten to drain the oil. After I narrowly avoided spilling motor oil all over the ground, I was finally able to detach the engine and transmission from the frame.

E50 engine and transmission still attached to frame


Flipped moped

Engine and transmission removed

Engine and transmission not attached to frame

Since I wanted to use a hub motor for this EV conversion, I had to take off the back wheel and replace it with one that was laced with a hub motor. The process was extremely simple. I just unscrewed everything that was in the way, and then the wheel was easily removed from the frame.

Nuts holding the wheel in place

Frame with engine, transmission, and back wheel removed

GunnHacks 5.0 — Augmented reality and orange UIs

I attended GunnHacks 5.0 to test out my programming skills while under a time constraint. Unfortunately, I woke up late and by the time I got to the venue, teams were already made. So as a one person group, I became the designer, programmer, and spokesperson.


The app, written in Java, performs image recognition on molecular diagrams and displays an augmented reality model of the recognized molecule. I used Google’s ARCore and Sceneform SDKs.

It was originally intended to help people visualize 3D molecular geometry while learning chemistry. But I had extra time so I decided to toss some other things in, hence the appearance of a turtle.

My favorite color is orange, therefore I wanted to make an orange themed UI. This design choice was not well recieved. Regardless, my project placed first at the hackathon, which gives me some sort of validation.

A Red VSCode Theme because red's a great color

While VSCode already has a default red editor theme, it’s very bright and hard on the eyes. I wanted to make a version that’s more suitable for everyday use.

The result is SkGRTT, a dark red VSCode theme with fire colored syntax (although people have said that it looks like candy corn). It’s currently available in the Visual Studio Marketplace with close to 200 installs, link here.

I used a Yeoman generator for VSCode extensions to create the main template, then tried different color combinations before finally settling on this specific one.

Tortuosity and Self-Organizing Feature Maps


I was lucky enough to spend the summer interning at Professor Van Savage’s lab at UCLA. Below is the combined work of Kaitlin Lim and I.


Vessels in the human body are normally straight, used for efficiently transporting blood to different regions of the human body. However, blood vessels can become tortuous, or twisted, due to vascular disease and abnormal development. The development of tortuous vessels is not well understood, allowing research to be done on finding quantitative methods of analyzing and characterizing the tortuosity of blood vessels in stroke recovery patients.

Vascular systems of mice brain affected by stroke were run with varying thresholds through Angicart, a software than analyses 3D radiographic images of blood vessels to determine their various characteristics. The resulting data points from Angicart were used to analyze a range of different metrics for measuring curvature and torsion. Metrics used each yielded slightly different values. The data points were also fitted with a polynomial and system of spline equations for visualization purposes.

After comparing the polynomial and spline fitting, we concluded that spline fitting consistently did better, with fitting values falling between the 0.5 and 0.6 range. Another area of research that was touched upon, but not deeply studied was the application of neural networks for differentiating between the three types of tortuous vessels. A self-organizing map was able to categorize semi-accurately the types of vessels, which indicates room for improvement.


My main area of focus revolved around writing code that was used to preprocess data before running it through Angicart, and then use the data produced by Angicart for further analysis.

Running an entire vascular network through Angicart is computationally expensive, which means that it is important to find the ideal threshold value beforehand. Preprocessing was done by segmenting full sized networks into smaller sections and running them through Angicart with varying threshold values. After finding a threshold value that was able to balance the number of features and noise, then the full-sized image was processed.

Analysis of different metrics for measuring curvature and torsion of vessels was automated with several R scripts that I wrote. Coordinate collections that made up each vessel were run through the script, and metrics such as average curvature, distance, maximum curvature, and sum of angles were calculated. These values were taken into account when fitting polynomial expressions and collections of splines to the coordinate collections for visualization purposes, done which was done by another R script.

Half of the values from the metric calculations were used to train a self-organizing feature map, an artificial neural network using unsupervised learning. The other half was used to test if the neural net was able to differentiate between the three vessel types. The accuracy plateauing at 0.86 indicates room for further research.

Angicart returns data regarding characteristics of blood vessel segments, which was used for threshold analysis where scaling exponents for vessels were calculated using a distribution-based method and a conservation-based method. I wrote several R scripts automate this sequence of calculations and output the values into a spreadsheet. These scaling exponents are important for finding patterns to predict the behavior of vessels that were outside of the specified region.


Walking into Professor Savage’s lab and being told to write code automating various calculations for blood vessel tortuosity analysis, when I had no idea what they even meant, was terrifying. So I spent the first days of the internship reading for hours on end to understand all that I could; everything from papers categorizing types of tortuous vessels to ones describing the methods behind scaling exponent calculations.

The most difficult aspect of this research process was the fact that no one’s doing anything similar. Meaning no cross referencing with others. When trying to fit a polynomial to a collection of coordinates, I wanted to minimize processing time, which meant I couldn’t use typical methods. Only after studying various fitting techniques I opted to use Frenet-Serret vectors, because they assume nonzero curvature. And since I was working with tortuous vessels, the fact that there will be a large number of twists and curves was a given.

I now realize that minimal guidance had its benefits, it really helped build problem-solving skills and good research habits.