# إبرافرس — full text # https://ibraverse.ca/ # Generated by Hugo from the site content. Canonical HTML is linked per page. -------------------------------------------------------------------------------- MeteoData https://ibraverse.ca/projects/meteodata/ 2026-08-20 · Python, Data Analysis, Québec -------------------------------------------------------------------------------- I graded three weather services against the station next door, to find out which parts of a forecast you can actually believe. The story I kept cancelling things because an app said rain, and then it did not rain. That is an ordinary complaint and a completely untestable one, because nobody remembers the times the forecast was right. The HydroData collector already existed and had the shape of the answer in it: poll a public source on a schedule, keep every version, and compare what was promised against what happened. The only thing missing was a source of truth. Environment Canada publishes station observations every minute, and there is a station close enough to two places I care about in Montréal to use as one. So the question stopped being does the forecast feel wrong and became something with an answer: for each hour that has now passed, what did each service say about it, and when did they say it? The product Two collectors in Docker containers. One reads the Open-Meteo API every five minutes for ECMWF, GFS and HRDPS at Parc Henri-Julien and Montréal—Trudeau, and writes a new record only when a service changes its mind. The other pulls Environment Canada’s SWOB-ML observations from the McTavish and Trudeau stations. Both land in date-partitioned JSONL and mirror hourly to Google Cloud Storage. The scorer joins them on the instant rather than the hour, refuses any forecast published after the moment it describes, and emits a single self-contained HTML file — inline SVG, no chart library, no network calls once it is open. The horizon control at the top re-grades every measure on the page: the same data asked a different question, which is the question the report exists to let you ask. Read the report → Five days is short, and one summer week in one city is not a verdict on forecasting. It is enough to separate the measures that hold from the one that does not, which was the thing I actually wanted to know. -------------------------------------------------------------------------------- Wingo https://ibraverse.ca/projects/wingo/ 2026-08-04 · Flutter, Firebase, Mobile -------------------------------------------------------------------------------- Helps a group of friends pick where to go out, and settles it, instead of a chat that never decides. The story Five friends picking a restaurant will fill a group chat with links, three thumbs-up reactions and no decision, and two days later nobody has booked anything. The problem was never a shortage of options. It is the locking in, not the voting, that ends the argument. Before that moment every message is only an opinion. The product A squad nominates options, everyone votes in real time, and one person locks the winner in. The app opens on a curated list of what is on in Montréal this week, so the first screen is useful before you have a single friend on the platform. Two decisions shaped the rest. Every write goes through a server function — no client touches the database directly — so the voting rules live in one place rather than in whichever app version someone still has installed. And deploys go through a small control plane instead of hand-run commands: build, deploy, publish, roll back, with an audit trail and a health check that reverts a bad release on its own. It has not launched. The honest reading of that is on the page below: the tooling around the product got further than the product did, and the day I wrote this page I found both of its sites returning 404 because nothing was watching them. -------------------------------------------------------------------------------- Farkad https://ibraverse.ca/projects/farkad/ 2026-08-01 · Flutter, Python, AI, Firebase, Mobile -------------------------------------------------------------------------------- A health tracker you talk to: say what you ate, drank and did in one sentence, and it records it. The story Every habit tracker asks you to become a data-entry clerk for your own life. Six taps for breakfast, a separate screen for water, a sleep slider you move without believing it. After about nine days you stop, and the app has learned nothing about you that it could not have asked. The information was never the hard part. You could say the whole day in one breath and a person would understand every piece of it. So the sentence should be the interface. The product You say one line — two eggs, a big glass of water, walked forty minutes, slept badly — and it comes back as structured entries across every area it touched, each one editable next to the words it came from. That last detail is the whole design. Free text means the failure mode is silent: the model does not refuse, it produces a confidently wrong number. The only correction interface that works is showing you your own sentence beside what was made of it. Users choose which of six areas they track, and the obvious build is one parser per area. It is one engine instead, handling any combination, so adding an area is data rather than code. I made that decision two rewrites late, after the first version had already worked. -------------------------------------------------------------------------------- RoboNode https://ibraverse.ca/projects/robonode/ 2026-08-01 · Robotics, C++, Simulation -------------------------------------------------------------------------------- A robot arm you drive from your browser, to test robotics code against real physics before touching hardware. The story Testing a robotics algorithm usually means building everything around it first. A simulator, a robot model, a scene, a controller, a way to see what happened — days of setup before the idea you actually wanted to try gets to run once. So most algorithms are evaluated in a notebook, on a chart, or in a simulation gentle enough that they pass. That is the part worth removing. Not the physics, and not the robot — the setup. A simulator that lets your algorithm succeed is worse than no simulator, because you believe it. The product One command brings up a cell in the browser: a UR10e on a rail, a conveyor, a bin, a pallet, and a running application you can watch. The physics is real. The arm is stopped by whatever is in its way, a grasp is a constraint on the part it actually caught, and the conveyor moves the workpiece by friction. Four things in that cell are nodes: what sees, what tracks, what plans the path, and what controls the arm. Each is one typed interface with several implementations and an empty slot for yours. Open the editor, write a grasp offset, and it compiles into a sandbox and becomes another selectable version. Then run the same application against both and compare. Breaking it is the demonstration. Switch tracking to the snapshot version and run the moving-bin application: the arm aims where the part was and misses, in the same way it would miss on a real line. Nothing about that failure needs a rig, a safety fence, or a technician. The engines underneath are borrowed on purpose: MuJoCo, OpenCV, Ruckig, Pinocchio. What I built is the boundary around them, the sandbox that lets a stranger’s code run beside mine, and the one wire contract that the browser, the CLI and anything else all speak. -------------------------------------------------------------------------------- Domia https://ibraverse.ca/projects/domia/ 2026-07-26 · AI, TypeScript, Testing -------------------------------------------------------------------------------- Software that tests other software by reading the screen and clicking, the way a person would. The story An end-to-end test does not usually fail because the product is broken. It fails because a button moved, a class was renamed, or a page took 300ms longer than it used to. So the tests get narrower until they check almost nothing, or someone spends a day a week keeping them alive. The reason is that a test written against selectors has no idea what it is looking at. It knows there is an element at .btn-primary. It does not know there is a button that says Continue. A person testing the same page never has that problem, because a person reads the screen. The product Domia takes a request in plain language and drives the application until it is done. It reads the accessibility tree, the same structure a screen reader uses, chooses an action, acts on what is actually there, looks at what changed, and decides again. One loop, no recorded steps, and the same loop whether the target is a web page or a desktop app. Judgement is the expensive part, so it is spent last. A deterministic pass over an origin’s files, headers and served HTML settles what it can in pure functions: fifteen findings in about a fifth of a second, with no model and no browser. The agent only picks up what is left. Every run is recorded as a tree and can be replayed from its own tape without calling the model again. It is a beta and says so. What is missing is the ordinary part: entering a key from the desktop UI, proper empty states, and signed builds. -------------------------------------------------------------------------------- Bâtir https://ibraverse.ca/projects/batir/ 2026-07-25 · AI, Python, RAG, Algeria -------------------------------------------------------------------------------- Answers Algerian construction questions in Arabic, French or English, and shows the building rule behind each answer. The story Algerian building codes exist, and they are not the problem. The problem is that finding the clause that applies to your slab means knowing which document to open, in a corpus written across two languages, and then reading it in a third if that is the one you think in. So the question gets asked to a colleague instead, and the answer is whatever that person remembers. For seismic rules and rebar spacing, “whatever someone remembers” is a load-bearing part of the process. A general model will answer confidently in all three languages and cite nothing. That is not an assistant, it is a liability with good grammar. The product You ask in Arabic, French or English and get an answer grounded in the corpus, with the source it came from attached. Three surfaces sit on one API: the chat itself, an operator console, and the service underneath. The console is the part I would build first again. Prompts, retrieval settings, embedding configuration and the corpus documents are all editable there, because the real improvement loop for a system like this is someone reading a bad answer and wanting to change one line — and that loop dies if it needs a release. Underneath, the backend is layered so the model provider is a detail: pure domain types, use-cases that depend on protocols rather than vendors, and the adapters at the edge. The retrieval sits on Postgres with pgvector rather than a separate vector service, which is one fewer system to run and one fewer place for the data to disagree with itself. -------------------------------------------------------------------------------- HydroData https://ibraverse.ca/projects/hydro-quebec/ 2026-06-01 · Python, Data Analysis, Québec -------------------------------------------------------------------------------- I recorded every power cut in Québec for two months, to see how often the power really returns when they say it will. The story Hydro-Québec publishes a live outage map: what is broken now, and when they expect it back. Then it forgets. No history, no accuracy record, no way to ask whether last Tuesday’s estimate meant anything. I wanted one number. When they say the power is back at four, how often is it back at four? Nobody publishes that, so I wrote the map down every minute for fifty-six days and worked it out. It is right about five percent of the time. The second thing I noticed was that I estimate exactly the same way. The product A reliability report the utility does not produce: where outages concentrate, how long they really last, which causes dominate, and how far the published estimates fall from what happened. The Montréal borough map exists because the raw feed ships geometry meant for drawing, not for counting, and turning polygons into per-municipality attribution was most of the work. The collector is deliberately dull. Snapshots append to the open hour’s file and only closed hours upload, so it can die at any moment and lose at most one minute — which it did, more than once, over eight weeks. I started polling before I knew the question. Every result worth having came from something I could not have thought to ask on day one, and could only ask because the data was already on disk. -------------------------------------------------------------------------------- TikiPro https://ibraverse.ca/projects/tikipro/ 2025-08-04 · Desktop, TypeScript, Firebase -------------------------------------------------------------------------------- Gives a small clinic a numbered queue on a screen, so patients stop asking who is next. The story A clinic’s waiting room runs on shouting. Someone calls a name, half the room doesn’t hear it, and the person who stepped outside loses their turn. The cost that never appears in a feature list is the staff time: a receptionist spends the day re-explaining the order to people convinced they were skipped, and every one of those conversations happens while somebody else waits. The software that fixes this is built for hospitals — a server, a network, a per-seat licence, an IT contact. A three-doctor clinic has one PC at reception and a television on the wall. The clinic never asked for software. It asked for the waiting room to stop being an argument. The product Reception, the waiting-room board and each doctor’s queue are three views of one application running on that single machine. The board is just a browser page on the second screen, so there is no second machine and no second licence, and patients can scan a QR code to watch the same live list on their phone. Around it sits everything needed for a stranger to buy and run it alone: offline-verified licence keys, an auto-update feed, an admin portal, and an audit log on every operation that touches patient data. Nothing phones home, because a clinic with a dead connection still has to open the door in the morning. -------------------------------------------------------------------------------- Rekba https://ibraverse.ca/projects/rekba/ 2024-11-21 · Python, Transit, Algeria -------------------------------------------------------------------------------- A bus, tram and metro app for Algeria, where no usable timetable existed until I built one. The story In Algiers you find out when the bus comes by standing where the bus comes and waiting. No agency publishes a usable timetable, and the mapping apps everyone already has show nothing, for the same reason: they consume open transit data, and there is none to consume. Where a timetable does exist it describes an intention rather than a schedule — which is its own kind of missing data, and harder to detect than an empty file. The app everyone wants cannot exist until someone builds the data underneath it. The pipeline is the product; the app is the part you can see. The product Riders get bus, tram and metro in one app, in the three languages the country actually reads in. Underneath, a pipeline manufactures a network out of sources never meant to be one: map geometry, timetable estimates, coarse positions. It publishes the result as a standard transit feed. The piece I did not expect to build is the review screen. Every candidate feed is compared against the published one and every change is graded before anything ships: routes removed, calendars changed, stations that quietly vanished. You approve a release rather than accept it, because publishing a broken timetable to someone standing at a stop is worse than publishing an old one. I surveyed riders before writing any of it, and what they wanted was not a journey planner. It was knowing whether the thing they are standing there for is actually coming. -------------------------------------------------------------------------------- Leorra https://ibraverse.ca/projects/leorra/ 2021-08-29 · Flutter, Firebase, Mobile, Clean Architecture -------------------------------------------------------------------------------- An app for sending a parcel abroad with a traveller who has room in their suitcase. The story Sending a laptop from Montréal to Algiers costs more than the laptop, takes a month, and may never arrive. Every Algerian family abroad already routes around this: you ask until you find someone flying home with room in a suitcase. It works entirely on trust, which means it only reaches as far as your own network does. Leorra tried to make that a marketplace — senders post what needs moving, travellers post their route and spare kilos, the app matches them. For a stranger to hand another stranger a laptop at an airport, the trust layer is the app. I built the marketplace instead. The product Listings, chat, contracts and matching all shipped, in three languages, with store screenshots ready. Identity verification, escrow and what happens when a package does not arrive: none of it. Two months in I deleted the working version and started again — the commit says restart from a very clean project, 1,134 deletions against 45 insertions. The first build worked and could not be extended; everything lived in the widgets. What replaced it separated domain from application from interface, and the fourth feature cost about what the second had. That was the right call and I would make it again. It is also not why the project stopped. -------------------------------------------------------------------------------- Montréal Forecast Reliability: Three Services, Two Places, 882 Comparisons https://ibraverse.ca/tech/montreal-forecast-reliability/ 2026-08-20 · Data Analysis, Québec -------------------------------------------------------------------------------- Three forecast services, two places in Montréal, every prediction graded against the weather station beside it over five days. Temperature holds up. Rain does not — 70% of the rain calls never happened. Interactive report, gradeable from one hour ahead to twenty-four. How this was built, and what it changed my mind about: the MeteoData project page. -------------------------------------------------------------------------------- Hydro-Québec Outage Analysis: Two Months of Grid Reliability Data https://ibraverse.ca/tech/hydro-quebec-outage-analysis/ 2026-06-01 · Data Analysis, Québec -------------------------------------------------------------------------------- A two-month reliability study of Hydro-Québec's public outage feed — regional reliability, Montréal boroughs, ETA accuracy, causes, record outages, and the data pipeline behind it. Interactive charts + map. How this was built, and what it changed my mind about: the HydroData project page. -------------------------------------------------------------------------------- How AI Agents Will Replace Software Testers: Building One That Tests a Live Site https://ibraverse.ca/tech/ai-agents-software-testing-domia/ 2026-04-07 · AI, Testing, Python -------------------------------------------------------------------------------- A practical guide to what AI agents actually are, and DOMIA — an autonomous end-to-end testing agent built on Google ADK and Gemini. A practical guide for engineers, QA leads, and managers who want to understand what AI agents really are — and how they are changing the way we test and validate software. This article is split into two parts. The first part is short — just enough to understand the building blocks. The second part is where things get interesting: a real-world example of an AI agent that tests software autonomously. I built it, it is working well, and I called it DOMIA. Part 1 — The building blocks 1. Chat: the foundation Everyone has used a chatbot by now. You type something, the model replies. That’s it. Chat: text in, text out. The model reads your text, predicts the most likely useful response, and sends it back. In this code snippet, we see how you can create a chat model using Gemini: import os from google import genai client = genai.Client(api_key=os.environ["GEMINI_API_KEY"]) response = client.models.generate_content( model="gemini-2.0-flash", contents="What is the capital of France?", ) print(response.text) # ── Expected output # Paris 2. Function calling: the AI picks up tools Chat alone can’t look up today’s weather, query a database, or click a button. It only knows what was in its training data. Function calling fixes that. You give the model a list of tools it can use, and it decides when to call them and with which arguments — you don’t have to route it manually. The model asks; your code runs the tool and hands back the result. The key thing that needs to be understood: the model does not run the tool. It returns a request — a JSON object saying “call this function with these args”. Your code runs the actual function and feeds the result back. The model asks; your code decides whether to comply. Every capability an agent has, it has because you handed it over. import os from google import genai from google.genai import types client = genai.Client(api_key=os.environ["GEMINI_API_KEY"]) # 1. Define the tool def get_weather(city: str) -> dict: """Returns the current weather for a given city.""" # In a real app this would call a weather API. ... # 2. Ask the model something that requires the tool. chat = client.chats.create( model="gemini-2.0-flash", config=types.GenerateContentConfig(tools=[get_weather]), ) response = chat.send_message("What's the weather like in Paris right now?") # ── Expected output ────────────────────────────────────────────────────────── # Tool called : get_weather({'city': 'Paris'}) # Tool result : {'temp': '18°C', 'condition': 'Cloudy'} 3. AI agent frameworks: closing the loop A single tool call is useful. But what if the task requires 10 steps? What if the model needs to look at the result, decide what to do next, call another tool, and keep going? That’s an agent: a model in a loop, with memory and tools, that keeps acting until it reaches a goal. Plan, act, observe, repeat — the agent loop. A few frameworks that make building agents practical today: Google ADK (great for Gemini-based and multi-agent systems), OpenAI Agents SDK (built around GPT and agent handoffs), LangChain (for custom control flow), Claude Agent SDK, and others. The agent loop handles everything: inject the state, let the model pick the next tool, run it, feed results back, repeat. You write the tools; the framework runs the loop. from google.adk.agents import LlmAgent from google.adk.runners import Runner from google.adk.sessions import InMemorySessionService from google.adk.tools import FunctionTool # 1. Define what the agent can do (tools) def navigate(url: str) -> dict: """Navigate the browser to a URL.""" return {"status": "success"} def click(ref: str) -> dict: """Click an element identified by its ARIA ref.""" return {"status": "success"} # 2. Build the agent — give it a goal and tools, nothing else agent = LlmAgent( name="web_tester", model="gemini-2.0-flash", instruction="You are a web testing agent. Use the tools to complete the given task.", tools=[FunctionTool(navigate), FunctionTool(click)], ) # 3. Run it — the agent decides which tools to call and in which order session_service = InMemorySessionService() runner = Runner(agent=agent, app_name="demo", session_service=session_service) session = session_service.create_session_sync(app_name="demo", user_id="user1") # Expected output: # → [tool] navigate called with https://example.com # → [tool] click called with first link ref # → "I navigated to example.com and clicked the first link." Part 2 — DOMIA: an agent that tests your software This is the part that actually matters. Let’s talk about a real problem: end-to-end (E2E) test automation — across web, mobile, and desktop apps. Why test automation is hard Traditional E2E tests are written like this: driver.find_element(By.ID, "submit-btn").click() assert driver.find(By.CLASS_NAME, "success-msg").is_displayed() Change the button ID, rename the class, add an animation — the test breaks. Someone has to fix it. On a fast-moving product, that someone ends up spending more time maintaining tests than catching real bugs. And the more complex the scenario, the worse it gets. A test that covers sign-up → email confirmation → login → onboarding takes a week to write and breaks constantly. What makes AI different here An AI agent doesn’t care about selectors. It looks at the page the way a human QA engineer does — reading labels, understanding context, figuring out what to click. If you rename a button from “Submit” to “Continue”, the agent still finds it. This is what we call self-healing: the test adapts to the UI instead of breaking. Three capabilities make this possible: Visual perception — the agent reads screenshots the way a human tester would, catching visual regressions and layout issues that pure DOM and output text data inspection misses. Natural language acceptance criteria — you describe what the system-under-test should do, not how to drive the browser step by step. Built-in test oracle — the model itself can judge whether the observed state matches the expected outcome, without you hardcoding every assertion. Introducing DOMIA DOMIA is an autonomous QA agent. You give it a target system — a URL for web apps, a mobile app, an Electron window — and a plain-English goal expressed as acceptance criteria. It navigates, clicks, types, scrolls, reads — and returns a structured verdict: PASS, FAIL, or UNCERTAIN, with a full execution trace as evidence. We’ll show three concrete runs: a multilingual UI regression, a visual assertion that requires a vision-capable model (VLLM), and a cross-layer test that writes a file to disk using a shell tool. DOMIA under the hood. Under the hood: Google ADK manages the agent loop (plan → act → observe → repeat). Gemini is the model, vision enabled — it sees screenshots, videos, and more. The underlying driver — Playwright for web, Appium for mobile, or Electron APIs for desktop — controls the actual app, the same SUT your users hit. Every action (click, type, navigate, shell command) is a tool the model can call. Results are stored with full observability: every step, tool call, screenshot, and verdict is recorded, so failures are reproducible and debuggable. Example 1 — Multilingual UI regression (ibraverse.ca) Acceptance criterion: “Open ibraverse.ca and verify that the website supports at least 3 languages — English, French, and one other.” No selectors. No hardcoded text comparisons. DOMIA navigates the site, finds the language switcher, cycles through options, and checks that the content actually changes. A traditional regression test would break every time the language menu is redesigned. DOMIA doesn’t care — it reads the UI the way a human tester would. Recording: DOMIA opens ibraverse.ca, cycles through languages and returns PASS. Watch the recording on LinkedIn. Example 2 — Visual understanding with a VLLM (is Brahim smiling?) This one can’t be done without vision. The acceptance criterion is: “Look at the profile photo of Brahim on the page and verify he is smiling.” No DOM element can answer that. A traditional E2E test has no way to pass. But DOMIA, running with a vision-capable model, takes a screenshot, looks at the image, and judges the expression — the same way a human QA reviewer would during a visual review. This scenario passes only when vision is enabled. With a text-only model, DOMIA correctly returns FAIL: it can read the alt text, but can’t verify the actual image content. Recording: DOMIA with a vision model navigates to the page, captures the photo, and returns PASS with the model’s reasoning as evidence. Watch the recording on LinkedIn. Example 3 — Cross-layer test with shell tools (counting articles) Acceptance criterion: “Count how many articles are published on the website and write the result into a local file called article_count.txt in the desktop folder.” This passes only when the shell tool is enabled. Without it, DOMIA can see the article count on screen but has no way to write it to disk. With the shell tool active, the agent reads the count from the page and calls echo "2" > article_count.txt on its own — no human needed. This illustrates a broader class of tests: scenarios where the verdict requires acting on the environment, not just reading it. Think of it as agentic reporting — the agent verifies a condition and leaves behind the artifact that proves it. Recording: DOMIA navigates, counts, runs the shell command, and the article_count.txt file appears on disk in real time. Watch the recording on LinkedIn. The bigger picture DOMIA is built for E2E and TNV testing across every major app type — the same goal syntax works everywhere, only the driver changes: Perception — after every action, DOMIA captures a screenshot (and video) and the data, and feeds both to the model. This is what makes self-healing work: the agent sees the app the way a human tester does, not through hardcoded selectors. Shell tool — the agent can run terminal commands mid-test: curl an API, query a database, check a file, write evidence to disk. This is what makes cross-layer TNV possible in a single run. Vision — --vision switches the model to multimodal mode. The agent sees real screenshots, not just the DOM or text — useful for visual regression, image checks, and UI states that aren’t exposed in the accessibility tree. Workflows — define a sequence of test goals in a file and run them as a batch. Each step is plain English. Steps can continue on failure so the full suite runs even when something breaks. Reports — --report html for a human-readable summary, --report junit for CI (Jenkins, GitHub Actions, GitLab CI). Every run is also persisted in a local SQLite database — re-inspect any step, re-run any scenario, track trends over time. Plugins — drop a JS file into ~/.domia/plugins/ and the agent gets a new tool automatically. No recompile needed. It’s worth being honest about the limits It’s not magic. Complex screens with heavy dynamic content, unusual gestures, or non-standard UI patterns — on any platform — can confuse the agent. It’s not free. Every LLM call costs tokens. Running a full TNV campaign with hundreds of test cases against a cloud model gets expensive. The local model option (Example 2) helps bring cost and latency down for any platform. It’s best for high-value, complex scenarios. For simple happy-path smoke tests on web, mobile, or desktop, use scripted automation — it’s faster and cheaper. DOMIA shines on long, stateful user journeys — the UAT scenarios and cross-layer regression tests that are too expensive to maintain with traditional selector-based automation. Conclusion Building DOMIA from scratch is what actually taught me how agents work — not reading about them, but writing every line of Python: the tool definitions, the session loop, the perception pipeline, the failure handling. You only really understand the limitations when you hit them yourself. If you want to learn agents, build one. Pick a problem you know well, give the model one tool, and see what happens. If you have ideas on how to improve DOMIA — better tools, smarter perception, new platforms — reach out. I’m always happy to talk. Sources Google — Agent Development Kit (ADK) Google — Gemini API: function calling OpenAI — Agents SDK LangChain Anthropic — Claude Agent SDK Playwright · Appium -------------------------------------------------------------------------------- كيف أنقذت معالجة الصور رحلتي: الباركود ولون المقدمة https://ibraverse.ca/ar/tech/barcode-flight/ 2025-12-03 · معالجة الصور -------------------------------------------------------------------------------- مثال واقعي على أهمية دعم لون المقدمة في قراءة الباركود. في أحد الأيام كنت في مطار دوسلدورف للحاق برحلة طيران. كنت قد سجلت وصولي عبر الإنترنت ولم يكن لدي سوى الباركود على هاتفي. عندما وصلت إلى نقطة التفتيش الأمني لمسح الباركود، لم يعمل. كنت متأخرًا بالفعل. جربت كل مستويات التكبير والتدوير… لا شيء. أخبرني رجل الأمن أنني بحاجة للعودة إلى بوابة تسجيل الوصول وآمل أن يكونوا لا يزالون هناك لطباعة بطاقة صعود ورقية. لكنني أدركت حينها: هاتفي كان في الوضع الداكن، وكان الباركود يظهر بلون مقدمة داكن. لذا قمت ببساطة بالتبديل إلى الوضع الفاتح… وقد وفر لي ذلك الوقت، وأنقذ رحلتي، وجنبني الكثير من التوتر. آمل حقًا أن يقوم مطار دوسلدورف وجميع المطارات بتحسين قارئ الباركود الخاص بهم. القارئ الذي يفترض داكنًا على فاتح هو قارئ يعمل في كل مكان إلا على هاتف في وضع ليلي — وهو بالضبط المكان الذي تعيش فيه بطاقات الصعود. لون المقدمة إعداد موجود في كل مكتبة تصوير جادّة، ومنها مكتبة Aurora Imaging التي أعمل عليها، وضبطه صحيحًا لا يكلّف شيئًا. ما زلت أُبدّل إلى الوضع الفاتح قبل كل بوابة. -------------------------------------------------------------------------------- Edge Measurement in Industrial Vision: Counting Saw Blade Teeth https://ibraverse.ca/tech/image-histograms/ 2025-01-08 · Industrial Vision, Image Processing -------------------------------------------------------------------------------- Counting the teeth on a saw blade looks trivial and is not. How edge measurement actually solves it, and which part of the method decides whether it works. A saw blade is a good way to explain edge measurement, because counting its teeth looks trivial and is not. You can see the teeth. A vision system cannot: it has a grid of intensities, and everything you want to know has to come out of where those intensities change. Every number below comes from the arbor hole and one circle drawn through the teeth. Do not count the teeth — measure across them The instinct is to find the blade’s outline and count the bumps on it. That means separating blade from background, which means picking a threshold, and a threshold is a promise about the lighting that you will not be able to keep. The better move is to segment nothing. Put a circle on the image, centred on the arbor hole, at a radius that passes through every tooth, and read the image along that circle. A closed path through all the teeth turns a two-dimensional counting problem into a one-dimensional signal with one feature per tooth. That is the actual idea, and the rest is consequences of it. A signal that should be periodic is a signal you can check, and being able to check the answer is worth more than getting it. The origin comes from the part, not from the image The circle needs a centre, and the centre has to be a feature of the blade rather than a position in the frame. The arbor hole is the obvious candidate: fit a circle to it and every measurement afterwards is expressed relative to the part. This is what makes the method survive contact with a real cell. The blade is never placed the same way twice. If the origin travels with the blade, that stops being a problem instead of becoming a calibration ritual. Stripes, not edges An edge is a single transition. A stripe is a pair of them — a rising edge and a falling edge, of a stated polarity, with a width between them. Counting stripes rather than edges is the whole reliability argument. An edge is a place where the intensity moved, and plenty of things move it: a glint on ground steel, a scratch, the seam of the background. A stripe additionally has a width, and a width is something you can reject on. Noise readily produces one transition. It much less readily produces a matched pair of the right polarity, of roughly the right width, at roughly the right spacing. So the parameters that matter are the ones that describe the tooth as an object: polarity, taken from which way the tooth is darker than what surrounds it; a width range, taken from the blade specification rather than from the image; and a contrast threshold set high enough that a soft gradient is not allowed to be a tooth. Edge positions themselves come out at sub-pixel precision from the shape of the intensity gradient, which is why this is a measurement rather than a pixel count. The check that costs nothing If the count is right, the teeth are evenly spaced in angle. That is not an extra requirement — it is a property of the part you are already holding, and it is free. Convert each detected stripe to an angle about the centre, take the gaps between consecutive angles, and look at their spread. Uniform gaps mean you counted teeth. One gap at twice the others means you missed one, and the gap tells you exactly where to look. Gaps at half the spacing mean you counted something twice. This is the difference between a number and a measurement. A system that reports 71 teeth is useless, because nobody can tell whether it is right. A system that reports 72 and can say the angular spacing was uniform to within a fraction of a degree has actually measured something, and can be trusted to say when it should not be. What actually breaks Three things, in my experience, and none of them is the algorithm. Reflection. Ground steel is close to a mirror. A specular highlight crossing the scan path is high-contrast and looks exactly like the transition you are hunting for. This is a lighting problem and it is solved with lighting — diffuse, off-axis — not by lowering a threshold until the highlight goes away, which also removes the teeth. Teeth that are not identical. Many blades are ground with an alternate top bevel, so consecutive teeth are angled opposite ways and do not present the same face to the camera. Anything that assumes every tooth looks like the last one will find half of them. The width tolerance has to cover both appearances, and the periodicity check has to expect one tooth per gap rather than one bevel. The radius. Too close to the hub and the path passes inside the tooth roots and sees nothing. Too far and it leaves the blade between teeth and the signal becomes background. It wants to sit where the teeth are widest, and it is worth deriving it from the detected hole radius rather than fixing it, so that one recipe covers a family of blade sizes. Where this generalises Nothing above is about saw blades. The method is: find a feature on the part that defines a coordinate system, scan a path the features of interest must cross, measure pairs rather than transitions, and verify the result against the regularity you already expect. That applies to gear teeth, holes in a perforated sheet, splines on a shaft, threads, connector pins — anything repetitive arranged around something you can locate. The part changes and the recipe does not. Here is the whole thing running on the blade at the top of this article: The one thing I would carry out of this into any measurement problem is the last section rather than the first. Getting the count is ordinary work. Being able to say why the count is right, from evidence in the same image, is what separates a demo from something you can put in front of a machine that acts on the answer. -------------------------------------------------------------------------------- Building a Chatbot That Answers From Your Own Documents https://ibraverse.ca/tech/rag-montreal-pools/ 2024-09-03 · Python, LLM, AI -------------------------------------------------------------------------------- Public schedules are published as pages nobody can query. This builds a chatbot that answers questions about Montréal's indoor pool times, and explains how retrieval over your own documents works. In this article, I will demonstrate how to use a Large Language Model (LLM) to create a chatbot that can answer questions about pool availability in Montréal. Using LLMs in a business can be highly beneficial, but allowing an LLM to operate unchecked and make decisions can be risky, potentially leading to significant issues and financial losses. For example, you can read about how Air Canada was compelled to pay compensation due to a misleading response from an LLM: Washington Post article. This article is intended only for educational purposes, showcasing the power of AI, generative models, and the tools that are developed very quickly. I understand that this solution may seem overly engineered, and I agree. A simpler, less expensive solution could address this problem. However, with more advanced LLMs available, implementing a solution using an LLM can be achieved with just a few lines of code, allowing the LLM to analyze all the data. I truly felt this while tackling this challenge: Software ate the world. Now AI is eating software. I — The problem I started swimming a couple of weeks ago, and fortunately, Montréal has many indoor swimming pools. The city’s website lists all the indoor pools along with their availability: Indoor swimming pools. The downside is that you cannot search for an available pool at a specific time, as each pool has its own schedule, which varies significantly. Some pools have their schedules embedded as a table on the website, like this one: Bain Émard. However, for other pools, you have to leave the city website and visit a Facebook page to see the pool’s schedule: Complexe Sportif de Saint-Laurent. Since I didn’t have a fixed time to go to the pool, I found myself constantly navigating through pool websites to find the next available slot, which was time-consuming and very manual. II — Large language models I decided to use a Large Language Model (LLM) like ChatGPT to address this problem. LLMs excel at understanding complex text — such as pool schedules — and grasping concepts related to time and space, like pool opening and closing times, or pools nearest to a particular address. One challenge is that pool schedules change frequently, and recent schedules were not included in the LLMs’ training data. So, how can we solve this problem? Here are three possible solutions: Fine-tune the model using recent data. Fine-tuning involves updating the model’s weights by training it with a new dataset. This is effective if you have a large, “static” dataset that was not available during the initial training of the LLM — by static, I mean data that does not change over time. However, this approach can be costly and resource-intensive. Provide the entire database alongside the user’s query. This technique involves including the entire database with the user’s query as a prompt, rather than retraining the model. It works well if the dataset is relatively small. Current LLMs support a large context window, but if the dataset is too large, this approach becomes impractical because the LLM can only process a limited portion of the prompt and will only consider the most recent part. Use retrieval-augmented generation. RAG enhances the capabilities of LLMs by integrating them with specific knowledge databases, without the need for retraining the model. This approach is cost-effective and helps maintain the relevance, accuracy, and usefulness of the LLM’s output in various contexts. RAG works by first searching through a large database to find relevant information related to a query. Then, it uses this information to generate a more accurate and informative response. An advanced RAG pipeline. Source: DeepLearning.AI — Building and Evaluating Advanced RAG. III — The design and implementation The pipeline has two halves that run at different times. Everything on the left happens once, offline, when the schedules are collected. Everything on the right happens per question: OFFLINE · ONCE Montréal pool pages HTML tables · Facebook One text file per pool schedule · address · closures Embedding model text → vectors Vector store LlamaIndex PER QUESTION User question ChainLit chat UI Same embedding model semantic search Prompt = context + question retrieved schedules LLM answer + link to the source A. Prepare the dataset We need to collect data on all the indoor pools. I tried to automate this process as much as possible, but there is room for improvement to handle more complex websites. Using some Python packages, I was able to successfully create a text that contains the pool schedule for each pool: Montréal indoor pools dataset — one file per pool. To achieve this, we need to extract all tables and relevant information from each pool’s HTML page, such as the pool’s address and any exceptional closures. There are tools that can help automate this process, such as LlamaIndex’s web readers — see data connectors and the web reader package. Each pool has its own file with a schedule that looks something like this: Example of a pool schedule after extraction. B. Create the embeddings When a user asks a question about a specific pool, we need to search the database for relevant information, combine this information — also known as context — with the user’s query, and then use the LLM to generate a response. To find relevant information in the database, we can use various search methods, such as semantic search or keyword search. Semantic search finds data based on the intent and contextual meaning of a query, rather than an exact match on query words. So, once the dataset of text files is ready, we need to represent each word or phrase with a special vector in a high-dimensional vector space, where words with similar meanings will be close to each other. LlamaIndex was used to handle data preprocessing. Text becomes vectors; similar meanings land close together. To create an embedding, we need an embedding model, which is an LLM used to convert text into vectors. This same model will later be used to embed the user’s query and search the database. It’s important to use the same model for both the user query and the database embeddings. C. Prepare the prompt With the embeddings ready, we need to obtain the user query, create an embedding for it, search the database for relevant documents, and prepare the prompt for the LLM. Tools like ChainLit can be used to create the chatbot, handling server creation, message history, themes, parsing, and more. IV — Demo I will not say anything here, and let you see this beauty of an LLM alive: ChatGPT used in a RAG system to answer questions about Montréal pools. V — The lesson As a software developer, I always aim to find exact solutions that are deterministic and proven through mathematics, physics, and science. Working with LLMs made me uncomfortable because I couldn’t fully trust them, even though the responses were accurate in my case. There is no guarantee — that I am aware of — that the model will consistently perform well and continue to provide reliable results. This is why in my demo I provide the URL to the pool website as a source. Due to the AI boom, there are countless tools available, with many attempting to achieve similar objectives. The internet is flooded with numerous tools that often overlap in functionality, making it challenging to navigate all the technologies. Standards and interfaces are not yet well defined, and code that works today may become obsolete in a few months. However, this rapid evolution might be a natural part of progress. The whole thing took an afternoon, with an LLM as a coding partner, to build an app that answers questions with an LLM. I was occasionally banned from accessing pool websites due to intensive data collection for the database. My apologies to Ville de Montréal for the load. :) Sources Washington Post — Air Canada must honor refund policy invented by its chatbot Ville de Montréal — indoor swimming pools LlamaIndex — data connectors and the web reader (the original WebPageDemo URL has since 404’d) ChainLit DeepLearning.AI — Building and Evaluating Advanced RAG -------------------------------------------------------------------------------- Finding an Infinite Loop in Scikit-Learn's K-Means https://ibraverse.ca/tech/scikit-learn-kmeans-infinite-loop/ 2024-04-29 · Python, Machine Learning -------------------------------------------------------------------------------- Finding and reporting an infinite loop in scikit-learn's K-means empty-cluster handling, and how the fix was reached. The boundaries are where a mathematical algorithm tells you what it really is. An implementation can be correct on every well-behaved input and still have a state it cannot leave, and the only way to find that state is to go looking for it. I went looking in scikit-learn’s K-means and found one: an input that makes it loop forever. Three conditions have to line up, and the last of them is the interesting part — the code that handles empty clusters creates the empty cluster it is handling. Below: what K-means does, how the loop happens, and what came of reporting it. Personal opinion. I think everyone should help make open source better however they can, as long as it doesn’t hurt their business. Open source is a great idea and has had a big impact on our world. Leaders of big tech companies should support it more. K-means The k-means algorithm is a popular unsupervised machine learning technique used for clustering data. The goal of the k-means algorithm is to partition a set of data points into k clusters where each data point belongs to the cluster with the nearest mean. The standard algorithm was first proposed by Stuart Lloyd of Bell Labs in 1957. K-means has two inputs: the number of clusters k, and the data points. Some implementations also support optional initial centroids. These are the main steps: Initialization — choose k initial cluster centroids from the data points (this could be random or based on some statistics). Assignment step — assign each data point to the nearest cluster centroid based on a distance metric (typically Euclidean distance). This step creates clusters. Update step — recalculate the centroids of the clusters based on the current assignment of data points. The new centroid is the mean of all data points assigned to that cluster. Repeat — alternate between the assignment step and the update step until the centroids no longer change significantly, or a maximum number of iterations is reached, or some other stop condition. The bug I noticed that a weird behaviour happens if we encounter an empty cluster in a very particular condition. If a cluster becomes empty during the execution of the k-means algorithm, it typically indicates a scenario where no data points are assigned to that cluster. This situation can occur due to several reasons, but mainly poor initialization and data distribution. In scikit-learn, to solve the problem of empty clusters, they decided to associate at least one data point to each empty cluster, and they were relocating data points that are far away from their centroids to do that job. The idea is good, but there was no check that the algorithm is not generating an empty cluster when trying to fill an empty cluster. And I noticed that if the following three conditions are satisfied, the algorithm gets stuck in an infinite loop: There are duplicate input data points. The number of clusters is equal to the total number of data points. Very specific initial positions. Here is a minimal example that reproduced the bug: A example that reproduced the bug. Green marks the input data, blue the cluster centres — note the duplicated points sharing coordinates. The fix There are several strategies to address this issue: Ensure that the relocation algorithm does not create an empty cluster when attempting to fill one. In certain cases, it may be beneficial to remove duplicate points. Reduce the number of clusters used if the specified number of clusters exceeds the number of unique input data points. You can learn more about the strategy that was finally used by reading the discussion of the resolved issue: scikit-learn#28055. To propose a fix for an open-source algorithm in general, one way is to open an issue in their GitHub repo. Scikit-learn did a good job documenting how you should do it: scikit-learn contributing. I followed the steps, and I opened an issue that was resolved quickly, in my opinion. The lessons The dangerous edge case was not an input the author failed to imagine. It was the repair code itself, which had never been run against the situation it creates. Reporting a bug well is most of the contribution. A reproducible case and the three conditions that produce it is what let someone else fix this in days. Sources scikit-learn — KMeans documentation scikit-learn — issue #28055, the bug and its discussion scikit-learn — contributing guide Wikipedia — Bell Labs, where Lloyd proposed the algorithm in 1957 -------------------------------------------------------------------------------- Windows Memory Management in Depth: Private Data, Stack, Heap and Mapped Files https://ibraverse.ca/tech/windows-memory-management-deep-dive/ 2024-01-10 · C++, Windows, Systems -------------------------------------------------------------------------------- A detailed walk through Windows memory states and memory types — private data, stack, heap, mapped files, images — with VirtualAlloc, File Mapping and std::vector examples. If you don’t want to be embarrassed due to your program running out of virtual memory — which could cause many passengers to miss their flights — perhaps you should take a 5-minute break and read this article on how Windows manages virtual memory for processes, and the tools you can use to monitor it. Running out of virtual memory is not always a quiet failure. Before reading this article, I strongly recommend reading the first article that discusses the basics of Windows virtual memory management: Windows Memory Management: Virtual vs Physical, and How to Measure It. I need to mention again that this is a very complex subject, extending even beyond my knowledge. For those who are curious and want to understand every bit, I recommend reading the book Windows Internals by Mark Russinovich. When you use the VMMap tool from Windows Sysinternals, you can take a snapshot that shows the usage of virtual memory within a process to a fine level of detail: Windows VMMap snapshot. Let’s refer to each column as a memory state and each row as a memory type. In this article, I will first provide a brief introduction to the different memory states, and then I will explain each memory type in detail with examples. Memory states Each page in a process virtual address space can be in different states. Let’s try to define each one of them briefly: Size, Reserved — total amount of memory reserved by the memory manager for the process. Committed — reserved memory that is actually in use, and when accessed, ultimately translates to valid pages in physical memory (either RAM or the paging file). Private — committed memory that is private, meaning it is only used by the owner process. This is what we refer to when we talk about private bytes. Total Workspace (WS) — committed memory that is in physical RAM. Private WS — memory that is in RAM and only used by the owner process. In Windows each process has its own private working set. Shareable WS — memory that is in RAM and can be shared between processes. Shared WS — memory that is in RAM and currently being shared between processes. Note that memory that is committed but not in the working set means that it is in the paging file (on disk), and the Memory Manager will copy it into RAM if a process needs it. Memory types Each row of the VMMap snapshot above is a type of memory. The rest of this article takes them one at a time: what each one represents, and how it gets allocated. 1 — Private data We start with private data because it is the memory allocated and used only by the owner process and cannot be shared between processes. It is also the main reason for most memory leaks and fragmentation. You can allocate it using the VirtualAlloc API, which is the lowest-level memory allocation API available with the Windows Memory Manager. In this example, we demonstrate how to use the VirtualAlloc function to reserve and commit memory: // Example 1 : Allocate memory without specifying the base address LPVOID MemPtr1 = VirtualAlloc(NULL, size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); // Example 2 : Allocate memory at the specified address LPVOID desiredAddress = (LPVOID)0x00400000; LPVOID MemPtr2 = VirtualAlloc(desiredAddress, size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); The first parameter of the VirtualAlloc function is the base address of the memory block you want to allocate, and NULL means that the Memory Manager will decide. Choosing the address yourself is occasionally what you want, and usually not — the Memory Manager has more information about the address space than you do. Important notes about VirtualAlloc: If you try to reserve memory that is already reserved, the operation will fail. If you try to use memory that is reserved but not committed, the operation will also fail. Allocated memory is always initialized with 0 for Windows security reasons. A snapshot from Visual Studio confirms that. Allocated memory using the VirtualAlloc function is always initialized. Committing memory does not necessarily mean that it is actually in RAM. Committed memory means that Windows guarantees it will be able to provide you with RAM when needed. You can force the Memory Manager to allocate RAM by using the memory (touching it), as demonstrated in this example: // 1 for (int i = 0; i < 100; i++) *((int*)(MemPtr) + i) = i; // 2 for (int i = 0; i < 100000000; i++) *((int*)(MemPtr) + i) = i; // 3 for (int i = 0; i < 200000000; i++) *((int*)(MemPtr) + i) = i; After each for loop (1, 2, and 3), we observe the growth of the working set — and consequently the used RAM — of the process each time we access more memory. It’s important to note that the reserved and committed memory do not change by only using memory, because the memory was allocated and committed in advance. The process working set grows when memory is used. Query private memory. You can determine if a specific address is private by using the VirtualQuery function and checking if the data member type of the output structure MEMORY_BASIC_INFORMATION is equal to MEM_PRIVATE. Code available on GitHub: https://github.com/brmel/MemoryTracer 2 — Stack The stack is a special private memory allocated by the Memory Manager for each thread, used to store function parameters, local variables, and so on. When a thread is created, the memory manager automatically reserves a predetermined amount of virtual memory, typically 1 MB by default. This amount can be configured when creating the thread. When the Memory Manager allocates the stack, memory is initially only reserved, and it is later committed only when needed. To illustrate stack memory committing, in this example we allocate a static array with different sizes in each case (1, 2, and 3). // 1 const SIZE_T size = 100; int Buffer[size]; // 2 const SIZE_T size = 100000; int Buffer[size]; // 3 const SIZE_T size = 200000; int Buffer[size]; We can observe how the committed memory size of the stack increases: Stack committed memory grows only when needed. When you allocate a thread dynamically at run time, its stack is also created at runtime. Query stack memory. To determine if a specific virtual address is in the stack, you can start by checking if the address is MEM_PRIVATE (refer to private memory above). If it is the case, you can then collect the base address for each stack of each thread using Windows functions such as OpenThread and GetThreadContext. Next, you can check whether it belongs to one of the thread’s stacks. Code available on GitHub: https://github.com/brmel/MemoryTracer 3 — Heap Windows aligns each region of reserved process address space to begin on an integral boundary defined by the value of the system allocation granularity; this value is 64 KB. Most applications allocate smaller blocks than the 64 KB minimum allocation granularity possible using page-granularity functions such as VirtualAlloc. Allocating such a large area for relatively small allocations is not optimal from a memory usage and performance standpoint. To address this, Windows provides a component called the heap manager, which manages allocations inside larger memory areas reserved using the page-granularity memory-allocation functions. The allocation granularity in the heap manager is relatively small: 8 bytes on 32-bit systems, and 16 bytes on 64-bit systems. The heap manager has been designed to optimize memory usage and performance in the case of these smaller allocations. Heap API layer. You can create many heaps for a process using the HeapCreate function, and you can use the HeapWalk function to enumerate the allocated memory blocks in a specified heap. Note. I didn’t find an easy way to determine if a virtual address is in the heap, except by enumerating all heap blocks and checking if the specific address is within one of the blocks. Example with C++ STL containers. When you call the reserve memory function of a std::vector container, memory is both reserved and committed in the heap. In this example, we can observe how both the reserve and resize functions actually reserve and commit memory. // 1 std::vector Vector1; Vector1.reserve(10); // 2 std::vector Vector2; Vector2.reserve(10000000); // 3 std::vector Vector3; Vector3.resize(10000000); We can observe that both reserve and resize functions of a std::vector are reserving and committing memory: Both reserve and resize functions of std::vector commit memory. 4 — Mapped files A mapped file is a file that has been mapped into virtual memory such that it looks like it has been loaded into memory. The operating system will then transparently load parts of the file into physical memory as the application accesses them, and release them again if not needed anymore. The API that is provided by Windows is called the File Mapping API, and it is the same API used to share memory between processes. Raymond Chen said: don’t forget that CreateFileMapping is used for creating both memory-mapped files and for creating plain old shared memory. The name of the function is misleading. Windows memory management API layers. Shared memory can be defined as memory that is visible to more than one process, or that is present in more than one process virtual address space. The underlying primitives in the memory manager used to implement shared memory are called section objects. A section object can be connected to an open file on disk (called a mapped file) or to committed memory (to provide shared memory). In this example I will use the File Mapping API to modify the content of this file: Text file before file memory mapping. The algorithm steps are easy to follow: HANDLE hFile = CreateFile( L"C:\\testfile.txt", // File name GENERIC_READ | GENERIC_WRITE, // Desired access 0, // Share mode (0 for no sharing) NULL, // Security attributes OPEN_ALWAYS, // Open existing or create new FILE_ATTRIBUTE_NORMAL, // File attributes NULL // Template file ); // Get the size of the file DWORD fileSize = GetFileSize(hFile, NULL); // Create a file mapping object HANDLE hMapping = CreateFileMapping( hFile, // File handle NULL, // Security attributes PAGE_READWRITE, // Protection mode 0, // High-order DWORD of the maximum size fileSize, // Low-order DWORD of the maximum size NULL // Name of the file mapping object ); // Map the file into memory LPVOID pMappedData = MapViewOfFile( hMapping, // File mapping handle FILE_MAP_WRITE, // Access mode (write) 0, // High-order DWORD of the file offset 0, // Low-order DWORD of the file offset fileSize // Number of bytes to map (entire file) ); // Modify the content in the memory-mapped file char* pData = static_cast(pMappedData); const char* newText = "My age is 30"; memcpy(pData, newText, strlen(newText)); // Unmap the file and clean up UnmapViewOfFile(pMappedData); CloseHandle(hMapping); CloseHandle(hFile); After running this code, the text file is successfully modified: Text file after file memory mapping. 5 — Image Images refer to DLLs and executables (.exe) that are loaded into memory. They can be shared between processes. When an image is loaded, the memory space is divided into many sub-regions with different protections (read, write, execute, copy-on-write, and so on). Memory sub-regions of a DLL loaded into a process virtual address space. When the data of an image is intended to be shared between processes and cannot be modified, it is marked as read. If the data can be modified by a specific process, it is marked as copy-on-write. In this example, page 2 is marked as copy-on-write. Therefore, when Process B wants to modify it, the memory manager creates a copy of page 2 accessed only by Process B and marks it as read-write. Pages marked as copy-on-write are copied only when modified by the process, to optimize memory. A mystery. It remains unclear to me how to explain why certain pages of an image that are marked as read end up in the private working set and are effectively added to the private bytes counter. I couldn’t find a definitive answer to this question, even after consulting the book. Memory pages of a DLL marked as private, even when they are marked as read-only. 6 — Unused regions When allocating a block of memory, the specified allocation base address is rounded down to the nearest multiple of the allocation granularity, which is currently 64 KB on Windows. This results in some regions that cannot be used. For 32-bit machines, unused regions caused a significant issue as an application could potentially run out of virtual memory due to memory fragmentation. However, for 64-bit machines this is no longer a concern, because the virtual memory space is very large — encompassing approximately 17.4 million terabytes of memory. 7 — Managed heap Managed heap is memory allocated and managed by .NET’s garbage collector. It will not be discussed in this article. Memory Tracer I introduced in the previous article the Memory Tracer project, an open-source C++ library designed to assist you in monitoring the virtual memory of a specific process over time. It allows you to display data on the screen, export well-formatted data to a file, and provides control over the snapshot frequency. Memory Tracer showing the same results as VMMap. Memory Tracer attempts to reproduce the exact results as VMMap. However, there is no guarantee of accuracy: VMMap appears to query virtual memory through interfaces that are not public, so the two will not always agree. Future improvements: There is no heap tracing, so all data in the heap will be in the private data row. If you know an efficient way to traverse the heap and determine if a virtual address is within the heap, we can discuss it — because I am very curious. We should avoid allocating memory when taking a snapshot and exporting data, to prevent influencing the process being examined. There are some pages marked as private data in VMMap, but the reasons are unclear. For instance, there are pages in DLLs with the M_IMAGE flag and protection M_READ, suggesting they should be shareable, but they appear as private. If you have any suggestions, or just want to chat about memory or C++, please feel free to reach out to me. Sources Mark Russinovich — Windows Internals, the book Microsoft — VMMap Microsoft — File Mapping API Microsoft — Creating Named Shared Memory Microsoft — .NET garbage collection fundamentals Raymond Chen — Why is address space allocation granularity 64KB? Raymond Chen — Trying to allocate the same virtual address in multiple processes Raymond Chen — Creating a shared memory block that can grow in size MemoryTracer on GitHub -------------------------------------------------------------------------------- Windows Memory Management: Virtual vs Physical, and How to Measure It https://ibraverse.ca/tech/windows-memory-management-overview/ 2023-11-29 · C++, Windows, Systems -------------------------------------------------------------------------------- An introduction to how the Windows Memory Manager separates virtual from physical memory, and a C++ tool for tracking a process's usage over time. Here is how hard it is to say how much memory a process is using: the people who build Windows have not agreed on it. Task Manager has labelled the same quantities differently across versions, and the Sysinternals team criticised one of those choices publicly. I came to this from C++ and an electrical engineering background, where the question of what the hardware is actually doing is not academic. After Mark Russinovich’s talks on Virtual Memory and Physical Memory management in Windows I wanted two things: to be able to state the difference precisely, and to watch a process’s usage over time instead of sampling it in a tool. This article is the first; the tool at the end is the second. How Task Manager labelled memory across Windows versions. Committed and reserved Suppose you need to allocate a portion of memory on Windows. If you are using C++, you can achieve this using the new keyword, for instance. Windows, if possible, will then provide you with a pointer to the allocated memory that you can use in your program. The Memory Manager is responsible for handling this task, allowing you to specify certain characteristics of the allocated memory. For example, you can determine whether it should be committed immediately or reserved for future use. Committed implies that the memory is ready for immediate use and is backed by physical memory (real hardware). On the other hand, reserved indicates that the memory is spoken for but not yet backed by anything physical; this memory is purely virtual. Virtual and physical memory Virtual and physical memory are important keywords. Let’s try to define them: Physical memory, often referred to as RAM (Random Access Memory), is the actual hardware component in a computer that stores data that is actively being used or processed by the CPU. Virtual memory refers to the address space that applications can use. In a 64-bit system, the virtual memory address space can theoretically address up to 2^64 (~17.4 million TB of memory). However, Windows 64-bit currently supports only ~16 TB. This separation gives more flexibility to Windows and the Memory Manager. When Windows runs out of RAM, it uses your storage drive as extra space, allowing it to smoothly handle multiple tasks and big programs without getting overwhelmed. Virtual address space, mapped by the Memory Manager onto physical RAM and the page file. Processes don’t have direct control over physical memory. The Memory Manager handles the mapping of virtual memory to physical memory and makes decisions about where to place data in physical RAM based on system demands and memory management policies. Private and shared The memory you allocate can also have many types, but most importantly, it can be private or shared. Private memory is accessed only by the owner process, while shared memory is like a common area where different processes can share and exchange information. Windows offers highly useful tools for monitoring both virtual and physical memory through Sysinternals tools: VMMap for virtual memory and RAMMap for physical memory. These tools are easy to install and are thoroughly explained in the videos mentioned at the beginning of this article. VMMap by Sysinternals. In the next article, I will discuss in detail every memory type, and how they can be used in real applications, with examples. Your feedback and inputs are very welcome. Debug virtual and physical memory usage VMMap and RAMMap are excellent at what they do, which is capture the state of virtual and physical memory at one moment. What they are not built for is watching that state change: you cannot set the sampling interval, and getting the numbers out in a form you can plot is awkward. A leak that only shows up over an hour is invisible to a snapshot. Introducing MemoryTracker Memory Tracker is a C++ open-source project designed to assist you in monitoring the virtual memory of a specific process over time. It allows you to display data on the screen, export well-formatted data to a file, and provides control over the snapshot frequency. You can integrate it into your code, utilizing it as a callback function to precisely control when to take a snapshot. Alternatively, you can employ it in another process if you prefer not to modify the code of the process you wish to track. This is an example of tracking virtual memory usage of the process with id 32404: // Process id you want to track int Pid = 32404; // Create MemoryTracker instance CSnapshotMngr MyMemTracer(Pid); // Specify the total duration and frequency, in seconds int Duration = 120; int PeriodT = 2; // Export usage for 2 min by taking a snapshot each 2 seconds MyMemTracer.Export(Duration, PeriodT); Virtual memory usage exported using MemoryTracker, regrouped by type. The same export over time — the view VMMap’s snapshots cannot give you. Private bytes of process usage exported using MemoryTracker, regrouped by memory type. I am very open to all your feedback and inputs. Thank you! — Brahim Sources Mark Russinovich — Virtual Memory (video) Mark Russinovich — Physical Memory management in Windows (video) Microsoft — Sysinternals Microsoft — VMMap, the virtual memory map Microsoft — RAMMap, the physical memory map -------------------------------------------------------------------------------- Auberge Estonia: A Nordic Spa Cabin Escape in Rawdon, One Hour from Montréal https://ibraverse.ca/adventures/auberge-estonia-rawdon/ 2026-05-31 · Québec, Spa, Getaway -------------------------------------------------------------------------------- A forest auberge in Rawdon, Lanaudière — free Nordic spa with hot tubs and a Finnish sauna, a cold-plunge lake, BBQ cabins, and hiking trails on the estate, all about an hour from Montréal. An hour from Montréal, tucked in the Rawdon woods: two Nordic hot tubs steaming under the trees, a Finnish sauna, a lake to cold-plunge into, and a BBQ cabin to come back to. Auberge Estonia is the low-key spa weekend that doesn’t cost spa-resort money. The place Auberge Estonia (Estonia Hébergement Nature) sits in the forested hills of Rawdon, in Québec’s Lanaudière region — about one hour from Montréal. It’s small and nature-first: 8 rooms in the inn (each with a private bathroom) and a handful of mini-chalets scattered in the woods. The headline is the Nordic spa, free for guests: two outdoor hot tubs, a Finnish dry sauna, a firepit ringed with Adirondack chairs, and a small lake for the cold plunge — the full Scandinavian hot–cold–rest circuit, open from morning to late evening. Inside there’s a shared kitchen, a big living room with a wood fireplace, hammocks, and a ping-pong table. What I did Ran the thermal circuit on loop — hot tub, then the cold lake, then the fire — which is the whole point of a Nordic spa and somehow never gets old. Between rounds I walked the hiking trails on the estate (forest paths, streams, the lakeshore), then fired up the BBQ at the chalet for dinner instead of going out. It’s a do-nothing-on-purpose kind of place — quiet, wooded, no crowds. Map Auberge Estonia — Rawdon ↗ Recommendations Go for: the free Nordic spa in the forest — hot tubs, Finnish sauna, firepit, and a cold-plunge lake, without paying day-spa prices. Stay in: a mini-chalet if you want privacy + a BBQ and kitchenette; pick an inn room if you’d rather use the shared kitchen and living room. Do the circuit right: hot tub → cold lake/plunge → rest by the fire, a few rounds. Bring sandals, a robe, and a warm layer for between rounds. Self-cater: chalets have a BBQ + kitchenette and the inn has a shared kitchen — bring groceries. Restaurants are ~10 min away in Rawdon village. Nearby: Dorwin Falls (Rawdon’s ~18 m / 60 ft waterfall) and La Source Bains Nordiques are both close — easy add-ons. Best time: summer for lake swims and trails; winter for sauna-to-snow and a lake that can turn into an ice rink. Fall for the foliage. Getting there: 4333 Chemin du Lac Brennan, Rawdon, QC — about 1 hour from Montréal by car. Free outdoor parking + EV charging on site. Book direct: reserve ahead (it’s small and fills up on weekends); estoniahebergement.com / 450-712-6337. -------------------------------------------------------------------------------- Indian Head & Rainbow Falls: The Adirondacks' Best Day Hike https://ibraverse.ca/adventures/indian-head-rainbow-falls/ 2026-05-31 · Adirondacks, Hiking, Day Hike -------------------------------------------------------------------------------- An 11-mile loop to a fjord-like vista over Lower Ausable Lake and a 150-foot waterfall — the most rewarding easy-graded hike in the Adirondack High Peaks. Eleven miles, almost no scrambling, and it ends at a cliff over a lake shaped like a Norwegian fjord — Indian Head & Rainbow Falls is the most bang-for-effort hike I’ve done in the Adirondacks. The place Indian Head is a rock ledge high above Lower Ausable Lake, deep in the Adirondack High Peaks near Keene Valley, NY. The lake below is long, narrow, and walled by mountains on both sides — from the top it reads like a fjord. Add Rainbow Falls, a ~150-foot cascade in a mossy gorge, and a side trip to Fish Hawk Cliffs, and you get three world-class viewpoints in one loop. The land is the Adirondack Mountain Reserve (AMR) — private, but open to hikers through a conservation easement. That comes with rules (below) — read them before you go. What I did I parked at St. Huberts and walked the flat Lake Road from the Ausable Club, then took the Gill Brook Trail, which climbs gently past a chain of small waterfalls. The only real work is the final push to the Indian Head ledge. From there I crossed to Fish Hawk Cliffs for a second angle on the lake, then dropped down to Rainbow Falls before the long, easy walk back on Lake Road. Map Indian Head & Rainbow Falls — Adirondacks ↗ Recommendations Go for: the Indian Head vista over Lower Ausable Lake — one of the best views in the Northeast for relatively little climbing. Do the loop: up via Gill Brook (waterfalls), down via the steep Indian Head trail to Lake Road. Add Fish Hawk Cliffs — it’s 5 minutes away. Reservation (important): AMR requires a free reservation to park/hike May 1 – Oct 31. Book ahead at hikeamr.org — no reservation, no entry. No dogs. Strictly enforced on AMR land. Distance / time: ~10.8–11 mi loop, 6–7 hours with breaks. Long but mostly flat — start early. Budget: free (reservation is free; bring your own food/water — no services on trail). Best time: late spring to mid-October; fall foliage is spectacular. Getting there: trailhead parking at St. Huberts / Ausable Club, Keene Valley, NY. ~2h from Montréal, ~2h from Albany. Bring: 2L+ water, layers (the ledge is windy), real hiking shoes, and a paper/offline map — no cell service. -------------------------------------------------------------------------------- Why Deadlines Slip, Mine Included https://ibraverse.ca/thoughts/estimates-and-the-five-percent/ 2026-08-09 · Data Analysis, Engineering -------------------------------------------------------------------------------- Hydro-Québec's restoration times were right one time in twenty. I recognised the failure immediately, because I make the same one. For fifty-six days I recorded every power outage in Québec, once a minute. Not for a client, not for work — I wanted to know one thing. When Hydro-Québec tells you the power is back at 4 p.m., how often is it back at 4 p.m.? The answer is five percent. Across 16,561 outages and 7,870 readings, the restoration actually landed within an hour of the announced time about one time in twenty. The full analysis has the regional breakdown and the charts. My first reaction was the obvious one. Five percent is terrible. Somebody should fix that. My second reaction took longer, and it is the reason I am writing this instead of leaving the number in a report. I have given that estimate. Not about power lines — about a feature, a fix, a release. Someone asks when it will be done, and I say Thursday. I am not lying. I believe Thursday when I say it. And then a dependency moves, or the bug is not the bug, and Thursday becomes the following Tuesday. The utility is not being dishonest either. It is doing what I do: estimating from the information available at the moment of asking, in front of someone who wants a number and will not accept “I don’t know yet.” That is the part worth sitting with. The bad estimate is not a character flaw. It is a structural consequence of being asked too early. A crew dispatched to a line does not know what it will find until it arrives. Neither do I, most of the time. What I took from the data is not that estimates are useless. It is which estimate is worth trusting. The one given at the moment the ticket opens carries almost no information — that is the five percent. The one given after somebody has actually looked at the problem is a different object entirely, built from evidence instead of hope. Same sentence, same confident tone, completely different thing. So now I try to say which one I am giving. “Thursday, and I have not opened it yet” is a weaker-sounding answer than “Thursday.” It is also the honest one, and it costs nothing to say. Two months of data to arrive at something I could have been told in a sentence. But I would not have believed the sentence. I believed the five percent, because I collected it myself. That is usually how it goes. -------------------------------------------------------------------------------- السيرة الذاتية https://ibraverse.ca/ar/resume/ --------------------------------------------------------------------------------