Monday, June 08, 2026

chiku: Efficient Polynomial Function Approximation in Python

chikuPolynomial function approximation in Python — one API, seven methodsTaylorFourierPadeChebyshevRemezANNLR

chiku is an open-source Python library for efficient polynomial function approximation. It takes an arbitrary continuous function and returns the coefficients of a polynomial that approximates it, using a unified API across seven different methods. It is available on PyPI and is particularly useful for evaluating non-linear functions (such as sigmoid or tanh) under Fully Homomorphic Encryption, where only additions and multiplications are available and functions must be replaced by polynomials.

Installation

pip install chiku

To enable the optional TensorFlow-based ANN approximator (TensorFlow currently needs Python 3.11):

pip install chiku[ann]

What it does

Complex non-linear functions can be approximated by polynomials so they can be computed in restricted settings such as encrypted (FHE) domains. Deterministic methods like Taylor, Pade, Chebyshev, Remez, and Fourier always yield the same polynomial for the same function. chiku adds two data-driven approaches on top: an Artificial Neural Network (ANN) approximator that derives the polynomial probabilistically from the network's weights, and linear-regression (LR) fitting in the monomial or Chebyshev basis. Unlike libraries such as mpmath, chiku needs no datatype conversion and works directly on ordinary Python callables.

Every approximator shares the same interface: construct with (f, degree, frange), with fitting done in the constructor, then read coefficients with get_coeffs() or evaluate with predict().

Quick start

import numpy as np
from chiku import taylor, pade, chebyshev, fourier, remez, sk_lr

def sigmoid(x):
    return 1 / (1 + np.exp(-x))

# Taylor expansion at the midpoint of frange
t = taylor.taylor(sigmoid, degree=5, frange=(-1, 1))
print(t.get_coeffs())

# Pade rational approximation, built directly from f
p = pade.pade(sigmoid, pd=3, qd=3, frange=(-1, 1))

# Chebyshev series
c = chebyshev.chebyshev(sigmoid, degree=8, frange=(-1, 1))

# Truncated Fourier series
f = fourier.fourier(lambda x: x*x, degree=70, frange=(-1, 5))

# Iterative Remez minimax
r = remez.RemezSolver(sigmoid, degree=4, frange=(-1, 1))
print(r.get_coeffs(), r.get_error())

# Polynomial regression
m = sk_lr.sk_lr(sigmoid, degree=[1, 2, 3, 4, 5], frange=(-1, 1))

For arbitrary-precision work, the chiku.mpmath subpackage provides mpTaylor, mpRemez, and others that operate directly on mpmath callables.

How chiku compares

Where numpy offers Chebyshev fitting and scipy and mpmath offer a subset of Taylor, Pade, Fourier, and Chebyshev, chiku covers all of those plus Remez minimax, the ANN approximator, and linear-regression fitting, behind one consistent API and with no datatype juggling.

Links

If you use chiku in academic work, citation entries are provided in the project's README.

Build, Test, and Publish a Python Package to PyPI

Before you push a release to the Python Package Index, it pays to build and test it locally so you catch problems before your users do. This guide walks through the full workflow: setting up an isolated environment, installing your package in editable mode, running the test suite, building the distribution, validating the metadata, uploading with Twine, and tagging the release in Git. (If you are starting from scratch and need to structure the package itself first, see How To Create A Python Package.)

Build, Test & Publish a PyPI Packagevenvcreate +activateInstalldeps +pip -e .Testpytest -vBuildpython-m buildChecktwinecheckUploadtwineuploadTaggit tag+ pushTest locally and validate metadata before you ever upload to PyPI.

1. Set up an isolated environment

Install the Python version you need. If your package depends on TensorFlow, use Python 3.11, since the newest releases are not always supported right away. On macOS with Homebrew:

brew install python@3.11

Verify the version:

python3.11 --version

Create a virtual environment so you do not disturb the Homebrew-managed system packages, then activate it:

python3.11 -m venv venv
source venv/bin/activate

2. Install dependencies and your package

List your dependencies in a requirements.txt file and install them:

pip install -r requirements.txt

Install your own package in editable mode so code changes are picked up without reinstalling:

pip install -e .

3. Run the tests

Run the test suite and make sure everything passes before going any further:

pytest -v

4. Build the distribution

Update the version and metadata in pyproject.toml, then remove any stale build artifacts so you get a clean build:

rm -rf dist/ build/ *.egg-info

Generate the fresh source distribution and wheel:

python3 -m build

5. Validate the metadata

Check the built artifacts for metadata problems before uploading, so you do not waste a version number on a broken release:

twine check dist/*

6. Publish to PyPI

Upload the validated distribution. Twine will prompt for your PyPI credentials or API token:

twine upload dist/*

7. Tag the release in Git

Finally, tag the release in your repository and push the tag so the published version is traceable to a commit:

git tag -a v3.0.0 -m "Release version 3.0.0"
git push origin v3.0.0

Summary

The discipline that keeps PyPI releases clean is simple: work inside a virtual environment, install editable, run pytest, build from a cleaned directory, run twine check before twine upload, and tag every release in Git. Following this order means each version you publish has been tested and validated first.

Where to Publish Your Research for Free: One Platform per Content Type

When you want to share your research with the world for free, the trick is matching each type of output to the platform built for it. Posting a dataset to a preprint server, or a manuscript to a code host, only makes your work harder to find and cite. Here is a simple map: one well-established, free, public platform for each kind of academic content.

Free Platforms for Public Academic ContentOne go-to tool for each type of research outputarXivPreprints — un-reviewed manuscriptsZenodoDatasets — raw research dataResearchGateAcademic papers — author PDFsPubPubOpen journals — academic HTML & rich textFigshareSupplementary media — figures & chartsGitHubResearch software — code & scriptsOSFStudy protocols — pre-registrationsProtocols.ioLab methodologies — step-by-step recipesWikiversityOpen courseware — lecture notes & slidesDSpaceInstitutional output — theses & dissertations

Every platform below is free to use and makes your content publicly accessible, and most also mint a DOI so your work stays citable:

  • arXiv — Preprints: the standard preprint server for physics, mathematics, computer science, and related fields, where you post un-reviewed manuscripts before or alongside formal peer review.
  • Zenodo — Datasets: a CERN-backed general-purpose repository that issues a DOI for any file you deposit, making it ideal for raw research data.
  • ResearchGate — Academic papers: a scholarly social network where you can share author copies (PDFs) of your published papers and connect with other researchers.
  • PubPub — Open journals: a platform for running community-led open-access journals, publishing academic content as living HTML and rich text rather than static PDFs.
  • Figshare — Supplementary media: a repository for figures, charts, posters, and other supplementary media, each assigned its own DOI.
  • GitHub — Research software: the standard host for code and scripts; pair a release with Zenodo to make your software formally citable.
  • OSF (Open Science Framework) — Study protocols: a free hub for pre-registering studies and organizing all of a project's materials in one place.
  • Protocols.io — Lab methodologies: a repository for detailed, versioned, step-by-step lab protocols that others can follow and cite.
  • Wikiversity — Open courseware: a Wikimedia project for hosting open learning materials, lecture notes, and slides.
  • DSpace — Institutional output: the open-source repository software that powers many university libraries' archives of theses and dissertations.

The takeaway: pick the home that fits the content. Manuscripts go to preprint servers and journals, data and media to repositories that mint DOIs, code to GitHub (mirrored to Zenodo for citation), and institutional work to your university's DSpace archive. Putting each piece where readers expect it maximizes both discoverability and proper credit.

Running Metabase from the JAR File

Terminal — start Metabase$ java -jar metabase.jarINFO metabase.core Starting Metabase ...INFO metabase.core Metabase Initialization COMPLETEServing on http://localhost:3000

Metabase is an open-source business intelligence tool, and the quickest way to run it on your own machine is the self-contained JAR. No installer, no Docker, just Java and a single file. This tutorial covers downloading the JAR, running it, and the common issues you may hit on the first launch.

Step 1: Install Java

The Metabase JAR needs a Java runtime. A current build requires Java 21 or higher (older Metabase versions ran on Java 8 or 11). It works with both OpenJDK and Oracle JDK. Check what you have:

java -version

If Java is missing or too old, install a JDK. On macOS with Homebrew:

brew install openjdk@21

On Debian or Ubuntu, sudo apt install openjdk-21-jre works. Always confirm the required version on the official download page, since it changes between Metabase releases.

Step 2: Download the JAR

Download metabase.jar from the official Metabase site (metabase.com/start/oss/jar) and put it in a folder of its own, because Metabase will create files next to it on first run.

Step 3: Run it

From the folder containing the file, start Metabase:

java -jar metabase.jar

It will log startup progress to the terminal, initialise its application database, and then report that it is ready. Open http://localhost:3000 in your browser and complete the one-time setup wizard.

Step 4: Choose where Metabase stores its data

By default Metabase creates an embedded H2 database (metabase.db.mv.db) in the working directory. That is fine for trying it out, but H2 is not recommended for production. To use a different port or a proper application database such as PostgreSQL, set environment variables before launching:

# Change the port
MB_JETTY_PORT=3001 java -jar metabase.jar

# Use Postgres as the application database
export MB_DB_TYPE=postgres
export MB_DB_DBNAME=metabaseappdb
export MB_DB_PORT=5432
export MB_DB_USER=metabase
export MB_DB_PASS=yourpassword
export MB_DB_HOST=localhost
java -jar metabase.jar

Common issues

  • Wrong Java version: an error like UnsupportedClassVersionError means your Java is older than the JAR requires. Install the version listed on the download page.
  • Port already in use: if 3000 is taken, set MB_JETTY_PORT to another port as shown above.
  • Out of memory: give the JVM more heap with java -Xmx2g -jar metabase.jar.
  • Run it in the background: on a server, keep it alive with nohup java -jar metabase.jar > metabase.log 2>&1 &, or wrap it in a systemd service.

Summary

Running Metabase from the JAR is just java -jar metabase.jar once a compatible Java is installed. For anything beyond a quick trial, move it off the default H2 database onto PostgreSQL and run it as a managed background service.

Remove Active Content (Links) from a LaTeX PDF Using Ghostscript

Terminal — flatten a LaTeX PDF$ gs -dNOPAUSE -sDEVICE=pdfwrite \\ -sOUTPUTFILE=NEW_FILE.pdf -dBATCH in.pdfProcessing pages 1 through 3.Page 1 ... Page 2 ... Page 3NEW_FILE.pdf active links removed

A PDF produced from LaTeX often carries "active" content: clickable hyperlinks, internal cross-reference links, bookmarks, and sometimes form fields or embedded JavaScript. When you need a flat, static PDF for printing, archiving, or submitting to a system that rejects interactive elements, the simplest reliable tool is Ghostscript. This tutorial shows how to strip that active content by re-distilling the file through Ghostscript's pdfwrite device.

Step 1: Install Ghostscript

On macOS, install it with Homebrew:

brew install ghostscript

On Debian or Ubuntu use sudo apt install ghostscript; on most Linux distributions Ghostscript is packaged as ghostscript and provides the gs command.

Step 2: Re-distill the PDF

Run the source PDF back through Ghostscript's pdfwrite device. This rewrites the file from its drawing operations, and the interactive annotation layer (links, bookmarks, form fields) is not carried across, leaving a flat PDF that looks identical but is no longer active:

gs -dNOPAUSE -sDEVICE=pdfwrite -sOUTPUTFILE=NEW_FILE.pdf -dBATCH Trivedi_Devharsh_CV.pdf

Breaking down the options:

  • -dNOPAUSE processes every page without pausing between them.
  • -sDEVICE=pdfwrite selects the PDF writer, which is what rebuilds the file.
  • -sOUTPUTFILE=NEW_FILE.pdf is the flat output file.
  • -dBATCH exits Ghostscript when the job is done instead of dropping into its prompt.
  • Trivedi_Devharsh_CV.pdf is the input file (replace with your own).

Ghostscript prints its banner and processes each page:

GPL Ghostscript 10.02.1 (2023-11-01)
Copyright (C) 2023 Artifex Software, Inc.  All rights reserved.
This software is supplied under the GNU AGPLv3 and comes with NO WARRANTY:
see the file COPYING for details.
Processing pages 1 through 3.
Page 1
Page 2
Page 3

The result, NEW_FILE.pdf, keeps the visible text and graphics but drops the clickable link layer.

Step 3: Verify

Open NEW_FILE.pdf and confirm the links no longer respond to clicks and the layout is unchanged. The text stays selectable and searchable because the page content itself is preserved; only the interactive annotations are gone.

If something interactive still survives

Re-distilling removes the annotation layer in the large majority of cases. If a particular file still carries something you need gone, the most thorough option is to rasterize each page to an image and wrap it back into a PDF, which guarantees nothing interactive remains:

gs -dNOPAUSE -dBATCH -sDEVICE=pdfimage24 -r300 -sOUTPUTFILE=NEW_FILE.pdf input.pdf

This renders every page at 300 DPI. The trade-off is that the text is no longer selectable or searchable, so use it only when a fully static, image-only PDF is acceptable.

Avoiding active content at the source

If you control the LaTeX source and simply never want these links, the cleaner long-term fix is to not load hyperref, or to disable its links with \hypersetup{draft}, so the PDF is produced flat in the first place. Ghostscript remains the right tool when you only have the finished PDF.

Reference

Measuring Physical and Virtual Memory Usage in FreeBSD

Terminal — memory-usage on FreeBSD$ clang++ memory-usage.cpp -o memory-usage$ ./memory-usage 839Process ID: 839Physical memory used: 2228.00 KBVirtual memory used: 12844.00 KB

Here is a C++ program to print the physical and virtual memory used by a process on FreeBSD. It uses the kernel's sysctl interface to read process information, so it does not depend on parsing the output of any command line tool.

What this program does

  • It includes the necessary headers for system calls and I/O operations.
  • It uses std::atoi() for argument parsing and <iomanip> for formatting output.
  • The main function handles command line arguments:
    • If an argument is provided, it is converted to an integer with std::atoi().
    • If the converted PID is invalid (0 or negative), an error message is displayed.
    • If no argument is provided, it uses getpid() to get the current process ID.
  • It sets up the Management Information Base (MIB) for the sysctl call to retrieve process information.
  • It calculates the physical memory (resident set size) and virtual memory used by the process.
  • It uses std::fixed and std::setprecision(2) to format the output with two decimal places.

The program

#include <iostream>
#include <sys/types.h>
#include <sys/sysctl.h>
#include <sys/user.h>
#include <unistd.h>
#include <cstdlib>
#include <iomanip>

void print_memory_usage(pid_t pid) {
    int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid};
    struct kinfo_proc kp;
    size_t len = sizeof(kp);

    if (sysctl(mib, 4, &kp, &len, NULL, 0) == -1) {
        std::cerr << "Error: sysctl failed for PID " << pid << std::endl;
        return;
    }

    // Convert pages to KB
    long page_size = sysconf(_SC_PAGESIZE);
    double physical_memory_kb = (double)kp.ki_rssize * page_size / 1024.0;
    double virtual_memory_kb = (double)kp.ki_size / 1024.0;

    std::cout << "Process ID: " << pid << std::endl;
    std::cout << std::fixed << std::setprecision(2);
    std::cout << "Physical memory used: " << physical_memory_kb << " KB" << std::endl;
    std::cout << "Virtual memory used: " << virtual_memory_kb << " KB" << std::endl;
}

int main(int argc, char* argv[]) {
    pid_t pid;

    if (argc > 1) {
        pid = std::atoi(argv[1]);
        if (pid <= 0) {
            std::cerr << "Invalid PID. Please provide a positive integer." << std::endl;
            return 1;
        }
    } else {
        pid = getpid();
    }

    print_memory_usage(pid);

    return 0;
}

To compile and run

  • Save the code to a file, e.g., memory-usage.cpp.
  • Compile it with the FreeBSD default compiler: clang++ memory-usage.cpp -o memory-usage (or g++ -o memory-usage memory-usage.cpp).
  • Run the program for the current process: ./memory-usage
  • Or run it for a specific process ID: ./memory-usage 1234 (replace 1234 with the desired PID).

Sample run

root@freebsd:~/hwcode # clang++ memory-usage.cpp -o memory-usage
root@freebsd:~/hwcode # ./memory-usage 919
Error: sysctl failed for PID 919
root@freebsd:~/hwcode # ./memory-usage 839
Process ID: 839
Physical memory used: 2228.00 KB
Virtual memory used: 12844.00 KB
root@freebsd:~/hwcode # ./memory-usage
Process ID: 922
Physical memory used: 3188.00 KB
Virtual memory used: 14244.00 KB

The first call with PID 919 fails because no such process exists. The second reads a specific running process, and the third, with no argument, reports the program's own process (PID 922 here). The program prints the physical and virtual memory usage in KB for either the specified process ID or the current process if no ID is provided.

[SOLVED] Tuple index out of range in TensorFlow model fit function

model.fit(): list vs NumPy array# Problem: plain Python listsmodel.fit(x_train, y_train)IndexError: tuple index out of range # Fix: wrap with np.array()model.fit(np.array(x_train), np.array(y_train)) training runs

When training a Keras or TensorFlow model you may hit an IndexError: tuple index out of range the moment you call model.fit(), even though your data looks fine. The cause is almost always the type of the data you passed in, not the model. This post explains why it happens and the one-line fix.

The problem code

Here training data is built up as plain Python lists and passed straight to fit:

x_train = []
y_train = []
# ... append samples to the lists ...

history = model.fit(x_train, y_train, batch_size=batch_size, epochs=epochs)

This raises:

IndexError: tuple index out of range

Why it happens

Keras needs to know the shape of your input to wire up the first layer and to split data into batches. It reads that shape from the .shape attribute of the array you pass in, which is a tuple like (num_samples, features). A NumPy array has this attribute; a plain Python list does not. When TensorFlow tries to index into the shape tuple of a list-like object that reports an empty or zero-dimensional shape, the index it expects is not there, and you get tuple index out of range. In short, the framework expected an array with real dimensions and received a bare list.

The solution

Convert the lists to NumPy arrays before calling fit:

import numpy as np

history = model.fit(np.array(x_train), np.array(y_train),
                    batch_size=batch_size, epochs=epochs)

Now x_train.shape and y_train.shape return proper tuples, Keras can read the input dimensions, and training proceeds normally.

Good practice

Build your data as lists if that is convenient, but convert once to arrays right before training, and check the shapes to catch problems early:

x_train = np.array(x_train)
y_train = np.array(y_train)
print(x_train.shape, y_train.shape)

history = model.fit(x_train, y_train, batch_size=batch_size, epochs=epochs)

If the printed shapes are not what you expect (for example a ragged list that NumPy turns into a 1-D object array), fix the data first; that same mismatch is a common second cause of this error.

Reference

[How To] Retrieve API keys in OpenClaw

Agent and CLI tools often save the API credentials you sign in with to a hidden folder in your home directory. If you have been using the openclaw tool and need to find the Anthropic API key or Bearer token it stored, for example to reuse it elsewhere or to rotate it, you can locate it in a few seconds with grep. This short tutorial shows how.

Note: "spi keys" in the URL is a typo for "api keys".

Step 1: Go to the config directory

openclaw keeps its per-agent configuration under a hidden .openclaw folder in your home directory:

cd ~/.openclaw/

Step 2: List what is there

List the contents, including hidden files, so you can see the agent folders:

ls -loa

Each agent typically has its own subfolder under agents/, and the credentials live in an auth-profiles.json file inside it.

Step 3: Search for the keys

Rather than opening each file by hand, recursively grep the whole directory for the two token formats. Anthropic keys begin with sk-ant-, and OAuth-style tokens are sent as a Bearer value:

grep -r "sk-ant\|Bearer" .

The output points you straight at the files that hold credentials, for example:

./agents/anthropic-claude-opus-4-6/agent/auth-profiles.json: ...
./agents/main/agent/auth-profiles.json:    "key": "sk-ant-..."

Open the matching auth-profiles.json to read the full value:

cat ./agents/main/agent/auth-profiles.json

Security reminder

An API key is a password. Anyone who has it can spend against your account, so treat it accordingly: never paste a real key into a blog post, screenshot, issue, or chat; do not commit auth-profiles.json to a public repository; and if a key has ever been exposed, revoke and rotate it from the Anthropic Console rather than reusing it. The key values in this post are intentionally truncated for that reason.

Summary

Stored credentials for the openclaw agent tool live in ~/.openclaw/agents/*/agent/auth-profiles.json. A single grep -r "sk-ant\|Bearer" . from the config directory finds every file that contains a key or token, so you do not have to hunt through folders by hand.

[Fix] imsg: Bad CPU type in executable

Terminal — fixing Bad CPU type$ archi386$ imsg.../libexec/imsg: Bad CPU type in executable$ make buildBuilt bin/imsg (arm64 x86_64) universal

imsg is a handy command line tool that reads the macOS Messages database so you can query your iMessages from the terminal. After installing it with Homebrew, you may hit this error the moment you run it:

/usr/local/bin/imsg: line 2: /usr/local/Cellar/imsg/0.5.0/libexec/imsg: Bad CPU type in executable
/usr/local/bin/imsg: line 2: /usr/local/Cellar/imsg/0.5.0/libexec/imsg: Undefined error: 0

This post explains what causes it and the fix that actually worked: building a universal binary from source.

What "Bad CPU type in executable" means

That error appears when the architecture of the binary does not match the architecture your shell is running under. On an Intel Mac, or on an Apple Silicon Mac running a terminal under Rosetta, the process expects an x86_64 binary, but the installed imsg binary was built only for arm64 (or vice versa). macOS refuses to run a binary whose CPU type it cannot execute.

Step 1: Confirm the architecture mismatch

Check what architecture your shell is running as:

arch
# i386

Then inspect the installed tool. The Homebrew wrapper is just a shell script that execs the real binary:

file /usr/local/Cellar/imsg/0.5.0/bin/imsg
# Bourne-Again shell script text executable, ASCII text

bash -x /usr/local/Cellar/imsg/0.5.0/bin/imsg
# + exec /usr/local/Cellar/imsg/0.5.0/libexec/imsg
# .../libexec/imsg: Bad CPU type in executable

So the wrapper runs fine, but the real executable in libexec is the wrong CPU type for this shell.

Step 2: Why reinstalling from the bottle is not enough

The obvious next move is to reinstall, forcing a source build:

brew uninstall imsg
brew install --build-from-source imsg

But on inspection the resulting binary was still single-architecture:

file /usr/local/Cellar/imsg/0.5.0/libexec/imsg
# Mach-O 64-bit executable arm64

Still arm64, which is exactly the architecture that does not run under an x86 shell. The Homebrew formula build did not produce something this shell could execute.

Step 3: Build a universal binary from source

The reliable fix is to clone the project and use its own build target, which produces a universal binary that contains both arm64 and x86_64 slices. macOS then picks whichever slice matches the running shell.

brew uninstall imsg

git clone https://github.com/steipete/imsg.git
cd imsg
make build

The make build target resolves the Swift package dependencies (Commander, PhoneNumberKit, SQLite.swift), links the tool, and reports a universal binary:

Built /Users/devharsh/Downloads/imsg/bin/imsg (arm64 x86_64)

The (arm64 x86_64) at the end is the important part: this binary runs on Apple Silicon and Intel, natively or under Rosetta, so the "Bad CPU type" error is gone. The project's Makefile exposes a few useful targets:

make format   - swift format in-place
make lint     - swift format lint + swiftlint
make test     - sync version, patch deps, run swift test
make build    - universal release build into bin/
make imsg     - clean rebuild + run debug binary (ARGS=...)
make clean    - swift package clean

Step 4: Verify with the test suite

Optionally confirm everything is healthy by running the tests, which build and exercise the package:

make test
# Build complete!
# Test Suite 'All tests' passed

Step 5: Grant Full Disk Access

Because imsg reads the Messages database, it needs Full Disk Access for your terminal:

  1. Open System Settings > Privacy & Security > Full Disk Access.
  2. Enable access for your terminal application (Terminal, iTerm, etc.).

To let it send messages, also allow your terminal to control Messages.app under System Settings > Privacy & Security > Automation.

Summary

"Bad CPU type in executable" is an architecture mismatch. Neither the bottle nor brew install --build-from-source produced a binary this shell could run, because both were single-architecture. Cloning the repo and running make build produced a universal arm64 x86_64 binary that runs everywhere, which resolved the error.

[How To] Take ICAO 630*810 Photo

ICAO Passport Photo: 630 x 810 px 630 px 810 px Specification Format: JPG Dimensions: 630 x 810 pixels (7:9) File size: about 10 KB to 250 KB Background: plain, light, no shadow Face: centred, neutral, eyes open No glasses glare; remove if unsure iPhone → convert HEIC to JPG → crop in Paint → compress

Several Indian government photo uploads, including Passport Seva and OCI services, expect a digital photograph that is exactly 630 x 810 pixels, follows ICAO face-geometry rules, and stays under a strict file-size limit. You do not need a studio or paid software for this. With a recent iPhone, a plain wall, and the built in Paint app on Windows, you can produce a compliant photo in a few minutes. This tutorial walks through the full process.

What the photo has to satisfy

Before editing, it helps to know the target. An ICAO-style passport photo for these portals generally needs to be 630 pixels wide by 810 pixels tall, in JPG format, with the file size kept small (the Passport Seva upload, for example, expects roughly 10 KB to 250 KB). The composition rules matter just as much as the pixels: a plain light background, the face centred and squared to the camera, a neutral expression with the mouth closed, both eyes open and clearly visible, even lighting with no harsh shadows, and no glare on glasses (removing glasses is the safest option). The exact rules are spelled out in the official references linked at the end.

Step 1: Take the photo

Click a selfie (or have someone take it) with an iPhone 12 or newer, standing against a plain, light coloured wall. Keep the phone at eye level, fill the frame with your head and the top of your shoulders, and use soft, even daylight so the wall behind you has no shadow. Modern iPhone cameras already produce more than enough resolution, so you can crop in tightly later without losing quality.

Step 2: Convert HEIC to JPG

By default an iPhone saves photos in HEIC format, which most government portals will not accept. Convert the image to JPG. The most reliable path is a direct HEIC to JPG conversion, but if a tool struggles with HEIC you can go HEIC to PNG first and then PNG to JPG.

A few easy ways to convert:

  • On the iPhone itself, set Settings > Camera > Formats to Most Compatible before shooting, which saves directly as JPG.
  • On Windows, open the HEIC in the Photos app and use Save as to export a JPG (the HEIF image extension may need to be installed once).
  • Email or AirDrop the photo to yourself, which often converts it to JPG automatically.

Step 3: Crop to 630 x 810 in MS Paint

Open the JPG in Microsoft Paint. Use Resize (switch to Pixels, and uncheck Maintain aspect ratio only after you have cropped to the right proportion) to set the final canvas to 630 x 810 pixels. The cleanest approach is to first crop the photo to a 7:9 ratio (630:810 reduces to 7:9) around your face, then resize that crop to exactly 630 x 810. Centre the face so the head occupies most of the frame with a little space above the hair, matching the face-position guidance in the official specification.

Step 4: Compress if the file is over 250 KB

After saving the JPG, check the file size. If it is larger than 250 KB, compress it. Re-saving from Paint at a slightly lower quality, running it through an image compressor, or reducing JPG quality in any editor will usually bring it well under the limit without visibly hurting a 630 x 810 image. Confirm the final file is still a JPG and still exactly 630 x 810 pixels after compressing.

Quick checklist

  • Plain light background, even lighting, no shadows.
  • Neutral expression, mouth closed, both eyes open, no glasses glare.
  • JPG format, exactly 630 x 810 pixels.
  • File size within the portal limit (about 10 KB to 250 KB for Passport Seva).

References