Nono.MA

MAY 13, 2021

Here's a way to map a given color in a Pillow image (PIL.Image) to another color. This is not the fastest method and it will only replace exact matches.

In this example, we're turning all black pixels in the input image (0,0,0) with blue (0,0,255).

from PIL import Image
import numpy as np

img = Image.open('/path/to/image.png')
img[np.where((img==[0,0,0]).all(axis=2))] = [0,0,255]
img.show()

MAY 13, 2021

from PIL import Image

img = Image.open('/path/to/image')

left = 10
top = 20
right = 10
bottom = 20

img = img.crop((left, top, right, bottom))

MAY 13, 2021

import json

file = open('my-file.json')
json = json.load(file)

MAY 8, 2021

After CVAT is running and you have access to its login screen, you need to have an admin account to log in and access the admin panel.

You create an admin user from the command-line interface.

docker exec -it cvat bash -ic 'python3 ~/manage.py createsuperuser'

This command will as you for a username, email, and password.

After you log in, you can hover your username (in the top-right corner of the screen) and select "Admin page" to access the Django admin panel, where you can manage your CVAT site, manage users, groups, and more.

MAY 1, 2021

When you use two-factor authentication to sign in to your Gmail account (or to "Sign in with Google") you access your account with your email, password, and a verification code generated by Google Authenticator or other authenticator apps (such as Duo).

You might get an error like the one that follows when trying to sign in to Gmail with your Google password.

Authentication failed. Please check your username/password and Less Secure Apps access for mail@example.com.
Server returned error: "534-5.7.9 Application-specific password required. Learn more at 534 5.7.9 https://support.google.com/mail/?p=InvalidSecondFactor l25sm248619lfe.188 - gsmtp , code: 534"

When the service you're trying to use your Gmail account with doesn't allow you to "Sign in with Google," you need to create an app-specific password as detailed in the support Url provided by the error message.

Create a Google App Password

This app password

  • Go to your Google account
  • Security
  • Sign in to Google
  • App passwords
  • Choose the service type — e.g., Mail, Calendar, Contacts, YouTube, or Other (custom)
  • Choose the device type — e.g., iPhone, iPad, Mac, Windows, etc.
  • Generate

You'll get an app-specific password like this one — dbkdwckcplvgaktc — that will let you log in to the authorized service with your email and this password.

In my case, I use this password to be able to "Send as" from Gmail from an email address that has two-factor authentication turned on.

APRIL 28, 2021

import torch
print(torch.__version__)

APRIL 20, 2021

cd /path/to/repo.git
sudo chgrp -R {groupname} .
sudo chmod -R g+rwX .
find . -type d -exec chmod g+s '{}' +

Source

APRIL 20, 2021

If you're receiving this error when trying to composer install.

Your GitHub OAuth token for github.com contains invalid characters

Here's what worked for me.

Edit the composer authentication configuration file ~/.composer/auth.json.

nano ~/.composer/auth.json

Then replace the following.

  "github-oauth": {
    "github.com": "ghp_[YOUR-PERSONAL-TOKEN]"
  }

With this (basic auth):

  "http-basic": {
    "github.com": {
      "username": "[YOUR-GITHUB-USERNAME]",
      "password": "ghp_[YOUR-PERSONAL-TOKEN]"
    }
  }

Source

APRIL 20, 2021

I found this error while trying to update and install composer packages with composer install.

could not find driver (SQL: select * from information_schema.tables where table_schema = folio_burns and table_name = folio_items and table_type = 'BASE TABLE')

At first, I thought the solution was to edit /etc/php/7.4/cli/php.ini (for PHP-FPM 7.4 in my case) and uncomment the line ;extension=pdo_mysql to be like extension=pdo_mysql . But I was still getting this error as the mysql extension was missing.

PHP Warning:  PHP Startup: Unable to load dynamic library 'pdo_mysql' (tried: /usr/lib/php/20190902/pdo_mysql (/usr/lib/php/20190902/pdo_mysql: cannot open shared object file: No such file or directory), /usr/lib/php/20190902/pdo_mysql.so (/usr/lib/php/20190902/pdo_mysql.so: cannot open shared object file: No such file or directory)) in Unknown on line 0

The solution ended up being to install the extension, which would also add its own .ini file and activate itself on installation.

sudo apt-get install -y php7.4-mysql

Note that you can run this command with multiple extensions to be installed at once.

sudo apt-get install -y php7.4-{xml,bcmath,gd,mbstring,xsl,zip,curl,mysql}

APRIL 15, 2021

After downloading a website as HTML with cURL or any other workflow, you can convert the HTML code to the Markdown syntax with pandoc.

pandoc -o output.md input.html

APRIL 15, 2021

You can download any website as an HTML file (without the site's assets) using cURL in the command line, using the -L flag to follow any existing redirects.

curl -L https://nono.ma --output nono-ma.html

The manual alternative is to right-click on a website on your browser of choice (say, Google Chrome or Firefox), select Save As.., and save the site as HTML with some of its assets in a subfolder.

Afterwards, you can convert the downloaded HTML page into a Markdown document with pandoc.

APRIL 14, 2021

Here's how to get the raw String value of a Stringable object. Laravel's Illuminate\Support\Stringable has lots of helper functions, but sometimes you want to get the raw string value. For that, you just need to use the strval PHP built-in function on an object of the Stringable class.

// Define Strintable object
$stringable = Str::of('laravel-stringable-to-string');
get_class($stringable); // returns Illuminate\Support\Stringable
gettype($stringable); // returns object

// Get its raw String value
$string = strval($stringable);
get_class($string); // returns PHP Warning:  get_class() expects parameter 1 to be object, string given in […]
gettype($string); // returns string

MARCH 28, 2021

I got a sudden fan shut down every time I would switch on my MacBook Pro (16-inch, 2019) after I upgraded macOS from Catalina to Big Sur. Here's how I fixed it.

  • CMD + R - Press on reboot to enter Rescue Mode
  • Login as a user you know the password for.
  • Utilities › Terminal
  • Run kmutil trigger-panic-medic --volume-root /Volumes/Macintosh\ HD
  • Start the computer normally

This is the Kext extension that, apparently, was causing the issue.

cd /System/Library/Extensions/AppleThunderboltNHI.kext

MARCH 19, 2021

If you're getting this message when encrypting files with a GnuPG—the GNU Pretty Good Privacy (PGP) package—you can mark your key as trusted (if that's the case). This often happens when you copy a trusted key from one machine to another.

Here's what I was getting before 'trusting' my own key.

gpg: <KEY_ID>: There is no assurance this key belongs to the named user
It is NOT certain that the key belongs to the person named
in the user ID.  If you *really* know what you are doing,
you may answer the next question with yes.

Use this key anyway? (y/N) y

Every time, I'd have to answer y to complete the encryption process.

I learned how to skip this step from this StackOverflow post.

gpg --edit-key <KEY_ID>
gpg › trust

Then you set the degree to which you trust the specified key.

1 = I don't know or won't say
2 = I do NOT trust
3 = I trust marginally
4 = I trust fully
5 = I trust ultimately
m = back to the main menu

I selected 5 as this is a key I created for myself.

Your decision? 5
Do you really want to set this key to ultimate trust? (y/N) y

Confirm, then quit.

gpg › quit

Now you can use your GPG key without confirm every operation.

MARCH 8, 2021

If you multiple Vue components of the same type throughout your application but they're not in the same parent DOM element, mounting them may not be as easy as calling their parent.

Mounting multiple Vue components with a shared parent

In this first example, we can mount all of our button-counter components by mounting their parent, #button-container.

<div id="button-container">
  <button-counter></button-counter>
  <button-counter></button-counter>
  <button-counter></button-counter>
</div>

We just need to make one call.

new Vue({ el: '#button-container' });

But what if our components are spread throughout different HTML elements?

Mount multiple Vue components without a shared parent

Imagine you have components of the same (or different) types spread throughout your website's DOM.

<div>
  <button-counter></button-counter>
  <button-counter></button-counter>
</div>

<div>
  <button-counter></button-counter>
</div>

<button-counter></button-counter>

We can iterate through the DOM elements and mount them one by one.

// Obtain all button-counter elements
let elements = document.getElementsByTagName('button-counter');

// Convert HTMLCollection to array
let arr = Array.prototype.slice.call( elements )

// Loop through array and mount all elements
arr.forEach((el) => {
    new Vue( { el });
});

Note that we had to convert the HTMLCollection to a JavaScript array before we could call the .forEach() function to iterate over the button-counter host DOM nodes.

This works with Vue 2, and Vue 3 recently released new features for multi-root components and optional TypeScript support.

MARCH 8, 2021

Change this.

window.Vue = require('vue');

For this.

window.Vue = require('vue').default;

Then this should work.

window.Vue = require('vue').default;
window.VueResource = require('vue-resource');
window.Vue.use(window.VueResource);

MARCH 8, 2021

When trying to use the newest features of Laravel 8.x to automatically install Babel plugins with the .vue() call after using mix.js(), I kept getting the following error.

AssertionError [ERR_ASSERTION]: mix.js() is missing required parameter 1: entry

Even though I was using Laravel 8.x, my application had been migrated from older versions and my JavaScript dependencies in package.json were old. Specifically, I was still using Laravel Mix 5 while the newest version 6 had been released.

The solution was to change to version ^6.0.6 in package.json and run npm install, as the .vue() call is a new feature in Laravel Mix 6.

    "devDependencies": {
        // ...
        "laravel-mix": "^6.0.6",
        // ...
    },

Then on webpack.mix.js.

mix.js('resources/js/app.js', 'public/js')
   .vue();

I could then run the following commands successfully.

npm run dev
npm run watch
npm run hot
npm run prod

I believe this works with Vue 2 and Vue 3, and that the Vue version wasn't the problem but the version of Mix. (Note that I had also run npm update previously to update other dependencies and had to remove the --inline, --hide-modules and --no-progress options from my development and production commands in the scripts property of package.json.)

MARCH 5, 2021

This script re-encodes the seconds between second 25 and 45 of an input.mov video from mov to mp4; rotates the frames 180 (the hflip,vflip flags flip all pixels vertically and horizontally); removes the audio (with the -an flag); and speeds up the video (i.e., skips frames, with the flag setpts=0.05*PTS, which you could adjust to have more frames, for instance, as setpts=0.1*PTS).

ffmpeg -ss 00:00:25 -to 00:00:45 \
       -i "input.mov" \
       -an -filter:v "hflip,vflip,setpts=0.05*PTS" \
       "output.mp4"

FEBRUARY 25, 2021

Batch-Export PowerPoint Slides to Images Programmatically

unoconv is a tool to "convert between any document format supported by OpenOffice," available to install via Homebrew on macOS. You can convert, for instance, ppt files to png images (or to a multi-page PDF files) by running a command with this command-line interface program. The project is open source and you can browse its code on GitHub.

Install unoconv with Homebrew

brew install unoconv

Common issues: LibreOffice not found on your system

I ran into this issue when I first ran the unoconv command.

unoconv
# unoconv: Cannot find a suitable office installation on your system.
# ERROR: Please locate your office installation and send your feedback to:
#        http://github.com/dagwieers/unoconv/issues

That's because unoconv can't find libreoffice. You can install its Homebrew Cask.

brew install --cask libreoffice

After doing that, unoconv can find the libreoffice installation.

unoconv
# unoconv: you have to provide a filename or url as argument
# Try `unoconv -h' for more information.

Export PowerPoint Slides to PDF

unoconv slides.pptx -f pdf

Convert PDF to PNG or JPG Images

Even though you can directly export a PowerPoint presentation to JPEG or PNG format, unoconv exports only the first page by default.

You can use ImageMagick's convert tool to rasterize the PDF pages as images.

convert -density 300 slides.pdf image%d.jpg

Batch-convert Presentations to Images

Here's a bash script that will convert all ppt presentations in a folder to jpg images by folders.

# Convert all pptx files to multi-page pdf files
unoconv -f pdf *.pptx

# Loop through pptx files
for f in *.pptx
do
    echo "${f}.."
    mkdir -p ${f}-jpg
    convert -density 20 ${f%.*}.pdf "./${f}-jpg/image%d.jpg"
done

Available formats

You can see the extensive list of supported input and output formats on unoconv's documentation and read more about how to use unoconv in its help manual page or by running unoconv -h.

FEBRUARY 24, 2021

To read environment variables from a Python script or a Jupyter notebook, you would use this code—assuming you have a .env file in the directory where your script or notebook lives.

# .env
FOO=BAR
S3_BUCKET=YOURS3BUCKET
S3_SECRET_KEY=YOURSECRETKEYGOESHERE
# script.py
import os
print(os.environ.get('FOO')) # Empty

But this won't return the value of the environment variables, though, as you need to parse the contents of your .env file first.

For that, you can either use python-dotenv.

pip install python-dotenv

Then use this Python library to load your variables.

# Example from https://pypi.org/project/python-dotenv/
from dotenv import load_dotenv
load_dotenv()

# OR, the same with increased verbosity
load_dotenv(verbose=True)

# OR, explicitly providing path to '.env'
from pathlib import Path  # Python 3.6+ only
env_path = Path('.') / '.env'
load_dotenv(dotenv_path=env_path)

# Print variable FOO
print(os.environ.get('FOO')) # Returns 'BAR'

Or load the variables manually with this script.

import os
env_vars = !cat ../script/.env
for var in env_vars:
    key, value = var.split('=')
    os.environ[key] = value

# Print variable FOO
print(os.environ.get('FOO')) # Returns 'BAR'

FEBRUARY 15, 2021

Here's how I installed pandoc on my MacBook Pro (13–inch, M1, 2020) to run with Rosetta 2 — not natively, but on the x86_64 architecture — until a universal binary for macOS is built that supports the arm64 architecture in new Appple Silicon Macs.

This guide may be used to install other non-universal brew packages.

# Install Homebrew for x86_64 architecture
# https://soffes.blog/homebrew-on-apple-silicon
arch -x86_64 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"
# Install pandoc using that version of Homebrew
arch -x86_64 /usr/local/bin/brew install pandoc

Outputs

==> Downloading https://homebrew.bintray.com/bottles/pandoc-2.11.4.big_sur.bottle.tar.gz
Already downloaded: /Users/nono/Library/Caches/Homebrew/downloads/34e1528919e624583d70b1ef24381db17f730fc69e59144bf48abedc63656678--pandoc-2.11.4.big_sur.bottle.tar.gz
==> Pouring pandoc-2.11.4.big_sur.bottle.tar.gz
🍺  /usr/local/Cellar/pandoc/2.11.4: 10 files, 146.0MB
# Check pandoc's version
arch -x86_64 pandoc --version

Outputs

pandoc 2.11.4
Compiled with pandoc-types 1.22, texmath 0.12.1, skylighting 0.10.2,
citeproc 0.3.0.5, ipynb 0.1.0.1
User data directory: /Users/nono/.local/share/pandoc or /Users/nono/.pandoc
Copyright (C) 2006-2021 John MacFarlane. Web:  https://pandoc.org
This is free software; see the source for copying conditions. There is no
warranty, not even for merchantability or fitness for a particular purpose.

Converting Markdown to Html

arch -x86_64 pandoc sample.md -o sample.html

Contents of sample.md:

# Hello, Apple Silicon!

- Pandoc
- seems
- to
- work.

Contents of sample.html:

<h1 id="hello-apple-silicon">Hello, Apple Silicon!</h1>
<ul>
<li>Pandoc</li>
<li>seems</li>
<li>to</li>
<li>work.</li>
</ul>

FEBRUARY 9, 2021

When running any git command — including git pull, git push, git status, etc. — I was getting this error on macOS Big Sur.

xcrun: error: invalid active developer path (/Library/Developer/CommandLineTools), missing xcrun at: /Library/Developer/CommandLineTools/usr/bin/xcrun

The message means the Xcode Developer Tools are not properly installed, and you need to run the following command to fix this.

xcode-select --install

A window will prompt you to Install the Developer Tools. After two minutes, a message saying "The software was installed" showed up on my machine, a MacBook Pro (13-inch, M1, 2020). I was good to go.

JANUARY 20, 2021

Supposing you've started your container with ./docker-wine wine notepad and saved your files to your volume, for instance, at My Music folder with the new.txt name.

docker cp wine:/home/wineuser/new.txt ~/Desktop/

JANUARY 19, 2021

Here's how to execute a deployed AWS Lambda function with the AWS command-line interface.

Create a payload.json file that contains a JSON payload.

{
  "foo": "bar"
}

Then convert the payload to base64.

base64 payload.json
# returns ewogICJmb28iOiAiYmFyIgp9Cg==

And replace the contents of payload.json with that base64 string.

ewogICJmb28iOiAiYmFyIgp9Cg==

Invoke your Lambda function using that payload.

aws lambda invoke \
--function-name My-Lambda-Function-Name \
--payload file://payload.json \
output.json

The request's response will be printed in the console and the output will be saved in output.json.

If you're developing locally, you can use the aws lambda update-function-code function to synchronize your local code with your Lambda funciton.

JANUARY 14, 2021

This experiment is incomplete.

Kurt Schmucker says it should work as well as on your computer's hard drive as long as you're not doing too much read and write operations.

Schmucker recommends SSD2GO hard-drives. The SSD2GO PKT XT 1TB (350€) reads up to 1,024 MB/s. At a more affordable price point, is the SAMSUNG 7T 1TB ($160) which reads up to 1,050 MB/s and write up to 1,000 MB/s (on USB 3.2 gen 2 supported devices). The SAMSUNG T7 Touch 1TB ($190) ships with the same read and write capabilities as the T7, except for fingerprint protection—the disk has a fingerprint reader on top and I suspect it lets you unblock the disk only if you verify your fingerprint. At the highest price point—but the fastest speed—is the SAMSUNG X5 1TB ($400) with a read and write performance levels of up to 2,800 MB/s and 2,300 MB/s, respectively, and up to 40 Gb/s data transfer speeds. The tiny size and portability of the ADATA SE730H 512GB ($170) called my attention—you can bring it in your pocket—but it reads at 500 megabytes per second.

If I continue investigating down this path, I'll write down the hard drive I bought, the process to install and move Windows Parallels to the drive, and if it performs well.

If you want to move your Parallels VM to your external drive, follow Kurt Schmucker's guide.

JANUARY 13, 2021

Create A Private Key

openssl genrsa -out private.pem 4096

Create A Public Key

openssl rsa -in private.pem -out public.pem -outform PEM -pubout

Encrypt Files

openssl rsautl -encrypt -inkey public.pem -pubin -in file.txt -out file.ssl

Decrypt Files

openssl rsautl -decrypt -inkey private.pem -in file.ssl -out decrypted.txt

Notes

JANUARY 13, 2021

Create a GPG Key

gpg --full-generate-key

List Keys

gpg --list-keys
gpg --list-secret-keys --keyid-format LONG

Encrypt a File

gpg --output file.gpg --encrypt --recipient mundowarezweb@gmail.com file.txt

Decrypt a File

gpg --output file.txt --decrypt file.gpg

Exporting a Public Key

From https://www.gnupg.org/gph/en/manual/x56.html.

In binary format (inconvenient to be public on the web or sent via email).

gpg --output nono.gpg --export mundowarezweb@gmail.com

In plain-text format.

gpg --armor --export mundowarezweb@gmail.com

In plain-text format, saved to a file.

gpg --armor --output nonos-key.gpg --export --recipient mundowarezweb@gmail.com
gpg --armor --export --recipient mundowarezweb@gmail.com > nonos-key.gpg

JANUARY 13, 2021

I got this error while trying to pip3 install tensorflow. I tried python3 -m pip install tensorflow as well — it didn't work.

ERROR: Could not find a version that satisfies the requirement tensorflow
ERROR: No matching distribution found for tensorflow

As was my case, the reason for this error might be that you are using pip from a Python version not yet supported by any version of TensorFlow. I was running Python 3.9 and TensorFlow only had compatibility up to Python 3.8. By creating a new environment with Python 3.8 (or reverting the current environment to use 3.8) I could pip3 install tensorflow successfully.

JANUARY 8, 2021

About six months ago, Microsoft launched Pylance, a "fast and feature-rich language support for Python," available in the Visual Studio Code marketplace.

Pylance depends on our core Python extension and builds upon that experience, for those of you who have already installed it.

Among its main features are type information, auto-imports, multi-root workspace support, and type checking diagnostics.

The name Pylance serves as a nod to Monty Python’s Lancelot, who is the first knight to answer the bridgekeeper’s questions in the Holy Grail.

DECEMBER 2, 2020

Why Spotify kept removing my show

I fixed a bug that sporadically made Spotify remove my show, the Getting Simple podcast, from its platform without any logical explanation and, more worrisome, without warnings or notifications.


Some time ago, I noticed the podcast's RSS feed displayed episode release dates localized in Chinese and other languages. Something that, to my eyes, seemed random. Yesterday, I finally identified the issue.

The XML feed is cached for thirty minutes at a time — a duration I set to avoid overloading the server by re-generating the feed on every request.

But this feed re-generation used the requesting party's "locale." This code corresponds to the language and region configured in the system that performs a web request. For instance, the en-US locale represents a visitor or bot configured to use the English language and the United States region. A localized site — that can adjust its content to different locales — would display a date as Wed, 02 Dec 2020 for en-US visitors and as Mié., 02 Dic. 2020 for es-ES visitors.

date('D, d M Y H:i:s O');
// returns "Wed, 02 Dec 2020 05:19:14 -0500"

This date('D, d M Y H:i:s O') PHP method uses the operating system's language and region to determine what to display, but a localized website can adjust to the visitor's locale or even comply with explicit requirements.

App::setLocale('en-US'); // force locale to en-US

Item::formatDate(Date::now(), 'D, d M Y H:i:s O')
// returns "Wed, 02 Dec 2020 05:22:55 -0500"

App::setLocale('es-ES'); // force locate to es-ES

Item::formatDate(Date::now(), 'D, d M Y H:i:s O')
// returns "Mié., 02 Dic. 2020 05:22:55 -0500"

The issue was that the re-generation of the podcast feed was dependent on the requesting agent's locale when the cache expired, which could be any user or bot. Spotify was pinging the podcast and could load a feed generated by an agent that used a locale other than English in the past thirty minutes.

App::setLocale('en-US');
// Generate episode timestamps here

When Spotify found dates were not in English, it removed the show altogether—something that Apple Podcasts and other networks didn't do—and then added the podcast back hours later, when episode dates were in English again.

Spotify player showing a Getting Simple episode.

Spotify took its time to reload all existing episodes after forcing the localization of episode timestamps to use the en-US locale and re-generating the feed. Now all episodes and their stats are back. Hopefully, the show won't disappear again, and users won't hit this ugly, erroring embedded player.

Want to see older publications? Visit the archive.

Listen to Getting Simple .